是否可以在非全局上下文中评估 Ruby DSL?

发布于 2024-08-23 01:16:02 字数 658 浏览 4 评论 0原文

我正在使用 Blockenspiel 使用 Ruby 创建 DSL。它效果很好并且 解决了我的很多问题,但是我遇到了以下问题 这与 Blockenspiel 没有严格关系。

假设我有一个如下所示的 DSL:

dish do
  name = 'Pizza'
  ingredients = ...
  nutrition_facts = ...
end

dish do
  name = 'Doner'
  ingredients = ...
  nutrition_facts = ...
end

现在我有一个菜单编译器,它可以将菜肴编译成 一个菜单。编译器现在应该能够编译多个菜单文件, 所以它已经设置并清除了全局上下文。这应该最好 并行发生。

我发现 sinatra 使用类变量,但这有 结果是它只能进行顺序处理并且您 当你想编译一个新的类时必须清除类变量 菜单。另一种方法是使用全局变量。

我更愿意在以下范围内评估 DSL 方法: 对象,这样就没有全局上下文,我可以编译 并行菜单,但上次我尝试这个时,我遇到了一些 在菜单文件中声明(helper-)方法时出现问题。

哪些方法是可行的?推荐的方法是什么?

I'm using Blockenspiel to create a DSL with Ruby. It works great and
solves a lot of my problems, but I encountered the following problem
that is not strictly related to Blockenspiel.

Suppose I have a DSL that looks like this:

dish do
  name = 'Pizza'
  ingredients = ...
  nutrition_facts = ...
end

dish do
  name = 'Doner'
  ingredients = ...
  nutrition_facts = ...
end

Now I have a menu compiler which takes the dishes and compiles them into
a menu. The compiler should now be able to compile multiple menu files,
so it has setup and clear a global context. This should preferably
happen in parallel.

I found out that sinatra uses class variables, but this has the
consequence that it can only do sequential processing and that you
have to clear the class variables when you want to compile a new
menu. An alternative would be to use global variables.

I would prefer to evaluate the DSL methods within the scope of an
object, so that there's no global context and I could compile the
menus in parallel, but the last time I tried this, I encountered some
problems when declaring (helper-)methods in the menu file.

Which methods are possible? What is the recommended way to do this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

凝望流年 2024-08-30 01:16:02

我见过的许多库都是利用 instance_eval 来完成此类事情。

只要性能不是一个大问题,您就可以执行以下操作:

class Menu
  def initialize file
    instance_eval File.read(file),file,1
  end

  def dish &block
    Dish.new &block
  end
  #....
end

class Dish
  def name(n=nil)
    @name = n if n
    @name
  end
  def ingredients(igrd=nil)
    @ingredients= igrd if igrd
    @ingredients
  end
end
#....

Menu.new 'menus/pizza_joint'

menus/pizza_joint

dish do
  name 'Cheese Pizza'
  ingredients ['Cheese','Dough','Sauce']
end

实际上有一些 DSL 库可以添加诸如 #name< 之类的访问器/code> 和 #ingredients 因此您不必手动构建它们。例如 dslify

What a number of libraries I've seen do is take advantage of instance_eval for this sort of thing.

As long as performance isn't a huge issue, you can do stuff like:

class Menu
  def initialize file
    instance_eval File.read(file),file,1
  end

  def dish &block
    Dish.new &block
  end
  #....
end

class Dish
  def name(n=nil)
    @name = n if n
    @name
  end
  def ingredients(igrd=nil)
    @ingredients= igrd if igrd
    @ingredients
  end
end
#....

Menu.new 'menus/pizza_joint'

menus/pizza_joint

dish do
  name 'Cheese Pizza'
  ingredients ['Cheese','Dough','Sauce']
end

There are actually DSL libraries that add accessors like #name and #ingredients so you don't have to build them by hand. eg dslify

二货你真萌 2024-08-30 01:16:02

基本上有两种方法可以实现您想要的。

选项a:使用setter方法生成一个对象:

Dish = Struct.new(:name, :ingredients, :nutrition_facts)
def dish
  d = Dish.new
  yield d
  d
end

dish do |d|
  d.name = 'Pizza'
  d.ingredients = ...
  d.nutrition_facts = ...
end

选项b:使用实例变量和instance_eval

class Dish
  attr_accessor :name, :ingredients, :nutrition_facts
end
def dish(&blk)
  d = Dish.new
  d.instance_eval(&blk)
  d
end

dish do
  @name = 'Doner'
  @ingredients = ...
  @nutrition_facts = ...
end

在这两种情况下,dish方法将返回一个Dish实例,您可以在其上调用例如name来访问块中设置的名称(对菜的多次调用将返回独立的对象)。请注意,使用 instance_eval,用户还可以调用块中 Dish 类的私有方法,并且拼写错误的变量名称不会导致错误。

There are basically two ways to archieve what you want.

Option a: You yield an object with setter-methods:

Dish = Struct.new(:name, :ingredients, :nutrition_facts)
def dish
  d = Dish.new
  yield d
  d
end

dish do |d|
  d.name = 'Pizza'
  d.ingredients = ...
  d.nutrition_facts = ...
end

Option b: you use instance variables and instance_eval

class Dish
  attr_accessor :name, :ingredients, :nutrition_facts
end
def dish(&blk)
  d = Dish.new
  d.instance_eval(&blk)
  d
end

dish do
  @name = 'Doner'
  @ingredients = ...
  @nutrition_facts = ...
end

In both cases the dish method will return an instance of Dish on which you can call e.g. name to access the name set in the block (and multiple calls to dish will return independent objects). Note that with instance_eval the user will also be able to call private methods of the Dish class in the block and that misspelling variable names will not cause an error.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文