Licensing
diff --git a/checks.lua b/checks.lua new file mode 100644 index 0000000..c0c99ac --- /dev/null +++ b/checks.lua
@@ -0,0 +1,59 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-- Alternative implementation of checks() in Lua. Slower than +-- the C counterpart, but no compilation/porting concerns. + +checkers = { } + +local function check_one(expected, val) + if type(val)==expected then return true end + local mt = getmetatable(val) + if mt and mt.__type==expected then return true end + local f = checkers[expected] + if f and f(val) then return true end + return false +end + +local function check_many(name, expected, val) + if expected=='?' then return true + elseif expected=='!' then return (val~=nil) + elseif type(expected) ~= 'string' then + error 'strings expected by checks()' + elseif val==nil and expected :sub(1,1) == '?' then return true end + for one in expected :gmatch "[^|?]+" do + if check_one(one, val) then return true end + end + return false +end + +function checks(...) + for i, arg in ipairs{...} do + local name, val = debug.getlocal(2, i) + local success = check_many(name, arg, val) + if not success then + local fname = debug.getinfo(2, 'n').name + local fmt = "bad argument #%d to '%s' (%s expected, got %s)" + local msg = string.format(fmt, i, fname or "?", arg, type(val)) + error(msg, 3) + end + end +end + +return checks
diff --git a/compiler/Makefile b/compiler/Makefile deleted file mode 100644 index 2d9d88c..0000000 --- a/compiler/Makefile +++ /dev/null
@@ -1,67 +0,0 @@ -include ../config - -all: $(LIBRARIES) install metalua - -$(PLATFORM): all - -LUA_RUN = ../$(LUA_VM_DIR)/$(RUN) -LUA_COMPILE = ../$(LUA_VM_DIR)/$(COMPILE) - -LIBRARIES = \ - bytecode.luac \ - mlp.luac \ - mlc.luac - -# Library which compiles an AST into a bytecode string. -BYTECODE_LUA = \ - lopcodes.lua \ - lcode.lua \ - ldump.lua \ - compile.lua - -# Library which compiles source strings into AST -MLP_LUA = \ - lexer.lua \ - gg.lua \ - mlp_lexer.lua \ - mlp_misc.lua \ - mlp_table.lua \ - mlp_meta.lua \ - mlp_expr.lua \ - mlp_stat.lua \ - mlp_ext.lua - -metalua.luac: mlc.luac - -bytecode.luac: $(BYTECODE_LUA) - $(LUA_COMPILE) -o $@ $^ - -mlp.luac: $(MLP_LUA) - $(LUA_COMPILE) -o $@ $^ - -# Plain lua files compilation -%.luac: %.mlua bootstrap.lua mlp.luac bytecode.luac - $(LUA_RUN) bootstrap.lua $< - -# FIXME what's this?! some old stuff from when metalua files hadn't their own -# extensions? -# Metalua files compilation through the bootstrap compiler -%.luac: %.lua - $(LUA_COMPILE) -o $@ bootstrap $< - -# Compiler/interpreter -metalua: metalua.luac install-lib - $(LUA_RUN) metalua.luac --verbose --sharpbang '#!$(TARGET_BIN_PATH)/lua' --output metalua --file metalua.mlua - -install-lib: $(LIBRARIES) - mkdir -p $(TARGET_LUA_PATH)/metalua - cp $(LIBRARIES) $(TARGET_LUA_PATH)/metalua/ - -install: install-lib metalua - mkdir -p $(TARGET_BIN_PATH) - cp metalua $(TARGET_BIN_PATH)/ - -.PHONY: all install - -clean: - -rm *.luac metalua
diff --git a/compiler/mlc.mlua b/compiler/mlc.mlua deleted file mode 100644 index 86a64df..0000000 --- a/compiler/mlc.mlua +++ /dev/null
@@ -1,248 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -------------------------------------------------------------------------------- --- This module is written in a more hackish way than necessary, just --- because I can. Its core feature is to dynamically generate a --- function that converts from a source format to a destination --- format; these formats are the various ways to represent a piece of --- program, from the source file to the executable function. Legal --- formats are: --- --- * luafile: the name of a file containing sources. --- * luastring: these sources as a single string. --- * lexstream: a stream of lexemes. --- * ast: an abstract syntax tree. --- * proto: a (Yueliang) struture containing a high level --- representation of bytecode. Largely based on the --- Proto structure in Lua's VM. --- * luacstring: a string dump of the function, as taken by --- loadstring() and produced by string.dump(). --- * function: an executable lua function in RAM. --- -------------------------------------------------------------------------------- - -require 'metalua.bytecode' -require 'metalua.mlp' - -mlc = { } -setmetatable(mlc, mlc) -mlc.metabugs = false - --------------------------------------------------------------------------------- --- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can --- be transformed into a 'b' (but not the other way around). --- mlc.sequence goes for numbers to format names, mlc.order goes from format --- names to numbers. --------------------------------------------------------------------------------- -mlc.sequence = { - 'luafile', 'luastring', 'lexstream', 'ast', 'proto', - 'luacstring', 'function' } -mlc.order = table.transpose(mlc.sequence) - --- Check whether a structure of nested tables is a valid AST. --- Currently thows an error if it isn't. --- @return true When no error is found in given ast --- @return false, error string --- @return false, error string, positions Provide position of error in --- given ast as a table. The position contains the following keys --- * column: number --- * line : number --- * offset: number --- TODO: build a detailed error location, with the lineinfo of every nested node. -local function check_ast(kind, ast) - if not ast then return check_ast('block', kind) end - assert(type(ast)=='table', "wrong AST type") - local cfg = {} - local function error2ast(error_node, ...) - if not error_node.stuffing then - if error_node.tag=='Error' then - cfg.errorfound = true - cfg.errormsg = error_node[1] - - -- Try to extract error position in source - local li = error_node.lineinfo and error_node.lineinfo.first - - -- Fill positions if undefined or not narrow enough - if li and ( not cfg.positions or cfg.positions.offset < li.offset ) then - cfg.positions = { - column = li.column, - line = li.line, - offset = li.offset - } - end - else - -- This block is for dealing with errors which are not error - -- nodes. It would be soooo nice to get rid of it. - -- TODO: Try to remove this bug when issue #20 is fixed - local li - for _, n in ipairs{ error_node, ... } do - if n.lineinfo then - li = n.lineinfo - cfg.errorfound = true - break - end - end - local posmsg - if li then - local column = li.first.column - local line = li.first.line - local offset = li.first.offset - posmsg = string.format("line %d, char %d, offset %d", - line, column, offset) - cfg.positions = { - column = column, - line = line, - offset = offset - } - else - posmsg = "unknown source position" - end - local msg = "Invalid node ".. - (error_node.tag and "tag "..tostring(error_node.tag) or "without tag").. - (posmsg and " at "..posmsg or "") - cfg.errormsg = msg - end - end - end - local f = require 'metalua.treequery.walk' [kind] - cfg.malformed=error2ast - cfg.unknown= error2ast - cfg.error= error2ast - f(cfg, ast) - return cfg.errorfound == nil, cfg.errormsg, cfg.positions -end - -mlc.check_ast = check_ast - -function mlc.luafile_to_luastring(x, name) - name = name or '@'..x - local f, msg = io.open (x, 'rb') - if not f then return f, msg end - local r = f :read '*a' - f :close() - return r, name -end - -function mlc.luastring_to_lexstream(src, name) - local r = mlp.lexer :newstream (src, name) - return r, name -end - -function mlc.lexstream_to_ast(lx, name) - if PRINT_PARSED_STAT then - print("About to parse a lexstream, starting with "..tostring(lx:peek())) - end - local r = mlp.chunk(lx) - r.source = name - return r, name -end - -function mlc.ast_to_proto(ast, name) - name = name or ast.source - return bytecode.metalua_compile(ast, name), name -end - -function mlc.proto_to_luacstring(proto, name) - return bytecode.dump_string(proto), name -end - -function mlc.luacstring_to_function(bc, name) - return string.undump(bc, name) -end - --- Create all sensible combinations -for i=1,#mlc.sequence do - for j=i+2, #mlc.sequence do - local dst_name = mlc.sequence[i].."_to_"..mlc.sequence[j] - local functions = { } - --local n = { } - for k=i, j-1 do - local name = mlc.sequence[k].."_to_"..mlc.sequence[k+1] - local f = assert(mlc[name]) - table.insert (functions, f) - --table.insert(n, name) - end - mlc[dst_name] = function(a, b) - for _, f in ipairs(functions) do - a, b = f(a, b) - end - return a, b - end - --printf("Created mlc.%s out of %s", dst_name, table.concat(n, ', ')) - end -end - - --------------------------------------------------------------------------------- --- This case isn't handled by the __index method, as it goes "in the wrong direction" --------------------------------------------------------------------------------- -mlc.function_to_luacstring = string.dump - --------------------------------------------------------------------------------- --- These are drop-in replacement for loadfile() and loadstring(). The --- C functions will call them instead of the original versions if --- they're referenced in the registry. --------------------------------------------------------------------------------- - -lua_loadstring = loadstring -local lua_loadstring = loadstring -lua_loadfile = loadfile -local lua_loadfile = loadfile - -function loadstring(str, name) - if type(str) ~= 'string' then error 'string expected' end - if str:match '^\027LuaQ' then return lua_loadstring(str) end - local n = str:match '^#![^\n]*\n()' - if n then str=str:sub(n, -1) end - -- FIXME: handle erroneous returns (return nil + error msg) - local success, f = pcall (mlc.luastring_to_function, str, name) - if success then return f else return nil, f end -end - -function loadfile(filename) - local f, err_msg = io.open(filename, 'rb') - if not f then return nil, err_msg end - local success, src = pcall( f.read, f, '*a') - pcall(f.close, f) - if success then return loadstring (src, '@'..filename) - else return nil, src end -end - -function load(f, name) - while true do - local x = f() - if not x then break end - assert(type(x)=='string', "function passed to load() must return strings") - table.insert(acc, x) - end - return loadstring(table.concat(x)) -end - -function dostring(src) - local f, msg = loadstring(src) - if not f then error(msg) end - return f() -end - -function dofile(name) - local f, msg = loadfile(name) - if not f then error(msg) end - return f() -end
diff --git a/compiler/mlp_expr.lua b/compiler/mlp_expr.lua deleted file mode 100644 index 0c7949c..0000000 --- a/compiler/mlp_expr.lua +++ /dev/null
@@ -1,194 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- --- Exported API: --- * [mlp.expr()] --- * [mlp.expr_list()] --- * [mlp.func_val()] --- --------------------------------------------------------------------------------- - ---require "gg" ---require "mlp_misc" ---require "mlp_table" ---require "mlp_meta" - --------------------------------------------------------------------------------- --- These function wrappers (eta-expansions actually) are just here to break --- some circular dependencies between mlp_xxx.lua files. --------------------------------------------------------------------------------- -local function _expr (lx) return mlp.expr (lx) end -local function block (lx) return mlp.block (lx) end -local function stat (lx) return mlp.stat (lx) end - -local function _table_content (lx) return mlp.table_content (lx) end - -module ("mlp", package.seeall) - --------------------------------------------------------------------------------- --- Non-empty expression list. Actually, this isn't used here, but that's --- handy to give to users. --------------------------------------------------------------------------------- -expr_list = gg.list{ _expr, separators = "," } - --------------------------------------------------------------------------------- --- Helpers for function applications / method applications --------------------------------------------------------------------------------- -func_args_content = gg.list { - name = "function arguments", - _expr, separators = ",", terminators = ")" } - --- Used to parse methods -method_args = gg.multisequence{ - name = "function argument(s)", - { "{", table_content, "}" }, - { "(", func_args_content, ")", builder = fget(1) }, - { "+{", quote_content, "}" }, - function(lx) local r = opt_string(lx); return r and {r} or { } end } - --------------------------------------------------------------------------------- --- [func_val] parses a function, from opening parameters parenthese to --- "end" keyword included. Used for anonymous functions as well as --- function declaration statements (both local and global). --- --- It's wrapped in a [_func_val] eta expansion, so that when expr --- parser uses the latter, they will notice updates of [func_val] --- definitions. --------------------------------------------------------------------------------- -func_params_content = gg.list{ name="function parameters", - gg.multisequence{ { "...", builder = "Dots" }, id }, - separators = ",", terminators = {")", "|"} } - -local _func_params_content = function (lx) return func_params_content(lx) end - -func_val = gg.sequence { name="function body", - "(", func_params_content, ")", block, "end", builder = "Function" } - -local _func_val = function (lx) return func_val(lx) end - --------------------------------------------------------------------------------- --- Default parser for primary expressions --------------------------------------------------------------------------------- -function id_or_literal (lx) - local a = lx:next() - if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then - local msg - if a.tag=='Eof' then - msg = "End of file reached when an expression was expected" - elseif a.tag=='Keyword' then - msg = "An expression was expected, and `"..a[1].. - "' can't start an expression" - else - msg = "Unexpected expr token " .. _G.table.tostring (a, 'nohash') - end - return gg.parse_error (lx, msg) - end - return a -end - - --------------------------------------------------------------------------------- --- Builder generator for operators. Wouldn't be worth it if "|x|" notation --- were allowed, but then lua 5.1 wouldn't compile it --------------------------------------------------------------------------------- - --- opf1 = |op| |_,a| `Op{ op, a } -local function opf1 (op) return - function (_,a) return { tag="Op", op, a } end end - --- opf2 = |op| |a,_,b| `Op{ op, a, b } -local function opf2 (op) return - function (a,_,b) return { tag="Op", op, a, b } end end - --- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed) -local function opf2r (op) return - function (a,_,b) return { tag="Op", op, b, a } end end - -local function op_ne(a, _, b) - -- The first version guarantees to return the same code as Lua, - -- but it relies on the non-standard 'ne' operator, which has been - -- suppressed from the official AST grammar (although still supported - -- in practice by the compiler). - -- return { tag="Op", "ne", a, b } - return { tag="Op", "not", { tag="Op", "eq", a, b, lineinfo= { - first = a.lineinfo.first, last = b.lineinfo.last } } } -end - - --------------------------------------------------------------------------------- --- --- complete expression --- --------------------------------------------------------------------------------- - --- FIXME: set line number. In [expr] transformers probably - -expr = gg.expr { name = "expression", - - primary = gg.multisequence{ name="expr primary", - { "(", _expr, ")", builder = "Paren" }, - { "function", _func_val, builder = fget(1) }, - { "-{", splice_content, "}", builder = fget(1) }, - { "+{", quote_content, "}", builder = fget(1) }, - { "nil", builder = "Nil" }, - { "true", builder = "True" }, - { "false", builder = "False" }, - { "...", builder = "Dots" }, - table, - id_or_literal }, - - infix = { name="expr infix op", - { "+", prec = 60, builder = opf2 "add" }, - { "-", prec = 60, builder = opf2 "sub" }, - { "*", prec = 70, builder = opf2 "mul" }, - { "/", prec = 70, builder = opf2 "div" }, - { "%", prec = 70, builder = opf2 "mod" }, - { "^", prec = 90, builder = opf2 "pow", assoc = "right" }, - { "..", prec = 40, builder = opf2 "concat", assoc = "right" }, - { "==", prec = 30, builder = opf2 "eq" }, - { "~=", prec = 30, builder = op_ne }, - { "<", prec = 30, builder = opf2 "lt" }, - { "<=", prec = 30, builder = opf2 "le" }, - { ">", prec = 30, builder = opf2r "lt" }, - { ">=", prec = 30, builder = opf2r "le" }, - { "and",prec = 20, builder = opf2 "and" }, - { "or", prec = 10, builder = opf2 "or" } }, - - prefix = { name="expr prefix op", - { "not", prec = 80, builder = opf1 "not" }, - { "#", prec = 80, builder = opf1 "len" }, - { "-", prec = 80, builder = opf1 "unm" } }, - - suffix = { name="expr suffix op", - { "[", _expr, "]", builder = function (tab, idx) - return {tag="Index", tab, idx[1]} end}, - { ".", id, builder = function (tab, field) - return {tag="Index", tab, id2string(field[1])} end }, - { "(", func_args_content, ")", builder = function(f, args) - return {tag="Call", f, unpack(args[1])} end }, - { "{", _table_content, "}", builder = function (f, arg) - return {tag="Call", f, arg[1]} end}, - { ":", id, method_args, builder = function (obj, post) - return {tag="Invoke", obj, id2string(post[1]), unpack(post[2])} end}, - { "+{", quote_content, "}", builder = function (f, arg) - return {tag="Call", f, arg[1] } end }, - default = { name="opt_string_arg", parse = mlp.opt_string, builder = function(f, arg) - return {tag="Call", f, arg } end } } }
diff --git a/lib/errnode.lua b/lib/errnode.lua deleted file mode 100644 index 1680c48..0000000 --- a/lib/errnode.lua +++ /dev/null
@@ -1,38 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Sierra Wireless and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Kevin Kin-Foo - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.compiler' --- --- Ecapsulates funcion mlc.luastring_to_ast in order to protect call and parse --- error string when an error occurs. --- --- @param src string containg Lua code to evaluate --- @return AST of table type, as returned by mlc.luastring_to_ast. Contains an --- error when AST generation fails --- -function getast(src) - local status, result = pcall(mlc.luastring_to_ast, src) - if status then return result else - local line, column, offset = result:match '%(l.(%d+), c.(%d+), k.(%d+)%)' - local filename = result :match '^([^:]+)' - local msg = result :match 'line %d+, char %d+: (.-)\n' - local li = {line, column, offset, filename} - return {tag='Error', lineinfo={first=li, last=li}, msg} - end -end
diff --git a/lib/metalua/compiler.lua b/lib/metalua/compiler.lua deleted file mode 100644 index 030b79b..0000000 --- a/lib/metalua/compiler.lua +++ /dev/null
@@ -1,22 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.runtime' -require 'metalua.mlc' -require 'metalua.package2'
diff --git a/lib/metalua/dollar.mlua b/lib/metalua/dollar.mlua deleted file mode 100644 index c4f324e..0000000 --- a/lib/metalua/dollar.mlua +++ /dev/null
@@ -1,43 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- TODO: support modules as macros? --- does it make sense to store a constant AST as a macro? - --{ extension 'match' } - -dollar = rawget(getfenv(), 'dollar') or { } - -local function dollar_builder(call) - match call with - | `Call{ `Id{name}, ... } -> return dollar[name](select(2, unpack(call))) - | `Id{name} -> - local m = dollar[name] - match type(m) with - | 'function' -> return m() - | 'table' -> return m - | 'nil' -> error "No such macro registered" - | t -> error ("Invalid macro type "..t) - end - | _ -> error "Invalid $macro, '$' should be followed by an identifier or function call" - end -end - -mlp.expr.prefix:add{ '$', prec = 100, builder = |_, x| dollar_builder(x) } -mlp.stat:add{ '$', mlp.expr, builder = |x| dollar_builder(x[1]) }
diff --git a/lib/metalua/extension/H-runtime.mlua b/lib/metalua/extension/H-runtime.mlua deleted file mode 100644 index 63ac784..0000000 --- a/lib/metalua/extension/H-runtime.mlua +++ /dev/null
@@ -1,235 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.walk.id' --{ extension 'log' } - --------------------------------------------------------------------------------- --- --- H params: --- * H.alpha is the `Local{ } (or `Set{ }) statement which will --- receive the alpha-conversions required to restore the free --- variables of the transformed term. For instance, --- H+{print(1)} will be transformed into +{.1.X.print(1)}, --- and alpha will contain +{local -{`Id '.1.X.print} = print }. --- alpha is reused and augmented by successive calls to H(). --- --- * H.side contains 'inside', 'outside', 'both' or nil (equivalent to --- 'both'). It indicates the kind of hygienization that's to be --- performed. --- --- * H.keep contain a set of free variable names which must not be --- renamed. --- --- * H.kind is the kind of walker that must be used ('expr', 'stat', --- 'block'...) and defaults to 'guess'. --- --- * H:set (field, val) sets a field in H and returns H, so that calls --- can be chained, e.g.: --- > H:set(keep, {'print'}):set('side', outside)+{print(x)} --- --- * H:reset(field) sets a field to nil, and returns the value of that --- field prior to nilification. --------------------------------------------------------------------------------- - -H = { } --setmetatable(H, H) -H.__index=H -H.template = { alpha = { } } - --------------------------------------------------------------------------------- --- --------------------------------------------------------------------------------- -function H:new(x) - local instance = table.deep_copy(self.template) - if x then instance <- x end - setmetatable(instance, self) - return instance -end - --------------------------------------------------------------------------------- --- --------------------------------------------------------------------------------- -function H:__call (ast) - assert (type(ast)=='table', "H expects an AST") - - local local_renames -- only set if inside hygienization's required - - ----------------------------------------------------------------------------- - -- kind of hygienization(s) to perform: h_inseide and/or h_outside - ----------------------------------------------------------------------------- - local h_inside, h_outside do - local side = self.side or 'both' - h_inside = side=='inside' or side=='both' - h_outside = side=='outside' or side=='both' - end - - ----------------------------------------------------------------------------- - -- Initialize self.keep: - -- self.keep is a dictionary of free var names to be protected from capture - ----------------------------------------------------------------------------- - do - local k = self.keep - -- If there's no self.keep, that's an empty dictionary - if not k then k = { }; self.keep = k - -- If it's a string, consider it as a single-element dictionary - elseif type(k)=='string' then k = { [k] = true }; self.keep=k - -- If there's a list-part in self.keep, transpose it: - else for i, v in ipairs(k) do k[v], k[i] = true, nil end end - end - - ----------------------------------------------------------------------------- - -- Config skeleton for the id walker - ----------------------------------------------------------------------------- - local cfg = { expr = { }, stat = { }, id = { } } - - ----------------------------------------------------------------------------- - -- Outside hygienization: all free variables are renamed to fresh ones, - -- and self.alpha is updated to contain the assignments required to keep - -- the AST's semantics. - ----------------------------------------------------------------------------- - if h_outside then - local alpha = self.alpha - - -- free_vars is an old_name -> new_name dictionary computed from alpha: - -- self.alpha is not an efficient representation for searching. - if not alpha then alpha = { }; self.alpha = alpha end - -- FIXME: alpha should only be overridden when there actually are some - -- globals renamed. - if #alpha==0 then alpha <- `Local{ { }, { } } end - local new, old = unpack(alpha) - local free_vars = { } - - assert (#new==#old, "Invalid alpha list") - for i = 1, #new do - assert (old[i].tag=='Id' and #old[i]==1, "Invalid lhs in alpha list") - assert (new[i].tag=='Id' and #new[i]==1, "Invalid rhs in alpha list") - free_vars[old[i][1]] = new[i][1] - end - - -- Rename free variables that are not supposed to be captured. - function cfg.id.free (id) - local old_name = id[1] - if self.keep[old_name] then return end - local new_name = free_vars[old_name] - if not new_name then - new_name = mlp.gensym('X.'..old_name)[1] - free_vars[old_name] = new_name - table.insert(alpha[1], `Id{new_name}) - table.insert(alpha[2], `Id{old_name}) - end - id[1] = new_name - end - end - - ----------------------------------------------------------------------------- - -- Inside hygienization: rename all local variables and their ocurrences. - ----------------------------------------------------------------------------- - if h_inside then - - ---------------------------------------------------------------- - -- Renamings can't performed on-the-spot, as it would - -- transiently break the link between binders and bound vars, - -- thus preventing the algo to work. They're therefore stored - -- in local_renames, and performed after the whole tree has been - -- walked. - ---------------------------------------------------------------- - - local_renames = { } -- `Id{ old_name } -> new_name - local bound_vars = { } -- binding statement -> old_name -> new_name - - ---------------------------------------------------------------- - -- Give a new name to newly created local vars, store it in - -- bound_vars - ---------------------------------------------------------------- - function cfg.binder (id, binder) - if id.h_boundary then return end - local old_name = id[1] - local binder_table = bound_vars[binder] - if not binder_table then - binder_table = { } - bound_vars[binder] = binder_table - end - local new_name = mlp.gensym('L.'..old_name)[1] - binder_table[old_name] = new_name - local_renames[id] = new_name - end - - ---------------------------------------------------------------- - -- List a bound var for renaming. The new name has already been - -- chosen and put in bound_vars by cfg.binder(). - ---------------------------------------------------------------- - function cfg.id.bound (id, binder) - if id.h_boundary then return end - local old_name = id[1] - local new_name = bound_vars[binder][old_name] - --.log(bound_vars[binder]) - assert(new_name, "no alpha conversion for a bound var?!") - local_renames[id] = new_name - end - end - - ----------------------------------------------------------------------------- - -- Don't traverse subtrees marked by '!' - ----------------------------------------------------------------------------- - local cut_boundaries = |x| x.h_boundary and 'break' or nil - cfg.stat.down, cfg.expr.down = cut_boundaries, cut_boundaries - - ----------------------------------------------------------------------------- - -- The walker's config is ready, let's go. - -- After that, ids are renamed in ast, free_vars and bound_vars are set. - ----------------------------------------------------------------------------- - walk_id [self.kind or 'guess'] (cfg, ast) - - if h_inside then -- Apply local name changes - for id, new_name in pairs(local_renames) do id[1] = new_name end - end - - return ast -end - --------------------------------------------------------------------------------- --- Return H to allow call chainings --------------------------------------------------------------------------------- -function H:set(field, val) - local t = type(field) - if t=='string' then self[field]=val - elseif t=='table' then self <- field - else error("Can't set H, field arg can't be of type "..t) end - return self -end - --------------------------------------------------------------------------------- --- Return the value before reset --------------------------------------------------------------------------------- -function H:reset(field) - if type(field) ~= 'string' then error "Can only reset H string fields" end - local r = H[field] - H[field] = nil - return r -end - --- local function commit_locals_to_chunk(x) --- local alpha = H:reset 'alpha' --- --$log ('commit locals', x, alpha, 'nohash') --- if not alpha or not alpha[1][1] then return end --- if not x then return alpha end --- table.insert(x, 1, alpha) --- end - --- mlp.chunk.transformers:add (commit_locals_to_chunk)
diff --git a/lib/metalua/extension/H.mlua b/lib/metalua/extension/H.mlua deleted file mode 100644 index 0a63f6e..0000000 --- a/lib/metalua/extension/H.mlua +++ /dev/null
@@ -1,41 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.walk.id' --{ extension 'log' } - -mlp.expr.prefix:add{ '!', prec = 5, - builder = function(_,x) - local v = mlp.gensym() - return `Stat{ +{ block: - local -{v} = -{x}; - (-{v}).h_boundary=true }, - v } - end } - -mlp.stat:add{ '!', mlp.expr, builder = |x| +{stat: (-{x[1]}).h_boundary=true } } - --- * if there's no boundary in it, is there a need to rename vars? --- ==> first pass to mark binders which contain boundaries, --- then 2nd pass only touched those which have a splice --- in them. - -return +{ require (-{ `String{ package.metalua_extension_prefix .. 'H-runtime' } }) } - -
diff --git a/lib/metalua/extension/anaphoric.mlua b/lib/metalua/extension/anaphoric.mlua deleted file mode 100644 index aacb0d4..0000000 --- a/lib/metalua/extension/anaphoric.mlua +++ /dev/null
@@ -1,73 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- --- Anaphoric macros. --- --- This extension turns 'it' into a special variable, that's bound to --- an often used value: --- --- * in an 'if' statement, 'it' is bound, in a block, to the condition --- that triggered the block's execution: --- > if 1234 then y=it end; assert (y == 1234) --- --- * in a while loop, it's bound to the test: --- > while file:read "*a" do table.insert (lines, it) end --- --- 'it' is bound the the most closely surrounding structure. If you wanted to --- use its content at a deeper position in the AST, you would have to save it --- in a temporary variable. But what you should really do in such a case is --- avoiding to use anaphoric macros: they're fine for one-liner, but they --- reduce readability for bigger functions. --------------------------------------------------------------------------------- - --- TODO: 'and' operator could, and maybe should, be anaphoric as well --- TODO: anaphoric functions would be cool for recursive functions, but --- recursive calls are always in an 'if' statement, so the pronoun --- used for functions must not be the same as for 'if'. - -require 'freevars' - -local function anaphoric_if(ast) - local it_found = false - for i=2, #ast do - if freevars.block(ast[i])['it'] then - it_found = true - break - end - end - if it_found then - local cond = ast[1] - ast[1] = +{it} - return +{stat: do local it = -{cond}; -{ast} end } - end -end - -local function anaphoric_while(ast) - local it_found = false - if freevars.block(ast[2])['it'] then - local cond = ast[1] - ast[1] = +{it} - return +{stat: do local it = -{cond}; -{ast} end } - end -end - -mlp.stat:get'if'.transformers:add(anaphoric_if) -mlp.stat:get'while'.transformers:add(anaphoric_while) \ No newline at end of file
diff --git a/lib/metalua/extension/continue.mlua b/lib/metalua/extension/continue.mlua deleted file mode 100644 index fbd0f87..0000000 --- a/lib/metalua/extension/continue.mlua +++ /dev/null
@@ -1,72 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require "metalua.walk" - ----------------------------------------------------------------------- --- * [loop_tags] are the tags of statements which support continue. --- * [loop_keywords] are the initial keywords which trigger the parsing --- of these statements: they're indeed indexed by keyword in [mlp.stat]. ----------------------------------------------------------------------- - -local loop_tags = table.transpose{ "Forin", "Fornum", "While", "Repeat" } -local loop_keywords = { "for", "while", "repeat" } - ----------------------------------------------------------------------- --- This function takes the AST of a continue-enabled loop, parse --- its body to find all instances of [`Continue]. If any of them --- is found ([label~=nil]), they're transformed in [`Goto{...}], and --- the corresponding label is added at the end of the loop's body. --- --- Caveat: if a [continue] appears in the non-body part of a loop --- (and therefore is relative to some enclosing loop), it isn't --- handled, and therefore causes a compilation error. This could --- only happen due in a [`Stat{ }], however, since [`Function{ }] --- cuts the search for [`Continue]. ----------------------------------------------------------------------- -local function loop_transformer (ast) - local label - local cfg = { stat = { }; expr = { } } - - function cfg.stat.down (x) - if loop_tags[x.tag] then return 'break' - elseif x.tag=='Continue' then - if not label then label = mlp.gensym 'continue' end - x <- `Goto{ label } - end - end - - function cfg.expr.down (x) - return x.tag=='Function' and 'break' - end - - local loop_body = ast.tag=="Repeat" and ast[1] or ast[#ast] - walk.block (cfg, loop_body) - if label then table.insert (loop_body, `Label{ label }) end -end - ----------------------------------------------------------------------- --- Register the transformer for each kind of loop: ----------------------------------------------------------------------- -for keyword in values (loop_keywords) do - mlp.stat:get(keyword).transformers:add (loop_transformer) -end - -mlp.lexer:add "continue" -mlp.stat:add{ "continue", builder = ||`Continue }
diff --git a/lib/metalua/extension/localin.mlua b/lib/metalua/extension/localin.mlua deleted file mode 100644 index 8599a32..0000000 --- a/lib/metalua/extension/localin.mlua +++ /dev/null
@@ -1,21 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -mlp.expr:add{ "local", mlp.id, "=", mlp.expr, "in", mlp.expr, - builder=|x| `Stat{ { `Local{ { x[1] }, { x[2] } } }, x[3] } } \ No newline at end of file
diff --git a/lib/metalua/extension/ternary.mlua b/lib/metalua/extension/ternary.mlua deleted file mode 100644 index 42cf767..0000000 --- a/lib/metalua/extension/ternary.mlua +++ /dev/null
@@ -1,29 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -local function b(x, suffix) - local v, ontrue, onfalse = mlp.gensym "test", unpack (suffix) - return `Stat{ - +{ block: - local -{v} - if -{x} then (-{v}) = -{ontrue} else (-{v}) = -{onfalse or `Nil} end }, - v } -end - -mlp.expr.suffix:add{ "?", mlp.expr, gg.onkeyword{ ",", mlp.expr }, prec=5, builder=b }
diff --git a/lib/metalua/extension/trycatch.mlua b/lib/metalua/extension/trycatch.mlua deleted file mode 100644 index 3f190d5..0000000 --- a/lib/metalua/extension/trycatch.mlua +++ /dev/null
@@ -1,208 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'match' } - --------------------------------------------------------------------------------- --- --- TODO: --- --- * Hygienize calls to pcall() --- --------------------------------------------------------------------------------- - --{ extension 'H' } --{ extension 'log' } - --- Get match parsers and builder, for catch cases handling: -local match_alpha = require 'metalua.extension.match' -local H = H:new{side='inside', alpha = match_alpha } - --- We'll need to track rogue return statements: -require 'metalua.walk' - --- Put a block AST into a pcall(): -local mkpcall = |block| +{pcall(function() -{block} end)} - --- The statement builder: -function trycatch_builder(x) - --$log ("trycatch_builder", x, 'nohash', 60) - local try_code, catch_cases, finally_code = unpack(x) - local insert_return_catcher = false - - -- Can't be hygienize automatically by the current version of H, as - -- it must bridge from inside user code (hacjed return statements) - -- to outside macro code. - local caught_return = !mlp.gensym 'caught_return' - local saved_args - - !try_code; !(finally_code or { }) - -- FIXME: Am I sure there's no need to hygienize inside? - --[[if catch_cases then - for case in ivalues(catch_cases) do - --$log(case,'nohash') - local patterns, guard, block = unpack(case) - ! block - end - end]] - - - ---------------------------------------------------------------- - -- Returns in the try-block must be transformed: - -- from the user's PoV, the code in the try-block isn't - -- a function, therefore a return in it must not merely - -- end the execution of the try block, but: - -- * not cause any error to be caught; - -- * let the finally-block be executed; - -- * only then, let the enclosing function return with the - -- appropraite values. - -- The way to handle that is that any returned value is stored - -- into the runtime variable caught_return, then a return with - -- no value is sent, to stop the execution of the try-code. - -- - -- Similarly, a return in a catch case code must not prevent - -- the finally-code from being run. - -- - -- This walker catches return statements and perform the relevant - -- transformation into caught_return setting + empty return. - -- - -- There is an insert_return_catcher compile-time flag, which - -- allows to avoid inserting return-handling code in the result - -- when not needed. - ---------------------------------------------------------------- - local replace_returns_and_dots do - local function f(x) - match x with - | `Return{...} -> - insert_return_catcher = true - -- Setvar's 'caught_return' code can't be hygienize by H currently. - local setvar = `Set{ {caught_return}, { `Table{ unpack(x) } } } - x <- { setvar; `Return }; x.tag = nil; - --$log('transformed return stat:', x, 60) - return 'break' - | `Function{...} -> return 'break' - -- inside this, returns would be the nested function's, not ours. - | `Dots -> - if not saved_args then saved_args = mlp.gensym 'args' end - x <- `Call{ `Id 'unpack', saved_args } - | _ -> -- pass - end - end - local cfg = { stat = {down=f}, expr = {down=f} } - replace_returns_and_dots = |x| walk.block(cfg, x) - end - - -- parse returns in the try-block: - replace_returns_and_dots (try_code) - - -- code handling the error catching process: - local catch_result do - if catch_cases and #catch_cases>0 then - ---------------------------------------------------------- - -- Protect catch code against failures: they run in a pcall(), and - -- the result is kept in catch_* vars so that it can be used to - -- relaunch the error after the finally code has been executed. - ---------------------------------------------------------- - for x in ivalues (catch_cases) do - local case_code = x[3] - -- handle rogue returns: - replace_returns_and_dots (case_code) - -- in case of error in the catch, we still need to run "finally": - x[3] = +{block: catch_success, catch_error = -{mkpcall(case_code)}} - end - ---------------------------------------------------------- - -- Uncaught exceptions must not cause a mismatch, - -- so we introduce a catch-all do-nothing last case: - ---------------------------------------------------------- - table.insert (catch_cases, { { { `Id '_' } }, false, { } }) - catch_result = spmatch.match_builder{ {+{user_error}}, catch_cases } - else - catch_result = { } - end - end - - ---------------------------------------------------------------- - -- Build the bits of code that will handle return statements - -- in the user code (try-block and catch-blocks). - ---------------------------------------------------------------- - local caught_return_init, caught_return_rethrow do - if insert_return_catcher then - caught_return_init = `Local{{caught_return}} - caught_return_rethrow = - +{stat: if -{caught_return} then return unpack(-{caught_return}) end} - else - caught_return_init, caught_return_rethrow = { }, { } - end - end - - local saved_args_init = - saved_args and `Local{ {saved_args}, { `Table{`Dots} } } or { } - - -- The finally code, to execute no matter what: - local finally_result = finally_code or { } - - -- And the whole statement, gluing all taht together: - local result = +{stat: - do - -{ saved_args_init } - -{ caught_return_init } - local user_success, user_error = -{mkpcall(try_code)} - local catch_success, catch_error = false, user_error - if not user_success then -{catch_result} end - -{finally_result} - if not user_success and not catch_success then error(catch_error) end - -{ caught_return_rethrow } - end } - - H(result) - - return result -end - -function catch_case_builder(x) - --$log ("catch_case_builder", x, 'nohash', 60) - local patterns, guard, _, code = unpack(x) - -- patterns ought to be a pattern_group, but each expression must - -- be converted into a single-element pattern_seq. - for i = 1, #patterns do patterns[i] = {patterns[i]} end - return { patterns, guard, code } -end - -mlp.lexer:add{ 'try', 'catch', 'finally', '->' } -mlp.block.terminators:add{ 'catch', 'finally' } - -mlp.stat:add{ - 'try', - mlp.block, - gg.onkeyword{ 'catch', - gg.list{ - gg.sequence{ - mlp.expr_list, - gg.onkeyword{ 'if', mlp.expr }, - gg.optkeyword 'then', - mlp.block, - builder = catch_case_builder }, - separators = 'catch' } }, - gg.onkeyword{ 'finally', mlp.block }, - 'end', - builder = trycatch_builder } - -return H.alpha - -
diff --git a/lib/metalua/extension/types-runtime.mlua b/lib/metalua/extension/types-runtime.mlua deleted file mode 100644 index 1f3e582..0000000 --- a/lib/metalua/extension/types-runtime.mlua +++ /dev/null
@@ -1,178 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- Initialize the types table. It has an __index metatable entry, --- so that if a symbol is not found in it, it is looked for in the current --- environment. It allows to write things like [ n=3; x :: vector(n) ]. --------------------------------------------------------------------------------- -types = { } -setmetatable (types, { __index = getfenv(0)}) - -function types.error (fmt, ...) - error(string.format("Runtime type-checking failure: "..fmt, ...)) -end - --------------------------------------------------------------------------------- --- Add a prefix to an error message, if an error occurs. --- Useful for type checkers that call sub-type-checkers. --------------------------------------------------------------------------------- -local function nest_error (prefix, ...) - local status, msg = pcall(...) - if not status then types.error("%s:\n%s", prefix, msg) end -end - --------------------------------------------------------------------------------- --- Built-in types --------------------------------------------------------------------------------- -for typename in values{ "number", "string", "boolean", "function", "thread" } do - types[typename] = - function (val) - if type(val) ~= typename then types.error ("%s expected", typename) end - end -end - -function types.integer(val) - if type(val)~='number' or val%1~=0 then types.error 'integer expected' end -end - --------------------------------------------------------------------------------- --- table(foo) checks --- table(foo, bar) checks --- table(i) where i is an integer checks --- table(i, j) where i and j are integers checks --- Integers and key/value types can be combined --------------------------------------------------------------------------------- -function types.table (...) - - local key_type, val_type, range_from, range_to - -- arguments parsing - for x in values{...} do - if type(x) == "number" then - if range2 then types.error "Invalid type: too many numbers in table type" - elseif range1 then range2 = x - else range1 = x end - else - if type_key then types.error "Invalid type: too many types" - elseif type_val then type_key, type_val = type_val, x - else type_val = x end - end - end - if not range2 then range2=range1 end - if not type_key then type_key = types.integer end - return function (val) - if type(val) ~= "table" then types.error "table expected" end - local s = #val - if range2 and range2 > s then types.error "Not enough table elements" end - if range1 and range1 < s then types.error "Too many elements table elements" end - for k,v in pairs(val) do - nest_error ("in table key", type_key, k) - nest_error ("in table value", type_val, v) - end - end -end - --------------------------------------------------------------------------------- --- [list (subtype)] checks that the term is a table, and all of its --- integer-indexed elements are of type [subtype]. --------------------------------------------------------------------------------- -types.list = |...| types.table (types.integer, ...) - --------------------------------------------------------------------------------- --- Check that [x] is an integral number --------------------------------------------------------------------------------- -function types.int (x) - if type(x)~="number" or x%1~=0 then types.error "Integer number expected" end -end - --------------------------------------------------------------------------------- --- [range(a,b)] checks that number [val] is between [a] and [b]. [a] and [b] --- can be omitted. --------------------------------------------------------------------------------- -function types.range (a,b) - return function (val) - if type(val)~="number" or a and val<a or b and val>b then - types.error ("Number between %s and %s expected", - a and tostring(a) or "-infty", - b and tostring(b) or "+infty") - end - end -end - --------------------------------------------------------------------------------- --- [inter (x, y)] checks that the term has both types [x] and [y]. --------------------------------------------------------------------------------- -function types.inter (...) - local args={...} - return function(val) - for t in values(args) do nest_error ("in inter type", t, args) end - end -end - --------------------------------------------------------------------------------- --- [inter (x, y)] checks that the term has type either [x] or [y]. --------------------------------------------------------------------------------- -function types.union (...) - local args={...} - return function(val) - for t in values(args) do if pcall(t, val) then return end end - types.error "None of the types in the union fits" - end -end - --------------------------------------------------------------------------------- --- [optional(t)] accepts values of types [t] or [nil]. --------------------------------------------------------------------------------- -function types.optional(t) - return function(val) - if val~=nil then nest_error("In optional type", t, val) end - end -end - --------------------------------------------------------------------------------- --- A call to this is done on litteral tables passed as types, i.e. --- type {1,2,3} is transformed into types.__table{1,2,3}. --------------------------------------------------------------------------------- -function types.__table(s_type) - return function (s_val) - if type(s_val) ~= "table" then types.error "Struct table expected" end - for k, field_type in pairs (s_type) do - nest_error ("in struct field "..k, field_type, s_val[k]) - end - end -end - --------------------------------------------------------------------------------- --- Same as __table, except that it's called on literal strings. --------------------------------------------------------------------------------- -function types.__string(s_type) - return function (s_val) - if s_val ~= s_type then - types.error("String %q expected", s_type) - end - end -end - --------------------------------------------------------------------------------- --- Top and Bottom: --------------------------------------------------------------------------------- -function types.any() end -function types.none() types.error "Empty type" end -types.__or = types.union -types.__and = types.inter \ No newline at end of file
diff --git a/lib/metalua/extension/types.mlua b/lib/metalua/extension/types.mlua deleted file mode 100644 index a8e30c8..0000000 --- a/lib/metalua/extension/types.mlua +++ /dev/null
@@ -1,371 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- This extension inserts type-checking code at approriate place in the code, --- thanks to annotations based on "::" keyword: --- --- * function declarations can be annotated with a returned type. When they --- are, type-checking code is inserted in each of their return statements, --- to make sure they return the expected type. --- --- * function parameters can also be annotated. If they are, type-checking --- code is inserted in the function body, which checks the arguments' types --- and cause an explicit error upon incorrect calls. Moreover, if a new value --- is assigned to the parameter in the function's body, the new value's type --- is checked before the assignment is performed. --- --- * Local variables can also be annotated. If they are, type-checking --- code is inserted before any value assignment or re-assignment is --- performed on them. --- --- Type checking can be disabled with: --- --- -{stat: types.enabled = false } --- --- Code transformation is performed at the chunk level, i.e. file by --- file. Therefore, it the value of compile-time variable --- [types.enabled] changes in the file, the only value that counts is --- its value once the file is entirely parsed. --- --- Syntax --- ====== --- --- Syntax annotations consist of "::" followed by a type --- specifier. They can appear after a function parameter name, after --- the closing parameter parenthese of a function, or after a local --- variable name in the declaration. See example in samples. --- --- Type specifiers are expressions, in which identifiers are taken --- from table types. For instance, [number] is transformed into --- [types.number]. These [types.xxx] fields must contain functions, --- which generate an error when they receive an argument which doesn't --- belong to the type they represent. It is perfectly acceptible for a --- type-checking function to return another type-checking function, --- thus defining parametric/generic types. Parameters can be --- identifiers (they're then considered as indexes in table [types]) --- or literals. --- --- Design hints --- ============ --- --- This extension uses the code walking library [walk] to globally --- transform the chunk AST. See [chunk_transformer()] for details --- about the walker. --- --- During parsing, type informations are stored in string-indexed --- fields, in the AST nodes of tags `Local and `Function. They are --- used by the walker to generate code only if [types.enabled] is --- true. --- --- TODO --- ==== --- --- It's easy to add global vars type-checking, by declaring :: as an --- assignment operator. It's easy to add arbitrary expr --- type-checking, by declaring :: as an infix operator. How to make --- both cohabit? - --------------------------------------------------------------------------------- --- --- Function chunk_transformer() --- --------------------------------------------------------------------------------- --- --- Takes a block annotated with extra fields, describing typing --- constraints, and returns a normal AST where these constraints have --- been turned into type-checking instructions. --- --- It relies on the following annotations: --- --- * [`Local{ }] statements may have a [types] field, which contains a --- id name ==> type name map. --- --- * [Function{ }] expressions may have an [param_types] field, also a --- id name ==> type name map. They may also have a [ret_type] field --- containing the type of the returned value. --- --- Design hints: --- ============= --- --- It relies on the code walking library, and two states: --- --- * [return_types] is a stack of the expected return values types for --- the functions currently in scope, the most deeply nested one --- having the biggest index. --- --- * [scopes] is a stack of id name ==> type name scopes, one per --- currently active variables scope. --- --- What's performed by the walker: --- --- * Assignments to a typed variable involve a type checking of the --- new value; --- --- * Local declarations are checked for additional type declarations. --- --- * Blocks create and destroy variable scopes in [scopes] --- --- * Functions create an additional scope (around its body block's scope) --- which retains its argument type associations, and stacks another --- return type (or [false] if no type constraint is given) --- --- * Return statements get the additional type checking statement if --- applicable. --- --------------------------------------------------------------------------------- - --- TODO: unify scopes handling with free variables detector --- FIXME: scopes are currently incorrect anyway, only functions currently define a scope. - -require "metalua.walk" - --{ extension 'match' } - -module("types", package.seeall) - -enabled = true - -local function chunk_transformer (block) - if not enabled then return end - local return_types, scopes = { }, { } - local cfg = { block = { }; stat = { }; expr = { } } - - function cfg.stat.down (x) - match x with - | `Local{ lhs, rhs, types = x_types } -> - -- Add new types declared by lhs in current scope. - local myscope = scopes [#scopes] - for var, type in pairs (x_types) do - myscope [var] = process_type (type) - end - -- Type-check each rhs value with the type of the - -- corresponding lhs declaration, if any. Check backward, in - -- case a local var name is used more than once. - for i = 1, max (#lhs, #rhs) do - local type, new_val = myscope[lhs[i][1]], rhs[i] - if type and new_val then - rhs[i] = checktype_builder (type, new_val, 'expr') - end - end - | `Set{ lhs, rhs } -> - for i=1, #lhs do - match lhs[i] with - | `Id{ v } -> - -- Retrieve the type associated with the variable, if any: - local j, type = #scopes, nil - repeat j, type = j-1, scopes[j][v] until type or j==0 - -- If a type constraint is found, apply it: - if type then rhs[i] = checktype_builder(type, rhs[i] or `Nil, 'expr') end - | _ -> -- assignment to a non-variable, pass - end - end - | `Return{ r_val } -> - local r_type = return_types[#return_types] - if r_type then - x <- `Return{ checktype_builder (r_type, r_val, 'expr') } - end - | _ -> -- pass - end - end - - function cfg.expr.down (x) - if x.tag ~= 'Function' then return end - local new_scope = { } - table.insert (scopes, new_scope) - for var, type in pairs (x.param_types or { }) do - new_scope[var] = process_type (type) - end - local r_type = x.ret_type and process_type (x.ret_type) or false - table.insert (return_types, r_type) - end - - ------------------------------------------------------------------- - -- Unregister the returned type and the variable scope in which - -- arguments are registered; - -- then, adds the parameters type checking instructions at the - -- beginning of the function, if applicable. - ------------------------------------------------------------------- - function cfg.expr.up (x) - if x.tag ~= 'Function' then return end - -- Unregister stuff going out of scope: - table.remove (return_types) - table.remove (scopes) - -- Add initial type checking: - for v, t in pairs(x.param_types or { }) do - table.insert(x[2], 1, checktype_builder(t, `Id{v}, 'stat')) - end - end - - cfg.block.down = || table.insert (scopes, { }) - cfg.block.up = || table.remove (scopes) - - walk.block(cfg, block) -end - --------------------------------------------------------------------------- --- Perform required transformations to change a raw type expression into --- a callable function: --- --- * identifiers are changed into indexes in [types], unless they're --- allready indexed, or into parentheses; --- --- * literal tables are embedded into a call to types.__table --- --- This transformation is not performed when type checking is disabled: --- types are stored under their raw form in the AST; the transformation is --- only performed when they're put in the stacks (scopes and return_types) --- of the main walker. --------------------------------------------------------------------------- -function process_type (type_term) - -- Transform the type: - cfg = { expr = { } } - - function cfg.expr.down(x) - match x with - | `Index{...} | `Paren{...} -> return 'break' - | _ -> -- pass - end - end - function cfg.expr.up (x) - match x with - | `Id{i} -> x <- `Index{ `Id "types", `String{ i } } - | `Table{...} | `String{...} | `Op{...} -> - local xcopy, name = table.shallow_copy(x) - match x.tag with - | 'Table' -> name = '__table' - | 'String' -> name = '__string' - | 'Op' -> name = '__'..x[1] - end - x <- `Call{ `Index{ `Id "types", `String{ name } }, xcopy } - | `Function{ params, { results } } if results.tag=='Return' -> - results.tag = nil - x <- `Call{ +{types.__function}, params, results } - | `Function{...} -> error "malformed function type" - | _ -> -- pass - end - end - walk.expr(cfg, type_term) - return type_term -end - --------------------------------------------------------------------------- --- Insert a type-checking function call on [term] before returning --- [term]'s value. Only legal in an expression context. --------------------------------------------------------------------------- -local non_const_tags = table.transpose - { 'Dots', 'Op', 'Index', 'Call', 'Invoke', 'Table' } -function checktype_builder(type, term, kind) - -- Shove type-checking code into the term to check: - match kind with - | 'expr' if non_const_tags [term.tag] -> - local v = mlp.gensym() - return `Stat{ { `Local{ {v}, {term} }; `Call{ type, v } }, v } - | 'expr' -> - return `Stat{ { `Call{ type, term } }, term } - | 'stat' -> - return `Call{ type, term } - end -end - --------------------------------------------------------------------------- --- Parse the typechecking tests in a function definition, and adds the --- corresponding tests at the beginning of the function's body. --------------------------------------------------------------------------- -local function func_val_builder (x) - local typed_params, ret_type, body = unpack(x) - local e = `Function{ { }, body; param_types = { }; ret_type = ret_type } - - -- Build [untyped_params] list, and [e.param_types] dictionary. - for i, y in ipairs (typed_params) do - if y.tag=="Dots" then - assert(i==#typed_params, "`...' must be the last parameter") - break - end - local param, type = unpack(y) - e[1][i] = param - if type then e.param_types[param[1]] = type end - end - return e -end - --------------------------------------------------------------------------- --- Parse ":: type" annotation if next token is "::", or return false. --- Called by function parameters parser --------------------------------------------------------------------------- -local opt_type = gg.onkeyword{ "::", mlp.expr } - --------------------------------------------------------------------------- --- Updated function definition parser, which accepts typed vars as --- parameters. --------------------------------------------------------------------------- - --- Parameters parsing: -local id_or_dots = gg.multisequence{ { "...", builder = "Dots" }, default = mlp.id } - --- Function parsing: -mlp.func_val = gg.sequence{ - "(", gg.list{ - gg.sequence{ id_or_dots, opt_type }, terminators = ")", separators = "," }, - ")", opt_type, mlp.block, "end", - builder = func_val_builder } - -mlp.lexer:add { "::", "newtype" } -mlp.chunk.transformers:add (chunk_transformer) - --- Local declarations parsing: -local local_decl_parser = mlp.stat:get "local" [2].default - -local_decl_parser[1].primary = gg.sequence{ mlp.id, opt_type } - -function local_decl_parser.builder(x) - local lhs, rhs = unpack(x) - local s, stypes = `Local{ { }, rhs or { } }, { } - for i = 1, #lhs do - local id, type = unpack(lhs[i]) - s[1][i] = id - if type then stypes[id[1]]=type end - end - if next(stypes) then s.types = stypes end - return s -end - -function newtype_builder(x) - local lhs, rhs = unpack(x) - match lhs with - | `Id{ x } -> t = process_type (rhs) - | `Call{ `Id{ x }, ... } -> - t = `Function{ { }, rhs } - for i = 2, #lhs do - if lhs[i].tag ~= "Id" then error "Invalid newtype parameter" end - t[1][i-1] = lhs[i] - end - | _ -> error "Invalid newtype definition" - end - return `Let{ { `Index{ `Id "types", `String{ x } } }, { t } } -end - -mlp.stat:add{ "newtype", mlp.expr, "=", mlp.expr, builder = newtype_builder } - - --------------------------------------------------------------------------- --- Register as an operator --------------------------------------------------------------------------- ---mlp.expr.infix:add{ "::", prec=100, builder = |a, _, b| insert_test(a,b) } - -return +{ require (-{ `String{ package.metalua_extension_prefix .. 'types-runtime' } }) }
diff --git a/lib/metalua/extension/withdo.mlua b/lib/metalua/extension/withdo.mlua deleted file mode 100644 index b361482..0000000 --- a/lib/metalua/extension/withdo.mlua +++ /dev/null
@@ -1,49 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- RAII in metalua. --- --- Write: --- with var_1, var_2... = val_1, val_2... do --- ... --- end --- --- will assign val_n to var_n foreach n, and guaranty that var_n:close() will be called, --- no matter what, even if the body causes an error, even if it returns, even --- if another :close() call causes an error, etc. No. Matter. What. - -require 'metalua.extension.trycatch' - -function withdo_builder (x) - local names, vals, body = unpack(x) - for i = #names, 1, -1 do - local name, val = names[i], vals[i] - body = trycatch_builder{ { `Set{ {name}, {val} }, body }, -- try-block - { }, -- catch-block - { +{ print ("closing "..-{`String{name[1]}}) }, - `Invoke{ name, `String "close" } } } - end - table.insert(body, 1, `Local{ names }) - return body -end - -mlp.lexer:add 'with' -mlp.stat:add{ - 'with', mlp.id_list, '=', mlp.expr_list, 'do', mlp.block, 'end', - builder = withdo_builder }
diff --git a/lib/metalua/extension/xglobal-runtime.lua b/lib/metalua/extension/xglobal-runtime.lua deleted file mode 100644 index b863fed..0000000 --- a/lib/metalua/extension/xglobal-runtime.lua +++ /dev/null
@@ -1,60 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -local _G = getfenv() -local _G_mt = getmetatable(_G) - - --- Set the __globals metafield in the global environment's metatable, --- if not already there. -if _G_mt then - if _G_mt.__globals then return else - print( "Warning: _G already has a metatable,".. - " which might interfere with xglobals") - _G_mt.__globals = { } - end -else - _G_mt = { __globals = { } } - setmetatable(_G, _G_mt) -end - --- add a series of variable names to the list of declared globals -function _G_mt.__newglobal(...) - local g = _G_mt.__globals - for v in ivalues{...} do g[v]=true end -end - --- Try to set a global that's not in _G: --- if it isn't declared, fail -function _G_mt.__newindex(_G, var, val) - if not _G_mt.__globals[var] then - error ("Setting undeclared global variable "..var) - end - rawset(_G, var, val) -end - --- Try to read a global that's not in _G: --- if it isn't declared, fail -function _G_mt.__index(_G, var) - if not _G_mt.__globals[var] then - error ("Reading undeclared global variable "..var) - end - return nil -end -
diff --git a/lib/metalua/extension/xglobal.mlua b/lib/metalua/extension/xglobal.mlua deleted file mode 100644 index 3859e98..0000000 --- a/lib/metalua/extension/xglobal.mlua +++ /dev/null
@@ -1,39 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- WARNING, this is undertested, especially in cases where mutliple --- modules have their own fenvs. Use at your own risks. - -require 'strict' - -local function decl_builder(x) - local ids, vals = unpack(x) - local ids_as_strings = table.imap(|x| `String{x[1]}, ids) - local decl = `Call{ +{getmetatable(getfenv()).__newglobal}, - unpack(ids_as_strings) } - if vals then return { decl, `Set{ ids, vals } } - else return decl end -end - -mlp.lexer:add 'global' -mlp.stat:add{ - 'global', mlp.id_list, gg.onkeyword{ '=', mlp.expr_list }, - builder = decl_builder } - -return +{ require (-{ `String{ package.metalua_extension_prefix .. 'xglobal-runtime' } }) }
diff --git a/lib/metalua/mlc_xcall.lua b/lib/metalua/mlc_xcall.lua deleted file mode 100644 index 7875874..0000000 --- a/lib/metalua/mlc_xcall.lua +++ /dev/null
@@ -1,142 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- Execute an `mlc.*_to_ast()' in a separate lua process. --- Communication between processes goes through temporary files, --- for the sake of portability. --------------------------------------------------------------------------------- - -mlc_xcall = { } - --------------------------------------------------------------------------------- --- Number of lines to remove at the end of a traceback, should it be --- dumped due to a compilation error in metabugs mode. --------------------------------------------------------------------------------- -local STACK_LINES_TO_CUT = 7 - --------------------------------------------------------------------------------- --- (Not intended to be called directly by users) --- --- This is the back-end function, called in a separate lua process --- by `mlc_xcall.client_*()' through `os.execute()'. --- * inputs: --- * the name of a lua source file to compile in a separate process --- * the name of a writable file where the resulting ast is dumped --- with `serialize()'. --- * metabugs: if true and an error occurs during compilation, --- the compiler's stacktrace is printed, allowing meta-programs --- debugging. --- * results: --- * an exit status of 0 or -1, depending on whethet compilation --- succeeded; --- * the ast file filled will either the serialized ast, or the --- error message. --------------------------------------------------------------------------------- -function mlc_xcall.server (luafilename, astfilename, metabugs) - - -- We don't want these to be loaded when people only do client-side business - require 'metalua.compiler' - require 'serialize' - - mlc.metabugs = metabugs - - -- compile the content of luafile name in an AST, serialized in astfilename - --local status, ast = pcall (mlc.luafile_to_ast, luafilename) - local status, ast - local function compile() return mlc.luafile_to_ast (luafilename) end - if mlc.metabugs then - --print 'mlc_xcall.server/metabugs' - --status, ast = xpcall (compile, debug.traceback) - --status, ast = xpcall (compile, debug.traceback) - local function tb(msg) - local r = debug.traceback(msg) - - -- Cut superfluous end lines - local line_re = '\n[^\n]*' - local re = "^(.-)" .. (line_re) :rep (STACK_LINES_TO_CUT) .. "$" - return r :strmatch (re) or r - end - --status, ast = xpcall (compile, debug.traceback) - status, ast = xpcall (compile, tb) - else status, ast = pcall (compile) end - if status then - local check_status, check_msg = pcall (mlc.check_ast, 'block', ast) - if not check_status then status, ast = false, check_msg end - end - local out = io.open (astfilename, 'w') - if status then -- success - out:write (serialize (ast)) - out:close () - os.exit (0) - else -- failure, `ast' is actually the error message - out:write (ast) - out:close () - os.exit (-1) - end -end - --------------------------------------------------------------------------------- --- Compile the file whose name is passed as argument, in a separate process, --- communicating through a temporary file. --- returns: --- * true or false, indicating whether the compilation succeeded --- * the ast, or the error message. --------------------------------------------------------------------------------- -function mlc_xcall.client_file (luafile) - - -- printf("\n\nmlc_xcall.client_file(%q)\n\n", luafile) - - local tmpfilename = os.tmpname() - local cmd = string.format ( - [=[lua -l metalua.mlc_xcall -e "mlc_xcall.server([[%s]], [[%s]], %s)"]=], - luafile, tmpfilename, mlc.metabugs and "true" or "false") - - -- printf("os.execute [[%s]]\n\n", cmd) - - local status = (0 == os.execute (cmd)) - local result -- ast or error msg - if status then - result = (lua_loadfile or loadfile) (tmpfilename) () - else - local f = io.open (tmpfilename) - result = f :read '*a' - f :close() - end - os.remove(tmpfilename) - return status, result -end - --------------------------------------------------------------------------------- --- Compile a source string into an ast, by dumping it in a tmp --- file then calling `mlc_xcall.client_file()'. --- returns: the same as `mlc_xcall.client_file()'. --------------------------------------------------------------------------------- -function mlc_xcall.client_literal (luasrc) - local srcfilename = os.tmpname() - local srcfile, msg = io.open (srcfilename, 'w') - if not srcfile then print(msg) end - srcfile :write (luasrc) - srcfile :close () - local status, ast = mlc_xcall.client_file (srcfilename) - os.remove(srcfilename) - return status, ast -end - -return mlc_xcall \ No newline at end of file
diff --git a/lib/metalua/runtime.lua b/lib/metalua/runtime.lua deleted file mode 100644 index cacf3d1..0000000 --- a/lib/metalua/runtime.lua +++ /dev/null
@@ -1,22 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.base' -require 'metalua.table2' -require 'metalua.string2'
diff --git a/lib/metalua/walk/bindings.mlua b/lib/metalua/walk/bindings.mlua deleted file mode 100644 index b072f9f..0000000 --- a/lib/metalua/walk/bindings.mlua +++ /dev/null
@@ -1,65 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.walk' -require 'metalua.walk.scope' - -function bindings(ast) - -- binders :: ast => name => occurences - -- unbound :: name => occurences - -- scope :: name => ast - - local binders, unbound, cfg, scope = { }, { }, { scope={ } }, scope:new() - - -- * id: identifier entering in scope - -- * ast: statement or expr carrying this id, on of: - -- Local, Localrec, Forin, Fornum, Function. - function cfg.binder (id, ast) - if id.tag ~= 'Id' then return end - local id_name = id[1] - -- Reference in scope, so that the binding statement can be retrieved: - scope.current[id_name] = ast - -- Init the occurences list for this identifier: - if binders[ast] then binders[ast][id_name] = { } - else binders[ast] = { [id_name] = { } } end - end - - -- identifier occurence, not as a binder: reference this occurence - function cfg.Id (id) - local id_name = id[1] - -- ast which binds this id, might be nil: - local binder_ast = scope.current [id_name] - -- dict id_name => occurences, might be the list of unbound occurences: - local occur_dict = binder_ast and binders[binder_ast] or unbound - -- add an occurence of `id' in the occurences list: - local occurences = occur_dict [id_name] - if occurences then table.insert (occurences, id) - else occur_dict [id_name] = { id } end - end - - --Do not choke on partial ASTs which contain `Error{} nodes - function cfg.error(x) end - - function cfg.scope.down() scope:push() end - function cfg.scope.up() scope:pop() end - - walk.guess (cfg, ast) - return binders, unbound -end -
diff --git a/lib/serialize.lua b/lib/serialize.lua deleted file mode 100644 index 4e5c8a7..0000000 --- a/lib/serialize.lua +++ /dev/null
@@ -1,202 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- --- --- Serialize an object into a source code string. This string, when passed as --- an argument to loadstring()(), returns an object structurally identical --- to the original one. --- --- The following are supported: --- --- * strings, numbers, booleans, nil --- --- * functions without upvalues --- --- * tables thereof. There is no restriction on keys; recursive and shared --- sub-tables are handled correctly. --- --- Caveat: metatables and environments aren't saved; this might or might not --- be what you want. --------------------------------------------------------------------------------- - -local no_identity = { number=1, boolean=1, string=1, ['nil']=1 } - -function serialize (x) - - local gensym_max = 0 -- index of the gensym() symbol generator - local seen_once = { } -- element->true set of elements seen exactly once in the table - local multiple = { } -- element->varname set of elements seen more than once - local nested = { } -- transient, set of elements currently being traversed - local nest_points = { } - local nest_patches = { } - - -- Generate fresh indexes to store new sub-tables: - local function gensym() - gensym_max = gensym_max + 1 ; return gensym_max - end - - ----------------------------------------------------------------------------- - -- `nest_points' are places where a (recursive) table appears within - -- itself, directly or not. for instance, all of these chunks - -- create nest points in table `x': - -- - -- "x = { }; x[x] = 1" - -- "x = { }; x[1] = x" - -- "x = { }; x[1] = { y = { x } }". - -- - -- To handle those, two tables are created by `mark_nest_point()': - -- - -- * `nest_points [parent]' associates all keys and values in table - -- parent which create a nest_point with boolean `true' - -- - -- * `nest_patches' contains a list of `{ parent, key, value }' - -- tuples creating a nest point. They're all dumped after all the - -- other table operations have been performed. - -- - -- `mark_nest_point (p, k, v)' fills tables `nest_points' and - -- `nest_patches' with informations required to remember that - -- key/value `(k,v)' creates a nest point in parent table `p'. It - -- also marks `p' as occuring multiple times, since several - -- references to it will be required in order to patch the nest - -- points. - ----------------------------------------------------------------------------- - local function mark_nest_point (parent, k, v) - local nk, nv = nested[k], nested[v] - assert (not nk or seen_once[k] or multiple[k]) - assert (not nv or seen_once[v] or multiple[v]) - local mode = (nk and nv and "kv") or (nk and "k") or ("v") - local parent_np = nest_points [parent] - local pair = { k, v } - if not parent_np then parent_np = { }; nest_points [parent] = parent_np end - parent_np [k], parent_np [v] = nk, nv - table.insert (nest_patches, { parent, k, v }) - seen_once [parent], multiple [parent] = nil, true - end - - ----------------------------------------------------------------------------- - -- 1st pass, list the tables and functions which appear more than once in `x' - ----------------------------------------------------------------------------- - local function mark_multiple_occurences (x) - if no_identity [type(x)] then return end - if seen_once [x] then seen_once [x], multiple [x] = nil, true - elseif multiple [x] then -- pass - else seen_once [x] = true end - - if type (x) == 'table' then - nested [x] = true - for k, v in pairs (x) do - if nested[k] or nested[v] then mark_nest_point (x, k, v) else - mark_multiple_occurences (k) - mark_multiple_occurences (v) - end - end - nested [x] = nil - end - end - - local dumped = { } -- multiply occuring values already dumped in localdefs - local localdefs = { } -- already dumped local definitions as source code lines - - - -- mutually recursive functions: - local dump_val, dump_or_ref_val - - ------------------------------------------------------------------------------ - -- if `x' occurs multiple times, dump the local var rather than the - -- value. If it's the first time it's dumped, also dump the content - -- in localdefs. - ------------------------------------------------------------------------------ - function dump_or_ref_val (x) - if nested[x] then return 'false' end -- placeholder for recursive reference - if not multiple[x] then return dump_val (x) end - local var = dumped [x] - if var then return "_[" .. var .. "]" end -- already referenced - local val = dump_val(x) -- first occurence, create and register reference - var = gensym() - table.insert(localdefs, "_["..var.."]="..val) - dumped [x] = var - return "_[" .. var .. "]" - end - - ----------------------------------------------------------------------------- - -- 2nd pass, dump the object; subparts occuring multiple times are dumped - -- in local variables, which can then be referenced multiple times; - -- care is taken to dump local vars in an order which repect dependencies. - ----------------------------------------------------------------------------- - function dump_val(x) - local t = type(x) - if x==nil then return 'nil' - elseif t=="number" then return tostring(x) - elseif t=="string" then return string.format("%q", x) - elseif t=="boolean" then return x and "true" or "false" - elseif t=="function" then - return string.format ("loadstring(%q,'@serialized')", string.dump (x)) - elseif t=="table" then - - local acc = { } - local idx_dumped = { } - local np = nest_points [x] - for i, v in ipairs(x) do - if np and np[v] then - table.insert (acc, 'false') -- placeholder - else - table.insert (acc, dump_or_ref_val(v)) - end - idx_dumped[i] = true - end - for k, v in pairs(x) do - if np and (np[k] or np[v]) then - --check_multiple(k); check_multiple(v) -- force dumps in localdefs - elseif not idx_dumped[k] then - table.insert (acc, "[" .. dump_or_ref_val(k) .. "] = " .. dump_or_ref_val(v)) - end - end - return "{ "..table.concat(acc,", ").." }" - else - error ("Can't serialize data of type "..t) - end - end - - -- Patch the recursive table entries: - local function dump_nest_patches() - for _, entry in ipairs(nest_patches) do - local p, k, v = unpack (entry) - assert (multiple[p]) - local set = dump_or_ref_val (p) .. "[" .. dump_or_ref_val (k) .. "] = " .. - dump_or_ref_val (v) .. " -- rec " - table.insert (localdefs, set) - end - end - - mark_multiple_occurences (x) - local toplevel = dump_or_ref_val (x) - dump_nest_patches() - - if next (localdefs) then - -- Dump local vars containing shared or recursive parts, - -- then the main table using them. - return "local _={ }\n" .. - table.concat (localdefs, "\n") .. - "\nreturn " .. toplevel - else - -- No shared part, straightforward dump: - return "return " .. toplevel - end -end
diff --git a/make.bat b/make.bat deleted file mode 100644 index d4e9ca6..0000000 --- a/make.bat +++ /dev/null
@@ -1,58 +0,0 @@ -@CLS - -@REM *** Settings *** - -@REM BASE = root directory of metalua sources -@REM DISTRIB_BIN = metalua executables target directory -@REM DISTRIB_LIB = metalua libraries target directory, can be an existing path referenced in LUA_PATH -@REM LUA, LUAC = Lua executables, provided by metalua by default. - -@REM --- BEGINNING OF USER-EDITABLE PART --- - -@set BASE=%CD% -@set DISTRIB=%BASE%\..\distrib -@set DISTRIB_BIN=%DISTRIB%\bin -@set DISTRIB_LIB=%DISTRIB%\lib -@set LUA=%DISTRIB_BIN%\lua -@set LUAC=%DISTRIB_BIN%\luac - -@REM --- END OF USER-EDITABLE PART --- - - -@REM *** Create the distribution directories, populate them with lib sources *** - -@set LUA_PATH=?.luac;?.lua;%DISTRIB_LIB%\?.luac;%DISTRIB_LIB%\?.lua -@set LUA_MPATH=?.mlua;%DISTRIB_LIB%\?.mlua - -mkdir %DISTRIB% -mkdir %DISTRIB_BIN% -mkdir %DISTRIB_LIB% -xcopy /y /s lib %DISTRIB_LIB% -xcopy /y /s bin %DISTRIB_BIN% - -@REM *** Generate a callable batch metalua.bat script *** - -echo @set LUA_PATH=?.luac;?.lua;%DISTRIB_LIB%\?.luac;%DISTRIB_LIB%\?.lua > %DISTRIB_BIN%\metalua.bat -echo @set LUA_MPATH=?.mlua;%DISTRIB_LIB%\?.mlua >> %DISTRIB_BIN%\metalua.bat -echo @%LUA% %DISTRIB_LIB%\metalua.luac %%* >> %DISTRIB_BIN%\metalua.bat - - -@REM *** Compiling the parts of the compiler written in plain Lua *** - -cd compiler -%LUAC% -o %DISTRIB_LIB%\metalua\bytecode.luac lopcodes.lua lcode.lua ldump.lua compile.lua -%LUAC% -o %DISTRIB_LIB%\metalua\mlp.luac lexer.lua gg.lua mlp_lexer.lua mlp_misc.lua mlp_table.lua mlp_meta.lua mlp_expr.lua mlp_stat.lua mlp_ext.lua -cd .. - -@REM *** Bootstrap the parts of the compiler written in metalua *** - -%LUA% %BASE%\build-utils\bootstrap.lua %BASE%\compiler\mlc.mlua output=%DISTRIB_LIB%\metalua\mlc.luac -%LUA% %BASE%\build-utils\bootstrap.lua %BASE%\compiler\metalua.mlua output=%DISTRIB_LIB%\metalua.luac - -@REM *** Finish the bootstrap: recompile the metalua parts of the compiler with itself *** - -call %DISTRIB_BIN%\metalua -vb -f compiler\mlc.mlua -o %DISTRIB_LIB%\metalua\mlc.luac -call %DISTRIB_BIN%\metalua -vb -f compiler\metalua.mlua -o %DISTRIB_LIB%\metalua.luac - -@REM *** Precompile metalua libraries *** -%LUA% %BASE%\build-utils\precompile.lua directory=%DISTRIB_LIB% command=%DISTRIB_BIN%\metalua
diff --git a/make.sh b/make.sh deleted file mode 100644 index fc890e9..0000000 --- a/make.sh +++ /dev/null
@@ -1,135 +0,0 @@ -#! /bin/sh - -# --- BEGINNING OF USER-EDITABLE PART --- - -# Metalua sources -BASE=${PWD} - -# Temporary building location. -# Upon installation, everything will be moved to ${INSTALL_LIB} and ${INSTALL_BIN} - -if [ -z "${BUILD}" ]; then - BUILD=$(mkdir -p ../build; cd ../build; pwd) -fi - -if [ -z "${BUILD_BIN}" ]; then - BUILD_BIN=${BUILD}/bin -fi - -if [ -z "${BUILD_LIB}" ]; then - BUILD_LIB=${BUILD}/lib -fi - -# Where to place the final results -# DESTDIR= -# INSTALL_BIN=/usr/local/bin -# INSTALL_LIB=/usr/local/lib/lua/5.1 -if [ -z "${INSTALL_BIN}" ]; then - INSTALL_BIN=~/local/bin -fi - -if [ -z "${INSTALL_LIB}" ]; then - INSTALL_LIB=~/local/lib/lua -fi - -# Where to find Lua executables. -# On many Debian-based systems, those can be installed with "sudo apt-get install lua5.1" -LUA=$(which lua) -LUAC=$(which luac) - -# --- END OF USER-EDITABLE PART --- - -if [ -z ${LUA} ] ; then echo "Error: no lua interpreter found"; exit 1; fi -if [ -z ${LUAC} ] ; then echo "Error: no lua compiler found"; exit 1; fi - -if [ -f ~/.metaluabuildrc ] ; then . ~/.metaluabuildrc; fi - -if [ -z "$LINEREADER" ] ; then LINEREADER=$(which rlwrap); fi - -if [ -z "$LINEREADER" ] ; then - echo "Warning, rlwrap not found, no line editor support for interactive mode" - echo "Consider performing the equivalent of 'sudo apt-get install rlwrap'." -fi - -echo '*** Lua paths setup ***' - -export LUA_PATH="?.luac;?.lua;${BUILD_LIB}/?.luac;${BUILD_LIB}/?.lua" -export LUA_MPATH="?.mlua;${BUILD_LIB}/?.mlua" - -echo '*** Create the distribution directories, populate them with lib sources ***' - -mkdir -p ${BUILD_BIN} -mkdir -p ${BUILD_LIB} -cp -Rp lib/* ${BUILD_LIB}/ -# cp -Rp bin/* ${BUILD_BIN}/ # No binaries provided for unix (for now) - -echo '*** Generating a callable metalua shell script ***' - -cat > ${BUILD_BIN}/metalua <<EOF -#!/bin/sh -export LUA_PATH='?.luac;?.lua;${BUILD_LIB}/?.luac;${BUILD_LIB}/?.lua' -export LUA_MPATH='?.mlua;${BUILD_LIB}/?.mlua' -exec ${LINEREADER} ${LUA} ${BUILD_LIB}/metalua.luac \$* -EOF -chmod a+x ${BUILD_BIN}/metalua - -echo '*** Compiling the parts of the compiler written in plain Lua ***' - -cd compiler -${LUAC} -o ${BUILD_LIB}/metalua/bytecode.luac lopcodes.lua lcode.lua ldump.lua compile.lua || exit 1 -${LUAC} -o ${BUILD_LIB}/metalua/mlp.luac lexer.lua gg.lua mlp_lexer.lua mlp_misc.lua mlp_table.lua mlp_meta.lua mlp_expr.lua mlp_stat.lua mlp_ext.lua || exit 1 -cd .. - -echo '*** Bootstrap the parts of the compiler written in metalua ***' - -${LUA} ${BASE}/build-utils/bootstrap.lua ${BASE}/compiler/mlc.mlua output=${BUILD_LIB}/metalua/mlc.luac -${LUA} ${BASE}/build-utils/bootstrap.lua ${BASE}/compiler/metalua.mlua output=${BUILD_LIB}/metalua.luac -${LUA} ${BASE}/build-utils/bootstrap.lua ${BASE}/lib/metalua/treequery/walk.mlua output=${BUILD_LIB}/metalua/treequery/walk.luac - -echo '*** Finish the bootstrap: recompile the metalua parts of the compiler with itself ***' - -${BUILD_BIN}/metalua -vb -f compiler/mlc.mlua -o ${BUILD_LIB}/metalua/mlc.luac -${BUILD_BIN}/metalua -vb -f compiler/metalua.mlua -o ${BUILD_LIB}/metalua.luac -${BUILD_BIN}/metalua -vb -f lib/metalua/treequery/walk.mlua -o ${BUILD_LIB}/metalua/treequery/walk.luac - -echo '*** Precompile metalua libraries ***' -for SRC in $(find ${BUILD_LIB} -name '*.mlua'); do - DST=$(dirname $SRC)/$(basename $SRC .mlua).luac - if [ $DST -nt $SRC ]; then - echo "+ $DST already up-to-date" - else - echo "- $DST generated from $SRC" - ${BUILD_BIN}/metalua $SRC -o $DST - fi -done - -echo '*** Generate make-install.sh script ***' - -cat > make-install.sh <<EOF2 -#!/bin/sh -mkdir -p ${INSTALL_BIN} -mkdir -p ${INSTALL_LIB} -if [ -n "${DESTDIR}" ]; then - mkdir -p ${DESTDIR}${INSTALL_BIN} - mkdir -p ${DESTDIR}${INSTALL_LIB} -fi -cat > ${DESTDIR}${INSTALL_BIN}/metalua <<EOF -#!/bin/sh -METALUA_LIB=${INSTALL_LIB} -export LUA_PATH="?.luac;?.lua;\\\${METALUA_LIB}/?.luac;\\\${METALUA_LIB}/?.lua" -export LUA_MPATH="?.mlua;\\\${METALUA_LIB}/?.mlua" -exec ${LINEREADER} ${LUA} \\\${METALUA_LIB}/metalua.luac "\\\$@" -EOF - -chmod a+x ${DESTDIR}${INSTALL_BIN}/metalua - -cp -pR ${BUILD_LIB}/* ${DESTDIR}${INSTALL_LIB}/ - -echo "metalua libs installed in ${INSTALL_LIB};" -echo "metalua executable in ${INSTALL_BIN}." -EOF2 -chmod a+x make-install.sh - -echo -echo "Build completed, proceed to installation with './make-install.sh' or 'sudo ./make-install.sh'" -echo
diff --git a/compiler/metalua.mlua b/metalua.lua similarity index 71% rename from compiler/metalua.mlua rename to metalua.lua index 4526243..7c97af3 100644 --- a/compiler/metalua.mlua +++ b/metalua.lua
@@ -17,24 +17,29 @@ -- ------------------------------------------------------------------------------- ---require 'verbose_require' +-- Main file for the metalua executable +require 'metalua.base' +require 'metalua.compiler.globals' -- metalua-aware loadstring, dofile etc. -require 'metalua.compiler' -require 'metalua.clopts' -require 'metalua.mlc_xcall' +local mlc = require 'metalua.compiler' +local clopts = require 'metalua.clopts' -AST_COMPILE_ERROR_NUMBER = -1 -RUNTIME_ERROR_NUMBER = -3 -BYTECODE_SYNTHESE_ERROR_NUMBER = -100 +local M = { } --{ extension 'match' } +local AST_COMPILE_ERROR_NUMBER = -1 +local RUNTIME_ERROR_NUMBER = -3 +local BYTECODE_SYNTHESE_ERROR_NUMBER = -100 local chunks = { } local runargs = { } -local acc_chunk = |kind| |arg| table.insert (chunks, { tag=kind, arg }) +local function acc_chunk(kind) + return function(x) + table.insert (chunks, { tag=kind, x }) + end +end -parser = clopts { +M.cmdline_parser = clopts { -- Chunk loading { short = 'f', long = 'file', type = 'string', action = acc_chunk 'File', usage = 'load a file to compile and/or run' @@ -74,13 +79,14 @@ { short = 's', long = 'sharpbang', type = 'string', usage = 'set a first line to add to compiled file, typically "#!/bin/env mlr"' }, - { long = 'no-runtime', type = 'boolean', - usage = "prevent the automatic requirement of metalua runtime" + { long = 'no-base-lib', type = 'boolean', + usage = "prevent the automatic requirement of metalua base lib" }, { long = '', short = 'p', type = '*', action= function (newargs) runargs=table.icat(runargs, newargs) end, usage = "pass all remaining arguments to the program" }, + usage=[[ Compile and/or execute metalua programs. Parameters passed to the @@ -102,9 +108,9 @@ forbids it. ]]} -local function main (...) +function M.main (...) - local cfg = parser(...) + local cfg = M.cmdline_parser(...) ------------------------------------------------------------------- -- Print messages if in verbose mode @@ -125,7 +131,7 @@ if #chunks==0 and cfg.params then local the_file = table.remove(cfg.params, 1) verb_print("Param %q considered as a source file", the_file) - chunks = { `File{ the_file } } + chunks = { {tag='File', the_file } } end ------------------------------------------------------------------- @@ -148,25 +154,32 @@ ------------------------------------------------------------------- -- Get ASTs from sources - mlc.metabugs = cfg.metabugs + local last_file - for x in values(chunks) do + for _, x in pairs(chunks) do + local compiler = mlc.new() + local tag, val = x.tag, x[1] verb_print("Compiling %s", table.tostring(x)) local st, ast - match x with - | `Library{ l } -> st, ast = true, `Call{ `Id 'require', `String{ l } } - | `Literal{ e } -> st, ast = mlc_xcall.client_literal (e) - | `File{ f } -> - st, ast = mlc_xcall.client_file (f) - -- Isolate each file in a separate fenv - if st then - ast = +{ function (...) -{ast} end (...) } - ast.source = '@'..f -- TODO [EVE] - code.source = '@'..f -- TODO [EVE] - last_file = ast - end + if tag=='Library' then + ast = { tag='Call', + {tag='Id', "require" }, + {tag='String', val } } + elseif tag=='Literal' then ast = compiler :src_to_ast(val) + elseif tag=='File' then + ast = compiler :srcfile_to_ast(val) + -- Isolate each file in a separate fenv + ast = { tag='Call', + { tag='Function', { { tag='Dots'} }, ast }, + { tag='Dots' } } + ast.source = '@'..val -- TODO [EVE] + code.source = '@'..val -- TODO [EVE] + last_file = ast + else + error ("Bad option "..tag) end - if not st then + local valid = true -- TODO: check AST's correctness + if not valid then printf ("Cannot compile %s:\n%s", table.tostring(x), ast or "no msg") os.exit (AST_COMPILE_ERROR_NUMBER) end @@ -175,15 +188,21 @@ end -- The last file returns the whole chunk's result if last_file then - local c = table.shallow_copy(last_file) - last_file <- `Return{ source = c.source, c } + local c = table.shallow_copy(last_file) + table.override(last_file, {tag='Return', source = c.source, c }) end + -- Further uses of compiler won't involve AST transformations: + -- they can share the same instance. + -- TODO: reuse last instance if possible. + local compiler = mlc.new() + + ------------------------------------------------------------------- -- AST printing if cfg['print-ast'] or cfg['print-ast-lineinfo'] then verb_print "Resulting AST:" - for x in ivalues(code) do + for _, x in ipairs(code) do printf("--- AST From %s: ---", table.tostring(x.source, 'nohash')) if x.origin and x.origin.tag=='File' then x=x[1][1][2][1] end if cfg['print-ast-lineinfo'] then table.print(x, 80, "indent1") @@ -195,25 +214,27 @@ -- Source printing if cfg['print-src'] then verb_print "Resulting sources:" - require 'metalua.ast_to_string' - for x in ivalues(code) do + for _, x in ipairs(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)) + if x.origin and x.origin.tag=='File' then x=x[1][1][2] end + print (compiler :ast2string (x)) end end - -- FIXME: canonize/check AST + -- TODO: canonize/check AST ------------------------------------------------------------------- - -- Insert runtime loader - if cfg['no-runtime'] then - verb_print "Prevent insertion of command \"require 'metalua.runtime'\"" + -- Insert base lib loader + if cfg['no-base-lib'] then + verb_print "Prevent insertion of command \"require 'metalua.base'\"" else - table.insert(code, 1, +{require'metalua.runtime'}) + local req_runtime = { tag='Call', + {tag='Id', "require"}, + {tag='String', "metalua.base"} } + table.insert(code, 1, req_runtime) end - local bytecode = mlc.ast_to_luacstring (code) + local bytecode = compiler :ast_to_bytecode (code) code = nil ------------------------------------------------------------------- @@ -221,8 +242,8 @@ if cfg.sharpbang then local shbang = cfg.sharpbang verb_print ("Adding sharp-bang directive %q", shbang) - if not shbang :strmatch'^#!' then shbang = '#!' .. shbang end - if not shbang :strmatch'\n$' then shbang = shbang .. '\n' end + if not shbang :match'^#!' then shbang = '#!' .. shbang end + if not shbang :match'\n$' then shbang = shbang .. '\n' end bytecode = shbang .. bytecode end @@ -244,16 +265,16 @@ -- Run compiled code if cfg.run then verb_print "Running" - local f = mlc.luacstring_to_function (bytecode) + local f = compiler :bytecode_to_function (bytecode) bytecode = nil -- FIXME: isolate execution in a ring -- FIXME: check for failures - runargs = table.icat(cfg.params or { }, runargs) local function print_traceback (errmsg) return errmsg .. '\n' .. debug.traceback ('',2) .. '\n' end - local st, msg = xpcall(|| f(unpack (runargs)), print_traceback) + local function g() return f(unpack (runargs)) end + local st, msg = xpcall(g, print_traceback) if not st then io.stderr:write(msg) os.exit(RUNTIME_ERROR_NUMBER) @@ -264,12 +285,16 @@ -- Run REPL loop if cfg.interactive then verb_print "Starting REPL loop" - require 'metalua.metaloop' - metaloop.run() + require 'metalua.repl' .run() end verb_print "Done" end -main(...) +-- If the lib is being loaded, the sentinel token is currently +-- put as a placeholder in its package.loaded entry. +local called_as_a_lib = type(package.loaded.metalua)=='userdata' + +if not called_as_a_lib then M.main(...) +else return M end \ No newline at end of file
diff --git a/lib/metalua/base.lua b/metalua/base.lua similarity index 63% rename from lib/metalua/base.lua rename to metalua/base.lua index 08be242..b0f0e36 100644 --- a/lib/metalua/base.lua +++ b/metalua/base.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,7 +15,7 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- ---------------------------------------------------------------------- ---------------------------------------------------------------------- @@ -25,53 +25,7 @@ ---------------------------------------------------------------------- ---------------------------------------------------------------------- -if not metalua then rawset(getfenv(), 'metalua', { }) end -metalua.version = "v-0.5" - -if not rawpairs then - rawpairs, rawipairs, rawtype = pairs, ipairs, type -end - -function pairs(x) - assert(type(x)=='table', 'pairs() expects a table') - local mt = getmetatable(x) - if mt then - local mtp = mt.__pairs - if mtp then return mtp(x) end - end - return rawpairs(x) -end - -function ipairs(x) - assert(type(x)=='table', 'ipairs() expects a table') - local mt = getmetatable(x) - if mt then - local mti = mt.__ipairs - if mti then return mti(x) end - end - return rawipairs(x) -end - ---[[ -function type(x) - local mt = getmetatable(x) - if mt then - local mtt = mt.__type - if mtt then return mtt end - end - return rawtype(x) -end -]] - -function min (a, ...) - for n in values{...} do if n<a then a=n end end - return a -end - -function max (a, ...) - for n in values{...} do if n>a then a=n end end - return a -end +require 'checks' function o (...) local args = {...} @@ -92,7 +46,7 @@ end function ivalues (x) - assert(type(x)=='table', 'ivalues() expects a table') + checks('table') local i = 1 local function iterator () local r = x[i]; i=i+1; return r @@ -102,7 +56,7 @@ function values (x) - assert(type(x)=='table', 'values() expects a table') + checks('table') local function iterator (state) local it state.content, it = next(state.list, state.content) @@ -112,7 +66,7 @@ end function keys (x) - assert(type(x)=='table', 'keys() expects a table') + checks('table') local function iterator (state) local it = next(state.list, state.content) state.content = it @@ -121,3 +75,6 @@ return iterator, { list = x } end +require 'metalua.table' +require 'metalua.string' +require 'metalua.package' \ No newline at end of file
diff --git a/lib/metalua/clopts.mlua b/metalua/clopts.mlua similarity index 98% rename from lib/metalua/clopts.mlua rename to metalua/clopts.mlua index 28d19c3..7ea6c4f 100644 --- a/lib/metalua/clopts.mlua +++ b/metalua/clopts.mlua
@@ -38,9 +38,9 @@ -- * add a ++ long counterpart to + -- --{ extension 'match' } +-{ extension ('match',...) } -function clopts(cfg) +local function clopts(cfg) local short, long, param_func = { }, { } local legal_types = table.transpose{ 'boolean','string','number','string*','number*','nil', '*' } @@ -48,7 +48,7 @@ ----------------------------------------------------------------------------- -- Fill short and long name indexes, and check its validity ----------------------------------------------------------------------------- - for x in ivalues(cfg) do + for _, x in ipairs(cfg) do local xtype = type(x) if xtype=='table' then if not x.type then x.type='nil' end @@ -73,7 +73,7 @@ local function print_usage(msg) if msg then print(msg,'\n') end print(cfg.usage or "Options:\n") - for x in values(cfg) do + for _, x in pairs(cfg) do if type(x) == 'table' then local opts = { } if x.type=='boolean' then @@ -220,4 +220,4 @@ return parse end - +return clopts
diff --git a/metalua/compiler.lua b/metalua/compiler.lua new file mode 100644 index 0000000..96c4e3d --- /dev/null +++ b/metalua/compiler.lua
@@ -0,0 +1,239 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +--*-lua-*----------------------------------------------------------------------- +-- +-- Convert between various code representation formats. Some atomic +-- converters are written in extenso, others are composed automatically +-- by chaining the atomic ones together in a closure. +-- +-- Supported formats are: +-- +-- * luafile: the name of a file containing sources. +-- * luastring: these sources as a single string. +-- * lexstream: a stream of lexemes. +-- * ast: an abstract syntax tree. +-- * proto: a (Yueliang) struture containing a high level +-- representation of bytecode. Largely based on the +-- Proto structure in Lua's VM +-- * luacstring: a string dump of the function, as taken by +-- loadstring() and produced by string.dump(). +-- * function: an executable lua function in RAM. +-- +-------------------------------------------------------------------------------- + +require 'checks' +require 'metalua.table' + +local M = { } + +-------------------------------------------------------------------------------- +-- Order of the transformations. if 'a' is on the left of 'b', then a 'a' can +-- be transformed into a 'b' (but not the other way around). +-- M.sequence goes for numbers to format names, M.order goes from format +-- names to numbers. +-------------------------------------------------------------------------------- +M.sequence = { + 'srcfile', 'src', 'lexstream', 'ast', 'proto', 'bytecode', 'function' } + +local arg_types = { + srcfile = { 'string', '?string' }, + src = { 'string', '?string' }, + lexstream = { 'lexer.stream', '?string' }, + ast = { 'table', '?string' }, + proto = { 'table', '?string' }, + bytecode = { 'string', '?string' }, +} + +M.order = table.transpose(M.sequence) + +local function check_ast(kind, ast) + if not ast then return check_ast('block', kind) end + assert(type(ast)=='table', "wrong AST type") + local cfg = {} + local function error2ast(error_node, ...) + if not error_node.earlier then + if error_node.tag=='Error' then + cfg.errorfound = true + cfg.errormsg = error_node[1] + + -- Try to extract error position in source + local li = error_node.lineinfo and error_node.lineinfo.first + + -- Fill positions if undefined or not narrow enough + if li and ( not cfg.positions or cfg.positions.offset < li.offset ) then + cfg.positions = { + column = li.column, + line = li.line, + offset = li.offset + } + end + else + -- This block is for dealing with errors which are not error + -- nodes. It would be soooo nice to get rid of it. + -- TODO: Try to remove this bug when issue #20 is fixed + local li + for _, n in ipairs{ error_node, ... } do + if n.lineinfo then + li = n.lineinfo + cfg.errorfound = true + break + end + end + local posmsg + if li then + local column = li.first.column + local line = li.first.line + local offset = li.first.offset + posmsg = string.format("line %d, char %d, offset %d", + line, column, offset) + cfg.positions = { + column = column, + line = line, + offset = offset + } + else + posmsg = "unknown source position" + end + local msg = "Invalid node ".. + (error_node.tag and "tag "..tostring(error_node.tag) or "without tag").. + (posmsg and " at "..posmsg or "") + cfg.errormsg = msg + end + end + end + local f = require 'metalua.treequery.walk' [kind] + cfg.malformed=error2ast + cfg.unknown= error2ast + cfg.error= error2ast + f(cfg, ast) + return cfg.errorfound == nil, cfg.errormsg, cfg.positions +end + +M.check_ast = check_ast + +local function find_error(ast, nested) + checks('table', '?table') + nested = nested or { } + if nested[ast] then return "Cyclic AST" end + nested[ast]=true + if ast.tag=='Error' then + local pos = tostring(ast.lineinfo.first) + return pos..": "..ast[1] + end + for _, item in ipairs(ast) do + if type(item)=='table' then + local err=find_error(item) + if err then return err end + end + end + nested[ast]=nil + return nil +end + +local CONV = { } -- conversion metatable __index + +function CONV :srcfile_to_src(x, name) + checks('metalua.compiler', 'string', '?string') + name = name or '@'..x + local f, msg = io.open (x, 'rb') + if not f then error(msg) end + local r, msg = f :read '*a' + if not r then error("Cannot read file '"..x.."': "..msg) end + f :close() + return r, name +end + +function CONV :src_to_lexstream(src, name) + checks('metalua.compiler', 'string', '?string') + local r = self.parser.lexer :newstream (src, name) + return r, name +end + +function CONV :lexstream_to_ast(lx, name) + checks('metalua.compiler', 'lexer.stream', '?string') + local r = self.parser.chunk(lx) + r.source = name + return r, name +end + +function CONV :ast_to_proto(ast, name) + checks('metalua.compiler', 'table', '?string') + --table.print(ast, 'nohash', 1) io.flush() + local err = find_error(ast) + if err then error(err) end + local f = require 'metalua.compiler.bytecode.compile'.ast_to_proto + return f(ast, name), name +end + +function CONV :proto_to_bytecode(proto, name) + local bc = require 'metalua.compiler.bytecode' + return bc.proto_to_bytecode(proto), name +end + +function CONV :bytecode_to_function(bc, name) + checks('metalua.compiler', 'string', '?string') + return loadstring(bc, name) +end + +-- Create all sensible combinations +for i=1,#M.sequence do + local src = M.sequence[i] + for j=i+2, #M.sequence do + local dst = M.sequence[j] + local dst_name = src.."_to_"..dst + local my_arg_types = arg_types[src] + local functions = { } + for k=i, j-1 do + local name = M.sequence[k].."_to_"..M.sequence[k+1] + local f = assert(CONV[name], name) + table.insert (functions, f) + end + CONV[dst_name] = function(self, a, b) + checks('metalua.compiler', unpack(my_arg_types)) + for _, f in ipairs(functions) do + a, b = f(self, a, b) + end + return a, b + end + --printf("Created M.%s out of %s", dst_name, table.concat(n, ', ')) + end +end + + +-------------------------------------------------------------------------------- +-- This one goes in the "wrong" direction, cannot be composed. +-------------------------------------------------------------------------------- +function CONV :function_to_bytecode(...) return string.dump(...) end + +function CONV :ast_to_src(...) + require 'metalua.package' -- ast_to_string isn't written in plain lua + return require 'metalua.compiler.ast_to_src' (...) +end + +local MT = { __index=CONV, __type='metalua.compiler' } + +function M.new() + local parser = require 'metalua.compiler.parser' .new() + local self = { parser = parser } + setmetatable(self, MT) + return self +end + +return M \ No newline at end of file
diff --git a/lib/metalua/ast_to_string.mlua b/metalua/compiler/ast_to_src.mlua similarity index 83% rename from lib/metalua/ast_to_string.mlua rename to metalua/compiler/ast_to_src.mlua index 33091c6..3eec4a5 100644 --- a/lib/metalua/ast_to_string.mlua +++ b/metalua/compiler/ast_to_src.mlua
@@ -17,13 +17,11 @@ -- ------------------------------------------------------------------------------- --{ extension 'match' } +-{ extension ('match', ...) } local M = { } M.__index = M -ast_to_string = |x| M.run(x) - -------------------------------------------------------------------------------- -- Instanciate a new AST->source synthetizer -------------------------------------------------------------------------------- @@ -134,7 +132,7 @@ local op_prec = { } for prec, ops in ipairs (op_preprec) do - for op in ivalues (ops) do + for _, op in ipairs (ops) do op_prec[op] = prec end end @@ -158,6 +156,7 @@ -------------------------------------------------------------------------------- function M:node (node) assert (self~=M and self._acc) + if node==nil then self:acc'<<error>>'; return end if not node.tag then -- tagless block. self:list (node, self.nl) else @@ -187,7 +186,7 @@ for i = start or 1, # list do self:node (list[i]) if list[i + 1] then - if not sep 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 @@ -279,6 +278,20 @@ self:list (lhs, ", ") self:acc " = " self:list (rhs, ", ") + | `Set{ lhs, rhs, annot } -> + -- ``... = ...'', no syntax sugar, annotation -- + local n = #lhs + for i=1,n do + local ell, a = lhs[i], annot[i] + self:node (ell) + if a then + self:acc ' #' + self:node(a) + end + if i~=n then self:acc ', ' end + end + self:acc " = " + self:list (rhs, ", ") end end @@ -353,17 +366,30 @@ 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 +function M:Local (node, lhs, rhs, annots) + if next (lhs) then + self:acc "local " + if annots then + local n = #lhs + for i=1, n do + self:node (lhs) + local a = annots[i] + if a then + self:acc ' #' + self:node (a) + end + if i~=n then self:acc ', ' end + end + else + self:list (lhs, ", ") + end + 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) @@ -441,14 +467,27 @@ 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" +function M:Function (node, params, body, annots) + self:acc "function (" + if annots then + local n = #params + for i=1,n do + local p, a = params[i], annots[i] + self:node(p) + if annots then + self:acc " #" + self:node(a) + end + if i~=n then self:acc ', ' end + end + else + self:list (params, ", ") + end + self:acc ")" + self:nlindent () + self:list (body, self.nl) + self:nldedent () + self:acc "end" end function M:Table (node) @@ -515,7 +554,7 @@ self:node (b) self:acc (right_paren and ")") - else -- unary operator. + else -- unary operator. local paren match a with | `Op{ op_a, ... } if op_prec[op] >= op_prec[op_a] -> paren = true @@ -570,3 +609,81 @@ end end + +M.TDyn = '*' +M.TDynbar = '**' +M.TPass = 'pass' +M.TField = 'field' +M.TIdbar = M.TId +M.TReturn = M.Return + + +function M:TId (node, name) self:acc(name) end + + +function M:TCatbar(node, te, tebar) + self:acc'(' + self:node(te) + self:acc'|' + self:tebar(tebar) + self:acc')' +end + +function M:TFunction(node, p, r) + self:tebar(p) + self:acc '->' + self:tebar(r) +end + +function M:TTable (node, default, pairs) + self:acc '[' + self:list (pairs, ', ') + if default.tag~='TField' then + self:acc '|' + self:node (default) + end + self:acc ']' +end + +function M:TPair (node, k, v) + self:node (k) + self:acc '=' + self:node (v) +end + +function M:TIdbar (node, name) + self :acc (name) +end + +function M:TCatbar (node, a, b) + self:node(a) + self:acc ' ++ ' + self:node(b) +end + +function M:tebar(node) + if node.tag then self:node(node) else + self:acc '(' + self:list(node, ', ') + self:acc ')' + end +end + +function M:TUnkbar(node, name) + self:acc '~~' + self:acc (name) +end + +function M:TUnk(node, name) + self:acc '~' + self:acc (name) +end + +for name, tag in pairs{ const='TConst', var='TVar', currently='TCurrently', just='TJust' } do + M[tag] = function(self, node, te) + self:acc (name..' ') + self:node(te) + end +end + +return (|x| M.run(x))
diff --git a/lib/verbose_require.lua b/metalua/compiler/bytecode.lua similarity index 70% copy from lib/verbose_require.lua copy to metalua/compiler/bytecode.lua index 0e07650..b3afbdb 100644 --- a/lib/verbose_require.lua +++ b/metalua/compiler/bytecode.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,17 +15,15 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +local compile = require 'metalua.compiler.bytecode.compile' +local ldump = require 'metalua.compiler.bytecode.ldump' -do - local xrequire, n, ind = require, 0, "| " - function require (x) - print(ind:rep(n).."/ require: "..x) - n=n+1 - local y = xrequire(x) - n=n-1 - print(ind:rep(n).."\\_") - return y - end -end +local M = { } + +M.ast_to_proto = compile.ast_to_proto +M.proto_to_bytecode = ldump.dump_string +M.proto_to_file = ldump.dump_file + +return M \ No newline at end of file
diff --git a/compiler/compile.lua b/metalua/compiler/bytecode/compile.lua similarity index 93% rename from compiler/compile.lua rename to metalua/compiler/bytecode/compile.lua index fc12ea5..1e2c972 100644 --- a/compiler/compile.lua +++ b/metalua/compiler/bytecode/compile.lua
@@ -17,10 +17,29 @@ -- Fabien Fleutot - Port to Lua 5.1, integration with Metalua -- ------------------------------------------------------------------------------- + +---------------------------------------------------------------------- +-- +-- WARNING! You're entering a hackish area, proceed at your own risks! +-- +-- This code partly results from the borrowing, then ruthless abuse, of +-- Yueliang's implementation of Lua 5.0 compiler. I claim +-- responsibility for all of the ugly, dirty stuff that you might spot +-- in it. +-- +-- Eventually, this code will be rewritten, either in Lua or more +-- probably in C. Meanwhile, if you're interested into digging +-- metalua's sources, this is not the best part to invest your time +-- on. +-- +-- End of warning. +-- +---------------------------------------------------------------------- + ---------------------------------------------------------------------- -- Metalua. -- --- Summary: Compile ASTs to Lua 5.1 VM function prototype. +-- Summary: Compile ASTs to Lua 5.1 VM function prototype. -- Largely based on: -- -- * Yueliang (http://luaforge.net/projects/yueliang), @@ -29,8 +48,16 @@ -- * Lua 5.1 sources (http://www.lua.org), src/lparser.c -- ---------------------------------------------------------------------- +-- +-- Copyright (c) 2006-2008, Fabien Fleutot <metalua@gmail.com>. +-- +-- This software is released under the MIT Licence, see licence.txt +-- for details. +-- +---------------------------------------------------------------------- -module ("bytecode", package.seeall) +local luaK = require 'metalua.compiler.bytecode.lcode' +local luaP = require 'metalua.compiler.bytecode.lopcodes' local debugf = function() end --local debugf=printf @@ -38,16 +65,18 @@ local stat = { } local expr = { } -MAX_INT = 2147483645 -- INT_MAX-2 for 32-bit systems (llimits.h) -MAXVARS = 200 -- (llimits.h) -MAXUPVALUES = 32 -- (llimits.h) -MAXPARAMS = 100 -- (llimits.h) -LUA_MAXPARSERLEVEL = 200 -- (llimits.h) +local M = { } + +M.MAX_INT = 2147483645 -- INT_MAX-2 for 32-bit systems (llimits.h) +M.MAXVARS = 200 -- (llimits.h) +M.MAXUPVALUES = 32 -- (llimits.h) +M.MAXPARAMS = 100 -- (llimits.h) +M.LUA_MAXPARSERLEVEL = 200 -- (llimits.h) -- from lobject.h -VARARG_HASARG = 1 -VARARG_ISVARARG = 2 -VARARG_NEEDSARG = 4 +M.VARARG_HASARG = 1 +M.VARARG_ISVARARG = 2 +M.VARARG_NEEDSARG = 4 local function hasmultret (k) return k=="VCALL" or k=="VVARARG" @@ -100,7 +129,7 @@ -- since [fs.nactvar] and [fs.freereg] aren't updated. ----------------------------------------------------------------------- local function registerlocalvar (fs, varname) - debugf("[locvar: %s = reg %i]", varname, fs.nlocvars) + --debugf("[locvar: %s = reg %i]", varname, fs.nlocvars) local f = fs.f f.locvars[fs.nlocvars] = { } -- LocVar f.locvars[fs.nlocvars].varname = varname @@ -280,7 +309,7 @@ ------------------------------------------------------------------------ -- FIXME: is there a need for f=fs.f? if yes, why not always using it? ------------------------------------------------------------------------ -function indexupvalue(fs, name, v) +local function indexupvalue(fs, name, v) local f = fs.f for i = 0, f.nups - 1 do if fs.upvalues[i].k == v.k and fs.upvalues[i].info == v.info then @@ -471,7 +500,7 @@ ------------------------------------------------------------------------ local function new_localvar (fs, name, n) assert (type (name) == "string") - if fs.nactvar + n > MAXVARS then error ("too many local vars") end + if fs.nactvar + n > M.MAXVARS then error ("too many local vars") end fs.actvar[fs.nactvar + n] = registerlocalvar (fs, name) --printf("[NEW_LOCVAR] %i = %s", fs.nactvar+n, name) end @@ -487,9 +516,9 @@ new_localvar (fs, ast_params[i][1], i-1) end -- from [code_param]: - --checklimit (fs, fs.nactvar, self.MAXPARAMS, "parameters") + --checklimit (fs, fs.nactvar, self.M.MAXPARAMS, "parameters") fs.f.numparams = fs.nactvar - fs.f.is_vararg = dots and VARARG_ISVARARG or 0 + fs.f.is_vararg = dots and M.VARARG_ISVARARG or 0 adjustlocalvars (fs, nparams) fs.f.numparams = fs.nactvar --FIXME vararg must be taken in account luaK:reserveregs (fs, fs.nactvar) -- reserve register for parameters @@ -524,7 +553,7 @@ ------------------------------------------------------------------------ local function enterlevel (fs) fs.nestlevel = fs.nestlevel + 1 - assert (fs.nestlevel <= LUA_MAXPARSERLEVEL, "too many syntax levels") + assert (fs.nestlevel <= M.LUA_MAXPARSERLEVEL, "too many syntax levels") end ------------------------------------------------------------------------ @@ -668,7 +697,7 @@ function stat.stat (fs, ast) if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end - -- debugf (" - Statement %s", disp.ast (ast) ) + --debugf (" - Statement %s", table.tostring (ast) ) if not ast.tag then chunk (fs, ast) else @@ -677,8 +706,7 @@ error ("A statement cannot have tag `"..ast.tag) end parser (fs, ast) end - --debugf (" - /Statement `%s", ast.tag or "<nil>") - debugf (" - /Statement `%s", ast.tag) + --debugf (" - /Statement `%s", ast.tag) end ------------------------------------------------------------------------ @@ -719,10 +747,10 @@ end first = fs.nactvar nret = luaK.LUA_MULTRET -- return all values - elseif nret == 1 then - --printf("[RETURN] 1 val: e=%s", tostringv(e)) - first = luaK:exp2anyreg(fs, e) - --printf("[RETURN] 1 val in reg %i", first) + elseif nret == 1 then + if ast[1].tag ~= "Error" then + first = luaK:exp2anyreg(fs, e) + end else --printf("* Return multiple vals in nextreg %i", fs.freereg) luaK:exp2nextreg(fs, e) -- values must go to the 'stack' @@ -1041,14 +1069,14 @@ if ast.lineinfo then fs.lastline = ast.lineinfo.last.line end - --debugf (" - Expression %s", tostringv (ast)) + --debugf (" - Expression %s", table.tostring (ast)) local parser = expr[ast.tag] if parser then parser (fs, ast, v) elseif not ast.tag then error ("No tag in expression "..table.tostring(ast, 'nohash', 80)) else error ("No parser for node `"..ast.tag) end - debugf (" - /`%s", ast.tag) + --debugf (" - /Expression `%s", ast.tag) end ------------------------------------------------------------------------ @@ -1071,8 +1099,8 @@ assert (fs.f.is_vararg ~= 0, "No vararg in this function") -- NEEDSARG flag is set if and only if the function is a vararg, -- but no vararg has been used yet in its code. - if fs.f.is_vararg < VARARG_NEEDSARG then - fs.f.is_varag = fs.f.is_vararg - VARARG_NEEDSARG end + if fs.f.is_vararg < M.VARARG_NEEDSARG then + fs.f.is_varag = fs.f.is_vararg - M.VARARG_NEEDSARG end init_exp (v, "VVARARG", luaK:codeABC (fs, "OP_VARARG", 0, 1, 0)) end @@ -1205,7 +1233,7 @@ -- "local foo = -{`Stat{...}}", variable foo will be messed up by -- the compilation of `Stat. -- FIX: save the active variables at indices >= nactvar in - -- save_actvar, and restore them after `Stat has been computer. + -- save_actvar, and restore them after `Stat has been computed. -- -- I use a while rather than for loops and length operators because -- fs.actvar is a 0-based array... @@ -1247,19 +1275,30 @@ --printf(" * End of Stat") end - +function expr.Error() end ------------------------------------------------------------------------ -- Main function: ast --> proto ------------------------------------------------------------------------ -function metalua_compile (ast, source) +function M.ast_to_proto (ast, source) local fs = open_func (nil) - fs.f.is_vararg = VARARG_ISVARARG + fs.f.is_vararg = M.VARARG_ISVARARG chunk (fs, ast) close_func (fs) assert (fs.prev == nil) assert (fs.f.nups == 0) assert (fs.nestlevel == 0) if source then fs.f.source = source end - return fs.f + return fs.f, source end + +local function Error (fs, ast, v) + local msg = string.format ("Error node in AST at position %s: %s", + tostring(ast.lineinfo), + table.tostring(ast, nohash)) + error(msg) +end + +expr.Error, stat.Error = Error, Error + +return M \ No newline at end of file
diff --git a/compiler/lcode.lua b/metalua/compiler/bytecode/lcode.lua similarity index 97% rename from compiler/lcode.lua rename to metalua/compiler/bytecode/lcode.lua index d6de77e..adabbb3 100644 --- a/compiler/lcode.lua +++ b/metalua/compiler/bytecode/lcode.lua
@@ -18,8 +18,28 @@ -- ------------------------------------------------------------------------------- +---------------------------------------------------------------------- +-- +-- WARNING! You're entering a hackish area, proceed at your own risks! +-- +-- This code results from the borrowing, then ruthless abuse, of +-- Yueliang's implementation of Lua 5.0 compiler. I claim +-- responsibility for all of the ugly, dirty stuff that you might spot +-- in it. +-- +-- Eventually, this code will be rewritten, either in Lua or more +-- probably in C. Meanwhile, if you're interested into digging +-- metalua's sources, this is not the best part to invest your time +-- on. +-- +-- End of warning. +-- +---------------------------------------------------------------------- + --[[-------------------------------------------------------------------- + $Id$ + lcode.lua Lua 5 code generator in Lua This file is part of Yueliang. @@ -52,11 +72,11 @@ -- luaK:sethvalue(o) (from lobject.h) ----------------------------------------------------------------------]] -module("bytecode", package.seeall) +local luaP = require 'metalua.compiler.bytecode.lopcodes' local function debugf() end -luaK = {} +local luaK = { } luaK.MAXSTACK = 250 -- (llimits.h, used in lcode.lua) luaK.LUA_MULTRET = -1 -- (lua.h) @@ -1032,3 +1052,5 @@ end fs.freereg = base + 1 end + +return luaK \ No newline at end of file
diff --git a/compiler/ldump.lua b/metalua/compiler/bytecode/ldump.lua similarity index 95% rename from compiler/ldump.lua rename to metalua/compiler/bytecode/ldump.lua index 05ace5c..01d1b97 100644 --- a/compiler/ldump.lua +++ b/metalua/compiler/bytecode/ldump.lua
@@ -18,6 +18,13 @@ -- ------------------------------------------------------------------------------- +---------------------------------------------------------------------- +-- +-- This code results from the borrowing, then ruthless abuse, of +-- Yueliang's implementation of Lua 5.0 compiler. +-- +---------------------------------------------------------------------- + --[[-------------------------------------------------------------------- ldump.lua @@ -56,9 +63,11 @@ -- luaU:ttype(o) (from lobject.h) ----------------------------------------------------------------------]] -module("bytecode", package.seeall) +local luaP = require 'metalua.compiler.bytecode.lopcodes' -format = { } +local M = { } + +local format = { } format.header = string.dump(function()end):sub(1, 12) format.little_endian, format.int_size, format.size_t_size, format.instr_size, @@ -70,7 +79,10 @@ assert(format.little_endian, "Big endian architectures not supported by dumper") --requires luaP -luaU = {} +local luaU = { } +M.luaU = luaU + +luaU.format = format -- constants used by dumper luaU.LUA_TNIL = 0 @@ -229,21 +241,21 @@ end ------------------------------------------------------------------------ --- dumps a 32-bit signed integer (for int) +-- dumps a signed integer of size `format.int_size` (for int) ------------------------------------------------------------------------ function luaU:DumpInt(x, D) self:DumpBlock(self:from_int(x, format.int_size), D) end ------------------------------------------------------------------------ --- dumps a 32-bit unsigned integer (for size_t) +-- dumps an unsigned integer of size `format.size_t_size` (for size_t) ------------------------------------------------------------------------ function luaU:DumpSize(x, D) self:DumpBlock(self:from_int(x, format.size_t_size), D) end ------------------------------------------------------------------------ --- dumps a LUA_NUMBER (hard-coded as a double) +-- dumps a LUA_NUMBER; can be an int or double depending on the VM. ------------------------------------------------------------------------ function luaU:DumpNumber(x, D) if format.integral then @@ -423,7 +435,7 @@ end -- FIXME: ugly concat-base generation in [make_setS], bufferize properly! -function dump_string (proto) +function M.dump_string (proto) local writer, buff = luaU:make_setS() luaU:dump (proto, writer, buff) return buff.data @@ -431,11 +443,13 @@ -- FIXME: [make_setS] sucks, perform synchronous file writing -- Now unused -function dump_file (proto, filename) +function M.dump_file (proto, filename) local writer, buff = luaU:make_setS() luaU:dump (proto, writer, buff) local file = io.open (filename, "wb") file:write (buff.data) io.close(file) if UNIX_SHARPBANG then os.execute ("chmod a+x "..filename) end -end \ No newline at end of file +end + +return M
diff --git a/compiler/lopcodes.lua b/metalua/compiler/bytecode/lopcodes.lua similarity index 95% rename from compiler/lopcodes.lua rename to metalua/compiler/bytecode/lopcodes.lua index 15d97f7..66ac329 100644 --- a/compiler/lopcodes.lua +++ b/metalua/compiler/bytecode/lopcodes.lua
@@ -18,8 +18,28 @@ -- ------------------------------------------------------------------------------- +---------------------------------------------------------------------- +-- +-- WARNING! You're entering a hackish area, proceed at your own risks! +-- +-- This code results from the borrowing, then ruthless abuse, of +-- Yueliang's implementation of Lua 5.0 compiler. I claim +-- responsibility for all of the ugly, dirty stuff that you might spot +-- in it. +-- +-- Eventually, this code will be rewritten, either in Lua or more +-- probably in C. Meanwhile, if you're interested into digging +-- metalua's sources, this is not the best part to invest your time +-- on. +-- +-- End of warning. +-- +---------------------------------------------------------------------- + --[[-------------------------------------------------------------------- + $Id$ + lopcodes.lua Lua 5 virtual machine opcodes in Lua This file is part of Yueliang. @@ -47,11 +67,9 @@ -- endian form and field size and positions are hard-coded ----------------------------------------------------------------------]] -module("bytecode", package.seeall) - local function debugf() end -luaP = { } +local luaP = { } --[[ =========================================================================== @@ -438,3 +456,5 @@ "0000102", -- OP_CLOSURE "0000101" -- OP_VARARG } + +return luaP \ No newline at end of file
diff --git a/metalua/compiler/globals.lua b/metalua/compiler/globals.lua new file mode 100644 index 0000000..e10b6cb --- /dev/null +++ b/metalua/compiler/globals.lua
@@ -0,0 +1,85 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +--*-lua-*----------------------------------------------------------------------- +-- Override Lua's default compilation functions, so that they support Metalua +-- rather than only plain Lua +-------------------------------------------------------------------------------- + +local mlc = require 'metalua.compiler' + +local M = { } + +-- Original versions +local original_lua_versions = { + load = load, + loadfile = loadfile, + loadstring = loadstring, + dofile = dofile } + +local lua_loadstring = loadstring +local lua_loadfile = loadfile + +function M.loadstring(str, name) + if type(str) ~= 'string' then error 'string expected' end + if str:match '^\027LuaQ' then return lua_loadstring(str) end + local n = str:match '^#![^\n]*\n()' + if n then str=str:sub(n, -1) end + -- FIXME: handle erroneous returns (return nil + error msg) + return mlc.new():src_to_function(str, name) +end + +function M.loadfile(filename) + local f, err_msg = io.open(filename, 'rb') + if not f then return nil, err_msg end + local success, src = pcall( f.read, f, '*a') + pcall(f.close, f) + if success then return M.loadstring (src, '@'..filename) + else return nil, src end +end + +function M.load(f, name) + while true do + local x = f() + if not x then break end + assert(type(x)=='string', "function passed to load() must return strings") + table.insert(acc, x) + end + return M.loadstring(table.concat(x)) +end + +function M.dostring(src) + local f, msg = M.loadstring(src) + if not f then error(msg) end + return f() +end + +function M.dofile(name) + local f, msg = M.loadfile(name) + if not f then error(msg) end + return f() +end + +-- Export replacement functions as globals +for name, f in pairs(M) do _G[name] = f end + +-- To be done *after* exportation +M.lua = original_lua_versions + +return M \ No newline at end of file
diff --git a/metalua/compiler/indent.lua b/metalua/compiler/indent.lua new file mode 100644 index 0000000..2b961de --- /dev/null +++ b/metalua/compiler/indent.lua
@@ -0,0 +1,255 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- Copyright (c) 2011, 2012 Sierra Wireless and others. +-- All rights reserved. This program and the accompanying materials +-- are made available under the terms of the Eclipse Public License v1.0 +-- which accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- Contributors: +-- Sierra Wireless - initial API and implementation +------------------------------------------------------------------------------- + +--- +-- Uses Metalua capabilities to indent code and provide source code offset +-- semantic depth +-- +-- @module luaformatter +local M = {} +local mlc = require 'metalua.compiler' +local math = require 'math' +local walk = require 'metalua.walk' + +--- +-- calculate all ident level +-- @param Source code to analyze +-- @return #table {linenumber = identationlevel} +-- @usage local depth = format.indentLevel("local var") +local function getindentlevel(source,indenttable) + + local function getfirstline(node) + -- Regular node + local offsets = node[1].lineinfo + local first + local offset + -- Consider previous comments as part of current chunk + -- WARNING: This is NOT the default in Metalua + if offsets.first.comments then + first = offsets.first.comments.lineinfo.first.line + offset = offsets.first.comments.lineinfo.first.offset + else + first = offsets.first.line + offset = offsets.first.offset + end + return first, offset + end + + local function getlastline(node) + -- Regular node + local offsets = node[#node].lineinfo + local last + -- Same for block end comments + if offsets.last.comments then + last = offsets.last.comments.lineinfo.last.line + else + last = offsets.last.line + end + return last + end + + -- + -- Define AST walker + -- + local linetodepth = { 0 } + local walker = { + block = { }, + expr = { }, + depth = 0, -- Current depth while walking + } + + function walker.block.down(node, parent,...) + --ignore empty node + if #node == 0 then + return end + -- get first line of the block + local startline,startoffset = getfirstline(node) + local endline = getlastline(node) + -- If the block doesn't start with a new line, don't indent the first line + if not source:sub(1,startoffset-1):find("[\r\n]%s*$") then + startline = startline + 1 + end + for i=startline, endline do + linetodepth[i]=walker.depth + end + walker.depth = walker.depth + 1 + end + + function walker.block.up(node, ...) + if #node == 0 then + return end + walker.depth = walker.depth - 1 + end + + function walker.expr.down(node, parent, ...) + if indenttable and node.tag == 'Table' then + if #node == 0 then + return end + local startline,startoffset = getfirstline(node) + local endline = getlastline(node) + if source:sub(1,startoffset-1):find("[\r\n]%s*$") then + for i=startline, endline do + linetodepth[i]=walker.depth + end + else + for i=startline+1, endline do + linetodepth[i]=walker.depth + end + end + walker.depth = walker.depth + 1 + elseif node.tag =='String' then + local firstline = node.lineinfo.first.line + local lastline = node.lineinfo.last.line + for i=firstline+1, lastline do + linetodepth[i]=false + end + end + end + + function walker.expr.up(node, parent, ...) + if indenttable and node.tag == 'Table' then + if #node == 0 then + return end + walker.depth = walker.depth - 1 + end + end + + -- Walk through AST to build linetodepth + local ast = mlc.src_to_ast(source) + mlc.check_ast(ast) + walk.block(walker, ast) + return linetodepth +end + +--- +-- Trim white spaces before and after given string +-- +-- @usage local trimmedstr = trim(' foo') +-- @param #string string to trim +-- @return #string string trimmed +local function trim(string) + local pattern = "^(%s*)(.*)" + local _, strip = string:match(pattern) + if not strip then return string end + local restrip + _, restrip = strip:reverse():match(pattern) + return restrip and restrip:reverse() or strip +end + +--- +-- Indent Lua Source Code. +-- @function [parent=#luaformatter] indentCode +-- @param source source code to format +-- @param delimiter line delimiter to use, usually '\n' or '\r\n' +-- @param indentTable boolean: whether table content must be indented +-- @param tab either a string representing a number of indentation, or the number +-- of spaces taken by a tab (often 8 or 4) +-- @param indentationSize if given, an indentation of depth `n` shifts the code +-- `indentationSize * n` chars to the right, with a mix of chars and spaces. +-- `tab` must then be a number +-- @return #string formatted code +-- @usage indentCode('local var', '\n', true, '\t') +-- @usage indentCode('local var', '\n', true, --[[tabulationSize]]4, --[[indentationSize]]2) +function M.indentcode(source, delimiter, indenttable, tab, indentationSize) + checks('string', 'string', '?', 'number|string', '?numer') + + -- function: generates a string which moves `depth` indentation levels from the left. + local tabulation + if indentationSize then + local tabSize = assert(tonumber(tab)) + -- When tabulation size and indentation size are given, + -- tabulate with a mix of tabs and spaces + tabulation = function(depth) + local range = depth * indentationSize + local tabCount = math.floor(range / tabSize) + local spaceCount = range % tabSize + return string.rep('\t', tabCount) .. string.rep(' ', spaceCount) + end + else + if type(tab)=='number' then tab = string.rep(' ', tab) end + tabulation = function (depth) return tab :rep (depth) end + end + + -- Delimiter position table: positions[x] is the offset of the first character + -- of the n-th delimiter in the source + local positions = { 1-#delimiter } + local a, b = nil, 0 + repeat + a, b = source :find (delimiter, b+1, true) + if a then table.insert (positions, a) end + until not a + + -- Don't try to indent a single line! + if #positions < 2 then return source end + + -- calculate the line number -> indentation correspondence table + local linetodepth = getindentlevel(source,indenttable) + + -- Concatenate string with right identation + local indented = { } + for position=1, #positions do + -- Extract source code line + local offset = positions[position] + -- Get the interval between two positions + local rawline + if positions[position + 1] then + rawline = source:sub(offset + delimiterLength, positions[position + 1] -1) + else + -- From current prosition to end of line + rawline = source:sub(offset + delimiterLength) + end + + -- Trim white spaces + local indentcount = linetodepth[position] + if not indentcount then + indented[#indented+1] = rawline + else + local line = trim(rawline) + -- Append right indentation + -- Indent only when there is code on the line + if line:len() > 0 then + -- Compute next real depth related offset + -- As is offset is pointing a white space before first statement of block, + -- We will work with parent node depth + indented[#indented+1] = tabulation( indentcount) + -- Append timmed source code + indented[#indented+1] = line + end + end + -- Append carriage return + -- While on last character append carriage return only if at end of original source + if position < #positions or source:sub(source:len()-delimiterLength, source:len()) == delimiter then + indented[#indented+1] = delimiter + end + end + return table.concat(indented) +end + +return M
diff --git a/metalua/compiler/parser.lua b/metalua/compiler/parser.lua new file mode 100644 index 0000000..3809c25 --- /dev/null +++ b/metalua/compiler/parser.lua
@@ -0,0 +1,62 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +-- Export all public APIs from sub-modules, squashed into a flat spacename + +local MT = { __type='metalua.compiler.parser' } + +local function new() + local mod_names = { "common", "expr", "lexer", "meta", "misc", "stat", "table", "ext", "annot" } + + for name, _ in pairs(package.loaded) do + local x = name :match '^metalua.compiler.parser.(.*)' + if x then + local found = false + for _, y in pairs(mod_names) do + if x==y then found=true; break end + end + --if found then print (">> found "..x) + --else print(">> not found: "..x) end + end + end + + -- Unload parser modules + for _, mod_name in ipairs(mod_names) do + package.loaded["metalua.compiler.parser."..mod_name] = nil + end + + local M = require 'metalua.compiler.parser.common' + + for _, mod_name in ipairs(mod_names) do + -- TODO: expose sub-modules as nested tables? + -- Not sure: it might be confusing, will clash with API names, e.g. for expr + local mod = require ("metalua.compiler.parser."..mod_name) + assert (type (mod) == 'table') + for api_name, val in pairs(mod) do + assert(not M[api_name]) + M[api_name] = val + end + end + + -- TODO: remove or make somehow optional the 'ext' module + + return setmetatable(M, MT) +end + +return { new = new }
diff --git a/metalua/compiler/parser/annot.lua b/metalua/compiler/parser/annot.lua new file mode 100644 index 0000000..ab2fa59 --- /dev/null +++ b/metalua/compiler/parser/annot.lua
@@ -0,0 +1,138 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local gg = require 'metalua.grammar.generator' +local misc = require 'metalua.compiler.parser.misc' +local mlp = require 'metalua.compiler.parser.common' +local lexer = require 'metalua.compiler.parser.lexer' +local M = { } + +lexer.lexer :add '->' + +function M.tid(lx) + local w = lx :next() + local t = w.tag + if t=='Keyword' and w[1] :match '^[%a_][%w_]*$' or w.tag=='Id' then + return {tag='TId'; lineinfo=w.lineinfo; w[1]} + else return gg.parse_error (lx, 'tid expected') end +end + +local function expr(...) return mlp.expr(...) end + +local function te(...) return M.te(...) end + +local field_types = { var='TVar'; const='TConst'; + currently='TCurrently'; field='TField' } + +function M.tf(lx) + local tk = lx:next() + local w = tk[1] + local tag = field_types[w] + if not tag then error ('Invalid field type '..w) + elseif tag=='TField' then return {tag='TField'} else + local te = M.te(lx) + return {tag=tag; te} + end +end + +local tebar_content = gg.list{ + name = 'tebar content', + primary = te, + separators = { ",", ";" }, + terminators = ")" } + +M.tebar = gg.multisequence{ + name = 'annot.tebar', + --{ '*', builder = 'TDynbar' }, -- maybe not user-available + { '(', tebar_content, ')', + builder = function(x) return x[1] end }, + { te } +} + +M.te = gg.multisequence{ + name = 'annot.te', + { M.tid, builder=function(x) return x[1] end }, + { '*', builder = 'TDyn' }, + { "[", + gg.list{ + primary = gg.sequence{ + expr, "=", M.tf, + builder = 'TPair' + }, + separators = { ",", ";" }, + terminators = { "]", "|" } }, + gg.onkeyword{ "|", M.tf }, + "]", + builder = function(x) + local fields, other = unpack(x) + return { tag='TTable', other or {tag='TField'}, fields } + end + }, -- "[ ... ]" + { '(', tebar_content, ')', '->', '(', tebar_content, ')', + builder = function(x) + local p, r = unpack(x) + return {tag='TFunction', p, r } + end } } + + +M.ts = gg.multisequence{ + name = 'annot.ts', + { 'return', tebar_content, builder='TReturn' }, + { M.tid, builder = function(x) + if x[1][1]=='pass' then return {tag='TPass'} + else error "Bad statement type" end + end } } + + +-- TODO: add parsers for statements: +-- #return tebar +-- #alias = te +-- #ell = tf + +M.stat_annot = gg.sequence{ + gg.list{ primary=M.tid, separators='.' }, + '=', + M.annot, + builder = 'Annot' } + +function M.opt(primary, a_type) + checks('table|function', 'string') + return gg.sequence{ + primary, + gg.onkeyword{ "#", assert(M[a_type]) }, + builder = function(x) + local t, annot = unpack(x) + return annot and { tag='Annot', t, annot } or t + end } +end + +-- split a list of "foo" and "`Annot{foo, annot}" into a list of "foo" +-- and a list of "annot". +-- No annot list is returned if none of the elements were annotated. +function M.split(lst) + local x, a, some = { }, { }, false + for i, p in ipairs(lst) do + if p.tag=='Annot' then + some, x[i], a[i] = true, unpack(p) + else x[i] = p end + end + if some then return x, a else return lst end +end + +return M \ No newline at end of file
diff --git a/lib/verbose_require.lua b/metalua/compiler/parser/common.lua similarity index 64% copy from lib/verbose_require.lua copy to metalua/compiler/parser/common.lua index 0e07650..1d6290e 100644 --- a/lib/verbose_require.lua +++ b/metalua/compiler/parser/common.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,17 +15,13 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- +-- Shared common parser table. It will be filled by parser.init(), +-- and every other module will be able to call its elements at runtime. +-- +-- If the table was directly created in parser.init, a circular +-- dependency would be created: parser.init depends on other modules to fill the table, +-- so other modules can't simultaneously depend on it. -do - local xrequire, n, ind = require, 0, "| " - function require (x) - print(ind:rep(n).."/ require: "..x) - n=n+1 - local y = xrequire(x) - n=n-1 - print(ind:rep(n).."\\_") - return y - end -end +return { } \ No newline at end of file
diff --git a/metalua/compiler/parser/expr.lua b/metalua/compiler/parser/expr.lua new file mode 100644 index 0000000..935d2c9 --- /dev/null +++ b/metalua/compiler/parser/expr.lua
@@ -0,0 +1,208 @@ +------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +------------------------------------------------------------------------------- + +------------------------------------------------------------------------------- +-- +-- Exported API: +-- * [mlp.expr()] +-- * [mlp.expr_list()] +-- * [mlp.func_val()] +-- +------------------------------------------------------------------------------- + +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser.common' +local M = { } + +local mlp_table = require 'metalua.compiler.parser.table' +local mlp_meta = require 'metalua.compiler.parser.meta' +local mlp_misc = require 'metalua.compiler.parser.misc' +local annot = require 'metalua.compiler.parser.annot' + +-- Delayed dependencies toward externally-defined parsers +local function block (lx) return mlp.block (lx) end +local function stat (lx) return mlp.stat (lx) end + +-- For recursive definitions +local function expr (lx) return M.expr (lx) end + +local id = mlp_misc.id + + +-------------------------------------------------------------------------------- +-- Non-empty expression list. Actually, this isn't used here, but that's +-- handy to give to users. +-------------------------------------------------------------------------------- +M.expr_list = gg.list{ primary=expr, separators="," } + +-------------------------------------------------------------------------------- +-- Helpers for function applications / method applications +-------------------------------------------------------------------------------- +M.func_args_content = gg.list{ + name = "function arguments", + primary = expr, + separators = ",", + terminators = ")" } + +-- Used to parse methods +M.method_args = gg.multisequence{ + name = "function argument(s)", + { "{", mlp_table.content, "}" }, + { "(", M.func_args_content, ")", builder = unpack }, + { "+{", mlp_meta.quote_content, "}" }, + function(lx) local r = mlp.opt_string(lx); return r and {r} or { } end } + +-------------------------------------------------------------------------------- +-- [func_val] parses a function, from opening parameters parenthese to +-- "end" keyword included. Used for anonymous functions as well as +-- function declaration statements (both local and global). +-- +-- It's wrapped in a [_func_val] eta expansion, so that when expr +-- parser uses the latter, they will notice updates of [func_val] +-- definitions. +-------------------------------------------------------------------------------- +M.func_params_content = gg.list{ name="function parameters", + gg.multisequence{ { "...", builder = "Dots" }, annot.opt(id, 'te') }, + separators = ",", terminators = {")", "|"} } + +M.func_val = gg.sequence{ name="function body", + "(", M.func_params_content, ")", block, "end", + builder = function(x) + local params, body = unpack(x) + local annots, some = { }, false + for i, p in ipairs(params) do + if p.tag=='Annot' then + params[i], annots[i], some = p[1], p[2], true + else annots[i] = false end + end + if some then return { tag='Function', params, body, annots } + else return { tag='Function', params, body } end + end } + +local func_val = function(lx) return M.func_val(lx) end + +-------------------------------------------------------------------------------- +-- Default parser for primary expressions +-------------------------------------------------------------------------------- +function M.id_or_literal (lx) + local a = lx:next() + if a.tag~="Id" and a.tag~="String" and a.tag~="Number" then + local msg + if a.tag=='Eof' then + msg = "End of file reached when an expression was expected" + elseif a.tag=='Keyword' then + msg = "An expression was expected, and `"..a[1].. + "' can't start an expression" + else + msg = "Unexpected expr token " .. table.tostring (a, 'nohash') + end + return gg.parse_error (lx, msg) + end + return a +end + + +-------------------------------------------------------------------------------- +-- Builder generator for operators. Wouldn't be worth it if "|x|" notation +-- were allowed, but then lua 5.1 wouldn't compile it +-------------------------------------------------------------------------------- + +-- opf1 = |op| |_,a| `Op{ op, a } +local function opf1 (op) return + function (_,a) return { tag="Op", op, a } end end + +-- opf2 = |op| |a,_,b| `Op{ op, a, b } +local function opf2 (op) return + function (a,_,b) return { tag="Op", op, a, b } end end + +-- opf2r = |op| |a,_,b| `Op{ op, b, a } -- (args reversed) +local function opf2r (op) return + function (a,_,b) return { tag="Op", op, b, a } end end + +local function op_ne(a, _, b) + -- This version allows to remove the "ne" operator from the AST definition. + -- However, it doesn't always produce the exact same bytecode as Lua 5.1. + return { tag="Op", "not", + { tag="Op", "eq", a, b, lineinfo= { + first = a.lineinfo.first, last = b.lineinfo.last } } } +end + + +-------------------------------------------------------------------------------- +-- +-- complete expression +-- +-------------------------------------------------------------------------------- + +-- FIXME: set line number. In [expr] transformers probably + +M.expr = gg.expr { name = "expression", + + primary = gg.multisequence{ name="expr primary", + { "(", expr, ")", builder = "Paren" }, + { "function", func_val, builder = unpack }, + { "-{", mlp_meta.splice_content, "}", builder = unpack }, + { "+{", mlp_meta.quote_content, "}", builder = unpack }, + { "nil", builder = "Nil" }, + { "true", builder = "True" }, + { "false", builder = "False" }, + { "...", builder = "Dots" }, + mlp_table.table, + M.id_or_literal }, + + infix = { name="expr infix op", + { "+", prec = 60, builder = opf2 "add" }, + { "-", prec = 60, builder = opf2 "sub" }, + { "*", prec = 70, builder = opf2 "mul" }, + { "/", prec = 70, builder = opf2 "div" }, + { "%", prec = 70, builder = opf2 "mod" }, + { "^", prec = 90, builder = opf2 "pow", assoc = "right" }, + { "..", prec = 40, builder = opf2 "concat", assoc = "right" }, + { "==", prec = 30, builder = opf2 "eq" }, + { "~=", prec = 30, builder = op_ne }, + { "<", prec = 30, builder = opf2 "lt" }, + { "<=", prec = 30, builder = opf2 "le" }, + { ">", prec = 30, builder = opf2r "lt" }, + { ">=", prec = 30, builder = opf2r "le" }, + { "and",prec = 20, builder = opf2 "and" }, + { "or", prec = 10, builder = opf2 "or" } }, + + prefix = { name="expr prefix op", + { "not", prec = 80, builder = opf1 "not" }, + { "#", prec = 80, builder = opf1 "len" }, + { "-", prec = 80, builder = opf1 "unm" } }, + + suffix = { name="expr suffix op", + { "[", expr, "]", builder = function (tab, idx) + return {tag="Index", tab, idx[1]} end}, + { ".", id, builder = function (tab, field) + return {tag="Index", tab, mlp_misc.id2string(field[1])} end }, + { "(", M.func_args_content, ")", builder = function(f, args) + return {tag="Call", f, unpack(args[1])} end }, + { "{", mlp_table.content, "}", builder = function (f, arg) + return {tag="Call", f, arg[1]} end}, + { ":", id, M.method_args, builder = function (obj, post) + local m_name, args = unpack(post) + return {tag="Invoke", obj, mlp_misc.id2string(m_name), unpack(args)} end}, + { "+{", mlp_meta.quote_content, "}", builder = function (f, arg) + return {tag="Call", f, arg[1] } end }, + default = { name="opt_string_arg", parse = mlp_misc.opt_string, builder = function(f, arg) + return {tag="Call", f, arg } end } } } + +return M \ No newline at end of file
diff --git a/compiler/mlp_ext.lua b/metalua/compiler/parser/ext.lua similarity index 65% rename from compiler/mlp_ext.lua rename to metalua/compiler/parser/ext.lua index 021a72f..294fb8a 100644 --- a/compiler/mlp_ext.lua +++ b/metalua/compiler/parser/ext.lua
@@ -23,57 +23,69 @@ -- -------------------------------------------------------------------------------- -module ("mlp", package.seeall) +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser.common' +local mlp_lexer = require 'metalua.compiler.parser.lexer' +local mlp_expr = require 'metalua.compiler.parser.expr' +local mlp_stat = require 'metalua.compiler.parser.stat' +local mlp_misc = require 'metalua.compiler.parser.misc' + +local expr = mlp_expr.expr + +local M = { } -------------------------------------------------------------------------------- -- Alebraic Datatypes -------------------------------------------------------------------------------- local function adt (lx) - local tagval = id (lx) [1] + local node = mlp_misc.id (lx) + local tagval = node[1] local tagkey = {tag="Pair", {tag="String", "tag"}, {tag="String", tagval} } if lx:peek().tag == "String" or lx:peek().tag == "Number" then return { tag="Table", tagkey, lx:next() } elseif lx:is_keyword (lx:peek(), "{") then - local x = table (lx) - _G.table.insert (x, 1, tagkey) + local x = mlp.table (lx) + table.insert (x, 1, tagkey) return x else return { tag="Table", tagkey } end end -expr:add{ "`", adt, builder = fget(1) } +M.adt = gg.sequence{ "`", adt, builder = unpack } + +expr.primary :add(M.adt) -------------------------------------------------------------------------------- -- Anonymous lambda -------------------------------------------------------------------------------- -local lambda_expr = gg.sequence{ - "|", func_params_content, "|", expr, - builder= function (x) +M.lambda_expr = gg.sequence{ + "|", mlp_expr.func_params_content, "|", expr, + builder = function (x) local li = x[2].lineinfo - return { tag="Function", x[1], + return { tag="Function", x[1], { {tag="Return", x[2], lineinfo=li }, lineinfo=li } } end } -- In an earlier version, lambda_expr took an expr_list rather than an expr -- after the 2nd bar. However, it happened to be much more of a burden than an --- help, So finally I disabled it. If you want to return several results, +-- help, So finally I disabled it. If you want to return several results, -- use the long syntax. -------------------------------------------------------------------------------- --- local lambda_expr = gg.sequence{ +-- local lambda_expr = gg.sequence{ -- "|", func_params_content, "|", expr_list, --- builder= function (x) +-- builder= function (x) -- return {tag="Function", x[1], { {tag="Return", unpack(x[2]) } } } end } -expr:add (lambda_expr) +expr.primary :add (M.lambda_expr) -------------------------------------------------------------------------------- -- Allows to write "a `f` b" instead of "f(a, b)". Taken from Haskell. -- This is not part of Lua 5.1 syntax, so it's added to the expression -- afterwards, so that it's easier to disable. -------------------------------------------------------------------------------- -local function expr_in_backquotes (lx) return expr(lx, 35) end +function M.expr_in_backquotes (lx) return expr(lx, 35) end -expr.infix:add{ name = "infix function", - "`", expr_in_backquotes, "`", prec = 35, assoc="left", +expr.infix :add{ name = "infix function", + "`", M.expr_in_backquotes, "`", prec = 35, assoc="left", builder = function(a, op, b) return {tag="Call", op[1], a, b} end } @@ -81,28 +93,34 @@ -- table.override assignment -------------------------------------------------------------------------------- -mlp.lexer:add "<-" -stat.assignments["<-"] = function (a, b) - assert( #a==1 and #b==1, "No multi-args for '<-'") +mlp_lexer.lexer:add "<-" +mlp_stat.stat.assignments["<-"] = function (a, b) + assert( #a==1 and #b==1, "No multi-args for '<-'") return { tag="Call", { tag="Index", { tag="Id", "table" }, { tag="String", "override" } }, - a[1], b[1]} + a[1], b[1]} end -------------------------------------------------------------------------------- -- C-style op+assignments +-- TODO: no protection against side-effects in LHS vars. -------------------------------------------------------------------------------- -local function op_assign(kw, op) +local function op_assign(kw, op) local function rhs(a, b) - return { tag="Op", op, a, b } + return { tag="Op", op, a, b } end - local function f(a,b) - return { tag="Set", a, _G.table.imap(rhs, a, b) } + local function f(a,b) + if #a ~= #b then return gg.parse_error "assymetric operator+assignment" end + local right = { } + local r = { tag="Set", a, right } + for i=1, #a do right[i] = { tag="Op", op, a[i], b[i] } end + return r end mlp.lexer:add (kw) mlp.stat.assignments[kw] = f end -_G.table.iforeach (op_assign, - {"+=", "-=", "*=", "/="}, - {"add", "sub", "mul", "div"}) \ No newline at end of file +local ops = { add='+='; sub='-='; mul='*='; div='/=' } +for ast_op_name, keyword in pairs(ops) do op_assign(keyword, ast_op_name) end + +return M \ No newline at end of file
diff --git a/compiler/mlp_lexer.lua b/metalua/compiler/parser/lexer.lua similarity index 70% rename from compiler/mlp_lexer.lua rename to metalua/compiler/parser/lexer.lua index 70ad94c..7f93f8a 100644 --- a/compiler/mlp_lexer.lua +++ b/metalua/compiler/parser/lexer.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,20 +15,23 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- ---------------------------------------------------------------------- +-- (Meta)lua-specific lexer, derived from the generic lexer. +---------------------------------------------------------------------- -- --- Summary: Source file lexer. ~~Currently only works on strings. --- Some API refactoring is needed. +-- Copyright (c) 2006-2012, Fabien Fleutot <metalua@gmail.com>. +-- +-- This software is released under the MIT Licence, see licence.txt +-- for details. -- ---------------------------------------------------------------------- -module ("mlp", package.seeall) +local generic_lexer = require 'metalua.grammar.lexer' +local M = { } -require "lexer" - -local mlp_lexer = lexer.lexer:clone() +M.lexer = generic_lexer.lexer :clone() local keywords = { "and", "break", "do", "else", "elseif", @@ -41,6 +44,6 @@ "::", -- Lua5,2 "+{", "-{" } -for w in values(keywords) do mlp_lexer:add(w) end +for _, w in ipairs(keywords) do M.lexer :add (w) end -_M.lexer = mlp_lexer +return M
diff --git a/compiler/mlp_meta.lua b/metalua/compiler/parser/meta.lua similarity index 74% rename from compiler/mlp_meta.lua rename to metalua/compiler/parser/meta.lua index 578dfd5..396df85 100644 --- a/compiler/mlp_meta.lua +++ b/metalua/compiler/parser/meta.lua
@@ -17,11 +17,11 @@ -- ------------------------------------------------------------------------------- ----------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- Summary: Meta-operations: AST quasi-quoting and splicing -- ----------------------------------------------------------------------- +------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- @@ -31,7 +31,9 @@ -- -------------------------------------------------------------------------------- -module ("mlp", package.seeall) +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser.common' +local M = { } -------------------------------------------------------------------------------- -- External splicing: compile an AST into a chunk, load and evaluate @@ -39,37 +41,41 @@ -- an AST). -------------------------------------------------------------------------------- -function splice (ast) - local f = mlc.ast_to_function(ast, '=splice') - local result=f() - return result +function M.splice (ast) + -- TODO: should there be one mlc per splice, or per parser instance? + local mlc = require 'metalua.compiler'.new() + local f = mlc :ast_to_function(ast, '=splice') + local result=f(mlp) + return result end -------------------------------------------------------------------------------- -- Going from an AST to an AST representing that AST -- the only key being lifted in this version is ["tag"] -------------------------------------------------------------------------------- -function quote (t) - --print("QUOTING:", _G.table.tostring(t, 60)) +function M.quote (t) + --print("QUOTING:", table.tostring(t, 60,'nohash')) local cases = { } function cases.table (t) local mt = { tag = "Table" } - --_G.table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } }) + --table.insert (mt, { tag = "Pair", quote "quote", { tag = "True" } }) if t.tag == "Splice" then assert (#t==1, "Invalid splice") local sp = t[1] return sp elseif t.tag then - _G.table.insert (mt, { tag = "Pair", quote "tag", quote (t.tag) }) + table.insert (mt, { tag="Pair", M.quote "tag", M.quote(t.tag) }) end for _, v in ipairs (t) do - _G.table.insert (mt, quote(v)) + table.insert (mt, M.quote(v)) end return mt end function cases.number (t) return { tag = "Number", t, quote = true } end function cases.string (t) return { tag = "String", t, quote = true } end - return cases [ type (t) ] (t) + function cases.boolean (t) return { tag = t and "True" or "False", t, quote = true } end + local f = cases [type(t)] + if f then return f(t) else error ("Cannot quote an AST containing "..tostring(t)) end end -------------------------------------------------------------------------------- @@ -78,21 +84,22 @@ -- parsing data inside a quasiquote), [-{foo}] is replaced by -- [`Splice{foo}], which will be unpacked by [quote()]. -------------------------------------------------------------------------------- -in_a_quote = false +M.in_a_quote = false -------------------------------------------------------------------------------- -- Parse the inside of a "-{ ... }" -------------------------------------------------------------------------------- -function splice_content (lx) +function M.splice_content (lx) local parser_name = "expr" if lx:is_keyword (lx:peek(2), ":") then local a = lx:next() lx:next() -- skip ":" assert (a.tag=="Id", "Invalid splice parser name") parser_name = a[1] - end - local ast = mlp[parser_name](lx) - if in_a_quote then + end + local parser = require 'metalua.compiler.parser'.new() + local ast = parser [parser_name](lx) + if M.in_a_quote then --printf("SPLICE_IN_QUOTE:\n%s", _G.table.tostring(ast, "nohash", 60)) return { tag="Splice", ast } else @@ -101,28 +108,29 @@ elseif parser_name ~= "block" then error ("splice content must be an expr, stat or block") end --printf("EXEC THIS SPLICE:\n%s", _G.table.tostring(ast, "nohash", 60)) - return splice (ast) + return M.splice (ast) end end -------------------------------------------------------------------------------- -- Parse the inside of a "+{ ... }" -------------------------------------------------------------------------------- -function quote_content (lx) - local parser +function M.quote_content (lx) + local parser if lx:is_keyword (lx:peek(2), ":") then -- +{parser: content } - parser = mlp[id(lx)[1]] + parser = mlp[mlp.id(lx)[1]] lx:next() else -- +{ content } parser = mlp.expr end - local prev_iq = in_a_quote - in_a_quote = true + local prev_iq = M.in_a_quote + M.in_a_quote = true --print("IN_A_QUOTE") local content = parser (lx) - local q_content = quote (content) - in_a_quote = prev_iq + local q_content = M.quote (content) + M.in_a_quote = prev_iq return q_content end +return M
diff --git a/compiler/mlp_misc.lua b/metalua/compiler/parser/misc.lua similarity index 72% rename from compiler/mlp_misc.lua rename to metalua/compiler/parser/misc.lua index dcde657..04c4f98 100644 --- a/compiler/mlp_misc.lua +++ b/metalua/compiler/parser/misc.lua
@@ -17,11 +17,11 @@ -- ------------------------------------------------------------------------------- ---------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- Summary: metalua parser, miscellaneous utility functions. -- ----------------------------------------------------------------------- +------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- @@ -30,17 +30,19 @@ -- * [mlp.id()] -- * [mlp.opt_id()] -- * [mlp.id_list()] --- * [mlp.gensym()] -- * [mlp.string()] -- * [mlp.opt_string()] -- * [mlp.id2string()] -- -------------------------------------------------------------------------------- ---require "gg" ---require "mll" +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser.common' +local mlp_meta = require 'metalua.compiler.parser.meta' +local M = { } -module ("mlp", package.seeall) + +local splice = gg.sequence{ "-{", mlp_meta.splice_content, "}", builder=unpack } -------------------------------------------------------------------------------- -- returns a function that takes the [n]th element of a table. @@ -53,7 +55,7 @@ -- a lightweight syntax. -------------------------------------------------------------------------------- -function fget (n, tag) +function M.fget (n, tag) assert (type (n) == "number") if tag then assert (type (tag) == "string") @@ -70,14 +72,14 @@ -- Try to read an identifier (possibly as a splice), or return [false] if no -- id is found. -------------------------------------------------------------------------------- -function opt_id (lx) +function M.opt_id (lx) local a = lx:peek(); if lx:is_keyword (a, "-{") then - local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1] - if v.tag ~= "Id" and v.tag ~= "Splice" then - return gg.parse_error(lx, "Bad id splice") - end - return v + local v = splice(lx) + if v.tag ~= "Id" and v.tag ~= "Splice" then + return gg.parse_error(lx, "Bad id splice") + end + return v elseif a.tag == "Id" then return lx:next() else return false end end @@ -85,38 +87,24 @@ -------------------------------------------------------------------------------- -- Mandatory reading of an id: causes an error if it can't read one. -------------------------------------------------------------------------------- -function id (lx) - return opt_id (lx) or gg.parse_error(lx,"Identifier expected") +function M.id (lx) + return M.opt_id (lx) or gg.parse_error(lx,"Identifier expected") end -------------------------------------------------------------------------------- -- Common helper function -------------------------------------------------------------------------------- -id_list = gg.list { primary = mlp.id, separators = "," } - --------------------------------------------------------------------------------- --- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier. --- The main purpose is to avoid variable capture in macros. --- --- If a string is passed as an argument, theis string will be part of the --- id name (helpful for macro debugging) --------------------------------------------------------------------------------- -local gensymidx = 0 - -function gensym (arg) - gensymidx = gensymidx + 1 - return { tag="Id", _G.string.format(".%i.%s", gensymidx, arg or "")} -end +M.id_list = gg.list { primary = M.id, separators = "," } -------------------------------------------------------------------------------- -- Converts an identifier into a string. Hopefully one day it'll handle -- splices gracefully, but that proves quite tricky. -------------------------------------------------------------------------------- -function id2string (id) +function M.id2string (id) --print("id2string:", disp.ast(id)) if id.tag == "Id" then id.tag = "String"; return id elseif id.tag == "Splice" then - assert (in_a_quote, "can't do id2string on an outermost splice") + assert (mlp_meta.in_a_quote, "can't do id2string on an outermost splice") error ("id2string on splice not implemented") -- Evaluating id[1] will produce `Id{ xxx }, -- and we want it to produce `String{ xxx } @@ -126,17 +114,17 @@ return {tag="String", {tag="Index", {tag="Splice", id[1] }, {tag="Number", 1 } } } elseif id.tag == 'Error' then return id - else error ("Identifier expected: ".._G.table.tostring(id, 'nohash')) end + else error ("Identifier expected: "..table.tostring(id, 'nohash')) end end -------------------------------------------------------------------------------- -- Read a string, possibly spliced, or return an error if it can't -------------------------------------------------------------------------------- -function string (lx) +function M.string (lx) local a = lx:peek() if lx:is_keyword (a, "-{") then - local v = gg.sequence{ "-{", splice_content, "}" } (lx) [1] - if v.tag ~= "" and v.tag ~= "Splice" then + local v = splice(lx) + if v.tag ~= "String" and v.tag ~= "Splice" then return gg.parse_error(lx,"Bad string splice") end return v @@ -147,36 +135,39 @@ -------------------------------------------------------------------------------- -- Try to read a string, or return false if it can't. No splice allowed. -------------------------------------------------------------------------------- -function opt_string (lx) +function M.opt_string (lx) return lx:peek().tag == "String" and lx:next() end -------------------------------------------------------------------------------- -- Chunk reader: block + Eof -------------------------------------------------------------------------------- -function skip_initial_sharp_comment (lx) +function M.skip_initial_sharp_comment (lx) -- Dirty hack: I'm happily fondling lexer's private parts -- FIXME: redundant with lexer:newstream() lx :sync() local i = lx.src:match ("^#.-\n()", lx.i) - if i then lx.i, lx.column_offset, lx.line = i, i, lx.line+1 end + if i then + lx.i = i + lx.column_offset = i + lx.line = lx.line and lx.line + 1 or 1 + end end -local function _chunk (lx) - if PRINT_PARSED_STAT then print "HI"; printf("about to chunk on %s", tostring(lx:peek())) end +local function chunk (lx) if lx:peek().tag == 'Eof' then - if PRINT_PARSED_STAT then print "at EOF" end return { } -- handle empty files else - skip_initial_sharp_comment (lx) - if PRINT_PARSED_STAT then printf("after skipping, at %s", tostring(lx:peek())) end - local chunk = block (lx) + M.skip_initial_sharp_comment (lx) + local chunk = mlp.block (lx) if lx:peek().tag ~= "Eof" then - _G.table.insert(chunk, gg.parse_error(lx, "End-of-file expected")) + table.insert(chunk, gg.parse_error(lx, "End-of-file expected")) end return chunk end end -- chunk is wrapped in a sequence so that it has a "transformer" field. -chunk = gg.sequence { _chunk, builder = unpack } +M.chunk = gg.sequence { chunk, builder = unpack } + +return M \ No newline at end of file
diff --git a/compiler/mlp_stat.lua b/metalua/compiler/parser/stat.lua similarity index 66% rename from compiler/mlp_stat.lua rename to metalua/compiler/parser/stat.lua index c5aa8c1..fc9a064 100644 --- a/compiler/mlp_stat.lua +++ b/metalua/compiler/parser/stat.lua
@@ -17,21 +17,28 @@ -- ------------------------------------------------------------------------------- ----------------------------------------------------------------------- +------------------------------------------------------------------------------- -- --- Summary: metalua parser, statement/block parser. This is part of --- the definition of module [mlp]. +-- Summary: metalua parser, statement/block parser. This is part of the +-- definition of module [mlp]. -- ----------------------------------------------------------------------- +------------------------------------------------------------------------------- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- Exports API: -- * [mlp.stat()] -- * [mlp.block()] -- * [mlp.for_header()] -- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- + +local mlp = require 'metalua.compiler.parser.common' +local mlp_misc = require 'metalua.compiler.parser.misc' +local mlp_meta = require 'metalua.compiler.parser.meta' +local annot = require 'metalua.compiler.parser.annot' +local gg = require 'metalua.grammar.generator' +local M = { } -------------------------------------------------------------------------------- -- eta-expansions to break circular dependency @@ -40,31 +47,29 @@ local func_val = function (lx) return mlp.func_val (lx) end local expr_list = function (lx) return mlp.expr_list(lx) end -module ("mlp", package.seeall) - -------------------------------------------------------------------------------- -- List of all keywords that indicate the end of a statement block. Users are -- likely to extend this list when designing extensions. -------------------------------------------------------------------------------- -local block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" } +M.block_terminators = { "else", "elseif", "end", "until", ")", "}", "]" } -- FIXME: this must be handled from within GG!!! -function block_terminators:add(x) - if type (x) == "table" then for _, y in ipairs(x) do self:add (y) end - else _G.table.insert (self, x) end +function M.block_terminators :add(x) + if type (x) == "table" then for _, y in ipairs(x) do self :add (y) end + else table.insert (self, x) end end -------------------------------------------------------------------------------- -- list of statements, possibly followed by semicolons -------------------------------------------------------------------------------- -block = gg.list { +M.block = gg.list { name = "statements block", - terminators = block_terminators, + terminators = M.block_terminators, primary = function (lx) -- FIXME use gg.optkeyword() - local x = stat (lx) + local x = mlp.stat (lx) if lx:is_keyword (lx:peek(), ";") then lx:next() end return x end } @@ -73,7 +78,7 @@ -- Helper function for "return <expr_list>" parsing. -- Called when parsing return statements. -- The specific test for initial ";" is because it's not a block terminator, --- so without itgg.list would choke on "return ;" statements. +-- so without it gg.list would choke on "return ;" statements. -- We don't make a modified copy of block_terminators because this list -- is sometimes modified at runtime, and the return parser would get out of -- sync if it was relying on a copy. @@ -81,20 +86,27 @@ local return_expr_list_parser = gg.multisequence{ { ";" , builder = function() return { } end }, default = gg.list { - expr, separators = ",", terminators = block_terminators } } + expr, separators = ",", terminators = M.block_terminators } } + + +local for_vars_list = gg.list{ + name = "for variables list", + primary = mlp_misc.id, + separators = ",", + terminators = "in" } -------------------------------------------------------------------------------- -- for header, between [for] and [do] (exclusive). -- Return the `Forxxx{...} AST, without the body element (the last one). -------------------------------------------------------------------------------- -function for_header (lx) +function M.for_header (lx) local vars = mlp.id_list(lx) if lx :is_keyword (lx:peek(), "=") then if #vars ~= 1 then return gg.parse_error (lx, "numeric for only accepts one variable") end lx:next() -- skip "=" - local exprs = expr_list (lx) + local exprs = mlp.expr_list (lx) if #exprs < 2 or #exprs > 3 then return gg.parse_error (lx, "numeric for requires 2 or 3 boundaries") end @@ -103,7 +115,7 @@ if not lx :is_keyword (lx :next(), "in") then return gg.parse_error (lx, '"=" or "in" expected in for loop') end - local exprs = expr_list (lx) + local exprs = mlp.expr_list (lx) return { tag="Forin", vars, exprs } end end @@ -113,16 +125,16 @@ -------------------------------------------------------------------------------- local function fn_builder (list) local r = list[1] - for i = 2, #list do r = { tag="Index", r, id2string(list[i]) } end + for i = 2, #list do r = { tag="Index", r, mlp.id2string(list[i]) } end return r end -local func_name = gg.list{ id, separators = ".", builder = fn_builder } +local func_name = gg.list{ mlp_misc.id, separators = ".", builder = fn_builder } -------------------------------------------------------------------------------- -- Function def parser helper: ( : id )? -------------------------------------------------------------------------------- -local method_name = gg.onkeyword{ name = "method invocation", ":", id, - transformers = { function(x) return x and x.tag=='Id' and id2string(x) end } } +local method_name = gg.onkeyword{ name = "method invocation", ":", mlp_misc.id, + transformers = { function(x) return x and x.tag=='Id' and mlp_misc.id2string(x) end } } -------------------------------------------------------------------------------- -- Function def builder @@ -138,7 +150,7 @@ name = { tag="Index", name, method, lineinfo = { first = name.lineinfo.first, last = method.lineinfo.last } } - _G.table.insert (func[1], 1, {tag="Id", "self"}) + table.insert (func[1], 1, {tag="Id", "self"}) end local r = { tag="Set", {name}, {func} } r[1].lineinfo = name.lineinfo @@ -152,13 +164,13 @@ -------------------------------------------------------------------------------- local function if_builder (x) local cb_pairs, else_block, r = x[1], x[2], {tag="If"} - if cb_pairs.tag=='Error' then return cb_pairs end -- propagate errors + if cb_pairs.tag=='Error' then return cb_pairs end -- propagate errors local n_pairs = #cb_pairs for i = 1, n_pairs do local cond, block = unpack(cb_pairs[i]) r[2*i-1], r[2*i] = cond, block end - if else_block then r[#r+1] = else_block end + if else_block then table.insert(r, #r+1, else_block) end return r end @@ -166,32 +178,48 @@ -- produce a list of (expr,block) pairs -------------------------------------------------------------------------------- local elseifs_parser = gg.list { - gg.sequence { expr, "then", block, name='if/then block' }, + gg.sequence { expr, "then", M.block , name='elseif parser' }, separators = "elseif", - terminators = { "else", "end" } } + terminators = { "else", "end" } +} + +local annot_expr = gg.sequence { + expr, + gg.onkeyword{ "#", annot.tf }, + builder = function(x) + local e, a = unpack(x) + if a then return { tag='Annot', e, a } + else return e end + end } + +local annot_expr_list = gg.list { + primary = annot.opt(expr, 'tf'), separators = ',' } -------------------------------------------------------------------------------- -- assignments and calls: statements that don't start with a keyword -------------------------------------------------------------------------------- local function assign_or_call_stat_parser (lx) - local e = expr_list (lx) + local e = annot_expr_list (lx) local a = lx:is_keyword(lx:peek()) - local op = a and stat.assignments[a] + local op = a and mlp.stat.assignments[a] + -- TODO: refactor annotations if op then --FIXME: check that [e] is a LHS - lx:next() - local v = expr_list (lx) - if type(op)=="string" then return { tag=op, e, v } + lx :next() + local annots + e, annots = annot.split(e) + local v = mlp.expr_list (lx) + if type(op)=="string" then return { tag=op, e, v, annots } else return op (e, v) end - else + else assert (#e > 0) - if #e > 1 then - return gg.parse_error (lx, + if #e > 1 then + return gg.parse_error (lx, "comma is not a valid statement separator; statement can be ".. "separated by semicolons, or not separated at all") end if e[1].tag ~= "Call" and e[1].tag ~= "Invoke" then local typename - if e[1].tag == 'Id' then + if e[1].tag == 'Id' then typename = '("'..e[1][1]..'") is an identifier' elseif e[1].tag == 'Op' then typename = "is an arithmetic operation" @@ -205,40 +233,51 @@ end end -local_stat_parser = gg.multisequence{ - -- local function <name> <func_val> - { "function", id, func_val, builder = +M.local_stat_parser = gg.multisequence{ + -- local function <name> <func_val> + { "function", mlp_misc.id, func_val, builder = function(x) - local vars = { x[1], lineinfo = x[1].lineinfo } - local vals = { x[2], lineinfo = x[2].lineinfo } - return { tag="Localrec", vars, vals } + local vars = { x[1], lineinfo = x[1].lineinfo } + local vals = { x[2], lineinfo = x[2].lineinfo } + return { tag="Localrec", vars, vals } end }, - -- local <id_list> ( = <expr_list> )? - default = gg.sequence{ id_list, gg.onkeyword{ "=", expr_list }, - builder = function(x) return {tag="Local", x[1], x[2] or { } } end } } + -- local <id_list> ( = <expr_list> )? + default = gg.sequence{ + gg.list{ + primary = annot.opt(mlp_misc.id, 'tf'), + separators = ',' }, + gg.onkeyword{ "=", expr_list }, + builder = function(x) + local annotated_left, right = unpack(x) + local left, annotations = annot.split(annotated_left) + return {tag="Local", left, right or { }, annotations } + end } } -------------------------------------------------------------------------------- -- statement -------------------------------------------------------------------------------- -stat = gg.multisequence { - name="statement", - { "do", block, "end", builder = +M.stat = gg.multisequence { + name = "statement", + { "do", M.block, "end", builder = function (x) return { tag="Do", unpack (x[1]) } end }, - { "for", for_header, "do", block, "end", builder = + { "for", M.for_header, "do", M.block, "end", builder = function (x) x[1][#x[1]+1] = x[2]; return x[1] end }, { "function", func_name, method_name, func_val, builder=funcdef_builder }, - { "while", expr, "do", block, "end", builder = "While" }, - { "repeat", block, "until", expr, builder = "Repeat" }, - { "local", local_stat_parser, builder = fget (1) }, - { "return", return_expr_list_parser, builder = fget (1, "Return") }, + { "while", expr, "do", M.block, "end", builder = "While" }, + { "repeat", M.block, "until", expr, builder = "Repeat" }, + { "local", M.local_stat_parser, builder = unpack }, + { "return", return_expr_list_parser, builder = + function(x) x[1].tag='Return'; return x[1] end }, { "break", builder = function() return { tag="Break" } end }, - { "-{", splice_content, "}", builder = fget(1) }, - { "if", gg.nonempty(elseifs_parser), gg.onkeyword{ "else", block }, "end", + { "-{", mlp_meta.splice_content, "}", builder = unpack }, + { "if", gg.nonempty(elseifs_parser), gg.onkeyword{ "else", M.block }, "end", builder = if_builder }, default = assign_or_call_stat_parser } -stat.assignments = { - ["="] = "Set" } +M.stat.assignments = { + ["="] = "Set" +} -stat.dontfail = true -function stat.assignments:add(k, v) self[k] = v end +function M.stat.assignments:add(k, v) self[k] = v end + +return M \ No newline at end of file
diff --git a/compiler/mlp_table.lua b/metalua/compiler/parser/table.lua similarity index 62% rename from compiler/mlp_table.lua rename to metalua/compiler/parser/table.lua index cfc26a2..ccffb2e 100644 --- a/compiler/mlp_table.lua +++ b/metalua/compiler/parser/table.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,53 +15,54 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- ----------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- --- Summary: metalua parser, table constructor parser. This is part --- of thedefinition of module [mlp]. +-- Copyright (c) 2006-2012, Fabien Fleutot <metalua@gmail.com>. -- ----------------------------------------------------------------------- +-- This software is released under the MIT Licence, see licence.txt +-- for details. +-- +-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Exported API: --- * [mlp.table_field()] --- * [mlp.table_content()] --- * [mlp.table()] +-- * [M.bracket_field()] +-- * [M.field()] +-- * [M.content()] +-- * [M.table()] -- -- KNOWN BUG: doesn't handle final ";" or "," before final "}" -- -------------------------------------------------------------------------------- ---require "gg" ---require "mll" ---require "mlp_misc" -local tableinsert = table.insert +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser.common' -module ("mlp", package.seeall) +local M = { } -------------------------------------------------------------------------------- -- eta expansion to break circular dependencies: -------------------------------------------------------------------------------- -local function _expr (lx) return expr(lx) end +local function _expr (lx) return mlp.expr(lx) end -------------------------------------------------------------------------------- -- [[key] = value] table field definition -------------------------------------------------------------------------------- -local bracket_field = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" } +M.bracket_field = gg.sequence{ "[", _expr, "]", "=", _expr, builder = "Pair" } -------------------------------------------------------------------------------- -- [id = value] or [value] table field definition; -- [[key]=val] are delegated to [bracket_field()] -------------------------------------------------------------------------------- -function table_field (lx) - if lx:is_keyword (lx:peek(), "[") then return bracket_field (lx) end +function M.field (lx) + if lx :is_keyword (lx :peek(), "[") then return M.bracket_field (lx) end local e = _expr (lx) - if lx:is_keyword (lx:peek(), "=") then - lx:next(); -- skip the "=" - local key = id2string(e) + if lx :is_keyword (lx :peek(), "=") then + lx :next(); -- skip the "=" + local key = mlp.id2string(e) local val = _expr(lx) local r = { tag="Pair", key, val } r.lineinfo = { first = key.lineinfo.first, last = val.lineinfo.last } @@ -69,35 +70,18 @@ else return e end end -local function _table_field(lx) return table_field(lx) end - -------------------------------------------------------------------------------- -- table constructor, without enclosing braces; returns a full table object -------------------------------------------------------------------------------- -function table_content(lx) - local items = {tag = "Table"} - while not lx:is_keyword(lx:peek(), '}') do - -- Seek for table values - local tablevalue = _table_field (lx) - if tablevalue then - tableinsert(items, tablevalue) - else - return gg.parse_error(lx, '`Pair of value expected.') - end - - -- Seek for values separators - if lx:is_keyword(lx:peek(), ',', ';') then - lx:next() - elseif not lx:is_keyword(lx:peek(), '}') then - return gg.parse_error(lx, '} expected.') - end - end - return items -end - -local function _table_content(lx) return table_content(lx) end +M.content = gg.list { + primary = function(...) return M.field(...) end, + separators = { ",", ";" }, + terminators = "}", + builder = "Table" } -------------------------------------------------------------------------------- -- complete table constructor including [{...}] -------------------------------------------------------------------------------- -table = gg.sequence{ "{", _table_content, "}", builder = fget(1) } +M.table = gg.sequence{ "{", function(...) return M.content(...) end, "}", builder = unpack } + +return M
diff --git a/lib/metalua/extension/clist.mlua b/metalua/extension/clist.mlua similarity index 86% rename from lib/metalua/extension/clist.mlua rename to metalua/extension/clist.mlua index ae28e84..14cfde8 100644 --- a/lib/metalua/extension/clist.mlua +++ b/metalua/extension/clist.mlua
@@ -16,15 +16,23 @@ -- Fabien Fleutot - API and implementation -- ------------------------------------------------------------------------------- - --------------------------------------------------------------------------------- -- -- This extension implements list comprehensions, similar to Haskell and -- Python syntax, to easily describe lists. -- --------------------------------------------------------------------------------- +-- * x[a ... b] is the list { x[a], x[a+1], ..., x[b] } +-- * { f()..., b } contains all the elements returned by f(), then b +-- (allows to expand list fields other than the last one) +-- * list comprehensions a la python, with "for" and "if" suffixes: +-- {i+10*j for i=1,3 for j=1,3 if i~=j} is { 21, 31, 12, 32, 13, 23 } +-- +------------------------------------------------------------------------------- --{ extension "match" } +-{ extension ("match", ...) } + +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser' +local mlp_table = require 'metalua.compiler.parser.table' local function dots_builder (x) return `Dots{ x } end @@ -43,7 +51,7 @@ | _ -> return `Comp{ x, p[1] } end end - + local function comp_builder(core, list, no_unpack) -- [ti] = temp var holding table.insert -- [v] = variable holding the table being built @@ -57,7 +65,7 @@ match core with | `Dots{ x } -> local w = mlp.gensym() - r = +{stat: for -{w} in values( -{x} ) do -{ `Call{ ti, v, w } } end } + r = +{stat: for _, -{w} in pairs( -{x} ) do -{ `Call{ ti, v, w } } end } | `Pair{ k, w } -> r = `Set{ { `Index{ v, k } }, { w } } | _ -> r = `Call{ ti, v, core } @@ -88,7 +96,7 @@ local tables = { `Table } local ctable = tables[1] local function flush() ctable=`Table; table.insert(tables, ctable) end - for x in values(list) do + for _, x in pairs(list) do match x with | `Comp{ y, acc } -> table.insert(ctable, comp_builder(y, acc)); flush() | `Dots{ y } -> table.insert(ctable, y); flush() @@ -104,14 +112,14 @@ end end -mlp.table_field = gg.expr{ name="table cell", - primary = mlp.table_field, +mlp_table.field = gg.expr{ name="table cell", + primary = mlp_table.field, suffix = { name="table cell suffix", { "...", builder = dots_builder }, { "for", mlp.for_header, builder = for_builder }, { "if", mlp.expr, builder = if_builder } } } -mlp.table_content.builder = table_content_builder +mlp_table.content.builder = table_content_builder --[[ mlp.stat:add{ "for", gg.expr { @@ -133,7 +141,7 @@ | ranges -> local r = `Call{ +{table.isub}, a } local function acc (x,y) table.insert (r,x); table.insert (r,y) end - for seq in ivalues (ranges) do + for _, seq in ipairs (ranges) do match seq with | { e, false } -> acc(e,e) | { e, f } -> acc(e,f)
diff --git a/lib/metalua/extension/log.mlua b/metalua/extension/log.mlua similarity index 96% rename from lib/metalua/extension/log.mlua rename to metalua/extension/log.mlua index 5f048c1..c9323a9 100644 --- a/lib/metalua/extension/log.mlua +++ b/metalua/extension/log.mlua
@@ -19,7 +19,7 @@ require 'metalua.dollar' --{ extension 'match' } +-{ extension ('match', ...) } function dollar.log(...) local args = {...} @@ -56,3 +56,5 @@ end return code end + +return function() end
diff --git a/lib/metalua/extension/match.mlua b/metalua/extension/match.mlua similarity index 83% rename from lib/metalua/extension/match.mlua rename to metalua/extension/match.mlua index eab9f8f..d7c7762 100644 --- a/lib/metalua/extension/match.mlua +++ b/metalua/extension/match.mlua
@@ -17,7 +17,7 @@ -- ------------------------------------------------------------------------------- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- Glossary: -- @@ -28,11 +28,11 @@ -- * pattern_group: several pattern seqs, one of them might match -- the term seq. -- * case: pattern_group * guard option * block --- * match_statement: tested term_seq * case list +-- * match_statement: tested term_seq * case list -- -- Hence a complete match statement is a: -- --- { list(expr), list{ list(list(expr)), expr or false, block } } +-- { list(expr), list{ list(list(expr)), expr or false, block } } -- -- Implementation hints -- ==================== @@ -64,15 +64,15 @@ -- Code generation is performed by acc_xxx() functions, which accumulate -- code in cfg.code: -- --- * acc_test(test, cfg) will generate a jump to cfg.on_failure +-- * acc_test(test, cfg) will generate a jump to cfg.on_failure -- *when the test returns TRUE* -- -- * acc_stat accumulates a statement --- --- * acc_assign accumulate an assignment statement, and makes sure that +-- +-- * acc_assign accumulate an assignment statement, and makes sure that -- the LHS variable the registered as local in cfg.locals. --- ----------------------------------------------------------------------- +-- +------------------------------------------------------------------------------- -- TODO: hygiene wrt type() -- TODO: cfg.ntmp isn't reset as often as it could. I'm not even sure @@ -80,6 +80,8 @@ module ('spmatch', package.seeall) +local gg = require 'metalua.grammar.generator' + ---------------------------------------------------------------------- -- This would have been best done through library 'metalua.walk', -- but walk depends on match, so we have to break the dependency. @@ -87,23 +89,24 @@ -- it appears in a function. ---------------------------------------------------------------------- function replace_dots (ast, term) - local function rec (x) - if type(x) == 'table' then - if x.tag=='Dots' then - if term=='ambiguous' then - error ("You can't use `...' on the right of a match case when it appears ".. - "more than once on the left") - else - x <- term - end - elseif x.tag=='Function' then return - else for y in ivalues (x) do rec (y) end end - end - end - return rec (ast) + local function rec (x) + if type(x) == 'table' then + if x.tag=='Dots' then + if term=='ambiguous' then + error ("You can't use `...' on the right of a match case when it appears ".. + "more than once on the left") + else + x <- term + end + elseif x.tag=='Function' then return nil + else for _, y in ipairs (x) do rec (y) end end + end + end + return rec (ast) end -tmpvar_base = mlp.gensym 'submatch.' [1] +tmpvar_base = gg.gensym 'submatch.' [1] + function next_tmpvar(cfg) assert (cfg.ntmp, "No cfg.ntmp imbrication level in the match compiler") cfg.ntmp = cfg.ntmp+1 @@ -127,15 +130,15 @@ -- term :: expr function id_pattern_element_builder (pattern, term, cfg) assert (pattern.tag == "Id") - if pattern[1] == "_" then + if pattern[1] == "_" then -- "_" is used as a dummy var ==> no assignment, no == checking cfg.locals._ = true - elseif cfg.locals[pattern[1]] then + elseif cfg.locals[pattern[1]] then -- This var is already bound ==> test for equality acc_test (+{ -{term} ~= -{pattern} }, cfg) else -- Free var ==> bind it, and remember it for latter linearity checking - acc_assign (pattern, term, cfg) + acc_assign (pattern, term, cfg) cfg.locals[pattern[1]] = true end end @@ -150,7 +153,7 @@ function pattern_element_builder (pattern, term, cfg) if literal_tags[pattern.tag] then acc_test (+{ -{term} ~= -{pattern} }, cfg) - elseif "Id" == pattern.tag then + elseif "Id" == pattern.tag then id_pattern_element_builder (pattern, term, cfg) elseif "Op" == pattern.tag and "div" == pattern[1] then regexp_pattern_element_builder (pattern, term, cfg) @@ -158,7 +161,7 @@ eq_pattern_element_builder (pattern, term, cfg) elseif "Table" == pattern.tag then table_pattern_element_builder (pattern, term, cfg) - else + else error ("Invalid pattern: "..table.tostring(pattern, "nohash")) end end @@ -178,11 +181,11 @@ -- Sanity checks -- assert (op=='div', "Don't know what to do with that op in a pattern") - assert (regexp.tag=="String", + assert (regexp.tag=="String", "Left hand side operand for '/' in a pattern must be ".. "a literal string representing a regular expression") if sub_pattern.tag=="Table" then - for x in ivalues(sub_pattern) do + for _, x in ipairs(sub_pattern) do assert (x.tag=="Id" or x.tag=='Dots', "Right hand side operand for '/' in a pattern must be ".. "a list of identifiers") @@ -218,15 +221,15 @@ else -- Implicit key len, key, sub_pattern = len+1, `Number{ len+1 }, pattern[i] end - + -- '...' can only appear in final position -- Could be fixed actually... assert (not seen_dots, "Wrongly placed `...' ") - if sub_pattern.tag == "Id" then + if sub_pattern.tag == "Id" then -- Optimization: save a useless [ v(n+1)=v(n).key ] id_pattern_element_builder (sub_pattern, `Index{ term, key }, cfg) - if sub_pattern[1] ~= "_" then + if sub_pattern[1] ~= "_" then acc_test (+{ -{sub_pattern} == nil }, cfg) end elseif sub_pattern.tag == "Dots" then @@ -241,7 +244,7 @@ end end if seen_dots then -- remember how to retrieve `...' - -- FIXME: check, but there might be cases where the variable -{term} + -- FIXME: check, but there might be cases where the variable -{term} -- will be overridden in contrieved tables. -- ==> save it now, and clean the setting statement if unused if cfg.dots_replacement then cfg.dots_replacement = 'ambiguous' @@ -274,10 +277,10 @@ -------------------------------------------------- function case_builder (case, term_seq, cfg) local patterns_group, guard, block = unpack(case) - local on_success = mlp.gensym 'on_success' [1] + local on_success = gg.gensym 'on_success' [1] for i = 1, #patterns_group do local pattern_seq = patterns_group[i] - cfg.on_failure = mlp.gensym 'match_fail' [1] + cfg.on_failure = gg.gensym 'match_fail' [1] cfg.dots_replacement = false pattern_seq_builder (pattern_seq, term_seq, cfg) if i<#patterns_group then @@ -298,9 +301,9 @@ function match_builder (x) local term_seq, cases = unpack(x) - local cfg = { + local cfg = { code = `Do{ }, - after_success = mlp.gensym "_after_success" } + after_success = gg.gensym "_after_success" } -- Some sharing issues occur when modifying term_seq, @@ -319,7 +322,7 @@ -- Temporary workaround: suppress the condition, so that -- all external variables are copied into unique names. --if t.tag ~= 'Id' and not literal_tags[t.tag] then - local v = mlp.gensym 'v' + local v = gg.gensym 'v' if not match_locals then match_locals = `Local{ {v}, {t} } else table.insert(match_locals[1], v) table.insert(match_locals[2], t) @@ -328,11 +331,11 @@ --end end term_seq = new_term_seq - + if match_locals then acc_stat(match_locals, cfg) end for i=1, #cases do - local case_cfg = { + local case_cfg = { after_success = cfg.after_success, code = `Do{ } -- locals = { } -- unnecessary, done by pattern_seq_builder @@ -341,7 +344,7 @@ if next (case_cfg.locals) then local case_locals = { } table.insert (case_cfg.code, 1, `Local{ case_locals, { } }) - for v in keys (case_cfg.locals) do + for v, _ in pairs (case_cfg.locals) do table.insert (case_locals, `Id{ v }) end end @@ -356,26 +359,30 @@ -- Syntactical front-end ---------------------------------------------------------------------- -mlp.lexer:add{ "match", "with", "->" } -mlp.block.terminators:add "|" +local function extend(mlp) + checks('metalua.compiler.parser') + mlp.lexer:add{ "match", "with", "->" } + mlp.block.terminators:add "|" -match_cases_list_parser = gg.list{ name = "match cases list", - gg.sequence{ name = "match case", - gg.list{ name = "match case patterns list", - primary = mlp.expr_list, - separators = "|", - terminators = { "->", "if" } }, - gg.onkeyword{ "if", mlp.expr, consume = true }, - "->", - mlp.block }, - separators = "|", - terminators = "end" } + match_cases_list_parser = gg.list{ name = "match cases list", + gg.sequence{ name = "match case", + gg.list{ name = "match case patterns list", + primary = mlp.expr_list, + separators = "|", + terminators = { "->", "if" } }, + gg.onkeyword{ "if", mlp.expr, consume = true }, + "->", + mlp.block }, + separators = "|", + terminators = "end" } -mlp.stat:add{ name = "match statement", - "match", - mlp.expr_list, - "with", gg.optkeyword "|", - match_cases_list_parser, - "end", - builder = |x| match_builder{ x[1], x[3] } } + mlp.stat:add{ name = "match statement", + "match", + mlp.expr_list, + "with", gg.optkeyword "|", + match_cases_list_parser, + "end", + builder = |x| match_builder{ x[1], x[3] } } +end +return extend \ No newline at end of file
diff --git a/lib/metalua/extension/xloop.mlua b/metalua/extension/xloop.mlua similarity index 80% rename from lib/metalua/extension/xloop.mlua rename to metalua/extension/xloop.mlua index f4b365f..b5aaed9 100644 --- a/lib/metalua/extension/xloop.mlua +++ b/metalua/extension/xloop.mlua
@@ -16,12 +16,38 @@ -- Fabien Fleutot - API and implementation -- ------------------------------------------------------------------------------- +-- Loop control syntax extensions +-- +-- * Allows to compound loop headers together, e.g. write: +-- for i=1,10 for j=1,10 do f(i,j) end +-- instead of: +-- for i=1,10 do for j=1,10 do f(i,j) end end +-- loop headers are "for/=" and "for/in" +-- +-- * while <condition> in a loop header will break the loop(s) +-- as soon as condition stops being satisfied. +-- +-- * until <condition> in a loop header will break the loop(s) +-- as soon as condition is satisfied. +-- +-- * if <condition> in a loop header will skip an iteration +-- if the condition is not satisfied. +-- +-- * unless <condition> in a loop header will skip an iteration +-- if the condition is satisfied. +-- +-- TODO: document ordering matters, e.g. how +-- "for i in x() if cond(i) for j in y()" is parsed. +-- +------------------------------------------------------------------------------- --{ extension 'match' } --{ extension 'log' } +-{ extension ('match', ...) } require 'metalua.walk' +local gg = require 'metalua.grammar.generator' +local mlp = require 'metalua.compiler.parser' + ---------------------------------------------------------------------- -- Back-end: ---------------------------------------------------------------------- @@ -113,7 +139,3 @@ mlp.stat:add{ 'while', mlp.expr, loop_element_list, 'do', mlp.block, 'end', builder = |x| xloop_builder{ `While{x[1]}, x[2], x[3] } } - -mlp.stat:add{ - 'unless', mlp.expr, 'then', mlp.block, 'end', - builder = |x| +{stat: if not -{x[1]} then -{x[2]} end} }
diff --git a/lib/metalua/extension/xmatch.mlua b/metalua/extension/xmatch.mlua similarity index 97% rename from lib/metalua/extension/xmatch.mlua rename to metalua/extension/xmatch.mlua index 1936bda..71fd0b0 100644 --- a/lib/metalua/extension/xmatch.mlua +++ b/metalua/extension/xmatch.mlua
@@ -23,8 +23,6 @@ require 'metalua.walk.id' --{extension 'log'} - ---------------------------------------------------------------------- -- Back-end for statements -- "match function ..." and "local match function...". @@ -119,7 +117,7 @@ local tested_term_seq, _, cases = unpack(x) local v = mlp.gensym 'match_expr' -- Replace expressions with blocks - for case in ivalues (cases) do + for _, case in ipairs (cases) do local body = case[3] case[3] = { `Set{ {v}, {body} } } end @@ -173,7 +171,7 @@ ------------------------------------------------------------------- local vars_not_in_pattern do vars_not_in_pattern = { } - for k in keys(vars) do + for k, _ in pairs(vars) do if not vars_in_pattern[k] then vars_not_in_pattern[k] = true end @@ -185,7 +183,7 @@ ------------------------------------------------------------------- if next(vars_not_in_pattern) then local loc = { } - for k in keys (vars_not_in_pattern) do + for k, _ in pairs(vars_not_in_pattern) do table.insert (loc, `Id{k}) end table.insert (code, 1, `Local{ loc, { } }) @@ -196,7 +194,7 @@ ------------------------------------------------------------------- local decl_list do decl_list = { } - for k in keys (vars_in_pattern) do + for k, _ in pairs(vars_in_pattern) do table.insert (decl_list, `Id{k}) end end
diff --git a/compiler/gg.lua b/metalua/grammar/generator.lua similarity index 84% rename from compiler/gg.lua rename to metalua/grammar/generator.lua index c4c6019..15dddf8 100644 --- a/compiler/gg.lua +++ b/metalua/grammar/generator.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,15 +15,15 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- ---------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- -- Summary: parser generator. Collection of higher order functors, -- which allow to build and combine parsers. Relies on a lexer -- that supports the same API as the one exposed in mll.lua. -- ----------------------------------------------------------------------- +-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- @@ -37,14 +37,31 @@ -- * [gg.onkeyword()] -- * [gg.optkeyword()] -- --- Other functions: +-- Other functions: -- * [gg.parse_error()] -- * [gg.make_parser()] -- * [gg.is_parser()] -- -------------------------------------------------------------------------------- -module("gg", package.seeall) +local M = { } + +local lexer = require 'metalua.grammar.lexer' + +-------------------------------------------------------------------------------- +-- Symbol generator: [gensym()] returns a guaranteed-to-be-unique identifier. +-- The main purpose is to avoid variable capture in macros. +-- +-- If a string is passed as an argument, theis string will be part of the +-- id name (helpful for macro debugging) +-------------------------------------------------------------------------------- +local gensymidx = 0 + +function M.gensym (arg) + gensymidx = gensymidx + 1 + return { tag="Id", string.format(".%i.%s", gensymidx, arg or "")} +end + ------------------------------------------------------------------------------- -- parser metatable, which maps __call to method parse, and adds some @@ -58,16 +75,22 @@ ------------------------------------------------------------------------------- local parser_metatable = { } -function parser_metatable :__call (...) - local r = self :parse(...) +function parser_metatable :__call (lx, ...) + --return self :parse(lx, ...) + local r = self :parse (lx, ...) + if r and type(r) ~= 'table' then + printf("Grammar generator: parser %s returned non-table %s at %s", + table.tostring(self.name), + table.tostring(r), + table.tostring((...):peek())) + end return r - --return self :parse(...) end ------------------------------------------------------------------------------- -- Turn a table into a parser, mainly by setting the metatable. ------------------------------------------------------------------------------- -function make_parser(kind, p) +function M.make_parser(kind, p) p.kind = kind if not p.transformers then p.transformers = { } end function p.transformers:add (x) @@ -81,7 +104,7 @@ -- Return true iff [x] is a parser. -- If it's a gg-generated parser, return the name of its kind. ------------------------------------------------------------------------------- -function is_parser (x) +function M.is_parser (x) return type(x)=="function" or getmetatable(x)==parser_metatable and x.kind end @@ -98,21 +121,25 @@ local r = { } local failed = false for i=1, #p do - e=p[i] + local e=p[i] if failed then - if type(e)=="string" then table.insert(r, earlier_error(lx)) end + if type(e)=="string" then table.insert(r, M.earlier_error(lx)) end elseif type(e) == "string" then - if not lx :is_keyword (lx :next(), e) then - table.insert(r, {tag='Error', "A keyword was expected, probably `"..e.."'."}) + local kw = lx :next() + if not lx :is_keyword (kw, e) then + table.insert(r, { + tag = 'Error', + lineinfo = kw.lineinfo, + "A keyword was expected, probably `"..e.."'."}) failed=true end - elseif is_parser (e) then + elseif M.is_parser (e) then local x = e(lx) if type(x)=='table' and x.tag=='Error' then failed=true end table.insert (r, x) else -- Invalid parser definition, this is not a parsing error, it must fail. - return gg.parse_error (lx,"Sequence `%s': element #%i is neither a string ".. - "nor a parser: %s", p.name, i, table.tostring(e)) + return M.parse_error (lx,"Sequence `%s': element #%i is neither a string ".. + "nor a parser: %s", p.name, i, table.tostring(e)) end end return r @@ -136,7 +163,7 @@ if parser.transformers then for _, t in ipairs (parser.transformers) do ast = t(ast) or ast end end - if type(ast) == 'table'then + if type(ast) == 'table' then local ali = ast.lineinfo if not ali or ali.first~=fli or ali.last~=lli then ast.lineinfo = lexer.new_lineinfo(fli, lli) @@ -146,13 +173,13 @@ end ------------------------------------------------------------------------------- --- Generate a tracable parsing error +-- Generate a tracable parsing error (not implemented yet) ------------------------------------------------------------------------------- -function parse_error(lx, fmt, ...) +function M.parse_error(lx, fmt, ...) local li = lx:lineinfo_left() local line, column, offset if li then line, column, offset = li.line, li.column, li.offset - else line, column, offset, src_name = -1, -1, -1 end + else line, column, offset = -1, -1, -1 end local msg = string.format("line %i, char %i: "..fmt, line, column, ...) local src = lx.src @@ -165,24 +192,22 @@ msg = string.format("%s\n>>> %s\n>>> %s", msg, srcline, idx) end lx :kill() - assert (lx :peek().tag=='Eof') - --printf ("gg.parse_error(%q)", msg) - return { tag='Error', msg, error=true } + return { tag='Error', msg } end -function wrap_error(lx, nchildren, tag, ...) +function M.wrap_error(lx, nchildren, tag, ...) local li = lx :peek() .lineinfo - local r = { tag=tag or 'Error', lineinfo=li, error=true } + local r = { tag=tag or 'Error', lineinfo=li } local children = {...} for i=1, nchildren do - r[i] = children[i] or earlier_error(lx) + r[i] = children[i] or M.earlier_error(lx) end return r end -function earlier_error(lx) +function M.earlier_error(lx) local li = lx and lx :peek().lineinfo - return { tag='Error', "earlier error", lineinfo=li, error=true, stuffing = true } + return { tag='Error', "earlier error", lineinfo=li, error=true, earlier=true } end ------------------------------------------------------------------------------- @@ -212,8 +237,8 @@ -- * [name] is set, if it wasn't in the input. -- ------------------------------------------------------------------------------- -function sequence (p) - make_parser ("sequence", p) +function M.sequence (p) + M.make_parser ("sequence", p) ------------------------------------------------------------------- -- Parsing method @@ -293,24 +318,25 @@ -- * [kind] == "multisequence" -- ------------------------------------------------------------------------------- -function multisequence (p) - make_parser ("multisequence", p) +function M.multisequence (p) + M.make_parser ("multisequence", p) ------------------------------------------------------------------- -- Add a sequence (might be just a config table for [gg.sequence]) ------------------------------------------------------------------- - function p:add (s) + function p :add (s) -- compile if necessary: local keyword = type(s)=='table' and s[1] - if type(s)=='table' and not is_parser(s) then sequence(s) end - if is_parser(s)~='sequence' or type(keyword)~='string' then + if type(s)=='table' and not M.is_parser(s) then M.sequence(s) end + if M.is_parser(s)~='sequence' or type(keyword)~='string' 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[keyword] then -- duplicate keyword - eprintf (" *** Warning: keyword %q overloaded in multisequence ***", - keyword) + print (string.format( + " *** Warning: keyword %q overloaded in multisequence ***", + keyword)) self.sequences[keyword] = s else -- newly caught keyword self.sequences[keyword] = s @@ -320,12 +346,12 @@ ------------------------------------------------------------------- -- Get the sequence starting with this keyword. [kw :: string] ------------------------------------------------------------------- - function p:get (kw) return self.sequences [kw] end + function p :get (kw) return self.sequences [kw] end ------------------------------------------------------------------- -- Remove the sequence starting with keyword [kw :: string] ------------------------------------------------------------------- - function p:del (kw) + function p :del (kw) if not self.sequences[kw] then eprintf("*** Warning: trying to delete sequence starting ".. "with %q from a multisequence having no such ".. @@ -338,7 +364,7 @@ ------------------------------------------------------------------- -- Parsing method ------------------------------------------------------------------- - function p:parse (lx) + function p :parse (lx) local fli = lx:lineinfo_right() local x = raw_parse_multisequence (lx, self.sequences, self.default) local lli = lx:lineinfo_left() @@ -352,7 +378,7 @@ -- from the array part of the parser to the hash part of field -- [sequences] p.sequences = { } - for i=1, #p do p:add (p[i]); p[i] = nil end + for i=1, #p do p :add (p[i]); p[i] = nil end -- FIXME: why is this commented out? --if p.default and not is_parser(p.default) then sequence(p.default) end @@ -379,7 +405,7 @@ -- and the prefixed expression -- - for [infix], it takes the left-hand-side expression, the results -- of the infix sequence parser, and the right-hand-side expression. --- - for [suffix], it takes the suffixed expression, and theresult +-- - for [suffix], it takes the suffixed expression, and the result -- of the suffix sequence parser. -- -- * the default field is a list, with parameters: @@ -405,8 +431,8 @@ -- [add] method -- ------------------------------------------------------------------------------- -function expr (p) - make_parser ("expr", p) +function M.expr (p) + M.make_parser ("expr", p) ------------------------------------------------------------------- -- parser method. @@ -414,7 +440,7 @@ -- it won't read expressions whose precedence is lower or equal -- to [prec]. ------------------------------------------------------------------- - function p:parse (lx, prec) + function p :parse (lx, prec) prec = prec or 0 ------------------------------------------------------ @@ -423,7 +449,7 @@ -- Options include prec, assoc, transformers. ------------------------------------------------------ local function get_parser_info (tab) - local p2 = tab:get (lx:is_keyword (lx:peek())) + local p2 = tab :get (lx :is_keyword (lx :peek())) if p2 then -- keyword-based sequence found local function parser(lx) return raw_parse_sequence(lx, p2) end return parser, p2 @@ -441,17 +467,17 @@ -- expr, and one for the one with the prefix op. ------------------------------------------------------ local function handle_prefix () - local fli = lx:lineinfo_right() + local fli = lx :lineinfo_right() local p2_func, p2 = get_parser_info (self.prefix) local op = p2_func and p2_func (lx) if op then -- Keyword-based sequence found - local ili = lx:lineinfo_right() -- Intermediate LineInfo - local e = p2.builder (op, self:parse (lx, p2.prec)) - local lli = lx:lineinfo_left() + local ili = lx :lineinfo_right() -- Intermediate LineInfo + local e = p2.builder (op, self :parse (lx, p2.prec)) + local lli = lx :lineinfo_left() return transform (transform (e, p2, ili, lli), self, fli, lli) else -- No prefix found, get a primary expression local e = self.primary(lx) - local lli = lx:lineinfo_left() + local lli = lx :lineinfo_left() return transform (e, self, fli, lli) end end --</expr.parse.handle_prefix> @@ -504,7 +530,7 @@ -- Check for non-associative operators, and complain if applicable. ----------------------------------------- elseif p2.assoc=="none" and p2.prec==prec then - return parse_error (lx, "non-associative operator!") + return M.parse_error (lx, "non-associative operator!") ----------------------------------------- -- No infix operator suitable at that precedence @@ -556,7 +582,7 @@ if not p.primary then p.primary=p[1]; p[1]=nil end for _, t in ipairs{ "primary", "prefix", "infix", "suffix" } do if not p[t] then p[t] = { } end - if not is_parser(p[t]) then multisequence(p[t]) end + if not M.is_parser(p[t]) then M.multisequence(p[t]) end end function p:add(...) return self.primary:add(...) end return p @@ -588,21 +614,18 @@ -- is the same as [{"do"}]. If [terminators] is empty/nil, then -- [separators] has to be non-empty. -- --- * [nonempty]: if true, empty lists are rejected by the parser whatever --- the [terminators] and [separators] values are. --- -- After creation, the following fields are added: -- * [parse] the parsing function lexer->AST -- * [kind] == "list" -- ------------------------------------------------------------------------------- -function list (p) - make_parser ("list", p) +function M.list (p) + M.make_parser ("list", p) ------------------------------------------------------------------- -- Parsing method ------------------------------------------------------------------- - function p:parse (lx) + function p :parse (lx) ------------------------------------------------------ -- Used to quickly check whether there's a terminator @@ -612,26 +635,23 @@ return keywords and lx:is_keyword(lx:peek(), unpack(keywords)) end local x = { } - local fli = lx:lineinfo_right() + local fli = lx :lineinfo_right() -- if there's a terminator to start with, don't bother trying - if not peek_is_in (self.terminators) then - - while true do - - local item = self.primary(lx) - table.insert(x, item) - - -- loop exit conditions - if type(item)=='table' and item.tag=='Error' then break -- error in last item - elseif self.separators then -- either a separator is consumed or an error is raised - if peek_is_in (self.separators) then lx :next() else break end - elseif peek_is_in (self.terminators) then break -- end on terminator detection - elseif lx :peek().tag == 'Eof' then break end -- Eof is always a terminator - end - end - if self.nonempty and not next(x) then - return gg.parse_error (lx,"List `%s' must not be empty", p.name) + if not peek_is_in (self.terminators) then + repeat + local item = self.primary(lx) + table.insert (x, item) -- read one element + until + -- Don't go on after an error + type(item)=='table' and item.tag=='Error' or + -- There's a separator list specified, and next token isn't in it. + -- Otherwise, consume it with [lx:next()] + self.separators and not(peek_is_in (self.separators) and lx:next()) or + -- Terminator token ahead + peek_is_in (self.terminators) or + -- Last reason: end of file reached + lx:peek().tag=="Eof" end local lli = lx:lineinfo_left() @@ -653,11 +673,9 @@ ------------------------------------------------------------------- -- Construction ------------------------------------------------------------------- - if not p.primary then p.primary, p[1] = p[1], nil end - + if not p.primary then p.primary = p[1]; p[1] = nil end if type(p.terminators) == "string" then p.terminators = { p.terminators } elseif p.terminators and #p.terminators == 0 then p.terminators = nil end - if type(p.separators) == "string" then p.separators = { p.separators } elseif p.separators and #p.separators == 0 then p.separators = nil end @@ -667,7 +685,7 @@ ------------------------------------------------------------------------------- -- --- Keyword-conditionned parser generator +-- Keyword-conditioned parser generator -- ------------------------------------------------------------------------------- -- @@ -686,7 +704,7 @@ -- -- * [transformers]: as usual -- --- * [peek]: if non-nil, the conditionning keyword is left in the lexeme +-- * [peek]: if non-nil, the conditioning keyword is left in the lexeme -- stream instead of being consumed. -- -- * [primary]: the subparser. @@ -704,14 +722,14 @@ -- * [keywords] -- ------------------------------------------------------------------------------- -function onkeyword (p) - make_parser ("onkeyword", p) +function M.onkeyword (p) + M.make_parser ("onkeyword", p) ------------------------------------------------------------------- -- Parsing method ------------------------------------------------------------------- - function p:parse(lx) - if lx:is_keyword (lx:peek(), unpack(self.keywords)) then + function p :parse (lx) + if lx :is_keyword (lx:peek(), unpack(self.keywords)) then local fli = lx:lineinfo_right() if not self.peek then lx:next() end local content = self.primary (lx) @@ -728,7 +746,7 @@ if not p.keywords then p.keywords = { } end for _, x in ipairs(p) do if type(x)=="string" then table.insert (p.keywords, x) - else assert (not p.primary and is_parser (x)); p.primary = x end + else assert (not p.primary and M.is_parser (x)); p.primary = x end end if not next (p.keywords) then eprintf("Warning, no keyword to trigger gg.onkeyword") end @@ -750,7 +768,7 @@ -- Notice that tokens returned by lexer already carry lineinfo, therefore -- there's no need to add them, as done usually through transform() calls. ------------------------------------------------------------------------------- -function optkeyword (...) +function M.optkeyword (...) local args = {...} if type (args[1]) == "table" then assert (#args == 1) @@ -777,7 +795,7 @@ -- The resulting parser returns whatever the argument parser does. -- ------------------------------------------------------------------------------- -function with_lexer(new_lexer, parser) +function M.with_lexer(new_lexer, parser) ------------------------------------------------------------------- -- Most gg functions take their parameters in a table, so it's @@ -785,7 +803,7 @@ -- its arguments in a list: ------------------------------------------------------------------- if not parser and #new_lexer==2 and type(new_lexer[1])=='table' then - return with_lexer(unpack(new_lexer)) + return M.with_lexer(unpack(new_lexer)) end ------------------------------------------------------------------- @@ -803,8 +821,13 @@ end end -function nonempty(primary) - local p = make_parser('non-empty list', { primary = primary, name=primary.name }) +-------------------------------------------------------------------------------- +-- +-- Make sure a parser is used and returns successfully. +-- +-------------------------------------------------------------------------------- +function M.nonempty(primary) + local p = M.make_parser('non-empty list', { primary = primary, name=primary.name }) function p :parse (lx) local fli = lx:lineinfo_right() local content = self.primary (lx) @@ -812,10 +835,12 @@ local li = content.lineinfo or { } fli, lli = li.first or fli, li.last or lli if #content == 0 then - return gg.parse_error (lx, "`%s' must not be empty", self.name or "list") + return gg.parse_error (lx, "`%s' must not be empty.", self.name or "list") else return transform (content, self, fli, lli) end end return p end + +return M
diff --git a/compiler/lexer.lua b/metalua/grammar/lexer.lua similarity index 87% rename from compiler/lexer.lua rename to metalua/grammar/lexer.lua index 15c1ad8..bfd0247 100644 --- a/compiler/lexer.lua +++ b/metalua/grammar/lexer.lua
@@ -17,11 +17,16 @@ -- ------------------------------------------------------------------------------- -module ("lexer", package.seeall) +require 'checks' +local M = { } -lexer = { alpha={ }, sym={ } } +local lexer = { alpha={ }, sym={ } } lexer.__index=lexer +lexer.__type='lexer.stream' + +M.lexer = lexer + local debugf = function() end -- local debugf=printf @@ -29,44 +34,40 @@ ---------------------------------------------------------------------- -- Some locale settings produce bad results, e.g. French locale -- expect float numbers to use commas instead of periods. +-- TODO: change number parser into something loclae-independent, +-- locales are nasty. ---------------------------------------------------------------------- os.setlocale('C') +local MT = { } - ----------------------------------------------------------------------- --- Some locale settings produce bad results, e.g. French locale --- expects float numbers to use commas instead of periods. ----------------------------------------------------------------------- -os.setlocale('C') +M.metatables=MT ---------------------------------------------------------------------- -- Create a new metatable, for a new class of objects. ---------------------------------------------------------------------- local function new_metatable(name) - local mt = { __type = 'metalua::lexer::'..name }; - mt.__index = mt; return mt + local mt = { __type = 'lexer.'..name }; + mt.__index = mt + MT[name] = mt end - ---------------------------------------------------------------------- -- Position: represent a point in a source file. ---------------------------------------------------------------------- -position_metatable = new_metatable 'position' +new_metatable 'position' local position_idx=1 -function new_position(line, column, offset, source) - -- assert(type(line)=='number') - -- assert(type(column)=='number') - -- assert(type(offset)=='number') - -- assert(type(source)=='string') +function M.new_position(line, column, offset, source) + checks('number', 'number', 'number', 'string') local id = position_idx; position_idx = position_idx+1 - return setmetatable({line=line, column=column, offset=offset, source=source, id=id}, position_metatable) + return setmetatable({line=line, column=column, offset=offset, + source=source, id=id}, MT.position) end -function position_metatable :__tostring() +function MT.position :__tostring() return string.format("<%s%s|L%d|C%d|K%d>", self.comments and "C|" or "", self.source, self.line, self.column, self.offset) @@ -77,9 +78,9 @@ ---------------------------------------------------------------------- -- Position factory: convert offsets into line/column/offset positions. ---------------------------------------------------------------------- -position_factory_metatable = new_metatable 'position_factory' +new_metatable 'position_factory' -function new_position_factory(src, src_name) +function M.new_position_factory(src, src_name) -- assert(type(src)=='string') -- assert(type(src_name)=='string') local lines = { 1 } @@ -87,10 +88,10 @@ local max = #src+1 table.insert(lines, max+1) -- +1 includes Eof return setmetatable({ src_name=src_name, line2offset=lines, max=max }, - position_factory_metatable) + MT.position_factory) end -function position_factory_metatable :get_position (offset) +function MT.position_factory :get_position (offset) -- assert(type(offset)=='number') assert(offset<=self.max) local line2offset = self.line2offset @@ -115,7 +116,7 @@ local line = left local column = offset - line2offset[line] + 1 self.last_left = left - return new_position(line, column, offset, self.src_name) + return M.new_position(line, column, offset, self.src_name) end @@ -124,15 +125,14 @@ -- Lineinfo: represent a node's range in a source file; -- embed information about prefix and suffix comments. ---------------------------------------------------------------------- -lineinfo_metatable = new_metatable 'lineinfo' +new_metatable 'lineinfo' -function new_lineinfo(first, last) - assert(first.__type=='metalua::lexer::position') - assert(last.__type=='metalua::lexer::position') - return setmetatable({first=first, last=last}, lineinfo_metatable) +function M.new_lineinfo(first, last) + checks('lexer.position', 'lexer.position') + return setmetatable({first=first, last=last}, MT.lineinfo) end -function lineinfo_metatable :__tostring() +function MT.lineinfo :__tostring() local fli, lli = self.first, self.last local line = fli.line; if line~=lli.line then line =line ..'-'..lli.line end local column = fli.column; if column~=lli.column then column=column..'-'..lli.column end @@ -143,21 +143,19 @@ lli.comments and "|C" or "") end - - ---------------------------------------------------------------------- -- Token: atomic Lua language element, with a category, a content, -- and some lineinfo relating it to its original source. ---------------------------------------------------------------------- -token_metatable = new_metatable 'token' +new_metatable 'token' -function new_token(tag, content, lineinfo) +function M.new_token(tag, content, lineinfo) --printf("TOKEN `%s{ %q, lineinfo = %s} boundaries %d, %d", -- tag, content, tostring(lineinfo), lineinfo.first.id, lineinfo.last.id) - return setmetatable({tag=tag, lineinfo=lineinfo, content}, token_metatable) + return setmetatable({tag=tag, lineinfo=lineinfo, content}, MT.token) end -function token_metatable :__tostring() +function MT.token :__tostring() --return string.format("`%s{ %q, %s }", self.tag, self[1], tostring(self.lineinfo)) return string.format("`%s %q", self.tag, self[1]) end @@ -167,16 +165,16 @@ -- Comment: series of comment blocks with associated lineinfo. -- To be attached to the tokens just before and just after them. ---------------------------------------------------------------------- -comment_metatable = new_metatable 'comment' +new_metatable 'comment' -function new_comment(lines) +function M.new_comment(lines) local first = lines[1].lineinfo.first local last = lines[#lines].lineinfo.last - local lineinfo = new_lineinfo(first, last) - return setmetatable({lineinfo=lineinfo, unpack(lines)}, comment_metatable) + local lineinfo = M.new_lineinfo(first, last) + return setmetatable({lineinfo=lineinfo, unpack(lines)}, MT.comment) end -function comment_metatable :text() +function MT.comment :text() local last_line = self[1].lineinfo.last.line local acc = { } for i, line in ipairs(self) do @@ -187,10 +185,8 @@ return table.concat(acc) end -function new_comment_line(text, lineinfo, nequals) - assert(type(text)=='string') - assert(lineinfo.__type=='metalua::lexer::lineinfo') - assert(nequals==nil or type(nequals)=='number') +function M.new_comment_line(text, lineinfo, nequals) + checks('string', 'lexer.lineinfo', '?number') return { lineinfo = lineinfo, text, nequals } end @@ -233,7 +229,7 @@ backslashes = backslashes :sub (1,-2) end local k, j, i = digits :reverse() :byte(1, 3) - local z = _G.string.byte "0" + local z = string.byte "0" local code = (k or z) + 10*(j or z) + 100*(i or z) - 111*z if code > 255 then error ("Illegal escape sequence '\\"..digits.. @@ -272,7 +268,7 @@ a = "\a", b = "\b", f = "\f", n = "\n", r = "\r", t = "\t", v = "\v", ["\\"] = "\\", ["'"] = "'", ['"'] = '"', ["\n"] = "\n" } - return t[x] or error([[Unknown escape sequence '\]]..x..[[']]) + return t[x] or x end s = s: gsub ("(\\+)(z%s*)", unesc_z) -- Lua 5.2 @@ -297,14 +293,13 @@ ---------------------------------------------------------------------- function lexer :extract () local attached_comments = { } - -- generate a non-comment token, attach comments to its lineinfo. - local function gen_token(...) - local token = new_token(...) + local function gen_token(...) + local token = M.new_token(...) if #attached_comments>0 then -- attach previous comments to token - local comments = new_comment(attached_comments) + local comments = M.new_comment(attached_comments) token.lineinfo.first.comments = comments if self.lineinfo_last_extracted then - self.lineinfo_last_extracted.comments = comments + self.lineinfo_last_extracted.comments = comments end attached_comments = { } end @@ -317,10 +312,10 @@ -- skip whitespaces self.i = self.src:match (self.patterns.spaces, self.i) - if self.i>#self.src then -- handle EOF + if self.i>#self.src then local fli = self.posfact :get_position (#self.src+1) local lli = self.posfact :get_position (#self.src+1) -- ok? - local tok = gen_token("Eof", "eof", new_lineinfo(fli, lli)) + local tok = gen_token("Eof", "eof", M.new_lineinfo(fli, lli)) tok.lineinfo.last.facing = lli return tok end @@ -332,7 +327,7 @@ if tag then local fli = self.posfact :get_position (i_first) local lli = self.posfact :get_position (self.i-1) - local lineinfo = new_lineinfo(fli, lli) + local lineinfo = M.new_lineinfo(fli, lli) if tag=='Comment' then local prev_comment = attached_comments[#attached_comments] if not xtra -- new comment is short @@ -342,7 +337,7 @@ prev_comment[1] = prev_comment[1].."\n"..content -- TODO quadratic, BAD! prev_comment.lineinfo.last = lli else -- accumulate comment - local comment = new_comment_line(content, lineinfo, xtra) + local comment = M.new_comment_line(content, lineinfo, xtra) table.insert(attached_comments, comment) end break -- back to skipping spaces @@ -379,7 +374,7 @@ ---------------------------------------------------------------------- function lexer :extract_short_string() local k = self.src :sub (self.i,self.i) -- first char - if k~=[[']] and k~=[["]] then return end -- no match ' + if k~=[[']] and k~=[["]] then return end -- no match' local i = self.i + 1 local j = i while true do @@ -481,14 +476,14 @@ local k = w:sub(1,1) local list = self.sym [k] if not list then list = { }; self.sym [k] = list end - _G.table.insert (list, w) + table.insert (list, w) elseif w:match "^%p$" then return else error "Invalid keyword" end end end ---------------------------------------------------------------------- --- Return the [n]th next token, without consumming it. +-- Return the [n]th next token, without consuming it. -- [n] defaults to 1. If it goes pass the end of the stream, an EOF -- token is returned. ---------------------------------------------------------------------- @@ -512,11 +507,11 @@ self :peek (n) local a for i=1,n do - a = _G.table.remove (self.peeked, 1) + a = table.remove (self.peeked, 1) -- TODO: is this used anywhere? I think not. a.lineinfo.last may be nil. --self.lastline = a.lineinfo.last.line end - self.lineinfo_last = a.lineinfo.last + self.lineinfo_last_consumed = a.lineinfo.last return a end @@ -524,7 +519,10 @@ -- Returns an object which saves the stream's current state. ---------------------------------------------------------------------- -- FIXME there are more fields than that to save -function lexer :save () return { self.i; _G.table.cat(self.peeked) } end +-- TODO remove metalua.table dependency, used here to make a shallow copy +-- of self.peeked, hash-part included. The hash-part seems unused, +-- so { unpack(x) } should work OK. +function lexer :save () return { self.i; table.cat(self.peeked) } end ---------------------------------------------------------------------- -- Restore the stream's state, as saved by method [save]. @@ -576,7 +574,7 @@ end function lexer :lineinfo_left() - return self.lineinfo_last + return self.lineinfo_last_consumed end ---------------------------------------------------------------------- @@ -588,16 +586,16 @@ return setmetatable ({ }, self) :takeover (src_or_stream) elseif type(src_or_stream)=='string' then -- it's a source string local src = src_or_stream - local first_position = new_position(1, 1, 1, name) + local pos1 = M.new_position(1, 1, 1, name) local stream = { src_name = name; -- Name of the file src = src; -- The source, as a single string peeked = { }; -- Already peeked, but not discarded yet, tokens i = 1; -- Character offset in src attached_comments = { },-- comments accumulator - lineinfo_last = first_position, -- right boundary of last consummed token - lineinfo_last_extracted = first_position, -- right boundary of last extracted token - posfact = new_position_factory (src_or_stream, name) + lineinfo_last_extracted = pos1, + lineinfo_last_consumed = pos1, + posfact = M.new_position_factory (src_or_stream, name) } setmetatable (stream, self) @@ -638,7 +636,7 @@ local function err () error ("Got " .. tostring (a) .. ", expected one of these keywords : '" .. - _G.table.concat (words,"', '") .. "'") end + table.concat (words,"', '") .. "'") end if not a or a.tag ~= "Keyword" then err () end if #words == 0 then return a[1] end @@ -652,8 +650,9 @@ -- ---------------------------------------------------------------------- function lexer :clone() - require 'metalua.runtime' + require 'metalua.table' local clone = { + -- TODO: remove metalua.table dependency alpha = table.deep_copy(self.alpha), sym = table.deep_copy(self.sym) } setmetatable(clone, self) @@ -671,3 +670,5 @@ self.attached_comments = { } self.lineinfo_last = self.posfact :get_position (#self.src+1) end + +return M \ No newline at end of file
diff --git a/lib/metalua/package2.lua b/metalua/package.lua similarity index 63% rename from lib/metalua/package2.lua rename to metalua/package.lua index d39f6c3..3bba351 100644 --- a/lib/metalua/package2.lua +++ b/metalua/package.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,15 +15,16 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- local package = package -require 'metalua.mlc' +require 'metalua.table' +require 'metalua.string' package.metalua_extension_prefix = 'metalua.extension.' -package.mpath = os.getenv 'LUA_MPATH' or +package.mpath = package.mpath or os.getenv 'LUA_MPATH' or './?.mlua;/usr/local/share/lua/5.1/?.mlua;'.. '/usr/local/share/lua/5.1/?/init.mlua;'.. '/usr/local/lib/lua/5.1/?.mlua;'.. @@ -59,29 +60,7 @@ if file then return file, filename end table.insert(errors, string.format("\tno lua file %q", filename)) end - return false, table.concat(errors, "\n")..'\n' -end - ----------------------------------------------------------------------- --- Execute a metalua module sources compilation in a separate process --- Sending back the bytecode directly is difficult, as some shells --- (at least MS-Windows') interpret some characters. So rather than --- base64-encoding the bytecode, AST is returned from the child --- process, and converted to bytecode then function in the calling --- process. ----------------------------------------------------------------------- -local function spring_load(filename) - -- FIXME: handle compilation errors - local pattern = - [=[lua -l metalua.compiler -l serialize -e ]=].. - [=["print(serialize(mlc.luafile_to_ast[[%s]]))"]=] - local cmd = string.format (pattern, filename) - --print ("running command: ``" .. cmd .. "''") - local fd = io.popen (cmd) - local ast_src = fd:read '*a' - fd:close() - local ast = lua_loadstring (ast_src) () -- much faster than loadstring() - return mlc.ast_to_function(ast, filename) + return false, '\n'..table.concat(errors, "\n")..'\n' end ---------------------------------------------------------------------- @@ -90,16 +69,10 @@ function package.metalua_loader (name) local file, filename_or_msg = package.findfile (name, package.mpath) if not file then return filename_or_msg end - if package.metalua_nopopen then - local luastring = file:read '*a' - file:close() - return mlc.luastring_to_function (luastring, name) - else - file:close() - require 'metalua.mlc_xcall' - local status, ast = mlc_xcall.client_file (filename_or_msg) - return mlc.ast_to_function(ast) - end + local luastring = file:read '*a' + file:close() + local mlc = require 'metalua.compiler'.new() + return mlc :src_to_function (luastring, name) end ---------------------------------------------------------------------- @@ -111,15 +84,12 @@ ---------------------------------------------------------------------- -- Load an extension. ---------------------------------------------------------------------- -function extension (name, noruntime) - local complete_name = package.metalua_extension_prefix..name - local x = require (complete_name) - if x==true then return - elseif type(x) ~= 'table' then - error ("extension returned %s instead of an AST", type(x)) - else - return x - end +function extension (name, mlp) + local complete_name = package.metalua_extension_prefix..name + -- TODO: pass mlp around + local extend_func = require (complete_name) + local ast =extend_func(mlp) + return ast end return package
diff --git a/lib/metalua/metaloop.mlua b/metalua/repl.mlua similarity index 83% rename from lib/metalua/metaloop.mlua rename to metalua/repl.mlua index 5e42f30..204482e 100644 --- a/lib/metalua/metaloop.mlua +++ b/metalua/repl.mlua
@@ -17,20 +17,21 @@ -- ------------------------------------------------------------------------------- -require 'metalua.compiler' - -module ('metaloop', package.seeall) - +-- Keep these global: PRINT_AST = true LINE_WIDTH = 60 PROMPT = "M> " PROMPT2 = ">> " +local M = { } + +local readline + do -- set readline() to a line reader, either editline otr a default local status, _ = pcall(require, 'editline') if status then local rl_handle = editline.init 'metalua' - readline = |p| rl_handle:read(p) + readline = |p| rl_handle:read(p) else function readline (p) io.write (p) @@ -40,14 +41,14 @@ end end -function reached_eof(lx, msg) +local function reached_eof(lx, msg) return lx:peek().tag=='Eof' or msg:find "token `Eof" end -printf ("Metalua, interactive REPLoop.\n".. - "(c) 2006-2011 <metalua@gmail.com>") -function run() +function M.run() + printf ("Metalua, interactive REPLoop.\n".. + "(c) 2006-2013 <metalua@gmail.com>") local lines = { } while true do local src, lx, ast, f, results, success @@ -60,17 +61,17 @@ table.insert(lines, line) src = table.concat (lines, "\n") until #line>0 - - lx = mlc.luastring_to_lexstream(src) - success, ast = pcall(mlc.lexstream_to_ast, lx) + + lx = mlc :src_to_lexstream(src) + success, ast = pcall(mlc.lexstream_to_ast, mlc, lx) if success then - local check_status, check_msg = pcall(mlc.check_ast, ast) + local check_status, check_msg = pcall(check_ast, ast) if not check_status then print "Invalid AST:" print (check_msg) lines = { } else - success, f = pcall(mlc.ast_to_function, ast, '=stdin') + success, f = pcall(mlc.ast_to_function, mlc, ast, '=stdin') if success then results = { xpcall(f, debug.traceback) } success = table.remove (results, 1) @@ -95,8 +96,10 @@ if not reached_eof(lx, ast) then print "Can't compile source into AST:" print (ast) - lines = { } + lines = { } end end end -end \ No newline at end of file +end + +return M \ No newline at end of file
diff --git a/lib/metalua/string2.lua b/metalua/string.lua similarity index 100% rename from lib/metalua/string2.lua rename to metalua/string.lua
diff --git a/lib/metalua/table2.lua b/metalua/table.lua similarity index 98% rename from lib/metalua/table2.lua rename to metalua/table.lua index 653fd68..565f832 100644 --- a/lib/metalua/table2.lua +++ b/metalua/table.lua
@@ -104,8 +104,8 @@ function table.icat(...) local result = { } - for t in values {...} do - for x in values (t) do + for _, t in ipairs {...} do + for _, x in pairs (t) do table.insert (result, x) end end @@ -152,7 +152,7 @@ -- the fact the [next()] enumerates the array-part before the hash-part. function table.cat(...) local y={ } - for x in values{...} do + for _, x in ipairs{...} do -- cat array-part for _, v in ipairs(x) do table.insert(y,v) end -- cat hash-part @@ -184,7 +184,6 @@ return dst end - function table.range(a,b,c) if not b then assert(not(c)); b=a; a=1 elseif not c then c = (b>=a) and 1 or -1 end @@ -193,7 +192,6 @@ return result end - -- FIXME: new_indent seems to be always nil?! -- FIXME: accumulator function should be configurable, -- so that print() doesn't need to bufferize the whole string
diff --git a/lib/metalua/treequery.mlua b/metalua/treequery.mlua old mode 100644 new mode 100755 similarity index 92% rename from lib/metalua/treequery.mlua rename to metalua/treequery.mlua index 5925471..c1f9f84 --- a/lib/metalua/treequery.mlua +++ b/metalua/treequery.mlua
@@ -74,7 +74,7 @@ from_up = { }, up_f = false, down_f = false, - filters = { } + filters = { }, }, Q) end @@ -265,10 +265,11 @@ if #args==1 then local tag = ... return (|node| node.tag==tag) + --return function(self, node) printf("node %s has_tag %s?", table.tostring(node), tag); return node.tag==tag end else local tags = { } for _, tag in ipairs(args) do tags[tag]=true end - return function(node) + return function(self, node) local node_tag = node.tag return node_tag and tags[node_tag] end @@ -287,7 +288,7 @@ --- Predicate to test whether a node represents a statement. -- It is context-aware, i.e. it recognizes `Call and `Invoke nodes -- used in a statement context as such. -function M.is_stat(node, parent) +function M.is_stat(self, node, parent) local tag = node.tag if not tag then return false elseif STAT_TAGS[tag] then return true @@ -296,7 +297,7 @@ end --- Predicate to test whether a node represents a statements block. -function M.is_block(node) return node.tag==nil end +function M.is_block(self, node) return node.tag==nil end -- ----------------------------------------------------------------------------- -- ----------------------------------------------------------------------------- @@ -322,11 +323,7 @@ return false end ---- Retrieve the binder associated to an occurrence within root. --- --- Each call to this function might cause a traversal of the whole --- `root` tree. For use inside a query method, prefer `treequery.get_binder`. --- +--- Retrieve the binder associated to an occurrence within root. -- @param occurrence an Id node representing an occurrence in `root`. -- @param root the tree in which `node` and its binder occur. -- @return the binder node, and its ancestors up to root if found. @@ -341,12 +338,25 @@ return unpack(result) end +--- Predicate to filter occurrences of a given binder. +-- Warning: it relies on internal scope book-keeping, +-- and for this reason, it only works as query method argument. +-- It won't work outside of a query. +-- @param binder the binder whose occurrences must be kept by predicate +-- @return a predicate ---- Return a predicate testing whether a node is an occurrence of the --- given binder. --- --- Caveat: only works in a treequery method. --- +-- function M.is_occurrence_of(binder) +-- return function(node, ...) +-- if node.tag ~= 'Id' then return nil end +-- if M.is_binder(node, ...) then return nil end +-- local scope = ACTIVE_SCOPE[node] +-- if not scope then return nil end +-- local result = scope :get (node[1]) or { } +-- if result[1] ~= binder then return nil end +-- return unpack(result) +-- end +-- end + function M.is_occurrence_of(binder) return function(node, ...) local b = M.get_binder(node) @@ -354,12 +364,6 @@ end end ---- Return the binder which captures this occurence of a variable if applicable; --- return `nil` if the variable is a binder or a free variable --- --- Caveat: this only works in arguments of query methods. --- Out of a query, use `treequery.binder()`. --- function M.get_binder(occurrence, ...) if occurrence.tag ~= 'Id' then return nil end if M.is_binder(occurrence, ...) then return nil end @@ -381,7 +385,7 @@ if type(a)~='number' then n, pred = 2, n end if type(pred)=='string' then pred = M.has_tag(pred, ...) end return function(self, ...) - return select(n, ...) and pred(select(n, ...)) + return select(n, ...) and pred(self, select(n, ...)) end end @@ -414,7 +418,7 @@ -- @return a predicate function M.is_nth(a, b) b = b or a - return function(node, parent) + return function(self, node, parent) if not parent then return false end local nchildren = #parent local a = a<=0 and nchildren+a+1 or a
diff --git a/lib/metalua/treequery/walk.mlua b/metalua/treequery/walk.mlua old mode 100644 new mode 100755 similarity index 96% rename from lib/metalua/treequery/walk.mlua rename to metalua/treequery/walk.mlua index 9d35e46..2e10956 --- a/lib/metalua/treequery/walk.mlua +++ b/metalua/treequery/walk.mlua
@@ -48,7 +48,7 @@ -- -- corresponding walk.xxx functions also take care of calling cfg callbacks. --{ extension "match" } +-{ extension ("match",...) } local M = { traverse = { }; tags = { }; debug = false } @@ -80,7 +80,7 @@ local CS = || cfg.scope :restore() -- Close scope match x with - | {...} if x.tag == nil -> for y in ivalues(x) do M.stat(cfg, y, ...) end + | {...} if x.tag == nil -> for _, y in ipairs(x) do M.stat(cfg, y, ...) end -- no tag --> node not inserted in the history ancestors | `Do{...} -> OS(x); for _, y in ipairs(x) do S(y) end; CS(x) | `Set{ lhs, rhs } -> EL(lhs); EL(rhs) @@ -139,16 +139,16 @@ end function M.traverse.block (cfg, x, ...) - assert(type(x)=='table', "traverse.block() expects a table") + assert(type(x)=='table', "traverse.block() expects a table") if x.tag then M.malformed(cfg, x, ...) - else for y in ivalues(x) do M.stat(cfg, y, x, ...) end + else for _, y in ipairs(x) do M.stat(cfg, y, x, ...) end end end function M.traverse.expr_list (cfg, x, ...) assert(type(x)=='table', "traverse.expr_list() expects a table") -- x doesn't appear in the ancestors - for y in ivalues(x) do M.expr(cfg, y, ...) end + for _, y in ipairs(x) do M.expr(cfg, y, ...) end end function M.malformed(cfg, x, ...) @@ -169,7 +169,7 @@ if cfg.occurrence then cfg.occurrence(cfg.scope :get(x[1]), x, ...) end end --- TODO: Is it useful to call each error handling function +-- TODO: Is it useful to call each error handling function? function M.binder_list (cfg, id_list, ...) local f = cfg.binder local ferror = cfg.error or cfg.malformed or cfg.unknown @@ -209,7 +209,7 @@ ---------------------------------------------------------------------- -- Declare [M.stat], [M.expr], [M.block] and [M.expr_list] ---------------------------------------------------------------------- -for w in values{ "stat", "expr", "block" } do --, "malformed", "unknown" } do +for _, w in ipairs{ "stat", "expr", "block" } do --, "malformed", "unknown" } do M[w] = walker_builder (w, M.traverse[w]) end @@ -246,4 +246,4 @@ function S :get (var_name) return self.current[var_name] end function S :set (key, val) self.current[key] = val end -return M +return M
diff --git a/lib/metalua/walk.mlua b/metalua/walk.mlua similarity index 91% rename from lib/metalua/walk.mlua rename to metalua/walk.mlua index 6c13fb9..139474f 100644 --- a/lib/metalua/walk.mlua +++ b/metalua/walk.mlua
@@ -17,7 +17,9 @@ -- ------------------------------------------------------------------------------- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- +-- Code walkers +-- -- This library offers a generic way to write AST transforming -- functions. Macros can take bits of AST as parameters and generate a -- more complex AST with them; but modifying an AST a posteriori is @@ -163,16 +165,16 @@ -- { stat = f1, expr = { up = f1 } } -- -- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- --{ extension "match" } +-{ extension ('match', ...) } walk = { traverse = { }; tags = { }; debug = false } --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- Standard tags: can be used to guess the type of an AST, or to check -- that the type of an AST is respected. --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- walk.tags.stat = table.transpose{ 'Do', 'Set', 'While', 'Repeat', 'Local', 'Localrec', 'Return', 'Fornum', 'Forin', 'If', 'Break', 'Goto', 'Label', @@ -193,14 +195,14 @@ function walk.traverse.stat (cfg, x, ...) if walk.debug then printf("traverse stat %s", table.tostring(x)) end local log = {...} - local B = |y| walk.block (cfg, y, x, unpack(log)) - local S = |y| walk.stat (cfg, y, x, unpack(log)) - local E = |y| walk.expr (cfg, y, x, unpack(log)) - local EL = |y| walk.expr_list (cfg, y, x, unpack(log)) - local I = |y| walk.binder_list (cfg, y, x, unpack(log)) - local DOWN = || scope(cfg, 'down') - local UP = || scope(cfg, 'up') - local function BS(y) DOWN(); B(y); UP() end + local B = |y| walk.block (cfg, y, x, unpack(log)) + local S = |y| walk.stat (cfg, y, x, unpack(log)) + local E = |y| walk.expr (cfg, y, x, unpack(log)) + local EL = |y| walk.expr_list (cfg, y, x, unpack(log)) + local I = |y| walk.binder_list (cfg, y, x, unpack(log)) + local function BS(y) + scope (cfg, 'down'); B(y); scope (cfg, 'up') + end match x with | {...} if x.tag == nil -> for y in ivalues(x) do walk.stat(cfg, y, ...) end @@ -208,13 +210,13 @@ | `Do{...} -> BS(x) | `Set{ lhs, rhs } -> EL(lhs); EL(rhs) | `While{ cond, body } -> E(cond); BS(body) - | `Repeat{ body, cond } -> DOWN(); B(body); E(cond); UP() + | `Repeat{ body, cond } -> scope(cfg, 'down'); B(body); E(cond); scope(cfg, 'up') | `Local{ lhs } -> I(lhs) | `Local{ lhs, rhs } -> EL(rhs); I(lhs) | `Localrec{ lhs, rhs } -> I(lhs); EL(rhs) - | `Fornum{ i, a, b, body } -> E(a); E(b); DOWN(); I{i}; B(body); UP() + | `Fornum{ i, a, b, body } -> E(a); E(b); I{i}; BS(body) | `Fornum{ i, a, b, c, body } -> E(a); E(b); E(c); I{i}; BS(body) - | `Forin{ i, rhs, body } -> EL(rhs); DOWN(); I(i); B(body); UP() + | `Forin{ i, rhs, body } -> EL(rhs); I(i); BS(body) | `If{...} -> for i=1, #x-1, 2 do E(x[i]); BS(x[i+1]) end if #x%2 == 1 then BS(x[#x]) end | `Call{...}|`Invoke{...}|`Return{...} -> EL(x) @@ -234,15 +236,13 @@ local E = |y| walk.expr (cfg, y, x, unpack(log)) local EL = |y| walk.expr_list (cfg, y, x, unpack(log)) local I = |y| walk.binder_list (cfg, y, x, unpack(log)) - local DOWN = || scope(cfg, 'down') - local UP = || scope(cfg, 'up') match x with | `Paren{ e } -> E(e) | `Call{...} | `Invoke{...} -> EL(x) | `Index{ a, b } -> E(a); E(b) | `Op{ opid, ... } -> E(x[2]); if #x==3 then E(x[3]) end - | `Function{ params, body } -> DOWN(); I(params); B(body); UP() - | `Stat{ b, e } -> DOWN(); B(b); E(e); UP() + | `Function{ params, body } -> I(params); scope(cfg, 'down'); B(body); scope (cfg, 'in') + | `Stat{ b, e } -> scope(cfg, 'down'); B(b); E(e); scope (cfg, 'in') | `Table{ ... } -> for i = 1, #x do match x[i] with | `Pair{ k, v } -> E(k); E(v) @@ -258,13 +258,13 @@ function walk.traverse.block (cfg, x, ...) assert(type(x)=='table', "traverse.block() expects a table") - for y in ivalues(x) do walk.stat(cfg, y, x, ...) end + for _, y in ipairs(x) do walk.stat(cfg, y, x, ...) end end function walk.traverse.expr_list (cfg, x, ...) assert(type(x)=='table', "traverse.expr_list() expects a table") -- x doesn't appear in the log - for y in ivalues(x) do walk.expr(cfg, y, ...) end + for _, y in ipairs(x) do walk.expr(cfg, y, ...) end end @@ -312,7 +312,7 @@ ---------------------------------------------------------------------- -- Declare [walk.stat], [walk.expr], [walk.block] and [walk.expr_list] ---------------------------------------------------------------------- -for w in values{ "stat", "expr", "block", "expr_list" } do +for _, w in ipairs{ "stat", "expr", "block", "expr_list" } do walk[w] = walker_builder (w, walk.traverse[w]) end @@ -321,7 +321,7 @@ ---------------------------------------------------------------------- function walk.binder_list (cfg, x, ...) local f = cfg.binder - if f then for v in ivalues(x) do f(v, ...) end end + if f then for _, v in ipairs(x) do f(v, ...) end end end ---------------------------------------------------------------------- @@ -334,3 +334,5 @@ if not x.tag then return walk.block(cfg, x, ...) end error ("Can't guess the AST type from tag "..(x.tag or '<none>')) end + +return walk
diff --git a/metalua/walk/bindings.lua b/metalua/walk/bindings.lua new file mode 100644 index 0000000..33ae4e1 --- /dev/null +++ b/metalua/walk/bindings.lua
@@ -0,0 +1,55 @@ +-------------------------------------------------------------------------------- +-- Copyright (c) 2006-2013 Fabien Fleutot and others. +-- +-- All rights reserved. +-- +-- This program and the accompanying materials are made available +-- under the terms of the Eclipse Public License v1.0 which +-- accompanies this distribution, and is available at +-- http://www.eclipse.org/legal/epl-v10.html +-- +-- This program and the accompanying materials are also made available +-- under the terms of the MIT public license which accompanies this +-- distribution, and is available at http://www.lua.org/license.html +-- +-- Contributors: +-- Fabien Fleutot - API and implementation +-- +-------------------------------------------------------------------------------- + +local W = require 'metalua.treequery.walk' + +local function bindings(ast) + local cfg = { } + local locals = { } + local globals = { } + + function cfg.occurrence(binding_ctx, id_occ, ...) + if binding_ctx then + local binder = binding_ctx[2] + local id_name = id_occ[1] + if not locals[binder][id_name] then + locals[binder][id_name] = {} + end + table.insert(locals[binder][id_name], id_occ) + else + local occ_name = id_occ[1] + local t = globals[occ_name] + if t then table.insert(t, id_occ) else globals[occ_name]={ id_occ } end + end + end + + function cfg.binder(id, stat, ...) + local id_name = id[1] + if not locals[stat] then + locals[stat] = {} + end + if not locals[stat][id_name] then + locals[stat][id_name] = {} + end + end + W.guess(cfg, ast) + return locals, globals +end + +return bindings
diff --git a/lib/metalua/walk/id.mlua b/metalua/walk/id.mlua similarity index 96% rename from lib/metalua/walk/id.mlua rename to metalua/walk/id.mlua index 9ad40c4..52eabe5 100644 --- a/lib/metalua/walk/id.mlua +++ b/metalua/walk/id.mlua
@@ -17,7 +17,7 @@ -- ------------------------------------------------------------------------------- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- This library walks AST to gather information about the identifiers -- in it. It classifies them between free variables and bound @@ -60,10 +60,10 @@ -- * `Splice must stop the walking, so that user code won't be -- converted -- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- --{ extension 'match' } --{ extension 'log' } +-{ extension ('match', ...) } +-- -{ extension ('log', ...) } require 'metalua.walk' require 'metalua.walk.scope' @@ -124,7 +124,7 @@ -- by returning 'break'. ------------------------------------------------------------- scope:push() - for stat in values (block) do walk.stat(cfg, stat, x, ...) end + for _, stat in ipairs (block) do walk.stat(cfg, stat, x, ...) end walk.expr(cfg, expr, x, unpack(parents)) scope:pop() return 'break' @@ -162,7 +162,7 @@ -- by returning 'break'. ------------------------------------------------------------- scope:push() - for stat in values (block) do walk.stat(cfg, stat, x, ...) end + for _, stat in ipairs (block) do walk.stat(cfg, stat, x, ...) end walk.expr(cfg, expr, x, ...) scope:pop() return 'break'
diff --git a/lib/metalua/walk/scope.lua b/metalua/walk/scope.lua similarity index 98% rename from lib/metalua/walk/scope.lua rename to metalua/walk/scope.lua index 856917b..29d9402 100644 --- a/lib/metalua/walk/scope.lua +++ b/metalua/walk/scope.lua
@@ -17,7 +17,7 @@ -- ------------------------------------------------------------------------------- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- -- -- Scopes: this library helps keeping track of identifier scopes, -- typically in code walkers. @@ -41,7 +41,7 @@ -- * s.current is the current scope, a table with variable names as -- keys and their associated value val (or 'true') as value. -- --------------------------------------------------------------------------------- +------------------------------------------------------------------------------- scope = { } scope.__index = scope
diff --git a/samples/clist_test.mlua b/samples/clist_test.mlua deleted file mode 100644 index 804b786..0000000 --- a/samples/clist_test.mlua +++ /dev/null
@@ -1,39 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{extension "clist"} - --- integers from 2 to 50, by steps of 2: -x = { i for i = 2, 50, 2 } - --- the same, obtained by filtering over all integers <= 50: -y = { i for i = 1, 50 if i%2==0 } - --- prime numbers, implemented in an inefficient way: -local sieve, n = { i for i=2, 100 }, 1 -while n < #sieve do - sieve = { - i for i in values(sieve[1 ... n]); - i for i in values(sieve[n+1 ... #sieve]) if i%sieve[n] ~= 0 } - n += 1 -end - -print "Prime numbers < 100, computed with lists by comprehension:" -table.print(sieve) -
diff --git a/samples/h_test.mlua b/samples/h_test.mlua deleted file mode 100644 index 7923884..0000000 --- a/samples/h_test.mlua +++ /dev/null
@@ -1,79 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'log' } --{ extension 'H' } - -require 'metalua.compiler' - -TEST_CASES = { - - { "everything should be renamed", - +{ block: - local x = 3 - print(x) }, - { } }, - - { "don't get confused between global and local x", - +{ block: - print(x) - local x = 3 - print(x) }, - { alpha = { } } }, - - { "don't rename keepme", - +{ block: - keepme() - dont_keep_me() }, - { keep = 'keepme' , alpha = `Local{ { }, { } } } }, - - { "print shouldn't be renamed the 2nd and 3rd time", - +{ block: - print(i) - -{!`Call{`Id 'print', `String 'hi' } } - -{!+{print 'hi'}} }, - { } }, - - { "print shouldn't be renamed at all", - +{ block: - print(i) - -{`Call{`Id 'print', `String 'hi' } } - -{+{print 'hi'}} }, - { keep = 'print' } }, - - { "Rename print with a pre-specified name, rename x freely, not y", - +{ block: - print (x, y) }, - { alpha = +{stat: local RENAMED_PRINT = print}, - keep = {y = true} } } } - -for case in ivalues(TEST_CASES) do - local comment, ast, cfg = unpack(case) - print ('\n'..'-':rep(70)) - print (comment) - local H = H:new(cfg) - print ("\nBEFORE PARSING:") - $log (ast, H, 50) - H(ast) - print ("\nAFTER PARSING:") - $log (ast, H, 50) -end - -print ('\n'..'=':rep(70)) -$log(TEST_CASES,40) \ No newline at end of file
diff --git a/samples/h_test2.mlua b/samples/h_test2.mlua deleted file mode 100644 index ee9f64d..0000000 --- a/samples/h_test2.mlua +++ /dev/null
@@ -1,44 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ block: - -{ extension 'log' } - -{ extension 'H' } - - require 'metalua.dollar' - - local H = H:new() - print("initial H.alpha", H.alpha) - - - function dollar.Q(cond, iftrue, iffalse) - local b = +{ block: - local v - if -{!cond} then v = -{!iftrue} - else v = -{!iffalse} end } - local r = `Stat{ b, +{v} } - H(r) - return r - end - - $log(H) - return H.alpha } - -x=1 ; y=$Q(x==1, 'one', 'two') ; print(y) -x=2 ; y=$Q(x==1, 'one', 'two') ; print(y) \ No newline at end of file
diff --git a/samples/hello_world.mlua b/samples/hello_world.mlua deleted file mode 100644 index 48cb7eb..0000000 --- a/samples/hello_world.mlua +++ /dev/null
@@ -1,26 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- The message numbered (1) will display at compilation, i.e. before --- (2) is displayed (supposing that compilation and execution are --- both requested) - -print "(2) This is a runtime message: Hello world!" --{ print "(1) This is a compile-time message: Hello world!" } -
diff --git a/samples/ifexpr.mlua b/samples/ifexpr.mlua deleted file mode 100644 index c6ca15a..0000000 --- a/samples/ifexpr.mlua +++ /dev/null
@@ -1,161 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - ----------------------------------------------------------------------------------- --- This samples walks you through the writing of a simple extension. --- --- Lua makes a difference between statements and expressions, and it's sometimes --- cumbersome to put a statement where an expression is expected. Among others, --- if-then-else constructs are statements, so you cannot write: --- --- > local foo = if bar then 1 else 2 --- --- Indeed, an expression is expected at the right of the equal, and "if ..." is --- a statement, which expects nested statements as "then" and "else" clauses. --- The example above must therefore be written: --- --- > local foo --- > if bar then foo=1 else foo=2 end --- --- --- Let's allow if-then-[elseif*]-[else] constructs to be used in an expression's --- context. In such a context, 'then' and 'else' are expected to be followed by --- expressions, not statement blocks. --- --- Stuff you probably need to understand, at least summarily, to follow this --- code: --- * Lua syntax --- * the fact that -{ ... } switches metalua into compile time mode --- * mlp, the dynamically extensible metalua parser, which will be extended with --- the new construct at compile time. --- * gg, the grammar generator that allows to build and extend parsers, and with --- which mlp is implemented. --- * the fact that code can be interchangeably represented as abstract syntax --- trees with `Foo{ bar } notations (easy to manipulate) or as quotes inside a --- +{ ... } (easy to read). --- ----------------------------------------------------------------------------------- - - ----------------------------------------------------------------------------------- --- How to turn this file in a proper syntax extension. --- =================================================== --- --- To turn this example's metalevel 0 code into a regular extension: --- * Put everything inside the -{block: ... } in a separate .mlua file; --- * save it in a directory called 'extension', which is itself --- in your $LUA_MPATH. For instance, if your $LUA_MPATH contains --- '~/local/metalua/?.mlua', you can save it as --- '~/local/metalua/extension-compiler/ifexpr.mlua' --- * Load the extension with "-{ extension 'ifexpr' }", whenever you want to --- use it. ----------------------------------------------------------------------------------- - --{ block: -- Enter metalevel 0, where we'll start hacking the parser. - - ------------------------------------------------------------------------------- - -- Most extension implementations are cut in two parts: a front-end which - -- parses the syntax into some custom tree, and a back-end which turns that - -- tree into a compilable AST. Since the front-end calls the back-end, the - -- later has to be declared first. - ------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------- - -- Back-end: - -- ========= - -- This is the builder that turns the parser's result into an expression AST. - -- Local vars: - -- ----------- - -- elseifthen_list : list of { condition, expression_if_true } pairs, - -- opt_else: either the expression in the 'else' final clause if any, - -- or false if there's no else clause. - -- v: the variable in which the result will be stored. - -- ifstat: the if-then-else statement that will be generated from - -- then if-then-else expression, then embedded in a `Stat{} - -- - -- The builder simply turns all expressions into blocks, so that they fit in - -- a regular if-then-else statement. Then the resulting if-then-else is - -- embedded in a `Stat{ } node, so that it can be placed where an expression - -- is expected. - -- - -- The variable in which the result is stored has its name generated by - -- mlp.gensym(). This way we're sure there will be no variable capture. - -- When macro hygiene problems are more complex, it's generally a good - -- idea to give a look at the extension 'H'. - ------------------------------------------------------------------------------- - local function builder (x) - local elseifthen_list, opt_else = unpack (x) - - local v = mlp.gensym 'ife' -- the selected expr will be stored in this var. - local ifstat = `If{ } - for y in ivalues (elseifthen_list) do - local cond, val = unpack (y) - table.insert (ifstat, cond) - table.insert (ifstat, { `Set{ {v}, {val} } }) -- change expr into stat. - end - if opt_else then -- the same for else clause, except that there's no cond. - table.insert (ifstat, { `Set{ {v}, {opt_else} } }) - end - return `Stat{ +{block: local -{v}; -{ifstat}}, v } - end - - ------------------------------------------------------------------------------- - -- Front-end: - -- ========== - -- This is mostly the same as the regular if-then-else parser, except that: - -- * it's added to the expression parser, not the statement parser; - -- * blocks after conditions are replaced by exprs; - -- - -- In Lua, 'end' traditionally terminates a block, not an - -- expression. Should there be a 'end' to respect if-then-else - -- usual syntax, or should there be none, to respect usual implicit - -- expression ending? I chose not to put an 'end' here, but other people - -- might have other tastes... - ------------------------------------------------------------------------------- - mlp.expr:add{ name = 'if-expression', - 'if', - gg.list { gg.sequence{mlp.expr, "then", mlp.expr}, separators="elseif" }, - gg.onkeyword{ 'else', mlp.expr }, - builder = builder } - -} -- Back to metalevel 1, with the new feature enabled - -local foo, bar - ------------------------------------------------------------- --- The parser will read this as: --- { { { `Id 'foo', `Number 1 }, --- { `Id 'bar', `Number 2 } }, --- `Number 3 }, --- then feed it to 'builder', which will turn it into an AST ------------------------------------------------------------- - -local x = if false then 1 elseif bar then 2 else 3 - ------------------------------------------------------------- --- The result after builder will be: --- `Stat{ +{block: local $v$ --- if foo then $v$ = 1 --- elseif bar then $v$ = 2 --- else $v$ = 3 --- end }, `Id "$v$" } ------------------------------------------------------------- - -assert (x == 3) -print "It seems to work..." \ No newline at end of file
diff --git a/samples/lex_switch_test.mlua b/samples/lex_switch_test.mlua deleted file mode 100644 index 4c79709..0000000 --- a/samples/lex_switch_test.mlua +++ /dev/null
@@ -1,59 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- This is a simple and somewhat stupid example of how to switch --- lexers dynamically. Behind a V, X and Y are the only reserved --- keywords. In normal conditions, X and Y aren't keywords and can be --- used as variables. - --{ block: - require 'lexer' - local my_lexer = lexer.lexer:clone() -- no keywords - my_lexer:add{"X", "Y"} - mlp.lexer:add "V" - - function num(lx) - local a = lx:next() - assert(a.tag=='Number') - return a - end - - my_parser = gg.list{ - gg.multisequence{ - { "X", num, builder = |x| `Table{ x[1], +{0} } }, - { "Y", num, builder = |y| `Table{ +{0}, y[1] } }, - default = gg.sequence{ mlp.id, builder = |x| `Pair{ `String{x[1][1]},`True } } }, - separators = { ',', ';' }, - builder = function(l) l.tag='Table'; return l end } - - mlp.expr:add{ "V", gg.with_lexer(my_lexer, my_parser), builder = unpack } } - --- Use the special lexer: -foo = V X 1, Y 2, X 3, - for, foo, in, tag, function -- check that these aren't keywords in my_lexer - --- Use X and Y as Id, in the unpolluted lexer: -print "Vector:" -X = table.tostring(foo, 60) -print (X) - -print "Sum:" -- Ready for a functional one-liner? :) -Y = |v| table.ifold (|a,b| table.imap (|c,d| c+d, a, b), {0,0}, v) -table.print (Y(foo)) -
diff --git a/samples/match_test.mlua b/samples/match_test.mlua deleted file mode 100644 index 4eff225..0000000 --- a/samples/match_test.mlua +++ /dev/null
@@ -1,105 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{extension 'match'} - -WIDTH = 50 -function p(msg) - io.write(msg, ' ':rep(WIDTH-#msg)) - io.flush() -end - -p "Basic match" -match 1 with 1 -> print 'ok' end - -p "Sequence match" -match 3, 4 with -| 1, 2 -> print 'KO' -| 3, 4 -> print 'ok' -end - -p "Id binding" -match 3, 4 with -| 1, 2 -> print 'KO' -| x, y -> print 'ok' -end - -p "Table destructuring & non-litteral tested term" -match {1, 2} with -|{a, 2} -> assert(a==1); print 'ok' -end - -p "Pattern group" -match {'?'} with -|1|2|3 -> print 'KO' -|{...} -> print 'ok' -end - -p "Multi-level destructuring" -match {{1000}} with -|{{2000}} -> print 'KO' -|{{3000}} -> print 'KO' -|{{1000}} -> print 'ok' -end - -p "Guard" -match 1 with -| 1 if false -> print 'KO' -| 1 -> print 'ok' -end - -p "Guard with bound var" -match 1 with -| a if a ~= 1 -> print 'KO' -| a if a == 1 -> print 'ok' -end - -p "Non linear var & destructuring" -match {1, {2}} with -| {a, {a}} -> print 'KO' -| {a, {b}} -> print 'ok' -end - -p "Non-linear vars on a sequence" -match 1, 2 with -| a, a -> print 'KO' -| a, b -> print 'ok' -end - -p "Multiple _ wildcards" -match 1, 2 with -| _, _ -> print 'ok' -| a, b -> print 'KO' -end - -p "Regexp & non-linear vars" -match 'toto' with -| 't(.)t(.)' / { a, a } -> print (a..'k') -end - -p "Nested match & ..." -match { { 'o', 'k', '!' } } with -| { t } -> match t with - | { a, b } -> print 'KO' - | { a, b, ... } -> print (a..b) - | _ -> print 'KO' - end -| _ -> print 'KO' -end -
diff --git a/samples/synth.mlua b/samples/synth.mlua deleted file mode 100644 index 880fc3a..0000000 --- a/samples/synth.mlua +++ /dev/null
@@ -1,579 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'strict' - --{ extension 'match' } - -synth = { } -synth.__index = synth - --------------------------------------------------------------------------------- --- Instanciate a new AST->source synthetizer --------------------------------------------------------------------------------- -function synth.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, synth) -end - --------------------------------------------------------------------------------- --- Run a synthetizer on the `ast' arg and return the source as a string. --- Can also be used as a static method `synth.run (ast)'; in this case, --- a temporary synthetizer is instanciated on the fly. --------------------------------------------------------------------------------- -function synth:run (ast) - if not ast then - self, ast = synth.new(), self - end - self._acc = { } - self:node (ast) - return table.concat (self._acc) -end - --------------------------------------------------------------------------------- --- Accumulate a piece of source file in the synthetizer. --------------------------------------------------------------------------------- -function synth: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 synth: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 synth:nlindent () - self.current_indent = self.current_indent + 1 - self:nl () -end - --------------------------------------------------------------------------------- --- Decrease indentation and accumulate a new line. --------------------------------------------------------------------------------- -function synth: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 synth:node (node) - assert (self~=synth and self._acc) - if not node.tag then -- tagless block. - self:list (node, self.nl) - else - local f = synth[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 synth: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 synth:Do (node) - self:acc "do" - self:nlindent () - self:list (node, self.nl) - self:nldedent () - self:acc "end" -end - -function synth: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 synth: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 synth:Repeat (node, body, cond) - self:acc "repeat" - self:nlindent () - self:list (body, self.nl) - self:nldedent () - self:acc "until " - self:node (cond) -end - -function synth: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 synth: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 synth: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 synth:Local (node, lhs, rhs) - self:acc "local " - self:list (lhs, ", ") - if rhs[1] then - self:acc " = " - self:list (rhs, ", ") - end -end - -function synth: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 synth: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 synth: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 synth:Return (node) - self:acc "return " - self:list (node, ", ") -end - -synth.Break = "break" -synth.Nil = "nil" -synth.False = "false" -synth.True = "true" -synth.Dots = "..." - -function synth:Number (node, n) - self:acc (tostring (n)) -end - -function synth: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 synth:Function (node, params, body) - self:acc "function " - self:acc " (" - self:list (params, ", ") - self:acc ")" - self:nlindent () - self:list (body, self.nl) - self:nldedent () - self:acc "end" -end - -function synth:Table (node) - if not node[1] then self:acc "{ }" else - self:acc "{" - self:nlindent () - 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 - self:nldedent () - self:acc "}" - end -end - -function synth: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 synth:Paren (node, content) - self:acc "(" - self:node (content) - self:acc ")" -end - -function synth: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 synth: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 - - --------------------------------------------------------------------------------- --- Read a file, get its AST, use synth to regenerate sources --- from that AST --------------------------------------------------------------------------------- -require 'metalua.compiler' -local filename = (arg[2] or arg[1]) or arg[0] -local ast = mlc.luafile_to_ast (filename) - -print(synth.run(ast))
diff --git a/samples/trycatch_test.mlua b/samples/trycatch_test.mlua deleted file mode 100644 index 80e4124..0000000 --- a/samples/trycatch_test.mlua +++ /dev/null
@@ -1,126 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'trycatch' } - - ----------------------------------------------------------------------- -print "1) no error" -try - print(" Hi") -end - - ----------------------------------------------------------------------- -print "2) caught error" -try - error "some_error" -catch x then - printf(" Successfully caught %q", x) -end - - --- [[ ----------------------------------------------------------------------- -print "3) no error, with a finally" -try - print " Hi" -finally - print " Finally OK" -end - - ----------------------------------------------------------------------- -print "4) error, with a finally" -try - print " Hi" - error "bang" -catch "bang"/{_} then - print " Bang caught" -finally - print " Finally OK" -end - - ----------------------------------------------------------------------- -print "5) nested catchers" -try - try - error "some_error" - catch "some_other_error" then - assert (false, "mismatch, this must not happen") - end - catch "some_error"/{x} then - printf(" Successfully caught %q across a try that didn't catch", x) -catch x then - assert (false, "We shouldn't reach this catch-all") -end - - ----------------------------------------------------------------------- -print "6) nested catchers, with a 'finally in the inner one" -try - try - error "some_error" - catch "some_other_error" then - assert (false, "mismatch, this must not happen") - finally - print " Leaving the inner try-catch" - end -catch "some_error"/{x} then - printf(" Successfully caught %q across a try that didn't catch", x) -catch x then - assert (false, "We shouldn't reach this catch-all") -end - - ----------------------------------------------------------------------- -print "7) 'finally' intercepts a return from a function" -function f() - try - print " into f:" - return "F_RESULT" - assert (false, "I'll never go there") - catch _ then - assert (false, "No exception should be thrown") - finally - print " I do the finally before leaving f()" - end -end -local fr = f() -printf(" f returned %q", fr) - - ----------------------------------------------------------------------- -print "8) don't be fooled by nested functions" -function f() - try - local function g() return "from g" end - printf(" g() returns %q", g()) - return "from f" - catch _ then - assert (false, "No exception should be thrown") - end -end -local fr = f() -printf(" f returned %q", fr) - ----------------------------------------------------------------------- -print "*) done." -
diff --git a/samples/types_test.mlua b/samples/types_test.mlua deleted file mode 100644 index aeb3945..0000000 --- a/samples/types_test.mlua +++ /dev/null
@@ -1,38 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension "types" } --{ extension "clist" } - --- Uncomment this to turn typechecking code generation off: --- -{stat: types.enabled=false} - -function sum (x :: table(number)) :: number - local acc :: number = 0 - for i=1, #x do - acc = acc + x[i] -- .. 'x' -- converts to string - end - --acc='bug' -- put a string in a number variable - return acc -end - -x = { i for i=1,100 } ---x[23] = 'toto' -- string in a number list, sum() will complain -y = sum (x) -printf ("sum 1 .. %i = %i", #x, y) \ No newline at end of file
diff --git a/samples/walk_id_test.mlua b/samples/walk_id_test.mlua deleted file mode 100644 index 5dfff6c..0000000 --- a/samples/walk_id_test.mlua +++ /dev/null
@@ -1,45 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'match' } - -require 'metalua.walk.id' - -ast = +{ block: - y = type(1) - function foo(x) - local type = 'number' - assert(x==type or not x) - end - foo(x) } - -disp = |msg,ast| printf("\n%s:\n%s", msg, table.tostring(ast, 80, 'nohash')) -disp('initial term', ast) - -do -- Make globals explicit: - local ast = table.deep_copy(ast) - local cfg = { id = { } } - function cfg.id.free(i) - i <- `Index{ `Id '_G', `String{i[1]} } - return 'break' - end - walk_id.block(cfg, ast) - disp('Globals made explicit', ast) -end -
diff --git a/samples/weaver.mlua b/samples/weaver.mlua deleted file mode 100644 index 3a7507f..0000000 --- a/samples/weaver.mlua +++ /dev/null
@@ -1,139 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -require 'metalua.mlc' -require 'metalua.walk' - -function weave_ast (src, ast, name) - - ------------------------------------------------------------------- - -- translation: associate an AST node to its recomposed source - -- ast_children: associate an AST node to the list of its children - -- ast_parent: associate an AST node to the list of its parent - -- weavable: whether an AST node supports weaving of its children - -- node: common walker config for exprs, stats & blocks - ------------------------------------------------------------------- - local translation, ast_children, ast_parent, weaveable, node = - { }, { }, { }, { }, { } - - ------------------------------------------------------------------- - -- Build up the parent/children relationships. This is not the same - -- as inclusion between tables: the relation we're building only - -- relates blocks, expressions and statements; in the AST, some - -- tables don't represent any of these node kinds. - -- For instance in `Local{ { `Id "x" }, { } }, `Id"x" is a child of - -- the `Local{ } node, although it's not directly included in it. - ------------------------------------------------------------------- - function node.down(ast, parent) - ---------------------------------------------------- - -- `Do{ } blocks are processed twice: - -- * once as a statement - -- * once as a block, child of itself - -- This prevents them from becoming their own child. - ---------------------------------------------------- - if ast==parent then return end - - if not ast.lineinfo then - weaveable [ast] = false, false - if parent then weaveable [parent] = false end - else - weaveable [ast] = true - - -- normalize lineinfo - -- TODO: FIXME - if ast.lineinfo.first[3] > ast.lineinfo.last[3] then - ast.lineinfo.first, ast.lineinfo.last = ast.lineinfo.last, ast.lineinfo.first - end - end - ast_children [ast] = { } - ast_parent [ast] = parent - if parent then table.insert (ast_children [parent], ast) end - end - - ------------------------------------------------------------------- - -- Visit up, from leaves to upper-level nodes, and weave leaves - -- back into the text of their parent node, recursively. Since the - -- visitor is imperative, we can't easily make it return a value - -- (the resulting recomposed source, here). Therefore we - -- imperatively store results in the association table - -- `translation'. - ------------------------------------------------------------------- - function node.up(ast) - local _acc = { } - local function acc(x) table.insert (_acc, x) end - - if not next(ast) then -- shadow node, remove from ast_children - local x = ast_children[ast_parent[ast]] - for i,a in ipairs (x) do if a==ast then table.remove (x, i); break end end - return "" -- no need to continue, we know that the node is empty! - end - - -- ast Can't be weaved normally, try something else -- - local function synthetize (ast) - acc "-{expr: " - acc (table.tostring (ast, 'nohash', 80, 8)) - acc " }" - end - - -- regular weaving of chidren in the parent's sources -- - local function weave (ast) - -- sort children in appearence order - local comp = |a,b| a.lineinfo.first[3] < b.lineinfo.first[3] - table.sort (ast_children [ast], comp) - - local li = ast.lineinfo - if not li then return synthetize (ast) end - local a, d = li.first[3], li.last[3] - for child in ivalues (ast_children [ast]) do - local li = child.lineinfo - local b, c = li.first[3], li.last[3] - acc (src:sub (a, b - 1)) - acc (translation [child]) - a = c + 1 - end - acc (src:sub (a, d)) - end - - -- compute the translation from the children's ones -- - if not translation [ast] then - if weaveable [ast] then weave (ast) else synthetize (ast) end - translation [ast] = table.concat (_acc) - end - end - - local cfg = { expr=node; stat=node; block=node } - walk.block (cfg, ast) - - return translation [ast] -end - --- Get the source. If none is given, use itself as an example. -- -local filename = arg[2] or arg[1] or arg[0] -local f = assert (io.open (filename, 'r')) -local src = f:read '*a' -f:close() - -local ast = mlc.luastring_to_ast (src, name) -if not next(ast) then - io.write (src) -- Empty ast, probably empty file, or comments only -else - local before = src:sub (1, ast.lineinfo.first[3]-1) - local after = src:sub (ast.lineinfo.last[3]+1, -1) - io.write (before .. weave_ast (src, ast) .. after) -end
diff --git a/samples/withdo_test.mlua b/samples/withdo_test.mlua deleted file mode 100644 index 9feb487..0000000 --- a/samples/withdo_test.mlua +++ /dev/null
@@ -1,32 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'withdo' } - -local original_close = io.close - -function x() - with f1, f2 = io.open 'withdo_test.mlua', io.open 'trycatch_test.mlua' do - local t1 = f1:read '*a' - local t2 = f2:read '*a' - return #t1, #t2 - end -end - -print(x()) \ No newline at end of file
diff --git a/samples/xglobals_test.mlua b/samples/xglobals_test.mlua deleted file mode 100644 index 7a46488..0000000 --- a/samples/xglobals_test.mlua +++ /dev/null
@@ -1,59 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'xglobal' } - ----------------------------------------------------------------------- -print "1) declare unassigned globals" -global a, b - ----------------------------------------------------------------------- -print "2) declare-and-assign global" -global c = 3 - ----------------------------------------------------------------------- -print "3) assign to pre-declared globals" -a, b = 1, 2 - ----------------------------------------------------------------------- -print "4) fail when setting an undeclared global" -local st1, msg1 = pcall(function() - a = 4 - d = 5 -- failure, assignment to undeclared global -end) -assert(not st1) -printf (" -> This error was expected: %s", msg1) - ----------------------------------------------------------------------- -print "5) fail when reading an undeclared global" -local st2, msg2 = pcall(function() - b = c -- OK - local _ = d -- failure, try to read undeclared global -end) -assert(not st2) -printf (" -> This error was expected: %s", msg2) - ----------------------------------------------------------------------- -print "6) check the globals' values" -assert(a==4) -assert(b==3) -assert(c==3) - ----------------------------------------------------------------------- -print "*) done."
diff --git a/samples/xloop_test.mlua b/samples/xloop_test.mlua deleted file mode 100644 index e87993a..0000000 --- a/samples/xloop_test.mlua +++ /dev/null
@@ -1,23 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'xloop' } -for i=1,9 for j=10,90,10 if i~=3 while i<8 do - io.write(i+j, ' ') -end \ No newline at end of file
diff --git a/samples/xmatch_test.mlua b/samples/xmatch_test.mlua deleted file mode 100644 index f651db4..0000000 --- a/samples/xmatch_test.mlua +++ /dev/null
@@ -1,73 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --{ extension 'xmatch' } - -WIDTH=60 -function p(msg) io.write(msg..' ':rep(WIDTH-#msg)) end - ----------------------------------------------------------------------- -p "match as an expression" -print(match 1 with 1 -> 'ok' | 2 -> 'KO') - ----------------------------------------------------------------------- -p "global match function" -match function g -| x if x<10 -> return 'o' -| _ -> return 'k' -end -print(g(1)..g(11)) - ----------------------------------------------------------------------- -p "global match function, multi-args" -match function cmp -| x, y if x<y -> return 'increasing' -| _, _ -> return 'decreasing' - end - -if cmp(1,2)=='increasing' and cmp(2,1)=='decreasing' then - print "ok" else print "KO" -end - ----------------------------------------------------------------------- -p "local match function" -do - local match function x - | 1 -> print 'ok' - end - x(1) -end -assert(not x) - ----------------------------------------------------------------------- -p "global bind assignment" -bind {a, b} = {'o', 'k'} -print(a..b) - ----------------------------------------------------------------------- -p "local bind assignment" -c, d = 'k', 'o' -do - local bind {c, {d}} = {'o', {'k'}} - print(c..d) -end - ----------------------------------------------------------------------- -p "local bind assignment scope" -print(d..c)
diff --git a/lib/strict.lua b/strict.lua similarity index 85% rename from lib/strict.lua rename to strict.lua index e35e22d..19213a8 100644 --- a/lib/strict.lua +++ b/strict.lua
@@ -12,6 +12,14 @@ -- ------------------------------------------------------------------------------- +-- +-- strict.lua +-- checks uses of undeclared global variables +-- All global variables must be 'declared' through a regular assignment +-- (even assigning nil will do) in a main chunk before being used +-- anywhere or assigned to inside a function. +-- + local getinfo, error, rawset, rawget = debug.getinfo, error, rawset, rawget local mt = getmetatable(_G)
diff --git a/tests/lexer.lua b/tests/lexer.lua deleted file mode 100644 index 6eb430a..0000000 --- a/tests/lexer.lua +++ /dev/null
@@ -1,149 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 David Manura and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- David Manura - API and implementation --- -------------------------------------------------------------------------------- - --- tests of lexer (preliminary) --- --- D.Manura. Copyright (c) 2011, Fabien Fleutot <metalua@gmail.com>. --- --- This software is released under the MIT Licence, see Metalua licence.txt --- for details. - - -package.path = 'src/compiler/?.lua;src/lib/?.lua' - -require 'mlp_lexer' -local LX = mlp.lexer - --- equality check. -local function checkeq(a, b) - if a ~= b then - error('not equal:\n' .. tostring(a) .. '\n' .. tostring(b), 2) - end -end - -local function checkmatch(a, b) - if not a:match(b) then - error('not match:\n' .. tostring(a) .. '\n' .. tostring(b), 2) - end -end - --- reads file to string (with limited error handling) -local function readfile(filename) - local fh = assert(io.open(filename, 'rb')) - local text = fh:read'*a' - fh:close() - return text -end - --- formats token succinctly. -local function tokfmt(tok) - local function fmt(o) - return (type(o) == 'string') and ("%q"):format(o):sub(2,-2) or tostring(o) - end - return [[`]] .. tok.tag .. tostring(tok.lineinfo):gsub('%|L[^%|]*%|C[^%|]*', '') .. '{' .. fmt(tok[1]) .. '}' -end - --- utility function to lex code -local function lex(code) - local sm = LX:newstream(code) - local toks = {} - while 1 do - local tok = sm:next() - toks[#toks+1] = tokfmt(tok) - if tok.tag == 'Eof' then - break - end - end - return table.concat(toks) -end -local function tlex(code) - local sm = LX:newstream(code) - local toks = {} - while 1 do - local tok = sm:next() - toks[#toks+1] = tok - if tok.tag == 'Eof' then break end - end -return toks -end -local function plex(code) - return pcall(lex, code) -end - ---FIX checkeq(nil, plex '====') - --- trivial tests -checkeq(lex[[]], [[`Eof<?|K1>{eof}]]) -checkeq(lex'\t', [[`Eof<?|K2>{eof}]]) -checkeq(lex'\n', [[`Eof<?|K2>{eof}]]) -checkeq(lex'--', [[`Eof<C|?|K3>{eof}]]) -checkeq(lex'\n -- \n--\n ', [[`Eof<C|?|K11>{eof}]]) -checkeq(lex[[return]], [[`Keyword<?|K1-6>{return}`Eof<?|K7>{eof}]]) - --- string tests -checkeq(lex[["\092b"]], [[`String<?|K1-7>{\\b}`Eof<?|K8>{eof}]]) -- was bug -checkeq(lex[["\x5Cb"]], [[`String<?|K1-7>{\\b}`Eof<?|K8>{eof}]]) -- [5.2] -checkeq(lex[["\0\t\090\100\\\1004"]], [[`String<?|K1-21>{\000 Zd\\d4}`Eof<?|K22>{eof}]]) -- decimal/escape - --- number tests, hex (including Lua 5.2) -local t = tlex[[0xa 0xB 0xfF -0xFf 0x1.8 0x1.8P1 0x1.8p+01 0x.8p-1]] -checkeq(t[1][1], 0xa) -checkeq(t[2][1], 0xB) -checkeq(t[3][1], 0xfF) -checkeq(t[4][1], '-') -checkeq(t[5][1], 0xFf) --- 5.2 hex floats -checkeq(t[6][1], 1.5) -- 0x1.8 -checkeq(t[7][1], 3) -- 0x1.8P1 -checkeq(t[8][1], 3) -- 0x1.8p+01 -checkeq(t[9][1], 0.25) -- 0x0.8p-1 -checkeq(t[10][1], 'eof') - - --- Lua 5.2 -checkeq(lex'"a\\z \n ."', [[`String<?|K1-9>{a.}`Eof<?|K10>{eof}]]) -- \z -checkeq(lex'"\\z"', [[`String<?|K1-4>{}`Eof<?|K5>{eof}]]) -- \z -checkeq(lex[["\x00\\\xfF\\xAB"]], [[`String<?|K1-17>{\000\\]]..'\255'..[[\\xAB}`Eof<?|K18>{eof}]]) - --- Lua 5.2 goto and :: -checkeq(lex'goto a1 ::a1 ::', [[`Keyword<?|K1-4>{goto}`Id<?|K6-7>{a1}]].. - [[`Keyword<?|K9-10>{::}`Id<?|K11-12>{a1}`Keyword<?|K14-15>{::}`Eof<?|K16>{eof}]]) - - -assert(lex(readfile(arg[0]))) -- lex self - --- checks of unescape_string -local p = function(s) local ok, o = pcall(tlex, s); return ok and o[1][1] or o end -checkmatch(p[['\']], 'Unterminated') -checkeq(p[['\\a\\\t']], [[\a\]]..'\t') -checkeq(p[['\\116']], [[\116]]) -checkeq(p[['\\\116']], [[\t]]) -checkeq(p[['\092\116']], [[\t]]) -- was bug -checkeq(p[['\x5c\x74']], [[\t]]) -- 5.2 hex --- Lua 5.2 -checkeq(p[['\z\z \ -a\z ']], [[ - -a]]) -checkeq(p[['\\z']], [[\z]]) -- was bug -checkmatch(p[['\xaz']], 'Unknown escape') -checkmatch(p[['\999']], 'must be in.*255') -checkeq(p[['\z\32']], ' ') -- was bug - -print 'DONE'
diff --git a/tests/locals-and-stats.mlua b/tests/locals-and-stats.mlua deleted file mode 100644 index 6e24fa5..0000000 --- a/tests/locals-and-stats.mlua +++ /dev/null
@@ -1,34 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - -local foo - -x = -{ `Stat{ { `Local{ { `Id "B" }, - { `Stat{ { `Local{ { `Id "A" }, - { `Number 4 } }, - `Set{ { `Id "y" }, - { `Id "A" } } }, - `Id "A" } } }, - `Set{ { `Id "x" }, - { `Id "B" } } }, - `Id "B" } } - -assert(x==4) -print "Test passed." -
diff --git a/tests/reweave.mlua b/tests/reweave.mlua deleted file mode 100644 index 3a891a4..0000000 --- a/tests/reweave.mlua +++ /dev/null
@@ -1,43 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- --{ extension 'xloop' } - -ls = io.popen ( (os.getenv("OS") or "") :match "^Windows" and "dir /b reweave" or "ls reweave") -this_script = arg[1] - -local errors = {} - -for filename in ls :lines() if filename :strmatch "%.m?lua$" do - printf ("--- weaver check %s ---", filename) - local ret = os.execute ("metalua ../samples/weaver.mlua reweave/"..filename.." | diff -q reweave/"..filename.." -") - if ret ~= 0 then - print("================================================================================") - print("Reweaved source does not match original:") - print("================================================================================") - os.execute ("metalua ../samples/weaver.mlua reweave/"..filename.." | diff reweave/"..filename.." -") - errors[#errors + 1] = "Reweaving of "..filename.." failed, returned "..ret - end -end - -ls :close() - -if #errors > 0 then - print("================================================================================") - error("REWEAVING ERRORS DETECTED:\n * " .. table.concat(errors, "\n * ")) -end
diff --git a/tests/reweave/comment.lua b/tests/reweave/comment.lua deleted file mode 100644 index 587d620..0000000 --- a/tests/reweave/comment.lua +++ /dev/null
@@ -1,3 +0,0 @@ ---[[ -comment ---]]
diff --git a/tests/reweave/comment2.lua b/tests/reweave/comment2.lua deleted file mode 100644 index 6222fb6..0000000 --- a/tests/reweave/comment2.lua +++ /dev/null
@@ -1,2 +0,0 @@ ---[[comment]] -local code = 5
diff --git a/tests/reweave/comment_dup.lua b/tests/reweave/comment_dup.lua deleted file mode 100644 index 17afdfd..0000000 --- a/tests/reweave/comment_dup.lua +++ /dev/null
@@ -1,8 +0,0 @@ -if true then - -- comment -end - -if true then - -- comment - print("something else after") -end
diff --git a/tests/reweave/comments.lua b/tests/reweave/comments.lua deleted file mode 100644 index 05e8762..0000000 --- a/tests/reweave/comments.lua +++ /dev/null
@@ -1,8 +0,0 @@ -# it eats ---[[ all ]] ---[===[ my ]===] -comments() -- foo ---[[ bar -baz ]] qqq() --- even -one() -- liners
diff --git a/tests/reweave/dup.lua b/tests/reweave/dup.lua deleted file mode 100644 index e6428ab..0000000 --- a/tests/reweave/dup.lua +++ /dev/null
@@ -1,2 +0,0 @@ -f(a > b) -f(c >= d)
diff --git a/tests/reweave/empty.lua b/tests/reweave/empty.lua deleted file mode 100644 index e69de29..0000000 --- a/tests/reweave/empty.lua +++ /dev/null
diff --git a/tests/reweave/extra_whitespace.lua b/tests/reweave/extra_whitespace.lua deleted file mode 100644 index 7358189..0000000 --- a/tests/reweave/extra_whitespace.lua +++ /dev/null
@@ -1,16 +0,0 @@ -t = {} - -t = { } - -t {} - -t { } - -assert(count(function () end) == 1) - -for k,v,w in a do end - -repeat until 1; repeat until true; -while false do end; while nil do end; - -foo(1) { };
diff --git a/tests/reweave/function-index-decl.lua b/tests/reweave/function-index-decl.lua deleted file mode 100644 index a7c77ac..0000000 --- a/tests/reweave/function-index-decl.lua +++ /dev/null
@@ -1,2 +0,0 @@ -function a.b.c.f1 (x) return x+1 end -function a.b.c:f2 (x,y) self[x] = y end
diff --git a/tests/reweave/if.lua b/tests/reweave/if.lua deleted file mode 100644 index 32ddd19..0000000 --- a/tests/reweave/if.lua +++ /dev/null
@@ -1 +0,0 @@ -f(5 > 7)
diff --git a/tests/reweave/index_index.lua b/tests/reweave/index_index.lua deleted file mode 100644 index 6aa0028..0000000 --- a/tests/reweave/index_index.lua +++ /dev/null
@@ -1 +0,0 @@ -function a.b.c (x) end
diff --git a/tests/reweave/schema.lua b/tests/reweave/schema.lua deleted file mode 100644 index c5275ba..0000000 --- a/tests/reweave/schema.lua +++ /dev/null
@@ -1,1286 +0,0 @@ -local print, verb, dbg, errr, print_table, printt = make_module_loggers("schema", "SCM") - -local CT, GMF, - game_const - = import 'game/const.lua' - { - 'chipTypes', - 'gameModeFlags' - } - -local MTF, - cast_type - = import (game_const.abilities) - { - 'manualTargetFlags', - 'castType' - } - -local AP, abiprob_mapping = import (game_const.abilities.property) - { - 'mappingInv', -- Note order (inverted goes first) - 'mapping' - } - -local PO, CM, CST, SO, - abie_const - = import 'abie/const.lua' - { - 'propObjects', - 'customMessages', - 'clientStat', - 'storeObjects' - } - -local non_empty_list, - no_check, - not_implemented, - get_children, - get_children_concat_newline, - get_children_concat_str, - get_children_concat_table, - get_value, - get_value_quoted, - get_value_tonumber, - check_mapping_tonumber, - get_value_mapped_tonumber_quoted, - node_children_placeholders_filler, - check_tonumber - = import 'jsle/schema/util.lua' - { - 'non_empty_list', - 'no_check', - 'not_implemented', - 'get_children', - 'get_children_concat_newline', - 'get_children_concat_str', - 'get_children_concat_table', - 'get_value', - 'get_value_quoted', - 'get_value_tonumber', - 'check_mapping_tonumber', - 'get_value_mapped_tonumber_quoted', - 'node_children_placeholders_filler', - 'check_tonumber' - } - -local declare_common = import 'jsle/schema/common.lua' { 'declare_common' } - --- Optional TODOs: - --- TODO: Must be able to fetch back data from lang file to this schema. --- TODO: Write effect validation with human readable answers. Make it available via jobman's job. --- TODO: Write auto-conversion function for old abilities (v.1.01->current) --- TODO: Embed limitations on number of simultanious identical active OT effects --- TODO: Write checkers for numeric fields --- TODO: Adapt game/ctrl.lua to abie - -local define_schema = function(jsle) - assert_is_table(jsle) - --- WARNING: Return nil on error from handlers, do not return false -- it is a legitimate value. --- WARNING: Reordering of schema elements would result in INCOMPATIBLE format change! - - local propwrite_values = - { - { ["health"] = [[жизнь]] }; - { ["health_max"] = [[здоровье]] }; - { ["mana1"] = [[красную ману]] }; - { ["mana2"] = [[зелёную ману]] }; - { ["mana3"] = [[синюю ману]] }; - -- Note mana4 is reserved for health - { ["mana5"] = [[ману 5]] }; - { ["mana6"] = [[ману 6]] }; - { ["mana7"] = [[ману 7]] }; - { ["mana8"] = [[ману 8]] }; - { ["armor"] = [[броню]] }; - { ["fury"] = [[ярость]] }; - { ["block"] = [[блок]] }; - { ["fortune"] = [[удачу]] }; - { ["stun"] = [[оглушение]] }; - { ["armour_piercing"] = [[бронебойность]] }; - { ["agility"] = [[ловкость]] }; - { ["counterattack"] = [[контрудар]] }; - { ["damage"] = [[базовый урон]] }; - { ["damage_min"] = [[минимальный урон]] }; - { ["damage_max"] = [[максимальный урон]] }; - { ["damage_mult"] = [[множитель урона]] }; - { ["vampiric"] = [[вампиризм]] }; - { ["stun_count"] = [[оглушённость]] }; - } - - local propread_values = tiappend( - tclone(propwrite_values), - { - { ["race_id"] = [[расу]] }, - { ["level"] = [[уровень]] }, - { ["grade"] = [[степень]] }, -- TODO: clan_rank?! - { ["rank"] = [[ранг]] }, - { ["glory"] = [[доблесть]] }, - { ["scalps"] = [[скальпы]] }, - { ["kills"] = [[убийства]] }, - } - ) - - -- TODO: Be more specific. Should be at least "abie-1.03". - jsle:version("1.03") -- WARNING: Do an ordering cleanup when this changes - - jsle:record "ROOT" - { - children = - { - [1] = "TARGET_LIST"; - [2] = "IMMEDIATE_EFFECT_LIST"; - [3] = "OVERTIME_EFFECT"; - [4] = { "BOOLEAN", default = 0 }; -- Warning! Do not use BOOLOP_VARIANT, nothing of it would work at this point. - [5] = { "CUSTOM_OVERTIME_EFFECTS", default = empty_table }; - }; - html = [[<h2>Цели</h2>%C(1)%<h2>Мгновенные эффекты</h2><b>Игнорировать активацию в статистике:</b>%C(4)%<br><br><b>Действия:</b>%C(2)%<h2>Овертайм-эффекты</h2>%C(3)%<hr>%C(5)%]]; - checker = no_check; - handler = function(self, node) - return self:effect_from_string( - node.value[1], -- Target list - node.value[4], -- Ignore usage stats flag - self:fill_placeholders( - node.value, -[[ -function(self) - self:set_custom_ot_effects($(5)) - - do - $(2) - end - - do - $(3) - end -end -]] - ) - ) - end; - } - - jsle:list "TARGET_LIST" - { - type = "TARGET_VALUE"; - html = [[%LIST(", ")%]]; - checker = non_empty_list; - handler = function(self, node) - local result = 0 - for i, v in ipairs(node.value) do - result = result + v - end - return result - end; - } - - jsle:enum "TARGET_VALUE" - { - values = - { - { [MTF.AUTO_ONLY] = [[неинтерактивно]] }; - { [MTF.SELF_HUMAN] = [[на себя]] }; - { [MTF.SELF_TEAM_HUMAN] = [[на человека в своей команде]] }; - { [MTF.OPP_HUMAN] = [[на противника]] }; - { [MTF.OPP_TEAM_HUMAN] = [[на человека в команде противника]] }; - { [MTF.FIELD_CHIP] = [[на фишку]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_tonumber; - numeric_keys = true; - } - - jsle:list "IMMEDIATE_EFFECT_LIST" - { - type = "ACTION_VARIANT"; - html = [[%LE("<i>Нет</i>")%%LNE("<ol><li>")%%LIST("<li>")%%LNE("</ol>")%]]; - checker = no_check; - handler = get_children_concat_newline; - } - - jsle:record "OVERTIME_EFFECT" - { - children = - { - [1] = "OT_EFFECT_TARGET"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - [4] = "BOOLOP_VARIANT"; - [5] = "OVERTIME_EFFECT_LIST"; - [6] = "OVERTIME_EFFECT_LIST"; - [7] = "OVERTIME_EFFECT_LIST"; - [8] = "OT_MODIFIER_LIST"; - [9] = "NUMOP_VARIANT"; -- TODO: Must be higher in the list. Straighten numbers on next version change (do not forget to fix texts) - [10] = "NUMOP_VARIANT"; -- TODO: Must be higher in the list. Straighten numbers on next version change (do not forget to fix texts) - [11] = { "GAME_MODES", default = GMF.ALL }; -- TODO: Must be higher in the list. Straighten numbers on next version change (do not forget to fix texts) - [12] = { "BOOLEAN", default = 0 }; - }; - html = [[<br><b>Цель:</b> %C(1)%<br><b>Время жизни:</b> %C(2)% <i>(≥255 — бессрочно)</i><br><b>Период:</b> %C(3)%<br><b>Изначальный кулдаун:</b> %C(10)%<br><b>Сброс в конце боя:</b> %C(4)%<br><b>Остается при снятии всех эффектов вручную:</b> %C(12)%<br><b>Максимальное число одновременно активных эффектов:</b> %C(9)% <i>(0 — не ограничено)</i><br><b>Игровые режимы:</b> %C(11)%<h3>При изменении набора характеристик</h3>%C(5)%<h3>В конце хода цели</h3>%C(7)%<h3>Временные модификаторы <i>(кроме жизни)</i></h3>%C(8)%]]; - checker = no_check; - handler = function(self, node) - if - node.value[5] ~= "" or - node.value[6] ~= "" or - node.value[7] ~= "" or - node.value[8] ~= "{}" - then - -- Spawning OT effect only if have any actions in it. - return node_children_placeholders_filler - [[ - self:spawn_overtime_effect( - $(1), - $(2), - $(3), - $(10), - $(4), - $(9), - function(self) - $(5) - end, - function(self) - $(6) - end, - function(self) - $(7) - end, - $(8), - $(11), - $(12) - ) - ]] (self, node) - else - return [[-- No OT effects]] - end - end; - } - - jsle:list "OT_MODIFIER_LIST" - { - type = "OT_MODIFIER_VARIANT"; - html = [[%LE("<i>Нет</i>")%%LNE("<ol><li>")%%LIST("<li>")%%LNE("</ol>")%]]; - checker = no_check; - handler = get_children_concat_table; - } - - jsle:variant "OT_MODIFIER_VARIANT" - { - values = - { - { ["MOD_SET"] = [[Установить]] }; - { ["MOD_INC"] = [[Увеличить]] }; - { ["MOD_DEC"] = [[Уменьшить]] }; - { ["MOD_MULT"] = [[Умножить]] }; - }; - label = [["<i title=\"Модификатор\">M</i>"]]; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value; - } - - jsle:record "MOD_SET" - { - children = - { - [1] = "PROPWRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Установить %C(1)% цели в %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[{ name = $(1), fn = function(self, value) return ($(2)) end; }]]; - } - - jsle:record "MOD_INC" - { - children = - { - [1] = "PROPWRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Увеличить %C(1)% цели на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[{ name = $(1), fn = function(self, value) return value + ($(2)) end; }]]; - } - - jsle:record "MOD_DEC" - { - children = - { - [1] = "PROPWRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Уменьшить %C(1)% цели на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[{ name = $(1), fn = function(self, value) return value - ($(2)) end; }]]; - } - - jsle:record "MOD_MULT" - { - children = - { - [1] = "PROPWRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Умножить %C(1)% цели на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[{ name = $(1), fn = function(self, value) return value * ($(2)) end; }]]; - } - - jsle:list "OVERTIME_EFFECT_LIST" - { - type = "ACTION_VARIANT"; - html = [[%LE("<i>Нет</i>")%%LNE("<ol><li>")%%LIST("<li>")%%LNE("</ol>")%]]; - checker = no_check; - handler = get_children_concat_newline; - } - - jsle:list "ACTION_LIST" - { - type = "ACTION_VARIANT"; - html = [[<ol><li>%LIST("<li>")%</ol>]]; - checker = non_empty_list; - handler = get_children_concat_newline; - } - - jsle:variant "ACTION_VARIANT" - { - values = - { - { ["ACT_SET"] = [[Установить]] }; - { ["ACT_INC"] = [[Увеличить]] }; - { ["ACT_DEC"] = [[Уменьшить]] }; - { ["ACT_MULT"] = [[Умножить]] }; - { ["ACT_DIRECTSET"] = [[Установить напрямую]] }; - { ["ACT_DIRECTINC"] = [[Увеличить напрямую]] }; - { ["ACT_DIRECTDEC"] = [[Уменьшить напрямую]] }; - { ["ACT_DIRECTMULT"] = [[Умножить напрямую]] }; - { ["ACT_FLDEXPLODE"] = [[Взорвать фишки]] }; - { ["ACT_FLDLEVELDELTA"] = [[Поднять уровень фишек]] }; - { ["ACT_FLDCOLLECT_COORDS"] = [[Собрать фишки по координатам]] }; - { ["ACT_FLDREPLACE_COORDS"] = [[Заменить фишки по координатам]] }; - { ["ACT_ONEMOREACTION"] = [[Дать ещё одно действие]] }; - { ["ACT_KEEPTIMEOUT"] = [[Не сбрасывать таймер]] }; - { ["ACT_SETVAR"] = [[Запомнить]] }; - { ["ACT_SETOBJVAR_LOCAL"] = [[Запомнить в объекте локально]] }; - { ["ACT_SETOBJVAR_GLOBAL"] = [[Запомнить в объекте глобально]] }; - { ["ACT_SETOBJVAR_OT"] = [[Запомнить в текущем овертайме]] }; - { ["ACT_DOIF"] = [[Если]] }; - { ["ACT_DOIFELSE"] = [[Если ... иначе]] }; - { ["ACT_PLAYABIANIM"] = [[Играть эффект абилки]] }; - { ["ACT_SENDCUSTOMMSG"] = [[Отправить данные клиентам]] }; - { ["ACT_INCSTAT"] = [[Увеличить статистику клиента]] }; - { ["ACT_ACTIVATEOT"] = [[Активировать ОТ-эффект]] }; - { ["ACT_REMOVE_OVERTIMES"] = [[Снять ОТ-эффекты]] }; - -- Keep these below -- - { ["ACT_FLDREPLACE"] = [[Заменить фишки <b><i>(устарело)</i></b>]] }; - { ["ACT_CRASH_GAME"] = [[УРОНИТЬ игру <b><i>(только для тестов)</i></b>]] }; - -- { ["PLAINLUA"] = [[Lua]] }; - }; - label = [["<i title=\"Действие\">A</i>"]]; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value; - } - - declare_common(jsle, "ACT_DOIF", "ACT_DOIFELSE") - - jsle:record "ACT_SET" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Установить %C(1)% в %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propset($(1), $(2))]]; - } - - jsle:record "ACT_INC" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Увеличить %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propinc($(1), $(2))]]; - } - - jsle:record "ACT_DEC" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Уменьшить %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propdec($(1), $(2))]]; - } - - jsle:record "ACT_MULT" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Умножить %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propmult($(1), $(2))]]; - } - - jsle:record "ACT_DIRECTSET" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Установить напрямую %C(1)% в %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propset_direct($(1), $(2))]]; - } - - jsle:record "ACT_DIRECTINC" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Увеличить напрямую %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propinc_direct($(1), $(2))]]; - } - - jsle:record "ACT_DIRECTDEC" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Уменьшить напрямую %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propdec_direct($(1), $(2))]]; - } - - jsle:record "ACT_DIRECTMULT" - { - children = - { - [1] = "PROPPATH_WRITE"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Умножить напрямую %C(1)% на %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propmult_direct($(1), $(2))]]; - } - - jsle:record "ACT_FLDEXPLODE" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = "CHIPCOORD"; - }; - html = [[Взорвать бомбу радиусом %C(1)% в координатах %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_explode($(1), $(2))]]; - } - - jsle:record "ACT_FLDREPLACE" - { - children = - { - [1] = "CHIPTYPE"; - [2] = "NUMOP_VARIANT"; - [3] = "CHIPTYPE"; - [4] = "NUMOP_VARIANT"; - }; - html = [[Заменить %C(1)% уровня %C(2)% на %C(3)% уровня %C(4)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_replace($(1), $(2), $(3), $(4))]]; - doc = [[Deprecated, use other replace actions]]; - } - - jsle:record "ACT_FLDLEVELDELTA" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = "CHIPTYPE"; - [3] = "NUMOP_VARIANT"; - [4] = "NUMOP_VARIANT"; - }; - html = [[Поднять уровень %C(2)% на %C(1)% в диапазоне от %C(3)% до %C(4)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_level_delta($(1), $(2), $(3), $(4))]]; - } - - jsle:record "ACT_FLDCOLLECT_COORDS" - { - children = - { - [1] = "COORDLISTOP_VARIANT"; - }; - html = [[Собрать %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_collect_coords($(1))]]; - } - - jsle:record "ACT_FLDREPLACE_COORDS" - { - children = - { - [1] = "COORDLISTOP_VARIANT"; - [2] = "CHIPTYPE_LIST"; - [3] = "NUMOP_VARIANT"; - }; - html = [[Заменить %C(1)% на %C(2)% уровня %C(3)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_replace_coords($(1),$(2),$(3))]]; - } - - jsle:literal "ACT_ONEMOREACTION" - { - html = [[Дать ещё одно действие <i>(только мгновенный эффект)</i>]]; - checker = no_check; - handler = invariant [[self:one_more_action()]]; - } - - jsle:literal "ACT_KEEPTIMEOUT" - { - html = [[Не сбрасывать таймер <i>(только мгновенный эффект)</i>]]; - checker = no_check; - handler = invariant [[self:keep_timeout()]]; - } - - jsle:record "ACT_SETVAR" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Запомнить в №%C(1)% значение %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:setvar($(1), $(2))]]; - } - - jsle:enum "OT_EFFECT_TARGET" - { - values = - { - { [PO.SELF] = [[на себя]] }; - { [PO.OPP] = [[на противника]] }; - { [PO.TARGET] = [[на цель]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_quoted; - } - - jsle:variant "BOOLOP_VARIANT" - { - values = - { - { ["BOOLEAN"] = [[Логическое значение]] }; - { ["BOOLOP_LT"] = [[<]] }; - { ["BOOLOP_LTE"] = [[≤]] }; - { ["BOOLOP_GT"] = [[>]] }; - { ["BOOLOP_GTE"] = [[≥]] }; - { ["BOOLOP_EQ"] = [[==]] }; - { ["BOOLOP_NEQ"] = [[!=]] }; - { ["BOOLOP_AND_MANY"] = [[И (Список)]] }; - { ["BOOLOP_OR_MANY"] = [[ИЛИ (Список)]] }; - { ["BOOLOP_NOT"] = [[НЕ]] }; - { ["BOOLOP_HAVEMEDAL"] = [[МЕДАЛЬ]] }; - { ["BOOLOP_ISACTIVE"] = [[Изменения инициированы целью овертайм-эффекта]] }; - { ["BOOLOP_IS_GAME_IN_MODE"] = [[Текущий игровой режим]] }; - -- Deprecated, keep below -- - { ["BOOLOP_AND"] = [[И]] }; - { ["BOOLOP_OR"] = [[ИЛИ]] }; - --{ ["PLAINLUA"] = [[Lua]] }; - }; - label = [["<i title=\"Логическая операция\">B</i>"]]; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value; - } - - jsle:record "BOOLOP_HAVEMEDAL" - { - children = - { - [1] = "PROPOBJECT"; - [2] = "NUMOP_VARIANT"; - }; - html = [[есть медаль №%C(2)% %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:have_medal($(1), $(2))]]; - } - - jsle:literal "BOOLOP_ISACTIVE" - { - html = [[изменения инициированы целью овертайм-эффекта]]; - checker = no_check; -- Only for on_changeset event. - handler = invariant [[self:is_overtime_target_active()]]; - } - - declare_common( - jsle, - "BOOLOP_LT", - "BOOLOP_LTE", - "BOOLOP_GT", - "BOOLOP_GTE", - "BOOLOP_EQ", - "BOOLOP_NEQ", - "BOOLOP_AND", - "BOOLOP_OR", - "BOOLOP_NOT" - ) - - jsle:variant "NUMOP_VARIANT" - { - values = - { - { ["NUMBER"] = [[Число]] }; - { ["NUMOP_ADD_MANY"] = [[+ (Список)]] }; - { ["NUMOP_DEC_MANY"] = [[- (Список)]] }; - { ["NUMOP_MUL_MANY"] = [[* (Список)]] }; - { ["NUMOP_DIV_MANY"] = [[/ (Список)]] }; - { ["NUMOP_POV"] = [[POW]] }; -- TODO: POW, not POV! Fix by search and replace - { ["NUMOP_MOD"] = [[MOD]] }; - { ["NUMOP_MIN"] = [[MIN]] }; - { ["NUMOP_MAX"] = [[MAX]] }; - { ["NUMOP_UNM"] = [[Знак]] }; - { ["NUMOP_GET"] = [[Характеристика]] }; - { ["NUMOP_GET_RAW"] = [[Базовое значение характеристики]] }; - { ["NUMOP_GET_ABIPROP"] = [[Характеристика абилки]] }; - { ["NUMOP_PERCENT_ROLL"] = [[Cлучайный процент]] }; - { ["NUMOP_TEAMSIZE"] = [[Размер команды]] }; - { ["NUMOP_GETVAR"] = [[Вспомнить]] }; - { ["NUMOP_GETOBJVAR_LOCAL"] = [[Вспомнить из объекта локально]] }; - { ["NUMOP_GETOBJVAR_GLOBAL"] = [[Вспомнить из объекта глобально]] }; - { ["NUMOP_GETOBJVAR_OT"] = [[Вспомнить из текущего овертайма]] }; - { ["NUMOP_OTLIFETIMELEFT"] = [[Оставшееся время жизни]] }; - { ["NUMOP_OTLIFETIMETOTAL"] = [[Общее время жизни]] }; - { ["NUMOP_FLDGETQUANTITYOFCHIPS"] = [[Число фишек по цвету и уровню]] }; - { ["NUMOP_TARGETX"] = [[Координата X выбранной фишки]] }; - { ["NUMOP_TARGETY"] = [[Координата Y выбранной фишки]] }; - { ["NUMOP_OTEFFECTCOUNT"] = [[Число активных овертайм-эффектов]] }; - { ["NUMOP_IFF"] = [[Если]] }; - { ["NUMOP_GETUID"] = [[Идентификатор игрока]] }; - -- Keep these below -- - { ["NUMOP_FLDCOUNTCHIPS"] = [[Число фишек на поле <b><i>(устарело)</i></b>]] }; - { ["NUMOP_ADD"] = [[+]] }; - { ["NUMOP_DEC"] = [[-]] }; - { ["NUMOP_MUL"] = [[*]] }; - { ["NUMOP_DIV"] = [[/]] }; - { ["NUMOP_CRASH_GAME"] = [[УРОНИТЬ игру <b><i>(только для тестов)</i></b>]] }; - --{ ["PLAINLUA"] = [[Lua]] }; - }; - label = [["<i title=\"Численная операция\">I</i>"]]; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value; - } - - declare_common( - jsle, - "NUMOP_ADD", - "NUMOP_DEC", - "NUMOP_MUL", - "NUMOP_DIV", - "NUMOP_POV", - "NUMOP_MOD", - "NUMOP_MIN", - "NUMOP_MAX", - "NUMOP_UNM" - ) - - jsle:record "NUMOP_GET" - { - children = - { - [1] = "PROPPATH_READ"; - }; - html = [[%C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propget($(1), false)]]; - } - - declare_common(jsle, "NUMOP_PERCENT_ROLL") - - jsle:record "NUMOP_FLDCOUNTCHIPS" - { - children = - { - [1] = "CHIPTYPE"; - [2] = "BOOLOP_VARIANT"; - }; - html = [[число %C(1)% на поле (учитывая уровни: %C(2)%)]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_count_chips($(1), $(2))]]; - doc = [[Deprecated, use other chip count operations]]; - } - - jsle:record "NUMOP_TEAMSIZE" - { - children = - { - [1] = "PROPOBJECT"; - }; - html = [[размер команды %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:team_size($(1))]]; - } - - jsle:record "NUMOP_GETVAR" - { - children = - { - [1] = "NUMOP_VARIANT"; - }; - html = [[вспомнить из №%C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:getvar($(1))]]; - } - - jsle:literal "NUMOP_OTLIFETIMELEFT" - { - html = [[оставшееся время жизни]]; - checker = no_check; - handler = invariant [[self:ot_lifetime_left()]]; - } - - jsle:literal "NUMOP_OTLIFETIMETOTAL" - { - html = [[общее время жизни]]; - checker = no_check; - handler = invariant [[self:ot_lifetime_total()]]; - } - - jsle:literal "NUMOP_TARGETX" - { - html = [[X выбранной фишки]]; - checker = no_check; - handler = invariant [[self:target_x()]]; - } - - jsle:literal "NUMOP_TARGETY" - { - html = [[Y выбранной фишки]]; - checker = no_check; - handler = invariant [[self:target_y()]]; - } - - jsle:record "PROPPATH_WRITE" - { - children = - { - [1] = "PROPOBJECT"; - [2] = "PROPWRITE"; - }; - html = [[%C(2)% %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:make_proppath($(1), $(2))]]; - } - - jsle:record "PROPPATH_READ" - { - children = - { - [1] = "PROPOBJECT"; - [2] = "PROPREAD"; - }; - html = [[%C(2)% %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:make_proppath($(1), $(2))]]; - } - - jsle:enum "PROPOBJECT" - { - values = - { - { [PO.SELF] = [[у себя]] }; - { [PO.OPP] = [[у противника]] }; - { [PO.TARGET] = [[у цели]] }; - { [PO.OWN_CHANGESET] = [[в своём наборе изменений]] }; - { [PO.OPP_CHANGESET] = [[в наборе изменений противника]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; -- Check value is valid for current action list subtype - handler = get_value_quoted; - } - - jsle:enum "PROPWRITE" - { - values = propwrite_values; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_quoted; - } - - jsle:enum "PROPREAD" - { - values = propread_values; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_quoted; - } - - jsle:enum "CHIPTYPE" - { - values = - { - { [CT.EMERALD] = [[зелёных фишек]] }; - { [CT.RUBY] = [[красных фишек]] }; - { [CT.AQUA] = [[синих фишек]] }; - { [CT.DMG] = [[черепов]] }; - { [CT.CHIP5] = [[фишек-5]] }; - { [CT.CHIP6] = [[фишек-6]] }; - { [CT.CHIP7] = [[фишек-7]] }; - { [CT.CHIP8] = [[фишек-8]] }; - { [CT.EMPTY] = [[пустых фишек]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_tonumber; - numeric_keys = true; - } - - jsle:edit "NUMBER" - { - size = 4; - numeric = true; - checker = check_tonumber; - handler = get_value_tonumber; - } - - declare_common( - jsle, - "BOOLEAN", - "PLAINLUA" - ) - - jsle:list "COORDLISTOP_STD" - { - type = "CHIPCOORD"; - html = [[фишки с координатами %LIST(", ")%]]; - checker = non_empty_list; - handler = get_children_concat_table; - } - - jsle:record "CHIPCOORD" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = "NUMOP_VARIANT"; - }; - html = [[(x: %C(1)%, y: %C(2)%)]]; - checker = no_check; - handler = node_children_placeholders_filler [[{x=$(1), y=$(2)}]]; - } - - -- TODO: UNUSED. Remove or use. - jsle:record "BOOLOP_SELECTEDTARGET" - { - children = - { - [1] = "TARGET_VALUE"; - }; - html = [[выбрана цель %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:is_target_selected($(1))]]; - doc = [[Currently not used]]; - } - - jsle:record "NUMOP_OTEFFECTCOUNT" - { - children = - { - [1] = "PROPOBJECT"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - }; - html = [[число овертайм-эффектов абилки ID %C(2)% <i>(0 — этот эффект)</i> № эффекта %C(3)% <i>(0 — по умолчанию)</i>, активных %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:active_ot_effect_count($(1), $(2), $(3))]]; - } - - declare_common(jsle, "NUMOP_IFF") - - jsle:record "NUMOP_GET_RAW" - { - children = - { - [1] = "PROPPATH_READ"; - }; - html = [[базовое значение %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:propget($(1), true)]]; - } - - -- TODO: Get rid of non-list versions! - - declare_common( - jsle, - "NUMOP_ADD_MANY", - "NUMOP_DEC_MANY", - "NUMOP_MUL_MANY", - "NUMOP_DIV_MANY" - ) - - declare_common( - jsle, - "BOOLOP_AND_MANY", - "BOOLOP_OR_MANY" - ) - - jsle:list "CHIPTYPE_LIST" - { - type = "CHIPTYPE"; - html = [[%LIST(", ")%]]; - checker = non_empty_list; - handler = get_children_concat_table; - } - - jsle:record "NUMOP_GET_ABIPROP" - { - children = - { - [1] = "ABIPROP_NAME"; - }; - html = [[%C(1)% абилки]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:abipropget($(1))]]; - } - - jsle:enum "ABIPROP_NAME" - { - values = - { - { [AP.prob] = [[вероятность активации]] }; - }; - html = [[%VALUE()%]]; - checker = check_mapping_tonumber; - handler = get_value_mapped_tonumber_quoted(abiprob_mapping); - } - - jsle:record "ACT_SENDCUSTOMMSG" - { - children = - { - [1] = "NUMOP_LIST"; - }; - html = [[Отправить участникам боя данные: %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:send_custom_msg($(1))]]; - } - - declare_common(jsle, "NUMOP_LIST") - - jsle:record "ACT_PLAYABIANIM" - { - children = - { - [1] = "NUMOP_VARIANT"; - }; - html = [[Играть эффект абилки ID: %C(1)%]]; - checker = no_check; - -- Hack. Should format be hardcoded here or below? - handler = node_children_placeholders_filler( - [[self:send_custom_msg({]]..assert_is_number(CM.PLAYABIANIM) - ..[[, $(1), self:get_uid("]]..PO.SELF..[[")})]] - ); - } - - jsle:variant "COORDLISTOP_VARIANT" - { - values = - { - { ["COORDLISTOP_STD"] = [[Обычный список коордтнат]] }; - { ["COORDLISTOP_GETLEVEL"] = [[Фишки цвета <i>цв1</i> с уровнями от <i>ур1</i> до <i>ур2</i>]] }; - }; - label = [["<i title=\"Список координат\">C</i>"]]; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value; - } - - jsle:record "COORDLISTOP_GETLEVEL" - { - children = - { - [1] = "CHIPTYPE"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - }; - html = [[%C(1)% с уровнями от %C(2)% до %C(3)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_get_coordlist_from_levels_and_type($(1), $(2), $(3))]]; - } - - jsle:record "NUMOP_FLDGETQUANTITYOFCHIPS" - { - children = - { - [1] = "CHIPTYPE"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - [4] = "BOOLOP_VARIANT"; - }; - html = [[число %C(1)% на поле уровней с %C(2)% до %C(3)% (учитывая уровень в счетчике: %C(4)%)]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:fld_get_quantity_of_chips($(1), $(2), $(3), $(4))]]; - } - - jsle:enum "CLIENTSTAT" - { - values = - { - -- TODO: Support commented out variants? - { [CST.SPELL_USE] = [[исп. спеллов]] }; - --{ [CST.SPELL_FRAG] = [[фраги от спеллов]] }; - { [CST.CONSUMABLE_USE] = [[исп. расходников]] }; - --{ [CST.CONSUMABLE_FRAG] = [[фраги от расходников]] }; - { [CST.AUTOABILITY_USE] = [[исп. автоабилок]] }; - --{ [CST.AUTOABILITY_FRAG] = [[фраги от автоабилок]] }; - --{ [CST.RATING] = [[рейтинг]] }; - --{ [CST.CUSTOM] = [[пользовательская]] }; - }; - html = [[%VALUE()%]]; - checker = check_mapping_tonumber; - handler = get_value_tonumber; - } - - jsle:record "ACT_INCSTAT" - { - children = - { - [1] = "PROPOBJECT"; - [2] = "CLIENTSTAT"; - [3] = "NUMOP_VARIANT"; - [4] = "NUMOP_VARIANT"; - }; - html = [[Увеличить %C(1)% статистику «%C(2)%» эффекта №%C(3)% <i>(0 — текущий)</i> на %C(4)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:inc_client_stat($(1), $(2), $(3), $(4))]]; - } - - jsle:record "ACT_ACTIVATEOT" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = { "KEYVALUE_LIST", default = empty_table }; - }; - html = [[Активировать ОТ-эффект №%C(1)%, передав %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:activate_custom_ot_effect($(1),$(2))]]; - } - - jsle:list "CUSTOM_OVERTIME_EFFECTS" - { - type = "OVERTIME_EFFECT"; - html = [[%LE("<i>(Нет дополнительных ОТ-эффектов)</i>")%%LNE("<ol><li><h2>Дополнительный OT-эффект</h2>")%%LIST("<hr><li><h2>Дополнительный OT-эффект</h2>")%%LNE("</ol>")%]]; - checker = no_check; - handler = function(self, node) - local buf = {[[{]]} - local _ = function(v) buf[#buf + 1] = tostring(v) end - for i, child in ipairs(node.value) do - _ [[ -[]] _(i) _[[] = function(self) -]] _(child) _ [[ -end; -]] - end - _ [[}]] - return table.concat(buf) - end; - } - - jsle:record "NUMOP_GETUID" - { - children = - { - [1] = "PROPOBJECT"; - }; - html = [[идентификатор игрока %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:get_uid($(1))]]; - } - - jsle:enum "STORE_OBJ" - { - values = - { - { [SO.CLIENT_SELF] = [[на себе]] }; - { [SO.CLIENT_OPP] = [[на противнике]] }; - { [SO.CLIENT_TARGET] = [[на цели]] }; - { [SO.FIGHT] = [[на бою]] }; - { [SO.GAME] = [[на игре]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_tonumber; - } - - jsle:record "ACT_SETOBJVAR_LOCAL" - { - children = - { - [1] = "STORE_OBJ"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - }; - html = [[Запомнить в объекте «%C(1)%» в слот №%C(2)% <b>приватное</b> значение %C(3)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:setobjvar_local($(1), $(2), $(3))]]; - } - - jsle:record "NUMOP_GETOBJVAR_LOCAL" - { - children = - { - [1] = "STORE_OBJ"; - [2] = "NUMOP_VARIANT"; - }; - html = [[вспомнить из объекта «%C(1)%» из слота №%C(2)% <b>приватное</b> значение]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:getobjvar_local($(1), $(2))]]; - } - - jsle:record "ACT_SETOBJVAR_GLOBAL" - { - children = - { - [1] = "STORE_OBJ"; - [2] = "NUMOP_VARIANT"; - [3] = "NUMOP_VARIANT"; - }; - html = [[Запомнить в объекте %C(1)% в слот №%C(2)% <b>публичное</b> значение %C(3)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:setobjvar_global($(1), $(2), $(3))]]; - } - - jsle:record "NUMOP_GETOBJVAR_GLOBAL" - { - children = - { - [1] = "STORE_OBJ"; - [2] = "NUMOP_VARIANT"; - }; - html = [[вспомнить из объекта %C(1)% из слота №%C(2)% <b>публичное</b> значение]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:getobjvar_global($(1), $(2))]]; - } - - jsle:record "ACT_REMOVE_OVERTIMES" - { - children = - { - [1] = "OT_EFFECT_TARGET"; - }; - html = [[Снять все эффекты, наложенные %C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:remove_overtime_effects($(1))]]; - } - - jsle:enum "GAME_MODES" - { - values = - { - { [GMF.ALL] = [[любой]] }; - { [GMF.DUEL] = [[дуэль]] }; - { [GMF.SINGLE] = [[одиночная игра]] }; - }; - html = [[%VALUE()%]]; - checker = no_check; - handler = get_value_tonumber; - } - - jsle:record "BOOLOP_IS_GAME_IN_MODE" - { - children = - { - [1] = "GAME_MODES"; - }; - html = [[игровой режим «%C(1)%» включён]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:is_game_in_mode($(1))]]; - } - - jsle:record "ACT_SETOBJVAR_OT" - { - children = - { - [1] = "NUMOP_VARIANT"; - [2] = "NUMOP_VARIANT"; - }; - html = [[Запомнить в текущем овертайме в слот №%C(1)% значение %C(2)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:setobjvar_ot($(1), $(2))]]; - } - - jsle:record "NUMOP_GETOBJVAR_OT" - { - children = - { - [1] = "NUMOP_VARIANT"; - }; - html = [[Вспомнить из текущего овертайма из слота №%C(1)%]]; - checker = no_check; - handler = node_children_placeholders_filler [[self:getobjvar_ot($(1))]]; - } - - declare_common( - jsle, - "KEYVALUE_LIST", - "KEYVALUE" - ) - - jsle:literal "ACT_CRASH_GAME" - { - html = [[<span style="color:red"><b>УРОНИТЬ</b> игру (только для теста)<span>]]; - checker = function(self, node) - if common_get_config().crashers_enabled == true then - errr("WARNING: ACT_CRASH_GAME CRASHER IS ON") - return true - end - - errr("DETECTED ATTEMPT TO UPLOAD CRASHERS (SCHEMA)") - return false, "crashers are disabled in config" - end; - handler = invariant [[self:crash_game()]]; - } - - jsle:literal "NUMOP_CRASH_GAME" - { - html = [[<span style="color:red"><b>УРОНИТЬ</b> игру (только для теста)<span>]]; - checker = function(self, node) - if common_get_config().crashers_enabled == true then - errr("WARNING: NUMOP_CRASH_GAME CRASHER IS ON") - return true - end - - errr("DETECTED ATTEMPT TO UPLOAD CRASHERS (SCHEMA)") - return false, "crashers are disabled in config" - end; - handler = invariant [[(self:crash_game() or 0)]]; - } - - return jsle -end - -return -{ - define_schema = define_schema; -}
diff --git a/tests/reweave/scope.lua b/tests/reweave/scope.lua deleted file mode 100644 index f871746..0000000 --- a/tests/reweave/scope.lua +++ /dev/null
@@ -1,3 +0,0 @@ -do - print("scope") -end
diff --git a/tests/reweave/str.lua b/tests/reweave/str.lua deleted file mode 100644 index bf63ff3..0000000 --- a/tests/reweave/str.lua +++ /dev/null
@@ -1,2 +0,0 @@ -sample=[==========[perl -e 'print "<IMG SRC=javascript:alert(\"XSS\")>";' > out]==========] -sample=[==========[perl -e 'print "<IMG SRC=javascript:alert(\"XSS\")>";' > out]==========]
diff --git a/tests/reweave/ws_simple.lua b/tests/reweave/ws_simple.lua deleted file mode 100644 index 044284a..0000000 --- a/tests/reweave/ws_simple.lua +++ /dev/null
@@ -1 +0,0 @@ -repeat until true
diff --git a/tests/run.mlua b/tests/run.mlua deleted file mode 100644 index f9f9896..0000000 --- a/tests/run.mlua +++ /dev/null
@@ -1,56 +0,0 @@ -------------------------------------------------------------------------------- --- Copyright (c) 2006-2013 Fabien Fleutot and others. --- --- All rights reserved. --- --- This program and the accompanying materials are made available --- under the terms of the Eclipse Public License v1.0 which --- accompanies this distribution, and is available at --- http://www.eclipse.org/legal/epl-v10.html --- --- This program and the accompanying materials are also made available --- under the terms of the MIT public license which accompanies this --- distribution, and is available at http://www.lua.org/license.html --- --- Contributors: --- Fabien Fleutot - API and implementation --- -------------------------------------------------------------------------------- - --- Run all *.lua and *.mlua files in this directory. --- This makes it easy to run all tests in the directory, - --{ extension 'xloop' } - -LS_COMMANDS = { "ls", "dir /b" } -for i, cmd in ipairs(LS_COMMANDS) do - local f = io.popen (cmd) - ls = f :read '*a' - f :close() - if ls ~= '' then - break - elseif i == #LS_COMMANDS then - error "Can't figure out how to list files on your OS" - end -end - -this_script = arg[1] - -local errors = {} - -for filename in ls :gmatch "[^\n]+" if filename ~= this_script and filename :strmatch "%.m?lua$" do - printf ("*** running %s ***", filename) - local ret = os.execute ("metalua "..filename) - if ret ~= 0 then - errors[#errors + 1] = "Test "..filename.." failed, returned "..ret - end -end - -if #errors > 0 then - print("\n\n================================================================================") - error( - "TEST FAILURES DETECTED:\n" .. - "-----------------------\n" .. - " * " .. table.concat(errors, "\n * ") - ) -end
diff --git a/lib/verbose_require.lua b/verbose_require.lua similarity index 96% rename from lib/verbose_require.lua rename to verbose_require.lua index 0e07650..7d0af79 100644 --- a/lib/verbose_require.lua +++ b/verbose_require.lua
@@ -1,4 +1,4 @@ -------------------------------------------------------------------------------- +-------------------------------------------------------------------------------- -- Copyright (c) 2006-2013 Fabien Fleutot and others. -- -- All rights reserved. @@ -15,8 +15,7 @@ -- Contributors: -- Fabien Fleutot - API and implementation -- -------------------------------------------------------------------------------- - +-------------------------------------------------------------------------------- do local xrequire, n, ind = require, 0, "| "