From e524f7e606950e8186adb302b93e67566d31cfb0 Mon Sep 17 00:00:00 2001 From: George Lemon Date: Thu, 18 Jan 2024 18:09:09 +0200 Subject: [PATCH] update Signed-off-by: George Lemon --- src/tim.nim | 3 - src/tim.nims | 0 src/tim/engine/ast.nim | 56 ++++++++--- src/tim/engine/compilers/html.nim | 155 ++++++++++++++++++++++-------- src/tim/engine/compilers/tim.nim | 5 +- src/tim/engine/logging.nim | 8 ++ src/tim/engine/meta.nim | 0 src/tim/engine/parser.nim | 127 ++++++++++++++++++++++-- src/tim/engine/tokens.nim | 47 ++++++++- 9 files changed, 332 insertions(+), 69 deletions(-) mode change 100644 => 100755 src/tim.nim mode change 100644 => 100755 src/tim.nims mode change 100644 => 100755 src/tim/engine/ast.nim mode change 100644 => 100755 src/tim/engine/compilers/html.nim mode change 100644 => 100755 src/tim/engine/compilers/tim.nim mode change 100644 => 100755 src/tim/engine/logging.nim mode change 100644 => 100755 src/tim/engine/meta.nim mode change 100644 => 100755 src/tim/engine/parser.nim mode change 100644 => 100755 src/tim/engine/tokens.nim diff --git a/src/tim.nim b/src/tim.nim old mode 100644 new mode 100755 index 9d25259..11126c9 --- a/src/tim.nim +++ b/src/tim.nim @@ -201,8 +201,5 @@ when defined napibuild: return %*(x) elif not isMainModule: - import tim/engine/[meta, parser, logging] - import tim/engine/compilers/html - export parser, html, json export meta except TimEngine \ No newline at end of file diff --git a/src/tim.nims b/src/tim.nims old mode 100644 new mode 100755 diff --git a/src/tim/engine/ast.nim b/src/tim/engine/ast.nim old mode 100644 new mode 100755 index 49fc82b..a6ada74 --- a/src/tim/engine/ast.nim +++ b/src/tim/engine/ast.nim @@ -18,7 +18,7 @@ else: type NodeType* = enum - ntInvalid + ntUnknown ntLitInt = "int" ntLitString = "string" @@ -26,17 +26,16 @@ type ntLitBool = "bool" ntLitArray = "array" ntLitObject = "object" - ntArrayStorage = "Array" - ntObjectStorage = "Object" + ntLitFunction = "function" ntVariableDef = "Variable" - ntFunctionDef = "Function" ntAssignExpr = "Assignment" ntHtmlElement = "HtmlElement" ntInfixExpr = "InfixExpression" ntMathInfixExpr = "MathExpression" ntCommandStmt = "CommandStatement" ntIdent = "Identifier" + ntCall = "FunctionCall" ntDotExpr ntBracketExpr ntConditionStmt = "ConditionStatement" @@ -45,6 +44,8 @@ type ntInclude = "Include" ntJavaScriptSnippet = "JavaScriptSnippet" + ntYamlSnippet = "YAMLSnippet" + ntJsonSnippet = "JsonSnippet" CommandType* = enum cmdEcho = "echo" @@ -90,7 +91,7 @@ type HtmlAttributes* = TableRef[string, seq[Node]] ConditionBranch* = tuple[expr: Node, body: seq[Node]] - + FnParam* = tuple[pName: string, pType: NodeType, pImplVal: Node, meta: Meta] Node* {.acyclic.} = ref object case nt*: NodeType of ntHtmlElement: @@ -128,20 +129,29 @@ type fVal*: float of ntLitBool: bVal*: bool - of ntArrayStorage: + of ntLitArray: arrayItems*: seq[Node] - of ntObjectStorage: + of ntLitObject: objectItems*: OrderedTableRef[string, Node] of ntCommandStmt: cmdType*: CommandType cmdValue*: Node of ntIdent: identName*: string + of ntCall: + callIdent*: string + callArgs*: seq[Node] of ntDotExpr: storageType*: StorageType lhs*, rhs*: Node - of ntJavaScriptSnippet: - jsCode*: string + of ntLitFunction: + fnIdent*: string + fnParams*: OrderedTable[string, FnParam] + fnBody*: seq[Node] + fnReturnType*: NodeType + of ntJavaScriptSnippet, ntYamlSnippet, + ntJsonSnippet: + snippetCode*: string of ntInclude: includes*: seq[string] else: discard @@ -363,6 +373,7 @@ proc newNode*(nt: static NodeType): Node = Node(nt: nt) proc newString*(tk: TokenTuple): Node = + ## Create a new string value node result = newNode(ntLitString, tk) result.sVal = tk.value @@ -371,23 +382,44 @@ proc newInteger*(v: int, tk: TokenTuple): Node = result.iVal = v proc newFloat*(v: float, tk: TokenTuple): Node = + ## Create a new float value node result = newNode(ntLitFloat, tk) result.fVal = v proc newBool*(v: bool, tk: TokenTuple): Node = + ## Create a new bool value Node result = newNode(ntLitBool, tk) result.bVal = v +proc newVariable*(varName: string, varValue: Node, meta: Meta): Node = + ## Create a new variable definition Node + result = newNode(ntVariableDef) + result.varName = varName + result.varValue = varvalue + result.meta = meta + proc newVariable*(varName: string, varValue: Node, tk: TokenTuple): Node = + ## Create a new variable definition Node result = newNode(ntVariableDef, tk) result.varName = varName result.varValue = varvalue proc newAssignment*(tk: TokenTuple, varValue: Node): Node = + ## Create a new assignment Node result = newNode(ntAssignExpr, tk) result.asgnIdent = tk.value result.asgnVal = varValue +proc newFunction*(tk: TokenTuple, ident: string): Node = + ## Create a new Function definition Node + result = newNode(ntLitFunction, tk) + result.fnIdent = ident + +proc newCall*(tk: TokenTuple): Node = + ## Create a new function call Node + result = newNode(ntCall) + result.callIdent = tk.value + proc newInfix*(lhs, rhs: Node, infixOp: InfixOp, tk: TokenTuple): Node = result = newNode(ntInfixExpr, tk) result.infixOp = infixOp @@ -418,7 +450,7 @@ proc newCondition*(condIfBranch: ConditionBranch, tk: TokenTuple): Node = proc newArray*(items: seq[Node] = @[]): Node = ## Creates a new `Array` node - result = newNode(ntArrayStorage) + result = newNode(ntLitArray) result.arrayItems = items proc toTimNode*(x: JsonNode): Node = @@ -436,12 +468,12 @@ proc toTimNode*(x: JsonNode): Node = result = newNode(ntLitBool) result.bVal = x.bval of JObject: - result = newNode(ntObjectStorage) + result = newNode(ntLitObject) result.objectItems = newOrderedTable[string, Node]() for k, v in x: result.objectItems[k] = toTimNode(v) of JArray: - result = newNode(ntArrayStorage) + result = newNode(ntLitArray) for v in x: result.arrayItems.add(toTimNode(v)) else: discard diff --git a/src/tim/engine/compilers/html.nim b/src/tim/engine/compilers/html.nim old mode 100644 new mode 100755 index 404f523..176f9c9 --- a/src/tim/engine/compilers/html.nim +++ b/src/tim/engine/compilers/html.nim @@ -12,10 +12,11 @@ import ../ast, ../logging from ../meta import TimEngine, TimTemplate, TimTemplateType, getType, getSourcePath -include ./tim +include ./tim # TimCompiler object type HtmlCompiler* = object of TimCompiler + ## Create a TimCompiler to output to `HTML` when not defined timStandalone: globalScope: ScopeTable = ScopeTable() data: JsonNode @@ -45,9 +46,7 @@ proc getIndent(c: HtmlCompiler, meta: Meta, skipbr = false): string = add result, indent("", c.baseIndent(meta[1])) when not defined timStandalone: - # - # Scope API - # + # Scope API, available for library version of TimEngine proc globalScope(c: var HtmlCompiler, key: string, node: Node) = # Add `node` to global scope c.globalScope[key] = node @@ -59,9 +58,16 @@ when not defined timStandalone: proc stack(c: var HtmlCompiler, key: string, node: Node, scopetables: var seq[ScopeTable]) = # Add `node` to either local or global scope - if scopetables.len > 0: - scopetables[^1][node.varName] = node - return + case node.nt + of ntVariableDef: + if scopetables.len > 0: + scopetables[^1][node.varName] = node + return + of ntLitFunction: + if scopetables.len > 0: + scopetables[^1][node.fnIdent] = node + return + else: discard c.globalScope[key] = node proc getCurrentScope(c: var HtmlCompiler, @@ -99,11 +105,11 @@ when not defined timStandalone: if some.scopeTable != nil: return some.scopeTable[key] - proc newScope(scopetables: var seq[ScopeTable]) = + proc newScope(scopetables: var seq[ScopeTable]) {.inline.} = ## Create a new Scope scopetables.add(ScopeTable()) - proc clearScope(scopetables: var seq[ScopeTable]) = + proc clearScope(scopetables: var seq[ScopeTable]) {.inline.} = ## Clears the current (latest) ScopeTable scopetables.delete(scopetables.high) @@ -187,7 +193,7 @@ proc evalStorage(c: var HtmlCompiler, node: Node): JsonNode = proc walkAccessorStorage(c: var HtmlCompiler, lhs, rhs: Node, scopetables: var seq[ScopeTable]): Node = case lhs.nt - of ntObjectStorage: + of ntLitObject: try: result = lhs.objectItems[rhs.identName] except KeyError: @@ -200,11 +206,12 @@ proc walkAccessorStorage(c: var HtmlCompiler, let x = c.fromScope(lhs.identName, scopetables) if likely(x != nil): result = c.walkAccessorStorage(x.varValue, rhs, scopetables) - of ntArrayStorage: + of ntLitArray: discard # todo handle accessor storage for arrays else: discard proc dotEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = + # Evaluate dot expressions case node.storageType of localStorage, globalStorage: let x = c.evalStorage(node) @@ -214,12 +221,14 @@ proc dotEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTab return c.walkAccessorStorage(node.lhs, node.rhs, scopetables) proc writeDotExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Handle dot expressions let someValue: Node = c.dotEvaluator(node, scopetables) if likely(someValue != nil): add c.output, someValue.toString() c.stickytail = true proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Evaluate a command var val: Node case node.cmdValue.nt of ntIdent: @@ -241,11 +250,10 @@ proc evalCmd(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) else: discard return else: discard - - case node.cmdType - of cmdEcho: - print(val) - else: discard + if val != nil: + case node.cmdType + of cmdEcho: print(val) + else: discard proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, infixOp: InfixOp, scopetables: var seq[ScopeTable]): bool = @@ -358,11 +366,6 @@ proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) return some.scopeTable[node.identName].varValue compileErrorWithArgs(undeclaredVariable, [node.identName]) -# proc calc(c: var HtmlCompiler, lhs, rhs: Node, op: MathOp, var seq[ScopeTable]): Node = -# case infixOp: -# of mPlus: - -# else: discard proc mathInfixEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = ## Evaluates a math expression and returns @@ -397,11 +400,10 @@ proc mathInfixEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[Sc else: discard let + intDefault = ast.newNode(ntLitInt) + strDefault = ast.newNode(ntLitString) boolDefault = ast.newNode(ntLitBool) boolDefault.bVal = true -let - strDefault = ast.newNode(ntLitString) - intDefault = ast.newNode(ntLitInt) template evalBranch(branch: Node, body: untyped) = case branch.nt @@ -477,14 +479,14 @@ proc evalLoop(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) c.varExpr(node.loopItem, scopetables) c.evaluateNodes(node.loopBody, scopetables) clearScope(scopetables) - of ntArrayStorage: + of ntLitArray: for x in items.varValue.arrayItems: newScope(scopetables) node.loopItem.varValue = x c.varExpr(node.loopItem, scopetables) c.evaluateNodes(node.loopBody, scopetables) clearScope(scopetables) - of ntObjectStorage: + of ntLitObject: for x, y in items.varValue.objectItems: newScope(scopetables) node.loopItem.varValue = y @@ -502,6 +504,11 @@ proc typeCheck(c: var HtmlCompiler, x, node: Node): bool = compileErrorWithArgs(typeMismatch, [$(node.nt), $(x.nt)]) result = true +proc typeCheck(c: var HtmlCompiler, node: Node, expect: NodeType): bool = + if unlikely(node.nt != expect): + compileErrorWithArgs(typeMismatch, [$(node.nt), $(expect)]) + result = true + # # Compile Handlers # @@ -512,6 +519,7 @@ proc varExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) else: compileErrorWithArgs(varRedefine, [node.varName]) proc assignExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Handle assignment expressions let some = c.getScope(node.asgnIdent, scopetables) if likely(some.scopeTable != nil): let varNode = some.scopeTable[node.asgnIdent] @@ -521,10 +529,77 @@ proc assignExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable else: compileErrorWithArgs(varImmutable, [varNode.varName]) +proc fnDef(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Handle function definitions + if likely(not c.inScope(node.fnIdent, scopetables)): + if node.fnParams.len > 0: + for k, p in node.fnParams: + if p.pImplVal != nil: + if p.pImplVal.nt != p.pType: + compileErrorWithArgs(typeMismatch, [$(p.pImplVal.nt), $p.pType], p.meta) + # if node.fnReturnType != ntUnknown: + # check if function has a return type + # where tkUnknown acts like a void + + c.stack(node.fnIdent, node, scopetables) + else: compileErrorWithArgs(fnRedefine, [node.fnIdent]) + +proc fnCall(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Handle function calls + let some = c.getScope(node.callIdent, scopetables) + if likely(some.scopeTable != nil): + newScope(scopetables) + let fnNode = some.scopeTable[node.callIdent] + if fnNode.fnParams.len > 0: + # add available param definition + # to current stack + for k, p in fnNode.fnParams: + var x = ast.newVariable(k, p.pImplVal, p.meta) + c.stack(k, x, scopetables) + if node.callArgs.len == fnNode.fnParams.len: + # checking if the number of given args + # is matching the number of parameters + if node.callArgs.len > 0: + var i = 0 + for k, p in fnNode.fnParams: + case node.callArgs[i].nt + of ntIdent: + var valNode = c.getValue(node.callArgs[i], scopetables) + if not c.typeCheck(valNode, p.pType): + return # error > type mismatch + let someParam = c.getScope(k, scopetables) + if likely(someParam.scopeTable != nil): + someParam.scopeTable[k].varValue = valNode + else: + if c.typeCheck(node.callArgs[i], p.pType): + let someParam = c.getScope(k, scopetables) + echo node.callArgs[i] + someParam.scopeTable[k].varValue = node.callArgs[i] + else: return + inc i + elif node.callArgs.len > fnNode.fnParams.len: + compileErrorWithArgs(fnExtraArg, [$(node.callArgs.len), $(fnNode.fnParams.len)]) + elif node.callArgs.len < fnNode.fnParams.len: + # check if function parameters has any implicit values + var i = 0 + for k, p in fnNode.fnParams: + if p.pImplVal != nil: + let someParam = c.getScope(k, scopetables) + someParam.scopeTable[k].varValue = p.pImplVal + 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) + clearScope(scopetables) + else: compileErrorWithArgs(fnUndeclared, [node.callIdent]) + # # Html Handler # proc getId(c: HtmlCompiler, node: Node): string = + # Get ID html attribute add result, indent("id=", 1) & "\"" let attrNode = node.attrs["id"][0] case attrNode.nt @@ -534,6 +609,7 @@ proc getId(c: HtmlCompiler, node: Node): string = add result, "\"" proc getAttrs(c: HtmlCompiler, attrs: HtmlAttributes): string = + # Write HTMLAttributes var i = 0 var skipQuote: bool let len = attrs.len @@ -593,15 +669,18 @@ template htmlblock(x: Node, body) = c.stickytail = false proc htmlElement(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = + # Handle HTML element htmlblock node: c.evaluateNodes(node.nodes, scopetables) proc evaluatePartials(c: var HtmlCompiler, includes: seq[string], scopetables: var seq[ScopeTable]) = + # Evaluate included partials for x in includes: 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]) = + # Evaluate a seq[Node] nodes for i in 0..nodes.high: case nodes[i].nt of ntHtmlElement: @@ -630,6 +709,8 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S of ntLitString, ntLitInt, ntLitFloat, ntLitBool: add c.output, nodes[i].toString c.stickytail = true + of ntLitFunction: + c.fnDef(nodes[i], scopetables) of ntInfixExpr: case nodes[i].infixOp of AMP: @@ -639,12 +720,14 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S # add c.output, c.getIndent(nodes[i].meta) c.head = c.output reset(c.output) + of ntCall: + c.fnCall(nodes[i], scopetables) of ntInclude: c.evaluatePartials(nodes[i].includes, scopetables) of ntJavaScriptSnippet: - add c.jsOutput, nodes[i].jsCode - if not c.jsCodeExists: - c.jsCodeExists = true + add c.jsOutput, nodes[i].snippetCode + of ntJsonSnippet: + add c.jsonOutput, nodes[i].snippetCode else: discard # @@ -705,13 +788,10 @@ proc newCompiler*(ast: Ast, minify = true, indent = 2): HtmlCompiler = proc getHtml*(c: HtmlCompiler): string = ## Get the compiled HTML result = c.output - if c.tplType == ttView: - case c.jsCodeExists - of true: - add result, "\n" & "" - else: discard + if c.tplType == ttView and c.jsOutput.len > 0: + add result, "\n" & "" proc getHead*(c: HtmlCompiler): string = ## Returns the top of a split layout @@ -721,8 +801,7 @@ proc getHead*(c: HtmlCompiler): string = proc getTail*(c: HtmlCompiler): string = ## Retruns the tail of a layout assert c.tplType == ttLayout - case c.jsCodeExists - of true: + if c.jsOutput.len > 0: result = "\n" & "" diff --git a/src/tim/engine/compilers/tim.nim b/src/tim/engine/compilers/tim.nim old mode 100644 new mode 100755 index 42af60d..f24c72f --- a/src/tim/engine/compilers/tim.nim +++ b/src/tim/engine/compilers/tim.nim @@ -3,9 +3,8 @@ type ast: Ast tpl: TimTemplate nl: string = "\n" - output: string - jsOutput: string - jsCodeExists: bool + output, jsOutput, jsonOutput, + yamlOutput, cssOutput: string start: bool case tplType: TimTemplateType of ttLayout: diff --git a/src/tim/engine/logging.nim b/src/tim/engine/logging.nim old mode 100644 new mode 100755 index cd404ae..95f9d2c --- a/src/tim/engine/logging.nim +++ b/src/tim/engine/logging.nim @@ -19,6 +19,9 @@ type undeclaredVariable = "Undeclared variable $" varRedefine = "Attempt to redefine variable $" varImmutable = "Attempt to reassign value to immutable constant $" + fnRedefine = "Attempt to redefine function $" + fnUndeclared = "Undeclared function $" + fnExtraArg = "Extra arguments given. Got $ expected $" badIndentation = "Nestable statement requires indentation" invalidContext = "Invalid $ in this context" invalidViewLoader = "Invalid use of `@view` in this context. Use a layout instead" @@ -160,6 +163,11 @@ template compileErrorWithArgs*(msg: Message, args: openarray[string]) = c.hasErrors = true return +template compileErrorWithArgs*(msg: Message, args: openarray[string], meta: Meta) = + c.logger.newError(msg, meta[0], meta[1], true, args) + c.hasErrors = true + return + template compileErrorWithArgs*(msg: Message) = c.logger.newError(msg, node.meta[0], node.meta[1], true, []) c.hasErrors = true diff --git a/src/tim/engine/meta.nim b/src/tim/engine/meta.nim old mode 100644 new mode 100755 diff --git a/src/tim/engine/parser.nim b/src/tim/engine/parser.nim old mode 100644 new mode 100755 index e9776c8..95a5e35 --- a/src/tim/engine/parser.nim +++ b/src/tim/engine/parser.nim @@ -40,10 +40,14 @@ const tkCompSet = {tkEQ, tkNE, tkGT, tkGTE, tkLT, tkLTE, tkAmp, tkAndAnd} tkMathSet = {tkPlus, tkMinus, tkMultiply, tkDivide} tkAssignableSet = { - tkString, tkBool, tkFloat, tkInteger, - tkIdentVar, tkLC, tkLB + tkString, tkBacktick, tkBool, tkFloat, + tkInteger, tkIdentVar, tkLC, tkLB } tkComparable = tkAssignableSet + tkTypedLiterals = { + tkLitArray, tkLitBool, tkLitFloat, tkLitFunction, + tkLitInt, tkLitObject, tkLitString + } # # Forward Declaration @@ -103,6 +107,11 @@ template expect(kind: TokenKind, body) = body else: return nil +template expect(kind: set[TokenKInd], body) = + if likely(p.curr in kind): + body + else: return nil + proc isIdent(tk: TokenTuple, anyIdent, anyStringKey = false): bool = result = tk is tkIdentifier if result or (anyIdent and tk.kind != tkString): @@ -155,11 +164,29 @@ proc getStorageType(p: var Parser): StorageType = return localStorage result = globalStorage +proc getType(p: var Parser): NodeType = + result = + case p.curr.kind: + of tkLitArray: ntLitArray + of tkLitBool: ntLitBool + of tkLitFloat: ntLitFloat + of tkLitFunction: ntLitFunction + of tkLitInt: ntLitInt + of tkLitObject: ntLitObject + of tkLitString: ntLitString + else: ntUnknown + # # Parse Handlers # prefixHandle pString: - # parse a string + # parse a single/double quote string + result = ast.newString(p.curr) + walk p + +prefixHandle pBacktick: + # parse template literals enclosed by backticks + # todo result = ast.newString(p.curr) walk p @@ -276,7 +303,7 @@ prefixHandle pEchoCommand: else: varNode = p.getPrefixOrInfix() return ast.newCommand(cmdEcho, varNode, tk) - else: discard + else: errorWithArgs(unexpectedToken, p.curr, [p.curr.value]) prefixHandle pReturnCommand: # parse `return` command @@ -324,8 +351,12 @@ proc parseAttributes(p: var Parser, attrs: var HtmlAttributes, el: TokenTuple) { let attrValue = ast.newString(p.curr) attrs[attrKey.value] = @[attrValue] walk p + of tkBacktick: + let attrValue = ast.newString(p.curr) + attrs[attrKey.value] = @[attrValue] + walk p else: - attrs[attrKey.value] = @[] + attrs[attrKey.value] = @[ast.newIdent(p.curr)] else: errorWithArgs(duplicateAttribute, attrKey, [attrKey.value]) else: break # errorWithArgs(invalidAttribute, p.prev, [p.prev.value]) @@ -484,7 +515,7 @@ prefixHandle pFor: prefixHandle pAnoObject: # parse an anonymous object - let anno = ast.newNode(ntObjectStorage, p.curr) + let anno = ast.newNode(ntLitObject, p.curr) anno.objectItems = newOrderedTable[string, Node]() walk p # { while p.curr.isIdent(anyIdent = true, anyStringKey = true) and p.next.kind == tkColon: @@ -534,7 +565,7 @@ prefixHandle pAnoArray: if p.curr is tkComma: walk p expectWalk tkRB - result = ast.newNode(ntArrayStorage, tk) + result = ast.newNode(ntLitArray, tk) result.arrayItems = items proc pAssignable(p: var Parser): Node {.gcsafe.} = @@ -575,7 +606,10 @@ prefixHandle pSnippet: case p.curr.kind of tkSnippetJS: result = ast.newNode(ntJavaScriptSnippet, p.curr) - result.jsCode = p.curr.value + result.snippetCode = p.curr.value + of tkSnippetYaml: + result = ast.newNode(ntYamlSnippet, p.curr) + result.snippetCode = p.curr.value else: discard # elif p.curr.kind == tkSass: # result = ast.newSnippet(p.curr) @@ -593,6 +627,70 @@ prefixHandle pSnippet: # # result.jsonCode = yaml(p.curr.value).toJsonStr walk p +template handleImplicitDefaultValue {.dirty.} = + # handle implicit default value + walk p + let implNode = p.getPrefixOrInfix(includes = tkAssignableSet) + if likely(implNode != nil): + result.fnParams[pName.value].pImplVal = implNode + +prefixHandle pFunction: + # parse a function declaration + let this = p.curr; walk p # tkFN + expect tkIdentifier: # function identifier + result = ast.newFunction(this, p.curr.value) + walk p + expectWalk tkLP + while p.curr isnot tkRP: + case p.curr.kind + of tkIdentifier: + let pName = p.curr + walk p + if p.curr is tkColon: + if likely(result.fnParams.hasKey(pName.value) == false): + walk p # tkColon + case p.curr.kind + of tkTypedLiterals: + let pType = p.getType + result.fnParams[pName.value] = + (pName.value, pType, nil, [p.curr.line, p.curr.pos, p.curr.col]) # todo parse implicit value + walk p + if p.curr is tkAssign: + handleImplicitDefaultValue() + else: break + elif p.curr is tkAssign: + result.fnParams[pName.value] = + (pName.value, ntUnknown, nil, [0, 0, 0]) + handleImplicitDefaultValue() + result.fnParams[pName.value].meta = implNode.meta + else: return nil + walk p # tkRP + if p.curr is tkColon: + walk p + expect tkTypedLiterals: + # set return type + result.fnReturnType = p.getType + walk p + expectWalk tkAssign + while p.curr.isChild(this): + # todo disallow use of html inside a function + let node = p.getPrefixOrInfix() + if likely(node != nil): + add result.fnBody, node + if unlikely(result.fnBody.len == 0): + error(badIndentation, p.curr) + +prefixHandle pFunctionCall: + # parse a function call + result = ast.newCall(p.curr) + walk p, 2 # we know tkLP is next so we'll skip it + while p.curr isnot tkRP: + let argNode = p.getPrefixOrInfix(includes = tkAssignableSet) + if likely(argNode != nil): + add result.callArgs, argNode + else: return nil + walk p # tkRP + # # Infix Main Handlers # @@ -672,19 +770,23 @@ proc getPrefixFn(p: var Parser, excludes, includes: set[TokenKind] = {}): Prefix case p.curr.kind of tkVar, tkConst: pAssignment of tkString: pString + of tkBacktick: pBacktick of tkInteger: pInt of tkFloat: pFloat of tkBool: pBool of tkEchoCmd: pEchoCommand of tkIF: pCondition of tkFor: pFor - of tkIdentifier: pElement + of tkIdentifier: + if p.next is tkLP and p.next.wsno == 0: pFunctionCall + else: pElement of tkIdentVar: pIdentOrAssignment of tkViewLoader: pViewLoader of tkSnippetJS: pSnippet of tkInclude: pInclude of tkLB: pAnoArray of tkLC: pAnoObject + of tkFN: pFunction else: nil proc parsePrefix(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.gcsafe.} = @@ -716,11 +818,16 @@ proc parseRoot(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.g of tkIF: p.pCondition() of tkFor: p.pFor() of tkViewLoader: p.pViewLoader() - of tkIdentifier: p.pElement() + of tkIdentifier: + if p.next is tkLP and p.next.wsno == 0: + p.pFunctionCall() + else: + p.pElement() of tkSnippetJS: p.pSnippet() of tkInclude: p.pInclude() of tkLB: p.pAnoArray() of tkLC: p.pAnoObject() + of tkFN: p.pFunction() else: nil if unlikely(result == nil): let tk = if p.curr isnot tkEOF: p.curr else: p.prev diff --git a/src/tim/engine/tokens.nim b/src/tim/engine/tokens.nim old mode 100644 new mode 100755 index 0f182f9..e2083b2 --- a/src/tim/engine/tokens.nim +++ b/src/tim/engine/tokens.nim @@ -81,13 +81,49 @@ handlers: if lex.next("js"): let pos = lex.getColNumber(lex.bufpos) inc lex.bufpos, 3 - collectSnippet(tkSnippetJS) + collectSnippet(tkSnippetJs) + elif lex.next("yaml"): + let pos = lex.getColNumber(lex.bufpos) + inc lex.bufpos, 5 + collectSnippet(tkSnippetYaml) + elif lex.next("json"): + let pos = lex.getColNumber(lex.bufpos) + inc lex.bufpos, 5 + collectSnippet(tkSnippetJson) elif lex.next("include"): lex.setToken tkInclude, 8 elif lex.next("view"): lex.setToken tkViewLoader, 5 else: discard + proc handleBackticks(lex: var Lexer, kind: TokenKind) = + lex.startPos = lex.getColNumber(lex.bufpos) + setLen(lex.token, 0) + let lineno = lex.lineNumber + inc lex.bufpos + while true: + case lex.buf[lex.bufpos] + of '\\': + lex.handleSpecial() + if lex.hasError(): return + of '`': + lex.kind = kind + inc lex.bufpos + break + of NewLines: + if lex.multiLineStr: + inc lex.bufpos + else: + lex.setError("EOL reached before end of string") + return + of EndOfFile: + lex.setError("EOF reached before end of string") + return + else: + add lex.token, lex.buf[lex.bufpos] + inc lex.bufpos + if lex.multiLineStr: + lex.lineNumber = lineno const toktokSettings = toktok.Settings( @@ -131,6 +167,7 @@ registerTokens toktokSettings: andAnd = '&' pipe = '|': orOr = '|' + backtick = tokenize(handleBackticks, '`') `if` = "if" `elif` = "elif" `else` = "else" @@ -147,10 +184,14 @@ registerTokens toktokSettings: litFloat = "float" litObject = "object" litArray = "array" - + litFunction = "function" + litVoid = "void" + # magics at = tokenize(handleMagics, '@') - snippetjs + snippetJs + snippetYaml + snippetJson viewLoader `include`