描述 cron 表达式未按预期工作的 Xtext 语法
Xtext grammar describing cron expression not working as expected
我目前正在使用 Xtext 开发领域特定语言,到目前为止一切顺利。此时此刻,我正在定义语法,特别是在一组生产规则中,让用户指定 cron 表达式(就像 Unix 中 crontab
文件中的那些)。
问题:如您所见,生产规则 RangeCronList
应该允许像 */1,3-4,JAN-DEC
这样的值,但它不允许。但是,它确实允许 */10,10-2
或 */1,JAN-DEC
之类的东西。
错误:我在生成的 Eclipse IDE(对于无法识别的表达式)中遇到的错误是 "no viable alternative at input '...'".
问题:为什么这些生产规则不允许指定整数或 ID?我希望用户能够指定单个整数、ID、它们的范围以及这些可能值的列表。
补充:我在 DSL 方面的经验很短,所以如果你能对这个语法片段提出一些建议,我将不胜感激。
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
;
这是我感兴趣的部分:
谢谢。
示例(输入)
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!
}
我不明白空格对于解析有何重要意义。如果你只是想禁止空格,你最好事后使它无效,因为这样你也可以更好地控制你告诉用户的内容和方式(即 'unexpected token RULE_WS' 不是很有用)。
这里的问题是,我们需要提前2来决定是进入规则TerminalCronElement还是RangeCronElement。两种备选方案都检查 LA(2) 处的令牌以进行可能的跟进。不幸的是,由于我们在 hidden() 上下文中,令牌是 WS,但未将其列为可能的跟进,因为后续令牌来自的规则确实隐藏了空格。
如果你像这样重写有问题的部分,你的语法就可以了:
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
;
(注意 RangeCronElement 的变化)
但是在这里你必须使 *-3 或 ?-5.
之类的东西无效
我建议您尝试不使用 hidden()。
我目前正在使用 Xtext 开发领域特定语言,到目前为止一切顺利。此时此刻,我正在定义语法,特别是在一组生产规则中,让用户指定 cron 表达式(就像 Unix 中 crontab
文件中的那些)。
问题:如您所见,生产规则 RangeCronList
应该允许像 */1,3-4,JAN-DEC
这样的值,但它不允许。但是,它确实允许 */10,10-2
或 */1,JAN-DEC
之类的东西。
错误:我在生成的 Eclipse IDE(对于无法识别的表达式)中遇到的错误是 "no viable alternative at input '...'".
问题:为什么这些生产规则不允许指定整数或 ID?我希望用户能够指定单个整数、ID、它们的范围以及这些可能值的列表。
补充:我在 DSL 方面的经验很短,所以如果你能对这个语法片段提出一些建议,我将不胜感激。
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
;
这是我感兴趣的部分:
谢谢。
示例(输入)
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!
}
我不明白空格对于解析有何重要意义。如果你只是想禁止空格,你最好事后使它无效,因为这样你也可以更好地控制你告诉用户的内容和方式(即 'unexpected token RULE_WS' 不是很有用)。
这里的问题是,我们需要提前2来决定是进入规则TerminalCronElement还是RangeCronElement。两种备选方案都检查 LA(2) 处的令牌以进行可能的跟进。不幸的是,由于我们在 hidden() 上下文中,令牌是 WS,但未将其列为可能的跟进,因为后续令牌来自的规则确实隐藏了空格。
如果你像这样重写有问题的部分,你的语法就可以了:
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
;
(注意 RangeCronElement 的变化) 但是在这里你必须使 *-3 或 ?-5.
之类的东西无效我建议您尝试不使用 hidden()。