-
Notifications
You must be signed in to change notification settings - Fork 82
Description
I've just spent way too long figuring out why I couldn't get an action to execute. It's a scope problem. When I define actions in the same file as the grammar, they only execute when the grammar itself is in scope when parse() is called. The small example has four different files
- Grammar. There's a simple global variable that allows detecting whether an action executes or not.
- Test cases. One that calls
parse()locally. Two that callparse()in other modules. - A forward of
parse()that has the grammar in scope. - A forward of
parse()that does not have the grammar in scope.
Test cases a1 and a2 pass; test case a3 does not. Test case a2 illustrates a work-around; instead of using parse() straight from the library, write a forward for it in some context that includes the grammar, perhaps the grammar itself, though not necessarily.
If this behavior is a consciously-designed behavior of Rascal, it's completely counter-intuitive, as it breaks encapsulation. A function defined in the same module as a grammar may not always execute when the grammar is used. It's one of the reasons this took so long to track down, since that's just not they way modules are supposed to work. I had been using an adorned version of parse() with some assists for testing (like turning parse error exceptions into simple failures). Actions finally started executing as I was cutting out code wholesale trying to isolate the error.
I can see a reason to be able to define actions outside the original module of a grammar that extend the actions of the grammar. Unfortunately, Rascal's overload resolution rules are not at present fully up to the task. Using the example below, if a user of the grammar wanted to write their own action for a(), there's already a non-default Word a( A _ ) in the grammar explicitly, in addition to the default one defined by the production. It would be possible for a local definition to resolve in favor of an imported one, but (at least as far as the documentation seems to say) that's not supported. And that's not the only issue; the fail statement would need to fall back to the imported, explicitly-defined function, and only after a fail there would it fall back to a default.
module action_01_grammar
lexical Word = a: A ;
lexical A = "A" ;
int x = 0;
public void reset() { x = 0; }
public bool secret(int y) = x == y;
public Word a( A _ ) { x = 1; fail; }
module action_01
import ParseTree;
import action_01_grammar;
import action_01_util_good;
import action_01_util_bad;
test bool a1()
{
reset();
parse( #Word, "A" );
return secret( 1 );
}
test bool a2()
{
reset();
parse_remote_good( #Word, "A" );
return secret( 1 );
}
test bool a3()
{
reset();
parse_remote_bad( #Word, "A" );
return secret( 1 );
}
module action_01_util_good
import ParseTree;
import action_01_grammar;
void parse_remote_good( type[&T<:Tree] begin, str input )
{
parse( begin, input ) ;
}
module action_01_util_bad
import ParseTree;
void parse_remote_bad( type[&T<:Tree] begin, str input )
{
parse( begin, input ) ;
}