1 -------------------------------------------------------------------------------
2 -- Copyright (c) 2006-2013 Fabien Fleutot and others.
4 -- All rights reserved.
6 -- This program and the accompanying materials are made available
7 -- under the terms of the Eclipse Public License v1.0 which
8 -- accompanies this distribution, and is available at
9 -- http://www.eclipse.org/legal/epl-v10.html
11 -- This program and the accompanying materials are also made available
12 -- under the terms of the MIT public license which accompanies this
13 -- distribution, and is available at http://www.lua.org/license.html
16 -- Fabien Fleutot - API and implementation
18 -------------------------------------------------------------------------------
20 -{ extension ('match', ...) }
25 local pp=require 'metalua.pprint'
27 --------------------------------------------------------------------------------
28 -- Instanciate a new AST->source synthetizer
29 --------------------------------------------------------------------------------
32 _acc = { }, -- Accumulates pieces of source as strings
33 current_indent = 0, -- Current level of line indentation
34 indent_step = " " -- Indentation symbol, normally spaces or '\t'
36 return setmetatable (self, M)
39 --------------------------------------------------------------------------------
40 -- Run a synthetizer on the `ast' arg and return the source as a string.
41 -- Can also be used as a static method `M.run (ast)'; in this case,
42 -- a temporary Metizer is instanciated on the fly.
43 --------------------------------------------------------------------------------
46 self, ast = M.new(), self
50 return table.concat (self._acc)
53 --------------------------------------------------------------------------------
54 -- Accumulate a piece of source file in the synthetizer.
55 --------------------------------------------------------------------------------
57 if x then table.insert (self._acc, x) end
60 --------------------------------------------------------------------------------
61 -- Accumulate an indented newline.
62 -- Jumps an extra line if indentation is 0, so that
63 -- toplevel definitions are separated by an extra empty line.
64 --------------------------------------------------------------------------------
66 if self.current_indent == 0 then self:acc "\n" end
67 self:acc ("\n" .. self.indent_step:rep (self.current_indent))
70 --------------------------------------------------------------------------------
71 -- Increase indentation and accumulate a new line.
72 --------------------------------------------------------------------------------
73 function M:nlindent ()
74 self.current_indent = self.current_indent + 1
78 --------------------------------------------------------------------------------
79 -- Decrease indentation and accumulate a new line.
80 --------------------------------------------------------------------------------
81 function M:nldedent ()
82 self.current_indent = self.current_indent - 1
83 self:acc ("\n" .. self.indent_step:rep (self.current_indent))
86 --------------------------------------------------------------------------------
87 -- Keywords, which are illegal as identifiers.
88 --------------------------------------------------------------------------------
89 local keywords_list = {
90 "and", "break", "do", "else", "elseif",
91 "end", "false", "for", "function", "if",
92 "in", "local", "nil", "not", "or",
93 "repeat", "return", "then", "true", "until",
96 for _, kw in pairs(keywords_list) do keywords[kw]=true end
98 --------------------------------------------------------------------------------
99 -- Return true iff string `id' is a legal identifier name.
100 --------------------------------------------------------------------------------
101 local function is_ident (id)
102 return string['match'](id, "^[%a_][%w_]*$") and not keywords[id]
105 --------------------------------------------------------------------------------
106 -- Return true iff ast represents a legal function name for
107 -- syntax sugar ``function foo.bar.gnat() ... end'':
108 -- a series of nested string indexes, with an identifier as
109 -- the innermost node.
110 --------------------------------------------------------------------------------
111 local function is_idx_stack (ast)
113 | `Id{ _ } -> return true
114 | `Index{ left, `String{ _ } } -> return is_idx_stack (left)
119 --------------------------------------------------------------------------------
120 -- Operator precedences, in increasing order.
121 -- This is not directly used, it's used to generate op_prec below.
122 --------------------------------------------------------------------------------
125 { "lt", "le", "eq", "ne" },
128 { "mul", "div", "mod" },
129 { "unary", "not", "len" },
133 --------------------------------------------------------------------------------
134 -- operator --> precedence table, generated from op_preprec.
135 --------------------------------------------------------------------------------
138 for prec, ops in ipairs (op_preprec) do
139 for _, op in ipairs (ops) do
144 --------------------------------------------------------------------------------
145 -- operator --> source representation.
146 --------------------------------------------------------------------------------
148 add = " + ", sub = " - ", mul = " * ",
149 div = " / ", mod = " % ", pow = " ^ ",
150 concat = " .. ", eq = " == ", ne = " ~= ",
151 lt = " < ", le = " <= ", ["and"] = " and ",
152 ["or"] = " or ", ["not"] = "not ", len = "# " }
154 --------------------------------------------------------------------------------
155 -- Accumulate the source representation of AST `node' in
156 -- the synthetizer. Most of the work is done by delegating to
157 -- the method having the name of the AST tag.
158 -- If something can't be converted to normal sources, it's
159 -- instead dumped as a `-{ ... }' splice in the source accumulator.
160 --------------------------------------------------------------------------------
161 function M:node (node)
162 assert (self~=M and self._acc)
163 if node==nil then self:acc'<<error>>'; return end
164 if not node.tag then -- tagless block.
165 self:list (node, self.nl)
167 local f = M[node.tag]
168 if type (f) == "function" then -- Delegate to tag method.
169 f (self, node, unpack (node))
170 elseif type (f) == "string" then -- tag string.
172 else -- No appropriate method, fall back to splice dumping.
173 -- This cannot happen in a plain Lua AST.
175 self:acc (pp.tostring (node, {metalua_tag=1, hide_hash=1}), 80)
181 --------------------------------------------------------------------------------
182 -- Convert every node in the AST list `list' passed as 1st arg.
183 -- `sep' is an optional separator to be accumulated between each list element,
184 -- it can be a string or a synth method.
185 -- `start' is an optional number (default == 1), indicating which is the
186 -- first element of list to be converted, so that we can skip the begining
188 --------------------------------------------------------------------------------
189 function M:list (list, sep, start)
190 for i = start or 1, # list do
194 elseif type (sep) == "function" then sep (self)
195 elseif type (sep) == "string" then self:acc (sep)
196 else error "Invalid list separator" end
201 --------------------------------------------------------------------------------
206 -- Specific AST node dumping methods, associated to their node kinds
207 -- by their name, which is the corresponding AST tag.
208 -- synth:node() is in charge of delegating a node's treatment to the
209 -- appropriate tag method.
211 -- Such tag methods are called with the AST node as 1st arg.
212 -- As a convenience, the n node's children are passed as args #2 ... n+1.
214 -- There are several things that could be refactored into common subroutines
215 -- here: statement blocks dumping, function dumping...
216 -- However, given their small size and linear execution
217 -- (they basically perform series of :acc(), :node(), :list(),
218 -- :nl(), :nlindent() and :nldedent() calls), it seems more readable
219 -- to avoid multiplication of such tiny functions.
221 -- To make sense out of these, you need to know metalua's AST syntax, as
222 -- found in the reference manual or in metalua/doc/ast.txt.
224 --------------------------------------------------------------------------------
229 self:list (node, self.nl)
234 function M:Set (node)
236 | `Set{ { `Index{ lhs, `String{ method } } },
237 { `Function{ { `Id "self", ... } == params, body } } }
238 if is_idx_stack (lhs) and is_ident (method) ->
239 -- ``function foo:bar(...) ... end'' --
245 self:list (params, ", ", 2)
248 self:list (body, self.nl)
252 | `Set{ { lhs }, { `Function{ params, body } } } if is_idx_stack (lhs) ->
253 -- ``function foo(...) ... end'' --
257 self:list (params, ", ")
260 self:list (body, self.nl)
264 | `Set{ { `Id{ lhs1name } == lhs1, ... } == lhs, rhs }
265 if not is_ident (lhs1name) ->
266 -- ``foo, ... = ...'' when foo is *not* a valid identifier.
267 -- In that case, the spliced 1st variable must get parentheses,
268 -- to be distinguished from a statement splice.
269 -- This cannot happen in a plain Lua AST.
273 if lhs[2] then -- more than one lhs variable
275 self:list (lhs, ", ", 2)
278 self:list (rhs, ", ")
280 | `Set{ lhs, rhs } ->
281 -- ``... = ...'', no syntax sugar --
282 self:list (lhs, ", ")
284 self:list (rhs, ", ")
285 | `Set{ lhs, rhs, annot } ->
286 -- ``... = ...'', no syntax sugar, annotation --
289 local ell, a = lhs[i], annot[i]
295 if i~=n then self:acc ', ' end
298 self:list (rhs, ", ")
302 function M:While (node, cond, body)
307 self:list (body, self.nl)
312 function M:Repeat (node, body, cond)
315 self:list (body, self.nl)
322 for i = 1, #node-1, 2 do
323 -- for each ``if/then'' and ``elseif/then'' pair --
324 local cond, body = node[i], node[i+1]
325 self:acc (i==1 and "if " or "elseif ")
329 self:list (body, self.nl)
332 -- odd number of children --> last one is an `else' clause --
336 self:list (node[#node], self.nl)
342 function M:Fornum (node, var, first, last)
343 local body = node[#node]
350 if #node==5 then -- 5 children --> child #4 is a step increment.
356 self:list (body, self.nl)
361 function M:Forin (node, vars, generators, body)
363 self:list (vars, ", ")
365 self:list (generators, ", ")
368 self:list (body, self.nl)
373 function M:Local (node, lhs, rhs, annots)
385 if i~=n then self:acc ', ' end
388 self:list (lhs, ", ")
392 self:list (rhs, ", ")
394 else -- Can't create a local statement with 0 variables in plain Lua
395 self:acc (table.tostring (node, 'nohash', 80))
399 function M:Localrec (node, lhs, rhs)
401 | `Localrec{ { `Id{name} }, { `Function{ params, body } } }
402 if is_ident (name) ->
403 -- ``local function name() ... end'' --
404 self:acc "local function "
407 self:list (params, ", ")
410 self:list (body, self.nl)
415 -- Other localrec are unprintable ==> splice them --
416 -- This cannot happen in a plain Lua AST. --
418 self:acc (table.tostring (node, 'nohash', 80))
423 function M:Call (node, f)
424 -- single string or table literal arg ==> no need for parentheses. --
427 | `Call{ _, `String{_} }
428 | `Call{ _, `Table{...}} -> parens = false
432 self:acc (parens and " (" or " ")
433 self:list (node, ", ", 2) -- skip `f'.
434 self:acc (parens and ")")
437 function M:Invoke (node, f, method)
438 -- single string or table literal arg ==> no need for parentheses. --
441 | `Invoke{ _, _, `String{_} }
442 | `Invoke{ _, _, `Table{...}} -> parens = false
448 self:acc (parens and " (" or " ")
449 self:list (node, ", ", 3) -- Skip args #1 and #2, object and method name.
450 self:acc (parens and ")")
453 function M:Return (node)
455 self:list (node, ", ")
464 function M:Number (node, n)
465 self:acc (tostring (n))
468 function M:String (node, str)
469 -- format "%q" prints '\n' in an umpractical way IMO,
470 -- so this is fixed with the :gsub( ) call.
471 self:acc (string.format ("%q", str):gsub ("\\\n", "\\n"))
474 function M:Function (node, params, body, annots)
475 self:acc "function ("
479 local p, a = params[i], annots[i]
485 if i~=n then self:acc ', ' end
488 self:list (params, ", ")
492 self:list (body, self.nl)
497 function M:Table (node)
498 if not node[1] then self:acc "{ }" else
500 if #node > 1 then self:nlindent () else self:acc " " end
501 for i, elem in ipairs (node) do
503 | `Pair{ `String{ key }, value } if is_ident (key) ->
504 -- ``key = value''. --
509 | `Pair{ key, value } ->
510 -- ``[key] = value''. --
525 if #node > 1 then self:nldedent () else self:acc " " end
530 function M:Op (node, op, a, b)
531 -- Transform ``not (a == b)'' into ``a ~= b''. --
533 | `Op{ "not", `Op{ "eq", _a, _b } }
534 | `Op{ "not", `Paren{ `Op{ "eq", _a, _b } } } ->
535 op, a, b = "ne", _a, _b
539 if b then -- binary operator.
540 local left_paren, right_paren
542 | `Op{ op_a, ...} if op_prec[op] >= op_prec[op_a] -> left_paren = true
543 | _ -> left_paren = false
546 match b with -- FIXME: might not work with right assoc operators ^ and ..
547 | `Op{ op_b, ...} if op_prec[op] >= op_prec[op_b] -> right_paren = true
548 | _ -> right_paren = false
551 self:acc (left_paren and "(")
553 self:acc (left_paren and ")")
555 self:acc (op_symbol [op])
557 self:acc (right_paren and "(")
559 self:acc (right_paren and ")")
561 else -- unary operator.
564 | `Op{ op_a, ... } if op_prec[op] >= op_prec[op_a] -> paren = true
567 self:acc (op_symbol[op])
568 self:acc (paren and "(")
570 self:acc (paren and ")")
574 function M:Paren (node, content)
580 function M:Index (node, table, key)
582 -- Check precedence, see if parens are needed around the table --
584 | `Op{ op, ... } if op_prec[op] < op_prec.index -> paren_table = true
585 | _ -> paren_table = false
588 self:acc (paren_table and "(")
590 self:acc (paren_table and ")")
593 | `String{ field } if is_ident (field) ->
598 -- ``table [key]''. --
605 function M:Id (node, name)
606 if is_ident (name) then
608 else -- Unprintable identifier, fall back to splice representation.
609 -- This cannot happen in a plain Lua AST.
611 self:String (node, name)
625 function M:TId (node, name) self:acc(name) end
628 function M:TCatbar(node, te, tebar)
636 function M:TFunction(node, p, r)
642 function M:TTable (node, default, pairs)
644 self:list (pairs, ', ')
645 if default.tag~='TField' then
652 function M:TPair (node, k, v)
658 function M:TIdbar (node, name)
662 function M:TCatbar (node, a, b)
668 function M:tebar(node)
669 if node.tag then self:node(node) else
671 self:list(node, ', ')
676 function M:TUnkbar(node, name)
681 function M:TUnk(node, name)
686 for name, tag in pairs{ const='TConst', var='TVar', currently='TCurrently', just='TJust' } do
687 M[tag] = function(self, node, te)
693 return (|x| M.run(x))