使用XText逐行匹配“文本”

时间:2012-12-21 15:21:46

标签: xtext bnf

我尝试为配置文件编写Xtext BNF(已知.ini扩展名)

例如,我想成功解析

[Section1]
a = Easy123
b = This *is* valid too

[Section_2]
c = Voilà # inline comments are ignored

我的问题是匹配属性值('='右侧的内容)。

如果属性与ID终端匹配(例如a = Easy123),则我当前的语法有效。

PropertyFile hidden(SL_COMMENT, WS):
    sections+=Section*;

Section:
    '[' name=ID ']'
    (NEWLINE properties+=Property)+
    NEWLINE+;

Property:
    name=ID (':' | '=') value=ID ';'?;

terminal WS:
    (' ' | '\t')+;

terminal NEWLINE:
// New line on DOS or Unix 
    '\r'? '\n';

terminal ID:
    ('A'..'Z' | 'a'..'z') ('A'..'Z' | 'a'..'z' | '_' | '-' | '0'..'9')*;

terminal SL_COMMENT:
// Single line comment
    '#' !('\n' | '\r')*;

我不知道如何概括语法以匹配任何文本(例如c = Voilà)。

我当然需要引入一个新的终端     属性:         name = ID(':'|'=')value = TEXT';'?;

问题是:我该如何定义这个TEXT终端?

我试过了

  • terminal TEXT: ANY_OTHER+; 这会引发警告

      

    以下令牌定义永远不能匹配,因为先前的令牌匹配相同的输入:RULE_INT,RULE_STRING,RULE_ML_COMMENT,RULE_ANY_OTHER

    (我认为没关系)。

    解析失败

      

    必需的循环(...)+与输入'à'

    中的任何内容都不匹配
  • terminal TEXT: !('\r'|'\n'|'#')+; 这会引发警告

      

    以下令牌定义永远不能匹配,因为先前的令牌匹配相同的输入:RULE_INT

    (我认为没关系)。

    解析失败

      

    在[第1节]缺少EOF

  • terminal TEXT: ('!'|'$'..'~');(涵盖大多数字符,#"除外) 在生成词法分析器/解析器期间没有警告。 但解析失败

      

    不匹配输入'Easy123'期待RULE_TEXT

         

    无关输入'这'期待RULE_TEXT

         

    必需的循环(...)+与'is'

    中的任何内容都不匹配

感谢您的帮助(我希望这个语法对其他人也有用)

3 个答案:

答案 0 :(得分:4)

这个语法可以解决问题:

grammar org.xtext.example.mydsl.MyDsl hidden(SL_COMMENT, WS)

generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
import "http://www.eclipse.org/emf/2002/Ecore"

PropertyFile:
    sections+=Section*;

Section:
    '[' name=ID ']' 
    (NEWLINE+ properties+=Property)+
    NEWLINE+;

Property:
    name=ID value=PROPERTY_VALUE;

terminal PROPERTY_VALUE: (':' | '=') !('\n' | '\r')*;

terminal WS:
    (' ' | '\t')+;

terminal NEWLINE:
// New line on DOS or Unix 
    '\r'? '\n';

terminal ID:
    ('A'..'Z' | 'a'..'z') ('A'..'Z' | 'a'..'z' | '_' | '-' | '0'..'9')*;

terminal SL_COMMENT:
// Single line comment
    '#' !('\n' | '\r')*;

关键是,您不会尝试仅在语法中覆盖完整语义,而是考虑其他服务。终端规则PROPERTY_VALUE使用完整值,包括前导分配和可选的尾随分号。

现在只需为该语言注册一个值转换器服务,并处理输入中无关紧要的部分:

import org.eclipse.xtext.conversion.IValueConverter;
import org.eclipse.xtext.conversion.ValueConverter;
import org.eclipse.xtext.conversion.ValueConverterException;
import org.eclipse.xtext.conversion.impl.AbstractDeclarativeValueConverterService;
import org.eclipse.xtext.conversion.impl.AbstractIDValueConverter;
import org.eclipse.xtext.conversion.impl.AbstractLexerBasedConverter;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.util.Strings;

import com.google.inject.Inject;

public class PropertyConverters extends AbstractDeclarativeValueConverterService {
    @Inject
    private AbstractIDValueConverter idValueConverter;

    @ValueConverter(rule = "ID")
    public IValueConverter<String> ID() {
        return idValueConverter;
    }

    @Inject
    private PropertyValueConverter propertyValueConverter;

    @ValueConverter(rule = "PROPERTY_VALUE")
    public IValueConverter<String> PropertyValue() {
        return propertyValueConverter;
    }

    public static class PropertyValueConverter extends AbstractLexerBasedConverter<String> {

        @Override
        protected String toEscapedString(String value) {
            return " = " + Strings.convertToJavaString(value, false);
        }

        public String toValue(String string, INode node) {
            if (string == null)
                return null;
            try {
                String value = string.substring(1).trim();
                if (value.endsWith(";")) {
                    value = value.substring(0, value.length() - 1);
                }
                return value;
            } catch (IllegalArgumentException e) {
                throw new ValueConverterException(e.getMessage(), node, e);
            }
        }
    }
}

在运行时模块中注册服务之后,以下测试用例将成功:

@Override
public Class<? extends IValueConverterService> bindIValueConverterService() {
    return PropertyConverters.class;
}

测试用例:

import org.junit.runner.RunWith
import org.eclipse.xtext.junit4.XtextRunner
import org.xtext.example.mydsl.MyDslInjectorProvider
import org.eclipse.xtext.junit4.InjectWith
import org.junit.Test
import org.eclipse.xtext.junit4.util.ParseHelper
import com.google.inject.Inject
import org.xtext.example.mydsl.myDsl.PropertyFile
import static org.junit.Assert.*

@RunWith(typeof(XtextRunner))
@InjectWith(typeof(MyDslInjectorProvider))
class ParserTest {

    @Inject
    ParseHelper<PropertyFile> helper

    @Test
    def void testSample() {
        val file = helper.parse('''
            [Section1]
            a = Easy123
            b : This *is* valid too;

            [Section_2]
            # comment
            c = Voilà # inline comments are ignored
        ''')
        assertEquals(2, file.sections.size)
        val section1 = file.sections.head
        assertEquals(2, section1.properties.size)
        assertEquals("a", section1.properties.head.name)
        assertEquals("Easy123", section1.properties.head.value)
        assertEquals("b", section1.properties.last.name)
        assertEquals("This *is* valid too", section1.properties.last.value)

        val section2 = file.sections.last
        assertEquals(1, section2.properties.size)
        assertEquals("Voilà # inline comments are ignored", section2.properties.head.value)
    }

}

答案 1 :(得分:1)

解析这样的格式的问题(或者一个问题)是,因为文本部分可能包含=个字符,所以像foo = bar这样的行将被解释为单个TEXT标记,不是ID,后跟'=',后跟TEXT。如果不禁止(或要求转义)文本部分中的=字符,我无法避免这种情况。

如果这不是一个选项,我认为,唯一的解决方案是制作一个匹配整行的令牌类型LINE,然后自己分开。您可以通过从语法中删除TEXTID并将其替换为与下一个换行符或注释符号相匹配的所有内容的令牌类型LINE来实现,并且必须以有效身份证件所以像这样:

LINE :
    ('A'..'Z' | 'a'..'z') ('A'..'Z' | 'a'..'z' | '_' | '-' | '0'..'9')*
    WS* '=' WS*
    !('\r' | '\n' | '#')+
;

此令牌基本上会取代您的Property规则。

当然这是一个相当令人不满意的解决方案,因为它会将整行作为一个字符串给你,你仍然必须自己挑选它以将ID与文本部分分开。它还会阻止您突出显示ID部分或=符号,因为整行是一个令牌而您无法突出显示部分令牌(据我所知)。总的来说,这并没有给你带来太大的影响,而根本没有使用XText,但我没有看到更好的方法。

答案 2 :(得分:0)

作为解决方法,我已更改

Property:
    name=ID ':' value=ID ';'?;

现在,当然,=不再存在冲突,但这肯定不是一个好的解决方案,因为属性通常可以用name=value定义

修改:实际上,我的输入是一个特定的属性文件,并且事先知道这些属性。

我的代码现在看起来像

Section:
    '[' name=ID ']'
    (NEWLINE (properties+=AbstractProperty)?)+;

AbstractProperty:
    ADef
        | BDef

ADef:
    'A' (':'|'=') ID;

BDef:
    'B' (':'|'=') Float;

还有一个额外的好处,属性名称被称为关键字,并且颜色如此。但是,自动完成仅建议'[':(