Alright. Funny enough. After another day with both, Groovy and JRuby, I would say Groovy did catch up. Here’s the whole story:
After accepting the fact that Groovy is very OO, I found a way to add my plugin’s configuration folder to the classpath and load additional stuff from there. This way I was able to write a simple LexerAdapter and LexerBase class in Groovy. The resulting lexer specification looks something like this:
import jfun.parsec.*
import jfun.parsec.tokens.*
import jfun.parsec.pattern.*
class MyRubySyntax extends jparsec.LexerBase
{
protected void setup()
{
def s_block_open = Scanners.isPattern( Patterns.regex( /(?m:^=begin)/ ), "BLOCK_OPEN" )
def s_block_close = Scanners.isPattern( Patterns.regex( /(?m:^=end$)/ ), "BLOCK_CLOSE" )
def s_block_commented = Scanners.anyChar()
scanner "DOC_COMMENT", Scanners.isBlockComment( s_block_open, s_block_close, s_block_commented )
regex "LINE_COMMENT", /(?m:#.*$)/
regex "SPECIAL_QUOTED_STRING", /%[qQ]\{(?:(?:\\\})|(?:[^\}]))*\}/
(..snip..)
regex "IDENTIFIER", /\b[a-z]\w*\b/
regex "NUMBER", /\b(?:0x[0-9A-Fa-f]+)|(?:[0-9]+(?:\.[0-9]+)?)\b/
}
}
return new MyRubySyntax().lexer()
This is still a lot uglier than the (J)Ruby version. But the thing is: This compiles to Java byte code. And with the LexerAdapter implemented in the scripting part of the system this is significant. To remedy the situation for JRuby I still have the pure Java LexerAdapter available on the classpath. (More on this in the example configuration files.)
What I didn’t figure out: There’s a nice use (“with”) codeword in Groovy. But it didn’t really work. It should provide functionality to merge a module’s methods into a closure/block of code. This way I wouldn’t have to create this MyRubySyntax class, etc and could get away with something like this:
use ( jparsec.LexerBase )
{
(..snip..)
regex "NUMBER", /\b(?:0x[0-9A-Fa-f]+)|(?:[0-9]+(?:\.[0-9]+)?)\b/
return lexer()
}
But I guess I still don’t understand Groovy good enough. It has quite a few little quirks.
Another of those would be that I cannot use closures in all the places that should allow them. Here’s one:
for ( idx in 0..myCache.size() - 1 )
{
def token = myCache.get( idx )
if ( aStartOffset >= token.start && aStartOffset < token.end ) return token
if ( aStartOffset < token.end ) return token
}
With myCache being an ArrayList I would expect myCache.each { token -> ... } to work. Somehow I get a compiler error mumbling about 1 parameter where 2 are expected or something like that. Weird.
Anyway, with the compiled approach of Groovy it suddenly feels a lot more practicable for the intended purpose of lexer adaptation and specification. Nice.
With the next step planned - extending the plugin to allow simple parser functionality - I assume JRuby will become even less an option. But it should be helpful to get some quick results. Especially for the Ruby configuration of the plugin. I intend to use one of the existing pure Ruby parsers for the Ruby syntax. We'll see how that turns out..
tfdj