描述 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()。