Skip to content

Action execution depends on scope of call and not on scope of definition #1202

@eh-adblockplus-org

Description

@eh-adblockplus-org

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 call parse() 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 ) ;
}

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions