Lua Fish

lua-users home
wiki

LuaFish 提供了各种 Lua 模块,用于通过 LuaPeg 解析 Lua 5.1 源代码为抽象语法树 (AST),并将 AST 序列化回 Lua 代码。它还对 LISP 式宏、静态类型检查和将 Lua 编译为 C 提供了实验性支持。

描述

LuaFish 在应用上类似于 Metalua [1]、Cheese [2] 和 LuaParse [3],但它基于 LPeg [4]。该项目将来可能会与 Metalua 合并,尽管 Metalua 基于 gg。一个非常类似的项目是 [Leg] [5]

宏处理提供了一些有趣的功能,例如静态类型检查和代码分析。宏在编译时对 AST 进行操作,以将类型(或元表)与词法相关联。宏使用标准的 Lua 函数调用语法,因此语法没有改变,只有语义改变。

状态 - 警告

LuaFish 的源代码分析部分在很大程度上被 LuaInspect 取代。不过,LuaFish 仍然可以用作基于 LuaPeg 的解析器。

解析、AST 操作和序列化相当健壮,但仍然可能存在错误,并且接口可能会发生变化。AST 格式应该与 [Metalua AST 格式](减去 lineinfo,它很可能在 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 (差异)