Lua Fish

lua-users home
wiki

LuaFish 透過 LuaPeg 提供了各式各樣的 Lua 模組,以便將 Lua 5.1 原始碼剖析至抽象語法樹 (AST),並將 AST 序列化回 Lua 程式碼。它同時也支援 LISP 式樣巨集、靜態類型檢查,以及將 Lua 編譯至 C。

說明

LuaFish 的應用與 [1] 的 Metalua、[2] 的 Cheese 以及 [3] 的 LuaParse 相似,但基礎則是 LPeg [4]。此專案有可能在未來與 Metalua 合併,儘管 Metalua 是基於 gg。一個非常類似的專案是 [Leg] [5]

巨集處理提供了許多有趣的功能,像是靜態類型檢查及程式碼分析。這些巨集在 AST 上以編譯時間運作,以將類型 (或元資料) 與字典關聯。巨集使用標準 Lua 函式呼叫語法,因此不會改變文法,只會改變語意。

狀態 - 警告

LuaFish 的原始碼分析部分在很大程度上已被 LuaInspect 取代。不過,LuaFish 仍然可以當作基於 LuaPeg 的剖析器。

剖析、AST 操作以及序列化相當穩健,但仍可能會有錯誤,且介面可能會變更。AST 格式應與 [Metalua AST 格式] 同步 (不含 metalua 可能會變更/移除的行號資訊)。

巨集、靜態類型檢查以及 Lua->C 編譯器在多個區域中並不完整或有損壞,應視為實驗性質。事實上,它們可能已不再維護。請參閱 LuaInspect 以取得較新的資訊。

請向此 wiki 頁面回報任何錯誤或錯誤修正。

下載檔案

作者

DavidManura

範例

範例:將 Lua 轉換為 AST

下列檔案的 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

可以將靜態類型描述視為附加至詞彙並在編譯時操作的元表。

使用巨集的另一種方法是將啟用巨集的程式碼放置在個別的檔案中,並使用啟用巨集版本的 requiredofile

範例:Lua->C 編譯器

此 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;
}

如需更多範例和詳細資料,請參閱發行版範例和原始程式碼。

另請參閱

LuaFish 原始碼分析 - 第二次(也稱為 LuaAnalyze)

以下為根據從 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 取代。

另請參閱


近期變更 · 偏好設定
編輯 · 歷史記錄
最後編輯於 2010 年 9 月 22 日,上午 6:28 GMT (diff)