Lua Fish |
|
Fish 透過 LuaPeg 提供了各式各樣的 Lua 模組,以便將 Lua 5.1 原始碼剖析至抽象語法樹 (AST),並將 AST 序列化回 Lua 程式碼。它同時也支援 LISP 式樣巨集、靜態類型檢查,以及將 Lua 編譯至 C。
LuaFish 的應用與 [1] 的 Metalua、[2] 的 Cheese 以及 [3] 的 Lua
Parse 相似,但基礎則是 LPeg [4]。此專案有可能在未來與 Metalua 合併,儘管 Metalua 是基於 gg。一個非常類似的專案是 [Leg] [5]。
巨集處理提供了許多有趣的功能,像是靜態類型檢查及程式碼分析。這些巨集在 AST 上以編譯時間運作,以將類型 (或元資料) 與字典關聯。巨集使用標準 Lua 函式呼叫語法,因此不會改變文法,只會改變語意。
LuaFish 的原始碼分析部分在很大程度上已被 LuaInspect 取代。不過,LuaFish 仍然可以當作基於 LuaPeg 的剖析器。
剖析、AST 操作以及序列化相當穩健,但仍可能會有錯誤,且介面可能會變更。AST 格式應與 [Metalua AST 格式] 同步 (不含 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 取代。