Ruby Ruby 的 sexp 宏

mizuhashi · 2016年04月13日 · 最后由 mizuhashi 回复于 2016年04月19日 · 3951 次阅读
require 'ruby_parser'
require 'ruby2ruby'
parser = RubyParser.new
r2r = Ruby2Ruby.new

def recur arr
  if arr.is_a? Array
    arr.map do |v|
      pattern = recognize v
      new_v = transform pattern, recur(v)
    end
  else
    arr
  end
end

def recognize v
  case
  when Array === v && v.size == 3 && v[0..1] == [:call, nil] && Symbol === v[2]
    :iden
  when Array === v && v.size == 2 && v[0] == :lit
    :lit
  end
end

def transform pattern, v
  case pattern
  when :iden
    [v[0], [:const, :Var], v[2]]
  when :lit
    [:call, nil, :Lit, v]
  else
    v
  end
end

# 标识符a转换为Var.a
code = recur parser.process('a + b').to_a
p r2r.process Sexp.from_array(code) # => "(Var.a + Var.b)"

# 字面量:a转换为Lit(:a)
code2 = recur parser.process(':a + b * (3 / 0)').to_a
p r2r.process Sexp.from_array(code2) # => "(Lit(:a) + (Var.b * (Lit(3) / Lit(0))))"

# 比较以下两者
p parser.process('a + b').to_a # => [:call, [:call, nil, :a], :+, [:call, nil, :b]]
p parser.process('Var.a + Var.b').to_a # => [:call, [:call, [:const, :Var], :a], :+, [:call, [:const, :Var], :b]]

所谓宏就是代码替换,例如 C 的宏是在字符串的层面上替换,但是这样坑比较多,安全的做法是在抽象语法树上做替换。

s-expression(S 表达式)是一种语法树表示法,同时也是 lisp 代码的形式,所以写 lisp 并不是写字符串,而是在用节点构造一棵语法树。这样的好处是“代码即数据”,也就是对于 lisp 而言,一段代码是像数据一样很容易被编辑的。1 _+ 2 + 3这样的代码翻译成 lisp 是 (+ (_+ 1 2) 3),如果我想把+方法替换成*,字符串替换会变成1 _* 2 * 3,而 lisp 可以明确地代换掉第一个节点的+

然而 sexp 并不是 lisp 独有的,因为他只是一种表示法,所以任何语言经过 parse 之后都可以变成 sexp(lisp 是不用 parse 的,可以直接 eval)。西雅图 ruby 有几个 sexp 相关的 gem,RubyParser可以把 ruby 字符代码转为 sexp,而Ruby2Ruby可以将 sexp 转为 ruby 代码,利用他们就可以在语法树的层面上编辑 ruby 代码。

这个东西有什么用呢?他可以增强 DSL,例如 ruby 里没办法捕获赋值,没办法捕获[]{}的创建,a = 1对于 DSL 来说是不可见的,但是我们可以把它转化为assign(Var.a, 1),然后用 assign 方法来捕获到 a 的赋值。另外诸如1 / 0,在 ruby 代码里会立即被求值然后出错,但是经过预处理之后,可以让他正常地被 DSL 处理。

记得在 lisp 中 1 + 2 + 3 好像是可以写成:(+ 1 2 3)

mizuhashi 新的可能,Lisp and Erlang, LFE 提及了此话题。 12月22日 22:41
mizuhashi Ruby 动态删除方法中的打印语句 提及了此话题。 08月25日 14:57
需要 登录 后方可回复, 如果你还没有账号请 注册新账号