Meta Lua

lua-users home
wiki

Metalua 是一个替代性的 Lua 编译器,支持编译时元编程和语法扩展。它还包含一个纯 Lua 库,用于将 Lua 源代码解析为抽象语法树 (AST)。

描述

Metalua 向后兼容 Lua 5.1。你可以动态地为语言添加新的惯用法,并提供元程序,即在编译器编译另一个文件时运行的程序,这些程序可以读取/修改/写入/操作正在编译的程序。

实用性

实际的兴趣在于你可以扩展语言,通常是通过从其他语言导入一个概念:你定义一种新语法,以及一个将这种新惯用法转化为有效(尽管可能更长、更难读、更难维护)的 Lua 程序的处理函数。例如,包括对各种 OO 风格的适当支持、ML 式的结构化模式匹配、契约式设计、静态或运行时类型检查、惰性求值、推导式列表生成等。

状态

Metalua 的开发仍处于 alpha 阶段。

抽象语法树

高效处理任何数据的秘诀在于在正确的抽象级别上表示它。程序可以以多种方式表示:作为 ASCII 源文件、标记流、抽象语法树、字节码、编译后的二进制文件……然而,共识是,对于自动程序处理,最实用的表示形式是抽象语法树 (AST)。由于人类用户更喜欢纯文本源,Metalua 允许你使用 AST 和纯文本,根据需要混合它们,并提供从一种格式到另一种格式的转换功能。

学习曲线

用 metalua 编写扩展并非易事:你需要了解 AST 结构,清楚地理解元级别是什么,并能轻松地在不同元级别之间切换而不感到困惑。显然,精通 Lua 是必须的。

然而,重要的是要认识到,尽管设计一个扩展通常并不容易,但使用一个设计良好的扩展应该是容易的。

这种设计与 Lua 的设计一致:即使是非开发者,编写基本程序也很容易。但是,如果你想做一些高级的事情,你需要掌握一些非平凡的概念,如协程、环境、元表、弱表、高阶函数等。Lua 没有竭力避免需要查看底层,而是将底层保持得干净整洁,以便高级用户可以自信地对其进行修改。请注意,虽然编写一个用户数据周围的正确 API 可能不容易,但如果设计得当,使用它应该很容易。

这种“比编写更容易使用”的方法也类似于 C++ 的模板,尽管声称 C++ 易于使用有点夸张:如果你真的这么认为,掌握 metalua 将真的感觉像在公园散步。

如果你觉得 metalua 的特殊之处是你扩展设计中最难的部分,那可能意味着你低估了实现正确扩展的复杂性,包括你正在编写的那个。

Metalua vs. Lisp

尽管 Metalua 在技术上允许编写大量小型临时宏的程序,就像 Lisp 中经常做的那样,但这并非预期用途,并且不被鼓励。尽管宏可能非常方便,尤其是在单人项目从头开始开发所有内容时,但为了可读性和互操作性付出的代价通常不值得。

Lisp 以不惜一切代价追求自由为价值,让用户几乎可以随心所欲地编写,而无需太多指导。如果你曾经需要处理一些不是由杰出黑客编写的代码,这可能会成为一个问题。在另一个极端,一些极其呆板的语言使用起来很痛苦,但却孕育了大量有用、健壮且维护良好的库。Metalua 的目标是:

示例

由于 Metalua 处理大量树,拥有类似 ML 的模式匹配非常方便。这种扩展在语言中可用,并允许编写如下内容。请注意源代码中出现的 `-{ extension 'foobar' }` 语句:它在编译器中加载扩展,通过列出它们,你就知道使用的是哪种精确的语言方言。你也不需要记住如何编译每个单独的源文件:这些信息都存储在其中。

----------------------------------------------------
-- Lambda-Calculus evaluator (weak head normal form)
----------------------------------------------------
-{ extension "match" }

function replace(var, newval, term)
   match term with
   | `Var{ v } if v==var       -> return newval
   | `Var{ _ }                 -> return term
   | `Lambda{ v, _ } if v==var -> return term
   | `Lambda{ v, b }           -> return Lambda{ v, replace(var, newval, b) }
   | `Apply{ f, x }            -> return `Apply{ replace(var, newval, f), replace(var, newval, x) }
   end
end

function reduce_whnf(term)
   match term with
   | `Apply{ `Lambda { param, body }, arg } ->
      local x = replace (param, arg, body)
      return reduce_whnf(x)
   | _ -> return term
   end
end

有趣的是,上面的例子与 OCaml(从中借用了模式匹配惯用法)非常相似。

(* Type declaration: OCaml is a statically typed language! *)
type term =
| Var    of string
| Apply  of term * term
| Lambda of string * term

let rec replace var newval term =
   match term with
   | Var(v) when v==var       -> newval
   | Var(_)                   -> term
   | Lambda(v, _) when v==var -> term
   | Lambda(v, b)             -> Lambda(v, replace var newval b)
   | Apply(f, x)              -> Apply(replace var newval f, replace var newval x)

let rec reduce_whnf term =
   match term with
   | Apply(Lambda(param, body), arg) ->
     let x = replace param arg body in
     reduce_whnf x
   | _ -> term

最后一个例子将展示一个扩展实现的样子。我们将引入经常被要求的三个选择运算符,也称为 C 语言的“?:”条件运算符。由于 Lua 已经使用“:”进行方法调用,我们将它设为“bool ? foo, bar”而不是“bool ? foo : bar”。一个正确的(尽管效率不高)的编码方式是将其放入一个函数:“(function() if bool then return foo else return bar end end)()”。这被实现为

local function t_builder(bool, suffix)
   local foo, bar = unpack (suffix)
   return +{ (function() if -{bool} then -{foo} else -{bar} end end)() }
end

mlp.expr.suffix:add{ "?", mlp.expr, "," mlp.expr, builder=t_builder }

(为记录在案,一个更灵活且效率更高的实现可以在 [编译器中] 找到)。

作者

FabienFleutot

使用 Metalua 的相关项目

链接


RecentChanges · preferences
编辑 · 历史
最后编辑于 2023 年 3 月 1 日 上午 11:36 GMT (差异)