clone url: git://git.m455.casa/fa
esperbuild/espersrc/fennel-0.7.0/old/fennel.lua
1 | --[[ |
2 | Copyright (c) 2016-2020 Calvin Rose and contributors |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of |
4 | this software and associated documentation files (the "Software"), to deal in |
5 | the Software without restriction, including without limitation the rights to |
6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
7 | the Software, and to permit persons to whom the Software is furnished to do so, |
8 | subject to the following conditions: |
9 | The above copyright notice and this permission notice shall be included in all |
10 | copies or substantial portions of the Software. |
11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
12 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
13 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
14 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
15 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
16 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
17 | ]] |
18 |
|
19 | -- Make global variables local. |
20 | local setmetatable = setmetatable |
21 | local getmetatable = getmetatable |
22 | local type = type |
23 | local assert = assert |
24 | local pairs = pairs |
25 | local ipairs = ipairs |
26 | local tostring = tostring |
27 | local unpack = _G.unpack or table.unpack |
28 |
|
29 | -- |
30 | -- Main Types and support functions |
31 | -- |
32 |
|
33 | local utils = (function() |
34 | -- Like pairs, but gives consistent ordering every time. On 5.1, 5.2, and LuaJIT |
35 | -- pairs is already stable, but on 5.3 every run gives different ordering. |
36 | local function stablepairs(t) |
37 | local keys, succ = {}, {} |
38 | for k in pairs(t) do table.insert(keys, k) end |
39 | table.sort(keys, function(a, b) return tostring(a) < tostring(b) end) |
40 | for i,k in ipairs(keys) do succ[k] = keys[i+1] end |
41 | local function stablenext(tbl, idx) |
42 | if idx == nil then return keys[1], tbl[keys[1]] end |
43 | return succ[idx], tbl[succ[idx]] |
44 | end |
45 | return stablenext, t, nil |
46 | end |
47 |
|
48 | -- Map function f over sequential table t, removing values where f returns nil. |
49 | -- Optionally takes a target table to insert the mapped values into. |
50 | local function map(t, f, out) |
51 | out = out or {} |
52 | if type(f) ~= "function" then local s = f f = function(x) return x[s] end end |
53 | for _,x in ipairs(t) do |
54 | local v = f(x) |
55 | if v then table.insert(out, v) end |
56 | end |
57 | return out |
58 | end |
59 |
|
60 | -- Map function f over key/value table t, similar to above, but it can return a |
61 | -- sequential table if f returns a single value or a k/v table if f returns two. |
62 | -- Optionally takes a target table to insert the mapped values into. |
63 | local function kvmap(t, f, out) |
64 | out = out or {} |
65 | if type(f) ~= "function" then local s = f f = function(x) return x[s] end end |
66 | for k,x in stablepairs(t) do |
67 | local korv, v = f(k, x) |
68 | if korv and not v then table.insert(out, korv) end |
69 | if korv and v then out[korv] = v end |
70 | end |
71 | return out |
72 | end |
73 |
|
74 | -- Returns a shallow copy of its table argument. Returns an empty table on nil. |
75 | local function copy(from) |
76 | local to = {} |
77 | for k, v in pairs(from or {}) do to[k] = v end |
78 | return to |
79 | end |
80 |
|
81 | -- Like pairs, but if the table has an __index metamethod, it will recurisvely |
82 | -- traverse upwards, skipping duplicates, to iterate all inherited properties |
83 | local function allpairs(t) |
84 | assert(type(t) == 'table', 'allpairs expects a table') |
85 | local seen = {} |
86 | local function allpairsNext(_, state) |
87 | local nextState, value = next(t, state) |
88 | if seen[nextState] then |
89 | return allpairsNext(nil, nextState) |
90 | elseif nextState then |
91 | seen[nextState] = true |
92 | return nextState, value |
93 | end |
94 | local meta = getmetatable(t) |
95 | if meta and meta.__index then |
96 | t = meta.__index |
97 | return allpairsNext(t) |
98 | end |
99 | end |
100 | return allpairsNext |
101 | end |
102 |
|
103 | local function deref(self) return self[1] end |
104 |
|
105 | local nilSym -- haven't defined sym yet; create this later |
106 |
|
107 | local function listToString(self, tostring2) |
108 | local safe, max = {}, 0 |
109 | for k in pairs(self) do if type(k) == "number" and k>max then max=k end end |
110 | for i=1,max do -- table.maxn was removed from Lua 5.3 for some reason??? |
111 | safe[i] = self[i] == nil and nilSym or self[i] |
112 | end |
113 | return '(' .. table.concat(map(safe, tostring2 or tostring), ' ', 1, max) .. ')' |
114 | end |
115 |
|
116 | local SYMBOL_MT = { 'SYMBOL', __tostring = deref, __fennelview = deref } |
117 | local EXPR_MT = { 'EXPR', __tostring = deref } |
118 | local VARARG = setmetatable({ '...' }, |
119 | { 'VARARG', __tostring = deref, __fennelview = deref }) |
120 | local LIST_MT = { 'LIST', __tostring = listToString, __fennelview = listToString } |
121 | local SEQUENCE_MARKER = { 'SEQUENCE' } |
122 |
|
123 | -- Safely load an environment variable |
124 | local getenv = os and os.getenv or function() return nil end |
125 |
|
126 | local pathTable = {"./?.fnl", "./?/init.fnl"} |
127 | table.insert(pathTable, getenv("FENNEL_PATH")) |
128 |
|
129 | local function debugOn(flag) |
130 | local level = getenv("FENNEL_DEBUG") or "" |
131 | return level == "all" or level:find(flag) |
132 | end |
133 |
|
134 | -- Create a new list. Lists are a compile-time construct in Fennel; they are |
135 | -- represented as tables with a special marker metatable. They only come from |
136 | -- the parser, and they represent code which comes from reading a paren form; |
137 | -- they are specifically not cons cells. |
138 | local function list(...) |
139 | return setmetatable({...}, LIST_MT) |
140 | end |
141 |
|
142 | -- Create a new symbol. Symbols are a compile-time construct in Fennel and are |
143 | -- not exposed outside the compiler. Symbols have source data describing what |
144 | -- file, line, etc that they came from. |
145 | local function sym(str, scope, source) |
146 | local s = {str, scope = scope} |
147 | for k, v in pairs(source or {}) do |
148 | if type(k) == 'string' then s[k] = v end |
149 | end |
150 | return setmetatable(s, SYMBOL_MT) |
151 | end |
152 |
|
153 | nilSym = sym("nil") |
154 |
|
155 | -- Create a new sequence. Sequences are tables that come from the parser when |
156 | -- it encounters a form with square brackets. They are treated as regular tables |
157 | -- except when certain macros need to look for binding forms, etc specifically. |
158 | local function sequence(...) |
159 | -- can't use SEQUENCE_MT directly as the sequence metatable like we do with |
160 | -- the other types without giving up the ability to set source metadata |
161 | -- on a sequence, (which we need for error reporting) so embed a marker |
162 | -- value in the metatable instead. |
163 | return setmetatable({...}, {sequence=SEQUENCE_MARKER}) |
164 | end |
165 |
|
166 | -- Create a new expr |
167 | -- etype should be one of |
168 | -- "literal": literals like numbers, strings, nil, true, false |
169 | -- "expression": Complex strings of Lua code, may have side effects, etc |
170 | -- but is an expression |
171 | -- "statement": Same as expression, but is also a valid statement |
172 | -- (function calls). |
173 | -- "vargs": varargs symbol |
174 | -- "sym": symbol reference |
175 | local function expr(strcode, etype) |
176 | return setmetatable({ strcode, type = etype }, EXPR_MT) |
177 | end |
178 |
|
179 | local function varg() |
180 | return VARARG |
181 | end |
182 |
|
183 | local function isExpr(x) |
184 | return type(x) == 'table' and getmetatable(x) == EXPR_MT and x |
185 | end |
186 |
|
187 | local function isVarg(x) |
188 | return x == VARARG and x |
189 | end |
190 |
|
191 | -- Checks if an object is a List. Returns the object if is a List. |
192 | local function isList(x) |
193 | return type(x) == 'table' and getmetatable(x) == LIST_MT and x |
194 | end |
195 |
|
196 | -- Checks if an object is a symbol. Returns the object if it is a symbol. |
197 | local function isSym(x) |
198 | return type(x) == 'table' and getmetatable(x) == SYMBOL_MT and x |
199 | end |
200 |
|
201 | -- Checks if an object any kind of table, EXCEPT list or symbol |
202 | local function isTable(x) |
203 | return type(x) == 'table' and |
204 | x ~= VARARG and |
205 | getmetatable(x) ~= LIST_MT and getmetatable(x) ~= SYMBOL_MT and x |
206 | end |
207 |
|
208 | -- Checks if an object is a sequence (created with a [] literal) |
209 | local function isSequence(x) |
210 | local mt = type(x) == "table" and getmetatable(x) |
211 | return mt and mt.sequence == SEQUENCE_MARKER and x |
212 | end |
213 |
|
214 | -- A multi symbol is a symbol that is actually composed of |
215 | -- two or more symbols using the dot syntax. The main differences |
216 | -- from normal symbols is that they cannot be declared local, and |
217 | -- they may have side effects on invocation (metatables) |
218 | local function isMultiSym(str) |
219 | if isSym(str) then |
220 | return isMultiSym(tostring(str)) |
221 | end |
222 | if type(str) ~= 'string' then return end |
223 | local parts = {} |
224 | for part in str:gmatch('[^%.%:]+[%.%:]?') do |
225 | local lastChar = part:sub(-1) |
226 | if lastChar == ":" then |
227 | parts.multiSymMethodCall = true |
228 | end |
229 | if lastChar == ":" or lastChar == "." then |
230 | parts[#parts + 1] = part:sub(1, -2) |
231 | else |
232 | parts[#parts + 1] = part |
233 | end |
234 | end |
235 | return #parts > 0 and |
236 | (str:match('%.') or str:match(':')) and |
237 | (not str:match('%.%.')) and |
238 | str:byte() ~= string.byte '.' and |
239 | str:byte(-1) ~= string.byte '.' and |
240 | parts |
241 | end |
242 |
|
243 | local function isQuoted(symbol) return symbol.quoted end |
244 |
|
245 | -- Walks a tree (like the AST), invoking f(node, idx, parent) on each node. |
246 | -- When f returns a truthy value, recursively walks the children. |
247 | local walkTree = function(root, f, customIterator) |
248 | local function walk(iterfn, parent, idx, node) |
249 | if f(idx, node, parent) then |
250 | for k, v in iterfn(node) do walk(iterfn, node, k, v) end |
251 | end |
252 | end |
253 |
|
254 | walk(customIterator or pairs, nil, nil, root) |
255 | return root |
256 | end |
257 |
|
258 | local luaKeywords = { |
259 | 'and', 'break', 'do', 'else', 'elseif', 'end', 'false', 'for', |
260 | 'function', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat', 'return', |
261 | 'then', 'true', 'until', 'while' |
262 | } |
263 |
|
264 | for i, v in ipairs(luaKeywords) do luaKeywords[v] = i end |
265 |
|
266 | local function isValidLuaIdentifier(str) |
267 | return (str:match('^[%a_][%w_]*$') and not luaKeywords[str]) |
268 | end |
269 |
|
270 | -- Certain options should always get propagated onwards when a function that |
271 | -- has options calls down into compile. |
272 | local propagatedOptions = {"allowedGlobals", "indent", "correlate", |
273 | "useMetadata", "env"} |
274 | local function propagateOptions(options, subopts) |
275 | for _,name in ipairs(propagatedOptions) do subopts[name] = options[name] end |
276 | return subopts |
277 | end |
278 |
|
279 | local root = { |
280 | -- Top level compilation bindings. |
281 | chunk=nil, scope=nil, options=nil, |
282 |
|
283 | -- The root.reset function needs to be called at every exit point of the |
284 | -- compiler including when there's a parse error or compiler |
285 | -- error. This would be better done using dynamic scope, but we don't |
286 | -- have dynamic scope, so we fake it by ensuring we call this at every |
287 | -- exit point, including errors. |
288 | reset=function() end, |
289 |
|
290 | setReset=function(root) |
291 | local chunk, scope, options = root.chunk, root.scope, root.options |
292 | local oldResetRoot = root.reset -- this needs to nest! |
293 | root.reset = function() |
294 | root.chunk, root.scope, root.options = chunk, scope, options |
295 | root.reset = oldResetRoot |
296 | end |
297 | end, |
298 | } |
299 |
|
300 | return { |
301 | -- basic general table functions: |
302 | stablepairs=stablepairs, allpairs=allpairs, map=map, kvmap=kvmap, |
303 | copy=copy, walkTree=walkTree, |
304 |
|
305 | -- AST functions: |
306 | list=list, sym=sym, sequence=sequence, expr=expr, varg=varg, |
307 | isVarg=isVarg, isList=isList, isSym=isSym, isTable=isTable, |
308 | isSequence=isSequence, isMultiSym=isMultiSym, isQuoted=isQuoted, |
309 | isExpr=isExpr, deref=deref, |
310 |
|
311 | -- other functions: |
312 | isValidLuaIdentifier=isValidLuaIdentifier, luaKeywords=luaKeywords, |
313 | propagateOptions=propagateOptions, debugOn=debugOn, |
314 | root=root, path=table.concat(pathTable, ";"),} |
315 | end)() |
316 |
|
317 | -- |
318 | -- Parser |
319 | -- |
320 |
|
321 | local parser = (function() |
322 | -- Convert a stream of chunks to a stream of bytes. |
323 | -- Also returns a second function to clear the buffer in the byte stream |
324 | local function granulate(getchunk) |
325 | local c = '' |
326 | local index = 1 |
327 | local done = false |
328 | return function (parserState) |
329 | if done then return nil end |
330 | if index <= #c then |
331 | local b = c:byte(index) |
332 | index = index + 1 |
333 | return b |
334 | else |
335 | c = getchunk(parserState) |
336 | if not c or c == '' then |
337 | done = true |
338 | return nil |
339 | end |
340 | index = 2 |
341 | return c:byte(1) |
342 | end |
343 | end, function () |
344 | c = '' |
345 | end |
346 | end |
347 |
|
348 | -- Convert a string into a stream of bytes |
349 | local function stringStream(str) |
350 | str=str:gsub("^#![^\n]*\n", "") -- remove shebang |
351 | local index = 1 |
352 | return function() |
353 | local r = str:byte(index) |
354 | index = index + 1 |
355 | return r |
356 | end |
357 | end |
358 |
|
359 | -- Table of delimiter bytes - (, ), [, ], {, } |
360 | -- Opener keys have closer as the value, and closers keys |
361 | -- have true as their value. |
362 | local delims = { |
363 | [40] = 41, -- ( |
364 | [41] = true, -- ) |
365 | [91] = 93, -- [ |
366 | [93] = true, -- ] |
367 | [123] = 125, -- { |
368 | [125] = true -- } |
369 | } |
370 |
|
371 | local function iswhitespace(b) |
372 | return b == 32 or (b >= 9 and b <= 13) |
373 | end |
374 |
|
375 | local function issymbolchar(b) |
376 | return b > 32 and |
377 | not delims[b] and |
378 | b ~= 127 and -- "<BS>" |
379 | b ~= 34 and -- "\"" |
380 | b ~= 39 and -- "'" |
381 | b ~= 126 and -- "~" |
382 | b ~= 59 and -- ";" |
383 | b ~= 44 and -- "," |
384 | b ~= 64 and -- "@" |
385 | b ~= 96 -- "`" |
386 | end |
387 |
|
388 | local prefixes = { -- prefix chars substituted while reading |
389 | [96] = 'quote', -- ` |
390 | [44] = 'unquote', -- , |
391 | [39] = 'quote', -- ' |
392 | [35] = 'hashfn' -- # |
393 | } |
394 |
|
395 | -- Parse one value given a function that |
396 | -- returns sequential bytes. Will throw an error as soon |
397 | -- as possible without getting more bytes on bad input. Returns |
398 | -- if a value was read, and then the value read. Will return nil |
399 | -- when input stream is finished. |
400 | local function parser(getbyte, filename, options) |
401 |
|
402 | -- Stack of unfinished values |
403 | local stack = {} |
404 |
|
405 | -- Provide one character buffer and keep |
406 | -- track of current line and byte index |
407 | local line = 1 |
408 | local byteindex = 0 |
409 | local lastb |
410 | local function ungetb(ub) |
411 | if ub == 10 then line = line - 1 end |
412 | byteindex = byteindex - 1 |
413 | lastb = ub |
414 | end |
415 | local function getb() |
416 | local r |
417 | if lastb then |
418 | r, lastb = lastb, nil |
419 | else |
420 | r = getbyte({ stackSize = #stack }) |
421 | end |
422 | byteindex = byteindex + 1 |
423 | if r == 10 then line = line + 1 end |
424 | return r |
425 | end |
426 |
|
427 | -- If you add new calls to this function, please update fenneldfriend.fnl |
428 | -- as well to add suggestions for how to fix the new error. |
429 | local function parseError(msg) |
430 | local source = utils.root.options and utils.root.options.source |
431 | utils.root.reset() |
432 | local override = options and options["parse-error"] |
433 | if override then override(msg, filename or "unknown", line or "?", |
434 | byteindex, source) end |
435 | return error(("Parse error in %s:%s: %s"): |
436 | format(filename or "unknown", line or "?", msg), 0) |
437 | end |
438 |
|
439 | -- Parse stream |
440 | return function() |
441 |
|
442 | -- Dispatch when we complete a value |
443 | local done, retval |
444 | local whitespaceSinceDispatch = true |
445 | local function dispatch(v) |
446 | if #stack == 0 then |
447 | retval = v |
448 | done = true |
449 | elseif stack[#stack].prefix then |
450 | local stacktop = stack[#stack] |
451 | stack[#stack] = nil |
452 | return dispatch(utils.list(utils.sym(stacktop.prefix), v)) |
453 | else |
454 | table.insert(stack[#stack], v) |
455 | end |
456 | whitespaceSinceDispatch = false |
457 | end |
458 |
|
459 | -- Throw nice error when we expect more characters |
460 | -- but reach end of stream. |
461 | local function badend() |
462 | local accum = utils.map(stack, "closer") |
463 | parseError(('expected closing delimiter%s %s'):format( |
464 | #stack == 1 and "" or "s", |
465 | string.char(unpack(accum)))) |
466 | end |
467 |
|
468 | -- The main parse loop |
469 | repeat |
470 | local b |
471 |
|
472 | -- Skip whitespace |
473 | repeat |
474 | b = getb() |
475 | if b and iswhitespace(b) then |
476 | whitespaceSinceDispatch = true |
477 | end |
478 | until not b or not iswhitespace(b) |
479 | if not b then |
480 | if #stack > 0 then badend() end |
481 | return nil |
482 | end |
483 |
|
484 | if b == 59 then -- ; Comment |
485 | repeat |
486 | b = getb() |
487 | until not b or b == 10 -- newline |
488 | elseif type(delims[b]) == 'number' then -- Opening delimiter |
489 | if not whitespaceSinceDispatch then |
490 | parseError('expected whitespace before opening delimiter ' |
491 | .. string.char(b)) |
492 | end |
493 | table.insert(stack, setmetatable({ |
494 | closer = delims[b], |
495 | line = line, |
496 | filename = filename, |
497 | bytestart = byteindex |
498 | }, getmetatable(utils.list()))) |
499 | elseif delims[b] then -- Closing delimiter |
500 | if #stack == 0 then parseError('unexpected closing delimiter ' |
501 | .. string.char(b)) end |
502 | local last = stack[#stack] |
503 | local val |
504 | if last.closer ~= b then |
505 | parseError('mismatched closing delimiter ' .. string.char(b) .. |
506 | ', expected ' .. string.char(last.closer)) |
507 | end |
508 | last.byteend = byteindex -- Set closing byte index |
509 | if b == 41 then -- ; ) |
510 | val = last |
511 | elseif b == 93 then -- ; ] |
512 | val = utils.sequence(unpack(last)) |
513 | -- for table literals we can store file/line/offset source |
514 | -- data in fields on the table itself, because the AST node |
515 | -- *is* the table, and the fields would show up in the |
516 | -- compiled output. keep them on the metatable instead. |
517 | for k,v in pairs(last) do getmetatable(val)[k]=v end |
518 | else -- ; } |
519 | if #last % 2 ~= 0 then |
520 | byteindex = byteindex - 1 |
521 | parseError('expected even number of values in table literal') |
522 | end |
523 | val = {} |
524 | setmetatable(val, last) -- see note above about source data |
525 | for i = 1, #last, 2 do |
526 | if(tostring(last[i]) == ":" and utils.isSym(last[i + 1]) |
527 | and utils.isSym(last[i])) then |
528 | last[i] = tostring(last[i + 1]) |
529 | end |
530 | val[last[i]] = last[i + 1] |
531 | end |
532 | end |
533 | stack[#stack] = nil |
534 | dispatch(val) |
535 | elseif b == 34 then -- Quoted string |
536 | local state = "base" |
537 | local chars = {34} |
538 | stack[#stack + 1] = {closer = 34} |
539 | repeat |
540 | b = getb() |
541 | chars[#chars + 1] = b |
542 | if state == "base" then |
543 | if b == 92 then |
544 | state = "backslash" |
545 | elseif b == 34 then |
546 | state = "done" |
547 | end |
548 | else |
549 | -- state == "backslash" |
550 | state = "base" |
551 | end |
552 | until not b or (state == "done") |
553 | if not b then badend() end |
554 | stack[#stack] = nil |
555 | local raw = string.char(unpack(chars)) |
556 | local formatted = raw:gsub("[\1-\31]", function (c) |
557 | return '\\' .. c:byte() end) |
558 | local loadFn = (loadstring or load)(('return %s'):format(formatted)) |
559 | dispatch(loadFn()) |
560 | elseif prefixes[b] then |
561 | -- expand prefix byte into wrapping form eg. '`a' into '(quote a)' |
562 | table.insert(stack, { |
563 | prefix = prefixes[b] |
564 | }) |
565 | local nextb = getb() |
566 | if iswhitespace(nextb) then |
567 | if b == 35 then |
568 | stack[#stack] = nil |
569 | dispatch(utils.sym('#')) |
570 | else |
571 | parseError('invalid whitespace after quoting prefix') |
572 | end |
573 | end |
574 | ungetb(nextb) |
575 | elseif issymbolchar(b) or b == string.byte("~") then -- Try sym |
576 | local chars = {} |
577 | local bytestart = byteindex |
578 | repeat |
579 | chars[#chars + 1] = b |
580 | b = getb() |
581 | until not b or not issymbolchar(b) |
582 | if b then ungetb(b) end |
583 | local rawstr = string.char(unpack(chars)) |
584 | if rawstr == 'true' then dispatch(true) |
585 | elseif rawstr == 'false' then dispatch(false) |
586 | elseif rawstr == '...' then dispatch(utils.varg()) |
587 | elseif rawstr:match('^:.+$') then -- colon style strings |
588 | dispatch(rawstr:sub(2)) |
589 | elseif rawstr:match("^~") and rawstr ~= "~=" then |
590 | -- for backwards-compatibility, special-case allowance |
591 | -- of ~= but all other uses of ~ are disallowed |
592 | parseError("illegal character: ~") |
593 | else |
594 | local forceNumber = rawstr:match('^%d') |
595 | local numberWithStrippedUnderscores = rawstr:gsub("_", "") |
596 | local x |
597 | if forceNumber then |
598 | x = tonumber(numberWithStrippedUnderscores) or |
599 | parseError('could not read number "' .. rawstr .. '"') |
600 | else |
601 | x = tonumber(numberWithStrippedUnderscores) |
602 | if not x then |
603 | if(rawstr:match("%.[0-9]")) then |
604 | byteindex = (byteindex - #rawstr + |
605 | rawstr:find("%.[0-9]") + 1) |
606 | parseError("can't start multisym segment " .. |
607 | "with a digit: ".. rawstr) |
608 | elseif(rawstr:match("[%.:][%.:]") and |
609 | rawstr ~= ".." and rawstr ~= '$...') then |
610 | byteindex = (byteindex - #rawstr + |
611 | rawstr:find("[%.:][%.:]") + 1) |
612 | parseError("malformed multisym: " .. rawstr) |
613 | elseif(rawstr:match(":.+[%.:]")) then |
614 | byteindex = (byteindex - #rawstr + |
615 | rawstr:find(":.+[%.:]")) |
616 | parseError("method must be last component " |
617 | .. "of multisym: " .. rawstr) |
618 | else |
619 | x = utils.sym(rawstr, nil, {line = line, |
620 | filename = filename, |
621 | bytestart = bytestart, |
622 | byteend = byteindex,}) |
623 | end |
624 | end |
625 | end |
626 | dispatch(x) |
627 | end |
628 | else |
629 | parseError("illegal character: " .. string.char(b)) |
630 | end |
631 | until done |
632 | return true, retval |
633 | end, function () |
634 | stack = {} |
635 | end |
636 | end |
637 | return { granulate=granulate, stringStream=stringStream, parser=parser } |
638 | end)() |
639 |
|
640 | -- |
641 | -- Compilation |
642 | -- |
643 |
|
644 | local compiler = (function() |
645 | local scopes = {} |
646 |
|
647 | -- Create a new Scope, optionally under a parent scope. Scopes are compile time |
648 | -- constructs that are responsible for keeping track of local variables, name |
649 | -- mangling, and macros. They are accessible to user code via the |
650 | -- 'eval-compiler' special form (may change). They use metatables to implement |
651 | -- nesting. |
652 | local function makeScope(parent) |
653 | if not parent then parent = scopes.global end |
654 | return { |
655 | unmanglings = setmetatable({}, { |
656 | __index = parent and parent.unmanglings |
657 | }), |
658 | manglings = setmetatable({}, { |
659 | __index = parent and parent.manglings |
660 | }), |
661 | specials = setmetatable({}, { |
662 | __index = parent and parent.specials |
663 | }), |
664 | macros = setmetatable({}, { |
665 | __index = parent and parent.macros |
666 | }), |
667 | symmeta = setmetatable({}, { |
668 | __index = parent and parent.symmeta |
669 | }), |
670 | includes = setmetatable({}, { |
671 | __index = parent and parent.includes |
672 | }), |
673 | refedglobals = setmetatable({}, { |
674 | __index = parent and parent.refedglobals |
675 | }), |
676 | autogensyms = {}, |
677 | parent = parent, |
678 | vararg = parent and parent.vararg, |
679 | depth = parent and ((parent.depth or 0) + 1) or 0, |
680 | hashfn = parent and parent.hashfn |
681 | } |
682 | end |
683 |
|
684 | -- Assert a condition and raise a compile error with line numbers. The ast arg |
685 | -- should be unmodified so that its first element is the form being called. |
686 | -- If you add new calls to this function, please update fenneldfriend.fnl |
687 | -- as well to add suggestions for how to fix the new error. |
688 | local function assertCompile(condition, msg, ast) |
689 | local override = utils.root.options and utils.root.options["assert-compile"] |
690 | if override then |
691 | local source = utils.root.options and utils.root.options.source |
692 | -- don't make custom handlers deal with resetting root; it's error-prone |
693 | if not condition then utils.root.reset() end |
694 | override(condition, msg, ast, source) |
695 | -- should we fall thru to the default check, or should we allow the |
696 | -- override to swallow the error? |
697 | end |
698 | if not condition then |
699 | utils.root.reset() |
700 | local m = getmetatable(ast) |
701 | local filename = m and m.filename or ast.filename or "unknown" |
702 | local line = m and m.line or ast.line or "?" |
703 | -- if we use regular `assert' we can't provide the `level' argument of 0 |
704 | error(string.format("Compile error in '%s' %s:%s: %s", |
705 | tostring(utils.isSym(ast[1]) and ast[1][1] or |
706 | ast[1] or '()'), |
707 | filename, line, msg), 0) |
708 | end |
709 | return condition |
710 | end |
711 |
|
712 | scopes.global = makeScope() |
713 | scopes.global.vararg = true |
714 | scopes.compiler = makeScope(scopes.global) |
715 | scopes.macro = scopes.global -- used by gensym, in-scope?, etc |
716 |
|
717 | -- Allow printing a string to Lua, also keep as 1 line. |
718 | local serializeSubst = { |
719 | ['\a'] = '\\a', |
720 | ['\b'] = '\\b', |
721 | ['\f'] = '\\f', |
722 | ['\n'] = 'n', |
723 | ['\t'] = '\\t', |
724 | ['\v'] = '\\v' |
725 | } |
726 | local function serializeString(str) |
727 | local s = ("%q"):format(str) |
728 | s = s:gsub('.', serializeSubst):gsub("[\128-\255]", function(c) |
729 | return "\\" .. c:byte() |
730 | end) |
731 | return s |
732 | end |
733 |
|
734 | -- Mangler for global symbols. Does not protect against collisions, |
735 | -- but makes them unlikely. This is the mangling that is exposed to |
736 | -- to the world. |
737 | local function globalMangling(str) |
738 | if utils.isValidLuaIdentifier(str) then |
739 | return str |
740 | end |
741 | -- Use underscore as escape character |
742 | return '__fnl_global__' .. str:gsub('[^%w]', function (c) |
743 | return ('_%02x'):format(c:byte()) |
744 | end) |
745 | end |
746 |
|
747 | -- Reverse a global mangling. Takes a Lua identifier and |
748 | -- returns the fennel symbol string that created it. |
749 | local function globalUnmangling(identifier) |
750 | local rest = identifier:match('^__fnl_global__(.*)$') |
751 | if rest then |
752 | local r = rest:gsub('_[%da-f][%da-f]', function (code) |
753 | return string.char(tonumber(code:sub(2), 16)) |
754 | end) |
755 | return r -- don't return multiple values |
756 | else |
757 | return identifier |
758 | end |
759 | end |
760 |
|
761 | -- If there's a provided list of allowed globals, don't let references thru that |
762 | -- aren't on the list. This list is set at the compiler entry points of compile |
763 | -- and compileStream. |
764 | local allowedGlobals |
765 |
|
766 | local function globalAllowed(name) |
767 | if not allowedGlobals then return true end |
768 | for _, g in ipairs(allowedGlobals) do |
769 | if g == name then return true end |
770 | end |
771 | end |
772 |
|
773 | -- Creates a symbol from a string by mangling it. |
774 | -- ensures that the generated symbol is unique |
775 | -- if the input string is unique in the scope. |
776 | local function localMangling(str, scope, ast, tempManglings) |
777 | local append = 0 |
778 | local mangling = str |
779 | assertCompile(not utils.isMultiSym(str), 'unexpected multi symbol ' .. str, ast) |
780 |
|
781 | -- Mapping mangling to a valid Lua identifier |
782 | if utils.luaKeywords[mangling] or mangling:match('^%d') then |
783 | mangling = '_' .. mangling |
784 | end |
785 | mangling = mangling:gsub('-', '_') |
786 | mangling = mangling:gsub('[^%w_]', function (c) |
787 | return ('_%02x'):format(c:byte()) |
788 | end) |
789 |
|
790 | -- Prevent name collisions with existing symbols |
791 | local raw = mangling |
792 | while scope.unmanglings[mangling] do |
793 | mangling = raw .. append |
794 | append = append + 1 |
795 | end |
796 |
|
797 | scope.unmanglings[mangling] = str |
798 | local manglings = tempManglings or scope.manglings |
799 | manglings[str] = mangling |
800 | return mangling |
801 | end |
802 |
|
803 | -- Calling this function will mean that further |
804 | -- compilation in scope will use these new manglings |
805 | -- instead of the current manglings. |
806 | local function applyManglings(scope, newManglings, ast) |
807 | for raw, mangled in pairs(newManglings) do |
808 | assertCompile(not scope.refedglobals[mangled], |
809 | "use of global " .. raw .. " is aliased by a local", ast) |
810 | scope.manglings[raw] = mangled |
811 | end |
812 | end |
813 |
|
814 | -- Combine parts of a symbol |
815 | local function combineParts(parts, scope) |
816 | local ret = scope.manglings[parts[1]] or globalMangling(parts[1]) |
817 | for i = 2, #parts do |
818 | if utils.isValidLuaIdentifier(parts[i]) then |
819 | if parts.multiSymMethodCall and i == #parts then |
820 | ret = ret .. ':' .. parts[i] |
821 | else |
822 | ret = ret .. '.' .. parts[i] |
823 | end |
824 | else |
825 | ret = ret .. '[' .. serializeString(parts[i]) .. ']' |
826 | end |
827 | end |
828 | return ret |
829 | end |
830 |
|
831 | -- Generates a unique symbol in the scope. |
832 | local function gensym(scope, base) |
833 | local mangling |
834 | local append = 0 |
835 | repeat |
836 | mangling = (base or '') .. '_' .. append .. '_' |
837 | append = append + 1 |
838 | until not scope.unmanglings[mangling] |
839 | scope.unmanglings[mangling] = true |
840 | return mangling |
841 | end |
842 |
|
843 | -- Generates a unique symbol in the scope based on the base name. Calling |
844 | -- repeatedly with the same base and same scope will return existing symbol |
845 | -- rather than generating new one. |
846 | local function autogensym(base, scope) |
847 | local parts = utils.isMultiSym(base) |
848 | if(parts) then |
849 | parts[1] = autogensym(parts[1], scope) |
850 | return table.concat(parts, parts.multiSymMethodCall and ":" or ".") |
851 | end |
852 |
|
853 | if scope.autogensyms[base] then return scope.autogensyms[base] end |
854 | local mangling = gensym(scope, base:sub(1, -2)) |
855 | scope.autogensyms[base] = mangling |
856 | return mangling |
857 | end |
858 |
|
859 | -- Check if a binding is valid |
860 | local function checkBindingValid(symbol, scope, ast) |
861 | -- Check if symbol will be over shadowed by special |
862 | local name = symbol[1] |
863 | assertCompile(not scope.specials[name] and not scope.macros[name], |
864 | ("local %s was overshadowed by a special form or macro") |
865 | :format(name), ast) |
866 | assertCompile(not utils.isQuoted(symbol), |
867 | ("macro tried to bind %s without gensym"):format(name), symbol) |
868 |
|
869 | end |
870 |
|
871 | -- Declare a local symbol |
872 | local function declareLocal(symbol, meta, scope, ast, tempManglings) |
873 | checkBindingValid(symbol, scope, ast) |
874 | local name = symbol[1] |
875 | assertCompile(not utils.isMultiSym(name), |
876 | "unexpected multi symbol " .. name, ast) |
877 | local mangling = localMangling(name, scope, ast, tempManglings) |
878 | scope.symmeta[name] = meta |
879 | return mangling |
880 | end |
881 |
|
882 | -- Convert symbol to Lua code. Will only work for local symbols |
883 | -- if they have already been declared via declareLocal |
884 | local function symbolToExpression(symbol, scope, isReference) |
885 | local name = symbol[1] |
886 | local multiSymParts = utils.isMultiSym(name) |
887 | if scope.hashfn then |
888 | if name == '$' then name = '$1' end |
889 | if multiSymParts then |
890 | if multiSymParts[1] == "$" then |
891 | multiSymParts[1] = "$1" |
892 | name = table.concat(multiSymParts, ".") |
893 | end |
894 | end |
895 | end |
896 | local parts = multiSymParts or {name} |
897 | local etype = (#parts > 1) and "expression" or "sym" |
898 | local isLocal = scope.manglings[parts[1]] |
899 | if isLocal and scope.symmeta[parts[1]] then scope.symmeta[parts[1]].used = true end |
900 | -- if it's a reference and not a symbol which introduces a new binding |
901 | -- then we need to check for allowed globals |
902 | assertCompile(not isReference or isLocal or globalAllowed(parts[1]), |
903 | 'unknown global in strict mode: ' .. parts[1], symbol) |
904 | if not isLocal then |
905 | utils.root.scope.refedglobals[parts[1]] = true |
906 | end |
907 | return utils.expr(combineParts(parts, scope), etype) |
908 | end |
909 |
|
910 |
|
911 | -- Emit Lua code |
912 | local function emit(chunk, out, ast) |
913 | if type(out) == 'table' then |
914 | table.insert(chunk, out) |
915 | else |
916 | table.insert(chunk, {leaf = out, ast = ast}) |
917 | end |
918 | end |
919 |
|
920 | -- Do some peephole optimization. |
921 | local function peephole(chunk) |
922 | if chunk.leaf then return chunk end |
923 | -- Optimize do ... end in some cases. |
924 | if #chunk >= 3 and |
925 | chunk[#chunk - 2].leaf == 'do' and |
926 | not chunk[#chunk - 1].leaf and |
927 | chunk[#chunk].leaf == 'end' then |
928 | local kid = peephole(chunk[#chunk - 1]) |
929 | local newChunk = {ast = chunk.ast} |
930 | for i = 1, #chunk - 3 do table.insert(newChunk, peephole(chunk[i])) end |
931 | for i = 1, #kid do table.insert(newChunk, kid[i]) end |
932 | return newChunk |
933 | end |
934 | -- Recurse |
935 | return utils.map(chunk, peephole) |
936 | end |
937 |
|
938 | -- correlate line numbers in input with line numbers in output |
939 | local function flattenChunkCorrelated(mainChunk) |
940 | local function flatten(chunk, out, lastLine, file) |
941 | if chunk.leaf then |
942 | out[lastLine] = (out[lastLine] or "") .. " " .. chunk.leaf |
943 | else |
944 | for _, subchunk in ipairs(chunk) do |
945 | -- Ignore empty chunks |
946 | if subchunk.leaf or #subchunk > 0 then |
947 | -- don't increase line unless it's from the same file |
948 | if subchunk.ast and file == subchunk.ast.file then |
949 | lastLine = math.max(lastLine, subchunk.ast.line or 0) |
950 | end |
951 | lastLine = flatten(subchunk, out, lastLine, file) |
952 | end |
953 | end |
954 | end |
955 | return lastLine |
956 | end |
957 | local out = {} |
958 | local last = flatten(mainChunk, out, 1, mainChunk.file) |
959 | for i = 1, last do |
960 | if out[i] == nil then out[i] = "" end |
961 | end |
962 | return table.concat(out, "\n") |
963 | end |
964 |
|
965 | -- Flatten a tree of indented Lua source code lines. |
966 | -- Tab is what is used to indent a block. |
967 | local function flattenChunk(sm, chunk, tab, depth) |
968 | if type(tab) == 'boolean' then tab = tab and ' ' or '' end |
969 | if chunk.leaf then |
970 | local code = chunk.leaf |
971 | local info = chunk.ast |
972 | -- Just do line info for now to save memory |
973 | if sm then sm[#sm + 1] = info and info.line or -1 end |
974 | return code |
975 | else |
976 | local parts = utils.map(chunk, function(c) |
977 | if c.leaf or #c > 0 then -- Ignore empty chunks |
978 | local sub = flattenChunk(sm, c, tab, depth + 1) |
979 | if depth > 0 then sub = tab .. sub:gsub('\n', '\n' .. tab) end |
980 | return sub |
981 | end |
982 | end) |
983 | return table.concat(parts, '\n') |
984 | end |
985 | end |
986 |
|
987 | -- Some global state for all fennel sourcemaps. For the time being, |
988 | -- this seems the easiest way to store the source maps. |
989 | -- Sourcemaps are stored with source being mapped as the key, prepended |
990 | -- with '@' if it is a filename (like debug.getinfo returns for source). |
991 | -- The value is an array of mappings for each line. |
992 | local fennelSourcemap = {} |
993 | -- TODO: loading, unloading, and saving sourcemaps? |
994 |
|
995 | local function makeShortSrc(source) |
996 | source = source:gsub('\n', ' ') |
997 | if #source <= 49 then |
998 | return '[fennel "' .. source .. '"]' |
999 | else |
1000 | return '[fennel "' .. source:sub(1, 46) .. '..."]' |
1001 | end |
1002 | end |
1003 |
|
1004 | -- Return Lua source and source map table |
1005 | local function flatten(chunk, options) |
1006 | chunk = peephole(chunk) |
1007 | if(options.correlate) then |
1008 | return flattenChunkCorrelated(chunk), {} |
1009 | else |
1010 | local sm = {} |
1011 | local ret = flattenChunk(sm, chunk, options.indent, 0) |
1012 | if sm then |
1013 | local key, short_src |
1014 | if options.filename then |
1015 | short_src = options.filename |
1016 | key = '@' .. short_src |
1017 | else |
1018 | key = ret |
1019 | short_src = makeShortSrc(options.source or ret) |
1020 | end |
1021 | sm.short_src = short_src |
1022 | sm.key = key |
1023 | fennelSourcemap[key] = sm |
1024 | end |
1025 | return ret, sm |
1026 | end |
1027 | end |
1028 |
|
1029 | -- module-wide state for metadata |
1030 | -- create metadata table with weakly-referenced keys |
1031 | local function makeMetadata() |
1032 | return setmetatable({}, { |
1033 | __mode = 'k', |
1034 | __index = { |
1035 | get = function(self, tgt, key) |
1036 | if self[tgt] then return self[tgt][key] end |
1037 | end, |
1038 | set = function(self, tgt, key, value) |
1039 | self[tgt] = self[tgt] or {} |
1040 | self[tgt][key] = value |
1041 | return tgt |
1042 | end, |
1043 | setall = function(self, tgt, ...) |
1044 | local kvLen, kvs = select('#', ...), {...} |
1045 | if kvLen % 2 ~= 0 then |
1046 | error('metadata:setall() expected even number of k/v pairs') |
1047 | end |
1048 | self[tgt] = self[tgt] or {} |
1049 | for i = 1, kvLen, 2 do self[tgt][kvs[i]] = kvs[i + 1] end |
1050 | return tgt |
1051 | end, |
1052 | }}) |
1053 | end |
1054 |
|
1055 | -- Convert expressions to Lua string |
1056 | local function exprs1(exprs) |
1057 | return table.concat(utils.map(exprs, 1), ', ') |
1058 | end |
1059 |
|
1060 | -- Compile side effects for a chunk |
1061 | local function keepSideEffects(exprs, chunk, start, ast) |
1062 | start = start or 1 |
1063 | for j = start, #exprs do |
1064 | local se = exprs[j] |
1065 | -- Avoid the rogue 'nil' expression (nil is usually a literal, |
1066 | -- but becomes an expression if a special form |
1067 | -- returns 'nil'.) |
1068 | if se.type == 'expression' and se[1] ~= 'nil' then |
1069 | emit(chunk, ('do local _ = %s end'):format(tostring(se)), ast) |
1070 | elseif se.type == 'statement' then |
1071 | local code = tostring(se) |
1072 | emit(chunk, code:byte() == 40 and ("do end " .. code) or code , ast) |
1073 | end |
1074 | end |
1075 | end |
1076 |
|
1077 | -- Does some common handling of returns and register |
1078 | -- targets for special forms. Also ensures a list expression |
1079 | -- has an acceptable number of expressions if opts contains the |
1080 | -- "nval" option. |
1081 | local function handleCompileOpts(exprs, parent, opts, ast) |
1082 | if opts.nval then |
1083 | local n = opts.nval |
1084 | if n ~= #exprs then |
1085 | local len = #exprs |
1086 | if len > n then |
1087 | -- Drop extra |
1088 | keepSideEffects(exprs, parent, n + 1, ast) |
1089 | for i = n + 1, len do |
1090 | exprs[i] = nil |
1091 | end |
1092 | else |
1093 | -- Pad with nils |
1094 | for i = #exprs + 1, n do |
1095 | exprs[i] = utils.expr('nil', 'literal') |
1096 | end |
1097 | end |
1098 | end |
1099 | end |
1100 | if opts.tail then |
1101 | emit(parent, ('return %s'):format(exprs1(exprs)), ast) |
1102 | end |
1103 | if opts.target then |
1104 | local result = exprs1(exprs) |
1105 | if result == '' then result = 'nil' end |
1106 | emit(parent, ('%s = %s'):format(opts.target, result), ast) |
1107 | end |
1108 | if opts.tail or opts.target then |
1109 | -- Prevent statements and expression from being used twice if they |
1110 | -- have side-effects. Since if the target or tail options are set, |
1111 | -- the expressions are already emitted, we should not return them. This |
1112 | -- is fine, as when these options are set, the caller doesn't need the result |
1113 | -- anyways. |
1114 | exprs = {} |
1115 | end |
1116 | return exprs |
1117 | end |
1118 |
|
1119 | local function macroexpand(ast, scope, once) |
1120 | if not utils.isList(ast) then return ast end -- bail early if not a list form |
1121 | local multiSymParts = utils.isMultiSym(ast[1]) |
1122 | local macro = utils.isSym(ast[1]) and scope.macros[utils.deref(ast[1])] |
1123 | if not macro and multiSymParts then |
1124 | local inMacroModule |
1125 | macro = scope.macros |
1126 | for i = 1, #multiSymParts do |
1127 | macro = utils.isTable(macro) and macro[multiSymParts[i]] |
1128 | if macro then inMacroModule = true end |
1129 | end |
1130 | assertCompile(not inMacroModule or type(macro) == 'function', |
1131 | 'macro not found in imported macro module', ast) |
1132 | end |
1133 | if not macro then return ast end |
1134 | local oldScope = scopes.macro |
1135 | scopes.macro = scope |
1136 | local ok, transformed = pcall(macro, unpack(ast, 2)) |
1137 | scopes.macro = oldScope |
1138 | assertCompile(ok, transformed, ast) |
1139 | if once or not transformed then return transformed end -- macroexpand-1 |
1140 | return macroexpand(transformed, scope) |
1141 | end |
1142 |
|
1143 | -- Compile an AST expression in the scope into parent, a tree |
1144 | -- of lines that is eventually compiled into Lua code. Also |
1145 | -- returns some information about the evaluation of the compiled expression, |
1146 | -- which can be used by the calling function. Macros |
1147 | -- are resolved here, as well as special forms in that order. |
1148 | -- the 'ast' param is the root AST to compile |
1149 | -- the 'scope' param is the scope in which we are compiling |
1150 | -- the 'parent' param is the table of lines that we are compiling into. |
1151 | -- add lines to parent by appending strings. Add indented blocks by appending |
1152 | -- tables of more lines. |
1153 | -- the 'opts' param contains info about where the form is being compiled. |
1154 | -- Options include: |
1155 | -- 'target' - mangled name of symbol(s) being compiled to. |
1156 | -- Could be one variable, 'a', or a list, like 'a, b, _0_'. |
1157 | -- 'tail' - boolean indicating tail position if set. If set, form will generate a return |
1158 | -- instruction. |
1159 | -- 'nval' - The number of values to compile to if it is known to be a fixed value. |
1160 |
|
1161 | -- In Lua, an expression can evaluate to 0 or more values via multiple |
1162 | -- returns. In many cases, Lua will drop extra values and convert a 0 value |
1163 | -- expression to nil. In other cases, Lua will use all of the values in an |
1164 | -- expression, such as in the last argument of a function call. Nval is an |
1165 | -- option passed to compile1 to say that the resulting expression should have |
1166 | -- at least n values. It lets us generate better code, because if we know we |
1167 | -- are only going to use 1 or 2 values from an expression, we can create 1 or 2 |
1168 | -- locals to store intermediate results rather than turn the expression into a |
1169 | -- closure that is called immediately, which we have to do if we don't know. |
1170 |
|
1171 | local function compile1(ast, scope, parent, opts) |
1172 | opts = opts or {} |
1173 | local exprs = {} |
1174 | -- expand any top-level macros before parsing and emitting Lua |
1175 | ast = macroexpand(ast, scope) |
1176 | -- Compile the form |
1177 | if utils.isList(ast) then -- Function call or special form |
1178 | assertCompile(#ast > 0, "expected a function, macro, or special to call", ast) |
1179 | -- Test for special form |
1180 | local len, first = #ast, ast[1] |
1181 | local multiSymParts = utils.isMultiSym(first) |
1182 | local special = utils.isSym(first) and scope.specials[utils.deref(first)] |
1183 | if special then -- Special form |
1184 | exprs = special(ast, scope, parent, opts) or utils.expr('nil', 'literal') |
1185 | -- Be very accepting of strings or expression |
1186 | -- as well as lists or expressions |
1187 | if type(exprs) == 'string' then exprs = utils.expr(exprs, 'expression') end |
1188 | if utils.isExpr(exprs) then exprs = {exprs} end |
1189 | -- Unless the special form explicitly handles the target, tail, and |
1190 | -- nval properties, (indicated via the 'returned' flag), handle |
1191 | -- these options. |
1192 | if not exprs.returned then |
1193 | exprs = handleCompileOpts(exprs, parent, opts, ast) |
1194 | elseif opts.tail or opts.target then |
1195 | exprs = {} |
1196 | end |
1197 | exprs.returned = true |
1198 | return exprs |
1199 | elseif multiSymParts and multiSymParts.multiSymMethodCall then |
1200 | local tableWithMethod = table.concat({ |
1201 | unpack(multiSymParts, 1, #multiSymParts - 1) |
1202 | }, '.') |
1203 | local methodToCall = multiSymParts[#multiSymParts] |
1204 | local newAST = utils.list(utils.sym(':', scope), utils.sym(tableWithMethod, scope), |
1205 | methodToCall) |
1206 | for i = 2, len do |
1207 | newAST[#newAST + 1] = ast[i] |
1208 | end |
1209 | local compiled = compile1(newAST, scope, parent, opts) |
1210 | exprs = compiled |
1211 | else -- Function call |
1212 | local fargs = {} |
1213 | local fcallee = compile1(ast[1], scope, parent, { |
1214 | nval = 1 |
1215 | })[1] |
1216 | assertCompile(fcallee.type ~= 'literal', |
1217 | 'cannot call literal value ' .. tostring(ast[1]), ast) |
1218 | fcallee = tostring(fcallee) |
1219 | for i = 2, len do |
1220 | local subexprs = compile1(ast[i], scope, parent, { |
1221 | nval = i ~= len and 1 or nil |
1222 | }) |
1223 | fargs[#fargs + 1] = subexprs[1] or utils.expr('nil', 'literal') |
1224 | if i == len then |
1225 | -- Add sub expressions to function args |
1226 | for j = 2, #subexprs do |
1227 | fargs[#fargs + 1] = subexprs[j] |
1228 | end |
1229 | else |
1230 | -- Emit sub expression only for side effects |
1231 | keepSideEffects(subexprs, parent, 2, ast[i]) |
1232 | end |
1233 | end |
1234 | local call = ('%s(%s)'):format(tostring(fcallee), exprs1(fargs)) |
1235 | exprs = handleCompileOpts({utils.expr(call, 'statement')}, parent, opts, ast) |
1236 | end |
1237 | elseif utils.isVarg(ast) then |
1238 | assertCompile(scope.vararg, "unexpected vararg", ast) |
1239 | exprs = handleCompileOpts({utils.expr('...', 'varg')}, parent, opts, ast) |
1240 | elseif utils.isSym(ast) then |
1241 | local e |
1242 | local multiSymParts = utils.isMultiSym(ast) |
1243 | assertCompile(not (multiSymParts and multiSymParts.multiSymMethodCall), |
1244 | "multisym method calls may only be in call position", ast) |
1245 | -- Handle nil as special symbol - it resolves to the nil literal rather than |
1246 | -- being unmangled. Alternatively, we could remove it from the lua keywords table. |
1247 | if ast[1] == 'nil' then |
1248 | e = utils.expr('nil', 'literal') |
1249 | else |
1250 | e = symbolToExpression(ast, scope, true) |
1251 | end |
1252 | exprs = handleCompileOpts({e}, parent, opts, ast) |
1253 | elseif type(ast) == 'nil' or type(ast) == 'boolean' then |
1254 | exprs = handleCompileOpts({utils.expr(tostring(ast), 'literal')}, parent, opts) |
1255 | elseif type(ast) == 'number' then |
1256 | local n = ('%.17g'):format(ast) |
1257 | exprs = handleCompileOpts({utils.expr(n, 'literal')}, parent, opts) |
1258 | elseif type(ast) == 'string' then |
1259 | local s = serializeString(ast) |
1260 | exprs = handleCompileOpts({utils.expr(s, 'literal')}, parent, opts) |
1261 | elseif type(ast) == 'table' then |
1262 | local buffer = {} |
1263 | for i = 1, #ast do -- Write numeric keyed values. |
1264 | local nval = i ~= #ast and 1 |
1265 | buffer[#buffer + 1] = exprs1(compile1(ast[i], scope, |
1266 | parent, {nval = nval})) |
1267 | end |
1268 | local function writeOtherValues(k) |
1269 | if type(k) ~= 'number' or math.floor(k) ~= k or k < 1 or k > #ast then |
1270 | if type(k) == 'string' and utils.isValidLuaIdentifier(k) then |
1271 | return {k, k} |
1272 | else |
1273 | local kstr = '[' .. tostring(compile1(k, scope, parent, |
1274 | {nval = 1})[1]) .. ']' |
1275 | return { kstr, k } |
1276 | end |
1277 | end |
1278 | end |
1279 | local keys = utils.kvmap(ast, writeOtherValues) |
1280 | table.sort(keys, function (a, b) return a[1] < b[1] end) |
1281 | utils.map(keys, function(k) |
1282 | local v = tostring(compile1(ast[k[2]], scope, parent, {nval = 1})[1]) |
1283 | return ('%s = %s'):format(k[1], v) end, |
1284 | buffer) |
1285 | local tbl = '{' .. table.concat(buffer, ', ') ..'}' |
1286 | exprs = handleCompileOpts({utils.expr(tbl, 'expression')}, parent, opts, ast) |
1287 | else |
1288 | assertCompile(false, 'could not compile value of type ' .. type(ast), ast) |
1289 | end |
1290 | exprs.returned = true |
1291 | return exprs |
1292 | end |
1293 |
|
1294 | -- Implements destructuring for forms like let, bindings, etc. |
1295 | -- Takes a number of options to control behavior. |
1296 | -- var: Whether or not to mark symbols as mutable |
1297 | -- declaration: begin each assignment with 'local' in output |
1298 | -- nomulti: disallow multisyms in the destructuring. Used for (local) and (global). |
1299 | -- noundef: Don't set undefined bindings. (set) |
1300 | -- forceglobal: Don't allow local bindings |
1301 | local function destructure(to, from, ast, scope, parent, opts) |
1302 | opts = opts or {} |
1303 | local isvar = opts.isvar |
1304 | local declaration = opts.declaration |
1305 | local nomulti = opts.nomulti |
1306 | local noundef = opts.noundef |
1307 | local forceglobal = opts.forceglobal |
1308 | local forceset = opts.forceset |
1309 | local setter = declaration and "local %s = %s" or "%s = %s" |
1310 |
|
1311 | local newManglings = {} |
1312 |
|
1313 | -- Get Lua source for symbol, and check for errors |
1314 | local function getname(symbol, up1) |
1315 | local raw = symbol[1] |
1316 | assertCompile(not (nomulti and utils.isMultiSym(raw)), |
1317 | 'unexpected multi symbol ' .. raw, up1) |
1318 | if declaration then |
1319 | return declareLocal(symbol, {var = isvar}, scope, |
1320 | symbol, newManglings) |
1321 | else |
1322 | local parts = utils.isMultiSym(raw) or {raw} |
1323 | local meta = scope.symmeta[parts[1]] |
1324 | if #parts == 1 and not forceset then |
1325 | assertCompile(not(forceglobal and meta), |
1326 | ("global %s conflicts with local"):format(tostring(symbol)), symbol) |
1327 | assertCompile(not (meta and not meta.var), |
1328 | 'expected var ' .. raw, symbol) |
1329 | assertCompile(meta or not noundef, |
1330 | 'expected local ' .. parts[1], symbol) |
1331 | end |
1332 | if forceglobal then |
1333 | assertCompile(not scope.symmeta[scope.unmanglings[raw]], |
1334 | "global " .. raw .. " conflicts with local", symbol) |
1335 | scope.manglings[raw] = globalMangling(raw) |
1336 | scope.unmanglings[globalMangling(raw)] = raw |
1337 | if allowedGlobals then |
1338 | table.insert(allowedGlobals, raw) |
1339 | end |
1340 | end |
1341 |
|
1342 | return symbolToExpression(symbol, scope)[1] |
1343 | end |
1344 | end |
1345 |
|
1346 | -- Compile the outer most form. We can generate better Lua in this case. |
1347 | local function compileTopTarget(lvalues) |
1348 | -- Calculate initial rvalue |
1349 | local inits = utils.map(lvalues, function(x) |
1350 | return scope.manglings[x] and x or 'nil' end) |
1351 | local init = table.concat(inits, ', ') |
1352 | local lvalue = table.concat(lvalues, ', ') |
1353 |
|
1354 | local plen, plast = #parent, parent[#parent] |
1355 | local ret = compile1(from, scope, parent, {target = lvalue}) |
1356 | if declaration then |
1357 | -- A single leaf emitted at the end of the parent chunk means |
1358 | -- a simple assignment a = x was emitted, and we can just |
1359 | -- splice "local " onto the front of it. However, we can't |
1360 | -- just check based on plen, because some forms (such as |
1361 | -- include) insert new chunks at the top of the parent chunk |
1362 | -- rather than just at the end; this loop checks for this |
1363 | -- occurance and updates plen to be the index of the last |
1364 | -- thing in the parent before compiling the new value. |
1365 | for pi = plen, #parent do |
1366 | if parent[pi] == plast then plen = pi end |
1367 | end |
1368 | if #parent == plen + 1 and parent[#parent].leaf then |
1369 | parent[#parent].leaf = 'local ' .. parent[#parent].leaf |
1370 | else |
1371 | table.insert(parent, plen + 1, |
1372 | { leaf = 'local ' .. lvalue .. ' = ' .. init, |
1373 | ast = ast}) |
1374 | end |
1375 | end |
1376 | return ret |
1377 | end |
1378 |
|
1379 | -- Recursive auxiliary function |
1380 | local function destructure1(left, rightexprs, up1, top) |
1381 | if utils.isSym(left) and left[1] ~= "nil" then |
1382 | checkBindingValid(left, scope, left) |
1383 | local lname = getname(left, up1) |
1384 | if top then |
1385 | compileTopTarget({lname}) |
1386 | else |
1387 | emit(parent, setter:format(lname, exprs1(rightexprs)), left) |
1388 | end |
1389 | elseif utils.isTable(left) then -- table destructuring |
1390 | if top then rightexprs = compile1(from, scope, parent) end |
1391 | local s = gensym(scope) |
1392 | local right = exprs1(rightexprs) |
1393 | if right == '' then right = 'nil' end |
1394 | emit(parent, ("local %s = %s"):format(s, right), left) |
1395 | for k, v in utils.stablepairs(left) do |
1396 | if utils.isSym(left[k]) and left[k][1] == "&" then |
1397 | assertCompile(type(k) == "number" and not left[k+2], |
1398 | "expected rest argument before last parameter", left) |
1399 | local subexpr = utils.expr(('{(table.unpack or unpack)(%s, %s)}') |
1400 | :format(s, k), 'expression') |
1401 | destructure1(left[k+1], {subexpr}, left) |
1402 | return |
1403 | else |
1404 | if utils.isSym(k) and tostring(k) == ":" and utils.isSym(v) then |
1405 | k = tostring(v) |
1406 | end |
1407 | if type(k) ~= "number" then k = serializeString(k) end |
1408 | local subexpr = utils.expr(('%s[%s]'):format(s, k), 'expression') |
1409 | destructure1(v, {subexpr}, left) |
1410 | end |
1411 | end |
1412 | elseif utils.isList(left) then -- values destructuring |
1413 | local leftNames, tables = {}, {} |
1414 | for i, name in ipairs(left) do |
1415 | local symname |
1416 | if utils.isSym(name) then -- binding directly to a name |
1417 | symname = getname(name, up1) |
1418 | else -- further destructuring of tables inside values |
1419 | symname = gensym(scope) |
1420 | tables[i] = {name, utils.expr(symname, 'sym')} |
1421 | end |
1422 | table.insert(leftNames, symname) |
1423 | end |
1424 | if top then |
1425 | compileTopTarget(leftNames) |
1426 | else |
1427 | local lvalue = table.concat(leftNames, ', ') |
1428 | emit(parent, setter:format(lvalue, exprs1(rightexprs)), left) |
1429 | end |
1430 | for _, pair in utils.stablepairs(tables) do -- recurse if left-side tables found |
1431 | destructure1(pair[1], {pair[2]}, left) |
1432 | end |
1433 | else |
1434 | assertCompile(false, ("unable to bind %s %s"): |
1435 | format(type(left), tostring(left)), |
1436 | type(up1[2]) == "table" and up1[2] or up1) |
1437 | end |
1438 | if top then return {returned = true} end |
1439 | end |
1440 |
|
1441 | local ret = destructure1(to, nil, ast, true) |
1442 | applyManglings(scope, newManglings, ast) |
1443 | return ret |
1444 | end |
1445 |
|
1446 | local function requireInclude(ast, scope, parent, opts) |
1447 | opts.fallback = function(e) |
1448 | return utils.expr(('require(%s)'):format(tostring(e)), 'statement') |
1449 | end |
1450 | return scopes.global.specials['include'](ast, scope, parent, opts) |
1451 | end |
1452 |
|
1453 | local function compileStream(strm, options) |
1454 | local opts = utils.copy(options) |
1455 | local oldGlobals = allowedGlobals |
1456 | utils.root:setReset() |
1457 | allowedGlobals = opts.allowedGlobals |
1458 | if opts.indent == nil then opts.indent = ' ' end |
1459 | local scope = opts.scope or makeScope(scopes.global) |
1460 | if opts.requireAsInclude then scope.specials.require = requireInclude end |
1461 | local vals = {} |
1462 | for ok, val in parser.parser(strm, opts.filename, opts) do |
1463 | if not ok then break end |
1464 | vals[#vals + 1] = val |
1465 | end |
1466 | local chunk = {} |
1467 | utils.root.chunk, utils.root.scope, utils.root.options = chunk, scope, opts |
1468 | for i = 1, #vals do |
1469 | local exprs = compile1(vals[i], scope, chunk, { |
1470 | tail = i == #vals, |
1471 | nval = i < #vals and 0 or nil |
1472 | }) |
1473 | keepSideEffects(exprs, chunk, nil, vals[i]) |
1474 | end |
1475 | allowedGlobals = oldGlobals |
1476 | utils.root.reset() |
1477 | return flatten(chunk, opts) |
1478 | end |
1479 |
|
1480 | local function compileString(str, options) |
1481 | options = options or {} |
1482 | local oldSource = options.source |
1483 | options.source = str -- used by fennelfriend |
1484 | local ast = compileStream(parser.stringStream(str), options) |
1485 | options.source = oldSource |
1486 | return ast |
1487 | end |
1488 |
|
1489 | local function compile(ast, options) |
1490 | local opts = utils.copy(options) |
1491 | local oldGlobals = allowedGlobals |
1492 | utils.root:setReset() |
1493 | allowedGlobals = opts.allowedGlobals |
1494 | if opts.indent == nil then opts.indent = ' ' end |
1495 | local chunk = {} |
1496 | local scope = opts.scope or makeScope(scopes.global) |
1497 | utils.root.chunk, utils.root.scope, utils.root.options = chunk, scope, opts |
1498 | if opts.requireAsInclude then scope.specials.require = requireInclude end |
1499 | local exprs = compile1(ast, scope, chunk, {tail = true}) |
1500 | keepSideEffects(exprs, chunk, nil, ast) |
1501 | allowedGlobals = oldGlobals |
1502 | utils.root.reset() |
1503 | return flatten(chunk, opts) |
1504 | end |
1505 |
|
1506 | -- A custom traceback function for Fennel that looks similar to |
1507 | -- the Lua's debug.traceback. |
1508 | -- Use with xpcall to produce fennel specific stacktraces. |
1509 | local function traceback(msg, start) |
1510 | local level = start or 2 -- Can be used to skip some frames |
1511 | local lines = {} |
1512 | if msg then |
1513 | if msg:find("^Compile error") or msg:find("^Parse error") then |
1514 | -- End users don't want to see compiler stack traces, but when |
1515 | -- you're hacking on the compiler, export FENNEL_DEBUG=trace |
1516 | if not utils.debugOn("trace") then return msg end |
1517 | table.insert(lines, msg) |
1518 | else |
1519 | local newmsg = msg:gsub('^[^:]*:%d+:%s+', 'runtime error: ') |
1520 | table.insert(lines, newmsg) |
1521 | end |
1522 | end |
1523 | table.insert(lines, 'stack traceback:') |
1524 | while true do |
1525 | local info = debug.getinfo(level, "Sln") |
1526 | if not info then break end |
1527 | local line |
1528 | if info.what == "C" then |
1529 | if info.name then |
1530 | line = (' [C]: in function \'%s\''):format(info.name) |
1531 | else |
1532 | line = ' [C]: in ?' |
1533 | end |
1534 | else |
1535 | local remap = fennelSourcemap[info.source] |
1536 | if remap and remap[info.currentline] then |
1537 | -- And some global info |
1538 | info.short_src = remap.short_src |
1539 | local mapping = remap[info.currentline] |
1540 | -- Overwrite info with values from the mapping (mapping is now |
1541 | -- just integer, but may eventually be a table) |
1542 | info.currentline = mapping |
1543 | end |
1544 | if info.what == 'Lua' then |
1545 | local n = info.name and ("'" .. info.name .. "'") or '?' |
1546 | line = (' %s:%d: in function %s'):format(info.short_src, info.currentline, n) |
1547 | elseif info.short_src == '(tail call)' then |
1548 | line = ' (tail call)' |
1549 | else |
1550 | line = (' %s:%d: in main chunk'):format(info.short_src, info.currentline) |
1551 | end |
1552 | end |
1553 | table.insert(lines, line) |
1554 | level = level + 1 |
1555 | end |
1556 | return table.concat(lines, '\n') |
1557 | end |
1558 |
|
1559 | -- make a transformer for key / value table pairs, preserving all numeric keys |
1560 | local function entryTransform(fk,fv) |
1561 | return function(k, v) |
1562 | if type(k) == 'number' then |
1563 | return k,fv(v) |
1564 | else |
1565 | return fk(k),fv(v) |
1566 | end |
1567 | end |
1568 | end |
1569 |
|
1570 | -- consume everything return nothing |
1571 | local function no() end |
1572 |
|
1573 | local function mixedConcat(t, joiner) |
1574 | local ret = "" |
1575 | local s = "" |
1576 | local seen = {} |
1577 | for k,v in ipairs(t) do |
1578 | table.insert(seen, k) |
1579 | ret = ret .. s .. v |
1580 | s = joiner |
1581 | end |
1582 | for k,v in utils.stablepairs(t) do |
1583 | if not(seen[k]) then |
1584 | ret = ret .. s .. '[' .. k .. ']' .. '=' .. v |
1585 | s = joiner |
1586 | end |
1587 | end |
1588 | return ret |
1589 | end |
1590 |
|
1591 | -- expand a quoted form into a data literal, evaluating unquote |
1592 | local function doQuote (form, scope, parent, runtime) |
1593 | local q = function (x) return doQuote(x, scope, parent, runtime) end |
1594 | -- vararg |
1595 | if utils.isVarg(form) then |
1596 | assertCompile(not runtime, "quoted ... may only be used at compile time", form) |
1597 | return "_VARARG" |
1598 | -- symbol |
1599 | elseif utils.isSym(form) then |
1600 | assertCompile(not runtime, "symbols may only be used at compile time", form) |
1601 | -- We should be able to use "%q" for this but Lua 5.1 throws an error |
1602 | -- when you try to format nil, because it's extremely bad. |
1603 | local filename = form.filename and ('%q'):format(form.filename) or "nil" |
1604 | if utils.deref(form):find("#$") or utils.deref(form):find("#[:.]") then -- autogensym |
1605 | return ("sym('%s', nil, {filename=%s, line=%s})"): |
1606 | format(autogensym(utils.deref(form), scope), filename, form.line or "nil") |
1607 | else -- prevent non-gensymmed symbols from being bound as an identifier |
1608 | return ("sym('%s', nil, {quoted=true, filename=%s, line=%s})"): |
1609 | format(utils.deref(form), filename, form.line or "nil") |
1610 | end |
1611 | -- unquote |
1612 | elseif(utils.isList(form) and utils.isSym(form[1]) and |
1613 | (utils.deref(form[1]) == 'unquote')) then |
1614 | local payload = form[2] |
1615 | local res = unpack(compile1(payload, scope, parent)) |
1616 | return res[1] |
1617 | -- list |
1618 | elseif utils.isList(form) then |
1619 | assertCompile(not runtime, "lists may only be used at compile time", form) |
1620 | local mapped = utils.kvmap(form, entryTransform(no, q)) |
1621 | local filename = form.filename and ('%q'):format(form.filename) or "nil" |
1622 | -- Constructing a list and then adding file/line data to it triggers a |
1623 | -- bug where it changes the value of # for lists that contain nils in |
1624 | -- them; constructing the list all in one go with the source data and |
1625 | -- contents is how we construct lists in the parser and works around |
1626 | -- this problem; allowing # to work in a way that lets us see the nils. |
1627 | return ("setmetatable({filename=%s, line=%s, bytestart=%s, %s}" .. |
1628 | ", getmetatable(list()))") |
1629 | :format(filename, form.line or "nil", form.bytestart or "nil", |
1630 | mixedConcat(mapped, ", ")) |
1631 | -- table |
1632 | elseif type(form) == 'table' then |
1633 | local mapped = utils.kvmap(form, entryTransform(q, q)) |
1634 | local source = getmetatable(form) |
1635 | local filename = source.filename and ('%q'):format(source.filename) or "nil" |
1636 | return ("setmetatable({%s}, {filename=%s, line=%s})"): |
1637 | format(mixedConcat(mapped, ", "), filename, source and source.line or "nil") |
1638 | -- string |
1639 | elseif type(form) == 'string' then |
1640 | return serializeString(form) |
1641 | else |
1642 | return tostring(form) |
1643 | end |
1644 | end |
1645 | return { |
1646 | -- compiling functions: |
1647 | compileString=compileString, compileStream=compileStream, |
1648 | compile=compile, compile1=compile1, emit=emit, destructure=destructure, |
1649 | requireInclude=requireInclude, |
1650 |
|
1651 | -- AST functions: |
1652 | gensym=gensym, autogensym=autogensym, doQuote=doQuote, |
1653 | macroexpand=macroexpand, globalUnmangling=globalUnmangling, |
1654 | applyManglings=applyManglings, globalMangling=globalMangling, |
1655 |
|
1656 | -- scope functions: |
1657 | makeScope=makeScope, keepSideEffects=keepSideEffects, |
1658 | declareLocal=declareLocal, symbolToExpression=symbolToExpression, |
1659 |
|
1660 | -- general functions: |
1661 | assert=assertCompile, metadata=makeMetadata(), traceback=traceback, |
1662 | scopes=scopes, |
1663 | } |
1664 | end)() |
1665 |
|
1666 | -- |
1667 | -- Specials and macros |
1668 | -- |
1669 |
|
1670 | local specials = (function() |
1671 | local SPECIALS = compiler.scopes.global.specials |
1672 |
|
1673 | -- Convert a fennel environment table to a Lua environment table. |
1674 | -- This means automatically unmangling globals when getting a value, |
1675 | -- and mangling values when setting a value. This means the original env |
1676 | -- will see its values updated as expected, regardless of mangling rules. |
1677 | local function wrapEnv(env) |
1678 | return setmetatable({}, { |
1679 | __index = function(_, key) |
1680 | if type(key) == 'string' then |
1681 | key = compiler.globalUnmangling(key) |
1682 | end |
1683 | return env[key] |
1684 | end, |
1685 | __newindex = function(_, key, value) |
1686 | if type(key) == 'string' then |
1687 | key = compiler.globalMangling(key) |
1688 | end |
1689 | env[key] = value |
1690 | end, |
1691 | -- checking the __pairs metamethod won't work automatically in Lua 5.1 |
1692 | -- sadly, but it's important for 5.2+ and can be done manually in 5.1 |
1693 | __pairs = function() |
1694 | local function putenv(k, v) |
1695 | return type(k) == 'string' and compiler.globalUnmangling(k) or k, v |
1696 | end |
1697 | local pt = utils.kvmap(env, putenv) |
1698 | return next, pt, nil |
1699 | end, |
1700 | }) |
1701 | end |
1702 |
|
1703 | local function currentGlobalNames(env) |
1704 | return utils.kvmap(env or _G, compiler.globalUnmangling) |
1705 | end |
1706 |
|
1707 | -- Load code with an environment in all recent Lua versions |
1708 | local function loadCode(code, environment, filename) |
1709 | environment = environment or _ENV or _G |
1710 | if setfenv and loadstring then |
1711 | local f = assert(loadstring(code, filename)) |
1712 | setfenv(f, environment) |
1713 | return f |
1714 | else |
1715 | return assert(load(code, filename, "t", environment)) |
1716 | end |
1717 | end |
1718 |
|
1719 | -- Return a docstring |
1720 | local doc = function(tgt, name) |
1721 | if(not tgt) then return name .. " not found" end |
1722 | local docstring = (compiler.metadata:get(tgt, 'fnl/docstring') or |
1723 | '#<undocumented>'):gsub('\n$', ''):gsub('\n', '\n ') |
1724 | if type(tgt) == "function" then |
1725 | local arglist = table.concat(compiler.metadata:get(tgt, 'fnl/arglist') or |
1726 | {'#<unknown-arguments>'}, ' ') |
1727 | return string.format("(%s%s%s)\n %s", name, #arglist > 0 and ' ' or '', |
1728 | arglist, docstring) |
1729 | else |
1730 | return string.format("%s\n %s", name, docstring) |
1731 | end |
1732 | end |
1733 |
|
1734 | local function docSpecial(name, arglist, docstring) |
1735 | compiler.metadata[SPECIALS[name]] = |
1736 | { ["fnl/docstring"] = docstring, ["fnl/arglist"] = arglist } |
1737 | end |
1738 |
|
1739 | -- Compile a list of forms for side effects |
1740 | local function compileDo(ast, scope, parent, start) |
1741 | start = start or 2 |
1742 | local len = #ast |
1743 | local subScope = compiler.makeScope(scope) |
1744 | for i = start, len do |
1745 | compiler.compile1(ast[i], subScope, parent, { |
1746 | nval = 0 |
1747 | }) |
1748 | end |
1749 | end |
1750 |
|
1751 | -- Implements a do statement, starting at the 'start' element. By default, start is 2. |
1752 | local function doImpl(ast, scope, parent, opts, start, chunk, subScope, preSyms) |
1753 | start = start or 2 |
1754 | subScope = subScope or compiler.makeScope(scope) |
1755 | chunk = chunk or {} |
1756 | local len = #ast |
1757 | local outerTarget = opts.target |
1758 | local outerTail = opts.tail |
1759 | local retexprs = {returned = true} |
1760 |
|
1761 | -- See if we need special handling to get the return values |
1762 | -- of the do block |
1763 | if not outerTarget and opts.nval ~= 0 and not outerTail then |
1764 | if opts.nval then |
1765 | -- Generate a local target |
1766 | local syms = {} |
1767 | for i = 1, opts.nval do |
1768 | local s = preSyms and preSyms[i] or compiler.gensym(scope) |
1769 | syms[i] = s |
1770 | retexprs[i] = utils.expr(s, 'sym') |
1771 | end |
1772 | outerTarget = table.concat(syms, ', ') |
1773 | compiler.emit(parent, ('local %s'):format(outerTarget), ast) |
1774 | compiler.emit(parent, 'do', ast) |
1775 | else |
1776 | -- We will use an IIFE for the do |
1777 | local fname = compiler.gensym(scope) |
1778 | local fargs = scope.vararg and '...' or '' |
1779 | compiler.emit(parent, ('local function %s(%s)'):format(fname, fargs), ast) |
1780 | retexprs = utils.expr(fname .. '(' .. fargs .. ')', 'statement') |
1781 | outerTail = true |
1782 | outerTarget = nil |
1783 | end |
1784 | else |
1785 | compiler.emit(parent, 'do', ast) |
1786 | end |
1787 | -- Compile the body |
1788 | if start > len then |
1789 | -- In the unlikely case we do a do with no arguments. |
1790 | compiler.compile1(nil, subScope, chunk, { |
1791 | tail = outerTail, |
1792 | target = outerTarget |
1793 | }) |
1794 | -- There will be no side effects |
1795 | else |
1796 | for i = start, len do |
1797 | local subopts = { |
1798 | nval = i ~= len and 0 or opts.nval, |
1799 | tail = i == len and outerTail or nil, |
1800 | target = i == len and outerTarget or nil |
1801 | } |
1802 | utils.propagateOptions(opts, subopts) |
1803 | local subexprs = compiler.compile1(ast[i], subScope, chunk, subopts) |
1804 | if i ~= len then |
1805 | compiler.keepSideEffects(subexprs, parent, nil, ast[i]) |
1806 | end |
1807 | end |
1808 | end |
1809 | compiler.emit(parent, chunk, ast) |
1810 | compiler.emit(parent, 'end', ast) |
1811 | return retexprs |
1812 | end |
1813 |
|
1814 | SPECIALS["do"] = doImpl |
1815 | docSpecial("do", {"..."}, "Evaluate multiple forms; return last value.") |
1816 |
|
1817 | -- Unlike most expressions and specials, 'values' resolves with multiple |
1818 | -- values, one for each argument, allowing multiple return values. The last |
1819 | -- expression can return multiple arguments as well, allowing for more than |
1820 | -- the number of expected arguments. |
1821 | SPECIALS["values"] = function(ast, scope, parent) |
1822 | local len = #ast |
1823 | local exprs = {} |
1824 | for i = 2, len do |
1825 | local subexprs = compiler.compile1(ast[i], scope, parent, { |
1826 | nval = (i ~= len) and 1 |
1827 | }) |
1828 | exprs[#exprs + 1] = subexprs[1] |
1829 | if i == len then |
1830 | for j = 2, #subexprs do |
1831 | exprs[#exprs + 1] = subexprs[j] |
1832 | end |
1833 | end |
1834 | end |
1835 | return exprs |
1836 | end |
1837 | docSpecial("values", {"..."}, |
1838 | "Return multiple values from a function. Must be in tail position.") |
1839 |
|
1840 | -- The fn special declares a function. Syntax is similar to other lisps; |
1841 | -- (fn optional-name [arg ...] (body)) |
1842 | -- Further decoration such as docstrings, meta info, and multibody functions a possibility. |
1843 | SPECIALS["fn"] = function(ast, scope, parent) |
1844 | local fScope = compiler.makeScope(scope) |
1845 | local fChunk = {} |
1846 | local index = 2 |
1847 | local fnName = utils.isSym(ast[index]) |
1848 | local isLocalFn |
1849 | local docstring |
1850 | fScope.vararg = false |
1851 | local multi = fnName and utils.isMultiSym(fnName[1]) |
1852 | compiler.assert(not multi or not multi.multiSymMethodCall, |
1853 | "unexpected multi symbol " .. tostring(fnName), ast[index]) |
1854 | if fnName and fnName[1] ~= 'nil' then |
1855 | isLocalFn = not multi |
1856 | if isLocalFn then |
1857 | fnName = compiler.declareLocal(fnName, {}, scope, ast) |
1858 | else |
1859 | fnName = compiler.symbolToExpression(fnName, scope)[1] |
1860 | end |
1861 | index = index + 1 |
1862 | else |
1863 | isLocalFn = true |
1864 | fnName = compiler.gensym(scope) |
1865 | end |
1866 | local argList = compiler.assert(utils.isTable(ast[index]), |
1867 | "expected parameters", |
1868 | type(ast[index]) == "table" and ast[index] or ast) |
1869 | local function getArgName(i, name) |
1870 | if utils.isVarg(name) then |
1871 | compiler.assert(i == #argList, "expected vararg as last parameter", ast[2]) |
1872 | fScope.vararg = true |
1873 | return "..." |
1874 | elseif(utils.isSym(name) and utils.deref(name) ~= "nil" |
1875 | and not utils.isMultiSym(utils.deref(name))) then |
1876 | return compiler.declareLocal(name, {}, fScope, ast) |
1877 | elseif utils.isTable(name) then |
1878 | local raw = utils.sym(compiler.gensym(scope)) |
1879 | local declared = compiler.declareLocal(raw, {}, fScope, ast) |
1880 | compiler.destructure(name, raw, ast, fScope, fChunk, |
1881 | { declaration = true, nomulti = true }) |
1882 | return declared |
1883 | else |
1884 | compiler.assert(false, ("expected symbol for function parameter: %s"): |
1885 | format(tostring(name)), ast[2]) |
1886 | end |
1887 | end |
1888 | local argNameList = utils.kvmap(argList, getArgName) |
1889 | if type(ast[index + 1]) == 'string' and index + 1 < #ast then |
1890 | index = index + 1 |
1891 | docstring = ast[index] |
1892 | end |
1893 | for i = index + 1, #ast do |
1894 | compiler.compile1(ast[i], fScope, fChunk, { |
1895 | tail = i == #ast, |
1896 | nval = i ~= #ast and 0 or nil, |
1897 | }) |
1898 | end |
1899 | if isLocalFn then |
1900 | compiler.emit(parent, ('local function %s(%s)') |
1901 | :format(fnName, table.concat(argNameList, ', ')), ast) |
1902 | else |
1903 | compiler.emit(parent, ('%s = function(%s)') |
1904 | :format(fnName, table.concat(argNameList, ', ')), ast) |
1905 | end |
1906 |
|
1907 | compiler.emit(parent, fChunk, ast) |
1908 | compiler.emit(parent, 'end', ast) |
1909 |
|
1910 | if utils.root.options.useMetadata then |
1911 | local args = utils.map(argList, function(v) |
1912 | -- TODO: show destructured args properly instead of replacing |
1913 | return utils.isTable(v) and '"#<table>"' or string.format('"%s"', tostring(v)) |
1914 | end) |
1915 |
|
1916 | local metaFields = { |
1917 | '"fnl/arglist"', '{' .. table.concat(args, ', ') .. '}', |
1918 | } |
1919 | if docstring then |
1920 | table.insert(metaFields, '"fnl/docstring"') |
1921 | table.insert(metaFields, '"' .. docstring:gsub('%s+$', '') |
1922 | :gsub('\\', '\\\\'):gsub('\n', '\\n') |
1923 | :gsub('"', '\\"') .. '"') |
1924 | end |
1925 | local metaStr = ('require("%s").metadata'): |
1926 | format(utils.root.options.moduleName or "fennel") |
1927 | compiler.emit(parent, string.format('pcall(function() %s:setall(%s, %s) end)', |
1928 | metaStr, fnName, table.concat(metaFields, ', '))) |
1929 | end |
1930 |
|
1931 | return utils.expr(fnName, 'sym') |
1932 | end |
1933 | docSpecial("fn", {"name?", "args", "docstring?", "..."}, |
1934 | "Function syntax. May optionally include a name and docstring." |
1935 | .."\nIf a name is provided, the function will be bound in the current scope." |
1936 | .."\nWhen called with the wrong number of args, excess args will be discarded" |
1937 | .."\nand lacking args will be nil, use lambda for arity-checked functions.") |
1938 |
|
1939 | -- (lua "print('hello!')") -> prints hello, evaluates to nil |
1940 | -- (lua "print 'hello!'" "10") -> prints hello, evaluates to the number 10 |
1941 | -- (lua nil "{1,2,3}") -> Evaluates to a table literal |
1942 | SPECIALS['lua'] = function(ast, _, parent) |
1943 | compiler.assert(#ast == 2 or #ast == 3, "expected 1 or 2 arguments", ast) |
1944 | if ast[2] ~= nil then |
1945 | table.insert(parent, {leaf = tostring(ast[2]), ast = ast}) |
1946 | end |
1947 | if #ast == 3 then |
1948 | return tostring(ast[3]) |
1949 | end |
1950 | end |
1951 |
|
1952 | SPECIALS['doc'] = function(ast, scope, parent) |
1953 | assert(utils.root.options.useMetadata, "can't look up doc with metadata disabled.") |
1954 | compiler.assert(#ast == 2, "expected one argument", ast) |
1955 |
|
1956 | local target = utils.deref(ast[2]) |
1957 | local specialOrMacro = scope.specials[target] or scope.macros[target] |
1958 | if specialOrMacro then |
1959 | return ("print([[%s]])"):format(doc(specialOrMacro, target)) |
1960 | else |
1961 | local value = tostring(compiler.compile1(ast[2], scope, parent, {nval = 1})[1]) |
1962 | -- need to require here since the metadata is stored in the module |
1963 | -- and we need to make sure we look it up in the same module it was |
1964 | -- declared from. |
1965 | return ("print(require('%s').doc(%s, '%s'))") |
1966 | :format(utils.root.options.moduleName or "fennel", value, tostring(ast[2])) |
1967 | end |
1968 | end |
1969 | docSpecial("doc", {"x"}, |
1970 | "Print the docstring and arglist for a function, macro, or special form.") |
1971 |
|
1972 | -- Table lookup |
1973 | SPECIALS["."] = function(ast, scope, parent) |
1974 | local len = #ast |
1975 | compiler.assert(len > 1, "expected table argument", ast) |
1976 | local lhs = compiler.compile1(ast[2], scope, parent, {nval = 1}) |
1977 | if len == 2 then |
1978 | return tostring(lhs[1]) |
1979 | else |
1980 | local indices = {} |
1981 | for i = 3, len do |
1982 | local index = ast[i] |
1983 | if type(index) == 'string' and utils.isValidLuaIdentifier(index) then |
1984 | table.insert(indices, '.' .. index) |
1985 | else |
1986 | index = compiler.compile1(index, scope, parent, {nval = 1})[1] |
1987 | table.insert(indices, '[' .. tostring(index) .. ']') |
1988 | end |
1989 | end |
1990 | -- extra parens are needed for table literals |
1991 | if utils.isTable(ast[2]) then |
1992 | return '(' .. tostring(lhs[1]) .. ')' .. table.concat(indices) |
1993 | else |
1994 | return tostring(lhs[1]) .. table.concat(indices) |
1995 | end |
1996 | end |
1997 | end |
1998 | docSpecial(".", {"tbl", "key1", "..."}, |
1999 | "Look up key1 in tbl table. If more args are provided, do a nested lookup.") |
2000 |
|
2001 | SPECIALS["global"] = function(ast, scope, parent) |
2002 | compiler.assert(#ast == 3, "expected name and value", ast) |
2003 | compiler.destructure(ast[2], ast[3], ast, scope, parent, { |
2004 | nomulti = true, |
2005 | forceglobal = true |
2006 | }) |
2007 | end |
2008 | docSpecial("global", {"name", "val"}, "Set name as a global with val.") |
2009 |
|
2010 | SPECIALS["set"] = function(ast, scope, parent) |
2011 | compiler.assert(#ast == 3, "expected name and value", ast) |
2012 | compiler.destructure(ast[2], ast[3], ast, scope, parent, { |
2013 | noundef = true |
2014 | }) |
2015 | end |
2016 | docSpecial("set", {"name", "val"}, |
2017 | "Set a local variable to a new value. Only works on locals using var.") |
2018 |
|
2019 | SPECIALS["set-forcibly!"] = function(ast, scope, parent) |
2020 | compiler.assert(#ast == 3, "expected name and value", ast) |
2021 | compiler.destructure(ast[2], ast[3], ast, scope, parent, { |
2022 | forceset = true |
2023 | }) |
2024 | end |
2025 |
|
2026 | SPECIALS["local"] = function(ast, scope, parent) |
2027 | compiler.assert(#ast == 3, "expected name and value", ast) |
2028 | compiler.destructure(ast[2], ast[3], ast, scope, parent, { |
2029 | declaration = true, |
2030 | nomulti = true |
2031 | }) |
2032 | end |
2033 | docSpecial("local", {"name", "val"}, |
2034 | "Introduce new top-level immutable local.") |
2035 |
|
2036 | SPECIALS["var"] = function(ast, scope, parent) |
2037 | compiler.assert(#ast == 3, "expected name and value", ast) |
2038 | compiler.destructure(ast[2], ast[3], ast, scope, parent, { |
2039 | declaration = true, nomulti = true, isvar = true }) |
2040 | end |
2041 | docSpecial("var", {"name", "val"}, |
2042 | "Introduce new mutable local.") |
2043 |
|
2044 | SPECIALS["let"] = function(ast, scope, parent, opts) |
2045 | local bindings = ast[2] |
2046 | compiler.assert(utils.isList(bindings) or utils.isTable(bindings), |
2047 | "expected binding table", ast) |
2048 | compiler.assert(#bindings % 2 == 0, |
2049 | "expected even number of name/value bindings", ast[2]) |
2050 | compiler.assert(#ast >= 3, "expected body expression", ast[1]) |
2051 | -- we have to gensym the binding for the let body's return value before |
2052 | -- compiling the binding vector, otherwise there's a possibility to conflict |
2053 | local preSyms = {} |
2054 | for _ = 1, (opts.nval or 0) do table.insert(preSyms, compiler.gensym(scope)) end |
2055 | local subScope = compiler.makeScope(scope) |
2056 | local subChunk = {} |
2057 | for i = 1, #bindings, 2 do |
2058 | compiler.destructure(bindings[i], bindings[i + 1], ast, subScope, subChunk, { |
2059 | declaration = true, nomulti = true }) |
2060 | end |
2061 | return doImpl(ast, scope, parent, opts, 3, subChunk, subScope, preSyms) |
2062 | end |
2063 | docSpecial("let", {"[name1 val1 ... nameN valN]", "..."}, |
2064 | "Introduces a new scope in which a given set of local bindings are used.") |
2065 |
|
2066 | -- For setting items in a table |
2067 | SPECIALS["tset"] = function(ast, scope, parent) |
2068 | compiler.assert(#ast > 3, ("expected table, key, and value arguments"), ast) |
2069 | local root = compiler.compile1(ast[2], scope, parent, {nval = 1})[1] |
2070 | local keys = {} |
2071 | for i = 3, #ast - 1 do |
2072 | local key = compiler.compile1(ast[i], scope, parent, {nval = 1})[1] |
2073 | keys[#keys + 1] = tostring(key) |
2074 | end |
2075 | local value = compiler.compile1(ast[#ast], scope, parent, {nval = 1})[1] |
2076 | local rootstr = tostring(root) |
2077 | -- Prefix 'do end ' so parens are not ambiguous (grouping or function call?) |
2078 | local fmtstr = (rootstr:match("^{")) and "do end (%s)[%s] = %s" or "%s[%s] = %s" |
2079 | compiler.emit(parent, fmtstr:format(tostring(root), |
2080 | table.concat(keys, ']['), |
2081 | tostring(value)), ast) |
2082 | end |
2083 | docSpecial("tset", {"tbl", "key1", "...", "keyN", "val"}, |
2084 | "Set the value of a table field. Can take additional keys to set" |
2085 | .. "nested values,\nbut all parents must contain an existing table.") |
2086 |
|
2087 | -- The if special form behaves like the cond form in |
2088 | -- many languages |
2089 | SPECIALS["if"] = function(ast, scope, parent, opts) |
2090 | local doScope = compiler.makeScope(scope) |
2091 | local branches = {} |
2092 | local elseBranch = nil |
2093 |
|
2094 | -- Calculate some external stuff. Optimizes for tail calls and what not |
2095 | local wrapper, innerTail, innerTarget, targetExprs |
2096 | if opts.tail or opts.target or opts.nval then |
2097 | if opts.nval and opts.nval ~= 0 and not opts.target then |
2098 | -- We need to create a target |
2099 | targetExprs = {} |
2100 | local accum = {} |
2101 | for i = 1, opts.nval do |
2102 | local s = compiler.gensym(scope) |
2103 | accum[i] = s |
2104 | targetExprs[i] = utils.expr(s, 'sym') |
2105 | end |
2106 | wrapper = 'target' |
2107 | innerTail = opts.tail |
2108 | innerTarget = table.concat(accum, ', ') |
2109 | else |
2110 | wrapper = 'none' |
2111 | innerTail = opts.tail |
2112 | innerTarget = opts.target |
2113 | end |
2114 | else |
2115 | wrapper = 'iife' |
2116 | innerTail = true |
2117 | innerTarget = nil |
2118 | end |
2119 |
|
2120 | -- Compile bodies and conditions |
2121 | local bodyOpts = { |
2122 | tail = innerTail, |
2123 | target = innerTarget, |
2124 | nval = opts.nval |
2125 | } |
2126 | local function compileBody(i) |
2127 | local chunk = {} |
2128 | local cscope = compiler.makeScope(doScope) |
2129 | compiler.keepSideEffects(compiler.compile1(ast[i], cscope, chunk, bodyOpts), |
2130 | chunk, nil, ast[i]) |
2131 | return { |
2132 | chunk = chunk, |
2133 | scope = cscope |
2134 | } |
2135 | end |
2136 | for i = 2, #ast - 1, 2 do |
2137 | local condchunk = {} |
2138 | local res = compiler.compile1(ast[i], doScope, condchunk, {nval = 1}) |
2139 | local cond = res[1] |
2140 | local branch = compileBody(i + 1) |
2141 | branch.cond = cond |
2142 | branch.condchunk = condchunk |
2143 | branch.nested = i ~= 2 and next(condchunk, nil) == nil |
2144 | table.insert(branches, branch) |
2145 | end |
2146 | local hasElse = #ast > 3 and #ast % 2 == 0 |
2147 | if hasElse then elseBranch = compileBody(#ast) end |
2148 |
|
2149 | -- Emit code |
2150 | local s = compiler.gensym(scope) |
2151 | local buffer = {} |
2152 | local lastBuffer = buffer |
2153 | for i = 1, #branches do |
2154 | local branch = branches[i] |
2155 | local fstr = not branch.nested and 'if %s then' or 'elseif %s then' |
2156 | local cond = tostring(branch.cond) |
2157 | local condLine = (cond == "true" and branch.nested and i == #branches) |
2158 | and "else" |
2159 | or fstr:format(cond) |
2160 | if branch.nested then |
2161 | compiler.emit(lastBuffer, branch.condchunk, ast) |
2162 | else |
2163 | for _, v in ipairs(branch.condchunk) do compiler.emit(lastBuffer, v, ast) end |
2164 | end |
2165 | compiler.emit(lastBuffer, condLine, ast) |
2166 | compiler.emit(lastBuffer, branch.chunk, ast) |
2167 | if i == #branches then |
2168 | if hasElse then |
2169 | compiler.emit(lastBuffer, 'else', ast) |
2170 | compiler.emit(lastBuffer, elseBranch.chunk, ast) |
2171 | -- TODO: Consolidate use of condLine ~= "else" with hasElse |
2172 | elseif(innerTarget and condLine ~= 'else') then |
2173 | compiler.emit(lastBuffer, 'else', ast) |
2174 | compiler.emit(lastBuffer, ("%s = nil"):format(innerTarget), ast) |
2175 | end |
2176 | compiler.emit(lastBuffer, 'end', ast) |
2177 | elseif not branches[i + 1].nested then |
2178 | compiler.emit(lastBuffer, 'else', ast) |
2179 | local nextBuffer = {} |
2180 | compiler.emit(lastBuffer, nextBuffer, ast) |
2181 | compiler.emit(lastBuffer, 'end', ast) |
2182 | lastBuffer = nextBuffer |
2183 | end |
2184 | end |
2185 |
|
2186 | if wrapper == 'iife' then |
2187 | local iifeargs = scope.vararg and '...' or '' |
2188 | compiler.emit(parent, ('local function %s(%s)'):format(tostring(s), iifeargs), ast) |
2189 | compiler.emit(parent, buffer, ast) |
2190 | compiler.emit(parent, 'end', ast) |
2191 | return utils.expr(('%s(%s)'):format(tostring(s), iifeargs), 'statement') |
2192 | elseif wrapper == 'none' then |
2193 | -- Splice result right into code |
2194 | for i = 1, #buffer do |
2195 | compiler.emit(parent, buffer[i], ast) |
2196 | end |
2197 | return {returned = true} |
2198 | else -- wrapper == 'target' |
2199 | compiler.emit(parent, ('local %s'):format(innerTarget), ast) |
2200 | for i = 1, #buffer do |
2201 | compiler.emit(parent, buffer[i], ast) |
2202 | end |
2203 | return targetExprs |
2204 | end |
2205 | end |
2206 | docSpecial("if", {"cond1", "body1", "...", "condN", "bodyN"}, |
2207 | "Conditional form.\n" .. |
2208 | "Takes any number of condition/body pairs and evaluates the first body where" |
2209 | .. "\nthe condition evaluates to truthy. Similar to cond in other lisps.") |
2210 |
|
2211 | -- (each [k v (pairs t)] body...) => [] |
2212 | SPECIALS["each"] = function(ast, scope, parent) |
2213 | local binding = compiler.assert(utils.isTable(ast[2]), "expected binding table", ast) |
2214 | compiler.assert(#ast >= 3, "expected body expression", ast[1]) |
2215 | local iter = table.remove(binding, #binding) -- last item is iterator call |
2216 | local destructures = {} |
2217 | local newManglings = {} |
2218 | local subScope = compiler.makeScope(scope) |
2219 | local function destructureBinding(v) |
2220 | if utils.isSym(v) then |
2221 | return compiler.declareLocal(v, {}, subScope, ast, newManglings) |
2222 | else |
2223 | local raw = utils.sym(compiler.gensym(subScope)) |
2224 | destructures[raw] = v |
2225 | return compiler.declareLocal(raw, {}, subScope, ast) |
2226 | end |
2227 | end |
2228 | local bindVars = utils.map(binding, destructureBinding) |
2229 | local vals = compiler.compile1(iter, subScope, parent) |
2230 | local valNames = utils.map(vals, tostring) |
2231 |
|
2232 | compiler.emit(parent, ('for %s in %s do'):format(table.concat(bindVars, ', '), |
2233 | table.concat(valNames, ", ")), ast) |
2234 | local chunk = {} |
2235 | for raw, args in utils.stablepairs(destructures) do |
2236 | compiler.destructure(args, raw, ast, subScope, chunk, |
2237 | { declaration = true, nomulti = true }) |
2238 | end |
2239 | compiler.applyManglings(subScope, newManglings, ast) |
2240 | compileDo(ast, subScope, chunk, 3) |
2241 | compiler.emit(parent, chunk, ast) |
2242 | compiler.emit(parent, 'end', ast) |
2243 | end |
2244 | docSpecial("each", {"[key value (iterator)]", "..."}, |
2245 | "Runs the body once for each set of values provided by the given iterator." |
2246 | .."\nMost commonly used with ipairs for sequential tables or pairs for" |
2247 | .." undefined\norder, but can be used with any iterator.") |
2248 |
|
2249 | -- (while condition body...) => [] |
2250 | SPECIALS["while"] = function(ast, scope, parent) |
2251 | local len1 = #parent |
2252 | local condition = compiler.compile1(ast[2], scope, parent, {nval = 1})[1] |
2253 | local len2 = #parent |
2254 | local subChunk = {} |
2255 | if len1 ~= len2 then |
2256 | -- Compound condition |
2257 | -- Move new compilation to subchunk |
2258 | for i = len1 + 1, len2 do |
2259 | subChunk[#subChunk + 1] = parent[i] |
2260 | parent[i] = nil |
2261 | end |
2262 | compiler.emit(parent, 'while true do', ast) |
2263 | compiler.emit(subChunk, ('if not %s then break end'):format(condition[1]), ast) |
2264 | else |
2265 | -- Simple condition |
2266 | compiler.emit(parent, 'while ' .. tostring(condition) .. ' do', ast) |
2267 | end |
2268 | compileDo(ast, compiler.makeScope(scope), subChunk, 3) |
2269 | compiler.emit(parent, subChunk, ast) |
2270 | compiler.emit(parent, 'end', ast) |
2271 | end |
2272 | docSpecial("while", {"condition", "..."}, |
2273 | "The classic while loop. Evaluates body until a condition is non-truthy.") |
2274 |
|
2275 | SPECIALS["for"] = function(ast, scope, parent) |
2276 | local ranges = compiler.assert(utils.isTable(ast[2]), "expected binding table", ast) |
2277 | local bindingSym = table.remove(ast[2], 1) |
2278 | local subScope = compiler.makeScope(scope) |
2279 | compiler.assert(utils.isSym(bindingSym), |
2280 | ("unable to bind %s %s"): |
2281 | format(type(bindingSym), tostring(bindingSym)), ast[2]) |
2282 | compiler.assert(#ast >= 3, "expected body expression", ast[1]) |
2283 | local rangeArgs = {} |
2284 | for i = 1, math.min(#ranges, 3) do |
2285 | rangeArgs[i] = tostring(compiler.compile1(ranges[i], subScope, parent, {nval = 1})[1]) |
2286 | end |
2287 | compiler.emit(parent, ('for %s = %s do'):format( |
2288 | compiler.declareLocal(bindingSym, {}, subScope, ast), |
2289 | table.concat(rangeArgs, ', ')), ast) |
2290 | local chunk = {} |
2291 | compileDo(ast, subScope, chunk, 3) |
2292 | compiler.emit(parent, chunk, ast) |
2293 | compiler.emit(parent, 'end', ast) |
2294 | end |
2295 | docSpecial("for", {"[index start stop step?]", "..."}, "Numeric loop construct." .. |
2296 | "\nEvaluates body once for each value between start and stop (inclusive).") |
2297 |
|
2298 | -- For statements and expressions, put the value in a local to avoid |
2299 | -- double-evaluating it. |
2300 | local function once(val, ast, scope, parent) |
2301 | if val.type == 'statement' or val.type == 'expression' then |
2302 | local s = compiler.gensym(scope) |
2303 | compiler.emit(parent, ('local %s = %s'):format(s, tostring(val)), ast) |
2304 | return utils.expr(s, 'sym') |
2305 | else |
2306 | return val |
2307 | end |
2308 | end |
2309 |
|
2310 | SPECIALS[":"] = function(ast, scope, parent) |
2311 | compiler.assert(#ast >= 3, "expected at least 2 arguments", ast) |
2312 | -- Compile object |
2313 | local objectexpr = compiler.compile1(ast[2], scope, parent, {nval = 1})[1] |
2314 | -- Compile method selector |
2315 | local methodstring |
2316 | local methodident = false |
2317 | if type(ast[3]) == 'string' and utils.isValidLuaIdentifier(ast[3]) then |
2318 | methodident = true |
2319 | methodstring = ast[3] |
2320 | else |
2321 | methodstring = tostring(compiler.compile1(ast[3], scope, parent, {nval = 1})[1]) |
2322 | objectexpr = once(objectexpr, ast[2], scope, parent) |
2323 | end |
2324 | -- Compile arguments |
2325 | local args = {} |
2326 | for i = 4, #ast do |
2327 | local subexprs = compiler.compile1(ast[i], scope, parent, { |
2328 | nval = i ~= #ast and 1 or nil |
2329 | }) |
2330 | utils.map(subexprs, tostring, args) |
2331 | end |
2332 | local fstring |
2333 | if not methodident then |
2334 | -- Make object first argument |
2335 | table.insert(args, 1, tostring(objectexpr)) |
2336 | fstring = objectexpr.type == 'sym' |
2337 | and '%s[%s](%s)' |
2338 | or '(%s)[%s](%s)' |
2339 | elseif(objectexpr.type == 'literal' or objectexpr.type == 'expression') then |
2340 | fstring = '(%s):%s(%s)' |
2341 | else |
2342 | fstring = '%s:%s(%s)' |
2343 | end |
2344 | return utils.expr(fstring:format( |
2345 | tostring(objectexpr), |
2346 | methodstring, |
2347 | table.concat(args, ', ')), 'statement') |
2348 | end |
2349 | docSpecial(":", {"tbl", "method-name", "..."}, |
2350 | "Call the named method on tbl with the provided args.".. |
2351 | "\nMethod name doesn\"t have to be known at compile-time; if it is, use" |
2352 | .."\n(tbl:method-name ...) instead.") |
2353 |
|
2354 | SPECIALS["comment"] = function(ast, _, parent) |
2355 | local els = {} |
2356 | for i = 2, #ast do |
2357 | els[#els + 1] = tostring(ast[i]):gsub('\n', ' ') |
2358 | end |
2359 | compiler.emit(parent, '-- ' .. table.concat(els, ' '), ast) |
2360 | end |
2361 | docSpecial("comment", {"..."}, "Comment which will be emitted in Lua output.") |
2362 |
|
2363 | SPECIALS["hashfn"] = function(ast, scope, parent) |
2364 | compiler.assert(#ast == 2, "expected one argument", ast) |
2365 | local fScope = compiler.makeScope(scope) |
2366 | local fChunk = {} |
2367 | local name = compiler.gensym(scope) |
2368 | local symbol = utils.sym(name) |
2369 | compiler.declareLocal(symbol, {}, scope, ast) |
2370 | fScope.vararg = false |
2371 | fScope.hashfn = true |
2372 | local args = {} |
2373 | for i = 1, 9 do args[i] = compiler.declareLocal(utils.sym('$' .. i), {}, fScope, ast) end |
2374 | -- recursively walk the AST, transforming $... into ... |
2375 | utils.walkTree(ast[2], function(idx, node, parentNode) |
2376 | if utils.isSym(node) and utils.deref(node) == '$...' then |
2377 | parentNode[idx] = utils.varg() |
2378 | fScope.vararg = true |
2379 | else -- truthy return value determines whether to traverse children |
2380 | return utils.isList(node) or utils.isTable(node) |
2381 | end |
2382 | end) |
2383 | -- Compile body |
2384 | compiler.compile1(ast[2], fScope, fChunk, {tail = true}) |
2385 | local maxUsed = 0 |
2386 | for i = 1, 9 do if fScope.symmeta['$' .. i].used then maxUsed = i end end |
2387 | if fScope.vararg then |
2388 | compiler.assert(maxUsed == 0, '$ and $... in hashfn are mutually exclusive', ast) |
2389 | args = {utils.deref(utils.varg())} |
2390 | maxUsed = 1 |
2391 | end |
2392 | local argStr = table.concat(args, ', ', 1, maxUsed) |
2393 | compiler.emit(parent, ('local function %s(%s)'):format(name, argStr), ast) |
2394 | compiler.emit(parent, fChunk, ast) |
2395 | compiler.emit(parent, 'end', ast) |
2396 | return utils.expr(name, 'sym') |
2397 | end |
2398 | docSpecial("hashfn", {"..."}, |
2399 | "Function literal shorthand; args are either $... OR $1, $2, etc.") |
2400 |
|
2401 | local function defineArithmeticSpecial(name, zeroArity, unaryPrefix, luaName) |
2402 | local paddedOp = ' ' .. (luaName or name) .. ' ' |
2403 | SPECIALS[name] = function(ast, scope, parent) |
2404 | local len = #ast |
2405 | if len == 1 then |
2406 | compiler.assert(zeroArity ~= nil, 'Expected more than 0 arguments', ast) |
2407 | return utils.expr(zeroArity, 'literal') |
2408 | else |
2409 | local operands = {} |
2410 | for i = 2, len do |
2411 | local subexprs = compiler.compile1(ast[i], scope, parent, { |
2412 | nval = (i == 1 and 1 or nil) |
2413 | }) |
2414 | utils.map(subexprs, tostring, operands) |
2415 | end |
2416 | if #operands == 1 then |
2417 | if unaryPrefix then |
2418 | return '(' .. unaryPrefix .. paddedOp .. operands[1] .. ')' |
2419 | else |
2420 | return operands[1] |
2421 | end |
2422 | else |
2423 | return '(' .. table.concat(operands, paddedOp) .. ')' |
2424 | end |
2425 | end |
2426 | end |
2427 | docSpecial(name, {"a", "b", "..."}, |
2428 | "Arithmetic operator; works the same as Lua but accepts more arguments.") |
2429 | end |
2430 |
|
2431 | defineArithmeticSpecial('+', '0') |
2432 | defineArithmeticSpecial('..', "''") |
2433 | defineArithmeticSpecial('^') |
2434 | defineArithmeticSpecial('-', nil, '') |
2435 | defineArithmeticSpecial('*', '1') |
2436 | defineArithmeticSpecial('%') |
2437 | defineArithmeticSpecial('/', nil, '1') |
2438 | defineArithmeticSpecial('//', nil, '1') |
2439 |
|
2440 | defineArithmeticSpecial("lshift", nil, "1", "<<") |
2441 | defineArithmeticSpecial("rshift", nil, "1", ">>") |
2442 | defineArithmeticSpecial("band", "0", "0", "&") |
2443 | defineArithmeticSpecial("bor", "0", "0", "|") |
2444 | defineArithmeticSpecial("bxor", "0", "0", "~") |
2445 |
|
2446 | docSpecial("lshift", {"x", "n"}, |
2447 | "Bitwise logical left shift of x by n bits; only works in Lua 5.3+.") |
2448 | docSpecial("rshift", {"x", "n"}, |
2449 | "Bitwise logical right shift of x by n bits; only works in Lua 5.3+.") |
2450 | docSpecial("band", {"x1", "x2"}, "Bitwise AND of arguments; only works in Lua 5.3+.") |
2451 | docSpecial("bor", {"x1", "x2"}, "Bitwise OR of arguments; only works in Lua 5.3+.") |
2452 | docSpecial("bxor", {"x1", "x2"}, "Bitwise XOR of arguments; only works in Lua 5.3+.") |
2453 |
|
2454 | defineArithmeticSpecial('or', 'false') |
2455 | defineArithmeticSpecial('and', 'true') |
2456 |
|
2457 | docSpecial("and", {"a", "b", "..."}, |
2458 | "Boolean operator; works the same as Lua but accepts more arguments.") |
2459 | docSpecial("or", {"a", "b", "..."}, |
2460 | "Boolean operator; works the same as Lua but accepts more arguments.") |
2461 | docSpecial("..", {"a", "b", "..."}, |
2462 | "String concatenation operator; works the same as Lua but accepts more arguments.") |
2463 |
|
2464 | local function defineComparatorSpecial(name, realop, chainOp) |
2465 | local op = realop or name |
2466 | SPECIALS[name] = function(ast, scope, parent) |
2467 | local len = #ast |
2468 | compiler.assert(len > 2, "expected at least two arguments", ast) |
2469 | local lhs = compiler.compile1(ast[2], scope, parent, {nval = 1})[1] |
2470 | local lastval = compiler.compile1(ast[3], scope, parent, {nval = 1})[1] |
2471 | -- avoid double-eval by introducing locals for possible side-effects |
2472 | if len > 3 then lastval = once(lastval, ast[3], scope, parent) end |
2473 | local out = ('(%s %s %s)'): |
2474 | format(tostring(lhs), op, tostring(lastval)) |
2475 | if len > 3 then |
2476 | for i = 4, len do -- variadic comparison |
2477 | local nextval = once(compiler.compile1(ast[i], scope, parent, {nval = 1})[1], |
2478 | ast[i], scope, parent) |
2479 | out = (out .. " %s (%s %s %s)"): |
2480 | format(chainOp or 'and', tostring(lastval), op, tostring(nextval)) |
2481 | lastval = nextval |
2482 | end |
2483 | out = '(' .. out .. ')' |
2484 | end |
2485 | return out |
2486 | end |
2487 | docSpecial(name, {"a", "b", "..."}, |
2488 | "Comparison operator; works the same as Lua but accepts more arguments.") |
2489 | end |
2490 |
|
2491 | defineComparatorSpecial('>') |
2492 | defineComparatorSpecial('<') |
2493 | defineComparatorSpecial('>=') |
2494 | defineComparatorSpecial('<=') |
2495 | defineComparatorSpecial('=', '==') |
2496 | defineComparatorSpecial('not=', '~=', 'or') |
2497 | SPECIALS["~="] = SPECIALS["not="] -- backwards-compatibility alias |
2498 |
|
2499 | local function defineUnarySpecial(op, realop) |
2500 | SPECIALS[op] = function(ast, scope, parent) |
2501 | compiler.assert(#ast == 2, 'expected one argument', ast) |
2502 | local tail = compiler.compile1(ast[2], scope, parent, {nval = 1}) |
2503 | return (realop or op) .. tostring(tail[1]) |
2504 | end |
2505 | end |
2506 |
|
2507 | defineUnarySpecial("not", "not ") |
2508 | docSpecial("not", {"x"}, "Logical operator; works the same as Lua.") |
2509 |
|
2510 | defineUnarySpecial("bnot", "~") |
2511 | docSpecial("bnot", {"x"}, "Bitwise negation; only works in Lua 5.3+.") |
2512 |
|
2513 | defineUnarySpecial("length", "#") |
2514 | docSpecial("length", {"x"}, "Returns the length of a table or string.") |
2515 | SPECIALS["#"] = SPECIALS["length"] |
2516 |
|
2517 | SPECIALS['quote'] = function(ast, scope, parent) |
2518 | compiler.assert(#ast == 2, "expected one argument") |
2519 | local runtime, thisScope = true, scope |
2520 | while thisScope do |
2521 | thisScope = thisScope.parent |
2522 | if thisScope == compiler.scopes.compiler then runtime = false end |
2523 | end |
2524 | return compiler.doQuote(ast[2], scope, parent, runtime) |
2525 | end |
2526 | docSpecial('quote', {'x'}, 'Quasiquote the following form. Only works in macro/compiler scope.') |
2527 |
|
2528 | local function makeCompilerEnv(ast, scope, parent) |
2529 | return setmetatable({ |
2530 | -- State of compiler if needed |
2531 | _SCOPE = scope, |
2532 | _CHUNK = parent, |
2533 | _AST = ast, |
2534 | _IS_COMPILER = true, |
2535 | _SPECIALS = compiler.scopes.global.specials, |
2536 | _VARARG = utils.varg(), |
2537 | -- Expose the module in the compiler |
2538 | fennel = utils.fennelModule, |
2539 | unpack = unpack, |
2540 |
|
2541 | -- Useful for macros and meta programming. All of Fennel can be accessed |
2542 | -- via fennel.myfun, for example (fennel.eval "(print 1)"). |
2543 | list = utils.list, |
2544 | sym = utils.sym, |
2545 | sequence = utils.sequence, |
2546 | gensym = function() |
2547 | return utils.sym(compiler.gensym(compiler.scopes.macro or scope)) |
2548 | end, |
2549 | ["list?"] = utils.isList, |
2550 | ["multi-sym?"] = utils.isMultiSym, |
2551 | ["sym?"] = utils.isSym, |
2552 | ["table?"] = utils.isTable, |
2553 | ["sequence?"] = utils.isSequence, |
2554 | ["varg?"] = utils.isVarg, |
2555 | ["get-scope"] = function() return compiler.scopes.macro end, |
2556 | ["in-scope?"] = function(symbol) |
2557 | compiler.assert(compiler.scopes.macro, "must call from macro", ast) |
2558 | return compiler.scopes.macro.manglings[tostring(symbol)] |
2559 | end, |
2560 | ["macroexpand"] = function(form) |
2561 | compiler.assert(compiler.scopes.macro, "must call from macro", ast) |
2562 | return compiler.macroexpand(form, compiler.scopes.macro) |
2563 | end, |
2564 | }, { __index = _ENV or _G }) |
2565 | end |
2566 |
|
2567 | -- have searchModule use package.config to process package.path (windows compat) |
2568 | local cfg = string.gmatch(package.config, "([^\n]+)") |
2569 | local dirsep, pathsep, pathmark = cfg() or '/', cfg() or ';', cfg() or '?' |
2570 | local pkgConfig = {dirsep = dirsep, pathsep = pathsep, pathmark = pathmark} |
2571 |
|
2572 | -- Escape a string for safe use in a Lua pattern |
2573 | local function escapepat(str) |
2574 | return string.gsub(str, "[^%w]", "%%%1") |
2575 | end |
2576 |
|
2577 | local function searchModule(modulename, pathstring) |
2578 | local pathsepesc = escapepat(pkgConfig.pathsep) |
2579 | local pathsplit = string.format("([^%s]*)%s", pathsepesc, pathsepesc) |
2580 | local nodotModule = modulename:gsub("%.", pkgConfig.dirsep) |
2581 | for path in string.gmatch((pathstring or utils.fennelModule.path) .. |
2582 | pkgConfig.pathsep, pathsplit) do |
2583 | local filename = path:gsub(escapepat(pkgConfig.pathmark), nodotModule) |
2584 | local filename2 = path:gsub(escapepat(pkgConfig.pathmark), modulename) |
2585 | local file = io.open(filename) or io.open(filename2) |
2586 | if(file) then |
2587 | file:close() |
2588 | return filename |
2589 | end |
2590 | end |
2591 | end |
2592 |
|
2593 | local function macroGlobals(env, globals) |
2594 | local allowed = currentGlobalNames(env) |
2595 | for _, k in pairs(globals or {}) do table.insert(allowed, k) end |
2596 | return allowed |
2597 | end |
2598 |
|
2599 | local function addMacros(macros, ast, scope) |
2600 | compiler.assert(utils.isTable(macros), 'expected macros to be table', ast) |
2601 | for k,v in pairs(macros) do |
2602 | compiler.assert(type(v) == 'function', 'expected each macro to be function', ast) |
2603 | scope.macros[k] = v |
2604 | end |
2605 | end |
2606 |
|
2607 | local function loadMacros(modname, ast, scope, parent) |
2608 | local filename = compiler.assert(searchModule(modname), |
2609 | modname .. " module not found.", ast) |
2610 | local env = makeCompilerEnv(ast, scope, parent) |
2611 | local globals = macroGlobals(env, currentGlobalNames()) |
2612 | return compiler.dofileFennel(filename, |
2613 | { env = env, allowedGlobals = globals, |
2614 | useMetadata = utils.root.options.useMetadata, |
2615 | scope = compiler.scopes.compiler }) |
2616 | end |
2617 |
|
2618 | local macroLoaded = {} |
2619 |
|
2620 | SPECIALS['require-macros'] = function(ast, scope, parent) |
2621 | compiler.assert(#ast == 2, "Expected one module name argument", ast) |
2622 | local modname = ast[2] |
2623 | if not macroLoaded[modname] then |
2624 | macroLoaded[modname] = loadMacros(modname, ast, scope, parent) |
2625 | end |
2626 | addMacros(macroLoaded[modname], ast, scope, parent) |
2627 | end |
2628 | docSpecial('require-macros', {'macro-module-name'}, |
2629 | 'Load given module and use its contents as macro definitions in current scope.' |
2630 | ..'\nMacro module should return a table of macro functions with string keys.' |
2631 | ..'\nConsider using import-macros instead as it is more flexible.') |
2632 |
|
2633 | SPECIALS['include'] = function(ast, scope, parent, opts) |
2634 | compiler.assert(#ast == 2, 'expected one argument', ast) |
2635 |
|
2636 | -- Compile mod argument |
2637 | local modexpr = compiler.compile1(ast[2], scope, parent, {nval = 1})[1] |
2638 | if modexpr.type ~= 'literal' or modexpr[1]:byte() ~= 34 then |
2639 | if opts.fallback then |
2640 | return opts.fallback(modexpr) |
2641 | else |
2642 | compiler.assert(false, 'module name must resolve to a string literal', ast) |
2643 | end |
2644 | end |
2645 | local code = 'return ' .. modexpr[1] |
2646 | local mod = loadCode(code)() |
2647 |
|
2648 | -- Check cache |
2649 | if utils.root.scope.includes[mod] then return utils.root.scope.includes[mod] end |
2650 |
|
2651 | -- Find path to source |
2652 | local path = searchModule(mod) |
2653 | local isFennel = true |
2654 | if not path then |
2655 | isFennel = false |
2656 | path = searchModule(mod, package.path) |
2657 | if not path then |
2658 | if opts.fallback then |
2659 | return opts.fallback(modexpr) |
2660 | else |
2661 | compiler.assert(false, 'module not found ' .. mod, ast) |
2662 | end |
2663 | end |
2664 | end |
2665 |
|
2666 | -- Read source |
2667 | local f = io.open(path) |
2668 | local s = f:read('*all'):gsub('[\r\n]*$', '') |
2669 | f:close() |
2670 |
|
2671 | -- splice in source and memoize it in compiler AND package.preload |
2672 | -- so we can include it again without duplication, even in runtime |
2673 | local ret = utils.expr('require("' .. mod .. '")', 'statement') |
2674 | local target = ('package.preload[%q]'):format(mod) |
2675 | local preloadStr = target .. ' = ' .. target .. ' or function(...)' |
2676 |
|
2677 | local tempChunk, subChunk = {}, {} |
2678 | compiler.emit(tempChunk, preloadStr, ast) |
2679 | compiler.emit(tempChunk, subChunk) |
2680 | compiler.emit(tempChunk, 'end', ast) |
2681 | -- Splice tempChunk to begining of root chunk |
2682 | for i, v in ipairs(tempChunk) do table.insert(utils.root.chunk, i, v) end |
2683 |
|
2684 | -- For fnl source, compile subChunk AFTER splicing into start of root chunk. |
2685 | if isFennel then |
2686 | local subscope = compiler.makeScope(utils.root.scope.parent) |
2687 | if utils.root.options.requireAsInclude then |
2688 | subscope.specials.require = compiler.requireInclude |
2689 | end |
2690 | -- parse Fennel src into table of exprs to know which expr is the tail |
2691 | local forms, p = {}, parser.parser(parser.stringStream(s), path) |
2692 | for _, val in p do table.insert(forms, val) end |
2693 | -- Compile the forms into subChunk; compiler.compile1 is necessary for all nested |
2694 | -- includes to be emitted in the same root chunk in the top-level module |
2695 | for i = 1, #forms do |
2696 | -- NOTE: nval=0 here at the end is the only change since 0.4.2 |
2697 | local subopts = i == #forms and {nval=1, tail=true} or {nval=0} |
2698 | utils.propagateOptions(opts, subopts) |
2699 | compiler.compile1(forms[i], subscope, subChunk, subopts) |
2700 | end |
2701 | else -- for Lua source, simply emit the src into the loader's body |
2702 | compiler.emit(subChunk, s, ast) |
2703 | end |
2704 |
|
2705 | -- Put in cache and return |
2706 | utils.root.scope.includes[mod] = ret |
2707 | return ret |
2708 | end |
2709 | docSpecial('include', {'module-name-literal'}, |
2710 | 'Like require, but load the target module during compilation and embed it in the\n' |
2711 | .. 'Lua output. The module must be a string literal and resolvable at compile time.') |
2712 |
|
2713 | local function evalCompiler(ast, scope, parent) |
2714 | local luaSource = |
2715 | compiler.compile(ast, { scope = compiler.makeScope(compiler.scopes.compiler), |
2716 | useMetadata = utils.root.options.useMetadata }) |
2717 | local loader = loadCode(luaSource, wrapEnv(makeCompilerEnv(ast, scope, parent))) |
2718 | return loader() |
2719 | end |
2720 |
|
2721 | SPECIALS['macros'] = function(ast, scope, parent) |
2722 | compiler.assert(#ast == 2, "Expected one table argument", ast) |
2723 | local macros = evalCompiler(ast[2], scope, parent) |
2724 | addMacros(macros, ast, scope, parent) |
2725 | end |
2726 | docSpecial('macros', {'{:macro-name-1 (fn [...] ...) ... :macro-name-N macro-body-N}'}, |
2727 | 'Define all functions in the given table as macros local to the current scope.') |
2728 |
|
2729 | SPECIALS['eval-compiler'] = function(ast, scope, parent) |
2730 | local oldFirst = ast[1] |
2731 | ast[1] = utils.sym('do') |
2732 | local val = evalCompiler(ast, scope, parent) |
2733 | ast[1] = oldFirst |
2734 | return val |
2735 | end |
2736 | docSpecial('eval-compiler', {'...'}, 'Evaluate the body at compile-time.' |
2737 | .. ' Use the macro system instead if possible.') |
2738 |
|
2739 | -- A few things that aren't specials, but are needed to define specials, but |
2740 | -- are also needed for the following code. |
2741 | return { wrapEnv=wrapEnv, |
2742 | currentGlobalNames=currentGlobalNames, |
2743 | loadCode=loadCode, |
2744 | doc=doc, |
2745 | macroLoaded=macroLoaded, |
2746 | searchModule=searchModule, |
2747 | makeCompilerEnv=makeCompilerEnv, } |
2748 | end)() |
2749 |
|
2750 | --- |
2751 | --- Evaluation, repl, public API, and macros |
2752 | --- |
2753 |
|
2754 | local function eval(str, options, ...) |
2755 | local opts = utils.copy(options) |
2756 | -- eval and dofile are considered "live" entry points, so we can assume |
2757 | -- that the globals available at compile time are a reasonable allowed list |
2758 | -- UNLESS there's a metatable on env, in which case we can't assume that |
2759 | -- pairs will return all the effective globals; for instance openresty |
2760 | -- sets up _G in such a way that all the globals are available thru |
2761 | -- the __index meta method, but as far as pairs is concerned it's empty. |
2762 | if opts.allowedGlobals == nil and not getmetatable(opts.env) then |
2763 | opts.allowedGlobals = specials.currentGlobalNames(opts.env) |
2764 | end |
2765 | local env = opts.env and specials.wrapEnv(opts.env) |
2766 | local luaSource = compiler.compileString(str, opts) |
2767 | local loader = specials.loadCode(luaSource, env, opts.filename and |
2768 | ('@' .. opts.filename) or str) |
2769 | opts.filename = nil |
2770 | return loader(...) |
2771 | end |
2772 |
|
2773 | -- This is bad; we have a circular dependency between the specials section and |
2774 | -- the evaluation section due to require-macros/import-macros needing to be able |
2775 | -- to do this. For now stash it in the compiler table, but we should untangle it |
2776 | compiler.dofileFennel = function(filename, options, ...) |
2777 | local opts = utils.copy(options) |
2778 | local f = assert(io.open(filename, "rb")) |
2779 | local source = f:read("*all") |
2780 | f:close() |
2781 | opts.filename = filename |
2782 | return eval(source, opts, ...) |
2783 | end |
2784 |
|
2785 | -- Everything exported by the module |
2786 | local module = { |
2787 | parser = parser.parser, |
2788 | granulate = parser.granulate, |
2789 | stringStream = parser.stringStream, |
2790 |
|
2791 | compile = compiler.compile, |
2792 | compileString = compiler.compileString, |
2793 | compileStream = compiler.compileStream, |
2794 | compile1 = compiler.compile1, |
2795 | traceback = compiler.traceback, |
2796 | mangle = compiler.globalMangling, |
2797 | unmangle = compiler.globalUnmangling, |
2798 | metadata = compiler.metadata, |
2799 | scope = compiler.makeScope, |
2800 | gensym = compiler.gensym, |
2801 |
|
2802 | list = utils.list, |
2803 | sym = utils.sym, |
2804 | varg = utils.varg, |
2805 | path = utils.path, |
2806 |
|
2807 | loadCode = specials.loadCode, |
2808 | macroLoaded = specials.macroLoaded, |
2809 | searchModule = specials.searchModule, |
2810 | doc = specials.doc, |
2811 |
|
2812 | eval = eval, |
2813 | dofile = compiler.dofileFennel, |
2814 | version = "0.4.3-dev", |
2815 | } |
2816 |
|
2817 | utils.fennelModule = module -- yet another circular dependency =( |
2818 |
|
2819 | -- In order to make this more readable, you can switch your editor to treating |
2820 | -- this file as if it were Fennel for the purposes of this section |
2821 | local replsource = [===[(local (fennel internals) ...) |
2822 |
|
2823 | (fn default-read-chunk [parser-state] |
2824 | (io.write (if (< 0 parser-state.stackSize) ".." ">> ")) |
2825 | (io.flush) |
2826 | (let [input (io.read)] |
2827 | (and input (.. input "\n")))) |
2828 |
|
2829 | (fn default-on-values [xs] |
2830 | (io.write (table.concat xs "\t")) |
2831 | (io.write "\n")) |
2832 |
|
2833 | (fn default-on-error [errtype err lua-source] |
2834 | (io.write |
2835 | (match errtype |
2836 | "Lua Compile" (.. "Bad code generated - likely a bug with the compiler:\n" |
2837 | "--- Generated Lua Start ---\n" |
2838 | lua-source |
2839 | "--- Generated Lua End ---\n") |
2840 | "Runtime" (.. (fennel.traceback err 4) "\n") |
2841 | _ (: "%s error: %s\n" :format errtype (tostring err))))) |
2842 |
|
2843 | (local save-source |
2844 | (table.concat ["local ___i___ = 1" |
2845 | "while true do" |
2846 | " local name, value = debug.getlocal(1, ___i___)" |
2847 | " if(name and name ~= \"___i___\") then" |
2848 | " ___replLocals___[name] = value" |
2849 | " ___i___ = ___i___ + 1" |
2850 | " else break end end"] "\n")) |
2851 |
|
2852 | (fn splice-save-locals [env lua-source] |
2853 | (set env.___replLocals___ (or env.___replLocals___ {})) |
2854 | (let [spliced-source [] |
2855 | bind "local %s = ___replLocals___['%s']"] |
2856 | (each [line (lua-source:gmatch "([^\n]+)\n?")] |
2857 | (table.insert spliced-source line)) |
2858 | (each [name (pairs env.___replLocals___)] |
2859 | (table.insert spliced-source 1 (bind:format name name))) |
2860 | (when (and (< 1 (# spliced-source)) |
2861 | (: (. spliced-source (# spliced-source)) :match "^ *return .*$")) |
2862 | (table.insert spliced-source (# spliced-source) save-source)) |
2863 | (table.concat spliced-source "\n"))) |
2864 |
|
2865 | (fn completer [env scope text] |
2866 | (let [matches [] |
2867 | input-fragment (text:gsub ".*[%s)(]+" "")] |
2868 | (fn add-partials [input tbl prefix] ; add partial key matches in tbl |
2869 | (each [k (internals.allpairs tbl)] |
2870 | (let [k (if (or (= tbl env) (= tbl env.___replLocals___)) |
2871 | (. scope.unmanglings k) |
2872 | k)] |
2873 | (when (and (< (# matches) 2000) ; stop explosion on too many items |
2874 | (= (type k) "string") |
2875 | (= input (k:sub 0 (# input)))) |
2876 | (table.insert matches (.. prefix k)))))) |
2877 | (fn add-matches [input tbl prefix] ; add matches, descending into tbl fields |
2878 | (let [prefix (if prefix (.. prefix ".") "")] |
2879 | (if (not (input:find "%.")) ; no more dots, so add matches |
2880 | (add-partials input tbl prefix) |
2881 | (let [(head tail) (input:match "^([^.]+)%.(.*)") |
2882 | raw-head (if (or (= tbl env) (= tbl env.___replLocals___)) |
2883 | (. scope.manglings head) |
2884 | head)] |
2885 | (when (= (type (. tbl raw-head)) "table") |
2886 | (add-matches tail (. tbl raw-head) (.. prefix head))))))) |
2887 |
|
2888 | (add-matches input-fragment (or scope.specials [])) |
2889 | (add-matches input-fragment (or scope.macros [])) |
2890 | (add-matches input-fragment (or env.___replLocals___ [])) |
2891 | (add-matches input-fragment env) |
2892 | (add-matches input-fragment (or env._ENV env._G [])) |
2893 | matches)) |
2894 |
|
2895 | (fn repl [options] |
2896 | (let [old-root-options internals.rootOptions |
2897 | env (if options.env |
2898 | (internals.wrapEnv options.env) |
2899 | (setmetatable {} {:__index (or _G._ENV _G)})) |
2900 | save-locals? (and (not= options.saveLocals false) |
2901 | env.debug env.debug.getlocal) |
2902 | opts {} |
2903 | _ (each [k v (pairs options)] (tset opts k v)) |
2904 | read-chunk (or opts.readChunk default-read-chunk) |
2905 | on-values (or opts.onValues default-on-values) |
2906 | on-error (or opts.onError default-on-error) |
2907 | pp (or opts.pp tostring) |
2908 | ;; make parser |
2909 | (byte-stream clear-stream) (fennel.granulate read-chunk) |
2910 | chars [] |
2911 | (read reset) (fennel.parser (fn [parser-state] |
2912 | (let [c (byte-stream parser-state)] |
2913 | (tset chars (+ (# chars) 1) c) |
2914 | c))) |
2915 | scope (fennel.scope)] |
2916 |
|
2917 | ;; use metadata unless we've specifically disabled it |
2918 | (set opts.useMetadata (not= options.useMetadata false)) |
2919 | (when (= opts.allowedGlobals nil) |
2920 | (set opts.allowedGlobals (internals.currentGlobalNames opts.env))) |
2921 |
|
2922 | (when opts.registerCompleter |
2923 | (opts.registerCompleter (partial completer env scope))) |
2924 |
|
2925 | (fn loop [] |
2926 | (each [k (pairs chars)] (tset chars k nil)) |
2927 | (let [(ok parse-ok? x) (pcall read) |
2928 | src-string (string.char ((or _G.unpack table.unpack) chars))] |
2929 | (internals.setRootOptions opts) |
2930 | (if (not ok) |
2931 | (do (on-error "Parse" parse-ok?) |
2932 | (clear-stream) |
2933 | (reset) |
2934 | (loop)) |
2935 | (when parse-ok? ; if this is false, we got eof |
2936 | (match (pcall fennel.compile x {:correlate opts.correlate |
2937 | :source src-string |
2938 | :scope scope |
2939 | :useMetadata opts.useMetadata |
2940 | :moduleName opts.moduleName |
2941 | :assert-compile opts.assert-compile |
2942 | :parse-error opts.parse-error}) |
2943 | (false msg) (do (clear-stream) |
2944 | (on-error "Compile" msg)) |
2945 | (true source) (let [source (if save-locals? |
2946 | (splice-save-locals env source) |
2947 | source) |
2948 | (lua-ok? loader) (pcall fennel.loadCode |
2949 | source env)] |
2950 | (if (not lua-ok?) |
2951 | (do (clear-stream) |
2952 | (on-error "Lua Compile" loader source)) |
2953 | (match (xpcall #[(loader)] |
2954 | (partial on-error "Runtime")) |
2955 | (true ret) |
2956 | (do (set env._ (. ret 1)) |
2957 | (set env.__ ret) |
2958 | (on-values (internals.map ret pp))))))) |
2959 | (internals.setRootOptions old-root-options) |
2960 | (loop))))) |
2961 | (loop)))]===] |
2962 |
|
2963 | module.repl = function(options) |
2964 | -- functionality the repl needs that isn't part of the public API yet |
2965 | local internals = { rootOptions = utils.root.options, |
2966 | setRootOptions = function(r) utils.root.options = r end, |
2967 | currentGlobalNames = specials.currentGlobalNames, |
2968 | wrapEnv = specials.wrapEnv, |
2969 | allpairs = utils.allpairs, |
2970 | map = utils.map } |
2971 | return eval(replsource, { correlate = true }, module, internals)(options) |
2972 | end |
2973 |
|
2974 | module.makeSearcher = function(options) |
2975 | return function(modulename) |
2976 | -- this will propagate options from the repl but not from eval, because |
2977 | -- eval unsets utils.root.options after compiling but before running the actual |
2978 | -- calls to require. |
2979 | local opts = utils.copy(utils.root.options) |
2980 | for k,v in pairs(options or {}) do opts[k] = v end |
2981 | local filename = specials.searchModule(modulename) |
2982 | if filename then |
2983 | return function(modname) |
2984 | return compiler.dofileFennel(filename, opts, modname) |
2985 | end |
2986 | end |
2987 | end |
2988 | end |
2989 |
|
2990 | -- This will allow regular `require` to work with Fennel: |
2991 | -- table.insert(package.loaders, fennel.searcher) |
2992 | module.searcher = module.makeSearcher() |
2993 | module.make_searcher = module.makeSearcher -- oops backwards compatibility |
2994 |
|
2995 | -- Load standard macros |
2996 | local stdmacros = [===[ |
2997 | {"->" (fn [val ...] |
2998 | "Thread-first macro. |
2999 | Take the first value and splice it into the second form as its first argument. |
3000 | The value of the second form is spliced into the first arg of the third, etc." |
3001 | (var x val) |
3002 | (each [_ e (ipairs [...])] |
3003 | (let [elt (if (list? e) e (list e))] |
3004 | (table.insert elt 2 x) |
3005 | (set x elt))) |
3006 | x) |
3007 | "->>" (fn [val ...] |
3008 | "Thread-last macro. |
3009 | Same as ->, except splices the value into the last position of each form |
3010 | rather than the first." |
3011 | (var x val) |
3012 | (each [_ e (pairs [...])] |
3013 | (let [elt (if (list? e) e (list e))] |
3014 | (table.insert elt x) |
3015 | (set x elt))) |
3016 | x) |
3017 | "-?>" (fn [val ...] |
3018 | "Nil-safe thread-first macro. |
3019 | Same as -> except will short-circuit with nil when it encounters a nil value." |
3020 | (if (= 0 (select "#" ...)) |
3021 | val |
3022 | (let [els [...] |
3023 | e (table.remove els 1) |
3024 | el (if (list? e) e (list e)) |
3025 | tmp (gensym)] |
3026 | (table.insert el 2 tmp) |
3027 | `(let [,tmp ,val] |
3028 | (if ,tmp |
3029 | (-?> ,el ,(unpack els)) |
3030 | ,tmp))))) |
3031 | "-?>>" (fn [val ...] |
3032 | "Nil-safe thread-last macro. |
3033 | Same as ->> except will short-circuit with nil when it encounters a nil value." |
3034 | (if (= 0 (select "#" ...)) |
3035 | val |
3036 | (let [els [...] |
3037 | e (table.remove els 1) |
3038 | el (if (list? e) e (list e)) |
3039 | tmp (gensym)] |
3040 | (table.insert el tmp) |
3041 | `(let [,tmp ,val] |
3042 | (if ,tmp |
3043 | (-?>> ,el ,(unpack els)) |
3044 | ,tmp))))) |
3045 | :doto (fn [val ...] |
3046 | "Evaluates val and splices it into the first argument of subsequent forms." |
3047 | (let [name (gensym) |
3048 | form `(let [,name ,val])] |
3049 | (each [_ elt (pairs [...])] |
3050 | (table.insert elt 2 name) |
3051 | (table.insert form elt)) |
3052 | (table.insert form name) |
3053 | form)) |
3054 | :when (fn [condition body1 ...] |
3055 | "Evaluate body for side-effects only when condition is truthy." |
3056 | (assert body1 "expected body") |
3057 | `(if ,condition |
3058 | (do ,body1 ,...))) |
3059 | :with-open (fn [closable-bindings ...] |
3060 | "Like `let`, but invokes (v:close) on every binding after evaluating the body. |
3061 | The body is evaluated inside `xpcall` so that bound values will be closed upon |
3062 | encountering an error before propagating it." |
3063 | (let [bodyfn `(fn [] ,...) |
3064 | closer `(fn close-handlers# [ok# ...] (if ok# ... (error ... 0))) |
3065 | traceback `(. (or package.loaded.fennel debug) :traceback)] |
3066 | (for [i 1 (# closable-bindings) 2] |
3067 | (assert (sym? (. closable-bindings i)) |
3068 | "with-open only allows symbols in bindings") |
3069 | (table.insert closer 4 `(: ,(. closable-bindings i) :close))) |
3070 | `(let ,closable-bindings ,closer |
3071 | (close-handlers# (xpcall ,bodyfn ,traceback))))) |
3072 | :partial (fn [f ...] |
3073 | "Returns a function with all arguments partially applied to f." |
3074 | (let [body (list f ...)] |
3075 | (table.insert body _VARARG) |
3076 | `(fn [,_VARARG] ,body))) |
3077 | :pick-args (fn [n f] |
3078 | "Creates a function of arity n that applies its arguments to f. |
3079 | For example,\n\t(pick-args 2 func) |
3080 | expands to\n\t(fn [_0_ _1_] (func _0_ _1_))" |
3081 | (assert (and (= (type n) :number) (= n (math.floor n)) (>= n 0)) |
3082 | "Expected n to be an integer literal >= 0.") |
3083 | (let [bindings []] |
3084 | (for [i 1 n] (tset bindings i (gensym))) |
3085 | `(fn ,bindings (,f ,(unpack bindings))))) |
3086 | :pick-values (fn [n ...] |
3087 | "Like the `values` special, but emits exactly n values.\nFor example, |
3088 | \t(pick-values 2 ...)\nexpands to\n\t(let [(_0_ _1_) ...] (values _0_ _1_))" |
3089 | (assert (and (= :number (type n)) (>= n 0) (= n (math.floor n))) |
3090 | "Expected n to be an integer >= 0") |
3091 | (let [let-syms (list) |
3092 | let-values (if (= 1 (select :# ...)) ... `(values ,...))] |
3093 | (for [i 1 n] (table.insert let-syms (gensym))) |
3094 | (if (= n 0) `(values) |
3095 | `(let [,let-syms ,let-values] (values ,(unpack let-syms)))))) |
3096 | :lambda (fn [...] |
3097 | "Function literal with arity checking. |
3098 | Will throw an exception if a declared argument is passed in as nil, unless |
3099 | that argument name begins with ?." |
3100 | (let [args [...] |
3101 | has-internal-name? (sym? (. args 1)) |
3102 | arglist (if has-internal-name? (. args 2) (. args 1)) |
3103 | docstring-position (if has-internal-name? 3 2) |
3104 | has-docstring? (and (> (# args) docstring-position) |
3105 | (= :string (type (. args docstring-position)))) |
3106 | arity-check-position (- 4 (if has-internal-name? 0 1) (if has-docstring? 0 1))] |
3107 | (fn check! [a] |
3108 | (if (table? a) |
3109 | (each [_ a (pairs a)] |
3110 | (check! a)) |
3111 | (and (not (: (tostring a) :match "^?")) |
3112 | (not= (tostring a) "&") |
3113 | (not= (tostring a) "...")) |
3114 | (table.insert args arity-check-position |
3115 | `(assert (not= nil ,a) |
3116 | (: "Missing argument %s on %s:%s" |
3117 | :format ,(tostring a) |
3118 | ,(or a.filename "unknown") |
3119 | ,(or a.line "?")))))) |
3120 | (assert (> (length args) 1) "expected body expression") |
3121 | (each [_ a (ipairs arglist)] |
3122 | (check! a)) |
3123 | `(fn ,(unpack args)))) |
3124 | :macro (fn macro [name ...] |
3125 | "Define a single macro." |
3126 | (assert (sym? name) "expected symbol for macro name") |
3127 | (local args [...]) |
3128 | `(macros { ,(tostring name) (fn ,name ,(unpack args))})) |
3129 | :macrodebug (fn macrodebug [form return?] |
3130 | "Print the resulting form after performing macroexpansion. |
3131 | With a second argument, returns expanded form as a string instead of printing." |
3132 | (let [(ok view) (pcall require :fennelview) |
3133 | handle (if return? `do `print)] |
3134 | `(,handle ,((if ok view tostring) (macroexpand form _SCOPE))))) |
3135 | :import-macros (fn import-macros [binding1 module-name1 ...] |
3136 | "Binds a table of macros from each macro module according to its binding form. |
3137 | Each binding form can be either a symbol or a k/v destructuring table. |
3138 | Example:\n (import-macros mymacros :my-macros ; bind to symbol |
3139 | {:macro1 alias : macro2} :proj.macros) ; import by name" |
3140 | (assert (and binding1 module-name1 (= 0 (% (select :# ...) 2))) |
3141 | "expected even number of binding/modulename pairs") |
3142 | (for [i 1 (select :# binding1 module-name1 ...) 2] |
3143 | (local (binding modname) (select i binding1 module-name1 ...)) |
3144 | ;; generate a subscope of current scope, use require-macros to bring in macro |
3145 | ;; module. after that, we just copy the macros from subscope to scope. |
3146 | (local scope (get-scope)) |
3147 | (local subscope (fennel.scope scope)) |
3148 | (fennel.compileString (string.format "(require-macros %q)" modname) |
3149 | {:scope subscope}) |
3150 | (if (sym? binding) |
3151 | ;; bind whole table of macros to table bound to symbol |
3152 | (do (tset scope.macros (. binding 1) {}) |
3153 | (each [k v (pairs subscope.macros)] |
3154 | (tset (. scope.macros (. binding 1)) k v))) |
3155 |
|
3156 | ;; 1-level table destructuring for importing individual macros |
3157 | (table? binding) |
3158 | (each [macro-name [import-key] (pairs binding)] |
3159 | (assert (= :function (type (. subscope.macros macro-name))) |
3160 | (.. "macro " macro-name " not found in module " modname)) |
3161 | (tset scope.macros import-key (. subscope.macros macro-name))))) |
3162 | ;; TODO: replace with `nil` once we fix macros being able to return nil |
3163 | `(do nil)) |
3164 | :match |
3165 | (fn match [val ...] |
3166 | "Perform pattern matching on val. See reference for details." |
3167 | ;; this function takes the AST of values and a single pattern and returns a |
3168 | ;; condition to determine if it matches as well as a list of bindings to |
3169 | ;; introduce for the duration of the body if it does match. |
3170 | (fn match-pattern [vals pattern unifications] |
3171 | ;; we have to assume we're matching against multiple values here until we |
3172 | ;; know we're either in a multi-valued clause (in which case we know the # |
3173 | ;; of vals) or we're not, in which case we only care about the first one. |
3174 | (let [[val] vals] |
3175 | (if (or (and (sym? pattern) ; unification with outer locals (or nil) |
3176 | (not= :_ (tostring pattern)) ; never unify _ |
3177 | (or (in-scope? pattern) |
3178 | (= :nil (tostring pattern)))) |
3179 | (and (multi-sym? pattern) |
3180 | (in-scope? (. (multi-sym? pattern) 1)))) |
3181 | (values `(= ,val ,pattern) []) |
3182 | ;; unify a local we've seen already |
3183 | (and (sym? pattern) |
3184 | (. unifications (tostring pattern))) |
3185 | (values `(= ,(. unifications (tostring pattern)) ,val) []) |
3186 | ;; bind a fresh local |
3187 | (sym? pattern) |
3188 | (let [wildcard? (= (tostring pattern) "_")] |
3189 | (if (not wildcard?) (tset unifications (tostring pattern) val)) |
3190 | (values (if (or wildcard? (: (tostring pattern) :find "^?")) |
3191 | true `(not= ,(sym :nil) ,val)) |
3192 | [pattern val])) |
3193 | ;; guard clause |
3194 | (and (list? pattern) (sym? (. pattern 2)) (= :? (tostring (. pattern 2)))) |
3195 | (let [(pcondition bindings) (match-pattern vals (. pattern 1) |
3196 | unifications) |
3197 | condition `(and ,pcondition)] |
3198 | (for [i 3 (# pattern)] ; splice in guard clauses |
3199 | (table.insert condition (. pattern i))) |
3200 | (values `(let ,bindings ,condition) bindings)) |
3201 |
|
3202 | ;; multi-valued patterns (represented as lists) |
3203 | (list? pattern) |
3204 | (let [condition `(and) |
3205 | bindings []] |
3206 | (each [i pat (ipairs pattern)] |
3207 | (let [(subcondition subbindings) (match-pattern [(. vals i)] pat |
3208 | unifications)] |
3209 | (table.insert condition subcondition) |
3210 | (each [_ b (ipairs subbindings)] |
3211 | (table.insert bindings b)))) |
3212 | (values condition bindings)) |
3213 | ;; table patterns) |
3214 | (= (type pattern) :table) |
3215 | (let [condition `(and (= (type ,val) :table)) |
3216 | bindings []] |
3217 | (each [k pat (pairs pattern)] |
3218 | (if (and (sym? pat) (= "&" (tostring pat))) |
3219 | (do (assert (not (. pattern (+ k 2))) |
3220 | "expected rest argument before last parameter") |
3221 | (table.insert bindings (. pattern (+ k 1))) |
3222 | (table.insert bindings [`(select ,k ((or _G.unpack table.unpack) |
3223 | ,val))])) |
3224 | (and (= :number (type k)) |
3225 | (= "&" (tostring (. pattern (- k 1))))) |
3226 | nil ; don't process the pattern right after &; already got it |
3227 | (let [subval `(. ,val ,k) |
3228 | (subcondition subbindings) (match-pattern [subval] pat |
3229 | unifications)] |
3230 | (table.insert condition subcondition) |
3231 | (each [_ b (ipairs subbindings)] |
3232 | (table.insert bindings b))))) |
3233 | (values condition bindings)) |
3234 | ;; literal value |
3235 | (values `(= ,val ,pattern) [])))) |
3236 | (fn match-condition [vals clauses] |
3237 | (let [out `(if)] |
3238 | (for [i 1 (length clauses) 2] |
3239 | (let [pattern (. clauses i) |
3240 | body (. clauses (+ i 1)) |
3241 | (condition bindings) (match-pattern vals pattern {})] |
3242 | (table.insert out condition) |
3243 | (table.insert out `(let ,bindings ,body)))) |
3244 | out)) |
3245 | ;; how many multi-valued clauses are there? return a list of that many gensyms |
3246 | (fn val-syms [clauses] |
3247 | (let [syms (list (gensym))] |
3248 | (for [i 1 (length clauses) 2] |
3249 | (if (list? (. clauses i)) |
3250 | (each [valnum (ipairs (. clauses i))] |
3251 | (if (not (. syms valnum)) |
3252 | (tset syms valnum (gensym)))))) |
3253 | syms)) |
3254 | ;; wrap it in a way that prevents double-evaluation of the matched value |
3255 | (let [clauses [...] |
3256 | vals (val-syms clauses)] |
3257 | (if (not= 0 (% (length clauses) 2)) ; treat odd final clause as default |
3258 | (table.insert clauses (length clauses) (sym :_))) |
3259 | ;; protect against multiple evaluation of the value, bind against as |
3260 | ;; many values as we ever match against in the clauses. |
3261 | (list (sym :let) [vals val] |
3262 | (match-condition vals clauses)))) |
3263 | } |
3264 | ]===] |
3265 | do |
3266 | -- docstrings rely on having a place to "put" metadata; we use the module |
3267 | -- system for that. but if you try to require the module while it's being |
3268 | -- loaded, you get a stack overflow. so we fake out the module for the |
3269 | -- purposes of boostrapping the built-in macros here. |
3270 | local moduleName = "__fennel-bootstrap__" |
3271 | package.preload[moduleName] = function() return module end |
3272 | local env = specials.makeCompilerEnv(nil, compiler.scopes.compiler, {}) |
3273 | local macros = eval(stdmacros, { |
3274 | env = env, |
3275 | scope = compiler.makeScope(compiler.scopes.compiler), |
3276 | -- assume the code to load globals doesn't have any |
3277 | -- mistaken globals, otherwise this can be |
3278 | -- problematic when loading fennel in contexts |
3279 | -- where _G is an empty table with an __index |
3280 | -- metamethod. (openresty) |
3281 | allowedGlobals = false, |
3282 | useMetadata = true, |
3283 | filename = "src/fennel/macros.fnl", |
3284 | moduleName = moduleName }) |
3285 | for k,v in pairs(macros) do compiler.scopes.global.macros[k] = v end |
3286 | package.preload[moduleName] = nil |
3287 | end |
3288 | compiler.scopes.global.macros['λ'] = compiler.scopes.global.macros['lambda'] |
3289 |
|
3290 | return module |