Ruby Ruby 的 sexp 宏

mizuhashi · 发布于 2016年04月13日 · 最后由 mizuhashi 回复于 2016年04月19日 · 1268 次阅读
23529
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处理。

共收到 2 条回复
9028

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

23529 mizuhashi 新的可能,Lisp and Erlang, LFE 中提及了此贴 12月22日 22:41
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册