Skip to content

Commit ebaa2be

Browse files
committed
update lua transpiler
Signed-off-by: George Lemon <georgelemon@protonmail.com>
1 parent 9b40fcc commit ebaa2be

File tree

1 file changed

+317
-0
lines changed

1 file changed

+317
-0
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
# A super fast template engine for cool kids
2+
#
3+
# (c) 2025 George Lemon | LGPL-v3 License
4+
# Made by Humans from OpenPeeps
5+
# https://github.com/openpeeps/tim | https://openpeeps.dev/packages/tim
6+
7+
import std/[macros, options, os, sequtils,
8+
strutils, ropes, tables]
9+
10+
import pkg/voodoo/language/[ast, chunk, errors, sym]
11+
12+
type
13+
GenKind = enum
14+
gkToplevel
15+
gkProc
16+
gkBlockProc
17+
gkIterator
18+
19+
CodeGen* {.acyclic.} = object
20+
## a code generator for a module or proc.
21+
script: Script # the script all procs go into
22+
module: Module # the global scope
23+
chunk: Chunk # the chunk of code we're generating
24+
case kind: GenKind # what this generator generates
25+
of gkToplevel: discard
26+
of gkProc, gkBlockProc:
27+
procReturnTy: Sym # the proc's return type
28+
of gkIterator:
29+
iter: Sym # the symbol representing the iterator
30+
iterForBody: Node # the for loop's body
31+
iterForVar: Node # the for loop variable's name
32+
iterForCtx: Context # the for loop's context
33+
counter: uint16
34+
const
35+
utilsJS = staticRead(currentSourcePath().parentDir / "tim.js")
36+
37+
proc initCodeGen*(script: Script, module: Module, chunk: Chunk,
38+
kind = gkToplevel): CodeGen =
39+
result = CodeGen(script: script, module: module,
40+
chunk: chunk, kind: kind)
41+
42+
template genGuard(body) =
43+
## Wraps ``body`` in a "guard" used for code generation. The guard sets the
44+
## line information in the target chunk. This is a helper used by {.codegen.}.
45+
when declared(node):
46+
let
47+
oldFile = gen.chunk.file
48+
oldLn = gen.chunk.ln
49+
oldCol = gen.chunk.col
50+
# gen.chunk.file = node.file
51+
gen.chunk.ln = node.ln
52+
gen.chunk.col = node.col
53+
body
54+
when declared(node):
55+
gen.chunk.file = oldFile
56+
gen.chunk.ln = oldLn
57+
gen.chunk.col = oldCol
58+
59+
macro codegen(theProc: untyped): untyped =
60+
## Wrap ``theProc``'s body in a call to ``genGuard``.
61+
theProc[3][0] = ident"Rope"
62+
theProc.params.insert(1,
63+
newIdentDefs(ident"gen", nnkVarTy.newTree(ident"CodeGen")))
64+
if theProc[^1].kind != nnkEmpty:
65+
let body = nnkStmtList.newTree(
66+
newAssignment( ident"result", newCall(ident"rope")),
67+
theProc[^1]
68+
)
69+
theProc[^1] = newCall("genGuard", body)
70+
result = theProc
71+
72+
#
73+
# Forward declarations
74+
#
75+
proc genStmt(node: Node, indent: int = 0) {.codegen.}
76+
77+
#
78+
# Lua Transpiler
79+
#
80+
81+
proc repeatStr(s: string, n: int): string =
82+
for i in 0..<n:
83+
result.add(s)
84+
85+
proc getImplValue(node: Node, unquoted = true): string =
86+
case node.kind
87+
of nkInt: $node.intVal
88+
of nkFloat: $node.floatVal
89+
of nkString:
90+
if unquoted: "\"" & node.stringVal & "\""
91+
else: node.stringVal
92+
of nkBool:
93+
if node.boolVal: "true"
94+
else: "false"
95+
of nkArray:
96+
"{" & node.children.mapIt(getImplValue(it)).join(", ") & "}"
97+
of nkObject:
98+
"{ " & node.children.mapIt(
99+
if it.kind == nkIdentDefs:
100+
let key = it[0].render
101+
let value = getImplValue(it[^1])
102+
key & " = " & value
103+
else:
104+
""
105+
).join(", ") & " }"
106+
else: ""
107+
108+
proc writeVar(node: Node, indent: int = 0): string {.codegen.} =
109+
let ind = repeatStr(" ", indent)
110+
for decl in node:
111+
let varName = decl[0].ident
112+
let value = decl[^1].getImplValue
113+
result.add(ind & varName & " = " & value & "\n")
114+
115+
proc renderHandle(node: Node, unquoted = true): string =
116+
case node.kind
117+
of nkString:
118+
if not unquoted: "\"" & node.stringVal & "\""
119+
else: node.stringVal
120+
of nkInt:
121+
$node.intVal
122+
of nkFloat:
123+
$node.floatVal
124+
of nkBool:
125+
if node.boolVal: "true" else: "false"
126+
of nkIdent:
127+
node.ident
128+
of nkPrefix:
129+
node[0].renderHandle & node[1].renderHandle
130+
of nkPostfix:
131+
node[0].renderHandle & node[1].renderHandle
132+
of nkInfix:
133+
node[1].renderHandle & ' ' & node[0].renderHandle & ' ' & node[2].renderHandle
134+
of nkCall:
135+
node[0].renderHandle & '(' & node[1..^1].mapIt(it.renderHandle).join(", ") & ')'
136+
else:
137+
""
138+
139+
proc writeHtml(node: Node, indent: int = 0): string {.codegen.} =
140+
let tag = node.getTag()
141+
var
142+
classNames: seq[string] = @[]
143+
idVal: string = ""
144+
customAttrs: seq[(string, string)] = @[]
145+
for attr in node.attributes:
146+
if attr.kind == nkHtmlAttribute:
147+
case attr.attrType
148+
of htmlAttrClass:
149+
case attr.attrNode.kind
150+
of nkString:
151+
classNames.add(attr.attrNode.stringVal)
152+
of nkIdent:
153+
classNames.add(attr.attrNode.ident)
154+
else:
155+
discard
156+
of htmlAttrId:
157+
case attr.attrNode.kind
158+
of nkString:
159+
idVal = attr.attrNode.stringVal
160+
of nkIdent:
161+
idVal = attr.attrNode.ident
162+
else:
163+
discard
164+
of htmlAttr:
165+
if attr.attrNode.kind == nkInfix:
166+
if attr.attrNode[2].kind == nkInfix:
167+
if attr.attrNode[2][0].ident == "&":
168+
let left = attr.attrNode[2][1]
169+
let right = attr.attrNode[2][2]
170+
let leftVal =
171+
if left.kind == nkString:
172+
left.stringVal
173+
else:
174+
"\" .. " & left.renderHandle(false) & " .. \""
175+
let rightVal =
176+
if right.kind == nkIdent and right.ident.len > 0 and right.ident[0] == '$':
177+
"\" .. "& right.ident[1..^1] & " .. \""
178+
else:
179+
(if right.kind == nkString: right.stringVal
180+
else: "\" .. " & right.renderHandle(false) & " .. \"")
181+
customAttrs.add((attr.attrNode[1].renderHandle(true), leftVal & rightVal))
182+
else:
183+
let key = attr.attrNode[1].renderHandle
184+
let value =
185+
case attr.attrNode[2].kind
186+
of nkIdent, nkCall:
187+
"\" .. " & attr.attrNode[2].renderHandle & " .. \""
188+
else:
189+
attr.attrNode[2].renderHandle
190+
customAttrs.add((key, value))
191+
elif attr.attrNode.kind == nkString:
192+
customAttrs.add((attr.attrNode.stringVal, ""))
193+
else:
194+
discard
195+
let ind = repeatStr(" ", indent)
196+
result.add(ind & "html = html .. \"<" & tag)
197+
if classNames.len > 0:
198+
result.add(" class=\\\"" & classNames.join(" ") & "\\\"")
199+
if idVal.len > 0:
200+
result.add(" id=\\\"" & idVal & "\\\"")
201+
for (name, value) in customAttrs:
202+
if value.len > 0:
203+
result.add(" " & name & "=\\\"" & value & "\\\"")
204+
else:
205+
result.add(" " & name)
206+
result.add(">\"\n")
207+
for child in node.childElements:
208+
case child.kind
209+
of nkBool, nkInt, nkFloat:
210+
result.add(ind & "html = html .. " & child.renderHandle & "\n")
211+
of nkString:
212+
result.add(ind & "html = html .. " & child.renderHandle(false) & "\n")
213+
of nkCall:
214+
if child[0].ident[0] == '@':
215+
discard
216+
else:
217+
result.add(gen.genStmt(child, indent + 2))
218+
else:
219+
result.add(gen.genStmt(child, indent + 2))
220+
if node.tag notin voidHtmlElements:
221+
result.add(ind & "html = html .. \"</" & tag & ">\"\n")
222+
223+
proc genStmt(node: Node, indent: int = 0): Rope {.codegen.} =
224+
result = Rope()
225+
let ind = repeatStr(" ", indent)
226+
case node.kind
227+
of nkVar, nkLet, nkConst:
228+
result.add(gen.writeVar(node, indent))
229+
of nkProc:
230+
let fnName = node[0].render
231+
let params = node[2]
232+
result.add(ind & "function " & fnName & "(")
233+
result.add(params[1..^1].mapIt(it[0].render).join(", "))
234+
result.add(")\n")
235+
for param in params[1..^1]:
236+
if param.kind == nkIdentDefs:
237+
let pname = param[0].render
238+
result.add(ind & " -- @param " & pname & "\n")
239+
result.add(gen.genStmt(node[3], indent + 1))
240+
result.add(ind & "end\n")
241+
of nkMacro:
242+
let fnName = node[0].ident[1..^1]
243+
let params = node[2]
244+
result.add(ind & "function " & fnName & "(")
245+
result.add(params[1..^1].mapIt(it[0].render).join(", "))
246+
result.add(")\n")
247+
for param in params[1..^1]:
248+
if param.kind == nkIdentDefs:
249+
let pname = param[0].render
250+
result.add(ind & " -- @param " & pname & "\n")
251+
result.add(ind & " -- @return string HTML\n")
252+
result.add(ind & " local html = ''\n")
253+
result.add(gen.genStmt(node[3], indent + 1))
254+
result.add(ind & " return html\n")
255+
result.add(ind & "end\n")
256+
of nkHtmlElement:
257+
result.add(gen.writeHtml(node, indent))
258+
of nkIf:
259+
result.add(ind & "if " & node[0].render & " then\n")
260+
result.add(gen.genStmt(node[1], indent + 1))
261+
let hasElse = node.children.len mod 2 == 1
262+
let elifBranches = if hasElse: node[2..^2] else: node[2..^1]
263+
for i in countup(0, elifBranches.len - 1, 2):
264+
result.add(ind & "elseif " & elifBranches[i].render & " then\n")
265+
result.add(gen.genStmt(elifBranches[i + 1], indent + 1))
266+
if hasElse:
267+
result.add(ind & "else\n")
268+
result.add(gen.genStmt(node[^1], indent + 1))
269+
result.add(ind & "end\n")
270+
of nkFor:
271+
let varName = node[0].render
272+
let iterable = node[1]
273+
if iterable.kind == nkInfix and iterable[0].kind == nkIdent and iterable[0].ident == "..":
274+
let startVal = iterable[1].render
275+
let endVal = iterable[2].render
276+
result.add(ind & "for " & varName & " = " & startVal & ", " & endVal & " do\n")
277+
result.add(gen.genStmt(node[2], indent + 1))
278+
result.add(ind & "end\n")
279+
else:
280+
result.add(ind & "for _, " & varName & " in ipairs(" & iterable.render & ") do\n")
281+
result.add(gen.genStmt(node[2], indent + 1))
282+
result.add(ind & "end\n")
283+
of nkCall:
284+
if node[0].ident == "echo":
285+
result.add(ind & "print(" & node[1..^1].mapIt(it.render).join(", ") & ")\n")
286+
else:
287+
result.add(ind & node[0].render & "(" & node[1..^1].mapIt(it.render).join(", ") & ")\n")
288+
of nkBlock:
289+
for i, s in node:
290+
result.add(gen.genStmt(s, indent))
291+
of nkReturn:
292+
if node[0].kind != nkEmpty:
293+
result.add(ind & "return " & node[0].render & "\n")
294+
else:
295+
result.add(ind & "return\n")
296+
of nkBreak:
297+
result.add(ind & "break\n")
298+
of nkContinue:
299+
result.add(ind & "goto continue\n")
300+
of nkWhile:
301+
result.add(ind & "while " & node[0].render & " do\n")
302+
result.add(gen.genStmt(node[1], indent + 1))
303+
result.add(ind & "end\n")
304+
else: discard
305+
306+
proc genScript*(program: Ast, includePath: Option[string],
307+
isMainScript: static bool = false,
308+
isSnippet: static bool = false) {.codegen.} =
309+
result.add("local $1 = {}\n" % [gen.module.getModuleName()])
310+
result.add("\n-- @param ... Additional arguments (not used in this method).\n-- @return string The generated HTML.\n")
311+
result.add("function $1.render(...)\n" % [gen.module.getModuleName()])
312+
result.add(" local html = ''\n")
313+
for node in program.nodes:
314+
result.add(gen.genStmt(node, 2))
315+
result.add(" return html\n")
316+
result.add("end\n")
317+
result.add("return $1\n" % [gen.module.getModuleName()])

0 commit comments

Comments
 (0)