Sil*_*olo 5 ruby dsl metaprogramming refinements
假设我正在设计一种特定于领域的语言。对于这个简化的示例,我们有变量、数字以及将事物添加在一起的能力(变量或数字)
class Variable
attr_reader :name
def initialize(name)
@name = name
end
end
class Addition
attr_reader :lhs, :rhs
def initialize(lhs, rhs)
@lhs = lhs
@rhs = rhs
end
end
Run Code Online (Sandbox Code Playgroud)
现在,如果我想表示表达式x + 1,我会写Addition.new(Variable.new('x'), 1)。
我们可以通过+在 上提供一种方法来使这更方便Variable。
class Variable
def +(other)
Addition.new(self, other)
end
end
Run Code Online (Sandbox Code Playgroud)
然后我们就可以写了Variable.new('x') + 1。
但现在,假设我想要相反的结果:1 + x。显然,我不想进行猴子补丁Integer#+,因为这会永久禁用普通的 Ruby 整数加法。我认为这将是一个很好的改进用例。
具体来说,我想定义一个方法expr,该方法采用一个块并在重新定义的上下文中评估该块+以构造我的 DSL 实例。也就是说,我想要类似的东西
module Context
refine Integer do
def +(other)
Addition.new(self, other)
end
end
end
def expr(&block)
Context.module_eval(&block)
end
Run Code Online (Sandbox Code Playgroud)
因此,理想情况下,expr { 1 + Variable.new('x') }会产生 DSL 表达式Addition.new(1, Variable.new('x'))。
然而,Ruby 细化似乎非常变化无常,并且module_eval进入具有活动细化的范围并不会像我希望的那样激活块内的细化。有没有办法使用module_eval、instance_eval等来激活特定 Ruby 块内的细化?
我意识到我可以将整数包装在一个IntegerExpr类中并提供+它。然而,这是 Ruby,元编程的极限是无限的,所以我很好奇是否可以用普通的 RubyInteger实例来完成。我想定义一个方法expr,这样,
expr { 1 + Variable.new('x') }
Run Code Online (Sandbox Code Playgroud)
块内部+是细化定义的Integer#+,即使该细化在 的调用站点处未激活expr。这可能吗?