]> git.lizzy.rs Git - metalua.git/commitdiff
introducing metalint
authorfft <fft@wipme.wm.corp.com>
Thu, 3 Apr 2008 19:06:30 +0000 (21:06 +0200)
committerfft <fft@wipme.wm.corp.com>
Thu, 3 Apr 2008 19:06:30 +0000 (21:06 +0200)
src/samples/metalint/INSTALL.TXT [new file with mode: 0644]
src/samples/metalint/LICENCE.TXT [new file with mode: 0644]
src/samples/metalint/README.TXT [new file with mode: 0644]
src/samples/metalint/dlua/base.dlua [new file with mode: 0644]
src/samples/metalint/dlua/metalua/compiler.dlua [new file with mode: 0644]
src/samples/metalint/dlua/walk.dlua [new file with mode: 0644]
src/samples/metalint/dlua/walk/id.dlua [new file with mode: 0644]
src/samples/metalint/metalint.dlua [new file with mode: 0644]
src/samples/metalint/metalint.mlua [new file with mode: 0644]

diff --git a/src/samples/metalint/INSTALL.TXT b/src/samples/metalint/INSTALL.TXT
new file mode 100644 (file)
index 0000000..600fdff
--- /dev/null
@@ -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 -- <metalint arguments>".
+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 (file)
index 0000000..41937b8
--- /dev/null
@@ -0,0 +1,27 @@
+Metalint
+
+Copyright (c) 2006-2008 Fabien Fleutot <metalua@gmail.com>
+
+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 (file)
index 0000000..45955ff
--- /dev/null
@@ -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      ::= <identifier> | <string>
+
+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 <metalua@gmail.com>
+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 (file)
index 0000000..c78c2d7
--- /dev/null
@@ -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 (file)
index 0000000..5a1b0fd
--- /dev/null
@@ -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 (file)
index 0000000..86939b8
--- /dev/null
@@ -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 (file)
index 0000000..7545b34
--- /dev/null
@@ -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 (file)
index 0000000..5f225d7
--- /dev/null
@@ -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 (file)
index 0000000..782f934
--- /dev/null
@@ -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      ::= <identifier> | <string>
+
+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 <metalua@gmail.com>
+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 -- <metalint arguments>".
+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 (...)