]> git.lizzy.rs Git - metalua.git/commitdiff
Merge branch 'master' of git@github.com:fab13n/metalua into cleanup
authorFabien Fleutot <fabien@MacFabien.home>
Tue, 3 Feb 2009 07:20:48 +0000 (08:20 +0100)
committerFabien Fleutot <fabien@MacFabien.home>
Tue, 3 Feb 2009 07:20:48 +0000 (08:20 +0100)
src/compiler/gg.lua
src/compiler/lexer.lua
src/compiler/metalua.mlua
src/compiler/mlp_expr.lua
src/compiler/mlp_meta.lua
src/compiler/mlp_stat.lua
src/lib/metalua/ast_to_string.mlua [new file with mode: 0644]
src/lib/metalua/extension/clist.mlua

index 486752d537e8f8db33a24db11c6b717844d59c8b..d237d28f1939f4fecc66df3ae42e707a5b02e479 100644 (file)
@@ -260,15 +260,16 @@ function multisequence (p)
       -- compile if necessary:
       if not is_parser(s) then sequence(s) end
       if is_parser(s) ~= 'sequence' or type(s[1]) ~= "string" then 
-         if self.default then
-            error "Invalid sequence for multiseq, there is already a default"
-         else
-            self.default = s
-         end
-      elseif self.sequences[s[1]] then 
+         if self.default then -- two defaults
+            error ("In a multisequence parser, all but one sequences "..
+                   "must start with a keyword")
+         else self.default = s end -- first default
+      elseif self.sequences[s[1]] then -- duplicate keyword
          eprintf (" *** Warning: keyword %q overloaded in multisequence ***", s[1])
+         self.sequences[s[1]] = s
+      else -- newly caught keyword
+         self.sequences[s[1]] = s
       end
-      self.sequences[s[1]] = s
    end -- </multisequence.add>
 
    -------------------------------------------------------------------
@@ -671,6 +672,8 @@ function onkeyword (p)
       if type(x)=="string" then table.insert (p.keywords, x)
       else assert (not p.primary and is_parser (x)); p.primary = x end
    end
+   if not next (p.keywords) then 
+      eprintf("Warning, no keyword to trigger gg.onkeyword") end
    assert (p.primary, 'no primary parser in gg.onkeyword')
    return p
 end --</onkeyword>
index 1ca7337ae1f9bc9d3d5512b88706268e20d537a3..edfa666c620401292c69fe05d8753e67f2278cb2 100644 (file)
@@ -7,9 +7,6 @@
 --
 -- TODO: 
 --
--- * Make it possible to change lexer on the fly. This implies the
---   ability to easily undo any pre-extracted tokens;
---
 -- * Make it easy to define new flavors of strings. Replacing the
 --   lexer.patterns.long_string regexp by an extensible list, with
 --   customizable token tag, would probably be enough. Maybe add:
@@ -50,12 +47,10 @@ lexer.patterns = {
    final_short_comment = "^%-%-([^\n]*)()$",
    long_comment        = "^%-%-%[(=*)%[\n?(.-)%]%1%]()",
    long_string         = "^%[(=*)%[\n?(.-)%]%1%]()",
-   number_mantissa     = {
-      "^%d+%.?%d*()",
-      "^%d*%.%d+()" },
-   number_exponant = "^[eE][%+%-]?%d+()",
-   number_hex      = "^0[xX]%x+()",
-   word            = "^([%a_][%w_]*)()"
+   number_mantissa     = { "^%d+%.?%d*()", "^%d*%.%d+()" },
+   number_exponant     = "^[eE][%+%-]?%d+()",
+   number_hex          = "^0[xX]%x+()",
+   word                = "^([%a_][%w_]*)()"
 }
 
 ----------------------------------------------------------------------
@@ -112,22 +107,6 @@ lexer.token_metatable = {
 } 
       
 lexer.lineinfo_metatable = { }
---[[ 
--- The presence of this function prevents serialization by Pluto, 
--- I can't figure out why :(
-function lexer.lineinfo_metatable:__tostring()
-   local txt = string.format("%s:%i(%i,%i)", self[4], self[3], self[1], self[2])
-   if self.comments then 
-      acc = { }
-      for comment in ivalues(self.comments) do
-        local content, loc1, loc2, kind = unpack(comment)
-        table.insert (acc, string.format ("%s@%i..%i:%q", kind, loc1, loc2, content))
-      end
-      txt = txt.."["..table.concat(acc,"; ").."]"
-   end
-   return txt
-end
---]]
 
 ----------------------------------------------------------------------
 -- Really extract next token fron the raw string 
@@ -140,7 +119,7 @@ function lexer:extract ()
    local loc = self.i
    local eof, token
 
-   -- Put line info, comments and metatable arount the tag and content
+   -- Put line info, comments and metatable around the tag and content
    -- provided by extractors, thus returning a complete lexer token.
    -- first_line: line # at the beginning of token
    -- first_column_offset: char # of the last '\n' before beginning of token
index 3647e4394c5f302abecb4ec417c881d64acca0c1..3c3564d0c37b167e47cf329f27cde1ae0ad92d63 100644 (file)
@@ -48,6 +48,9 @@ parser = clopts {
    {  short = 'A', long = 'print-ast-lineinfo',  type = 'boolean',
       usage = 'print the AST resulting from file compilation, including lineinfo data'
    },
+   {  short = 'S', long = 'print-src',  type = 'boolean',
+      usage = 'print the AST resulting from file compilation, as re-gerenerated sources'
+   },
    {  short = 'b', long = 'metabugs', type = 'boolean',
       usage = 'show syntax errors as compile-time execution errors'
    },
@@ -170,6 +173,18 @@ local function main (...)
       end
    end
 
+   -------------------------------------------------------------------
+   -- Source printing
+   if cfg['print-src'] then
+      verb_print "Resulting sources:"
+      require 'metalua.ast_to_string'
+      for x in ivalues(code) do
+         printf("--- Source From %s: ---", table.tostring(x.source, 'nohash'))
+         if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end
+         print (ast_to_string (x))
+      end
+   end
+
    -------------------------------------------------------------------
    -- Insert runtime loader
    if cfg['no-runtime'] then
index 761d9cf8cd10405cd4e8ef82d73c615463741c73..091f92e2c26bf381ec574d2ef5555e0704113a57 100644 (file)
@@ -71,12 +71,12 @@ expr_list = gg.list{ _expr, separators = "," }
 --------------------------------------------------------------------------------
 -- Helpers for function applications / method applications
 --------------------------------------------------------------------------------
-local func_args_content = gg.list { 
+func_args_content = gg.list { 
    name = "function arguments",
    _expr, separators = ",", terminators = ")" } 
 
 -- Used to parse methods
-local method_args = gg.multisequence{
+method_args = gg.multisequence{
    name = "function argument(s)",
    { "{", table_content, "}" },
    { "(", func_args_content, ")", builder = fget(1) },
@@ -105,10 +105,11 @@ local _func_val = function (lx) return func_val(lx) end
 --------------------------------------------------------------------------------
 -- Default parser for primary expressions
 --------------------------------------------------------------------------------
-local function id_or_literal (lx)
+function id_or_literal (lx)
    local a = lx:next()
    if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then
-      gg.parse_error (lx, "Unexpected expr token %s", _G.table.tostring(a))
+      gg.parse_error (lx, "Unexpected expr token %s",
+                      _G.table.tostring (a, 'nohash'))
    end
    return a
 end
index 60a8d457704dd4f0b24ba8c824593b5d3f3937ac..27d476a157410b7089aa1c6831649e6a82f76600 100644 (file)
 -- for details.
 --
 ----------------------------------------------------------------------
--- History:
--- $Log: mlp_meta.lua,v $
--- Revision 1.4  2006/11/15 09:07:50  fab13n
--- debugged meta operators.
---
--- Revision 1.2  2006/11/09 09:39:57  fab13n
--- some cleanup
---
--- Revision 1.1  2006/11/07 21:29:02  fab13n
--- improved quasi-quoting
---
--- Revision 1.3  2006/11/07 04:38:00  fab13n
--- first bootstrapping version.
---
--- Revision 1.2  2006/11/05 15:08:34  fab13n
--- updated code generation, to be compliant with 5.1
---
-----------------------------------------------------------------------
 
 
 --------------------------------------------------------------------------------
@@ -39,9 +21,6 @@
 --
 --------------------------------------------------------------------------------
 
---require "compile"
---require "ldump"
-
 module ("mlp", package.seeall)
 
 --------------------------------------------------------------------------------
@@ -51,12 +30,7 @@ module ("mlp", package.seeall)
 --------------------------------------------------------------------------------
 
 function splice (ast)
-   --printf(" [SPLICE] Ready to compile:\n%s", _G.table.tostring (ast, "nohash", 60))
    local f = mlc.function_of_ast(ast, '=splice')
-   --printf " [SPLICE] Splice Compiled."
-   --local status, result = pcall(f)
-   --printf " [SPLICE] Splice Evaled."
-   --if not status then print 'ERROR IN SPLICE' end
    local result=f()
    return result
 end
@@ -106,12 +80,6 @@ function splice_content (lx)
       lx:next() -- skip ":"
       assert (a.tag=="Id", "Invalid splice parser name")
       parser_name = a[1]
---       printf("this splice is a %s", parser_name)
---    else
---       printf("no splice specifier:\npeek(1)")
---       _G.table.print(lx:peek(1))
---       printf("peek(2)")
---       _G.table.print(lx:peek(2))
    end
    local ast = mlp[parser_name](lx)
    if in_a_quote then
@@ -132,25 +100,18 @@ end
 --------------------------------------------------------------------------------
 function quote_content (lx)
    local parser 
-   if lx:is_keyword (lx:peek(1), ":") then -- +{:parser: content }
-      lx:next()
-      errory "NOT IMPLEMENTED"
-   elseif lx:is_keyword (lx:peek(2), ":") then -- +{parser: content }
+   if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content }
       parser = mlp[id(lx)[1]]
       lx:next()
    else -- +{ content }
       parser = mlp.expr
    end
 
-   --assert(not in_a_quote, "Nested quotes not handled yet")
    local prev_iq = in_a_quote
    in_a_quote = true
    --print("IN_A_QUOTE")
    local content = parser (lx)
    local q_content = quote (content)
---     printf("/IN_A_QUOTE:\n* content=\n%s\n* q_content=\n%s\n",
---            _G.table.tostring(content, "nohash", 60),
---            _G.table.tostring(q_content, "nohash", 60))
    in_a_quote = prev_iq
    return q_content
 end
index bf19c6fc3c720fef4ac439c992bf0f3faf7b0174..b959d96551bc3908b46d8c9faf7c1c874a35c451 100644 (file)
@@ -179,7 +179,7 @@ local function assign_or_call_stat_parser (lx)
    end
 end
 
-local local_stat_parser = gg.multisequence{
+local_stat_parser = gg.multisequence{
    -- local function <name> <func_val>
    { "function", id, func_val, builder = 
       function(x) 
diff --git a/src/lib/metalua/ast_to_string.mlua b/src/lib/metalua/ast_to_string.mlua
new file mode 100644 (file)
index 0000000..e27827d
--- /dev/null
@@ -0,0 +1,553 @@
+-{ 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
+
index e420f343c179a7e2c3f4336a1a3f9eb00b325f7e..b7e1992a0b580ebccd597a23f5312416726c2b34 100644 (file)
@@ -53,7 +53,7 @@ local function comp_builder(core, list, no_unpack)
       r = +{stat: for -{w} in values( -{x} ) do -{ `Call{ ti, v, w } } end }
    | `Pair{ k, w } ->
       r = `Set{ { `Index{ v, k } }, { w } }
-   |  x -> r = `Call{ ti, v, x }
+   |  _ -> r = `Call{ ti, v, core }
    end
 
    -----------------------------------------------------------------------------
@@ -115,13 +115,18 @@ mlp.stat:add{ "for", gg.expr {
    "do", mlp.block, "end", builder = for_stat_builder }
 --]]
 
+--------------------------------------------------------------------------------
+-- Back-end for improved index operator.
+--------------------------------------------------------------------------------
 local function index_builder(a, suffix)
    match suffix[1] with
+   -- Single index, no range: keep the native semantics
    | { { e, false } } -> return `Index{ a, e }
+   -- Either a range, or multiple indexes, or both
    | ranges ->
       local r = `Call{ +{table.isub}, a }
-      local function acc(x,y) table.insert(r,x); table.insert(r,y) end
-      for seq in values(ranges) do
+      local function acc (x,y) table.insert (r,x); table.insert (r,y) end
+      for seq in ivalues (ranges) do
          match seq with
          | { e, false } -> acc(e,e)
          | { e, f }     -> acc(e,f)
@@ -131,6 +136,11 @@ local function index_builder(a, suffix)
    end
 end
 
+--------------------------------------------------------------------------------
+-- Improved "[...]" index operator:
+--  * support for multi-indexes ("foo[bar, gnat]")
+--  * support for ranges ("foo[bar ... gnat]")
+--------------------------------------------------------------------------------
 mlp.expr.suffix:del '['
 mlp.expr.suffix:add{ name="table index/range",
    "[", gg.list{