From a1f7adb55a13f0bbc52a6856127e23c1aebddd63 Mon Sep 17 00:00:00 2001 From: fft Date: Thu, 3 Apr 2008 21:06:30 +0200 Subject: [PATCH] introducing metalint --- src/samples/metalint/INSTALL.TXT | 22 + src/samples/metalint/LICENCE.TXT | 27 ++ src/samples/metalint/README.TXT | 149 +++++++ src/samples/metalint/dlua/base.dlua | 204 +++++++++ .../metalint/dlua/metalua/compiler.dlua | 402 +++++++++++++++++ src/samples/metalint/dlua/walk.dlua | 3 + src/samples/metalint/dlua/walk/id.dlua | 3 + src/samples/metalint/metalint.dlua | 10 + src/samples/metalint/metalint.mlua | 404 ++++++++++++++++++ 9 files changed, 1224 insertions(+) create mode 100644 src/samples/metalint/INSTALL.TXT create mode 100644 src/samples/metalint/LICENCE.TXT create mode 100644 src/samples/metalint/README.TXT create mode 100644 src/samples/metalint/dlua/base.dlua create mode 100644 src/samples/metalint/dlua/metalua/compiler.dlua create mode 100644 src/samples/metalint/dlua/walk.dlua create mode 100644 src/samples/metalint/dlua/walk/id.dlua create mode 100644 src/samples/metalint/metalint.dlua create mode 100644 src/samples/metalint/metalint.mlua diff --git a/src/samples/metalint/INSTALL.TXT b/src/samples/metalint/INSTALL.TXT new file mode 100644 index 0000000..600fdff --- /dev/null +++ b/src/samples/metalint/INSTALL.TXT @@ -0,0 +1,22 @@ +Metalint 0.1 - INSTALL.TXT +========================== + +Metalint is a regular Metalua program, and relies on Metalua compilation +libraries. You must therefore have a working Metalua installation on your +system. You can run it with: "metalua metalint.mlua -- ". +For instance, to check metalint itself: + + ~/src/metalua/src/sandbox$ metalua metalint.mlua -- metalint.mlua + File metalint.mlua checked successfully + ~/src/metalua/src/sandbox$ + +You can also precompile it: + + ~/src/metalua/src/sandbox$ metalua metalint.mlua -s '#!/usr/bin/env lua' -o metalint + ~/src/metalua/src/sandbox$ ./metalint lint.mlua + File lint.mlua checked successfully + ~/src/metalua/src/sandbox$ + +Beware that even when precompiled, it still requires the Metalua runtime libs in LUA_PATH. + +Don't forget to set the LUA_DPATH environment variable! \ No newline at end of file diff --git a/src/samples/metalint/LICENCE.TXT b/src/samples/metalint/LICENCE.TXT new file mode 100644 index 0000000..41937b8 --- /dev/null +++ b/src/samples/metalint/LICENCE.TXT @@ -0,0 +1,27 @@ +Metalint + +Copyright (c) 2006-2008 Fabien Fleutot + +Metalint is available under the MIT licence. + +MIT License +=========== + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/samples/metalint/README.TXT b/src/samples/metalint/README.TXT new file mode 100644 index 0000000..45955ff --- /dev/null +++ b/src/samples/metalint/README.TXT @@ -0,0 +1,149 @@ +Metalint 0.1 - README.TXT +========================= + +Metalint is a utility that checks Lua and Metalua source files for global +variables usage. Beyond checking toplevel global variables, it also checks +fields in modules: for instance, it will catch typos such as taable.insert(), +both also table.iinsert(). + +Metalint works with declaration files, which list which globals are declared, +and what can be done with them. The syntax is: + +DECL ::= (DECL_ELEM ";"?) * +DECL_ELEM ::= NAME | "module" NAME DECL "end" | "free" NAME | "private" DECL_ELEM +NAME ::= | + +Identifiers and strings are the same as in Lua, except that the only reserved +keywords are "free", "module", "end" and "private". A variable name can be +equivalently specified as a string or as an identifier. Lua comments are allowed +in declaration files, short and long. Check for *.dlua files in the distribution +for examples. + +Meaning of declaration elements: + +- Standalone names declare the existence of a variable. This variable is not a + module, i.e. people must not extract fields from it. For instance, the + function ipairs() will simply be declared as: "ipairs". With this declaration, + it's an error to write, for instance, "ipairs.some_field". + +- Names preceded with "free" can be used as you want, including arbitrary + sub-indexing. This is useful for global tables not used as modules, and for + modules you're too lazy to fully declare. For instance, the declaration "free + _G" allows you to bypass all checkings, as long as you access stuff through _G + rather than directly (i.e. "table.iinsert" will fail, but "_G.table.iinsert" + will be accepted). + +- modules contain field declarations. For instance, the contents of the standard + "os" module will be declared as "module os exit ; setlocale; date; [...] + execute end". + +Declaration files are loaded: + +- manually, by passing "-f filename", "-l libname" or "-e + decl_literal_expression" as options to the checking program. Options are + processed in order, i.e. if you load a library after a file name to check, + this library won't be accessible while checking the dource file. + +- automatically, when a call to "require()" is found in the code. + +- declaration library "base" is automatically loaded. + +Declaration library files are retrieved with the same algorithm as for Lua +libraries, except that the pattern string is taken from environment variable +LUA_DPATH rather than LUA_PATH or LUA_CPATH. For instance, if +LUA_DPATH="./?.dlua" and a "require 'walk.id'" is found, the checker will +attempt to load "./walk/id.dlua". It won't fail if it can't find it, but then, +attempts to use globals declared by walk.id are likely to fail. + +The metalua base libraries, which include Lua base libraries, can be found in +base.dlua. They're automatically loaded when you run metalint. + +Limitations: if you try to affect custom names to modules, e.g. "local +wi=require 'walk.id'", the checker won't be able to check your usage of +subfields of "wi". Similarly, if you redefine require() or module(), or create +custom versions of these, metalint will be lost. Finally, computed access to +modules are obviously not checked, i.e. "local x, y = 'iinsert', { }; +table[x](y, 1)" will be accepted. + +Future: Metalint is intended to support richer static type checkings, including +function argument types. The idea is not to formally prove type soundness, but +to accelerate the discovery of many silly bugs when using a (possibly third +party) library. However, to perform interesting checks, the type declaration +system must support a couple of non-trivial stuff like union types and higher +order functions. Moreover, runtime checking code could optionally be inserted to +check that a function API is respected when it's called (check the types +extension in Metalua). Stay tuned. + +Notice that metalint can easily be turned into a smarter variable localizer, +which would change references to module elements into local variables. +For instance, it would add "local _table_insert = table.insert" at the beginning +of the file, and change every instance of "table.insert" into a reference to the +local variable. This would be much more efficient than simply adding a "local +table=table". + + + +Finally, to accelerate the migration of existing codebases, a decl_dump() +function is provided with metalint, which attempts to generate a declaration for +a module currently loaded in RAM. The result is not always perfect, but remains +a serious time saver: + +~/src/metalua/src/sandbox$ metalua +Metalua, interactive REPLoop. +(c) 2006-2008 +M> require "metalint" +M> require "walk" +M> decl_dump ("walk", "decl/walk.dlua") +M> ^D +~/src/metalua/src/sandbox$ cat decl/walk.dlua +module walk + debug; + module tags + module stat + Forin; + Do; + Set; + Fornum; + Invoke; + While; + Break; + Call; + Label; + Goto; + Local; + If; + Repeat; + Localrec; + Return; + end; + module expr + True; + String; + Index; + Paren; + Id; + False; + Invoke; + Function; + Op; + Number; + Table; + Dots; + Nil; + Stat; + Call; + end; + end; + expr_list; + binder_list; + guess; + expr; + block; + module traverse + expr; + block; + stat; + expr_list; + end; + stat; +end; diff --git a/src/samples/metalint/dlua/base.dlua b/src/samples/metalint/dlua/base.dlua new file mode 100644 index 0000000..c78c2d7 --- /dev/null +++ b/src/samples/metalint/dlua/base.dlua @@ -0,0 +1,204 @@ +rawtype; +gcinfo; +module os + exit; + setlocale; + date; + getenv; + difftime; + remove; + time; + clock; + tmpname; + rename; + execute; +end; +o; +getfenv; +const; +pairs; +max; +tonumber; +module io + lines; + write; + close; + flush; + open; + output; + type; + read; + stderr; + stdin; + input; + stdout; + popen; + tmpfile; +end; +load; +"module"; +free _G; +rawpairs; +module coroutine + resume; + yield; + status; + wrap; + create; + running; +end; +rawipairs; +loadstring; +module string + split; + match; + gmatch; + upper; + gsub; + format; + lower; + sub; + gfind; + find; + char; + dump; + undump; + reverse; + byte; + strmatch; + len; + rep; +end; +module metalua + version; + ext_compiler_prefix; + ext_runtime_prefix; +end; +module package + path; + metalua_loader; + cpath; + findfile; + free preload; + free loaders; + config; + free loaded; + loadlib; + mpath; + seeall; +end; +module table + shallow_copy; + iforeach; + tostring; + getn; + foreachi; + foreach; + sort; + ifold; + print; + icat; + isub; + transpose; + iany; + override; + imap; + izip; + range; + deep_copy; + cat; + iall; + maxn; + remove; + concat; + iflatten; + irev; + ifilter; + setn; + insert; +end +min; +printf; +require; +unpack; +global; +setmetatable; +next; +ipairs; +parser; +rawequal; +clopts; +collectgarbage; +arg; +newproxy; +values; +xpcall; +rawset; +keys; +tostring; +print; +dostring; +decl_builder; +module math + log; + max; + acos; + huge; + ldexp; + pi; + cos; + tanh; + pow; + deg; + tan; + cosh; + sinh; + random; + randomseed; + frexp; + ceil; + floor; + rad; + abs; + sqrt; + modf; + asin; + min; + mod; + fmod; + log10; + atan2; + exp; + sin; + atan; +end +lua_loadstring; +pcall; +assert; +type; +getmetatable; +select; +ivalues; +rawget; +id; +setfenv; +module debug + getupvalue; + debug; + sethook; + getmetatable; + gethook; + setmetatable; + setlocal; + traceback; + setfenv; + getinfo; + setupvalue; + getlocal; + getregistry; + getfenv; +end +module strict end +dofile; +error; +loadfile; diff --git a/src/samples/metalint/dlua/metalua/compiler.dlua b/src/samples/metalint/dlua/metalua/compiler.dlua new file mode 100644 index 0000000..5a1b0fd --- /dev/null +++ b/src/samples/metalint/dlua/metalua/compiler.dlua @@ -0,0 +1,402 @@ +module lexer + module lexer + save; + newstream; + extract_long_string; + extract_word; + extract_short_string; + clone; + free __index; + is_keyword; + peek; + module sym + end; + extract; + next; + restore; + module extractors + end; + module alpha + end; + sync; + takeover; + module patterns + spaces; + number_exponant; + word; + long_string; + short_comment; + long_comment; + module number_mantissa + end; + final_short_comment; + end; + extract_symbol; + skip_whitespaces_and_comments; + check; + extract_number; + module token_metatable + end; + add; + end; + free _M; + _NAME; + _PACKAGE; +end; + +module gg + sequence; + _PACKAGE; + e; + is_parser; + with_lexer; + optkeyword; + onkeyword; + make_parser; + _NAME; + list; + expr; + free _M; + multisequence; + parse_error; +end; + +module bytecode + MAXPARAMS; + metalua_compile; + dump_file; + dump_string; + VARARG_ISVARARG; + indexupvalue; + MAX_INT; + free _M; + module luaU + LUA_TSTRING; + DumpBlock; + DumpByte; + DumpProtos; + DumpCode; + LUA_TNIL; + endianness; + LUA_TBOOLEAN; + DumpConstants; + DumpInt; + DumpDebug; + DumpLiteral; + DumpNumber; + from_int; + DumpSize; + LUA_TNUMBER; + DumpHeader; + dump; + DumpString; + ttype; + make_setS; + DumpUpvalues; + DumpLines; + DumpLocals; + make_setF; + LUA_TNONE; + DumpFunction; + from_double; + end; + LUA_MAXPARSERLEVEL; + VARARG_NEEDSARG; + module luaK + infix; + codenot; + NO_JUMP; + indexed; + checkstack; + dischargejpc; + fixline; + concat; + exp2reg; + code; + code_label; + exp2val; + sethvalue; + jumponcond; + prefix; + jump; + condjump; + ttisnumber; + exp2anyreg; + exp2RK; + setsvalue; + setnilvalue; + _nil; + exp2nextreg; + getjump; + codeAsBx; + addk; + need_value; + freeexp; + posfix; + nilK; + discharge2reg; + storevar; + setmultret; + setlist; + codeABx; + MAXSTACK; + codeABC; + freereg; + reserveregs; + codecomp; + dischargevars; + hasjumps; + setnvalue; + module arith_opc + sub; + mul; + not; + len; + pow; + div; + mod; + add; + end; + patchlist; + constfolding; + getlabel; + module test_opc + module ne + cond; + opc; + end; + module eq + cond; + opc; + end; + module ge + cond; + opc; + end; + module gt + cond; + opc; + end; + module le + cond; + opc; + end; + module lt + cond; + opc; + end; + end; + getjumpcontrol; + patchtohere; + LUA_MULTRET; + codearith; + boolK; + fixjump; + ret; + nvalue; + goiffalse; + isnumeral; + patchlistaux; + discharge2anyreg; + setoneret; + patchtestreg; + removevalues; + getcode; + _self; + goiftrue; + numberK; + setcallreturns; + invertjump; + setreturns; + stringK; + end; + module luaP + MAXARG_C; + SETARG_C; + MAXARG_A; + SETARG_sBx; + MAXARG_sBx; + MAXARG_Bx; + GETARG_A; + GETARG_C; + GETARG_sBx; + OpModeT; + POS_C; + GET_OPCODE; + SIZE_B; + module OpCode + OP_GETTABLE; + OP_GETGLOBAL; + OP_NOT; + OP_MOD; + OP_LOADK; + OP_TAILCALL; + OP_TEST; + OP_TESTSET; + OP_LE; + OP_GETUPVAL; + OP_CALL; + OP_SETTABLE; + OP_LT; + OP_POW; + OP_ADD; + OP_EQ; + OP_SETLIST; + OP_CONCAT; + OP_JMP; + OP_SETGLOBAL; + OP_CLOSE; + OP_SETUPVAL; + OP_NEWTABLE; + OP_DIV; + OP_LEN; + OP_CLOSURE; + OP_SELF; + OP_TFORLOOP; + OP_MUL; + OP_FORPREP; + OP_MOVE; + OP_LOADBOOL; + OP_FORLOOP; + OP_SUB; + OP_LOADNIL; + OP_RETURN; + OP_UNM; + OP_VARARG; + end; + MAXARG_B; + SETARG_A; + testOpMode; + SIZE_OP; + OpModeK; + module ROpCode + end; + SET_OPCODE; + NO_REG; + ISK; + module opnames + end; + MAXINDEXRK; + getOpMode; + SIZE_C; + RKASK; + OpModesetA; + SETARG_Bx; + OpModeCrk; + OpModeBrk; + OpModeBreg; + NUM_OPCODES; + LFIELDS_PER_FLUSH; + DecodeInst; + Instruction; + SETARG_B; + CREATE_ABC; + CREATE_ABx; + GETARG_B; + module OpMode + end; + POS_A; + POS_B; + POS_Bx; + module opmodes + end; + SIZE_A; + BITRK; + SIZE_Bx; + GETARG_Bx; + end; + _NAME; + VARARG_HASARG; + MAXUPVALUES; + _PACKAGE; + MAXVARS; + module format + number_size; + instr_size; + header; + little_endian; + int_size; + size_t_size; + integral; + end; +end; + +module mlc + + luastring_of_luafile; + lexstream_of_luafile; + ast_of_luafile; + proto_of_luafile; + luacstring_of_luafile; + function_of_luafile; + + lexstream_of_luastring; + ast_of_luastring; + proto_of_luastring; + luacstring_of_luastring; + function_of_luastring; + + ast_of_lexstream; + proto_of_lexstream; + luacstring_of_lexstream; + function_of_lexstream; + + proto_of_ast; + luacstring_of_ast; + function_of_ast; + + luacstring_of_proto; + function_of_proto; + + function_of_luacstring; + + luafile_to_luastring; + luafile_to_lexstream; + luafile_to_ast; + luafile_to_proto; + luafile_to_luacstring; + luafile_to_function; + + luastring_to_lexstream; + luastring_to_ast; + luastring_to_proto; + luastring_to_luacstring; + luastring_to_function; + + lexstream_to_ast; + lexstream_to_proto; + lexstream_to_luacstring; + lexstream_to_function; + + ast_to_proto; + ast_to_luacstring; + ast_to_function; + + proto_to_luacstring; + proto_to_function; + + luacstring_to_function; + + luacstring_of_function; + function_to_luacstring; + + convert; + module order + function; + luafile; + luacstring; + proto; + lexstream; + luastring; + ast; + end; + __index; +end; +extension; +module mlp +end + +module metalua + module compiler + end +end \ No newline at end of file diff --git a/src/samples/metalint/dlua/walk.dlua b/src/samples/metalint/dlua/walk.dlua new file mode 100644 index 0000000..86939b8 --- /dev/null +++ b/src/samples/metalint/dlua/walk.dlua @@ -0,0 +1,3 @@ +module walk + expr; block; stat; expr_list; guess +end \ No newline at end of file diff --git a/src/samples/metalint/dlua/walk/id.dlua b/src/samples/metalint/dlua/walk/id.dlua new file mode 100644 index 0000000..7545b34 --- /dev/null +++ b/src/samples/metalint/dlua/walk/id.dlua @@ -0,0 +1,3 @@ +module walk_id + expr; block; stat; expr_list; guess +end \ No newline at end of file diff --git a/src/samples/metalint/metalint.dlua b/src/samples/metalint/metalint.dlua new file mode 100644 index 0000000..5f225d7 --- /dev/null +++ b/src/samples/metalint/metalint.dlua @@ -0,0 +1,10 @@ +free decl_lexer; -- I want to access its alpha symbols table +decl_builder; +decl_parser; +decl_elem_parser; +parse_decl_lib; +parse_decl_expr; +parse_decl_file; +free DECLARATIONS; +check_src_file; +decl_dump; diff --git a/src/samples/metalint/metalint.mlua b/src/samples/metalint/metalint.mlua new file mode 100644 index 0000000..782f934 --- /dev/null +++ b/src/samples/metalint/metalint.mlua @@ -0,0 +1,404 @@ +--[[ Announce: I'm happy to annouce Metalint 0.1. + +README + +Metalint is a utility that checks Lua and Metalua source files for global +variables usage. Beyond checking toplevel global variables, it also checks +fields in modules: for instance, it will catch typos such as taable.insert(), +both also table.iinsert(). + +Metalint works with declaration files, which list which globals are declared, +and what can be done with them. The syntax is: + +DECL ::= (DECL_ELEM ";"?) * +DECL_ELEM ::= NAME | "module" NAME DECL "end" | "free" NAME | "private" DECL_ELEM +NAME ::= | + +Identifiers and strings are the same as in Lua, except that the only reserved +keywords are "free", "module", "end" and "private". A variable name can be +equivalently specified as a string or as an identifier. Lua comments are allowed +in declaration files, short and long. Check for *.dlua files in the distribution +for examples. + +Meaning of declaration elements: + +- Standalone names declare the existence of a variable. This variable is not a + module, i.e. people must not extract fields from it. For instance, the + function ipairs() will simply be declared as: "ipairs". With this declaration, + it's an error to write, for instance, "ipairs.some_field". + +- Names preceded with "free" can be used as you want, including arbitrary + sub-indexing. This is useful for global tables not used as modules, and for + modules you're too lazy to fully declare. For instance, the declaration "free + _G" allows you to bypass all checkings, as long as you access stuff through _G + rather than directly (i.e. "table.iinsert" will fail, but "_G.table.iinsert" + will be accepted). + +- modules contain field declarations. For instance, the contents of the standard + "os" module will be declared as "module os exit ; setlocale; date; [...] + execute end". + +Declaration files are loaded: + +- manually, by passing "-f filename", "-l libname" or "-e + decl_literal_expression" as options to the checking program. Options are + processed in order, i.e. if you load a library after a file name to check, + this library won't be accessible while checking the dource file. + +- automatically, when a call to "require()" is found in the code. + +- declaration library "base" is automatically loaded. + +Declaration library files are retrieved with the same algorithm as for Lua +libraries, except that the pattern string is taken from environment variable +LUA_DPATH rather than LUA_PATH or LUA_CPATH. For instance, if +LUA_DPATH="./?.dlua" and a "require 'walk.id'" is found, the checker will +attempt to load "./walk/id.dlua". It won't fail if it can't find it, but then, +attempts to use globals declared by walk.id are likely to fail. + +The metalua base libraries, which include Lua base libraries, can be found in +base.dlua. They're automatically loaded when you run metalint. + +Limitations: if you try to affect custom names to modules, e.g. "local +wi=require 'walk.id'", the checker won't be able to check your usage of +subfields of "wi". Similarly, if you redefine require() or module(), or create +custom versions of these, metalint will be lost. Finally, computed access to +modules are obviously not checked, i.e. "local x, y = 'iinsert', { }; +table[x](y, 1)" will be accepted. + +Future: Metalint is intended to support richer static type checkings, including +function argument types. The idea is not to formally prove type soundness, but +to accelerate the discovery of many silly bugs when using a (possibly third +party) library. However, to perform interesting checks, the type declaration +system must support a couple of non-trivial stuff like union types and higher +order functions. Moreover, runtime checking code could optionally be inserted to +check that a function API is respected when it's called (check the types +extension in Metalua). Stay tuned. + +Notice that metalint can easily be turned into a smarter variable localizer, +which would change references to module elements into local variables. +For instance, it would add "local _table_insert = table.insert" at the beginning +of the file, and change every instance of "table.insert" into a reference to the +local variable. This would be much more efficient than simply adding a "local +table=table". + +Finally, to accelerate the migration of existing codebases, a decl_dump() +function is provided with metalint, which attempts to generate a declaration for +a module currently loaded in RAM. The result is not always perfect, but remains +a serious time saver: + +~/src/metalua/src/sandbox$ metalua +Metalua, interactive REPLoop. +(c) 2006-2008 +M> require "metalint" +M> require "walk" +M> decl_dump ("walk", "decl/walk.dlua") +M> ^D +~/src/metalua/src/sandbox$ cat decl/walk.dlua +module walk + debug; + module tags + module stat + Forin; + Do; + Set; + Fornum; + Invoke; + While; + Break; + Call; + Label; + Goto; + Local; + If; + Repeat; + Localrec; + Return; + end; + module expr + True; + String; + Index; + Paren; + Id; + False; + Invoke; + Function; + Op; + Number; + Table; + Dots; + Nil; + Stat; + Call; + end; + end; + expr_list; + binder_list; + guess; + expr; + block; + module traverse + expr; + block; + stat; + expr_list; + end; + stat; +end; + +INSTALL + +Metalint is a regular Metalua program, and relies on Metalua compilation +libraries. You must therefore have a working Metalua installation on your +system. You can run it with: "metalua metalint.mlua -- ". +For instance, to check metalint itself: + + ~/src/metalua/src/sandbox$ metalua metalint.mlua -- metalint.mlua + File metalint.mlua checked successfully + ~/src/metalua/src/sandbox$ + +You can also precompile it: + + ~/src/metalua/src/sandbox$ metalua metalint.mlua -s '#!/usr/bin/env lua' -o metalint + ~/src/metalua/src/sandbox$ ./metalint lint.mlua + File lint.mlua checked successfully + ~/src/metalua/src/sandbox$ + +Beware that even when precompiled, it still requires the Metalua runtime libs in LUA_PATH. + +']] + +-{ extension 'match' } +-{ extension 'log' } + +require 'strict' +require 'metalua.compiler' + +local VERBOSE = false +local KEEP_PRIVATE = false + +local function debug_print(...) + if VERBOSE then return printf(...) end +end + +-- Lexer -- +require 'lexer' + +decl_lexer = lexer.lexer:clone() +decl_lexer:add{ 'module', 'free', 'end', 'private' } + +-- Parser -- +require 'gg' + +-- Merge two decl together +local function merge (x, y) + --$log('merge', x, y) + for k, v in pairs (y) do + match x[k], v with + | `Free, _ | `Atom{x}, `Atom{x} -> -- pass + | _, `Free | nil, _ -> x[k] = v + | `Module{ _, mod_x }, `Module{ _, mod_y } -> merge (mod_x, mod_y) + + | _, _ -> + $log("Merge failure", x[k], v) + error ("Can't merge type elements") + end + end +end + +-- break mutual dependency between decl_elem_parser and decl_parser +local _decl_elem_parser = |...| decl_elem_parser(...) + +-- Parse a name, presented as an `Id or a `String +local function name(lx) + local a = lx:next() + if a.tag=='String' or a.tag=='Id' then return a[1] + else error("Name expected, got "..table.tostring(a,'nohash')) end +end + +function decl_builder(x) + --$log('decl_builder', x) + local r = { } + for y in ivalues(x) do + if y.tag ~= 'Private' then merge (r, {[y[1]]=y}) end + end + return r +end + +decl_parser = gg.list{ + gg.sequence{ _decl_elem_parser, gg.optkeyword ';', builder = |x|x[1] }, + terminators = 'end', builder = decl_builder } + +decl_elem_parser = gg.multisequence{ + { 'module', name, decl_parser, 'end', builder = |x| `Module{x[1], x[2]} }, + { 'free', name, builder = |x| `Free{x[1]} }, + { 'private', _decl_elem_parser, builder = |x| KEEP_PRIVATE and x[1] or `Private }, + default = gg.sequence{ name, builder = |x| `Atom{x[1]} } } + +function parse_decl_lib (libname) + debug_print ("Loading decl lib "..libname) + local fd, msg = package.findfile (libname, os.getenv 'LUA_DPATH' or "?.dlua") + if not fd then error ("Can't find declaration file for "..libname) end + local src = fd:read '*a' + fd:close() + return parse_decl_expr (src) +end + +function parse_decl_expr (src) + local lx = decl_lexer:newstream (src) + local r = decl_parser (lx) + --$log('result of parse_decl', r) + merge(DECLARATIONS, r) + return r +end + +function parse_decl_file (filename) + debug_print ("Loading decl file "..filename) + local src = mlc.luastring_of_luafile (filename) + return parse_decl_expr (src) +end + +-- AST checker -- + +require 'walk.id' + +DECLARATIONS = { } + +local cfg = { id = { }, stat = { }, expr = { } } + +local function dummy() + table.insert() +end + +function cfg.id.free(x, ...) + --$log('in free id walker', x) + local parents = {...} + local dic = DECLARATIONS + local name = x[1] + for p in ivalues (parents) do + local decl = dic[name] + if not decl then error("Not declared: "..name) end + match p with + | `Index{ _x, `String{n} } | `Invoke{ _x, `String{n}, ...} if _x==x -> + match decl with + | `Free{...} -> break + | `Atom{...} -> error (name.." is not a module") + | `Module{ _, dic2 } -> dic, name, x = dic2, n, p + end + | _ -> -- x == last checked variable + debug_print("Checked "..table.tostring(x, 'nohash')) + break + end + end +end + +--[[ +function cfg.id.free(x, ...) + --$log('in free id walker', x) + local parents = {...} + local dic = DECLARATIONS + local name = x[1] + for p in ivalues (parents) do + match p with + | `Index{ _x, `String{n} } | `Invoke{ _x, `String{n}, ...} if _x==x -> + if not dic then error ("No field declared in "..name) end + local decl = dic[name] + x=p + match decl with + | nil -> error ("Undeclared global "..name) + | `Free{...} -> break + | `Atom{...} -> dic=nil + | `Module{ _, dic2 } -> dic=dic2 + end + | _ -> -- x == last checked variable + debug_print("Checked ", table.tostring(x, 'nohash')) + break + end + end +end +--]] + +local function try_load_decl (kind, mod_name) + local success, msg = pcall(_G['parse_decl_'..kind], mod_name) + if not success then + debug_print("Warning, error when trying to load %s:\n%s", mod_name, msg) + end +end + +local function call_walker(x) + --$log('in call walker', x) + match x with + | `Call{ `Id 'require', `String{ mod_name } } -> + if not DECLARATIONS[mod_name] then try_load_decl('lib', mod_name) end + | `Module{ `Id 'module', _ } -> -- no package.seeall + DECLARATIONS = { } -- reset declarations + | _ -> -- pass + end +end + +cfg.expr.down = call_walker +cfg.stat.down = call_walker + +function check_src_file(name) + debug_print ("Checking file "..name) + local ast = mlc.ast_of_luafile (name) + --$log(ast,'nohash') + KEEP_PRIVATE = true + try_load_decl('lib', name:gsub("%.m?lua$", "")) + KEEP_PRIVATE = false + walk_id.block(cfg, ast) + printf("File %s checked successfully", name) +end + +-- RAM dumper -- + +function decl_dump(name, f) + match type(f) with + | 'nil' -> f=io.stdout + | 'string' -> f=io.open(f, 'w') or error ("Can't open file "..f) + | 'userdata' -> -- pass + | t -> error ("Invalid target file type "..t) + end + local indentation, acc, seen = 0, { }, { } + local function esc(n) + if n:gmatch "[%a_][%w_]*" and not decl_lexer.alpha[n] then return n else return '"'..n..'"' end + end + local function add_line(...) table.insert(acc, table.concat{' ':rep(indentation), ...}) end + local function rec(n, v) + if seen[v] then add_line ('free ', esc(n), ";") + elseif type(v)=='table' then + seen[v] = true + add_line ('module ', esc(n)) + indentation += 1 + for n2, v2 in pairs(v) do + if type(n2)=='string' then rec (n2, v2) end + end + indentation -= 1 + add_line 'end;' + else + add_line (esc(n), ';') + end + end + rec(name, _G[name]) + for line in ivalues (acc) do + f:write(line, '\n') + end + if f~=io.stdout then f:close() end +end + +-- options handling -- +require 'clopts' +local cl_parser = clopts { + { short = 'd', long = 'debug', type = 'boolean', + usage = 'print debug traces', action = function(x) VERBOSE=x end }, + { short = 'l', long = 'decl_lib', type = 'string*', + usage = 'load decl lib', action = parse_decl_lib }, + { short = 'f', long = 'decl_file', type = 'string*', + usage = 'load decl file', action = parse_decl_file }, + { short = 'x', long = 'decl_expr', type = 'string*', + usage = 'decl expression to eval', action = parse_decl_expr}, + check_src_file } + +try_load_decl('lib', 'base') +cl_parser (...) -- 2.44.0