@@ -66,6 +66,20 @@ block extendvancodeAstAndCodeGen:
6666
6767 # Extends the AST module with new node constructors and utilities
6868 # for HTML elements and macros
69+
70+ extendCaseStmt " astHashCase" :
71+ case node.kind
72+ of nkHtmlElement:
73+ h = h !& hash (node.tag)
74+ for attr in node.attributes:
75+ h = h !& hash (attr.attrType)
76+ h = h !& hash (attr.attrNode)
77+ for child in node.childElements:
78+ h = h !& hash (child)
79+ of nkHtmlAttribute:
80+ h = h !& hash (node.attrType)
81+ h = h !& hash (node.attrNode)
82+
6983 extendModule " vancode" / " interpreter" / " ast.nim" :
7084 const voidHtmlElements* = [tagArea, tagBase, tagBr, tagCol,
7185 tagEmbed, tagHr, tagImg, tagInput, tagLink, tagMeta,
@@ -232,10 +246,118 @@ block extendvancodeAstAndCodeGen:
232246 result = $ node.tag
233247
234248 extendModule " vancode" / " interpreter" / " codegen.nim" :
249+
250+ proc genMacro * (node: Node , isInstantiation = false ): Sym {.codegen .}
251+
252+ proc procCall * (node: Node , procSym: Sym ): Sym {.codegen .} =
253+ var argTypes: seq [Sym ]
254+ let hasTrailingStmt = node.len > 1 and (node[^ 1 ].kind in {nkHtmlElement, nkIf, nkFor, nkCall})
255+ let isMacroSym = procSym.kind == skProc and procSym.procType == ProcType .procTypeMacro
256+
257+ proc bindStatementBody (macroImpl: Node , injectedStmt: Node ) =
258+ if macroImpl == nil or macroImpl.len < 4 : return
259+ let body = macroImpl[3 ]
260+ if body == nil or body.kind != nkBlock: return
261+
262+ for i in 0 .. body.children.high:
263+ let child = body[i]
264+ if child.kind == nkMacro and child.len >= 4 and child[0 ].kind == nkIdent and child[0 ].ident == " @statement" :
265+ var blk = ast.newNode (nkBlock)
266+ if injectedStmt.kind == nkBlock:
267+ blk = deepCopy (injectedStmt)
268+ else :
269+ blk.add (deepCopy (injectedStmt))
270+ child[3 ] = blk
271+ return
272+
273+ if isMacroSym and hasTrailingStmt:
274+ let injectedBlock = node[^ 1 ]
275+ let keyHash = hash (procSym).int64 xor int64 (injectedBlock.hash ())
276+ # if gen.instantiationCache.hasKey(keyHash):
277+ # result = gen.instantiationCache[keyHash]
278+ # else:
279+ let macroImpl = procSym.impl
280+ if macroImpl == nil :
281+ node.error (" macro implementation missing" )
282+
283+ var clonedImpl = deepCopy (macroImpl)
284+ # unique name for cloned instantiation
285+ let uniqueName = procSym.name.ident & " $inst$" & $ (gen.count ())
286+ clonedImpl[0 ] = newIdent (uniqueName)
287+
288+ # move trailing stmt into inner @statement macro body
289+ bindStatementBody (clonedImpl, injectedBlock)
290+
291+ # remove synthetic `body` param from clone; statement is now baked in
292+ clonedImpl[2 ].children.delete (clonedImpl[2 ].len - 1 )
293+
294+ # compile clone as instantiation (no extra macro injections)
295+ let instSym = gen.genMacro (clonedImpl, isInstantiation = true )
296+
297+ # gen.instantiationCache[keyHash] = instSym
298+ result = instSym
299+
300+ if node.len > 2 :
301+ for arg in node[1 ..^ 2 ]:
302+ let argSym: Sym = gen.genExpr (arg)
303+ assert argSym != nil , " Expression must return a symbol"
304+ argTypes.add (argSym)
305+
306+ return gen.callProc (result , argTypes, errorNode = node)
307+ else :
308+ if node.len > 1 :
309+ for arg in node[1 ..^ 1 ]:
310+ let argSym: Sym = gen.genExpr (arg)
311+ assert argSym != nil , " Expression must return a symbol"
312+ argTypes.add (argSym)
313+ return gen.callProc (procSym, argTypes, errorNode = node)
314+
315+ proc hasParamNamed (formalParams: Node , paramName: string ): bool =
316+ if formalParams == nil or formalParams.kind == nkEmpty or formalParams.len <= 1 :
317+ return false
318+ for defs in formalParams[1 ..^ 1 ]:
319+ if defs.len < 3 : continue
320+ for i in 0 .. (defs.len - 3 ):
321+ var n = defs[i]
322+ if n.kind == nkPostfix and n.len == 2 :
323+ n = n[1 ]
324+ if n.kind == nkIdent and n.ident == paramName:
325+ return true
326+ false
327+
328+ proc genInnerMacro : Node =
329+ # reserved macro slot where trailing statement gets injected
330+ result = ast.newNode (nkMacro)
331+ result .add (ast.newIdent (" @statement" )) # name
332+ result .add (ast.newNode (nkEmpty)) # generic params
333+ let fp = ast.newNode (nkFormalParams) # formal params
334+ fp.add (ast.newNode (nkEmpty)) # return type
335+ result .add (fp)
336+ result .add (ast.newNode (nkBlock)) # body
337+
338+ proc hasInnerStatementMacro (body: Node ): bool =
339+ if body == nil or body.kind != nkBlock: return false
340+ for child in body.children:
341+ if child.kind == nkMacro and child.len > 0 and child[0 ].kind == nkIdent and child[0 ].ident == " @statement" :
342+ return true
343+ false
344+
235345 proc genMacro (node: Node , isInstantiation = false ): Sym {.codegen .} =
236346 # # Generates code for a block of code that contains a procedure.
237347 if not isInstantiation and node[1 ].kind != nkEmpty:
238348 gen.pushScope ()
349+
350+ if not isInstantiation and node[0 ].ident != " @statement" :
351+ if not hasParamNamed (node[2 ], " body" ):
352+ let bodyParam = ast.newNode (nkIdentDefs)
353+ bodyParam.add (ast.newIdent (" body" ))
354+ bodyParam.add (ast.newIdent (" stmt" ))
355+ bodyParam.add (ast.newNode (nkNil))
356+ node[2 ].add (bodyParam)
357+
358+ if not hasInnerStatementMacro (node[3 ]):
359+ node[3 ].children.insert (genInnerMacro (), 0 )
360+
239361 # get some basic metadata
240362 let
241363 name = node[0 ]
@@ -246,12 +368,10 @@ block extendvancodeAstAndCodeGen:
246368 # collect generic params if we're not instantiating
247369 gen.collectGenericParams (node[1 ])
248370 else : none (seq [Sym ])
249- params = gen.collectParams (formalParams, genericParams)
250- returnTy = # empty return type == void
251- if formalParams[0 ].kind != nkEmpty:
252- gen.lookup (formalParams[0 ])
253- else :
254- gen.module.sym " void"
371+
372+ var params = gen.collectParams (formalParams, genericParams)
373+ # macros always return the `stmt` (HTML) type
374+ let returnTy = gen.module.sym " void"
255375
256376 # create a new proc
257377 var (sym, theProc) =
@@ -260,7 +380,7 @@ block extendvancodeAstAndCodeGen:
260380 genKind = gen.kind)
261381 sym.genericParams = genericParams
262382 sym.procType = ProcType .procTypeMacro
263-
383+
264384 # add the proc into the declaration scope
265385 # we need to do this here, otherwise recursive calls will be broken
266386 gen.addSym (sym, scopeOffset = ord (sym.genericParams.isSome))
@@ -279,52 +399,45 @@ block extendvancodeAstAndCodeGen:
279399 procGen.procReturnTy = returnTy
280400
281401 # add the proc's parameters as locals
282- # TODO : closures and upvalues
283402 procGen.pushScope ()
284403 for (name, ty, implValTy, isMut, isOpt) in params:
285404 var varType = if isMut: skVar else : skLet
286405 let param = procGen.declareVar (name, varType, ty)
287406 param.varSet = true # arguments are not assignable
288407
289- # todo
290- # let stmtVar = procGen.declareVar(ast.newIdent("stmt"), skLet, gen.module.sym"any")
291- # stmtVar.varSet = true
292- # procGen.pushDefault(gen.module.sym"string")
293-
408+ # declare ``result`` if applicable
409+ let returnNode = newIdent (" result" )
410+ if returnTy.tyKind != ttyVoid:
411+ let res = newIdent (" result" )
412+ procGen.declareVar (res, skVar, returnTy, isMagic = true )
413+ procGen.pushDefault (returnTy)
414+ procGen.popVar (res)
415+
294416 # define the default `attrs` variable
295417 # this is used to store the attributes of the block.
296418 let attrs = newIdent (" attrs" )
297419 procGen.declareVar (attrs, skVar, gen.module.sym " string" , isMagic = true )
298420 procGen.pushDefault (gen.module.sym " string" )
299421 procGen.popVar (attrs)
300422
301- # defines the default `blockStmt` variable
302- # this is used to store any additional statements
303- # provided at call time
304- # let blockStmt = newIdent("blockStmt")
305- # procGen.declareVar(blockStmt, skVar, gen.module.sym"any", isMagic = true)
306- # procGen.popVar(blockStmt)
307-
308423 # add the proc into the script
309424 gen.script.procs.add (theProc)
310425 if sym.procExport:
426+ # if the proc is exported, we also add it to the export list so it can be
311427 gen.script.procsExport.add (theProc)
312428
313429 # compile the proc's body
314430 discard procGen.genBlock (body, isStmt = true )
315-
316- # if the macro has any deferred code to be executed,
317- # we need to emit it now.
318- # procGen.chunk.emit(opcLoadDeferred)
319-
431+
320432 # finally, return ``result`` if applicable
321433 if returnTy.tyKind != ttyVoid:
322- let resultSym = procGen.lookup (newIdent ( " result " ) )
434+ let resultSym = procGen.lookup (returnNode )
323435 procGen.chunk.emit (opcPushL)
324436 procGen.chunk.emit (resultSym.varStackPos.uint8 )
325437 procGen.chunk.emit (opcReturnVal)
326438 else :
327439 procGen.chunk.emit (opcReturnVoid)
440+ procGen.popScope ()
328441
329442 # pop the generic declaration scope
330443 if not isInstantiation and sym.isGeneric:
0 commit comments