diff --git a/src/tim/engine/ast.nim b/src/tim/engine/ast.nim index a6ada74..2615c95 100755 --- a/src/tim/engine/ast.nim +++ b/src/tim/engine/ast.nim @@ -123,6 +123,7 @@ type loopBody*: seq[Node] of ntLitString: sVal*: string + sVals*: seq[Node] of ntLitInt: iVal*: int of ntLitFloat: @@ -138,6 +139,7 @@ type cmdValue*: Node of ntIdent: identName*: string + identSafe*: bool of ntCall: callIdent*: string callArgs*: seq[Node] @@ -417,7 +419,7 @@ proc newFunction*(tk: TokenTuple, ident: string): Node = proc newCall*(tk: TokenTuple): Node = ## Create a new function call Node - result = newNode(ntCall) + result = newNode(ntCall, tk) result.callIdent = tk.value proc newInfix*(lhs, rhs: Node, infixOp: InfixOp, tk: TokenTuple): Node = diff --git a/src/tim/engine/compilers/html.nim b/src/tim/engine/compilers/html.nim index e93dbba..469f5bc 100755 --- a/src/tim/engine/compilers/html.nim +++ b/src/tim/engine/compilers/html.nim @@ -4,12 +4,13 @@ # Made by Humans from OpenPeeps # https://github.com/openpeeps/tim -import std/[tables, strutils, - json, jsonutils, options, terminal] +import std/[tables, strutils, json, + jsonutils, options, terminal] import pkg/jsony import ../ast, ../logging +from std/xmltree import escape from ../meta import TimEngine, TimTemplate, TimTemplateType, getType, getSourcePath @@ -17,17 +18,20 @@ include ./tim # TimCompiler object type HtmlCompiler* = object of TimCompiler - ## Create a TimCompiler to output to `HTML` + ## Object of a TimCompiler to output `HTML` when not defined timStandalone: globalScope: ScopeTable = ScopeTable() data: JsonNode # Forward Declaration -proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[ScopeTable]) +proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], + scopetables: var seq[ScopeTable], parentNode: Node = nil): Node {.discardable.} proc typeCheck(c: var HtmlCompiler, x, node: Node): bool +proc typeCheck(c: var HtmlCompiler, node: Node, expect: NodeType): bool proc mathInfixEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node proc dotEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node - +proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node +proc fnCall(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node proc hasError*(c: HtmlCompiler): bool = c.hasErrors proc baseIndent(c: HtmlCompiler, isize: int): int = @@ -160,7 +164,7 @@ proc dumpHook*(s: var string, v: OrderedTableRef[string, Node]) = s.add("}") -proc toString(node: Node): string = +proc toString(node: Node, escape = false): string = result = case node.nt of ntLitString: node.sVal @@ -172,8 +176,34 @@ proc toString(node: Node): string = of ntLitArray: fromJson(jsony.toJson(node.arrayItems)).pretty else: "" + if escape: + result = xmltree.escape(result) + +proc toString(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable], escape = false): string = + result = + case node.nt + of ntLitString: + if node.sVals.len == 0: + node.sVal + else: + var concat: string + for concatNode in node.sVals: + let x = c.getValue(concatNode, scopetables) + if likely(x != nil): + add concat, x.sVal + node.sVal & concat + of ntLitInt: $node.iVal + of ntLitFloat: $node.fVal + of ntLitBool: $node.bVal + of ntLitObject: + fromJson(jsony.toJson(node.objectItems)).pretty + of ntLitArray: + fromJson(jsony.toJson(node.arrayItems)).pretty + else: "" + if escape: + result = xmltree.escape(result) -proc toString(node: JsonNode): string = +proc toString(node: JsonNode, escape = false): string = result = case node.kind of JString: node.str @@ -183,13 +213,13 @@ proc toString(node: JsonNode): string = of JObject, JArray: $(node) else: "null" -proc toString(value: Value): string = +proc toString(value: Value, escape = false): string = result = case value.kind of jsonValue: - value.jVal.toString() + value.jVal.toString(escape) of nimValue: - value.nVal.toString() + value.nVal.toString(escape) proc print(val: Node) = let meta = " ($1:$2) " % [$val.meta[0], $val.meta[2]] @@ -270,7 +300,7 @@ proc writeDotExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTab add c.output, someValue.toString() c.stickytail = true -proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = +proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = # Evaluate a command var val: Node case node.cmdValue.nt @@ -284,6 +314,8 @@ proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) val = node.cmdValue of ntMathInfixExpr: val = c.mathInfixEvaluator(node.cmdValue, scopetables) + of ntCall: + val = c.fnCall(node.cmdValue, scopetables) of ntDotExpr: let someValue: Node = c.dotEvaluator(node.cmdValue, scopetables) if likely(someValue != nil): @@ -295,7 +327,11 @@ proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) else: discard if val != nil: case node.cmdType - of cmdEcho: print(val) + of cmdEcho: + val.meta = node.cmdValue.meta + print(val) + of cmdReturn: + return val else: discard proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, @@ -402,13 +438,47 @@ proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, else: discard # todo else: discard # todo -proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = - # Evaluates an identifier node - let some = c.getScope(node.identName, scopetables) - if likely(some.scopeTable != nil): - return some.scopeTable[node.identName].varValue - compileErrorWithArgs(undeclaredVariable, [node.identName]) +proc getValues(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): seq[Node] = + # lhs + case node.infixLeft.nt + of ntDotExpr: + add result, c.dotEvaluator(node.infixLeft, scopetables) + of ntAssignableSet: + add result, node.infixLeft + of ntInfixExpr: + add result, c.getValue(node.infixLeft, scopetables) + else: discard + # rhs + case node.infixRight.nt + of ntDotExpr: + add result, c.dotEvaluator(node.infixRight, scopetables) + of ntAssignableSet: + add result, node.infixRight + of ntInfixExpr: + add result, c.getValue(node.infixRight, scopetables) + of ntMathInfixExpr: + let someValue = c.mathInfixEvaluator(node.infixRight, scopetables) + if likely(someValue != nil): + add result, someValue + else: discard +proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = + # Get a literal node from ntIdent, ntDotExpr, ntCall, ntInfixExpr + case node.nt + of ntIdent: + let some = c.getScope(node.identName, scopetables) + if likely(some.scopeTable != nil): + return some.scopeTable[node.identName].varValue + compileErrorWithArgs(undeclaredVariable, [node.identName]) + of ntInfixExpr: + case node.infixOp + of AMP: + result = ast.newNode(ntLitString) + let vNodes: seq[Node] = c.getValues(node, scopetables) + for vNode in vNodes: + add result.sVal, vNode.sVal + else: discard # todo + else: discard proc mathInfixEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = ## Evaluates a math expression and returns @@ -442,6 +512,7 @@ proc mathInfixEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[Sc else: discard else: discard +# define default value nodes let intDefault = ast.newNode(ntLitInt) strDefault = ast.newNode(ntLitString) @@ -549,7 +620,8 @@ proc evalLoop(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) let items = c.dotEvaluator(node.loopItems, scopetables) if likely(items != nil): loopEvaluator(items) - else: compileErrorWithArgs(undeclaredVariable, [node.loopItems.identName]) + else: + compileErrorWithArgs(undeclaredVariable, [node.loopItems.lhs.identName]) else: return proc typeCheck(c: var HtmlCompiler, x, node: Node): bool = @@ -572,6 +644,14 @@ proc checkObjectStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[Sc of ntIdent: var valNode = c.getValue(v, scopetables) if likely(valNode != nil): + # todo something with safe var + # if v.identSafe: + # v = valNode + # case v.nt + # of ntLitString: + # v.sVal = xmltree.escape(v.sVal) + # else: discard + # else: v = valNode else: return else: discard @@ -629,7 +709,8 @@ proc fnDef(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = c.stack(node.fnIdent, node, scopetables) else: compileErrorWithArgs(fnRedefine, [node.fnIdent]) -proc fnCall(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = +proc fnCall(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable]): Node = # Handle function calls let some = c.getScope(node.callIdent, scopetables) if likely(some.scopeTable != nil): @@ -674,9 +755,11 @@ proc fnCall(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = else: compileErrorWithArgs(typeMismatch, ["none", $p.pType], p.meta) inc i - # todo find a way to cache the results of - # this function call - c.evaluateNodes(fnNode.fnBody, scopetables) + result = c.evaluateNodes(fnNode.fnBody, scopetables) + if result != nil: + if unlikely(not c.typeCheck(result, fnNode.fnReturnType)): + clearScope(scopetables) + return nil clearScope(scopetables) else: compileErrorWithArgs(fnUndeclared, [node.callIdent]) @@ -704,7 +787,7 @@ proc getAttrs(c: var HtmlCompiler, attrs: HtmlAttributes, scopetables: var seq[S for attrNode in attrNodes: case attrNode.nt of ntAssignableSet: - add attrStr, attrNode.toString() + add attrStr, c.toString(attrNode, scopetables) of ntIdent: let x = c.getValue(attrNode, scopetables) if likely(x != nil): @@ -773,7 +856,8 @@ proc evaluatePartials(c: var HtmlCompiler, includes: seq[string], scopetables: v if likely(c.ast.partials.hasKey(x)): c.evaluateNodes(c.ast.partials[x][0].nodes, scopetables) -proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[ScopeTable]) = +proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], + scopetables: var seq[ScopeTable], parentNode: Node = nil): Node {.discardable.} = # Evaluate a seq[Node] nodes for i in 0..nodes.high: case nodes[i].nt @@ -782,7 +866,7 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S of ntIdent: let x = c.getValue(nodes[i], scopetables) if likely(x != nil): - add c.output, x.toString + add c.output, x.toString(nodes[i].identSafe) c.stickytail = true of ntDotExpr: let someValue: Node = c.dotEvaluator(nodes[i], scopetables) @@ -792,11 +876,14 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S of ntVariableDef: c.varExpr(nodes[i], scopetables) of ntCommandStmt: - c.evalCmd(nodes[i], scopetables) + case nodes[i].cmdType + of cmdReturn: + return c.evalCmd(nodes[i], scopetables) + else: + discard c.evalCmd(nodes[i], scopetables) of ntAssignExpr: c.assignExpr(nodes[i], scopetables) of ntConditionStmt: - # echo nodes[i] c.evalCondition(nodes[i], scopetables) of ntLoopStmt: c.evalLoop(nodes[i], scopetables) @@ -815,7 +902,7 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S c.head = c.output reset(c.output) of ntCall: - c.fnCall(nodes[i], scopetables) + echo c.fnCall(nodes[i], scopetables) of ntInclude: c.evaluatePartials(nodes[i].includes, scopetables) of ntJavaScriptSnippet: diff --git a/src/tim/engine/parser.nim b/src/tim/engine/parser.nim index b1713a5..462e160 100755 --- a/src/tim/engine/parser.nim +++ b/src/tim/engine/parser.nim @@ -11,10 +11,6 @@ from std/os import `/` import ./meta, ./tokens, ./ast, ./logging import pkg/kapsis/cli -# from ./meta import TimEngine, TimTemplate, TimTemplateType, getType, -# getTemplateByPath, getSourcePath, setViewIndent, jitEnable, -# addDep, hasDep, getDeps - import pkg/importer type @@ -41,7 +37,7 @@ const tkMathSet = {tkPlus, tkMinus, tkMultiply, tkDivide} tkAssignableSet = { tkString, tkBacktick, tkBool, tkFloat, - tkInteger, tkIdentVar, tkLC, tkLB + tkInteger, tkIdentVar, tkIdentVarSafe, tkLC, tkLB } tkComparable = tkAssignableSet tkTypedLiterals = { @@ -63,6 +59,8 @@ proc pAnoArray(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.g proc pAnoObject(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.gcsafe.} proc pAssignable(p: var Parser): Node {.gcsafe.} +proc pFunctionCall(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.gcsafe.} + template caseNotNil*(x: Node, body): untyped = if likely(x != nil): body @@ -184,6 +182,10 @@ prefixHandle pString: result = ast.newString(p.curr) walk p +prefixHandle pStringConcat: + walk p # tkAmp + return p.getPrefixOrInfix() + prefixHandle pBacktick: # parse template literals enclosed by backticks # todo @@ -268,7 +270,9 @@ prefixHandle pIdentOrAssignment: let varValue = p.getPrefixOrInfix() caseNotNil varValue: return ast.newAssignment(ident, varValue) - return p.pIdent() + result = p.pIdent() + if result.nt == ntIdent: + result.identSafe = ident.kind == tkIdentVarSafe prefixHandle pAssignment: # parse assignment @@ -295,7 +299,7 @@ prefixHandle pEchoCommand: var varNode: Node case p.curr.kind of tkAssignableSet: - if p.curr is tkIdentVar: + if p.curr in {tkIdentVar, tkIdentVarSafe}: if p.next.isInfix: varNode = p.getPrefixOrInfix() else: @@ -303,6 +307,12 @@ prefixHandle pEchoCommand: else: varNode = p.getPrefixOrInfix() return ast.newCommand(cmdEcho, varNode, tk) + of tkIdentifier: + if p.next is tkLP and p.next.wsno == 0: + varNode = p.pFunctionCall() + return ast.newCommand(cmdEcho, varNode, tk) + else: + return nil else: errorWithArgs(unexpectedToken, p.curr, [p.curr.value]) prefixHandle pReturnCommand: @@ -344,17 +354,24 @@ proc parseAttributes(p: var Parser, attrs: var HtmlAttributes, el: TokenTuple) { if anyAttrIdent(): let attrKey = p.curr walk p - if p.curr is tkAssign: walk p + if p.curr is tkAssign: + walk p + # else: return if not attrs.hasKey(attrKey.value): case p.curr.kind of tkString: let attrValue = ast.newString(p.curr) attrs[attrKey.value] = @[attrValue] walk p + if p.curr is tkAmp: + while p.curr is tkAmp: + let attrValue = p.pStringConcat() + if likely(attrValue != nil): + attrs[attrKey.value][^1].sVals.add(attrValue) of tkBacktick: let attrValue = ast.newString(p.curr) attrs[attrKey.value] = @[attrValue] - walk p + walk p else: let x = p.pIdent() if likely(x != nil): @@ -670,15 +687,17 @@ prefixHandle pFunction: if p.curr is tkColon: walk p expect tkTypedLiterals: - # set return type + # set a return type result.fnReturnType = p.getType walk p - expectWalk tkAssign + expectWalk tkAssign # begin function body while p.curr.isChild(this): - # todo disallow use of html inside a function + # todo disallow use of html inside a function + # todo cleanup parser code and make use of includes/excludes let node = p.getPrefixOrInfix() if likely(node != nil): add result.fnBody, node + else: return nil if unlikely(result.fnBody.len == 0): error(badIndentation, p.curr) @@ -780,15 +799,18 @@ proc getPrefixFn(p: var Parser, excludes, includes: set[TokenKind] = {}): Prefix of tkIF: pCondition of tkFor: pFor of tkIdentifier: - if p.next is tkLP and p.next.wsno == 0: pFunctionCall - else: pElement - of tkIdentVar: pIdentOrAssignment + if p.next is tkLP and p.next.wsno == 0: + pFunctionCall # function call by ident + else: + pElement # parse HTML element + of tkIdentVar, tkIdentVarSafe: pIdentOrAssignment of tkViewLoader: pViewLoader of tkSnippetJS: pSnippet of tkInclude: pInclude of tkLB: pAnoArray of tkLC: pAnoObject of tkFN: pFunction + of tkReturnCmd: pReturnCommand else: nil proc parsePrefix(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.gcsafe.} = @@ -815,8 +837,9 @@ proc parseRoot(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.g case p.curr.kind of tkVar,tkConst: p.pAssignment() of tkEchoCmd: p.pEchoCommand() - of tkReturnCmd: p.pReturnCommand() - of tkIdentVar: p.pIdentOrAssignment() + # of tkReturnCmd: p.pReturnCommand() + of tkIdentVar, tkIdentVarSafe: + p.pIdentOrAssignment() of tkIF: p.pCondition() of tkFor: p.pFor() of tkViewLoader: p.pViewLoader() diff --git a/src/tim/engine/tokens.nim b/src/tim/engine/tokens.nim index e2083b2..dd0706c 100755 --- a/src/tim/engine/tokens.nim +++ b/src/tim/engine/tokens.nim @@ -37,6 +37,10 @@ handlers: proc handleVar(lex: var Lexer, kind: TokenKind) = lexReady lex inc lex.bufpos + var isSafe: bool + if lex.current == '$': + isSafe = true + inc lex.bufpos case lex.buf[lex.bufpos] of IdentStartChars: add lex @@ -50,7 +54,10 @@ handlers: else: break else: discard - lex.kind = kind + if not isSafe: + lex.kind = kind + else: + lex.kind = tkIdentVarSafe if lex.token.len > 255: lex.setError("Identifier name is longer than 255 characters") @@ -200,4 +207,5 @@ registerTokens toktokSettings: `const` = "const" returnCmd = "return" echoCmd = "echo" - identVar = tokenize(handleVar, '$') \ No newline at end of file + identVar = tokenize(handleVar, '$') + identVarSafe \ No newline at end of file