Before you ask for it, here is the Metalua file I load which may corrupt something. But there are no reference to match in it
require 'metalua.compiler'
require 'metalua.walk'
-{ extension 'match' }
-- Just redefining classic print, as there is a flush problem calling it from Java
local print = function (string) print(string) io.flush() end
---
-- Simple type printing visitor
--
local ind = 0
local sDown = function(node, ...) ind=ind+1; print(string.rep('| ', ind)..'Down on '..(node.tag or 'chunk')) end
local sUp = function(node, ...) print(string.rep('| ', ind)..'Up on '..(node.tag or 'chunk')); ind=ind-1 end
local simple={
binder = function(node, ...) sUp(node) sDown(node)end,
block = {up = sUp, down = sDown},
expr = {up = sUp, down = sDown},
stat = {up = sUp, down = sDown}
}
--
-- The real work
--
local module = {}
---
-- Initialize cache for Lua ast objects
--
-- While processing this table will contain
-- key: Metalua AST Nodes
-- values: Children key node
local hash = {}
---
-- Initialize cache for Java Objects
--
-- During process this table will associate a Metalua AST Node to a Java one
-- key: Metalua AST Node
-- value: Java DLTK Node
local object= {}
---
-- Contains Declaration Metalua nodes
--
-- Use to backpatch nodes which had not full descents at construction
-- key: Random numbers
-- value: Metalua AST Node
local declaration = {}
---
-- Provides Metalua AST Nodes offset
--
-- @param node Metalua AST Node
-- @return number Node's start offset
-- @return number Node's end offset
local function offsets(node)
local start = node.lineinfo and node.lineinfo.first[3] - 1 or 0
local _end = node.lineinfo and node.lineinfo.last[3] or 0
return start, _end
end
---
-- Converts a Metalua node list in a DLTK Chunk
--
-- Append list's Metalua nodes in a Java Chunk object using association available in <strong>object</strong> table.
-- @param list Table containing nodes at integer fields
-- @return Filled Java DLTK Chunk representing given <strong>list</strong> content
local function statListToChunk(list)
local chunk = DLTK.Chunk(0, 0)
for k, node in ipairs(list) do
DLTK.appendStatementToChunk(chunk, object[node])
end
return chunk
end
---
-- Converts a list of Metalua AST Nodes in Java DLTK CallArgumentsList
--
-- @param list Table gathering Metalua AST Nodes on integer values
-- @return Filled Java DLTK CallArgumentsList representing nodes in <strong>list</strong>
local function statListToCallArgList(list)
local clist = DLTK.CallArgumentsList(0, 0)
for k, node in ipairs(list) do
DLTK.appendNodeToCallArgumentList(clist, object[node])
end
return clist
end
---
-- Indexes nodes and relates then to their parents
--
-- <strong>This function is intended to be called from a visitor parsing an AST down.</strong>.
-- Fills <code>hash</code> table with Metalua AST Nodes as key and back patch parent node adding children to build a direct hirarchy between nodes.
--
-- @param node Processed Metalua AST Node
-- @param parent Metalua AST Node, parent of given node, allow to back patch parent children
-- @param parent ... Metalua AST Node, reprensenting grand-parent, grand-grand-parent and so on
local function down(node,parent, ...)
if not hash[node] then
hash[node] = {}
end
if parent then
table.insert(hash[parent], node)
end
end
---
-- Visitor that enables building DLTK ASTs from visting Metalua AST ones
--
-- The method is simple. Firstly, visit down the Metalua AST brearing in mind parenthood between nodes.
-- In order to do so, parse down fill <code>hash</code> using <code>down(node, parent, ...)</code> function.<br/>
-- Secondly, while parsing up, visitor intaciates Java DLTK object, storing them in <code>object</code> table and using <code>hash</code> to ensure
-- parenthood consistency.
local visitor = {
block = {down=down},
expr = {down=down},
stat = {down=down}
}
---
-- Common processing for nodes which are Expressions and Statements
--
-- While parsing up and creating Java objects, some node's types are both expressions and and statements. So, they could be processed identically in
-- <code>visitor.expr.up</code> and <code>visitor.stat.up</code>. Therefore, they are dealt with here, in order to factorise treatment.
-- <strong>This function uses pattern matching, it is intented to be called while visiting an AST.</strong>
--
-- @param node Metalua AST node
local function apply(node, ...)
local first, last = offsets(node)
match node with
| `Call {...} ->
-- Load param list
local clist = DLTK.CallArgumentsList(0, 0)
for child = 2,#node do
DLTK.appendNodeToCallArgumentList(clist, object[node[child]] )
end
object[ node ] = DLTK.Call(first, last, object[node[1]], clist )
| `Invoke {expr, string } ->
object[ node ] = DLTK.Invoke(first, last, object[expr], object[string])
| `Invoke {expr, string, ...} ->
local clist = DLTK.CallArgumentsList(0, 0)
for child = 3,#node do
DLTK.appendNodeToCallArgumentList(clist, object[node[child]])
end
object[ node ] = DLTK.Invoke(first, last, object[expr], object[string], clist)
end
end
---
-- Registers given node in <code>hash</code> and creates appropriate object in <code>object</table>
--
-- @param node Metalua AST Node to process
-- @param parent Given <code>node</code> parent
-- @param ... grand-parent, grand-grand-parent and so on
visitor.binder = function(node, parent, ...)
local first, last = offsets(node)
match node with
| `Id{name} ->
-- Use general indexation
down(node, parent, ...)
--Process as a Declaration
if mark.is_declaration( node ) then
-- Create appropriate declaration type
local type = mark.declaration_type (node) or 'undef'
local init = mark.declaration_initialization( node )
local name, iStart, iEnd = node[1]
if init then iStart, iEnd = offsets(init) end
if type == 'Function' then
-- Function Declaration
object[ node ] = DLTK.FunctionDeclaration(name, first, last, iStart, iEnd, mark.declaration_scope(node))
elseif type == 'Table' then
-- Table Declaration
object[ node ] = DLTK.TableDeclaration(name, first, last, iStart, iEnd, mark.declaration_scope(node))
elseif type == 'Nil' or type == 'Number' or type == 'String' or type == 'Boolean' then
-- Scalar Declaration
object[ node ] = DLTK.ScalarVariableDeclaration(name, first, last, iStart, iEnd, mark.declaration_scope(node))
else
-- Default Declaration
object[ node ] = DLTK.VariableDeclaration(name, first, last, mark.declaration_scope(node))
end
-- Register node for backpatch as Java object for parameters and body are still not available
table.insert(declaration, node)
else
-- Create Java Identifier
object[ node ] = DLTK.Identifier(first, last, name)
end
| `Dots ->
object[ node ] = DLTK.Dots(first, last)
| _ ->
-- print('In binder missing type `'..(node.tag))
-- table.print(node, 'nohash',1)
end
end
visitor.Dots = visitor.binder
visitor.Id = visitor.binder
---
-- Instanciates a Java object in <code>object</code> from the given node.
--
-- @param node Metalua node which will have its Java Object image stored in <code>object</code>
-- @param parent Given node parent
-- @param ... grand-parent, grand-grand-parent and so on
visitor.block.up =function(node, parent, ...)
local first, last = offsets(node)
match node with
| { tag=nil, ... } -> -- Dealing with Chunks
local chunk = DLTK.Chunk(first, last)
-- Append childern Java objects
if hash[node] then
for index, child in pairs(hash[node]) do
DLTK.appendStatementToChunk(chunk, object[child])
end
end
object[ node ] = chunk
| `Do { ... } ->
object[ node ] = DLTK.Do(first, last, statListToChunk(node))
| _ ->
-- print('In block missing type `'..(node.tag))
-- table.print(node, 'nohash',1)
end
end
---
-- Instanciates a Java object in <code>object</code> from the given node.
--
-- @param node Metalua node which will have its Java Object image stored in <code>object</code>
-- @param ... parent, grand-parent, grand-grand-parent and so on
visitor.stat.up = function (node, ...)
local first, last = offsets(node)
match node with
| `Set {left, right} ->
object[ node ] = DLTK.Set(first, last, statListToChunk(left), statListToChunk(right))
| `While {expr, block} ->
object[ node ] = DLTK.While(first, last, object[expr], object[block])
| `Repeat {block, expr} ->
object[ node ] = DLTK.Repeat(first, last, object[block], object[expr])
| `If { expr , block} ->
object[ node ] = DLTK.If(first, last, object[expr], object[block])
| `If { expr , block, alt} ->
object[ node ] = DLTK.If(first, last, object[expr], object[block], object[alt])
| `If { expr , block, ... } ->
local nodeSize = select("#", ...)
if (nodeSize % 2) == 0 then
object[ node ] = DLTK.ElseIf(first, last, object[expr], object[block])
else
local elseBlock = select(nodeSize, node)
object[ node ] = DLTK.ElseIf(first, last, object[expr], object[block], object[elseBlock])
end
for k =2,nodeSize,2 do
local cond, chunk = select(k-1, ...)
DLTK.addExpressionAndRelatedChunk(object[ node ], object[cond], object[chunk])
end
| `Fornum {identifier, min, max, range, block} ->
object[ node ] = DLTK.ForNumeric(first, last, object[identifier], object[min], object[max], object[range], object[block])
| `Fornum {identifier, min, max, block} ->
object[ node ] = DLTK.ForNumeric(first, last, object[identifier], object[min], object[max], object[block])
| `Forin {identifiers, exprs, block} ->
object[ node ] = DLTK.ForInPair(first, last, statListToChunk(identifiers), statListToChunk(exprs), object[block])
| `Local {identifiers} ->
object[ node ] = DLTK.Local(first, last, statListToChunk(identifiers))
| `Local {identifiers, inits} ->
object[ node ] = DLTK.Local(first, last, statListToChunk(identifiers), statListToChunk(inits))
| `Localrec {identifiers} ->
object[ node ] = DLTK.LocalRec(first, last, statListToChunk(identifiers))
| `Localrec {identifiers, inits} ->
object[ node ] = DLTK.LocalRec(first, last, statListToChunk(identifiers), statListToChunk(inits))
| `Break ->
object[ node ] = DLTK.Break(first, last)
| `Return {...} ->
object[ node ] = DLTK.Return(first, last)
-- Back patch children
for k,child in ipairs( node ) do
DLTK.addReturnValue(object[ node ], object[child] )
end
| `Call{...} | `Invoke{...} ->
apply(node, ...)
| _ ->
-- print('In stat.up missing type `'..(node.tag))
-- table.print(node, 'nohash',1)
end
end
---
-- Instanciates a Java object in <code>object</code> from the given node.
--
-- @param node Metalua node which will have its Java Object image stored in <code>object</code>
-- @param ... parent, grand-parent, grand-grand-parent and so on
visitor.expr.up = function (node, ...)
local first, last = offsets(node)
match node with
| `Function {param, block} ->
object[ node ] = DLTK.Function(first, last, statListToChunk( param ), object[block])
| `Nil ->
object[ node ] = DLTK.Nil(first, last)
| `True | `False ->
object[ node ] = DLTK.Boolean(first, last, node.tag == "True")
| `Number{number} ->
object[ node ] = DLTK.Number(first, last, number)
| `String{string} ->
object[ node ] = DLTK.String(first, last, string)
| `Pair { expr, sexpr } ->
object[ node ] = DLTK.Pair(first, last, object[expr], object[sexpr])
| `Table { ... } ->
object[ node ] = DLTK.Table(first, last)
-- Backpatch table content at initialisation
for k, child in ipairs( node ) do
DLTK.addStatement(object[ node ], object[child])
end
| `Op { operator, left, right} ->
object[ node ] = DLTK.BinaryExpression(first, last,object[left], operator, object[right])
| `Op { operator, expr } ->
object[ node ] = DLTK.UnaryExpression(first, last, operator, object[expr])
| `Paren{ expr } ->
object[ node ] = DLTK.Parenthesis(first, last, object[expr])
| `Id { name } ->
object[ node ] = DLTK.Identifier(first, last, name)
| `Index { expr, sexpr } ->
object[ node ] = DLTK.Index(object[expr], object[sexpr])
| `Call{...} | `Invoke{...} ->
apply(node, ...)
| `Id {name} ->
visitor.binder(node, ...)
| `Dots ->
visitor.binder(node, ...)
| _ ->
-- print('In expr.up missing type `'..(node.tag))
-- table.print(node, 'nohash',1)
end
end
---
-- Back patch node missing Java objects at their construction, uses global table <code>declaration</code>
local function backpatch()
for k, node in ipairs(declaration) do
local itype = type
local type = mark.declaration_type(node)
local init = mark.declaration_initialization(node)
local occurrences
if type == 'Function' then
local params, body = init[1], init[2]
-- Define function's parameters
DLTK.acceptArguments(object[node], statListToChunk(params))
-- Define function's body
DLTK.acceptBody(object[node], object[ body ])
elseif type == 'Nil' or type == 'Number' or type == 'String' or type == 'Boolean' then
DLTK.setInitialization(object[node], object[init])
end
--
-- Match node with its occurrences
--
local occ = mark.declaration_occurrences( node )
for i, occurrence in ipairs( occ ) do
-- Link occurrence to declaration
DLTK.setDeclaration(object[occurrence], object[node])
-- Add occurrence to declaration
DLTK.addOccurrence(object[node], object[occurrence])
end
end
end
---
-- Sets parent of DLTK ASTNode
--
-- @param node Metalua node
-- @param parent Metalua parent node
-- @param ... other nodes, not used
local parentMatcher = function ( node, parent , ... )
if parent then
DLTK.setParent( object[node], object[parent] )
end
end
---
-- This visitor enable to link child node to their parents
--
local parenthood = {
binder = parentMatcher,
block = { up = parentMatcher },
expr = { up = parentMatcher },
stat = { up = parentMatcher },
Id = parentMatcher,
Dots = parentMatcher
}
---
-- Build a Metalua AST from source code
--
-- @param source Code to parse
-- @return LuaModuleDeclaration, DLTK node, root of DLTK AST
module.ast_builder = function(source)
-- Build AST
require 'errnode'
local ast = getast( source )
local root = DLTK.LuaModuleDeclaration(#source, true)
if ast and ast.tag == 'Error' then
local line, column, offset = ast.lineinfo.first[1], ast.lineinfo.first[2], ast.lineinfo.first[3]
local errorMessage = ast[1] or 'Unable to determine error'
DLTK.setProblem(root, line, column, offset, errorMessage )
return root
end
-- Mark intersting nodes such as declaration
ast = mark.declaration( ast )
-- Walk through AST
walk.block(visitor, ast)
-- Backpatch partial nodes
backpatch()
-- Link nodes to their parents
walk.block(parenthood, ast)
-- Achieve DLTK Java Objects AST
DLTK.addStatementToModuleDeclaration(root, object[ast])
return root
end
return module