I just started out using IronRuby (but the behaviour seems consistent when I tested it in plain Ruby) for a DSL in my .NET application - and as part of this I'm defining methods to be called from the DSL via define_method.
However, I've run into an issue regarding optional parens when calling methods starting with an uppercase letter.
Given the following program:
class DemoClass
define_method :test do puts "output from test" end
define_method :Test do puts "output from Test" end
def run
puts "Calling 'test'"
test()
puts "Calling 'test'"
test
puts "Calling 'Test()'"
Test()
puts "Calling 'Test'"
Test
end
end
demo = DemoClass.new
demo.run
Running this code in a console (using plain ruby) yields the following output:
ruby .\test.rb
Calling 'test'
output from test
Calling 'test'
output from test
Calling 'Test()'
output from Test
Calling 'Test'
./test.rb:13:in `run': uninitialized constant DemoClass::Test (NameError)
from ./test.rb:19:in `<main>'
I realize that the Ruby convention is that constants start with an uppercase letter and that the general naming convention for methods in Ruby is lowercase. But the parens are really killing my DSL syntax at the moment.
Is there any way around this issue?
This is just part of Ruby's ambiguity resolution.
In Ruby, methods and variables live in different namespaces, therefore there can be methods and variables (or constants) with the same name. This means that, when using them, there needs to be some way to distinguish them. In general, that's not a problem: messages have receivers, variables don't. Messages have arguments, variables don't. Variables are assigned to, messages aren't.
The only problem is when you have no receiver, no argument and no assignment. Then, Ruby cannot tell the difference between a receiverless message send without arguments and a variable. So, it has to make up some arbitrary rules, and those rules are basically:
Note that for a message send with arguments (even if the argument list is empty), there is no ambiguity, which is why your third example works.
test()
: obviously a message send, no ambiguity heretest
: might be a message send or a variable; resolution rules say it is a message sendTest()
: obviously a message send, no ambiguity hereself.Test
: also obviously a message send, no ambiguity hereTest
: might be a message send or a constant; resolution rules say it is a constantNote that those rules are a little bit subtle, for example here:
The rules say that whether an ambiguous token gets interpreted as a variable or a message send is determined by the parser and not the interpreter. So, because the parser has seen
foo = whatever
, it tagsfoo
as a variable, even though the code will never get executed andfoo
will evaluate tonil
as all uninitialized variables in Ruby do.TL;DR summary: you're SOL.
What you could do is override
const_missing
to translate into a message send. Something like this:Except this obviously won't work, since
const_missing
is defined onDemoClass
and thus, whenconst_missing
is run,self
isDemoClass
which means that it tries to callDemoClass.test
when it should be callingDemoClass#test
viademo.test
.I don't know how to easily solve this.