4

我正在开发用于创建演示文稿的 Ruby Gem,并且我想创建一种用于定义简单直观的幻灯片的语法。我正在使用,instance_eval所以我可以调用自己的方法。这是我最初计划做的事情:

slide {
  title 'What is Ruby?'
  * 'a programming language'
  * 'with lots of interpreters'
  * 'lots of fun!'
}

即使我已经定义了一个*方法,我还是得到了错误:

在 `instance_eval': ... 语法错误,意外的 '\n',期待 :: 或 '[' 或 '.' (语法错误)

我通过创建一个称为b创建项目符号的简短方法来妥协,但它不是那么好:

slide {
  title 'What is Ruby?'
  b 'a programming language'
  b 'with lots of interpreters'
  b 'lots of fun!'
}

这只是解释器的限制吗?或者有没有办法绕过它?

更新:如果你愿意,你可以深入研究完整的源代码,但这里有一个小例子来说明它是如何实现的:

class Slide
  attr_accessor :title, :bullets
end

class SlidesDSL
  attr_accessor :slides
  def slide
    @slides ||= []
    s = SlideDSL.new
    s.instance_eval(&block)
    @slides << s.slide
  end
  class SlideDSL
    def slide
      @slide ||= Slide.new
    end

    def title(text)
      slide.title
    end

    def *(text)
      bullet(text)
    end

    def b(text)
      slide.bullets ||= []
      slide.bullets << text
    end
  end
end

# load_slides_from_file
source = File.read(filename)
dsl = SlidesDSL.new
dsl.instance_eval(source, filename, 0)
@slides = dsl.slides
4

5 回答 5

4

似乎您在很多事情上都依赖于 * 方法的语法糖。

事实并非如此。你可以这样做:

class Dog

  private
  def do_stuff(arg)
    puts 2 + arg
  end

end

d = Dog.new

d.instance_eval do
  do_stuff(3) 
end

--output:--
5
  1. instance_eval()d在这种情况下 ,将 self 更改为其接收者。
  2. 在 Ruby 中,private仅意味着您不能使用显式接收器调用该方法。
  3. 没有显式接收者的方法调用隐式使用 self 作为接收者。

现在,如果您将方法的名称从更改do_stuff*

class Dog

  private
  def *(arg)
    puts 2 + arg
  end

end

d = Dog.new

d.instance_eval do
  *(3)
end

--output:--
1.rb:13: syntax error, unexpected '\n', expecting tCOLON2 or '[' or '.'

所以 op 依赖于正常的方法操作,而不是任何归因于*. 在instance_eval块内,您会期望 Ruby 隐式执行:

self.*(3)

这相当于:

d.*(3)
于 2013-08-24T23:39:45.640 回答
3

是的,这是 Ruby 语法的限制。具体来说,正如sawa 指出的那样,您不能将它与隐式接收器一起使用(也不能变成一元运算符):它期望左侧有一些东西。

所有的操作符都是在它之前引用的对象上调用的简单方法,但有些操作符比其他操作符更平等。大多数方法都接受隐式接收器,但命名的方法*不接受。

我选择o了类似的情况。

-- 稍后添加(正如我最初评论 7stud 的帖子):

问题是 Ruby 解析器(Yacc 语法 + 一堆方法)根本不允许解析以 * 开头的行,这样 * 表示方法调用。如果一行以 * 开头,则唯一可能的解析是其中 * 是“splat”运算符。此限制对于用作方法名称的 * 字符是唯一的。

于 2013-08-24T22:33:57.263 回答
2

*如果您愿意使用-(或)而不是使用,则+可以通过在 上重载一元减(或加)运算符来获得非常相似的结果String。以下代码自动定义和取消定义-字符串上的一元,因此它在块期间工作,但在块完成时不会更改任何定义。请注意,如果在块内调用方法,-则仍将使用自定义行为。-但这并不是什么大问题,因为字符串的一元没有默认定义。它对于提取公共代码也很有用。

class Slide
  attr_accessor :title, :bullets
end

class SlidesDSL
  attr_accessor :slides
  def slide(&block)
    @slides ||= []
    s = SlideDSL.new
    old_unary_minus = String.instance_method(:-@) rescue nil
    begin
      String.send(:define_method, :-@) do
        s.b(self)
      end
      s.instance_eval(&block)
    ensure
      String.send(:remove_method, :-@)
      if old_unary_minus
        String.send(:define_method, :-@) do
          old_unary_minus.bind(self).call
        end
      end
    end
    @slides << s.slide
  end
  class SlideDSL
    def slide
      @slide ||= Slide.new
    end

    def title(text)
      slide.title = text
    end

    def *(text)
      bullet(text)
    end

    def b(text)
      slide.bullets ||= []
      slide.bullets << text
    end
  end
end

示例用法:

SlidesDSL.new.slide do
  title "Some title"
  - "one value"
  - "another value"
  4.times do |i|
    - "repeated value #{i}"
  end
end

回报:

[#<Slide:0x007f99a194d0f8 @bullets=["one value", "another value", "repeated value 0", "repeated value 1", "repeated value 2", "repeated value 3"], @title="Some title">]
于 2013-08-26T08:11:27.323 回答
0

看起来该*方法必须是“反私有的”。使用数字上的预定义方法*,您可以观察到这一点。

3.instance_eval{*}      # => syntax error, unexpected '}', expecting '='
3.instance_eval{*()}    # => syntax error, unexpected '}', expecting :: or '[' or '.'
3.instance_eval{self.*} # => wrong number of arguments (0 for 1)

看起来*需要一个明确的接收器是硬连线的。

于 2013-08-24T22:55:28.360 回答
0

似乎您在很多事情上都依赖于 * 方法的语法糖。

如果您编写*方法并将其与隐式一起使用.以表明它是给接收者的消息,那么您可能会有更好的运气。

例如,当我这样做时Fixnum

class Fixnum
  def *
    "This is the modified star"
  end
end

当我在 IRB 中尝试这个时

>> 1 * 1
ArgumentError: wrong number of arguments (1 for 0)

如果我只是按回车键,我也会遇到让它识别的正常问题*......它会期望在下一行有更多输入......

但是,如果我这样做:

>> 1.*
=> ""This is the modified star"

因此,这并不是不可能做到的,您可能只是在与围绕这个特定符号演变的语法糖进行斗争。

考虑私有方法,您可以这样做,但使用“eval”系列调用会遇到困难。你可以这样做:

some_instance.send(:*)

当然,如果需要论证,我们可以这样做some_instance.send(:*, argument)

于 2013-08-24T23:05:01.640 回答