+++ /dev/null
--{ extension 'match' }
-
-local M = { }
-M.__index = M
-
-ast_to_string = |x| M.run(x)
-
---------------------------------------------------------------------------------
--- Instanciate a new AST->source synthetizer
---------------------------------------------------------------------------------
-function M.new ()
- local self = {
- _acc = { }, -- Accumulates pieces of source as strings
- current_indent = 0, -- Current level of line indentation
- indent_step = " " -- Indentation symbol, normally spaces or '\t'
- }
- return setmetatable (self, M)
-end
-
---------------------------------------------------------------------------------
--- Run a synthetizer on the `ast' arg and return the source as a string.
--- Can also be used as a static method `M.run (ast)'; in this case,
--- a temporary Metizer is instanciated on the fly.
---------------------------------------------------------------------------------
-function M:run (ast)
- if not ast then
- self, ast = M.new(), self
- end
- self._acc = { }
- self:node (ast)
- return table.concat (self._acc)
-end
-
---------------------------------------------------------------------------------
--- Accumulate a piece of source file in the synthetizer.
---------------------------------------------------------------------------------
-function M:acc (x)
- if x then table.insert (self._acc, x) end
-end
-
---------------------------------------------------------------------------------
--- Accumulate an indented newline.
--- Jumps an extra line if indentation is 0, so that
--- toplevel definitions are separated by an extra empty line.
---------------------------------------------------------------------------------
-function M:nl ()
- if self.current_indent == 0 then self:acc "\n" end
- self:acc ("\n" .. self.indent_step:rep (self.current_indent))
-end
-
---------------------------------------------------------------------------------
--- Increase indentation and accumulate a new line.
---------------------------------------------------------------------------------
-function M:nlindent ()
- self.current_indent = self.current_indent + 1
- self:nl ()
-end
-
---------------------------------------------------------------------------------
--- Decrease indentation and accumulate a new line.
---------------------------------------------------------------------------------
-function M:nldedent ()
- self.current_indent = self.current_indent - 1
- self:acc ("\n" .. self.indent_step:rep (self.current_indent))
-end
-
---------------------------------------------------------------------------------
--- Keywords, which are illegal as identifiers.
---------------------------------------------------------------------------------
-local keywords = table.transpose {
- "and", "break", "do", "else", "elseif",
- "end", "false", "for", "function", "if",
- "in", "local", "nil", "not", "or",
- "repeat", "return", "then", "true", "until",
- "while" }
-
---------------------------------------------------------------------------------
--- Return true iff string `id' is a legal identifier name.
---------------------------------------------------------------------------------
-local function is_ident (id)
- return id:strmatch "^[%a_][%w_]*$" and not keywords[id]
-end
-
---------------------------------------------------------------------------------
--- Return true iff ast represents a legal function name for
--- syntax sugar ``function foo.bar.gnat() ... end'':
--- a series of nested string indexes, with an identifier as
--- the innermost node.
---------------------------------------------------------------------------------
-local function is_idx_stack (ast)
- match ast with
- | `Id{ _ } -> return true
- | `Index{ left, `String{ _ } } -> return is_idx_stack (left)
- | _ -> return false
- end
-end
-
---------------------------------------------------------------------------------
--- Operator precedences, in increasing order.
--- This is not directly used, it's used to generate op_prec below.
---------------------------------------------------------------------------------
-local op_preprec = {
- { "or", "and" },
- { "lt", "le", "eq", "ne" },
- { "concat" },
- { "add", "sub" },
- { "mul", "div", "mod" },
- { "unary", "not", "len" },
- { "pow" },
- { "index" } }
-
---------------------------------------------------------------------------------
--- operator --> precedence table, generated from op_preprec.
---------------------------------------------------------------------------------
-local op_prec = { }
-
-for prec, ops in ipairs (op_preprec) do
- for op in ivalues (ops) do
- op_prec[op] = prec
- end
-end
-
---------------------------------------------------------------------------------
--- operator --> source representation.
---------------------------------------------------------------------------------
-local op_symbol = {
- add = " + ", sub = " - ", mul = " * ",
- div = " / ", mod = " % ", pow = " ^ ",
- concat = " .. ", eq = " == ", ne = " ~= ",
- lt = " < ", le = " <= ", ["and"] = " and ",
- ["or"] = " or ", ["not"] = "not ", len = "# " }
-
---------------------------------------------------------------------------------
--- Accumulate the source representation of AST `node' in
--- the synthetizer. Most of the work is done by delegating to
--- the method having the name of the AST tag.
--- If something can't be converted to normal sources, it's
--- instead dumped as a `-{ ... }' splice in the source accumulator.
---------------------------------------------------------------------------------
-function M:node (node)
- assert (self~=M and self._acc)
- if not node.tag then -- tagless block.
- self:list (node, self.nl)
- else
- local f = M[node.tag]
- if type (f) == "function" then -- Delegate to tag method.
- f (self, node, unpack (node))
- elseif type (f) == "string" then -- tag string.
- self:acc (f)
- else -- No appropriate method, fall back to splice dumping.
- -- This cannot happen in a plain Lua AST.
- self:acc " -{ "
- self:acc (table.tostring (node, "nohash"), 80)
- self:acc " }"
- end
- end
-end
-
---------------------------------------------------------------------------------
--- Convert every node in the AST list `list' passed as 1st arg.
--- `sep' is an optional separator to be accumulated between each list element,
--- it can be a string or a synth method.
--- `start' is an optional number (default == 1), indicating which is the
--- first element of list to be converted, so that we can skip the begining
--- of a list.
---------------------------------------------------------------------------------
-function M:list (list, sep, start)
- for i = start or 1, # list do
- self:node (list[i])
- if list[i + 1] then
- if not sep then
- elseif type (sep) == "function" then sep (self)
- elseif type (sep) == "string" then self:acc (sep)
- else error "Invalid list separator" end
- end
- end
-end
-
---------------------------------------------------------------------------------
---
--- Tag methods.
--- ------------
---
--- Specific AST node dumping methods, associated to their node kinds
--- by their name, which is the corresponding AST tag.
--- synth:node() is in charge of delegating a node's treatment to the
--- appropriate tag method.
---
--- Such tag methods are called with the AST node as 1st arg.
--- As a convenience, the n node's children are passed as args #2 ... n+1.
---
--- There are several things that could be refactored into common subroutines
--- here: statement blocks dumping, function dumping...
--- However, given their small size and linear execution
--- (they basically perform series of :acc(), :node(), :list(),
--- :nl(), :nlindent() and :nldedent() calls), it seems more readable
--- to avoid multiplication of such tiny functions.
---
--- To make sense out of these, you need to know metalua's AST syntax, as
--- found in the reference manual or in metalua/doc/ast.txt.
---
---------------------------------------------------------------------------------
-
-function M:Do (node)
- self:acc "do"
- self:nlindent ()
- self:list (node, self.nl)
- self:nldedent ()
- self:acc "end"
-end
-
-function M:Set (node)
- match node with
- | `Set{ { `Index{ lhs, `String{ method } } },
- { `Function{ { `Id "self", ... } == params, body } } }
- if is_idx_stack (lhs) and is_ident (method) ->
- -- ``function foo:bar(...) ... end'' --
- self:acc "function "
- self:node (lhs)
- self:acc ":"
- self:acc (method)
- self:acc " ("
- self:list (params, ", ", 2)
- self:acc ")"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-
- | `Set{ { lhs }, { `Function{ params, body } } } if is_idx_stack (lhs) ->
- -- ``function foo(...) ... end'' --
- self:acc "function "
- self:node (lhs)
- self:acc " ("
- self:list (params, ", ")
- self:acc ")"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-
- | `Set{ { `Id{ lhs1name } == lhs1, ... } == lhs, rhs }
- if not is_ident (lhs1name) ->
- -- ``foo, ... = ...'' when foo is *not* a valid identifier.
- -- In that case, the spliced 1st variable must get parentheses,
- -- to be distinguished from a statement splice.
- -- This cannot happen in a plain Lua AST.
- self:acc "("
- self:node (lhs1)
- self:acc ")"
- if lhs[2] then -- more than one lhs variable
- self:acc ", "
- self:list (lhs, ", ", 2)
- end
- self:acc " = "
- self:list (rhs, ", ")
-
- | `Set{ lhs, rhs } ->
- -- ``... = ...'', no syntax sugar --
- self:list (lhs, ", ")
- self:acc " = "
- self:list (rhs, ", ")
- end
-end
-
-function M:While (node, cond, body)
- self:acc "while "
- self:node (cond)
- self:acc " do"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-end
-
-function M:Repeat (node, body, cond)
- self:acc "repeat"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "until "
- self:node (cond)
-end
-
-function M:If (node)
- for i = 1, #node-1, 2 do
- -- for each ``if/then'' and ``elseif/then'' pair --
- local cond, body = node[i], node[i+1]
- self:acc (i==1 and "if " or "elseif ")
- self:node (cond)
- self:acc " then"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- end
- -- odd number of children --> last one is an `else' clause --
- if #node%2 == 1 then
- self:acc "else"
- self:nlindent ()
- self:list (node[#node], self.nl)
- self:nldedent ()
- end
- self:acc "end"
-end
-
-function M:Fornum (node, var, first, last)
- local body = node[#node]
- self:acc "for "
- self:node (var)
- self:acc " = "
- self:node (first)
- self:acc ", "
- self:node (last)
- if #node==5 then -- 5 children --> child #4 is a step increment.
- self:acc ", "
- self:node (node[4])
- end
- self:acc " do"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-end
-
-function M:Forin (node, vars, generators, body)
- self:acc "for "
- self:list (vars, ", ")
- self:acc " in "
- self:list (generators, ", ")
- self:acc " do"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-end
-
-function M:Local (node, lhs, rhs)
- if next (lhs) then
- self:acc "local "
- self:list (lhs, ", ")
- if rhs[1] then
- self:acc " = "
- self:list (rhs, ", ")
- end
- else -- Can't create a local statement with 0 variables in plain Lua
- self:acc (table.tostring (node, 'nohash', 80))
- end
-end
-
-function M:Localrec (node, lhs, rhs)
- match node with
- | `Localrec{ { `Id{name} }, { `Function{ params, body } } }
- if is_ident (name) ->
- -- ``local function name() ... end'' --
- self:acc "local function "
- self:acc (name)
- self:acc " ("
- self:list (params, ", ")
- self:acc ")"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-
- | _ ->
- -- Other localrec are unprintable ==> splice them --
- -- This cannot happen in a plain Lua AST. --
- self:acc "-{ "
- self:acc (table.tostring (node, 'nohash', 80))
- self:acc " }"
- end
-end
-
-function M:Call (node, f)
- -- single string or table literal arg ==> no need for parentheses. --
- local parens
- match node with
- | `Call{ _, `String{_} }
- | `Call{ _, `Table{...}} -> parens = false
- | _ -> parens = true
- end
- self:node (f)
- self:acc (parens and " (" or " ")
- self:list (node, ", ", 2) -- skip `f'.
- self:acc (parens and ")")
-end
-
-function M:Invoke (node, f, method)
- -- single string or table literal arg ==> no need for parentheses. --
- local parens
- match node with
- | `Invoke{ _, _, `String{_} }
- | `Invoke{ _, _, `Table{...}} -> parens = false
- | _ -> parens = true
- end
- self:node (f)
- self:acc ":"
- self:acc (method[1])
- self:acc (parens and " (" or " ")
- self:list (node, ", ", 3) -- Skip args #1 and #2, object and method name.
- self:acc (parens and ")")
-end
-
-function M:Return (node)
- self:acc "return "
- self:list (node, ", ")
-end
-
-M.Break = "break"
-M.Nil = "nil"
-M.False = "false"
-M.True = "true"
-M.Dots = "..."
-
-function M:Number (node, n)
- self:acc (tostring (n))
-end
-
-function M:String (node, str)
- -- format "%q" prints '\n' in an umpractical way IMO,
- -- so this is fixed with the :gsub( ) call.
- self:acc (string.format ("%q", str):gsub ("\\\n", "\\n"))
-end
-
-function M:Function (node, params, body)
- self:acc "function ("
- self:list (params, ", ")
- self:acc ")"
- self:nlindent ()
- self:list (body, self.nl)
- self:nldedent ()
- self:acc "end"
-end
-
-function M:Table (node)
- if not node[1] then self:acc "{ }" else
- self:acc "{"
- if #node > 1 then self:nlindent () else self:acc " " end
- for i, elem in ipairs (node) do
- match elem with
- | `Pair{ `String{ key }, value } if is_ident (key) ->
- -- ``key = value''. --
- self:acc (key)
- self:acc " = "
- self:node (value)
-
- | `Pair{ key, value } ->
- -- ``[key] = value''. --
- self:acc "["
- self:node (key)
- self:acc "] = "
- self:node (value)
-
- | _ ->
- -- ``value''. --
- self:node (elem)
- end
- if node [i+1] then
- self:acc ","
- self:nl ()
- end
- end
- if #node > 1 then self:nldedent () else self:acc " " end
- self:acc "}"
- end
-end
-
-function M:Op (node, op, a, b)
- -- Transform ``not (a == b)'' into ``a ~= b''. --
- match node with
- | `Op{ "not", `Op{ "eq", _a, _b } }
- | `Op{ "not", `Paren{ `Op{ "eq", _a, _b } } } ->
- op, a, b = "ne", _a, _b
- | _ ->
- end
-
- if b then -- binary operator.
- local left_paren, right_paren
- match a with
- | `Op{ op_a, ...} if op_prec[op] >= op_prec[op_a] -> left_paren = true
- | _ -> left_paren = false
- end
-
- match b with -- FIXME: might not work with right assoc operators ^ and ..
- | `Op{ op_b, ...} if op_prec[op] >= op_prec[op_b] -> right_paren = true
- | _ -> right_paren = false
- end
-
- self:acc (left_paren and "(")
- self:node (a)
- self:acc (left_paren and ")")
-
- self:acc (op_symbol [op])
-
- self:acc (right_paren and "(")
- self:node (b)
- self:acc (right_paren and ")")
-
- else -- unary operator.
- local paren
- match a with
- | `Op{ op_a, ... } if op_prec[op] >= op_prec[op_a] -> paren = true
- | _ -> paren = false
- end
- self:acc (op_symbol[op])
- self:acc (paren and "(")
- self:node (a)
- self:acc (paren and ")")
- end
-end
-
-function M:Paren (node, content)
- self:acc "("
- self:node (content)
- self:acc ")"
-end
-
-function M:Index (node, table, key)
- local paren_table
- -- Check precedence, see if parens are needed around the table --
- match table with
- | `Op{ op, ... } if op_prec[op] < op_prec.index -> paren_table = true
- | _ -> paren_table = false
- end
-
- self:acc (paren_table and "(")
- self:node (table)
- self:acc (paren_table and ")")
-
- match key with
- | `String{ field } if is_ident (field) ->
- -- ``table.key''. --
- self:acc "."
- self:acc (field)
- | _ ->
- -- ``table [key]''. --
- self:acc "["
- self:node (key)
- self:acc "]"
- end
-end
-
-function M:Id (node, name)
- if is_ident (name) then
- self:acc (name)
- else -- Unprintable identifier, fall back to splice representation.
- -- This cannot happen in a plain Lua AST.
- self:acc "-{`Id "
- self:String (node, name)
- self:acc "}"
- end
-end
-