Bug 470632 - hidden() causing grammar to stop working in some cases
Summary: hidden() causing grammar to stop working in some cases
Status: NEW
Alias: None
Product: TMF
Classification: Modeling
Component: Xtext (show other bugs)
Version: 2.9.0   Edit
Hardware: Macintosh Mac OS X
: P3 normal (vote)
Target Milestone: ---   Edit
Assignee: Project Inbox CLA
QA Contact:
URL:
Whiteboard:
Keywords:
Depends on:
Blocks:
 
Reported: 2015-06-19 19:54 EDT by Miguel Jimenez CLA
Modified: 2015-06-20 02:39 EDT (History)
1 user (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Miguel Jimenez CLA 2015-06-19 19:54:54 EDT
I am working on a grammar to specify cron expressions (like the ones within the crontab file in Unix). For some inputs the grammar works fine, but for others that seem to be right the generated Eclipse IDE shows an error. I search for help in stackoverflow (http://stackoverflow.com/questions/30880548/xtext-grammar-describing-cron-expression-not-working-as-expected) and a user suggested that the error comes from the use of hidden(), and effectively, when I removed the hidden statements all cases work ok (however in a cron expression spaces are important).

The following grammar and input allow to reproduce the error.


Grammar:
---------------------------------

grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"

Model:
	expressions+=CronExpression*;

CronExpression
	:	'('
			seconds = CronElement
			minutes = CronElement 
			hours = CronElement  
			days = CronElement 
			months = CronElement  
			daysOfWeek = CronElement 
			(year = CronElement)?
		')'
	| 	'@' constant = ID
	;
	
CronElement
	:	TerminalCronElement | RangeCronElement | PeriodicCronElement
	;
	
RangeCronElement hidden()
	:	start = IntLiteral '-' end = IntLiteral
	|	start = ID '-' end = ID
	;
	
TerminalCronElement
	:	expression = (IntLiteral | ID | '*' | '?')
	;
	
PeriodicCronElement hidden()
	:	expression = TerminalCronElement '/' elements = RangeCronList
	;
	
RangeCronList hidden()
	:	elements += (TerminalCronElement | RangeCronElement) (',' elements += (TerminalCronElement | RangeCronElement))*
	;
	
IntLiteral
	:	INT
	;


Demo input:
---------------------------------

(0 */1,10-20 * * * *) // works ok!
(0 */1 * * * *) // shows error: no viable alternative at input '1'
Comment 1 Sven Efftinge CLA 2015-06-20 02:39:30 EDT
I don't see how whitespace is significant for parsing. If you just want to disallow whitespace you are better off invalidating it afterwards, because then you also have more control over what and how you tell the user (i.e. 'unexpected token RULE_WS' is not super useful).

The problem here is, that we need a look ahead of 2 in order to decide whether to enter  the rule TerminalCronElement or RangeCronElement. Both alternatives check the token at LA(2) for possible follow ups. Unfortunately as we are in a hidden() context the token is WS, but that is not listed as a possible follow up, since the rule where the follow up tokens come from does hide whitespaces. 

Your grammar works, if you rewrite the problematic part a bit like this:

Model:
   expressions+=CronExpression*;

CronExpression
   :    '('
           seconds = CronElement
           minutes = CronElement 
           hours = CronElement  
           days = CronElement 
           months = CronElement  
           daysOfWeek = CronElement 
           (year = CronElement)?
       ')'
   |     '@' constant = ID
   ;

CronElement
   :    RangeCronElement | PeriodicCronElement
   ;

RangeCronElement hidden()
   :    TerminalCronElement ({RangeCronElement.start=current}'-' end = IntLiteral)*
   ;

TerminalCronElement
   :    expression = (IntLiteral | ID | '*' | '?')
   ;

PeriodicCronElement hidden()
   :    expression = TerminalCronElement '/' elements = RangeCronList
   ;

RangeCronList hidden()
   :    elements += RangeCronElement (',' elements +=RangeCronElement)*
   ;

IntLiteral
   :    INT
   ;


(note the change in RangeCronElement)
But here you'll have to invalidate things like *-3 or ?-5.
I suggest you try to do without the use of hidden().