Lua Fish |
|
Fish 提供了各种 Lua 模块,用于通过 LuaPeg 解析 Lua 5.1 源代码为抽象语法树 (AST),并将 AST 序列化回 Lua 代码。它还对 LISP 式宏、静态类型检查和将 Lua 编译为 C 提供了实验性支持。
LuaFish 在应用上类似于 Metalua [1]、Cheese [2] 和 Lua
Parse [3],但它基于 LPeg [4]。该项目将来可能会与 Metalua 合并,尽管 Metalua 基于 gg。一个非常类似的项目是 [Leg] [5]。
宏处理提供了一些有趣的功能,例如静态类型检查和代码分析。宏在编译时对 AST 进行操作,以将类型(或元表)与词法相关联。宏使用标准的 Lua 函数调用语法,因此语法没有改变,只有语义改变。
LuaFish 的源代码分析部分在很大程度上被 LuaInspect 取代。不过,LuaFish 仍然可以用作基于 LuaPeg 的解析器。
解析、AST 操作和序列化相当健壮,但仍然可能存在错误,并且接口可能会发生变化。AST 格式应该与 [Metalua AST 格式](减去 lineinfo,它很可能在 Metalua 中被更改/删除)保持同步。
宏、静态类型检查和 Lua->C 编译器在各个方面都不完整或存在问题,应被视为实验性的。事实上,它们可能不再维护。有关更新内容,请参阅 LuaInspect。
请将错误或错误修复报告到此 wiki 页面。
以下文件的 AST
-- example for Lua->C compiler. local x,y = 4,5 x = x + 1 local function f(x) return x * x end x = f(x) print(x)
可以快速显示如下
$ lua bin/luafish.lua -a examples/1.lua {tag="Block",{tag="Local",{tag="NameList",{tag="Id","x"},{tag="Id","y"}},{tag="E xpList",{tag="Number",4},{tag="Number",5} } },{tag="Assign",{tag="VarList",{tag="I d","x"} },{tag="ExpList",{tag="Op","+",{tag="Id","x"},{tag="Number",1} } } },{tag="L ocalFunctionDef",{tag="Id","f"},{tag="NameList",{tag="Id","x"}},{tag="Block",{ta g="Return",{tag="ExpList",{tag="Op","*",{tag="Id","x"},{tag="Id","x"} } } } } },{tag= "Assign",{tag="VarList",{tag="Id","x"}},{tag="ExpList",{tag="Call",{tag="Id","f" },{tag="ExpList",{tag="Id","x"} } } } },{tag="Call",{tag="Id","print"},{tag="ExpList ",{tag="Id","x"} } } }
此示例演示了一些静态类型检查功能。类型在编译时通过 TYPED/NUMBER/STRING 宏或自动推断绑定到词法。类型在编译时进行检查,常量表达式在编译时进行评估。REQUIRE 宏提供编译时宏和类型导入,并在运行时执行常规的 require
。所有全局变量通过 NOGLOBALS 宏禁用(除了通过 REQUIRE 绑定到词法的变量),因此全局变量访问(包括拼写错误的词法)会触发编译时错误。
-- type_usage2.lua -- LuaFish static type checking example. -- Using math library. -- Requires LuaFish 0.4. -- -- Note: CAPS identifiers are typically macros. -- Compile-time import of static typing macros NUMBER, STRING, and TYPED REQUIRE 'luafish.type' -- disable global variable usage NOGLOBALS() -- Compile-time import of static type definitions for standard modules. local math = REQUIRE 'math' local _G = REQUIRE '_G' local print = _G.print -- False conditional demonstrates that static type checking is done -- at compile-time. if false then print(math.sqrt) -- ok --print(math.asdf) -- compile error: asdf not in math --print(math.sqrt('one')) -- compile error: arg must be number -- print(math.sqrt(2,3)) -- compile error: num args -- print(math.sqrt(-1)) -- compile error: arg must be non-negative print(math.sqrt(2)) -- ok local x = 2 -- weak, implicit type Number --x() -- compiler error: not callable x = print() -- implicit type not unknown after calling unknown function x() -- ok now -- Note: compare this to the above. local x = TYPED(-3) -- bind strong, implicit type to lexical --local x = NUMBER(-3) -- alternate form with explicit type --x() -- compile error: not callable x = print() -- does not modify strong type --x() -- compile error: not callable local x = -3 --print(math.sqrt(x)) -- compile error: arg must be non-negative x = x + 2 --print(math.sqrt(x)) -- compile error: arg must be non-negative x = x + 1 print(math.sqrt(x)) -- ok --math.sqrt(math.sin(-math.pi/2)) -- compile error: arg must be non-negative local x = STRING(print()) -- bind string type, unknown value f() x = 5 -- doesn't affect strong type -- TODO: we could guard against such assignment. --print(math.sqrt(x)) -- compile error: arg must be number local sqrt = math.sqrt -- print(sqrt(-2)) -- compile error: arg must be non-negative local sqrt = TYPED(math.sqrt) -- print(sqrt(-2)) -- compile error: arg must be non-negative end print 'type_usage2.lua : done'
这是另一个使用模块的实验性宏处理功能的示例。这使用了一种处理宏的样式。
-- module_usage2.lua -- LuaFish example that tests square2.lua module. -- It uses both the static type and runtime definition -- in square2.lua. print 'DEBUG:main:begin compiletime' -- trace -- TSquare is the static type of square2. local TSquare = require "square2" -- This compiles and executes the given code string. -- During compilation, the SQUARE macro is evaluated. -- The SQUARE macro is defined as TSquare.bind, which -- binds the given lexical to the TSquare static type -- and returns an empty code block that replaces the macro -- in the AST. The code is then statically checked -- against the bound static types. Finally, the code -- is executed. require "luafish.staticmodule" { SQUARE = TSquare.bind, ISSQUARE = TSquare.isa } [[ print 'DEBUG:main:end compiletime' print 'DEBUG:main:begin runtime' -- Load run-time behavior of square2. local Square = require "square2" . class -- Create instance. Assign to lexical. -- Bind static-type to lexical. local m = Square.create(5); SQUARE(m) -- This demonstrates that even though the following code is -- not executed at run-time, it is still compile-time checked. if false then m:setcolor('blue') -- ok local a = m.hello -- compile error (field name) local b = m.setcolor(m,'blue') -- ok local b = m.setcolor(m,5) -- compile error (arg type) local b = m.setcolor(m,5,6) -- compile error (num args) local b = (m * 2):area(1) -- compile error (num args) -- local a = false + false -- compile error (op not defined) local a = false and true -- ok local a = 5 + 3^3 -- ok end print 'DEBUG:main:end runtime' ]] --[[OUTPUT: DEBUG:main:begin compiletime DEBUG:square2:begin compiletime DEBUG:square2:end compiletime DEBUG:square2:begin runtime DEBUG:square2:end runtime static __index [TSquare Class] setcolor static call {"Id","m"} {"String","blue"} static __index [TSquare Class] hello ERROR: hello not in [TSquare Class] static __index [TSquare Class] setcolor static call {"Id","m"} {"String","blue"} static __index [TSquare Class] setcolor static call {"Id","m"} {"Number",5} ERROR: second param must be string static __index [TSquare Class] setcolor static call {"Id","m"} {"Number",5} {"Number",6} ERROR: second param must be string ERROR: expected two arguments static __mul [TSquare Class] table: 0127EE68 ERROR: first op must be TSquare static __index [TSquare Class] area static call {"Parens",{"*",{"Id","m"},{"Number",2} } } {"Number",1} ERROR: expected zero arguments DEBUG:main:end compiletime DEBUG:main:begin runtime DEBUG:main:end runtime --]]
其中模块定义为
-- square2.lua -- LuaFish example of a module that indirectly -- contains macros. Contains both -- static type check and run-time behavior. -- Static type definition. local TSquare = {}; do print 'DEBUG:square2:begin compiletime' -- trace local Macro = require "luafish.macro" -- Helper functions. local report = function(...) print('ERROR:', ...) end local check = function(test,message) if not test then report(message) else return true end end setmetatable(TSquare, { __tostring = function() return '[TSquare Class]' end }) -- bind lexical to this type. function TSquare.bind(obj_ast) obj_ast.stype = TSquare end -- tests if expression is of this type function TSquare.isa(obj_ast) return 'value', obj_ast.stype == TSquare end local is_method = {area=true,perimeter=true,setcolor=true} function TSquare:__index(k) print('static __index', self, k) if not is_method[k] then report(tostring(k) .. ' not in ' .. tostring(TSquare)) end if k == 'setcolor' then return function(self, o, ...) print('static call', self, o, ...) check(self.stype == TSquare, 'first param must be TSquare') check(Macro.TString.isa(o.stype), 'second param must be string') if select('#', ...) ~= 0 then report('expected two arguments') end end else return function(self, ...) print('static call', self, ...) if select('#', ...) ~= 0 then report('expected zero arguments') end end end end function TSquare:__mul(other) print('static __mul', self, other) if not (check(stype == TSquare, 'first op must be TSquare') or check(Macro.TNumber.isa(other), 'second op must be number')) then return end return TSquare end print 'DEBUG:square2:end compiletime' end -- Run-time behavior. TSquare.class = require "luafish.staticmodule" {} [[ print 'DEBUG:square2:begin runtime' local Square = {} Square.__index = Square function Square.create(length) return setmetatable({length=length}, Square) end function Square:area(length) return self.length^2 end function Square:perimeter(length) return self.length*4 end function Square:setcolor(color) self.color = color end function Square:__mul(other, val) return Square.create(self.length * val) end print 'DEBUG:square2:end runtime' return Square ]] return TSquare
您可以将静态类型描述视为附加到词法并在编译时操作的元表。
另一种使用宏的方法是将启用宏的代码放在单独的文件中,并使用 require
或 dofile
的替换启用宏的版本。
此 Lua->C 编译器非常初步,并做出了许多假设。它更像是一个原型。如果它不能确保编译为等效的 C,它应该进行更多检查并触发错误。
$ lua 1.lua 25 $ lua lib/luafish/lua2c.lua 1.lua | gcc -xc - $ ./a.out 25.000000 -- input: 1.lua -- local x,y = 4,5 x = x + 1 local function f(x) return x * x end x = f(x) print(x) -- output: 1.c -- #include <stdio.h> double f(double x) { return x * x; } int main() { double x = 4; double y = 5; x = x + 1; x = f(x); printf("%f\n", x); return 0; }
有关更多示例和详细信息,请参阅发行版示例和源代码。
Fish 源代码分析 - 再来一次(又名 Lua
Analyze)这是基于从 LuaFish 工作中学习的一些原则重新设计的源代码分析器的预览(警告:alpha 版本):[luaanalyze-20080925b.tar.gz]
新代码试图使设计更实用。它还使用 gg/mlp(来自 Metalua)而不是 LPeg。示例文件
-- examples/ex1.lua do --! typeimport('luaanalyze.library.standard') --! typematch('idx$', 'luaanalyze.type.number') --! checkglobals() --! checktypes() for my_idx=1,10 do local s = string local f = s.format --print(f(my_idx)) -- fails: got number expected string --print(myy_idx) -- fails: undefined global end end print(myy_idx) -- no error
要检查,请运行“lua luaanalyze.lua examples/ex1.lua
”。以“!”开头的注释会被源代码分析器解释。关于上面的例子,有很多有趣的事情需要说明(稍后会详细介绍)。
警告:luaanalyze 已被 LuaInspect 取代。