clone url: git://git.m455.casa/fa
esperbuild/espersrc/fennel-0.7.0/src/fennel/compiler.fnl
1 | ;; This is the core compiler module responsible for taking a parsed AST |
2 | ;; and turning it into Lua code. Main entry points are `compile` (which |
3 | ;; takes an AST), `compile-stream` and `compile-string`. |
4 |
|
5 | (local utils (require "fennel.utils")) |
6 | (local parser (require "fennel.parser")) |
7 | (local friend (require :fennel.friend)) |
8 |
|
9 | (local unpack (or _G.unpack table.unpack)) |
10 |
|
11 | (local scopes []) |
12 |
|
13 | (fn make-scope [parent] |
14 | "Create a new Scope, optionally under a parent scope. |
15 | Scopes are compile time constructs that are responsible for keeping track of |
16 | local variables, name mangling, and macros. They are accessible to user code |
17 | via the 'eval-compiler' special form (may change). They use metatables to |
18 | implement nesting. " |
19 | (let [parent (or parent scopes.global)] |
20 | {:includes (setmetatable [] {:__index (and parent parent.includes)}) |
21 | :macros (setmetatable [] {:__index (and parent parent.macros)}) |
22 | :manglings (setmetatable [] {:__index (and parent parent.manglings)}) |
23 | :refedglobals (setmetatable [] {:__index (and parent parent.refedglobals)}) |
24 | :specials (setmetatable [] {:__index (and parent parent.specials)}) |
25 | :symmeta (setmetatable [] {:__index (and parent parent.symmeta)}) |
26 | :unmanglings (setmetatable [] {:__index (and parent parent.unmanglings)}) |
27 |
|
28 | :autogensyms [] |
29 | :vararg (and parent parent.vararg) |
30 | :depth (if parent (+ (or parent.depth 0) 1) 0) |
31 | :hashfn (and parent parent.hashfn) |
32 | :parent parent})) |
33 |
|
34 | ;; If you add new calls to this function, please update fennel.friend |
35 | ;; as well to add suggestions for how to fix the new error! |
36 | (fn assert-compile [condition msg ast] |
37 | "Assert a condition and raise a compile error with line numbers. |
38 | The ast arg should be unmodified so that its first element is the form called." |
39 | (when (not condition) |
40 | (let [{: source : unfriendly} (or utils.root.options {})] |
41 | (utils.root.reset) |
42 | (if unfriendly |
43 | (let [m (getmetatable ast) |
44 | filename (or (and m m.filename) ast.filename "unknown") |
45 | line (or (and m m.line) ast.line "?") |
46 | target (tostring (if (utils.sym? (. ast 1)) |
47 | (utils.deref (. ast 1)) |
48 | (or (. ast 1) "()")))] |
49 | ;; if we use regular `assert' we can't set level to 0 |
50 | (error (string.format "Compile error in '%s' %s:%s: %s" |
51 | target filename line msg) 0)) |
52 | (friend.assert-compile condition msg ast source)))) |
53 | condition) |
54 |
|
55 | (set scopes.global (make-scope)) |
56 | (set scopes.global.vararg true) |
57 | (set scopes.compiler (make-scope scopes.global)) |
58 | (set scopes.macro scopes.global) |
59 |
|
60 | ;; Allow printing a string to Lua, also keep as 1 line. |
61 | (local serialize-subst {"\a" "\\a" "\b" "\\b" "\t" "\\t" |
62 | "\n" "n" "\v" "\\v" "\f" "\\f"}) |
63 |
|
64 | (fn serialize-string [str] |
65 | (-> (string.format "%q" str) |
66 | (string.gsub "." serialize-subst) |
67 | (string.gsub "[€-ÿ]" #(.. "\\" ($:byte))))) |
68 |
|
69 | (fn global-mangling [str] |
70 | "Mangler for global symbols. Does not protect against collisions, |
71 | but makes them unlikely. This is the mangling that is exposed to to the world." |
72 | (if (utils.valid-lua-identifier? str) |
73 | str |
74 | (.. "__fnl_global__" |
75 | (str:gsub "[^%w]" #(string.format "_%02x" ($:byte)))))) |
76 |
|
77 | (fn global-unmangling [identifier] |
78 | "Reverse a global mangling. |
79 | Takes a Lua identifier and returns the Fennel symbol string that created it." |
80 | (match (string.match identifier "^__fnl_global__(.*)$") |
81 | rest (pick-values 1 (string.gsub rest "_[%da-f][%da-f]" |
82 | #(string.char (tonumber ($:sub 2) 16)))) |
83 | _ identifier)) |
84 |
|
85 | (var allowed-globals nil) |
86 |
|
87 | (fn global-allowed [name] |
88 | "If there's a provided list of allowed globals, don't let references thru that |
89 | aren't on the list. This list is set at the compiler entry points of compile |
90 | and compile-stream." |
91 | (or (not allowed-globals) (utils.member? name allowed-globals))) |
92 |
|
93 | (fn unique-mangling [original mangling scope append] |
94 | (if (. scope.unmanglings mangling) |
95 | (unique-mangling original (.. original append) scope (+ append 1)) |
96 | mangling)) |
97 |
|
98 | (fn local-mangling [str scope ast temp-manglings] |
99 | "Creates a symbol from a string by mangling it. ensures that the generated |
100 | symbol is unique if the input string is unique in the scope." |
101 | (assert-compile (not (utils.multi-sym? str)) |
102 | (.. "unexpected multi symbol " str) ast) |
103 | (let [;; Mapping mangling to a valid Lua identifier |
104 | raw (if (or (. utils.lua-keywords str) (str:match "^%d")) |
105 | (.. "_" str) |
106 | str) |
107 | mangling (-> raw |
108 | (string.gsub "-" "_") |
109 | (string.gsub "[^%w_]" #(string.format "_%02x" ($:byte)))) |
110 | unique (unique-mangling mangling mangling scope 0)] |
111 | (tset scope.unmanglings unique str) |
112 | (let [manglings (or temp-manglings scope.manglings)] |
113 | (tset manglings str unique)) |
114 | unique)) |
115 |
|
116 | (fn apply-manglings [scope new-manglings ast] |
117 | "Calling this function will mean that further compilation in scope will use |
118 | these new manglings instead of the current manglings." |
119 | (each [raw mangled (pairs new-manglings)] |
120 | (assert-compile (not (. scope.refedglobals mangled)) |
121 | (.. "use of global " raw " is aliased by a local") ast) |
122 | (tset scope.manglings raw mangled))) |
123 |
|
124 | (fn combine-parts [parts scope] |
125 | "Combine parts of a symbol." |
126 | (var ret (or (. scope.manglings (. parts 1)) (global-mangling (. parts 1)))) |
127 | (for [i 2 (# parts)] |
128 | (if (utils.valid-lua-identifier? (. parts i)) |
129 | (if (and parts.multi-sym-method-call (= i (# parts))) |
130 | (set ret (.. ret ":" (. parts i))) |
131 | (set ret (.. ret "." (. parts i)))) |
132 | (set ret (.. ret "[" (serialize-string (. parts i)) "]")))) |
133 | ret) |
134 |
|
135 | (fn gensym [scope base] |
136 | "Generates a unique symbol in the scope." |
137 | (var (append mangling) (values 0 (.. (or base "") "_0_"))) |
138 | (while (. scope.unmanglings mangling) |
139 | (set mangling (.. (or base "") "_" append "_")) |
140 | (set append (+ append 1))) |
141 | (tset scope.unmanglings mangling (or base true)) |
142 | mangling) |
143 |
|
144 | (fn autogensym [base scope] |
145 | "Generates a unique symbol in the scope based on the base name. Calling |
146 | repeatedly with the same base and same scope will return existing symbol |
147 | rather than generating new one." |
148 | (match (utils.multi-sym? base) |
149 | parts (do (tset parts 1 (autogensym (. parts 1) scope)) |
150 | (table.concat parts (or (and parts.multi-sym-method-call ":") "."))) |
151 | _ (or (. scope.autogensyms base) |
152 | (let [mangling (gensym scope (base:sub 1 (- 2)))] |
153 | (tset scope.autogensyms base mangling) |
154 | mangling)))) |
155 |
|
156 | (fn check-binding-valid [symbol scope ast] |
157 | "Check to see if a symbol will be overshadowed by a special." |
158 | (let [name (utils.deref symbol)] |
159 | (assert-compile (not (or (. scope.specials name) (. scope.macros name))) |
160 | (: "local %s was overshadowed by a special form or macro" |
161 | :format name) ast) |
162 | (assert-compile (not (utils.quoted? symbol)) |
163 | (string.format "macro tried to bind %s without gensym" name) |
164 | symbol))) |
165 |
|
166 | (fn declare-local [symbol meta scope ast temp-manglings] |
167 | "Declare a local symbol" |
168 | (check-binding-valid symbol scope ast) |
169 | (let [name (utils.deref symbol)] |
170 | (assert-compile (not (utils.multi-sym? name)) |
171 | (.. "unexpected multi symbol " name) ast) |
172 | (tset scope.symmeta name meta) |
173 | (local-mangling name scope ast temp-manglings))) |
174 |
|
175 | (fn hashfn-arg-name [name multi-sym-parts scope] |
176 | (if (not scope.hashfn) nil |
177 | (= name "$") "$1" |
178 | multi-sym-parts |
179 | (do (when (and multi-sym-parts (= (. multi-sym-parts 1) "$")) |
180 | (tset multi-sym-parts 1 "$1")) |
181 | (table.concat multi-sym-parts ".")))) |
182 |
|
183 | (fn symbol-to-expression [symbol scope reference?] |
184 | "Convert symbol to Lua code. Will only work for local symbols |
185 | if they have already been declared via declare-local" |
186 | (utils.hook :symbol-to-expression symbol scope reference?) |
187 | (let [name (. symbol 1) |
188 | multi-sym-parts (utils.multi-sym? name) |
189 | name (or (hashfn-arg-name name multi-sym-parts scope) name)] |
190 | (let [parts (or multi-sym-parts [name]) |
191 | etype (or (and (> (# parts) 1) "expression") "sym") |
192 | local? (. scope.manglings (. parts 1))] |
193 | (when (and local? (. scope.symmeta (. parts 1))) |
194 | (tset (. scope.symmeta (. parts 1)) "used" true)) |
195 | ;; if it's a reference and not a symbol which introduces a new binding |
196 | ;; then we need to check for allowed globals |
197 | (assert-compile (or (not reference?) local? (global-allowed (. parts 1))) |
198 | (.. "unknown global in strict mode: " (. parts 1)) symbol) |
199 | (when (and allowed-globals (not local?)) |
200 | (tset utils.root.scope.refedglobals (. parts 1) true)) |
201 | (utils.expr (combine-parts parts scope) etype)))) |
202 |
|
203 | (fn emit [chunk out ast] |
204 | "Emit Lua code." |
205 | (if (= (type out) "table") |
206 | (table.insert chunk out) |
207 | (table.insert chunk {:ast ast :leaf out}))) |
208 |
|
209 | (fn peephole [chunk] |
210 | "Do some peephole optimization." |
211 | (if chunk.leaf chunk |
212 | (and (>= (# chunk) 3) |
213 | (= (. (. chunk (- (# chunk) 2)) "leaf") "do") |
214 | (not (. (. chunk (- (# chunk) 1)) "leaf")) |
215 | (= (. (. chunk (# chunk)) "leaf") "end")) |
216 | (let [kid (peephole (. chunk (- (# chunk) 1))) |
217 | new-chunk {:ast chunk.ast}] |
218 | (for [i 1 (- (# chunk) 3)] |
219 | (table.insert new-chunk (peephole (. chunk i)))) |
220 | (for [i 1 (# kid)] |
221 | (table.insert new-chunk (. kid i))) |
222 | new-chunk) |
223 | (utils.map chunk peephole))) |
224 |
|
225 | (fn flatten-chunk-correlated [main-chunk] |
226 | "Correlate line numbers in input with line numbers in output." |
227 | (fn flatten [chunk out last-line file] |
228 | (var last-line last-line) |
229 | (if chunk.leaf |
230 | (tset out last-line (.. (or (. out last-line) "") " " chunk.leaf)) |
231 | (each [_ subchunk (ipairs chunk)] |
232 | (when (or subchunk.leaf (> (# subchunk) 0)) ; ignore empty chunks |
233 | ;; don't increase line unless it's from the same file |
234 | (when (and subchunk.ast (= file subchunk.ast.file)) |
235 | (set last-line (math.max last-line (or subchunk.ast.line 0)))) |
236 | (set last-line (flatten subchunk out last-line file))))) |
237 | last-line) |
238 | (let [out [] |
239 | last (flatten main-chunk out 1 main-chunk.file)] |
240 | (for [i 1 last] |
241 | (when (= (. out i) nil) |
242 | (tset out i ""))) |
243 | (table.concat out "\n"))) |
244 |
|
245 | (fn flatten-chunk [sm chunk tab depth] |
246 | "Flatten a tree of indented Lua source code lines. |
247 | Tab is what is used to indent a block." |
248 | (if chunk.leaf |
249 | (let [code chunk.leaf |
250 | info chunk.ast] |
251 | (when sm |
252 | (table.insert sm (or (and info info.line) (- 1)))) |
253 | code) |
254 | (let [tab (match tab |
255 | true " " false "" tab tab nil "")] |
256 | (fn parter [c] |
257 | (when (or c.leaf (> (# c) 0)) |
258 | (let [sub (flatten-chunk sm c tab (+ depth 1))] |
259 | (if (> depth 0) |
260 | (.. tab (sub:gsub "\n" (.. "\n" tab))) |
261 | sub)))) |
262 | (table.concat (utils.map chunk parter) "\n")))) |
263 |
|
264 | ;; Some global state for all fennel sourcemaps. For the time being, this seems |
265 | ;; the easiest way to store the source maps. Sourcemaps are stored with source |
266 | ;; being mapped as the key, prepended with '@' if it is a filename (like |
267 | ;; debug.getinfo returns for source). The value is an array of mappings for |
268 | ;; each line. |
269 | (local fennel-sourcemap []) |
270 |
|
271 | (fn make-short-src [source] |
272 | (let [source (source:gsub "\n" " ")] |
273 | (if (<= (# source) 49) |
274 | (.. "[fennel \"" source "\"]") |
275 | (.. "[fennel \"" (source:sub 1 46) "...\"]")))) |
276 |
|
277 | (fn flatten [chunk options] |
278 | "Return Lua source and source map table." |
279 | (let [chunk (peephole chunk)] |
280 | (if options.correlate |
281 | (values (flatten-chunk-correlated chunk) []) |
282 | (let [sm [] |
283 | ret (flatten-chunk sm chunk options.indent 0)] |
284 | (when sm |
285 | (set sm.short_src (make-short-src (or options.filename |
286 | options.source ret))) |
287 | (set sm.key (if options.filename (.. "@" options.filename) ret)) |
288 | (tset fennel-sourcemap sm.key sm)) |
289 | (values ret sm))))) |
290 |
|
291 | (fn make-metadata [] |
292 | "Make module-wide state table for metadata." |
293 | (setmetatable |
294 | [] {:__index {:get (fn [self tgt key] |
295 | (when (. self tgt) |
296 | (. (. self tgt) key))) |
297 | :set (fn [self tgt key value] |
298 | (tset self tgt (or (. self tgt) [])) |
299 | (tset (. self tgt) key value) |
300 | tgt) |
301 | :setall (fn [self tgt ...] |
302 | (let [kv-len (select "#" ...) |
303 | kvs [...]] |
304 | (when (not= (% kv-len 2) 0) |
305 | (error "metadata:setall() expected even number of k/v pairs")) |
306 | (tset self tgt (or (. self tgt) [])) |
307 | (for [i 1 kv-len 2] |
308 | (tset (. self tgt) (. kvs i) (. kvs (+ i 1)))) |
309 | tgt))} |
310 | :__mode "k"})) |
311 |
|
312 | (fn exprs1 [exprs] |
313 | "Convert expressions to Lua string." |
314 | (table.concat (utils.map exprs 1) ", ")) |
315 |
|
316 | (fn keep-side-effects [exprs chunk start ast] |
317 | "Compile side effects for a chunk." |
318 | (let [start (or start 1)] |
319 | (for [j start (# exprs)] |
320 | (let [se (. exprs j)] |
321 | ;; Avoid the rogue 'nil' expression (nil is usually a literal, |
322 | ;; but becomes an expression if a special form returns 'nil') |
323 | (if (and (= se.type "expression") (not= (. se 1) "nil")) |
324 | (emit chunk (string.format "do local _ = %s end" (tostring se)) ast) |
325 | (= se.type "statement") |
326 | (let [code (tostring se)] |
327 | (emit chunk (or (and (= (code:byte) 40) |
328 | (.. "do end " code)) code) ast))))))) |
329 |
|
330 | (fn handle-compile-opts [exprs parent opts ast] |
331 | "Does some common handling of returns and register targets for special |
332 | forms. Also ensures a list expression has an acceptable number of expressions |
333 | if opts contains the nval option." |
334 | (when opts.nval |
335 | (let [n opts.nval |
336 | len (# exprs)] |
337 | (when (not= n len) |
338 | (if (> len n) |
339 | (do ; drop extra |
340 | (keep-side-effects exprs parent (+ n 1) ast) |
341 | (for [i (+ n 1) len] |
342 | (tset exprs i nil))) |
343 | (for [i (+ (# exprs) 1) n] ; pad with nils |
344 | (tset exprs i (utils.expr :nil :literal))))))) |
345 | (when opts.tail |
346 | (emit parent (string.format "return %s" (exprs1 exprs)) ast)) |
347 | (when opts.target |
348 | (let [result (exprs1 exprs)] |
349 | (emit parent (string.format "%s = %s" opts.target |
350 | (if (= result "") "nil" result)) ast))) |
351 | (if (or opts.tail opts.target) |
352 | ;; Prevent statements and expression from being used twice if they |
353 | ;; have side-effects. Since if the target or tail options are set, |
354 | ;; the expressions are already emitted, we should not return them. This |
355 | ;; is fine, as when these options are set, the caller doesn't need the |
356 | ;; result anyways. |
357 | {:returned true} |
358 | (doto exprs (tset :returned true)))) |
359 |
|
360 | (fn find-macro [ast scope multi-sym-parts] |
361 | (fn find-in-table [t i] |
362 | (if (<= i (# multi-sym-parts)) |
363 | (find-in-table (and (utils.table? t) (. t (. multi-sym-parts i))) |
364 | (+ i 1)) |
365 | t)) |
366 | (let [macro* (and (utils.sym? (. ast 1)) |
367 | (. scope.macros (utils.deref (. ast 1))))] |
368 | (if (and (not macro*) multi-sym-parts) |
369 | (let [nested-macro (find-in-table scope.macros 1)] |
370 | (assert-compile (or (not (. scope.macros (. multi-sym-parts 1))) |
371 | (= (type nested-macro) :function)) |
372 | "macro not found in imported macro module" ast) |
373 | nested-macro) |
374 | macro*))) |
375 |
|
376 | (fn macroexpand* [ast scope once] |
377 | "Expand macros in the ast. Only do one level if once is true." |
378 | (if (not (utils.list? ast)) ; bail early if not a list |
379 | ast |
380 | (let [macro* (find-macro ast scope (utils.multi-sym? (. ast 1)))] |
381 | (if (not macro*) |
382 | ast |
383 | (let [old-scope scopes.macro |
384 | _ (set scopes.macro scope) |
385 | (ok transformed) (pcall macro* (unpack ast 2))] |
386 | (set scopes.macro old-scope) |
387 | (assert-compile ok transformed ast) |
388 | (if (or once (not transformed)) |
389 | transformed |
390 | (macroexpand* transformed scope))))))) |
391 |
|
392 | (fn compile-special [ast scope parent opts special] |
393 | (let [exprs (or (special ast scope parent opts) |
394 | (utils.expr :nil :literal)) |
395 | ;; Be very accepting of strings or expressions as well as lists |
396 | ;; or expressions |
397 | exprs (if (= (type exprs) :string) |
398 | (utils.expr exprs :expression) |
399 | exprs) |
400 | exprs (if (utils.expr? exprs) |
401 | [exprs] |
402 | exprs)] |
403 | ;; Unless the special form explicitly handles the target, tail, |
404 | ;; and nval properties, (indicated via the 'returned' flag), |
405 | ;; handle these options. |
406 | (if (not exprs.returned) |
407 | (handle-compile-opts exprs parent opts ast) |
408 | (or opts.tail opts.target) |
409 | {:returned true} |
410 | exprs))) |
411 |
|
412 | ;; TODO: too long |
413 | (fn compile-call [ast scope parent opts compile1] |
414 | (utils.hook :call ast scope) |
415 | (let [len (# ast) |
416 | first (. ast 1) |
417 | multi-sym-parts (utils.multi-sym? first) |
418 | special (and (utils.sym? first) (. scope.specials (utils.deref first)))] |
419 | (assert-compile (> len 0) |
420 | "expected a function, macro, or special to call" ast) |
421 | (if special |
422 | (compile-special ast scope parent opts special) |
423 | (and multi-sym-parts multi-sym-parts.multi-sym-method-call) |
424 | (let [table-with-method (table.concat |
425 | [(unpack multi-sym-parts 1 |
426 | (- (# multi-sym-parts) 1))] |
427 | ".") |
428 | method-to-call (. multi-sym-parts (# multi-sym-parts)) |
429 | new-ast (utils.list (utils.sym ":" scope) |
430 | (utils.sym table-with-method scope) |
431 | method-to-call (select 2 (unpack ast)))] |
432 | (compile1 new-ast scope parent opts)) |
433 | (let [fargs [] ; regular function call |
434 | fcallee (. (compile1 (. ast 1) scope parent {:nval 1}) 1)] |
435 | (assert-compile (not= fcallee.type :literal) |
436 | (.. "cannot call literal value " (tostring first)) ast) |
437 | (for [i 2 len] |
438 | (let [subexprs (compile1 (. ast i) scope parent |
439 | {:nval (or (and (not= i len) 1) nil)})] |
440 | (table.insert fargs (or (. subexprs 1) (utils.expr :nil :literal))) |
441 | (if (= i len) |
442 | ;; Add sub expressions to function args |
443 | (for [j 2 (# subexprs)] |
444 | (table.insert fargs (. subexprs j))) |
445 | ;; Emit sub expression only for side effects |
446 | (keep-side-effects subexprs parent 2 (. ast i))))) |
447 | (let [call (string.format "%s(%s)" (tostring fcallee) (exprs1 fargs))] |
448 | (handle-compile-opts [(utils.expr call :statement)] parent opts ast)))))) |
449 |
|
450 | (fn compile-varg [ast scope parent opts] |
451 | (assert-compile scope.vararg "unexpected vararg" ast) |
452 | (handle-compile-opts [(utils.expr "..." "varg")] parent opts ast)) |
453 |
|
454 | (fn compile-sym [ast scope parent opts] |
455 | (let [multi-sym-parts (utils.multi-sym? ast)] |
456 | (assert-compile (not (and multi-sym-parts multi-sym-parts.multi-sym-method-call)) |
457 | "multisym method calls may only be in call position" ast) |
458 | ;; Handle nil as special symbol - it resolves to the nil literal |
459 | ;; rather than being unmangled. Alternatively, we could remove it |
460 | ;; from the lua keywords table. |
461 | (let [e (if (= (. ast 1) "nil") |
462 | (utils.expr "nil" "literal") |
463 | (symbol-to-expression ast scope true))] |
464 | (handle-compile-opts [e] parent opts ast)))) |
465 |
|
466 | (fn compile-scalar [ast _scope parent opts] |
467 | (let [serialize (match (type ast) |
468 | :nil tostring |
469 | :boolean tostring |
470 | :string serialize-string |
471 | :number (partial string.format "%.17g"))] |
472 | (handle-compile-opts [(utils.expr (serialize ast) :literal)] parent opts))) |
473 |
|
474 | (fn compile-table [ast scope parent opts compile1] |
475 | (let [buffer []] |
476 | (for [i 1 (# ast)] ; write numeric keyed values |
477 | (let [nval (and (not= i (# ast)) 1)] |
478 | (table.insert buffer (exprs1 (compile1 (. ast i) scope parent |
479 | {:nval nval}))))) |
480 | (fn write-other-values [k] |
481 | (when (or (not= (type k) :number) |
482 | (not= (math.floor k) k) |
483 | (< k 1) (> k (# ast))) |
484 | (if (and (= (type k) :string) (utils.valid-lua-identifier? k)) |
485 | [k k] |
486 | (let [[compiled] (compile1 k scope parent {:nval 1}) |
487 | kstr (.. "[" (tostring compiled) "]")] |
488 | [kstr k])))) |
489 | (let [keys (doto (utils.kvmap ast write-other-values) |
490 | (table.sort (fn [a b] (< (. a 1) (. b 1)))))] |
491 | (utils.map keys (fn [k] |
492 | (let [v (tostring (. (compile1 (. ast (. k 2)) |
493 | scope parent |
494 | {:nval 1}) 1))] |
495 | (string.format "%s = %s" (. k 1) v))) |
496 | buffer)) |
497 | (handle-compile-opts [(utils.expr (.. "{" (table.concat buffer ", ") "}") |
498 | :expression)] parent opts ast))) |
499 |
|
500 | (fn compile1 [ast scope parent opts] |
501 | "Compile an AST expression in the scope into parent, a tree of lines that is |
502 | eventually compiled into Lua code. Also returns some information about the |
503 | evaluation of the compiled expression, which can be used by the calling |
504 | function. Macros are resolved here, as well as special forms in that order. |
505 |
|
506 | * the `ast` param is the root AST to compile |
507 | * the `scope` param is the scope in which we are compiling |
508 | * the `parent` param is the table of lines that we are compiling into. |
509 | add lines to parent by appending strings. Add indented blocks by appending |
510 | tables of more lines. |
511 | * the `opts` param contains info about where the form is being compiled |
512 |
|
513 | Fields of `opts` include: |
514 | target: mangled name of symbol(s) being compiled to. |
515 | Could be one variable, 'a', or a list, like 'a, b, _0_'. |
516 | tail: boolean indicating tail position if set. If set, form will generate |
517 | a return instruction. |
518 | nval: The number of values to compile to if it is known to be a fixed value. |
519 |
|
520 | In Lua, an expression can evaluate to 0 or more values via multiple returns. In |
521 | many cases, Lua will drop extra values and convert a 0 value expression to |
522 | nil. In other cases, Lua will use all of the values in an expression, such as |
523 | in the last argument of a function call. Nval is an option passed to compile1 |
524 | to say that the resulting expression should have at least n values. It lets us |
525 | generate better code, because if we know we are only going to use 1 or 2 values |
526 | from an expression, we can create 1 or 2 locals to store intermediate results |
527 | rather than turn the expression into a closure that is called immediately, |
528 | which we have to do if we don't know." |
529 | (let [opts (or opts []) |
530 | ast (macroexpand* ast scope)] |
531 | (if (utils.list? ast) |
532 | (compile-call ast scope parent opts compile1) |
533 | (utils.varg? ast) |
534 | (compile-varg ast scope parent opts) |
535 | (utils.sym? ast) |
536 | (compile-sym ast scope parent opts) |
537 | (= (type ast) "table") |
538 | (compile-table ast scope parent opts compile1) |
539 | (or (= (type ast) "nil") (= (type ast) "boolean") |
540 | (= (type ast) "number") (= (type ast) "string")) |
541 | (compile-scalar ast scope parent opts) |
542 | (assert-compile false (.. "could not compile value of type " |
543 | (type ast)) ast)))) |
544 |
|
545 | ;; You may be tempted to clean up and refactor this function because it's so |
546 | ;; huge and stateful but it really needs to get replaced; it is too tightly |
547 | ;; coupled to the way the compiler outputs Lua; it should be split into general |
548 | ;; data-driven parts vs Lua-emitting parts. |
549 | (fn destructure [to from ast scope parent opts] |
550 | "Implements destructuring for forms like let, bindings, etc. |
551 | Takes a number of opts to control behavior. |
552 | * var: Whether or not to mark symbols as mutable |
553 | * declaration: begin each assignment with 'local' in output |
554 | * nomulti: disallow multisyms in the destructuring. for (local) and (global) |
555 | * noundef: Don't set undefined bindings. (set) |
556 | * forceglobal: Don't allow local bindings" |
557 | (let [opts (or opts {}) |
558 | {: isvar : declaration : nomulti : noundef : forceglobal : forceset} opts |
559 | setter (if declaration "local %s = %s" "%s = %s") |
560 | new-manglings []] |
561 |
|
562 | (fn getname [symbol up1] |
563 | "Get Lua source for symbol, and check for errors" |
564 | (let [raw (. symbol 1)] |
565 | (assert-compile (not (and nomulti (utils.multi-sym? raw))) |
566 | (.. "unexpected multi symbol " raw) up1) |
567 | (if declaration |
568 | ;; Technically this is too early to declare the local, but leaving |
569 | ;; out the meta table and setting it later works around the problem. |
570 | (declare-local symbol nil scope symbol new-manglings) |
571 | (let [parts (or (utils.multi-sym? raw) [raw]) |
572 | meta (. scope.symmeta (. parts 1))] |
573 | (when (and (= (# parts) 1) (not forceset)) |
574 | (assert-compile (not (and forceglobal meta)) |
575 | (string.format "global %s conflicts with local" |
576 | (tostring symbol)) symbol) |
577 | (assert-compile (not (and meta (not meta.var))) |
578 | (.. "expected var " raw) symbol) |
579 | (assert-compile (or meta (not noundef)) |
580 | (.. "expected local " (. parts 1)) symbol)) |
581 | (when forceglobal |
582 | (assert-compile (not (. scope.symmeta (. scope.unmanglings raw))) |
583 | (.. "global " raw " conflicts with local") symbol) |
584 | (tset scope.manglings raw (global-mangling raw)) |
585 | (tset scope.unmanglings (global-mangling raw) raw) |
586 | (when allowed-globals |
587 | (table.insert allowed-globals raw))) |
588 | (. (symbol-to-expression symbol scope) 1))))) |
589 |
|
590 | (fn compile-top-target [lvalues] |
591 | "Compile the outer most form. We can generate better Lua in this case." |
592 | ;; Calculate initial rvalue |
593 | (let [inits (utils.map lvalues #(if (. scope.manglings $) $ "nil")) |
594 | init (table.concat inits ", ") |
595 | lvalue (table.concat lvalues ", ")] |
596 | (var (plen plast) (values (# parent) (. parent (# parent)))) |
597 | (local ret (compile1 from scope parent {:target lvalue})) |
598 | (when declaration |
599 | ;; A single leaf emitted at the end of the parent chunk means a |
600 | ;; simple assignment a = x was emitted, and we can just splice |
601 | ;; "local " onto the front of it. However, we can't just check |
602 | ;; based on plen, because some forms (such as include) insert new |
603 | ;; chunks at the top of the parent chunk rather than just at the |
604 | ;; end; this loop checks for this occurance and updates plen to be |
605 | ;; the index of the last thing in the parent before compiling the |
606 | ;; new value. |
607 | (for [pi plen (# parent)] |
608 | (when (= (. parent pi) plast) |
609 | (set plen pi))) |
610 | (if (and (= (# parent) (+ plen 1)) (. (. parent (# parent)) "leaf")) |
611 | (tset (. parent (# parent)) :leaf |
612 | (.. "local " (. (. parent (# parent)) "leaf"))) |
613 | (table.insert parent (+ plen 1) |
614 | {:ast ast :leaf (.. "local " lvalue " = " init)}))) |
615 | ret)) |
616 |
|
617 | (fn effective-key [key val] |
618 | (let [key (if (and (utils.sym? key) (= (tostring key) ":") (utils.sym? val)) |
619 | (tostring val) |
620 | key)] |
621 | (if (= (type key) :string) |
622 | (serialize-string key) |
623 | key))) |
624 |
|
625 | (fn destructure1 [left rightexprs up1 top] |
626 | "Recursive auxiliary function" |
627 | (if (and (utils.sym? left) (not= (. left 1) "nil")) |
628 | (let [lname (getname left up1)] |
629 | (check-binding-valid left scope left) |
630 | (if top |
631 | (compile-top-target [lname]) |
632 | (emit parent (setter:format lname (exprs1 rightexprs)) left)) |
633 | ;; We have to declare meta for the left *after* compiling the right |
634 | ;; see https://todo.sr.ht/~technomancy/fennel/12 |
635 | (when declaration |
636 | (tset scope.symmeta (utils.deref left) {:var isvar}))) |
637 | (utils.table? left) ; table destructuring |
638 | (let [s (gensym scope)] |
639 | (var right (if top |
640 | (exprs1 (compile1 from scope parent)) |
641 | (exprs1 rightexprs))) |
642 | (when (= right "") |
643 | (set right "nil")) |
644 | (emit parent (string.format "local %s = %s" s right) left) |
645 | (each [k v (utils.stablepairs left)] |
646 | (if (and (utils.sym? (. left k)) (= (. (. left k) 1) "&")) |
647 | (do |
648 | (assert-compile (and (= (type k) "number") |
649 | (not (. left (+ k 2)))) |
650 | "expected rest argument before last parameter" |
651 | left) |
652 | (let [unpack-str "{(table.unpack or unpack)(%s, %s)}" |
653 | formatted (string.format unpack-str s k) |
654 | subexpr (utils.expr formatted "expression")] |
655 | (destructure1 (. left (+ k 1)) [subexpr] left) |
656 | (lua "return"))) |
657 | (let [subexpr (utils.expr (string.format "%s[%s]" s |
658 | (effective-key k v)) |
659 | :expression)] |
660 | (destructure1 v [subexpr] left))))) |
661 | (utils.list? left) ;; values destructuring |
662 | (let [(left-names tables) (values [] [])] |
663 | (each [i name (ipairs left)] |
664 | (if (utils.sym? name) ; binding directly to a name |
665 | (table.insert left-names (getname name up1)) |
666 | (let [symname (gensym scope)] |
667 | ;; further destructuring of tables inside values |
668 | (table.insert left-names symname) |
669 | (tset tables i [name (utils.expr symname "sym")])))) |
670 | (if top |
671 | (compile-top-target left-names) |
672 | (let [lvalue (table.concat left-names ", ") |
673 | setting (setter:format lvalue (exprs1 rightexprs))] |
674 | (emit parent setting left))) |
675 | (when declaration |
676 | (each [_ sym (ipairs left)] |
677 | (tset scope.symmeta (utils.deref sym) {:var isvar}))) |
678 | ;; recurse if left-side tables found |
679 | (each [_ pair (utils.stablepairs tables)] |
680 | (destructure1 (. pair 1) [(. pair 2)] left))) |
681 | (assert-compile false (string.format "unable to bind %s %s" |
682 | (type left) (tostring left)) |
683 | (or (and (= (type (. up1 2)) "table") (. up1 2)) up1))) |
684 | (when top |
685 | {:returned true})) |
686 |
|
687 | (let [ret (destructure1 to nil ast true)] |
688 | (utils.hook :destructure from to scope) |
689 | (apply-manglings scope new-manglings ast) |
690 | ret))) |
691 |
|
692 | (fn require-include [ast scope parent opts] |
693 | (fn opts.fallback [e] |
694 | (utils.expr (string.format "require(%s)" (tostring e)) :statement)) |
695 | (scopes.global.specials.include ast scope parent opts)) |
696 |
|
697 | (fn compile-stream [strm options] |
698 | (let [opts (utils.copy options) |
699 | old-globals allowed-globals |
700 | scope (or opts.scope (make-scope scopes.global)) |
701 | vals [] |
702 | chunk []] |
703 | (utils.root:set-reset) |
704 | (set allowed-globals opts.allowedGlobals) |
705 | (when (= opts.indent nil) |
706 | (set opts.indent " ")) |
707 | (when opts.requireAsInclude |
708 | (set scope.specials.require require-include)) |
709 | (set (utils.root.chunk utils.root.scope utils.root.options) |
710 | (values chunk scope opts)) |
711 | (each [_ val (parser.parser strm opts.filename opts)] |
712 | (table.insert vals val)) |
713 | (for [i 1 (# vals)] |
714 | (let [exprs (compile1 (. vals i) scope chunk |
715 | {:nval (or (and (< i (# vals)) 0) nil) |
716 | :tail (= i (# vals))})] |
717 | (keep-side-effects exprs chunk nil (. vals i)))) |
718 | (set allowed-globals old-globals) |
719 | (utils.root.reset) |
720 | (flatten chunk opts))) |
721 |
|
722 | (fn compile-string [str opts] |
723 | (compile-stream (parser.string-stream str) (or opts {}))) |
724 |
|
725 | (fn compile [ast opts] |
726 | (let [opts (utils.copy opts) |
727 | old-globals allowed-globals |
728 | chunk [] |
729 | scope (or opts.scope (make-scope scopes.global))] |
730 | (utils.root:set-reset) |
731 | (set allowed-globals opts.allowedGlobals) |
732 | (when (= opts.indent nil) |
733 | (set opts.indent " ")) |
734 | (when opts.requireAsInclude |
735 | (set scope.specials.require require-include)) |
736 | (set (utils.root.chunk utils.root.scope utils.root.options) |
737 | (values chunk scope opts)) |
738 | (let [exprs (compile1 ast scope chunk {:tail true})] |
739 | (keep-side-effects exprs chunk nil ast) |
740 | (set allowed-globals old-globals) |
741 | (utils.root.reset) |
742 | (flatten chunk opts)))) |
743 |
|
744 | (fn traceback-frame [info] |
745 | (if (and (= info.what "C") info.name) |
746 | (string.format " [C]: in function '%s'" info.name) |
747 | (= info.what "C") |
748 | " [C]: in ?" |
749 | (let [remap (. fennel-sourcemap info.source)] |
750 | (when (and remap (. remap info.currentline)) |
751 | ;; And some global info |
752 | (set info.short-src remap.short-src) |
753 | ;; Overwrite info with values from the mapping |
754 | ;; (mapping is now just integer, but may |
755 | ;; eventually be a table) |
756 | (set info.currentline (. remap info.currentline))) |
757 | (if (= info.what "Lua") |
758 | (string.format " %s:%d: in function %s" |
759 | info.short_src info.currentline |
760 | (if info.name (.. "'" info.name "'") "?")) |
761 | (= info.short-src "(tail call)") |
762 | " (tail call)" |
763 | (string.format " %s:%d: in main chunk" |
764 | info.short_src info.currentline))))) |
765 |
|
766 | (fn traceback [msg start] |
767 | "A custom traceback function for Fennel that looks similar to debug.traceback. |
768 | Use with xpcall to produce fennel specific stacktraces. Skips frames from the |
769 | compiler by default; these can be re-enabled with export FENNEL_DEBUG=trace." |
770 | (let [msg (or msg "")] |
771 | (if (and (or (msg:find "^Compile error") (msg:find "^Parse error")) |
772 | (not (utils.debug-on? :trace))) |
773 | msg ; skip the trace because it's compiler internals. |
774 | (let [lines []] |
775 | (if (or (msg:find "^Compile error") (msg:find "^Parse error")) |
776 | (table.insert lines msg) |
777 | (let [newmsg (msg:gsub "^[^:]*:%d+:%s+" "runtime error: ")] |
778 | (table.insert lines newmsg))) |
779 | (table.insert lines "stack traceback:") |
780 | (var (done? level) (values false (or start 2))) |
781 | ;; This would be cleaner factored out into its own recursive |
782 | ;; function, but that would interfere with the traceback itself! |
783 | (while (not done?) |
784 | (match (debug.getinfo level "Sln") |
785 | nil (set done? true) |
786 | info (table.insert lines (traceback-frame info))) |
787 | (set level (+ level 1))) |
788 | (table.concat lines "\n"))))) |
789 |
|
790 | (fn entry-transform [fk fv] |
791 | "Make a transformer for key / value table pairs, preserving all numeric keys" |
792 | (fn [k v] (if (= (type k) "number") |
793 | (values k (fv v)) |
794 | (values (fk k) (fv v))))) |
795 |
|
796 | (fn no [] "Consume everything and return nothing." nil) |
797 |
|
798 | (fn mixed-concat [t joiner] |
799 | (let [seen []] |
800 | (var (ret s) (values "" "")) |
801 | (each [k v (ipairs t)] |
802 | (table.insert seen k) |
803 | (set ret (.. ret s v)) |
804 | (set s joiner)) |
805 | (each [k v (utils.stablepairs t)] |
806 | (when (not (. seen k)) |
807 | (set ret (.. ret s "[" k "]" "=" v)) |
808 | (set s joiner))) |
809 | ret)) |
810 |
|
811 | ;; TODO: too long |
812 | (fn do-quote [form scope parent runtime?] |
813 | "Expand a quoted form into a data literal, evaluating unquote" |
814 | (fn q [x] (do-quote x scope parent runtime?)) |
815 | (if (utils.varg? form) |
816 | (do |
817 | (assert-compile (not runtime?) |
818 | "quoted ... may only be used at compile time" form) |
819 | "_VARARG") |
820 | (utils.sym? form) ; symbol |
821 | (let [filename (if form.filename (string.format "%q" form.filename) :nil) |
822 | symstr (utils.deref form)] |
823 | (assert-compile (not runtime?) |
824 | "symbols may only be used at compile time" form) |
825 | ;; We should be able to use "%q" for this but Lua 5.1 throws an error |
826 | ;; when you try to format nil, because it's extremely bad. |
827 | (if (or (symstr:find "#$") (symstr:find "#[:.]")) ; autogensym |
828 | (string.format "sym('%s', nil, {filename=%s, line=%s})" |
829 | (autogensym symstr scope) filename (or form.line :nil)) |
830 | ;; prevent non-gensymed symbols from being bound as an identifier |
831 | (string.format "sym('%s', nil, {quoted=true, filename=%s, line=%s})" |
832 | symstr filename (or form.line :nil)))) |
833 | (and (utils.list? form) ; unquote |
834 | (utils.sym? (. form 1)) |
835 | (= (utils.deref (. form 1)) :unquote)) |
836 | (let [payload (. form 2) |
837 | res (unpack (compile1 payload scope parent))] |
838 | (. res 1)) |
839 | (utils.list? form) ; list |
840 | (let [mapped (utils.kvmap form (entry-transform no q)) |
841 | filename (if form.filename (string.format "%q" form.filename) :nil)] |
842 | (assert-compile (not runtime?) |
843 | "lists may only be used at compile time" form) |
844 | ;; Constructing a list and then adding file/line data to it triggers a |
845 | ;; bug where it changes the value of # for lists that contain nils in |
846 | ;; them; constructing the list all in one go with the source data and |
847 | ;; contents is how we construct lists in the parser and works around |
848 | ;; this problem; allowing # to work in a way that lets us see the nils. |
849 | (string.format (.. "setmetatable({filename=%s, line=%s, bytestart=%s, %s}" |
850 | ", getmetatable(list()))") |
851 | filename (or form.line :nil) (or form.bytestart :nil) |
852 | (mixed-concat mapped ", "))) |
853 | (= (type form) "table") ; table |
854 | (let [mapped (utils.kvmap form (entry-transform q q)) |
855 | source (getmetatable form) |
856 | filename (if source.filename (string.format "%q" source.filename) :nil)] |
857 | (string.format "setmetatable({%s}, {filename=%s, line=%s})" |
858 | (mixed-concat mapped ", ") filename |
859 | (if source source.line :nil))) |
860 | (= (type form) "string") |
861 | (serialize-string form) |
862 | (tostring form))) |
863 |
|
864 | {;; compiling functions |
865 | : compile : compile1 : compile-stream : compile-string : emit : destructure |
866 | : require-include |
867 |
|
868 | ;; AST functions |
869 | : autogensym : gensym : do-quote : global-mangling : global-unmangling |
870 | : apply-manglings :macroexpand macroexpand* |
871 |
|
872 | ;; scope functions |
873 | : declare-local : make-scope : keep-side-effects : symbol-to-expression |
874 |
|
875 | ;; general |
876 | :assert assert-compile : scopes : traceback :metadata (make-metadata) |
877 | } |