ruby, fun May 2, 2006 8:43 pm (Save post)
(post in english for LiquidDevelopment compat
)
Chiaroscuro is having fun with ruby metaprogramming recipes and so will I.
My version does not contain cool tricks such as allow_method_chaining but I think it is fun anyway, so I here it is.
I’ll start straight with some numeric stuff:
class Integer
names = %w[ one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen
sixteen seventeen eighteen nineteen ]
NAMES=Hash[*names.zip((1..15).to_a).flatten]
end
Yeah, I know I could hack the 11..19 group with a map, but I think it is not that nice.
Now, replace the ugly $global with a constant, and make @ingredients readable:
class Recipe
KNOWN={}
attr :ingredients
The proposal is to make something like this work:
Recipe.new \"breakfast\" {
one of cappuccino
two of brioche
}
so we have to handle one/two etc as methods. Obviously we even have to handle of and then get the rest with method_missing, so the basic code for initialize will need to call instance_eval:
def initialize(recipe_name , &block)
@name = recipe_name
@ingredients = {}
instance_eval &block
KNOWN[recipe_name]=self
end
Notice the ultrasimple handling of registered recipes
Now, we can define of as a simple method that returns it’s argument:
def of(thing)
thing
end
and handle ingredients via method_missing:
# XXX: we could raise if args.size > 0
def method_missing(sym,*args)
sym.to_s
end
Finally, the code for the numbers/methods may be something like:
for name in Integer::NAMES.keys
eval %{
def #{name}(*values)
first= values.shift #coffee
values.push \"#{name}\" # [ \"macchiato\", \"one\"]
@ingredients[first]=values
end
}
end
There are two things to say here: first, we need to store the name of the ingredient as the key and put the number as a value in it, together with any optional thing. Second, we can’t use for or each with define_method and we have to fall back on eval.
This happens because if we used a block with it, it would be a closure, and thus the last value assigned to name would be carried on by all the methods.
Mh.. is this perfect?
Not really, as of now this code allows you to write
Recipe.new(n) do
one coffee, macchiato
ten of saccottino
end
But it will fail miserably with one of coffee, macchiato.
It is quite easy to handle this case if you choose to disallow commands with missing of, by redefining it to take variable arguments (and changing the number-methods accordingly), but to make it work fine in both cases there is the need for ugly checks with and this is left as an exercise for the reader
Finally, you have to set $VERBOSE to nil so that the intepreter does not spit out warnings when it tries to interpret foo bar baz, quux, which it recognizes as foo(bar(baz,quux)).
Oh, by the way, I did not convert numbers to integers, since I found it was useless, but you can safely use the Hash that I set up for this.
Thanks for reading till here, now get the full code with my minimal test suite
PS
chiaroscuro, ho cancellato un tuo vecchio commento qui sopra per sbaglio, scusami

