Skip to content

Commit 81a0490

Browse files
committed
add more extensions
Signed-off-by: George Lemon <georgelemon@protonmail.com>
1 parent 80c3587 commit 81a0490

9 files changed

Lines changed: 258 additions & 59 deletions

File tree

src/tim.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ elif isMainModule:
6060
?bool("--nocache"), # tells Tim to import modules and rebuild cache
6161
?bool("--bench"): # benchmark operations
6262
## Transpile `timl` to specific target source
63-
63+
6464
ast path(timl):
6565
## Transpile timl code to AST representation
6666

src/tim/app/build.nim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import pkg/flatty
1010
import pkg/kapsis/runtime
1111
import pkg/kapsis/interactive/prompts
1212

13-
import pkg/vancode/interpreter/[ast, codegen, chunk, sym, vm, value]
13+
import pkg/vancode/interpreter/[ast, codegen, chunk, sym, vm, value, resolver]
1414
import pkg/vancode/manager/packager
1515

1616
import ../engine/parser
1717
import ../engine/stdlib/[libsystem, libarrays]
1818
import ../engine/transpilers/[jsgen, pygen, rbgen, phpgen, luagen, nimgen]
1919

20-
proc parserCallback(astProgram: var Ast, path: string) =
20+
proc parserCallback(astProgram: var Ast, path: string, resolver: FileResolver) =
2121
parser.parseScript(astProgram, readFile(path), path)
2222

2323
proc declareGlobals(compiler: codegen.CodeGen) =

src/tim/engine/parser.nim

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,7 @@ proc parseMacroFunctionHead(p: var Parser, isAnon: bool;
839839
else:
840840
name = ast.newEmpty()
841841
genericParams = ast.newEmpty() # todo
842-
formalParams = newTree(nkFormalParams, newEmpty())
842+
formalParams = newTree(nkFormalParams, newSeq[Node](0))
843843
if p.curr is tkLP:
844844
var params: seq[Node]
845845
if p.parseCommaIdentList(tkLP, tkRP, params):
@@ -856,7 +856,7 @@ prefixHandle parseMacroFunction:
856856
# parse function statement
857857
let fnBlock: Node = p.parseBlock(fnpos, parseFnBlock = true)
858858
caseNotNil fnBlock:
859-
result = ast.newTree(nkMacro, name, genericParams, formalParams, fnBlock)
859+
result = ast.newTree(nkMacro, name, genericParams, formalParams, fnBlock)
860860

861861
prefixHandle parseMacroCall:
862862
# parse a block call
@@ -924,7 +924,8 @@ prefixHandle parseMacroCall:
924924

925925
prefixHandle parseCall:
926926
# parse a function call
927-
result = ast.newCall(ast.newIdent(p.curr.value))
927+
let fnName = ast.newIdent(p.curr.value, p.curr.line, p.curr.col)
928+
result = ast.newCall(fnName)
928929
var expectRP: bool
929930
walk p # tkIdentifier
930931

src/tim/engine/stdlib/libsystem.nim

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ proc loadLibrary*(script: Script): Module =
276276

277277
script.addProc(result, "echo", @[paramDef("x", ttyString)], ttyVoid,
278278
proc (args: StackView, argc: int): Value =
279-
echo args[0].stringVal[])
279+
if likely(args[0].typeId == tyString):
280+
echo args[0].stringVal[]
281+
else:
282+
echo "<nil>"
283+
)
280284

281285
script.addProc(result, "echo", @[paramDef("x", ttyInt)], ttyVoid,
282286
proc (args: StackView, argc: int): Value =

src/tim/engine/transformers.nim

Lines changed: 139 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)