I'm working on a Ruby Gem for creating presentations, and I would like to create a syntax for defining slides that is simple and intuitive. I'm making use of instance_eval
so I can call methods on self. Here's what I originally planned to do:
slide {
title 'What is Ruby?'
* 'a programming language'
* 'with lots of interpreters'
* 'lots of fun!'
}
Even though I've defined a *
method, I get the error:
in `instance_eval': ... syntax error, unexpected '\n', expecting :: or '[' or '.' (SyntaxError)
I have compromised by creating a short method called b
for creating bullets, but it's not as nice:
slide {
title 'What is Ruby?'
b 'a programming language'
b 'with lots of interpreters'
b 'lots of fun!'
}
Is this just a limitation of the interpreter? Or is there a way to get around it?
Update: If you want, you can dig into the full source code, but here is a small example of how it is implemented:
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
It seems that you are relying on syntactic sugar that is given to the * method for many things.
That's not the case. You can do this:
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()
changes self to its receiver, d
in this case. private
only means you cannot call the method with an explicit receiver. Now if you change the method's name from do_stuff
to *
:
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 '.'
So the op is relying on normal method operation, not any syntactic sugar attributed to *
. Inside the instance_eval
block, you would expect Ruby to implicitly execute:
self.*(3)
which is equivalent to:
d.*(3)
Yes, this is a limitation of the Ruby grammar. Specifically, you cannot, as sawa points out, use it with an implicit receiver (nor turn into a unary operator): it expects something on the left hand side.
All operators are simply methods called on the object referenced before it, but some operators are more equal than others. Most methods accept an implicit receiver, but the one named *
doesn't.
I opted for o
in a similar situation.
-- Added later (as I originally commented on 7stud's post):
The problem is that the Ruby parser (Yacc grammar + a bunch of methods) simply does not allow a line starting with a * to be parsed such that the * denotes a method call. If a line starts with a *, the only possible parsing is one where the * is the 'splat' operator. This limitation is unique to the * character used as a method name.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With