I am currently developing a Domain Specific Language using Xtext, and until now everything has gone well. At this very moment I am working in the definition of the grammar, specifically in a set of production rules to let the user specify cron expressions (like the ones within the crontab
file in Unix).
The problem: as you can see, production rule RangeCronList
is supposed to allow values like */1,3-4,JAN-DEC
but it does not. However, it does allow things like */10,10-2
or */1,JAN-DEC
.
The error: The error I am getting in the generated Eclipse IDE (for the non-recognized expressions) is "no viable alternative at input '...'".
The question: why these production rules do not allow to specify integers or IDs? I want users to be able to specify single integers, ids, ranges of them, and a list of these possible values.
Additional: my experience with DSLs is short, so I appreciate if you can give some advices on this grammar fragment.
grammar org.pascani.Pascani with org.eclipse.xtext.xbase.Xbase
import "http://www.eclipse.org/xtext/common/JavaVMTypes" as types
generate pascani "http://www.pascani.org/Pascani"
Model
: package = PackageDeclaration?
imports = XImportSection?
usings = UsingSection?
typeDeclaration = TypeDeclaration?
;
PackageDeclaration returns Package
: 'package' name = QualifiedName ';'?
;
UsingSection
: usingDeclarations += UsingDeclaration+
;
UsingDeclaration returns Using
: 'using' namespace ?= 'namespace' type = [Namespace | QualifiedName] ';'?
;
TypeDeclaration
: MonitorDeclaration
| NamespaceDeclaration
;
MonitorDeclaration returns Monitor
: 'monitor' name = ID '{'
typeDeclarations += MemberDeclaration*
'}'
;
NamespaceDeclaration returns Namespace
: 'namespace' name = ID '{'
typeDeclarations += NamespaceMemberDeclaration*
'}'
;
NamespaceMemberDeclaration
: NamespaceDeclaration
| VariableDeclaration
;
MemberDeclaration
: VariableDeclaration
| HandlerDeclaration
| EventDeclaration
;
VariableDeclaration
: jvmType = JvmTypeReference expression = XExpression ';'? // (XAssignment | MapValue | PairValue | ArrayValue)
;
HandlerDeclaration returns Handler
: 'handler' name = ID '(' declaredFormalParameter = JvmFormalParameter ')' body = XBlockExpression
;
// Special data types declaration
MapValue returns Map
: {Dictionary} '{' ( pairs += [Pair] (',' pairs += [Pair])* )? '}'
;
PairValue returns Pair
: key = (ID | STRING) ':' value = XExpression
;
ArrayValue returns Array
: {Array} '[' (elements += XExpression (',' elements+= XExpression)*)? ']'
;
// Event declarations
EventDeclaration returns Event
: 'event' name = ID 'raised' (periodically ?= 'periodically')? 'on' emitter = EventEmitter ';'?
;
EventType
: ('invoke'|'return'|'change'|'exception')
;
EventEmitter
: eventType = EventType 'of' emitter = QualifiedName (=>specifier = (RelationalEventSpecifier | EventSpecifier))? ('using' probe = ID)?
| cronExpression = CronExpression
;
RelationalEventSpecifier
: '(' RelationalEventSpecifier ')'
| left = EventSpecifier (and ?= 'and' | or ?= 'or') right = EventSpecifier
;
EventSpecifier
: 'below' 'of' EventSpecifierValue
| 'above' 'of' EventSpecifierValue
| 'equal' 'to' EventSpecifierValue
;
EventSpecifierValue
: value = Number percentage ?= '%'?
| variable = QualifiedName
;
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
;
This is the part I am interested in:
Thanks.
Example (input)
package org.example.monitors
using namespace System
monitor Performance {
event e1 raised on (0 */1,10-20 * * * *) // works fine
event e2 raised on (0 */1 * * * *) // It's not recognized!
}
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:
(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().