题叶
  • 16.9k

在 Julia 当中实现 Cirru 解释器的初步想法

 阅读约 9 分钟

文章内容所描述的方案, 已经实现了一个原型, 有时间我继续改进
https://github.com/Cirru/CirruSepal.jl


昨天晚上不知怎么想起来 Julia, 翻了翻文档, 又有发现,
就是 Julia 有出色的元编程能力, 可以在执行过程中拼接 AST 然后执行
http://julia.readthedocs.org/en/latest/manual/metaprogramming/
比如说文档里给出了这样一些例子:

textjulia> ex2 = Expr(:call, :+, 1, 1)
:(1 + 1)

元编程

大致梳理一下, 我现在了解到的就是几个方面,

  • Symbol 类型

Symbol 是和 String 不一样的数据类型, 两者共同点是数据不可修改
两者差别需要看网上的说明, 但是 Symbol 是可以用于生成 AST 的:
http://stackoverflow.com/questions/23480722/what-is-a-symbol-in-julia/...
或者说 Symbol 可以用于表示程序自身, 比如说这样的代码:

textjulia> a = 1
1

julia> eval(a)
1

julia> eval(:a)
1
  • Quoting

Quote 可以得到表达式的 expr 对象, 对应表达式的 AST.
这些跟 Lisp 当中的 Quote 跟 Symbol 大概非常相似吧
只不过在 Julia 同时又有语法糖, 所以看起来怪怪的

textjulia> a = Expr(:call, :+, 1, 2)
:(1 + 2)

julia> b = :(1 + 2)
:(1 + 2)

julia> a == b
true

julia> eval(a)
3

julia> eval(b)
3
  • parse 函数

parse 可以将一段字符串解析成 AST, 或者也能看出了就是 Quote:

textjulia> a = :(1 + 2)
:(1 + 2)

julia> c = parse("1 + 2")
:(1 + 2)

julia> a == c
true

然后可以打印具体的结构, 用文档例子里的 dump 函数:

julia> dump(c)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
  typ: Any

以及通过 eval 可以将上边得到的 AST 执行:
http://julia.readthedocs.org/en/latest/stdlib/base/#Base.eval

julia> eval(c)
3

思路

把上述几个流程链接到一起, 就也办法在 Julia 当中自己生成 AST 去执行

先查看正常的代码如何编写, 了解 AST 的结构:

julia> a = parse("1 + 2")
:(1 + 2)

julia> b = dump(a)
Expr
  head: Symbol call
  args: Array(Any,(3,))
    1: Symbol +
    2: Int64 1
    3: Int64 2
  typ: Any

然后用 Expr 自己构造一份 AST, 执行一下:

julia> c = Expr(:call, :+,  1, 2)
:(1 + 2)

julia> d = eval(c)
3

而且可以下有个 Julia 自身实现的 parser, 其中有不少 AST 的例子
https://github.com/jakebolewski/JuliaParser.jl/blob/master/src/parser....

另外按照元编程文档开头讲的, Julia 的 AST 跟 Lisp 很像, 都是表达式:

Like Lisp, Julia represents its own code as a data structure of the language itself. Since code is represented by objects that can be created and manipulated from within the language, it is possible for a program to transform and generate its own code. This allows sophisticated code generation without extra build steps, and also allows true Lisp-style macros operating at the level of abstract syntax trees.

就是说不像是 JavaScript AST 存在 Statement 那样奇葩的结构,
而是在一层包裹下, 一切都是表达式, 能够实现自由组合, 看看分号分隔的语句:

julia> a = parse("1 + 2; 2 + 3")
:($(Expr(:toplevel, :(1 + 2), :(2 + 3))))

julia> dump(a)
Expr
  head: Symbol toplevel
  args: Array(Any,(2,))
    1: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol +
        2: Int64 1
        3: Int64 2
      typ: Any
    2: Expr
      head: Symbol call
      args: Array(Any,(3,))
        1: Symbol +
        2: Int64 2
        3: Int64 3
      typ: Any
  typ: Any

总体上是一个嵌套表达式的结构, 也就是 Cirru 所模仿的 Lisp 的形态
因此 Cirru 的语法是个 Julia 的 AST 结构对应的,
就有可能达到编写 Cirru 脚本, 直接替换为 Julia AST 执行

前面已经说过, Julia 可以在运行过程中生成 LLVM IR 的
就是说实现的话, Cirru 的解释器就算跑在 LLVM IR 上边了
不知道性能如何, 但是有 Julia 做中间层优化, 效果呢应该不会太差
之前 Sepal 项目直接放弃了, 这个地方倒是有一线希望

另一个以后需要考虑的是多个文件组成模块系统的问题,
现在可以先不考虑的吧..

实现问题

Cirru 在 JavaScript 当中是先转化成 JSON 对象, 然后操作的
Julia 当然少不了操作 JSON 的库, 我最初的思路是从 JSON 开始
https://github.com/JuliaLang/JSON.jl

不过这地方有棘手的问题, Cirru 的 JSON 结构是嵌套的数组,
而 Jualia 当中, 分成了不同的 Array 跟 Tuple 两种结构,
http://en.wikibooks.org/wiki/Introducing_Julia/Arrays_and_tuples
Array 限定了元素类型一致, 可以不断增长, 而 Tuple 允许多种类型, 但不能增长
这个类似 Haskell, Haskell 的 Cirru Parser 用代数类型系统解决了,
因为就是说 Haskell 定义类型系统支持递归, 就能表示 Cirru 的树
但是 Julia 我似乎没找到递归类型.. 那么 JSON 还能用么?
极端毕竟注重性能的语言, 很难像 JavaScript 跟 Haskell 这么潇洒...

另外我也尝试考虑直接解析 Cirru 代码生成 Quote 的可能,
目前 Cirru 解析分成两步,

  1. 生成基于缩进跟括号的树, 其中还没有出来 $, 形成的转换
  2. 识别 $, 对树进行 desuger 的转换

原来 parser 的实现极大地借助了 JSON 自由的结构,
这里直接生成 Quote 似乎也是不可行的, 中间过程还是受到 Array 和 Tuple 限制.

其他的考虑, 还有其他一些 Lisp LLVM 实现, 我在 Google 随手找到的:
https://github.com/drmeister/clasp
https://github.com/artagnon/rhine-ml
https://github.com/mylesmegyesi/lisp-compiler-llvm
https://github.com/eudoxia0/corvus
一般 Lisp 会有直接操作 AST 的功能, 也就是 Quote
至少从原理上, Cirru 转化成 Quote, 然后 eval, 这是行得通的
但考虑到我对 Lisp 本身不够深入, 短时间是没法看了
不过, 从成熟度上, 我想要 Cirru 生成 LLVM IR 玩, 还是 Julia 吧..

总之就是还没找到具体实现的办法.


看了份中文的笔记, 发现可以定义任意类型的数组... 得研究下
http://www.justinablog.com/archives/1604

julia> {"1", {"2", "3", {"4"}}}
2-element Array{Any,1}:
 "1"
 {"2","3",{"4"}}
阅读 2.5k更新于 2015-04-19
推荐阅读
题叶
用户专栏

ClojureScript 爱好者.

487 人关注
242 篇文章
专栏主页
目录