Added a sample (global variables detection)
diff --git a/samples/globals.lua b/samples/globals.lua
new file mode 100644
index 0000000..94773fd
--- /dev/null
+++ b/samples/globals.lua
@@ -0,0 +1,137 @@
+-------------------------------------------------------------------------------
+-- Copyright (c) 2006-2014 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
+--
+-------------------------------------------------------------------------------
+
+--
+-- Example of treequery usage: list all the unauthorized global
+-- variable occurrences in a given source file.
+--
+-- Usage: lua globals.lua file_to_analyze.lua
+--
+-- TODO:
+-- * take command-line options
+-- *
+
+require 'metalua.loader' -- needed to load "metalua/treequery.mlua"
+
+mlc = require 'metalua.compiler'.new() -- compiler
+Q = require 'metalua.treequery' -- AST queries
+pp = require 'metalua.pprint' -- pretty-printer
+srcfile = assert(..., "no file name") -- filename to analyze...
+ast = mlc :srcfile_to_ast(srcfile) -- ...converted in an AST
+
+-- Global variable names which won't be reported:
+
+allowed = [[ _G _VERSION assert collectgarbage coroutine
+coroutine.create coroutine.resume coroutine.running coroutine.status
+coroutine.wrap coroutine.yield debug debug.debug debug.getfenv
+debug.gethook debug.getinfo debug.getlocal debug.getmetatable
+debug.getregistry debug.getupvalue debug.setfenv debug.sethook
+debug.setlocal debug.setmetatable debug.setupvalue debug.traceback
+dofile error gcinfo getfenv getmetatable io io.close io.flush io.input
+io.lines io.open io.output io.popen io.read io.stderr io.stdin
+io.stdout io.tmpfile io.type io.write ipairs list load loadfile
+loadstring math math.abs math.acos math.asin math.atan math.atan2
+math.ceil math.cos math.cosh math.deg math.exp math.floor math.fmod
+math.frexp math.huge math.ldexp math.log math.log10 math.max math.min
+math.mod math.modf math.pi math.pow math.rad math.random
+math.randomseed math.sin math.sinh math.sqrt math.tan math.tanh module
+newproxy next os os.clock os.date os.difftime os.execute os.exit
+os.getenv os.remove os.rename os.setlocale os.time os.tmpname package
+package.config package.cpath package.loaded package.loaders
+package.loadlib package.path package.preload package.seeall pairs
+pcall print rawequal rawget rawset require select setfenv setmetatable
+string string.byte string.char string.dump string.find string.format
+string.gfind string.gmatch string.gsub string.len string.lower
+string.match string.rep string.reverse string.sub string.upper table
+table.concat table.foreach table.foreachi table.getn table.insert
+table.maxn table.remove table.setn table.sort tonumber tostring type
+unpack xpcall ]]
+
+
+-- The same, put in a hash-table for easy retrieval:
+allowed_set = { }
+for word in allowed :gmatch "%S+" do allowed_set[word]=true end
+
+function analyze(ast)
+ local globals = { } -- variable name -> list of line numbers
+
+ -- add the line number where node `id` occurs into table `globals`:
+ local function log_global(id, ...)
+ local name = id[1]
+ local line = id.lineinfo and id.lineinfo.first.line or "no info"
+ local full_chain = {[0]=id, ...}
+ -- Retrieve the subfields x.subfield_1.....subfield_n
+ for i, ancestor in ipairs(full_chain) do
+ -- `Index{ x, `String y } if x==full_chain[i-1]
+ if ancestor.tag=='Index' and
+ ancestor[1]==full_chain[i-1] and
+ ancestor[2].tag=='String' then
+ name = name.."."..ancestor[2][1]
+ else break end
+ end
+ local lines = globals[name]
+ if lines then table.insert(lines, line)
+ else globals[name] = { line } end
+ end
+
+ -- Treequery command to retrieve global variable occurrences:
+ Q(ast) -- globals are...
+ :filter 'Id' -- ...nodes with tag 'Id'...
+ :filter_not (Q.is_binder) -- ...which are not local binders...
+ :filter_not (Q.get_binder) -- ...and aren't bound;
+ :foreach (log_global) -- log each of them in `globals`.
+
+ -- format and sort the result
+ local sort_them = { }
+ for name, lines in pairs(globals) do
+ if not allowed_set[name] then
+ local line = name .. ": " .. pp.tostring(lines) :sub (2, -2)
+ table.insert(sort_them, line)
+ end
+ end
+
+ -- print the result
+ if next(sort_them) then
+ table.sort(sort_them)
+ print(table.concat(sort_them, "\n"))
+ else print "no global found" end
+end
+
+function list()
+ local banned = { ['package.loaded'] = true }
+ local acc = { }
+ local seen = { }
+ local function rec(prefix, t)
+ if seen[t] then return end
+ seen[t]=true
+ for k, v in pairs(t) do
+ if type(k)=='string' then
+ local path = prefix and prefix..'.'..k or k
+ table.insert(acc, path)
+ if type(v)=='table' and not banned[path] then rec(path, v) end
+ end
+ end
+ end
+ rec(nil, _G)
+ table.sort(acc)
+ return table.concat(acc, ' ')
+end
+
+analyze(ast)
+