4

我尝试为配置文件编写 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

    (我认为没关系)。

    解析失败

    [Section1] 缺少 EOF

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

    期望 RULE_TEXT 的不匹配输入“Easy123”

    外部输入 'This' 期望 RULE_TEXT

    所需的循环 (...)+ 与“is”处的任何内容都不匹配

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

4

3 回答 3

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)
    }

}
于 2013-01-15T16:19:49.653 回答
1

解析这样的格式的问题(或无论如何都是一个问题)是,由于文本部分可能包含=字符,因此一行 likefoo = bar将被解释为单个 TEXT 标记,而不是 ID,后跟一个“=”,然后是一个TEXT=如果不禁止(或要求转义)文本部分中的字符,我无法避免这种情况。

如果这不是一个选项,我认为,唯一的解决方案是制作一个LINE与整行匹配的令牌类型,然后自己将其拆开。为此,您可以从语法中删除TEXTID替换它们,并将它们替换为与LINE下一个换行符或注释符号匹配的所有内容并且必须以有效 ID 开头的标记类型。所以是这样的:

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

这个令牌基本上会取代你的Property规则。

当然,这是一个相当不令人满意的解决方案,因为它会将整行作为字符串提供给您,您仍然必须自己将其分开以将 ID 与文本部分分开。它还可以防止您突出显示 ID 部分或 = 符号,因为整行是一个标记,并且您不能突出显示标记的一部分(据我所知)。总的来说,与根本不使用相比,这并没有给你带来太多好处XText,但我没有看到更好的方法。

于 2012-12-21T16:29:55.330 回答
0

作为一种解决方法,我已经改变了

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

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

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

我的代码现在看起来像

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

AbstractProperty:
    ADef
        | BDef

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

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

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

于 2013-01-15T20:54:02.787 回答