文章内容所描述的方案, 已经实现了一个原型, 有时间我继续改进
https://github.com/Cirru/CirruSepal.jl
昨天晚上不知怎么想起来 Julia, 翻了翻文档, 又有发现,
就是 Julia 有出色的元编程能力, 可以在执行过程中拼接 AST 然后执行
http://julia.readthedocs.org/en/latest/manual/metaprogramming/
比如说文档里给出了这样一些例子:
text
julia> ex2 = Expr(:call, :+, 1, 1) :(1 + 1)
元编程
大致梳理一下, 我现在了解到的就是几个方面,
- Symbol 类型
Symbol 是和 String 不一样的数据类型, 两者共同点是数据不可修改
两者差别需要看网上的说明, 但是 Symbol 是可以用于生成 AST 的:
http://stackoverflow.com/questions/23480722/what-is-a-symbol-in-julia/...
或者说 Symbol 可以用于表示程序自身, 比如说这样的代码:
text
julia> a = 1 1 julia> eval(a) 1 julia> eval(:a) 1
- Quoting
Quote 可以得到表达式的 expr
对象, 对应表达式的 AST.
这些跟 Lisp 当中的 Quote 跟 Symbol 大概非常相似吧
只不过在 Julia 同时又有语法糖, 所以看起来怪怪的
text
julia> 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:
text
julia> 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 解析分成两步,
- 生成基于缩进跟括号的树, 其中还没有出来
$
和,
形成的转换 - 识别
$
和,
对树进行 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"}}