From 0fbec7af9d5cd35a9bf5dac5d10abe11b31f5bc3 Mon Sep 17 00:00:00 2001 From: George Lemon Date: Sun, 24 Mar 2024 18:03:33 +0200 Subject: [PATCH] update Signed-off-by: George Lemon --- example/templates/partials/ws.timl | 13 +- example/templates/views/index.timl | 17 +- src/tim/engine/ast.nim | 41 +-- src/tim/engine/compilers/html.nim | 398 +++++++++++++++++++---------- src/tim/engine/compilers/tim.nim | 1 + src/tim/engine/parser.nim | 113 +++++--- src/tim/engine/tokens.nim | 5 +- 7 files changed, 384 insertions(+), 204 deletions(-) diff --git a/example/templates/partials/ws.timl b/example/templates/partials/ws.timl index 4147b16..cf27c44 100644 --- a/example/templates/partials/ws.timl +++ b/example/templates/partials/ws.timl @@ -1,6 +1,15 @@ @js { - const watchout = new WebSocket('ws://127.0.0.1:6502/ws'); - watchout.addEventListener('message', () => location.reload()); + function connectWatchoutServer() { + const watchout = new WebSocket('ws://127.0.0.1:6502/ws'); + watchout.addEventListener('message', () => location.reload()); + watchout.addEventListener('close', () => { + setTimeout(() => { + console.log('Watchout WebSocket is closed. Try again...') + connectWatchoutServer() + }, 300) + }) + } + connectWatchoutServer() } @end diff --git a/example/templates/views/index.timl b/example/templates/views/index.timl index ee8e9b1..b89eb30 100644 --- a/example/templates/views/index.timl +++ b/example/templates/views/index.timl @@ -1,9 +1,16 @@ -section.pt-5 > div.container > div.row > div#content-zone.col-lg-7.mx-auto - div.text-center > img src="https://raw.githubusercontent.com/openpeeps/tim/main/.github/timengine.png" alt="Tim Engine" width="200px" - h1.display-4.fw-bold: - "This is Tim 👋 A super fast template engine for cool kids!" - p.mb-4.h4.fw-normal.px-4 style="line-height: 1.8em": +// tips: variables declared at template level +// with default value are known at compile-time + +var logo = "https://raw.githubusercontent.com/openpeeps/tim/main/.github/timengine.png" +var heading = "This is Tim 👋 A super fast template engine for cool kids!" +var lead = // double quote in multi-line strings "Build sleek, dynamic websites and apps in a breeze with Tim Engine's intuitive syntax and powerful features. It's the template engine that keeps up with your creativity." + +section.pt-5 > div.container > div.row > div#content-zone.col-lg-7.mx-auto + div.text-center + img src=$logo alt="Tim Engine" width="200px" + h1.display-4.fw-bold: $heading + p.mb-4.h4.fw-normal.px-4 style="line-height: 1.8em": $lead @include "foot" // include footer \ No newline at end of file diff --git a/src/tim/engine/ast.nim b/src/tim/engine/ast.nim index 8b14141..7a9fd5d 100755 --- a/src/tim/engine/ast.nim +++ b/src/tim/engine/ast.nim @@ -102,6 +102,8 @@ type ConditionBranch* = tuple[expr: Node, body: seq[Node]] FnParam* = tuple[pName: string, pType: NodeType, pImplVal: Node, meta: Meta] Node* {.acyclic.} = ref object + ## Part of the compiler's abstract syntax tree + ## **Important** do not initialize this object directly case nt*: NodeType of ntHtmlElement: tag*: HtmlTag @@ -181,11 +183,12 @@ type # identifier name identSafe*: bool # whether to escape the stored value of `identName` - of ntCall: - callIdent*: string - ## identifier name of a callable function - callArgs*: seq[Node] - ## a sequence of arguments to to pass + identArgs*: seq[Node] + # of ntCall: + # callIdent*: string + # ## identifier name of a callable function + # callArgs*: seq[Node] + # ## a sequence of arguments to to pass of ntDotExpr: storageType*: StorageType ## holds the storage type of a dot expression @@ -211,7 +214,8 @@ type ## if a function has no return type, then `ntUnknown` ## is used as default (void) fnReturnHtmlElement*: HtmlTag - fnFwdDecl*, fnExport*: bool + fnFwdDecl*, fnExport*, fnImportNim*: bool + fnSource*: string of ntJavaScriptSnippet, ntYamlSnippet, ntJsonSnippet: snippetCode*: string @@ -260,23 +264,22 @@ type Meta* = array[3, int] ScopeTable* = TableRef[string, Node] TimPartialsTable* = TableRef[string, (Ast, seq[cli.Row])] - Ast* = object + TimModulesTable* = TableRef[string, Ast] + Ast* {.acyclic.} = ref object src*: string - ## trace the source path + ## the source path of the ast nodes*: seq[Node] ## a seq containing tree nodes partials*: TimPartialsTable - ## other trees resulted from imports + ## AST trees from included partials + modules*: TimModulesTable + ## AST trees from imported modules jit*: bool + ## whether the current AST requires JIT compliation or not -const ntAssignableSet* = {ntLitString, ntLitInt, ntLitFloat, ntLitBool, ntLitObject, ntLitArray} - -# proc add*(x: Node, y: Node) = -# if likely y != nil: -# case x.nt -# of ntStmtList: -# x.stmtList.add(y) -# else: discard +const ntAssignableSet* = + {ntLitString, ntLitInt, ntLitFloat, + ntLitBool, ntLitObject, ntLitArray} proc getInfixOp*(kind: TokenKind, isInfixInfix: bool): InfixOp = result = @@ -533,8 +536,8 @@ proc newFunction*(tk: TokenTuple, ident: string): Node = proc newCall*(tk: TokenTuple): Node = ## Create a new function call Node - result = newNode(ntCall, tk) - result.callIdent = tk.value + result = newNode(ntIdent, tk) + result.identName = tk.value proc newInfix*(lhs, rhs: Node, infixOp: InfixOp, tk: TokenTuple): Node = result = newNode(ntInfixExpr, tk) diff --git a/src/tim/engine/compilers/html.nim b/src/tim/engine/compilers/html.nim index a8a5a7e..d70ebe4 100755 --- a/src/tim/engine/compilers/html.nim +++ b/src/tim/engine/compilers/html.nim @@ -1,14 +1,14 @@ # A super fast template engine for cool kids # -# (c) 2023 George Lemon | LGPL License +# (c) 2024 George Lemon | LGPL License # Made by Humans from OpenPeeps # https://github.com/openpeeps/tim import std/[tables, strutils, json, - jsonutils, options, terminal] + jsonutils, options, terminal, sequtils] import pkg/jsony -import ./tim +import ./tim, ../stdlib from std/xmltree import escape from ../meta import TimEngine, TimTemplate, TimTemplateType, @@ -35,9 +35,15 @@ proc walkNodes(c: var HtmlCompiler, nodes: seq[Node], xel = newStringOfCap(0)): Node {.discardable.} proc typeCheck(c: var HtmlCompiler, x, node: Node, parent: Node = nil): bool -proc typeCheck(c: var HtmlCompiler, node: Node, expect: NodeType, parent: Node = nil): bool -proc mathInfixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, op: MathOp, scopetables: var seq[ScopeTable]): Node -proc dotEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node + +proc typeCheck(c: var HtmlCompiler, node: Node, + expect: NodeType, parent: Node = nil): bool + +proc mathInfixEvaluator(c: var HtmlCompiler, lhs, + rhs: Node, op: MathOp, scopetables: var seq[ScopeTable]): Node + +proc dotEvaluator(c: var HtmlCompiler, + node: Node, scopetables: var seq[ScopeTable]): Node proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, infixOp: InfixOp, scopetables: var seq[ScopeTable]): bool @@ -45,6 +51,9 @@ proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node +proc unsafeCall(c: var HtmlCompiler, node, fnNode: Node, + scopetables: var seq[ScopeTable]): Node + proc fnCall(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node @@ -53,11 +62,13 @@ proc hasError*(c: HtmlCompiler): bool = c.hasErrors # or c.logger.errorLogs.len proc bracketEvaluator(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node -proc baseIndent(c: HtmlCompiler, isize: int): int = +proc baseIndent(c: HtmlCompiler, i: int): int = if c.indent == 2: - int(isize / c.indent) - else: - isize + if c.partialIndent == 0: + int(i / c.indent) + else: + int((c.partialIndent + i) / c.indent) # fixing html indentation inside partials + else: i proc getIndent(c: HtmlCompiler, meta: Meta, skipbr = false): string = case meta[1] @@ -65,6 +76,8 @@ proc getIndent(c: HtmlCompiler, meta: Meta, skipbr = false): string = if not c.stickytail: if not skipbr: add result, c.nl + if c.partialIndent != 0: + add result, indent("", c.baseIndent(meta[1])) else: if not c.stickytail: add result, c.nl @@ -96,10 +109,12 @@ when not defined timStandalone: else: c.globalScope[node.varName] = node of ntFunction: - if scopetables.len > 0: - scopetables[^1][node.fnIdent] = node - else: - c.globalScope[node.fnIdent] = node + if c.ast.src != "std/system": + if scopetables.len > 0: + scopetables[^1][node.fnIdent] = node + else: + c.globalScope[node.fnIdent] = node + else: discard # todo else: discard proc getCurrentScope(c: var HtmlCompiler, @@ -123,7 +138,8 @@ when not defined timStandalone: if likely(c.globalScope.hasKey(key)): result = (c.globalScope, 0) - proc inScope(c: HtmlCompiler, key: string, scopetables: var seq[ScopeTable]): bool = + proc inScope(c: HtmlCompiler, key: string, + scopetables: var seq[ScopeTable]): bool = # Performs a quick search in the current `ScopeTable` if scopetables.len > 0: result = scopetables[^1].hasKey(key) @@ -147,6 +163,16 @@ when not defined timStandalone: scopetables.delete(scopetables.high) except RangeDefect: discard +template notnil(x, body) = + if likely(x != nil): + body + +template notnil(x, body, elseBody) = + if likely(x != nil): + body + else: + elseBody + # define default value nodes let intDefault = ast.newNode(ntLitInt) @@ -157,7 +183,8 @@ boolDefault.bVal = true # # Forward Declaration # -proc varExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) +proc varExpr(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable]) # # AST Evaluators @@ -215,7 +242,8 @@ proc toString(node: Node, escape = false): string = if escape: result = xmltree.escape(result) -proc toString(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable], escape = false): string = +proc toString(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable], escape = false): string = result = case node.nt of ntLitString: @@ -322,15 +350,32 @@ proc walkAccessorStorage(c: var HtmlCompiler, result = lhs.objectItems[rhs.identName] except KeyError: compileErrorWithArgs(undeclaredField, rhs.meta, [rhs.identName]) - else: compileErrorWithArgs(invalidAccessorStorage, rhs.meta, [rhs.toString, $lhs.nt]) + else: compileErrorWithArgs(invalidAccessorStorage, + rhs.meta, [rhs.toString, $lhs.nt]) of ntDotExpr: - let x = c.walkAccessorStorage(lhs.lhs, lhs.rhs, scopetables) - if likely(x != nil): - return c.walkAccessorStorage(x, rhs, scopetables) + let lhs = c.walkAccessorStorage(lhs.lhs, lhs.rhs, scopetables) + notnil lhs: + case rhs.nt + of ntIdent: + rhs.identArgs.insert(lhs, 0) + result = c.fnCall(rhs, scopetables) + rhs.identArgs.del(0) + else: + return c.walkAccessorStorage(lhs, rhs, scopetables) of ntIdent: - let x = c.getValue(lhs, scopetables) - if likely(x != nil): - result = c.walkAccessorStorage(x, rhs, scopetables) + let lhs = c.getValue(lhs, scopetables) + notnil lhs: + case rhs.nt + of ntIdent: + let some = c.getScope(rhs.identName, scopetables) + if likely(some.scopeTable != nil): + rhs.identArgs.insert(lhs, 0) + result = c.fnCall(rhs, scopetables) + rhs.identArgs.del(0) + else: + return c.walkAccessorStorage(lhs, rhs, scopetables) + else: + return c.walkAccessorStorage(lhs, rhs, scopetables) of ntBracketExpr: let lhs = c.bracketEvaluator(lhs, scopetables) if likely(lhs != nil): @@ -341,7 +386,8 @@ proc walkAccessorStorage(c: var HtmlCompiler, try: result = lhs.arrayItems[rhs.iVal] except Defect: - compileErrorWithArgs(indexDefect, lhs.meta, [$(rhs.iVal), "0.." & $(lhs.arrayItems.high)]) + compileErrorWithArgs(indexDefect, lhs.meta, + [$(rhs.iVal), "0.." & $(lhs.arrayItems.high)]) of ntIndexRange: let l = c.getValue(rhs.rangeNodes[0], scopetables) let r = c.getValue(rhs.rangeNodes[1], scopetables) @@ -360,9 +406,11 @@ proc walkAccessorStorage(c: var HtmlCompiler, let someRange = if rhs.rangeLastIndex: $(l) & "..^" & $(r) else: $(l) & ".." & $(r) - compileErrorWithArgs(indexDefect, lhs.meta, [someRange, "0.." & $(lhs.arrayItems.high)]) + compileErrorWithArgs(indexDefect, lhs.meta, + [someRange, "0.." & $(lhs.arrayItems.high)]) else: discard # todo error? - else: compileErrorWithArgs(invalidAccessorStorage, rhs.meta, [rhs.toString, $lhs.nt]) + else: compileErrorWithArgs(invalidAccessorStorage, + rhs.meta, [rhs.toString, $lhs.nt]) else: discard proc dotEvaluator(c: var HtmlCompiler, node: Node, @@ -389,7 +437,8 @@ proc bracketEvaluator(c: var HtmlCompiler, node: Node, if likely(index != nil): result = c.walkAccessorStorage(node.bracketLHS, index, scopetables) -proc writeDotExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = +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): @@ -397,7 +446,8 @@ proc writeDotExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTab c.stickytail = true proc evalCmd(c: var HtmlCompiler, node: Node, - scopetables: var seq[ScopeTable], parentNodeType: NodeType = ntUnknown): Node = + scopetables: var seq[ScopeTable], + parentNodeType: NodeType = ntUnknown): Node = # Evaluate a command case node.cmdType of cmdBreak: @@ -576,21 +626,25 @@ proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, of AND: case lhs.nt of ntInfixExpr: - result = c.infixEvaluator(lhs.infixLeft, lhs.infixRight, lhs.infixOp, scopetables) + result = c.infixEvaluator(lhs.infixLeft, + lhs.infixRight, lhs.infixOp, scopetables) if result: case rhs.nt of ntInfixExpr: - return c.infixEvaluator(rhs.infixLeft, rhs.infixRight, rhs.infixOp, scopetables) + return c.infixEvaluator(rhs.infixLeft, + rhs.infixRight, rhs.infixOp, scopetables) else: discard # todo else: discard of OR: case lhs.nt of ntInfixExpr: - result = c.infixEvaluator(lhs.infixLeft, lhs.infixRight, lhs.infixOp, scopetables) + result = c.infixEvaluator(lhs.infixLeft, + lhs.infixRight, lhs.infixOp, scopetables) if not result: case rhs.nt of ntInfixExpr: - return c.infixEvaluator(rhs.infixLeft, rhs.infixRight, rhs.infixOp, scopetables) + return c.infixEvaluator(rhs.infixLeft, + rhs.infixRight, rhs.infixOp, scopetables) else: discard # todo else: discard # todo else: discard # todo @@ -602,12 +656,21 @@ proc getValues(c: var HtmlCompiler, node: Node, proc getValue(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node = + if unlikely(node == nil): return case node.nt of ntIdent: # evaluates an identifier let some = c.getScope(node.identName, scopetables) if likely(some.scopeTable != nil): - return c.getValue(some.scopeTable[node.identName].varValue, scopetables) + # echo some.scopeTable[node.identName].nt + case some.scopeTable[node.identName].nt + of ntFunction: + # evaluate a function call and return the result + # if the retun type is not void, otherwise nil + return c.unsafeCall(node, some.scopeTable[node.identName], scopetables) + of ntVariableDef: + return c.getValue(some.scopeTable[node.identName].varValue, scopetables) + else: return if node.identName == "this": return c.data["local"].toTimNode if node.identName == "app": @@ -641,19 +704,19 @@ proc getValue(c: var HtmlCompiler, node: Node, # evaluate a math expression and returns its value result = c.mathInfixEvaluator(node.infixMathLeft, node.infixMathRight, node.infixMathOp, scopetables) - of ntCall: - # evaluate a function call and return the result - # if the retun type is not void, otherwise nil - result = c.fnCall(node, scopetables) + # of ntCall: + # result = c.fnCall(node, scopetables) else: discard template calcInfixEval() {.dirty.} = - let lhs = c.mathInfixEvaluator(lhs.infixMathLeft, lhs.infixMathRight, lhs.infixMathOp, scopetables) + let lhs = c.mathInfixEvaluator(lhs.infixMathLeft, + lhs.infixMathRight, lhs.infixMathOp, scopetables) if likely(lhs != nil): return c.mathInfixEvaluator(lhs, rhs, op, scopetables) template calcInfixNest() {.dirty.} = - let rhs = c.mathInfixEvaluator(rhs.infixMathLeft, rhs.infixMathRight, rhs.infixMathOp, scopetables) + let rhs = c.mathInfixEvaluator(rhs.infixMathLeft, + rhs.infixMathRight, rhs.infixMathOp, scopetables) if likely(rhs != nil): return c.mathInfixEvaluator(lhs, rhs, op, scopetables) @@ -828,7 +891,8 @@ template evalBranch(branch: Node, body: untyped) = return # condition is thruty else: discard -proc evalCondition(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): Node {.discardable.} = +proc evalCondition(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable]): Node {.discardable.} = # Evaluates condition branches evalBranch node.condIfBranch.expr: result = c.walkNodes(node.condIfBranch.body, scopetables) @@ -864,7 +928,8 @@ template loopEvaluator(kv, items: Node) = of ntVariableDef: for x in items.sVal: newScope(scopetables) - node.loopItem.varValue = ast.Node(nt: ntLitString, sVal: $(x)) # todo implement `ntLitChar` + node.loopItem.varValue = + ast.Node(nt: ntLitString, sVal: $(x)) # todo implement `ntLitChar` c.varExpr(node.loopItem, scopetables) let x = c.walkNodes(node.loopBody, scopetables) clearScope(scopetables) @@ -948,7 +1013,8 @@ proc evalLoop(c: var HtmlCompiler, node: Node, else: compileErrorWithArgs(invalidIterator) -proc typeCheck(c: var HtmlCompiler, x, node: Node, parent: Node = nil): bool = +proc typeCheck(c: var HtmlCompiler, + x, node: Node, parent: Node = nil): bool = if unlikely(x == nil): compileErrorWithArgs(typeMismatch, ["none", $(node.nt)]) if unlikely(x.nt != node.nt): @@ -959,7 +1025,8 @@ proc typeCheck(c: var HtmlCompiler, x, node: Node, parent: Node = nil): bool = compileErrorWithArgs(typeMismatch, [$(node.nt), $(x.nt)]) result = true -proc typeCheck(c: var HtmlCompiler, node: Node, expect: NodeType, parent: Node = nil): bool = +proc typeCheck(c: var HtmlCompiler, node: Node, + expect: NodeType, parent: Node = nil): bool = if unlikely(node == nil): let node = parent compileErrorWithArgs(typeMismatch, ["none", $(expect)]) @@ -976,10 +1043,14 @@ proc typeCheck(c: var HtmlCompiler, node: Node, expect: HtmlTag): bool = # # Compile Handlers # -proc checkArrayStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): bool -proc checkObjectStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): bool +proc checkArrayStorage(c: var HtmlCompiler, + node: Node, scopetables: var seq[ScopeTable]): bool -proc checkObjectStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): bool = +proc checkObjectStorage(c: var HtmlCompiler, + node: Node, scopetables: var seq[ScopeTable]): bool + +proc checkObjectStorage(c: var HtmlCompiler, + node: Node, scopetables: var seq[ScopeTable]): bool = # a simple checker and ast modified for object storages for k, v in node.objectItems.mpairs: case v.nt @@ -993,7 +1064,8 @@ proc checkObjectStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[Sc else: discard # todo error result = true -proc checkArrayStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]): bool = +proc checkArrayStorage(c: var HtmlCompiler, + node: Node, scopetables: var seq[ScopeTable]): bool = # a simple checker and ast modified for array storages for v in node.arrayItems.mitems: var valNode = c.getValue(v, scopetables) @@ -1002,7 +1074,8 @@ proc checkArrayStorage(c: var HtmlCompiler, node: Node, scopetables: var seq[Sco else: discard # todo error result = true -proc varExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = +proc varExpr(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable]) = # Evaluates a variable if likely(not c.inScope(node.varName, scopetables)): case node.varValue.nt @@ -1014,12 +1087,14 @@ proc varExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) c.stack(node.varName, node, scopetables) of ntIdent: if unlikely(not c.inScope(node.varValue.identName, scopetables)): - compileErrorWithArgs(undeclaredVariable, [node.varValue.identName]) + compileErrorWithArgs(undeclaredVariable, + [node.varValue.identName]) else: discard c.stack(node.varName, node, scopetables) else: compileErrorWithArgs(varRedefine, [node.varName]) -proc assignExpr(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = +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): @@ -1030,87 +1105,120 @@ 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]) = +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) + 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]) + else: + # if node.fnParams + compileErrorWithArgs(fnRedefine, [node.fnIdent]) -proc fnCall(c: var HtmlCompiler, node: Node, +proc unsafeCall(c: var HtmlCompiler, node, fnNode: Node, scopetables: var seq[ScopeTable]): Node = - # Handle function calls - let some = c.getScope(node.callIdent, scopetables) - if likely(some.scopeTable != nil): - let fnNode = some.scopeTable[node.callIdent] - newScope(scopetables) - if fnNode.fnParams.len > 0: - # add params to the 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 + let params = fnNode.fnParams.keys.toSeq() + var asgnValArgs: seq[Node] + if node.identArgs.len == fnNode.fnParams.len: + # checking if the number of given args + # is matching the total number of parameters + if node.identArgs.len > 0: + var i = 0 + if fnNode.fnImportNim: + var args: seq[stdlib.Arg] + for i in 0..node.identArgs.high: + try: + let param = fnNode.fnParams[params[i]] + let argValue = c.getValue(node.identArgs[i], scopetables) + if c.typeCheck(argValue, param[1]): + add args, (param[0][1..^1], argValue) + else: return # typeCheck returns `typeMismatch` + except Defect: + compileErrorWithArgs(fnExtraArg, + [node.identName, $(params.len), $(node.identArgs.len)]) + try: + return stdlib.call(fnNode.fnSource, node.identName, args) + except SystemModule as e: + compileErrorWithArgs(internalError, + [e.msg, fnNode.fnSource, fnNode.fnIdent], node.meta) + else: for k, p in fnNode.fnParams: - case node.callArgs[i].nt + case node.identArgs[i].nt of ntIdent, ntMathInfixExpr, ntInfixExpr: - var valNode = c.getValue(node.callArgs[i], scopetables) + var valNode = c.getValue(node.identArgs[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 + add asgnValArgs, valNode + # 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] + if c.typeCheck(node.identArgs[i], p.pType): + # let someParam = c.getScope(k, scopetables) + # someParam.scopeTable[k].varValue = node.identArgs[i] + add asgnValArgs, node.identArgs[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 - result = c.walkNodes(fnNode.fnBody, scopetables, ntFunction) - if result != nil: - case result.nt - of ntHtmlElement: - if c.typeCheck(result, fnNode.fnReturnHtmlElement): - result = c.walkNodes(@[result], scopetables) + elif node.identArgs.len > fnNode.fnParams.len: + compileErrorWithArgs(fnExtraArg, + [$(node.identArgs.len), $(fnNode.fnParams.len)]) + elif node.identArgs.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 + + if fnNode.fnParams.len > 0: + # stack provided argument values + newScope(scopetables) + var i = 0 + for k, p in fnNode.fnParams: + var x = ast.newVariable(k, p.pImplVal, p.meta) + x.varValue = asgnValArgs[i] + c.stack(k, x, scopetables) + inc i + result = c.walkNodes(fnNode.fnBody, scopetables, ntFunction) + if result != nil: + case result.nt + of ntHtmlElement: + if c.typeCheck(result, fnNode.fnReturnHtmlElement): + result = c.walkNodes(@[result], scopetables) + else: + result = nil + else: + let x = c.getValue(result, scopetables) + if likely(x != nil): + if unlikely(c.typeCheck(x, fnNode.fnReturnType, x)): + result = x else: result = nil - else: - let x = c.getValue(result, scopetables) - if likely(x != nil): - if unlikely(c.typeCheck(x, fnNode.fnReturnType, x)): - result = x - else: - result = nil - else: discard # ? - # if unlikely(not c.typeCheck(result, fnNode.fnReturnType, fnNode)): - # clearScope(scopetables) - # return nil - clearScope(scopetables) - else: compileErrorWithArgs(fnUndeclared, [node.callIdent]) + else: discard # ? + # if unlikely(not c.typeCheck(result, fnNode.fnReturnType, fnNode)): + # clearScope(scopetables) + # return nil + clearScope(scopetables) + +proc fnCall(c: var HtmlCompiler, node: Node, + scopetables: var seq[ScopeTable]): Node = + # Handle function calls + let some = c.getScope(node.identName, scopetables) + if likely(some.scopeTable != nil): + return c.unsafeCall(node, some.scopeTable[node.identName], scopetables) + else: compileErrorWithArgs(fnUndeclared, [node.identName]) # # Html Handler @@ -1144,10 +1252,10 @@ proc getAttrs(c: var HtmlCompiler, attrs: HtmlAttributes, if likely(xVal != nil): add attrStr, xVal.toString() else: return # undeclaredVariable - of ntCall: - let xVal = c.fnCall(attrNode, scopetables) - if likely(xVal != nil): - add attrStr, xVal.toString() + # of ntCall: + # let xVal = c.fnCall(attrNode, scopetables) + # if likely(xVal != nil): + # add attrStr, xVal.toString() of ntDotExpr: let xVal = c.dotEvaluator(attrNode, scopetables) if likely(xVal != nil): @@ -1210,11 +1318,13 @@ proc htmlElement(c: var HtmlCompiler, node: Node, c.walkNodes(node.nodes, scopetables, ntHtmlElement) proc evaluatePartials(c: var HtmlCompiler, - includes: seq[string], scopetables: var seq[ScopeTable]) = + node: Node, scopetables: var seq[ScopeTable]) = # Evaluate included partials - for x in includes: + c.partialIndent = node.meta[1] + for x in node.includes: if likely(c.ast.partials.hasKey(x)): c.walkNodes(c.ast.partials[x][0].nodes, scopetables) + c.partialIndent = 0 proc evaluatePlaceholder(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTable]) = @@ -1232,7 +1342,8 @@ proc evaluatePlaceholder(c: var HtmlCompiler, node: Node, # # JS API # -proc createHtmlElement(c: var HtmlCompiler, x: Node, scopetables: var seq[ScopeTable], pEl: string) = +proc createHtmlElement(c: var HtmlCompiler, x: Node, + scopetables: var seq[ScopeTable], pEl: string) = ## Create a new HtmlElement let xel = "el" & $(c.jsCountEl) add c.jsOutputCode, domCreateElement % [xel, x.getTag()] @@ -1246,7 +1357,8 @@ proc createHtmlElement(c: var HtmlCompiler, x: Node, scopetables: var seq[ScopeT domInsertAdjacentElement % [pEl, xel] else: add c.jsOutputCode, - domInsertAdjacentElement % ["document.querySelector('" & c.jsTargetElement & "')", xel] + domInsertAdjacentElement % + ["document.querySelector('" & c.jsTargetElement & "')", xel] # # Main nodes walker @@ -1266,6 +1378,16 @@ proc walkNodes(c: var HtmlCompiler, nodes: seq[Node], else: c.createHtmlElement(node, scopetables, xel) of ntIdent: + # case parentNodeType + # of ntHtmlElement: + # let returnNode = c.fnCall(node, scopetables) + # if likely(returnNode != nil): + # write returnNode, true, false + # else: + # let x = c.fnCall(node, scopetables) + # if unlikely x != nil: + # if parentNodeType != ntFunction and x.nt != ntHtmlElement: + # compileErrorWithArgs(fnReturnMissingCommand, [node.identName, $(x.nt)]) let x: Node = c.getValue(node, scopetables) if not c.isClientSide: write x, true, node.identSafe @@ -1314,19 +1436,19 @@ proc walkNodes(c: var HtmlCompiler, nodes: seq[Node], of ntViewLoader: c.head = c.output reset(c.output) - of ntCall: - case parentNodeType - of ntHtmlElement: - let returnNode = c.fnCall(node, scopetables) - if likely(returnNode != nil): - write returnNode, true, false - else: - let x = c.fnCall(node, scopetables) - if unlikely x != nil: - if parentNodeType != ntFunction and x.nt != ntHtmlElement: - compileErrorWithArgs(fnReturnMissingCommand, [node.callIdent, $(x.nt)]) + # of ntCall: + # case parentNodeType + # of ntHtmlElement: + # let returnNode = c.fnCall(node, scopetables) + # if likely(returnNode != nil): + # write returnNode, true, false + # else: + # let x = c.fnCall(node, scopetables) + # if unlikely x != nil: + # if parentNodeType != ntFunction and x.nt != ntHtmlElement: + # compileErrorWithArgs(fnReturnMissingCommand, [node.identName, $(x.nt)]) of ntInclude: - c.evaluatePartials(node.includes, scopetables) + c.evaluatePartials(node, scopetables) of ntJavaScriptSnippet: add c.jsOutput, node.snippetCode of ntJsonSnippet: @@ -1352,10 +1474,10 @@ proc walkNodes(c: var HtmlCompiler, nodes: seq[Node], c.evaluatePlaceholder(node, scopetables) else: discard + # # Public API # - when not defined timStandalone: proc newCompiler*(engine: TimEngine, ast: Ast, tpl: TimTemplate, minify = true, indent = 2, @@ -1376,6 +1498,8 @@ when not defined timStandalone: ) if minify: setLen(result.nl, 0) var scopetables = newSeq[ScopeTable]() + for moduleName, moduleAst in result.ast.modules: + result.walkNodes(moduleAst.nodes, scopetables) result.walkNodes(result.ast.nodes, scopetables) else: proc newCompiler*(ast: Ast, tpl: TimTemplate, @@ -1394,6 +1518,7 @@ else: ) if minify: setLen(result.nl, 0) var scopetables = newSeq[ScopeTable]() + result.walkNodes(result.ast.nodes, scopetables) proc newCompiler*(ast: Ast, minify = true, indent = 2): HtmlCompiler = @@ -1432,9 +1557,12 @@ proc getTail*(c: HtmlCompiler): string = ## Retruns the tail of a layout assert c.tplType == ttLayout if c.jsOutput.len > 0: - result = "\n" & "" + var indentSize: int + if not c.minify: + indentSize = c.indent * 2 + result = "\n" & indent("", indentSize) add result, c.getHtml else: result = c.getHtml \ No newline at end of file diff --git a/src/tim/engine/compilers/tim.nim b/src/tim/engine/compilers/tim.nim index e6870bf..8ac022c 100755 --- a/src/tim/engine/compilers/tim.nim +++ b/src/tim/engine/compilers/tim.nim @@ -16,6 +16,7 @@ type else: discard logger*: Logger indent*: int = 2 + partialIndent* : int = 0 minify*, hasErrors*: bool stickytail*: bool # when `false` inserts a `\n` char diff --git a/src/tim/engine/parser.nim b/src/tim/engine/parser.nim index 432513b..455034d 100755 --- a/src/tim/engine/parser.nim +++ b/src/tim/engine/parser.nim @@ -5,11 +5,11 @@ # https://github.com/openpeeps/tim {.warning[ImplicitDefaultValue]:off.} -import std/[macros, streams, lexbase, strutils, sequtils, re, tables] -from std/os import `/` +import std/[macros, streams, lexbase, + strutils, sequtils, re, tables, os, with] import ./meta, ./tokens, ./ast, ./logging -# import ./stdlib +import ./stdlib import pkg/kapsis/cli import pkg/importer @@ -159,6 +159,13 @@ proc isIdent(tk: TokenTuple, anyIdent, anyStringKey = false): bool = if result or anyStringKey: return tk.value.validIdentifier +proc skipNextComment(p: var Parser) = + while true: + case p.next.kind + of tkComment: + p.next = p.lex.getToken() # skip inline comments + else: break + proc walk(p: var Parser, offset = 1) {.gcsafe.} = var i = 0 while offset > i: @@ -166,10 +173,11 @@ proc walk(p: var Parser, offset = 1) {.gcsafe.} = p.prev = p.curr p.curr = p.next p.next = p.lex.getToken() - case p.next.kind - of tkComment: - p.next = p.lex.getToken() # skip inline comments - else: discard + p.skipNextComment() + +proc skipComments(p: var Parser) = + while p.curr is tkComment: + walk p macro prefixHandle(name: untyped, body: untyped) = # Create a new prefix procedure with `name` and `body` @@ -193,7 +201,7 @@ macro prefixHandle(name: untyped, body: untyped) = ) proc includePartial(p: var Parser, node: Node, s: string) = - node.meta = [p.curr.line, p.curr.pos, p.curr.col] + # node.meta = [p.curr.line, p.curr.pos, p.curr.col] add node.includes, "/" & s & ".timl" p.includes[p.engine.getPath(s & ".timl", ttPartial)] = node.meta @@ -818,6 +826,10 @@ prefixHandle pInclude: let tk = p.curr walk p result = ast.newNode(ntInclude, tk) + # I guess this will fix indentation + # inside partials (when not minified) + result.meta[1] = p.lvl * 4 + result.meta[2] = (p.lvl * 4) + 1 p.includePartial(result, p.curr.value) walk p while p.curr is tkComma: @@ -848,20 +860,6 @@ prefixHandle pSnippet: result = ast.newNode(ntJsonSnippet, p.curr) result.snippetCode = p.curr.value else: discard - # elif p.curr.kind == tkSass: - # result = ast.newSnippet(p.curr) - # result.sassCode = p.curr.value - # elif p.curr.kind in {tkJson, tkYaml}: - # let code = p.curr.value.split(Newlines, maxsplit = 1) - # var ident = code[0] - # p.curr.value = code[1] - # if p.curr.kind == tkJson: - # result = newSnippet(p.curr, ident) - # result.jsonCode = p.curr.value - # else: - # p.curr.kind = tkJson - # result = newSnippet(p.curr, ident) - # # result.jsonCode = yaml(p.curr.value).toJsonStr walk p prefixHandle pClientSide: @@ -907,6 +905,7 @@ prefixHandle pFunction: walk p if p.curr is tkAsterisk: result.fnExport = true + walk p expectWalk tkLP while p.curr isnot tkRP: case p.curr.kind @@ -963,6 +962,9 @@ prefixHandle pFunction: if unlikely(result.fnBody.len == 0): error(badIndentation, p.curr) else: + result.fnImportNim = p.tree.src.startsWith("std/") + if result.fnImportNim: + result.fnSource = p.tree.src result.fnFwdDecl = true prefixHandle pFunctionCall: @@ -972,7 +974,7 @@ prefixHandle pFunctionCall: while p.curr isnot tkRP: let argNode = p.getPrefixOrInfix(includes = tkAssignableSet) caseNotNil argNode: - add result.callArgs, argNode + add result.identArgs, argNode walk p # tkRP # @@ -1173,9 +1175,11 @@ proc parseHandle[T](i: Import[T], importFile: ImportFile, tpl.addDep(i.handle.tpl.getSourcePath()) template startParse(path: string): untyped = - p.handle.curr = p.handle.lex.getToken() - p.handle.next = p.handle.lex.getToken() - p.handle.logger = Logger(filePath: path) + with p.handle: + curr = p.handle.lex.getToken() + next = p.handle.lex.getToken() + logger = Logger(filePath: path) + p.handle.skipComments() # if any while p.handle.curr isnot tkEOF: if unlikely(p.handle.lex.hasError): p.handle.logger.newError(internalError, p.handle.curr.line, @@ -1207,14 +1211,42 @@ template collectImporterErrors = meta[2], true, [err.fpath.replace(engine.getSourcePath(), "")]) p.handle.hasErrors = true +proc parseModule(engine: TimEngine, moduleName: string, + code: SourceCode = SourceCode("")): Ast = + var p = Parser( + tree: Ast(src: moduleName), + engine: engine, + lex: newLexer(code.string, allowMultilineStrings = true), + logger: Logger(filePath: "") + ) + p.curr = p.lex.getToken() + p.next = p.lex.getToken() + # p.skipComments() # if any + while p.curr isnot tkEOF: + if unlikely(p.lex.hasError): + p.logger.newError(internalError, p.curr.line, + p.curr.col, false, p.lex.getError) + if unlikely(p.hasErrors): + break + let node = p.parseRoot() + if node != nil: + add p.tree.nodes, node + p.lex.close() + result = p.tree + proc initSystemModule(p: var Parser) = ## Make `std/system` available by default - let sysid = "std/system" - var sysNode = ast.newNode(ntImport) - sysNode.modules.add(sysid) - p.tree.nodes.add(sysNode) + {.gcsafe.}: + let stdsystem = "std/system" + var sysNode = ast.newNode(ntImport) + sysNode.modules.add(stdsystem) + p.tree.nodes.add(sysNode) + p.tree.modules = TimModulesTable() + p.tree.modules[stdsystem] = + p.engine.parseModule(stdsystem, std(stdsystem)[1]) + # var L = initTicketLock() - # importer(sysid, p.dirPath, addr(p.stylesheets), + # parseHandle[Parser](sysid, dirPath(p.tpl.sources), addr(p.imports), # addr L, true, std(sysid)[1]) # @@ -1228,14 +1260,15 @@ proc newParser*(engine: TimEngine, tpl: TimTemplate, engine.getSourcePath() / $(ttPartial), baseIsMain = true ) - p.handle.tree = Ast() - p.handle.lex = newLexer(readFile(tpl.sources.src), allowMultilineStrings = true) - p.handle.engine = engine - p.handle.tpl = tpl - p.handle.isMain = isMainParser - p.handle.refreshAst = refreshAst - # initstdlib() - # p.initSystemModule() + with p.handle: + tree = Ast() + lex = newLexer(readFile(tpl.sources.src), allowMultilineStrings = true) + engine = engine + tpl = tpl + isMain = isMainParser + refreshAst = refreshAst + initstdlib() + p.handle.initSystemModule() startParse(tpl.sources.src) if isMainParser: {.gcsafe.}: @@ -1256,6 +1289,7 @@ proc parseSnippet*(id, code: string): Parser {.gcsafe.} = ) p.curr = p.lex.getToken() p.next = p.lex.getToken() + # p.skipComments() # if any while p.curr isnot tkEOF: if unlikely(p.lex.hasError): p.logger.newError(internalError, p.curr.line, @@ -1279,6 +1313,7 @@ proc parseSnippet*(snippetPath: string): Parser {.gcsafe.} = ) p.curr = p.lex.getToken() p.next = p.lex.getToken() + # p.skipComments() # if any while p.curr isnot tkEOF: if unlikely(p.lex.hasError): p.logger.newError(internalError, p.curr.line, diff --git a/src/tim/engine/tokens.nim b/src/tim/engine/tokens.nim index 163b557..1973b41 100755 --- a/src/tim/engine/tokens.nim +++ b/src/tim/engine/tokens.nim @@ -26,10 +26,7 @@ handlers: inc lex.bufpos while true: case lex.buf[lex.bufpos]: - of NewLines: - lex.handleNewLine() - break - of EndOfFile: break + of NewLines, EndOfFile: break else: inc lex.bufpos lex.kind = kind