On Dec 14, 2007 12:31 AM, Alexander Gladysh &lt;<a href="mailto:agladysh@gmail.com">agladysh@gmail.com</a>&gt; wrote:<br><div class="gmail_quote"><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
Hi, all!</blockquote><div><br>Welcome on board :) <br></div><div>&nbsp;<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">OK, maybe metalua is not the right tool to do what I want, but (1)
<br>I&#39;ve finally got a slice of time to implement some offline global<br>protection for our project and (2) for a long time I wanted to learn<br>metalua -- so I want to give it a chance.</blockquote><div><br>Analyzing code is definitely half of metalua&#39;s purpose, and yes, analyzing without generating makes a lot of sense, even if that&#39;s not what&nbsp; most current samples are about (it tends to be less impressive from an outsider&#39;s point of view).
<br>&nbsp;<br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">By global(s) protection I mean some system for protection of global<br>environment. Lua distribution already provide `/etc/strict.lua&#39; as an
<br>example for runtime protection and `/test/global.lua&#39; as one for<br>offline static validation.</blockquote><div><br>So what you want is extracting the free variables (not bound as function param, for variable, local var etc), and check that they are authorized. This is a frequent need, and I was actually needing it for a hack, so I&#39;ve just written down that part of the code; feel free to share (consider it MIT license):
<br><br><font style="font-family: courier new,monospace;" size="1">require &#39;std&#39;<br>require &#39;walk&#39;<br><br>-{ extension &#39;match&#39; }<br><br>--------------------------------------------------------------------------------
<br>-- Scope handling: &#39;:push()&#39; saves the current scope, &#39;:pop()&#39; restores the<br>-- previously saved one. &#39;:add(identifiers_list)&#39; add identifiers to the current<br>-- scope. Current scope is stored in &#39;.current&#39;, as a string-&gt;boolean hashtable.
<br>--------------------------------------------------------------------------------<br><br>scope = { }<br>scope.__index = scope<br><br>function scope:new()<br>&nbsp;&nbsp; local ret = { current = { } }<br>&nbsp;&nbsp; ret.stack = { ret.current
 }<br>&nbsp;&nbsp; setmetatable (ret, self)<br>&nbsp;&nbsp; return ret<br>end<br><br>function scope:push()<br>&nbsp;&nbsp; self.current = table.shallow_copy (self.current)<br>&nbsp;&nbsp; table.insert (self.stack, self.current)<br>end<br><br>function scope:pop()
<br>&nbsp;&nbsp; self.current = table.remove (self.stack)<br>end<br><br>function scope:add (vars)<br>&nbsp;&nbsp; for id in values (vars) do<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match id with `Id{ x } -&gt; self.current[x] = true end<br>&nbsp;&nbsp; end<br>end<br><br>--------------------------------------------------------------------------------
<br>-- Return the string-&gt;boolean hash table of the names of all free variables<br>-- in &#39;term&#39;.<br>--------------------------------------------------------------------------------<br>function freevars (term)<br>
&nbsp;&nbsp; local freevars = { }<br>&nbsp;&nbsp; local scope&nbsp;&nbsp;&nbsp; = scope:new()<br>&nbsp;&nbsp; local cfg = { expr&nbsp; = { pred = { &quot;Function&quot;, &quot;Id&quot; } },<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stat&nbsp; = { pred = { &quot;Forin&quot;, &quot;Fornum&quot;, &quot;Local&quot;, &quot;Localrec&quot; } },
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; block = { pred = true } }<br><br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; -- Check identifiers; add functions parameters to scope<br>&nbsp;&nbsp; -----------------------------------------------------------------------------
<br>&nbsp;&nbsp; function cfg.expr.down(x)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match x with<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Id{ name } -&gt; if not scope.current[name] then freevars[name] = true end<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Function{ params, _ } -&gt; scope:push(); scope:add (params)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end
<br>&nbsp;&nbsp; end<br><br><br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; -- Close the function scope opened by &#39;down()&#39;<br>&nbsp;&nbsp; -----------------------------------------------------------------------------
<br>&nbsp;&nbsp; function cfg.expr.up(x)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match x with<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Function{ ... } -&gt; scope:pop()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Id{ ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; -- pass<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end<br>&nbsp;&nbsp; end<br><br>&nbsp;&nbsp; -----------------------------------------------------------------------------
<br>&nbsp;&nbsp; -- Create a new scope and register loop variable[s] in it<br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; function cfg.stat.down(x)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match x with<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Forin{ vars, ... }&nbsp;&nbsp;&nbsp; -&gt; scope:push(); scope:add(vars)
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Fornum{ var, ... }&nbsp;&nbsp;&nbsp; -&gt; scope:push(); scope:add{var}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Localrec{ vars, ... } -&gt; scope:add(vars)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Local{ ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; -- pass<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Repeat{ block, cond } -&gt; -- &#39;cond&#39; is in the scope of &#39;block&#39;
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scope:push()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for s in values (block) do walk.stat(cfg)(s) end -- no new scope<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; walk.expr(cfg)(cond)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scope:pop()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return &#39;break&#39; -- No automatic walking of subparts
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end<br>&nbsp;&nbsp; end<br><br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; -- Close the scopes opened by &#39;up()&#39;<br>&nbsp;&nbsp; -----------------------------------------------------------------------------
<br>&nbsp;&nbsp; function cfg.stat.up(x)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match x with<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Forin{ ... } | `Fornum{ ... } -&gt; scope:pop()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Local{ vars, ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; scope:add(vars)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Localrec{ ... }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt; -- pass
<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -- `Repeat never happens, it cancels the &#39;up()&#39; call by returning &#39;break&#39;.<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; end<br>&nbsp;&nbsp; end<br><br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; -- Create a separate scope for each block
<br>&nbsp;&nbsp; -----------------------------------------------------------------------------<br>&nbsp;&nbsp; function cfg.block.down() scope:push() end<br>&nbsp;&nbsp; function cfg.block.up()&nbsp;&nbsp; scope:pop()&nbsp; end<br><br>&nbsp;&nbsp; walk.block(cfg)(term)<br>&nbsp;&nbsp; return freevars
<br>end<br></font><br>It&#39;s based on:<br>- the &#39;match&#39; extension, which is the ultimate tool for easy AST manipulation. You really want to know this as soon as you want to do anything serious with tree-like structures.
<br>- walk, a library that generates AST reading and manipulation functions. <br><br>Both are currently under-documented, I&#39;ll try to fix that very fast (maybe a quick&amp;dirty blog post). Match is pretty simple to use, walk is much more tricky to master (but the problem it addresses is way tougher. Reliable code walker generation is still considered unsolved in Lisp). The latter is more or less documented through comments in the source, if it helps.
<br><br>For what you want to do, you might use freevars to get all free vars, then re-parse to extract all declarations, and finally compare both sets; or you can hack freevars to keep track of all declaration calls: `Call{ `Id &#39;declare&#39;, `String{ ? } }.
<br><br>You can use walk to find all declarations:<br><br><font style="font-family: courier new,monospace;" size="1">-{ extension &#39;match&#39; }<br><br>require &#39;mlc&#39;<br>require &#39;walk&#39;<br><br></font><font style="font-family: courier new,monospace;" size="1">
--------------------------------------------------------------------------------<br>
</font><font style="font-family: courier new,monospace;" size="1">-- Where declared vars will be accumulated<br></font><font style="font-family: courier new,monospace;" size="1">--------------------------------------------------------------------------------
<br>
</font><font style="font-family: courier new,monospace;" size="1">vars = { }<br><br></font><font style="font-family: courier new,monospace;" size="1">--------------------------------------------------------------------------------
<br>
</font><font style="font-family: courier new,monospace;" size="1">-- Walker config: catch `Call statements, and insert the declared<br>-- variable into table &#39;vars&#39;<br></font><font style="font-family: courier new,monospace;" size="1">
--------------------------------------------------------------------------------<br>
</font><font style="font-family: courier new,monospace;" size="1">cfg = { stat = { pred = &#39;Call&#39;;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; down = function (ast)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; match ast with<br>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | `Call{ `Id &#39;declare&#39;, `String{v} } -&gt; vars[v] = true
<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; | `Call{ `Id &#39;declare&#39;, ... } -&gt; error &quot;Invalid &#39;declare()&#39; form&quot;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; | _ -&gt; -- pass<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; end<br>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; end } }<br><br>
</font><font style="font-family: courier new,monospace;" size="1">--------------------------------------------------------------------------------<br>
</font><font style="font-family: courier new,monospace;" size="1">-- Walker itself<br></font><font style="font-family: courier new,monospace;" size="1">--------------------------------------------------------------------------------
<br>
</font><font style="font-family: courier new,monospace;" size="1">xtract_decl = walk.block(cfg)<br></font><br></div><div>(This approach doesn&#39;t take into account the case where a local variable &#39;declare&#39; shadows the declaration operator. I&#39;m not sure what you want to do in such a case; probably stop and return an error ?)
<br><br>You might want to do slightly more than that, though: when this code will find a call to, say, table.foreach(), it will check that &#39;table&#39; is declared (through &#39;declare()&#39; or in a constants list), but it won&#39;t check that &#39;foreach&#39; actually exists in module &#39;table&#39;. It would be interesting to handle that as well.
<br><br>From these bricks, I think you can design the exportation system you want. I&#39;d suggest using table.print() to save exported variable lists in transient files, so that you don&#39;t reparse your source files needlessly when you want to safely import the module&#39;s API.
<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex; font-family: courier new,monospace;"><font size="1">&nbsp; &nbsp; &nbsp;&nbsp; -{ block:<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
mlp.lexer:add { &quot;declare&quot; }<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mlp.expr:add {<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;name = &quot;global symbol declaration&quot;;<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&quot;declare&quot;, gg.optkeyword(&quot;(&quot;), mlp.string, gg.optkeyword(&quot;)&quot;) ;
<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;builder = function(symbol)<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;print(&quot;declare&quot;, symbol[2][1])<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;return +{ declare( -{symbol[2]} ) }<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;end<br> &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;}<br> &nbsp; &nbsp; &nbsp; &nbsp;}</font></blockquote><div>
<br>This approach is more complex, and harder to make robust, than necessary. As suggested above, since you don&#39;t actually want to change the syntax, you&#39;d better let the parser alone and analyze the resulting AST, which is more abstract and easier to manipulate.
<br>&nbsp;</div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">One thing that disturbs me is<br>that mlc compiles my parsed results into bytecode -- I do not need
<br>that.</blockquote><div><br>You&#39;re interested by mlc.ast_of_luafile (filename) or mlc.ast_of_string (source).<br><br></div><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
So, the question: would it work and does it worth it?<br></blockquote><div><br>It definitely does! And if you&#39;re allowed to contribute your work back to the community, it&#39;s likely to interest other people in similar situations as yours, and help disseminate metalua :)
<br><br>I&#39;m very interested by feedbacks, as always.<br><br>-- Fabien.<br></div></div>