我正在处理Ruby Gem for creating presentations,我想创建一种用于定义简单直观的幻灯片的语法。我正在使用instance_eval
,因此我可以在自己上调用方法。这是我原本打算做的事情:
slide {
title 'What is Ruby?'
* 'a programming language'
* 'with lots of interpreters'
* 'lots of fun!'
}
即使我已经定义了*
方法,我也会收到错误:
在`instance_eval':...语法错误,意外' \ n',期待::或' ['或'。' (的SyntaxError)
我已经通过创建一个名为b
的简短方法来创建项目符号,但它并没有那么好:
slide {
title 'What is Ruby?'
b 'a programming language'
b 'with lots of interpreters'
b 'lots of fun!'
}
这仅仅是口译员的限制吗?或者有办法解决它吗?
更新:如果您愿意,可以深入了解full source code,但这里有一个小例子:
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
答案 0 :(得分: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
instance_eval()
将自我更改为接收者d
。 private
仅表示您无法使用显式接收器调用该方法。 现在,如果您将方法的名称从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)
答案 1 :(得分:3)
是的,这是Ruby语法的限制。具体来说,你不能像sawa指出的那样,将它与隐式接收器一起使用(也不能变成一元运算符):它需要左侧的东西。
所有运算符都是在它之前引用的对象上调用的方法,但是某些运算符比其他运算符更平等。大多数方法接受隐式接收器,但名为*
的接收器不接受。
我在类似情况下选择o
。
- 稍后添加(正如我最初评论7stud的帖子):
问题是Ruby解析器(Yacc语法+一堆方法)根本不允许解析以*开头的行,以便*表示方法调用。如果一行以*开头,则唯一可能的解析是*是'splat'运算符。此限制对于用作方法名称的*字符是唯一的。
答案 2 :(得分: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">]
答案 3 :(得分: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)
看起来*
很难连接到需要明确的接收器。
答案 4 :(得分: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)