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+
17include ./ private
28
39#
@@ -9,16 +15,36 @@ proc genStmt(node: Node, indent: int = 0) {.codegen.}
915# Lua Transpiler
1016#
1117
12- proc repeatStr (s: string , n: int ): string =
13- for i in 0 ..< n:
14- result .add (s)
18+ proc luaOp (op: string ): string =
19+ case op
20+ of " &" : " .."
21+ of " ==" : " =="
22+ of " !=" : " ~="
23+ of " is" : " =="
24+ of " isnot" : " ~="
25+ of " and" : " and"
26+ of " or" : " or"
27+ of " not" : " not"
28+ of " mod" : " %"
29+ else : op
30+
31+ proc luaEscapeStr (s: string ): string =
32+ result = newStringOfCap (s.len)
33+ for c in s:
34+ case c
35+ of '\n ' : result .add (" \\ n" )
36+ of '\r ' : result .add (" \\ r" )
37+ of '\t ' : result .add (" \\ t" )
38+ of '\\ ' : result .add (" \\\\ " )
39+ of '"' : result .add (" \\\" " )
40+ else : result .add (c)
1541
1642proc getImplValue (node: Node , unquoted = true ): string =
1743 case node.kind
1844 of nkInt: $ node.intVal
1945 of nkFloat: $ node.floatVal
2046 of nkString:
21- if unquoted: " \" " & node.stringVal & " \" "
47+ if unquoted: " \" " & luaEscapeStr ( node.stringVal) & " \" "
2248 else : node.stringVal
2349 of nkBool:
2450 if node.boolVal: " true"
@@ -37,42 +63,39 @@ proc getImplValue(node: Node, unquoted = true): string =
3763 else : " "
3864
3965proc writeVar (node: Node , indent: int = 0 ): string {.codegen .} =
40- let ind = repeatStr (" " , indent)
66+ let ind = repeat (" " , indent)
4167 for decl in node[0 ]:
42- let varName = decl[0 ].ident
68+ let varName = if decl[ 0 ].kind == nkIdent: decl[ 0 ].ident else : decl[ 0 ] [0 ].ident
4369 let value = decl[^ 1 ].getImplValue
4470 result .add (ind & " local " & varName & " = " & value & " \n " )
4571
46- proc renderHandle (node: Node , unquoted = true ): string =
72+ proc exprToString (node: Node ): string =
4773 case node.kind
48- of nkString:
49- if not unquoted: " \" " & node.stringVal & " \" "
50- else : node.stringVal
51- of nkInt:
52- $ node.intVal
53- of nkFloat:
54- $ node.floatVal
74+ of nkString: " \" " & luaEscapeStr (node.stringVal) & " \" "
75+ of nkInt: $ node.intVal
76+ of nkFloat: $ node.floatVal
5577 of nkBool:
5678 if node.boolVal: " true" else : " false"
57- of nkIdent:
58- node.ident
59- of nkPrefix:
60- node[0 ].renderHandle & node[1 ].renderHandle
61- of nkPostfix:
62- node[0 ].renderHandle & node[1 ].renderHandle
79+ of nkIdent: node.ident
80+ of nkPrefix: luaOp (node[0 ].ident) & exprToString (node[1 ])
81+ of nkPostfix: exprToString (node[0 ]) & luaOp (node[1 ].ident)
6382 of nkInfix:
64- node[1 ].renderHandle & ' ' & node[0 ].renderHandle & ' ' & node[2 ].renderHandle
83+ let op = if node[0 ].kind == nkIdent: luaOp (node[0 ].ident) else : luaOp (node[0 ].render)
84+ " (" & exprToString (node[1 ]) & " " & op & " " & exprToString (node[2 ]) & " )"
6585 of nkCall:
66- node[0 ].renderHandle & '(' & node[1 ..^ 1 ].mapIt (it.renderHandle).join (" , " ) & ')'
86+ let callee = if node[0 ].kind == nkIdent: node[0 ].ident else : exprToString (node[0 ])
87+ callee & " (" & node[1 ..^ 1 ].mapIt (exprToString (it)).join (" , " ) & " )"
88+ of nkBracket:
89+ exprToString (node [0 ]) & " [" & exprToString (node [1 ]) & " ]"
90+ of nkDot:
91+ exprToString (node[0 ]) & " ." & exprToString (node[1 ])
6792 else :
6893 " "
6994
70- proc writeHtml (node: Node , indent: int = 0 ): string {.codegen .} =
71- let tag = node.getTag ()
72- var
73- classNames: seq [string ] = @ []
74- idVal: string = " "
75- customAttrs: seq [(string , string )] = @ []
95+ proc attrsToLua (node: Node ): (seq [string ], string , seq [(string , string )]) =
96+ var classNames: seq [string ] = @ []
97+ var idVal: string = " "
98+ var customAttrs: seq [(string , string )] = @ []
7699 for attr in node.attributes:
77100 if attr.kind == nkHtmlAttribute:
78101 case attr.attrType
@@ -82,86 +105,85 @@ proc writeHtml(node: Node, indent: int = 0): string {.codegen.} =
82105 classNames.add (attr.attrNode.stringVal)
83106 of nkIdent:
84107 classNames.add (attr.attrNode.ident)
85- else :
86- discard
108+ of nkInfix, nkCall:
109+ classNames.add (" \" .. " & exprToString (attr.attrNode) & " .. \" " )
110+ else : discard
87111 of htmlAttrId:
88112 case attr.attrNode.kind
89113 of nkString:
90114 idVal = attr.attrNode.stringVal
91115 of nkIdent:
92116 idVal = attr.attrNode.ident
93- else :
94- discard
117+ of nkInfix, nkCall:
118+ idVal = " \" .. " & exprToString (attr.attrNode) & " .. \" "
119+ else : discard
95120 of htmlAttr:
96- if attr.attrNode.kind == nkInfix:
97- if attr.attrNode[2 ].kind == nkInfix:
98- if attr.attrNode[2 ][0 ].ident == " &" :
99- let left = attr.attrNode[2 ][1 ]
100- let right = attr.attrNode[2 ][2 ]
101- let leftVal =
102- if left.kind == nkString:
103- left.stringVal
104- else :
105- " \" .. " & left.renderHandle (false ) & " .. \" "
106- let rightVal =
107- if right.kind == nkIdent and right.ident.len > 0 and right.ident[0 ] == '$' :
108- " \" .. " & right.ident[1 ..^ 1 ] & " .. \" "
109- else :
110- (if right.kind == nkString: right.stringVal
111- else : " \" .. " & right.renderHandle (false ) & " .. \" " )
112- customAttrs.add ((attr.attrNode[1 ].renderHandle (true ), leftVal & rightVal))
113- else :
114- let key = attr.attrNode[1 ].renderHandle
115- let value =
116- case attr.attrNode[2 ].kind
117- of nkIdent, nkCall:
118- " \" .. " & attr.attrNode[2 ].renderHandle & " .. \" "
119- else :
120- attr.attrNode[2 ].renderHandle
121- customAttrs.add ((key, value))
121+ if attr.attrNode.kind == nkInfix and attr.attrNode.len >= 3 :
122+ let keyNode = attr.attrNode[1 ]
123+ let valNode = attr.attrNode[2 ]
124+ let key = if keyNode.kind == nkIdent: keyNode.ident
125+ elif keyNode.kind == nkString: keyNode.stringVal
126+ else : $ keyNode.render
127+ let valStr = case valNode.kind
128+ of nkString: luaEscapeStr (valNode.stringVal)
129+ of nkInt, nkFloat, nkBool: $ valNode.render
130+ of nkIdent: " \" .. " & valNode.ident & " .. \" "
131+ else : " \" .. " & exprToString (valNode) & " .. \" "
132+ customAttrs.add ((key, valStr))
122133 elif attr.attrNode.kind == nkString:
123134 customAttrs.add ((attr.attrNode.stringVal, " " ))
124- else :
125- discard
126- let ind = repeatStr (" " , indent)
135+ elif attr.attrNode.kind == nkIdent:
136+ customAttrs.add ((attr.attrNode.ident, " " ))
137+ else :
138+ discard
139+ else : discard
140+ (classNames, idVal, customAttrs)
141+
142+ proc writeHtml (node: Node , indent: int = 0 ): string {.codegen .} =
143+ let tag = node.getTag ()
144+ let (classNames, idVal, customAttrs) = attrsToLua (node)
145+ let ind = repeat (" " , indent)
146+
127147 result .add (ind & " html = html .. \" <" & tag)
148+
128149 if classNames.len > 0 :
129- result .add (" class=\\\" " & classNames.join (" " ) & " \\\" " )
150+ result .add (" class=\\\" " )
151+ var first = true
152+ for c in classNames:
153+ if not first: result .add (" " )
154+ first = false
155+ result .add (c)
156+ result .add (" \\\" " )
157+
130158 if idVal.len > 0 :
131159 result .add (" id=\\\" " & idVal & " \\\" " )
132- for (name, value) in customAttrs:
160+
161+ for (key, value) in customAttrs:
133162 if value.len > 0 :
134- result .add (" " & name & " =\\\" " & value & " \\\" " )
163+ result .add (" " & key & " =\\\" " & value & " \\\" " )
135164 else :
136- result .add (" " & name )
165+ result .add (" " & key )
137166 result .add (" >\"\n " )
167+
138168 for child in node.childElements:
139169 case child.kind
140- of nkBool, nkInt, nkFloat:
141- result .add (ind & " html = html .. " & child.renderHandle & " \n " )
142170 of nkString:
143- if tag == " script" :
144- let js = minifyInlineJs (child.stringVal)
145- result .add (ind & " html = html .. [[" )
146- result .add (js)
147- result .add (" ]]\n " )
148- else :
149- result .add (ind & " html = html .. " & child.renderHandle (false ) & " \n " )
171+ result .add (ind & " html = html .. \" " & luaEscapeStr (child.stringVal) & " \"\n " )
172+ of nkBool, nkInt, nkFloat:
173+ result .add (ind & " html = html .. tostring(" & $ child.render & " )\n " )
150174 of nkIdent:
151- result .add (ind & " html = html .. " & child.renderHandle & " \n " )
152- of nkCall:
153- if child[0 ].ident[0 ] == '@' :
154- discard
155- else :
156- result .add (gen.genStmt (child, indent))
175+ result .add (ind & " html = html .. tostring(" & child.ident & " )\n " )
176+ of nkInfix, nkPrefix, nkPostfix, nkCall, nkBracket, nkDot:
177+ result .add (ind & " html = html .. tostring(" & exprToString (child) & " )\n " )
157178 else :
158179 result .add (gen.genStmt (child, indent))
180+
159181 if node.tag notin voidHtmlElements:
160182 result .add (ind & " html = html .. \" </" & tag & " >\"\n " )
161183
162184proc genStmt (node: Node , indent: int = 0 ): Rope {.codegen .} =
163185 result = Rope ()
164- let ind = repeatStr (" " , indent)
186+ let ind = repeat (" " , indent)
165187 case node.kind
166188 of nkVar, nkLet, nkConst:
167189 result .add (gen.writeVar (node, indent))
@@ -195,12 +217,12 @@ proc genStmt(node: Node, indent: int = 0): Rope {.codegen.} =
195217 of nkHtmlElement:
196218 result .add (gen.writeHtml (node, indent))
197219 of nkIf:
198- result .add (ind & " if " & node[0 ].render & " then\n " )
220+ result .add (ind & " if " & exprToString ( node[0 ]) & " then\n " )
199221 result .add (gen.genStmt (node[1 ], indent + 1 ))
200222 let hasElse = node.children.len mod 2 == 1
201223 let elifBranches = if hasElse: node[2 ..^ 2 ] else : node[2 ..^ 1 ]
202224 for i in countup (0 , elifBranches.len - 1 , 2 ):
203- result .add (ind & " elseif " & elifBranches[i].render & " then\n " )
225+ result .add (ind & " elseif " & exprToString ( elifBranches[i]) & " then\n " )
204226 result .add (gen.genStmt (elifBranches[i + 1 ], indent + 1 ))
205227 if hasElse:
206228 result .add (ind & " else\n " )
@@ -209,56 +231,80 @@ proc genStmt(node: Node, indent: int = 0): Rope {.codegen.} =
209231 of nkFor:
210232 let varName = node[0 ].render
211233 let iterable = node[1 ]
212- if iterable .kind == nkInfix and iterable [0 ].kind == nkIdent and iterable [0 ].ident == " .." :
213- let startVal = iterable [1 ].render
214- let endVal = iterable [2 ].render
234+ if iterable .kind == nkCall and iterable [0 ].kind == nkIdent and iterable [0 ].ident == " .." :
235+ let startVal = exprToString ( iterable [1 ])
236+ let endVal = exprToString ( iterable [2 ])
215237 result .add (ind & " for " & varName & " = " & startVal & " , " & endVal & " do\n " )
216238 result .add (gen.genStmt (node[2 ], indent + 1 ))
217239 result .add (ind & " end\n " )
218240 else :
219- if iterable [0 ].ident == " .." :
220- # a tim range `0..10` in lua shoild be `0, 10`
221- result .add (ind & " for x, " & varName & " in " & iterable [1 ].render & " , " & iterable [2 ].render & " do\n " )
222- else :
223- result .add (ind & " for _, " & varName & " in ipairs(" & iterable .render & " ) do\n " )
241+ let iterExpr = exprToString (iterable )
242+ result .add (ind & " for _, " & varName & " in ipairs(" & iterExpr & " ) do\n " )
224243 result .add (gen.genStmt (node[2 ], indent + 1 ))
225244 result .add (ind & " end\n " )
226- of nkCall:
227- if node[0 ].ident == " echo" :
228- result .add (ind & " print(" & node[1 ..^ 1 ].mapIt (it.render).join (" , " ) & " )\n " )
229- else :
230- result .add (ind & node[0 ].render & " (" & node[1 ..^ 1 ].mapIt (it.render).join (" , " ) & " )\n " )
245+ of nkWhile:
246+ result .add (ind & " while " & exprToString (node[0 ]) & " do\n " )
247+ result .add (gen.genStmt (node[1 ], indent + 1 ))
248+ result .add (ind & " end\n " )
231249 of nkBlock:
232250 for i, s in node:
233251 result .add (gen.genStmt (s, indent))
234252 of nkReturn:
235253 if node[0 ].kind != nkEmpty:
236- result .add (ind & " return " & node[0 ].render & " \n " )
254+ result .add (ind & " return " & exprToString ( node[0 ]) & " \n " )
237255 else :
238256 result .add (ind & " return\n " )
239257 of nkBreak:
240258 result .add (ind & " break\n " )
241259 of nkContinue:
242260 result .add (ind & " goto continue\n " )
243- of nkWhile:
244- result .add (ind & " while " & node[0 ].render & " do\n " )
245- result .add (gen.genStmt (node[1 ], indent + 1 ))
246- result .add (ind & " end\n " )
261+ of nkCall:
262+ let callee = if node[0 ].kind == nkIdent: node[0 ].ident else : exprToString (node[0 ])
263+ if callee == " echo" :
264+ result .add (ind & " print(" & node[1 ..^ 1 ].mapIt (exprToString (it)).join (" , " ) & " )\n " )
265+ else :
266+ result .add (ind & callee & " (" & node[1 ..^ 1 ].mapIt (exprToString (it)).join (" , " ) & " )\n " )
267+ of nkInfix, nkPrefix, nkPostfix:
268+ let op = if node.kind == nkInfix and node[0 ].kind == nkIdent: node[0 ].ident
269+ elif node.kind == nkInfix: node[0 ].render
270+ else : " "
271+ if op == " =" :
272+ result .add (ind & exprToString (node[1 ]) & " = " & exprToString (node[2 ]) & " \n " )
273+ else :
274+ result .add (ind & exprToString (node) & " \n " )
275+ of nkIdent:
276+ result .add (ind & node.ident & " \n " )
277+ of nkRawHtml:
278+ result .add (ind & " html = html .. [[" & node.rawHtml & " ]]\n " )
279+ of nkCssSnippet:
280+ let css = node.snippetCode
281+ result .add (ind & " html = html .. \" <style>\"\n " )
282+ result .add (ind & " html = html .. [[" & css & " ]]\n " )
283+ result .add (ind & " html = html .. \" </style>\"\n " )
284+ of nkJavaScriptSnippet:
285+ let js = node.snippetCode
286+ result .add (ind & " html = html .. \" <script>\"\n " )
287+ result .add (ind & js & " \n " )
288+ result .add (ind & " html = html .. \" </script>\"\n " )
289+ of nkDocComment:
290+ if node.comment.len > 0 :
291+ result .add (ind & " --[[ " & node.comment & " ]]\n " )
247292 else : discard
248293
249294proc genScript * (program: Ast , includePath: Option [string ],
250295 isMainScript: static bool = false ,
251296 isSnippet: static bool = false ,
252297 withReturnStmt: static bool = true ) {.codegen .} =
253- result .add (" local $1 = {}\n " % [gen.module.getModuleName ()])
298+ let modName = gen.module.getModuleName ()
299+ result .add (" local $1 = {}\n " % [modName])
254300 result .add (" \n -- @param args Table\n -- @return string The generated HTML.\n " )
255- result .add (" function $1.render(args)\n " % [gen.module. getModuleName () ])
301+ result .add (" function $1.render(args)\n " % [modName ])
256302 result .add (" local app = args.app or {}\n " )
257- result .add (" local this = args.app or {}\n " )
303+ result .add (" local this = args.this or {}\n " )
258304 result .add (" local html = ''\n " )
259305 for node in program.nodes:
260306 result .add (gen.genStmt (node, 1 ))
261307 result .add (" return html\n " )
262308 result .add (" end\n " )
263309 when withReturnStmt == true :
264- result .add (" return $1\n " % [gen.module. getModuleName () ])
310+ result .add (" return $1\n " % [modName ])
0 commit comments