diff --git a/src/tim.nim b/src/tim.nim index 15e3891..9d25259 100644 --- a/src/tim.nim +++ b/src/tim.nim @@ -6,9 +6,11 @@ import std/json except `%*` import std/times -import tim/engine/[meta, parser, compiler, logging] import pkg/[watchout, kapsis/cli] +import tim/engine/[meta, parser, logging] +import tim/engine/compilers/html + from std/strutils import `%`, indent from std/os import `/` @@ -17,7 +19,7 @@ const DOCKTYPE = "" defaultLayout = "base" -proc jitCompiler*(engine: Tim, tpl: TimTemplate, data: JsonNode): HtmlCompiler = +proc jitCompiler*(engine: TimEngine, tpl: TimTemplate, data: JsonNode): HtmlCompiler = ## Compiles `tpl` AST at runtime newCompiler(engine.readAst(tpl), tpl, engine.isMinified(), engine.getIndentSize(), data) @@ -26,7 +28,7 @@ proc displayErrors(l: Logger) = display(err) display(l.filePath) -proc compileCode*(engine: Tim, tpl: TimTemplate, refreshAst = false) = +proc compileCode*(engine: TimEngine, tpl: TimTemplate, refreshAst = false) = # Compiles `tpl` TimTemplate to either `.html` or binary `.ast` var tplView: TimTemplate if tpl.getType == ttView: @@ -53,7 +55,7 @@ proc compileCode*(engine: Tim, tpl: TimTemplate, refreshAst = false) = else: p.logger.displayErrors() -proc precompile*(engine: Tim, callback: TimCallback = nil, +proc precompile*(engine: TimEngine, callback: TimCallback = nil, flush = true, waitThread = false) = ## Precompiles available templates inside `layouts` and `views` ## directories to either static `.html` or binary `.ast`. @@ -68,25 +70,29 @@ proc precompile*(engine: Tim, callback: TimCallback = nil, when defined timHotCode: var watchable: seq[string] # Define callback procs for pkg/watchout + # Callback `onFound` proc onFound(file: watchout.File) = # Runs when detecting a new template. let tpl: TimTemplate = engine.getTemplateByPath(file.getPath()) + # if not tpl.isUsed(): return # prevent compiling tpl if not in use case tpl.getType of ttView, ttLayout: engine.compileCode(tpl) if engine.errors.len > 0: for err in engine.errors: echo err - # setLen(engine.errors, 0) else: discard + # Callback `onChange` proc onChange(file: watchout.File) = # Runs when detecting changes + let tpl: TimTemplate = engine.getTemplateByPath(file.getPath()) + # echo tpl.isUsed() + # if not tpl.isUsed(): return # prevent compiling tpl if not in use echo "✨ Changes detected" echo indent(file.getName() & "\n", 3) # echo toUnix(getTime()) - let tpl: TimTemplate = engine.getTemplateByPath(file.getPath()) case tpl.getType() of ttView, ttLayout: engine.compileCode(tpl) @@ -96,18 +102,19 @@ proc precompile*(engine: Tim, callback: TimCallback = nil, else: for path in tpl.getDeps: let deptpl = engine.getTemplateByPath(path) - # echo indent($(ttView) / deptpl.getName(), 4) engine.compileCode(deptpl, refreshAst = true) if engine.errors.len > 0: for err in engine.errors: echo err + # Callback `onDelete` proc onDelete(file: watchout.File) = # Runs when deleting a file echo "✨ Deleted\n", file.getName() engine.clearTemplateByPath(file.getPath()) - var w = newWatchout(@[engine.getSourcePath() / "*"], onChange, onFound, onDelete) + var w = newWatchout(@[engine.getSourcePath() / "*"], onChange, + onFound, onDelete, recursive = true, ext = @["timl"]) w.start(waitThread) else: for tpl in engine.getViews(): @@ -120,7 +127,7 @@ proc precompile*(engine: Tim, callback: TimCallback = nil, for tpl in engine.getLayouts(): engine.compileCode(tpl) -proc render*(engine: Tim, viewName: string, +proc render*(engine: TimEngine, viewName: string, layoutName = defaultLayout, global, local = newJObject()): string = ## Renders a view based on `viewName` and `layoutName`. ## Exposing data to a template is possible using `global` or @@ -167,15 +174,15 @@ proc render*(engine: Tim, viewName: string, raise newException(TimError, "View not found: `$1`" % [viewName]) when defined napibuild: - # Setup for building Tim as a node addon via NAPI + # Setup for building TimEngine as a node addon via NAPI import pkg/denim from std/sequtils import toSeq - var timjs: Tim + var timjs: TimEngine init proc(module: Module) = proc init(src: string, output: string, basepath: string, minify: bool, indent: int) {.export_napi.} = - ## Initialize Tim Engine + ## Initialize TimEngine Engine timjs = newTim( args.get("src").getStr, args.get("output").getStr, @@ -185,7 +192,7 @@ when defined napibuild: ) proc precompileSync() {.export_napi.} = - ## Precompile Tim templates + ## Precompile TimEngine templates timjs.precompile(flush = true, waitThread = false) proc renderSync(view: string) {.export_napi.} = @@ -194,7 +201,8 @@ when defined napibuild: return %*(x) elif not isMainModule: - import tim/engine/[meta, parser, compiler, logging] + import tim/engine/[meta, parser, logging] + import tim/engine/compilers/html - export parser, compiler, json - export meta except Tim \ No newline at end of file + export parser, html, json + export meta except TimEngine \ No newline at end of file diff --git a/src/tim/engine/ast.nim b/src/tim/engine/ast.nim index 63cb515..49fc82b 100644 --- a/src/tim/engine/ast.nim +++ b/src/tim/engine/ast.nim @@ -6,6 +6,8 @@ import ./tokens import std/[tables, json, macros] +import kapsis/cli + from std/htmlparser import tagToStr, htmlTag, HtmlTag export tagToStr, htmlTag, HtmlTag @@ -157,11 +159,15 @@ type Meta* = array[3, int] ScopeTable* = TableRef[string, Node] - # PartialsTable* = TableRef[string, Ast] - PartialTable* = TableRef[string, Ast] + TimPartialsTable* = TableRef[string, (Ast, seq[cli.Row])] Ast* = object + src*: string + ## trace the source path nodes*: seq[Node] - partials*: PartialTable + ## a seq containing tree nodes + partials*: TimPartialsTable + ## other trees resulted from imports + jit*: bool const ntAssignableSet* = {ntLitString, ntLitInt, ntLitFloat, ntLitBool} diff --git a/src/tim/engine/compiler.nim b/src/tim/engine/compilers/html.nim similarity index 96% rename from src/tim/engine/compiler.nim rename to src/tim/engine/compilers/html.nim index 3345987..404f523 100644 --- a/src/tim/engine/compiler.nim +++ b/src/tim/engine/compilers/html.nim @@ -7,32 +7,15 @@ import std/[tables, strutils, json, options, terminal] -import ./ast, ./logging +import ../ast, ../logging -from ./meta import Tim, TimTemplate, TimTemplateType, +from ../meta import TimEngine, TimTemplate, TimTemplateType, getType, getSourcePath +include ./tim + type - HtmlCompiler* = object - ast: Ast - tpl: TimTemplate - nl: string = "\n" - output: string - jsOutput: string - jsCodeExists: bool - start: bool - case tplType: TimTemplateType - of ttLayout: - head: string - else: discard - logger*: Logger - indent: int = 2 - minify, hasErrors: bool - stickytail: bool - # when `false` inserts a `\n` char - # before closing the HTML element tag. - # Does not apply to `textarea`, `button` and other - # self closing tags (such as `submit`, `img` and so on) + HtmlCompiler* = object of TimCompiler when not defined timStandalone: globalScope: ScopeTable = ScopeTable() data: JsonNode @@ -160,7 +143,6 @@ proc toString(value: Value): string = value.nVal.toString() proc print(val: Node) = - echo val let meta = " ($1:$2) " % [$val.meta[0], $val.meta[2]] stdout.styledWriteLine( fgGreen, "Debug", @@ -297,10 +279,11 @@ proc infixEvaluator(c: var HtmlCompiler, lhs, rhs: Node, else: discard of ntIdent: var lhs = c.fromScope(lhs.identName, scopetables) + if lhs == nil or rhs == nil: return # false case rhs.nt of ntIdent: var rhs = c.fromScope(rhs.identName, scopetables) - if lhs != nil and rhs != nil: + if rhs != nil: result = c.infixEvaluator(lhs.varValue, rhs.varValue, infixOp, scopetables) else: result = c.infixEvaluator(lhs.varValue, rhs, infixOp, scopetables) @@ -425,17 +408,23 @@ template evalBranch(branch: Node, body: untyped) = of ntInfixExpr, ntMathInfixExpr: if c.infixEvaluator(branch.infixLeft, branch.infixRight, branch.infixOp, scopetables): + newScope(scopetables) body + clearScope(scopetables) return # condition is thruty of ntIdent: if c.infixEvaluator(branch, boolDefault, EQ, scopetables): + newScope(scopetables) body + clearScope(scopetables) return # condition is thruty of ntDotExpr: let x = c.dotEvaluator(branch, scopetables) if likely(x != nil): if c.infixEvaluator(x, boolDefault, EQ, scopetables): + newScope(scopetables) body + clearScope(scopetables) return else: discard @@ -610,7 +599,7 @@ proc htmlElement(c: var HtmlCompiler, node: Node, scopetables: var seq[ScopeTabl proc evaluatePartials(c: var HtmlCompiler, includes: seq[string], scopetables: var seq[ScopeTable]) = for x in includes: if likely(c.ast.partials.hasKey(x)): - c.evaluateNodes(c.ast.partials[x].nodes, scopetables) + c.evaluateNodes(c.ast.partials[x][0].nodes, scopetables) proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[ScopeTable]) = for i in 0..nodes.high: @@ -634,6 +623,7 @@ proc evaluateNodes(c: var HtmlCompiler, nodes: seq[Node], scopetables: var seq[S of ntAssignExpr: c.assignExpr(nodes[i], scopetables) of ntConditionStmt: + # echo nodes[i] c.evalCondition(nodes[i], scopetables) of ntLoopStmt: c.evalLoop(nodes[i], scopetables) diff --git a/src/tim/engine/compilers/tim.nim b/src/tim/engine/compilers/tim.nim new file mode 100644 index 0000000..42af60d --- /dev/null +++ b/src/tim/engine/compilers/tim.nim @@ -0,0 +1,21 @@ +type + TimCompiler* = object of RootObj + ast: Ast + tpl: TimTemplate + nl: string = "\n" + output: string + jsOutput: string + jsCodeExists: bool + start: bool + case tplType: TimTemplateType + of ttLayout: + head: string + else: discard + logger*: Logger + indent: int = 2 + minify, hasErrors: bool + stickytail: bool + # when `false` inserts a `\n` char + # before closing the HTML element tag. + # Does not apply to `textarea`, `button` and other + # self closing tags (such as `submit`, `img` and so on) \ No newline at end of file diff --git a/src/tim/engine/logging.nim b/src/tim/engine/logging.nim index 8ada80a..cd404ae 100644 --- a/src/tim/engine/logging.nim +++ b/src/tim/engine/logging.nim @@ -27,6 +27,7 @@ type duplicateAttribute = "Duplicate HTML attribute $" duplicateField = "Duplicate field $" undeclaredField = "Undeclared field $" + importNotFound = "Cannot open file: $" internalError = "$" Level* = enum @@ -142,6 +143,12 @@ template error*(msg: Message, tk: TokenTuple, strFmt: bool, p.hasErrors = true return # block code execution +template error*(msg: Message, meta: Meta, args: varargs[string]) = + if not p.hasErrors: + p.logger.newError(msg, meta[0], meta[2], true, args) + p.hasErrors = true + return # block code execution + template errorWithArgs*(msg: Message, tk: TokenTuple, args: openarray[string]) = if not p.hasErrors: p.logger.newError(msg, tk.line, tk.pos, true, args) diff --git a/src/tim/engine/meta.nim b/src/tim/engine/meta.nim index 6b09dc6..21ae997 100644 --- a/src/tim/engine/meta.nim +++ b/src/tim/engine/meta.nim @@ -23,7 +23,7 @@ type TemplateSourcePaths = tuple[src, ast, html: string] TimTemplate* = ref object - jit: bool + jit, inUse: bool templateId: string templateName: string case templateType: TimTemplateType @@ -38,27 +38,21 @@ type TemplateTable = TableRef[string, TimTemplate] TimCallback* = proc() {.nimcall, gcsafe.} - Tim* = ref object + TimEngine* = ref object base, src, output: string minify: bool indentSize: int layouts, views, partials: TemplateTable = TemplateTable() errors*: seq[string] - # sources: tuple[ - # layoutsPath = "layouts", - # viewsPath = "views", - # partialsPath = "partials" - # ] when defined timStandalone: globals: Globals else: globals: JsonNode = newJObject() - # imports: TableRef[string, ImportFunction] TimError* = object of CatchableError -proc getPath(engine: Tim, key: string, templateType: TimTemplateType): string = - ## Retrieve path key for either a partial, view or layout +proc getPath*(engine: TimEngine, key: string, templateType: TimTemplateType): string = + ## Get absolute path of `key` view, partial or layout var k: string var tree: seq[string] result = engine.src & "/" & $templateType & "/$1" @@ -78,18 +72,18 @@ proc hashid(path: string): string = # Creates an MD5 hashed version of `path` result = getMD5(path) -proc getHtmlPath(engine: Tim, path: string): string = +proc getHtmlPath(engine: TimEngine, path: string): string = engine.output / "html" / hashid(path) & ".html" -proc getAstPath(engine: Tim, path: string): string = +proc getAstPath(engine: TimEngine, path: string): string = engine.output / "ast" / hashid(path) & ".ast" -proc getHtmlStoragePath*(engine: Tim): string = +proc getHtmlStoragePath*(engine: TimEngine): string = ## Returns the `html` directory path used for ## storing static HTML files result = engine.output / "html" -proc getAstStoragePath*(engine: Tim): string = +proc getAstStoragePath*(engine: TimEngine): string = ## Returns the `ast` directory path used for ## storing binary AST files. result = engine.output / "ast" @@ -140,19 +134,19 @@ proc addDep*(t: TimTemplate, path: string) = proc getDeps*(t: TimTemplate): seq[string] = t.dependents.keys.toSeq() -proc writeHtml*(engine: Tim, tpl: TimTemplate, htmlCode: string) = +proc writeHtml*(engine: TimEngine, tpl: TimTemplate, htmlCode: string) = ## Writes `htmlCode` on disk using `tpl` info writeFile(tpl.sources.html, htmlCode) -proc writeHtmlTail*(engine: Tim, tpl: TimTemplate, htmlCode: string) = +proc writeHtmlTail*(engine: TimEngine, tpl: TimTemplate, htmlCode: string) = ## Writes `htmlCode` tails on disk using `tpl` info writeFile(tpl.sources.html.changeFileExt("tail"), htmlCode) -proc writeAst*(engine: Tim, tpl: TimTemplate, astCode: Ast) = +proc writeAst*(engine: TimEngine, tpl: TimTemplate, astCode: Ast) = ## Writes `astCode` on disk using `tpl` info writeFile(tpl.sources.ast, supersnappy.compress(flatty.toFlatty(astCode))) -proc readAst*(engine: Tim, tpl: TimTemplate): Ast = +proc readAst*(engine: TimEngine, tpl: TimTemplate): Ast = ## Get `AST` of `tpl` TimTemplate from storage try: let binAst = readFile(tpl.sources.ast) @@ -188,19 +182,19 @@ proc getTail*(t: TimTemplate): string = ## Returns the tail of a split layout result = readFile(t.getHtmlPath.changeFileExt("tail")) -iterator getViews*(engine: Tim): TimTemplate = +iterator getViews*(engine: TimEngine): TimTemplate = for id, tpl in engine.views: yield tpl -iterator getLayouts*(engine: Tim): TimTemplate = +iterator getLayouts*(engine: TimEngine): TimTemplate = for id, tpl in engine.layouts: yield tpl # -# Tim Engine API +# TimEngine Engine API # -proc getTemplateByPath*(engine: Tim, path: string): TimTemplate = +proc getTemplateByPath*(engine: TimEngine, path: string): TimTemplate = ## Search for `path` in `layouts` or `views` table let id = hashid(path) # todo extract parent dir from path? if engine.views.hasKey(path): @@ -223,25 +217,29 @@ proc getTemplateByPath*(engine: Tim, path: string): TimTemplate = engine.partials[path] = newTemplate(id, ttPartial, sources) return engine.partials[path] -proc hasLayout*(engine: Tim, key: string): bool = +proc hasLayout*(engine: TimEngine, key: string): bool = ## Determine if `key` exists in `layouts` table result = engine.layouts.hasKey(engine.getPath(key, ttLayout)) -proc getLayout*(engine: Tim, key: string): TimTemplate = +proc getLayout*(engine: TimEngine, key: string): TimTemplate = ## Get a `TimTemplate` from `layouts` by `key` result = engine.layouts[engine.getPath(key, ttLayout)] + result.inUse = true -proc hasView*(engine: Tim, key: string): bool = +proc hasView*(engine: TimEngine, key: string): bool = ## Determine if `key` exists in `views` table result = engine.views.hasKey(engine.getPath(key, ttView)) -proc getView*(engine: Tim, key: string): TimTemplate = +proc getView*(engine: TimEngine, key: string): TimTemplate = ## Get a `TimTemplate` from `views` by `key` result = engine.views[engine.getPath(key, ttView)] + result.inUse = true + +proc isUsed*(t: TimTemplate): bool = t.inUse proc newTim*(src, output, basepath: string, - minify = true, indent = 2): Tim = - ## Initializes `Tim` engine + minify = true, indent = 2): TimEngine = + ## Initializes `TimEngine` engine var basepath = if basepath.fileExists: basepath.parentDir # if comes from `currentSourcePath()` @@ -254,7 +252,7 @@ proc newTim*(src, output, basepath: string, raise newException(TimError, "Expecting a relative path for `src` and `output`") result = - Tim( + TimEngine( src: normalizedPath(basepath / src), output: normalizedPath(basepath / output), base: basepath, @@ -283,13 +281,13 @@ proc newTim*(src, output, basepath: string, discard existsOrCreateDir(result.output / "ast") discard existsOrCreateDir(result.output / "html") -proc isMinified*(engine: Tim): bool = +proc isMinified*(engine: TimEngine): bool = result = engine.minify -proc getIndentSize*(engine: Tim): int = +proc getIndentSize*(engine: TimEngine): int = result = engine.indentSize -proc flush*(engine: Tim) = +proc flush*(engine: TimEngine) = ## Flush precompiled files for f in walkDir(engine.getAstStoragePath): if f.path.endsWith(".ast"): @@ -299,17 +297,17 @@ proc flush*(engine: Tim) = if f.path.endsWith(".html"): f.path.removeFile() -proc getSourcePath*(engine: Tim): string = +proc getSourcePath*(engine: TimEngine): string = result = engine.src -proc getTemplateType*(engine: Tim, path: string): TimTemplateType = +proc getTemplateType*(engine: TimEngine, path: string): TimTemplateType = ## Returns `TimTemplateType` by `path` let basepath = engine.getSourcePath() for xdir in ["layouts", "views", "partials"]: if path.startsWith(basepath / xdir): return parseEnum[TimTemplateType](xdir) -proc clearTemplateByPath*(engine: Tim, path: string) = +proc clearTemplateByPath*(engine: TimEngine, path: string) = ## Clear a template from `TemplateTable` by `path` case engine.getTemplateType(path): of ttLayout: diff --git a/src/tim/engine/parser.nim b/src/tim/engine/parser.nim index 257c7b6..e9776c8 100644 --- a/src/tim/engine/parser.nim +++ b/src/tim/engine/parser.nim @@ -5,28 +5,33 @@ # https://github.com/openpeeps/tim {.warning[ImplicitDefaultValue]:off.} -import std/[macros, streams, lexbase, strutils, re, tables] +import std/[macros, streams, lexbase, strutils, sequtils, re, tables] from std/os import `/` -import ./tokens, ./ast, ./logging -from meta import Tim, TimTemplate, TimTemplateType, getType, - getTemplateByPath, getSourcePath, setViewIndent, jitEnable +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 Parser* = object lex: Lexer + lvl: int prev, curr, next: TokenTuple - engine: Tim + engine: TimEngine tpl: TimTemplate logger*: Logger hasErrors*, nilNotError, hasLoadedView: bool - tree: Ast parentNode: seq[Node] - lvl: int - includes: seq[string] # a seq of `ntInclude` nodes + includes: Table[string, Meta] isMain: bool + refreshAst: bool + tplView: TimTemplate + tree: Ast PrefixFunction = proc(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.gcsafe.} InfixFunction = proc(p: var Parser, lhs: Node): Node {.gcsafe.} @@ -139,9 +144,9 @@ macro prefixHandle(name: untyped, body: untyped) = ) proc includePartial(p: var Parser, node: Node, s: string) = - node.includes.add("/" & s & ".timl") node.meta = [p.curr.line, p.curr.pos, p.curr.col] - p.includes.add(s & ".timl") + add node.includes, "/" & s & ".timl" + p.includes[p.engine.getPath(s & ".timl", ttPartial)] = node.meta proc getStorageType(p: var Parser): StorageType = if p.curr.value in ["this", "app"]: @@ -229,7 +234,7 @@ prefixHandle pIdent: return # result else: discard -proc pIdentOrAssignment(p: var Parser): Node {.gcsafe.} = +prefixHandle pIdentOrAssignment: let ident = p.curr if p.next is tkAssign: walk p, 2 # tkAssign @@ -551,7 +556,7 @@ prefixHandle pViewLoader: prefixHandle pInclude: # parse `@include` magic call. - # Tim parse included files in separate threads + # TimEngine parse included files in separate threads # using pkg/importer if likely p.next is tkString: let tk = p.curr @@ -565,7 +570,6 @@ prefixHandle pInclude: p.includePartial(result, p.curr.value) walk p else: return nil - # add p.includes, result prefixHandle pSnippet: case p.curr.kind @@ -675,7 +679,7 @@ proc getPrefixFn(p: var Parser, excludes, includes: set[TokenKind] = {}): Prefix of tkIF: pCondition of tkFor: pFor of tkIdentifier: pElement - of tkIdentVar: pIdent + of tkIdentVar: pIdentOrAssignment of tkViewLoader: pViewLoader of tkSnippetJS: pSnippet of tkInclude: pInclude @@ -722,19 +726,23 @@ proc parseRoot(p: var Parser, excludes, includes: set[TokenKind] = {}): Node {.g let tk = if p.curr isnot tkEOF: p.curr else: p.prev errorWithArgs(unexpectedToken, tk, [tk.value]) -proc newParser*(engine: Tim, tpl: TimTemplate, isMainParser = true): Parser {.gcsafe.} +proc newParser*(engine: TimEngine, tpl, tplView: TimTemplate, isMainParser = true, refreshAst = false): Parser {.gcsafe.} proc getAst*(p: Parser): Ast {.gcsafe.} +let partials = TimPartialsTable() +var jitMainParser: bool # force main parser enable JIT -let partials = PartialTable() proc parseHandle[T](i: Import[T], importFile: ImportFile, ticket: ptr TicketLock): seq[string] {.gcsafe, nimcall.} = withLock ticket[]: let fpath = importFile.getImportPath let path = fpath.replace(i.handle.engine.getSourcePath() / $(ttPartial) / "", "") - if likely(not partials.hasKey(path)): - var tpl: TimTemplate = i.handle.engine.getTemplateByPath(fpath) - var childParser: Parser = i.handle.engine.newParser(tpl, false) - partials[path] = childParser.getAst() + var tpl: TimTemplate = i.handle.engine.getTemplateByPath(fpath) + if likely(not partials.hasKey(path) or i.handle.refreshAst): + var childParser: Parser = i.handle.engine.newParser(tpl, i.handle.tplView, false) + if childParser.tpl.jitEnabled(): + jitMainParser = true + partials[path] = (childParser.getAst(), childParser.logger.errors.toSeq) + tpl.addDep(i.handle.tplView.getSourcePath()) template startParse(path: string): untyped = p.handle.curr = p.handle.lex.getToken() @@ -745,30 +753,44 @@ template startParse(path: string): untyped = p.handle.logger.newError(internalError, p.handle.curr.line, p.handle.curr.col, false, p.handle.lex.getError) if unlikely(p.handle.hasErrors): - echo "error" + reset(p.handle.tree) # reset incomplete tree break let node = p.handle.parseRoot() if likely(node != nil): add p.handle.tree.nodes, node lexbase.close(p.handle.lex) - if p.handle.includes.len > 0: - # continue parse other included templates - p.imports(p.handle.includes, parseHandle[Parser]) + if p.handle.includes.len > 0 and not p.handle.hasErrors: + # continue parse other partials + p.imports(p.handle.includes.keys.toSeq, parseHandle[Parser]) # # Public API # -proc newParser*(engine: Tim, tpl: TimTemplate, isMainParser = true): Parser {.gcsafe.} = +proc newParser*(engine: TimEngine, tpl, tplView: TimTemplate, + isMainParser = true, refreshAst = false): Parser {.gcsafe.} = ## Parse `tpl` TimTemplate - var p = newImport[Parser](tpl.sources.src, engine.getSourcePath() / $(ttPartial), baseIsMain=true) + let partialSrcPath = engine.getSourcePath() / $(ttPartial) + var p = newImport[Parser](tpl.sources.src, partialSrcPath, baseIsMain=true) 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 + p.handle.tplView = tplView startParse(tpl.sources.src) if isMainParser: {.gcsafe.}: - p.handle.tree.partials = partials + if partials.len > 0: + p.handle.tree.partials = partials + if jitMainParser: + p.handle.tpl.jitEnable + for err in p.importErrors: + case err.reason + of ImportErrorMessage.importNotFound: + let meta: Meta = p.handle.includes[err.fpath] + p.handle.logger.newError(Message.importNotFound, meta[0], meta[2], true, [err.fpath.replace(engine.getSourcePath(), "")]) + p.handle.hasErrors = true + else: discard result = p.handle proc getAst*(p: Parser): Ast {.gcsafe.} = diff --git a/tim.nimble b/tim.nimble index 29e3676..eda1bf5 100644 --- a/tim.nimble +++ b/tim.nimble @@ -2,7 +2,7 @@ version = "0.1.0" author = "George Lemon" -description = "A new awesome nimble package" +description = "A super fast template engine for cool kids!" license = "MIT" srcDir = "src" installExt = @["nim"]