我尝试为配置文件编写 Xtext BNF(.ini扩展名已知)
例如,我想成功解析
\n\n[Section1]\na = Easy123\nb = This *is* valid too\n\n[Section_2]\nc = Voil\xc3\xa0 # inline comments are ignored\nRun Code Online (Sandbox Code Playgroud)\n\n我的问题是匹配属性值(“=”右侧的内容)。
\n\n如果属性与ID终端匹配(例如a = Easy123),我当前的语法将有效。
PropertyFile hidden(SL_COMMENT, WS):\n sections+=Section*;\n\nSection:\n \'[\' name=ID \']\'\n (NEWLINE properties+=Property)+\n NEWLINE+;\n\nProperty:\n name=ID (\':\' | \'=\') value=ID \';\'?;\n\nterminal WS:\n (\' \' | \'\\t\')+;\n\nterminal NEWLINE:\n// New line on DOS or Unix \n \'\\r\'? \'\\n\';\n\nterminal ID:\n (\'A\'..\'Z\' | \'a\'..\'z\') (\'A\'..\'Z\' | \'a\'..\'z\' | \'_\' | \'-\' | \'0\'..\'9\')*;\n\nterminal SL_COMMENT:\n// Single line comment\n \'#\' !(\'\\n\' | \'\\r\')*;\nRun Code Online (Sandbox Code Playgroud)\n\n我不知道如何概括语法以匹配任何文本(例如c = Voil\xc3\xa0)。
我当然需要引入一个新的终端\n属性:\n name=ID (\':\' | \'=\') value=TEXT \';\'?;
\n\n问题是:我应该如何定义这个TEXT终端?
我努力了
\n\nterminal TEXT: ANY_OTHER+;\n这会引发警告
\n\n\n以下标记定义永远无法匹配,因为先前的标记与相同的输入匹配:RULE_INT、RULE_STRING、RULE_ML_COMMENT、RULE_ANY_OTHER
\n
(我认为这并不重要)。
\n\n解析失败
\n\n\n所需的循环 (...)+ 与输入 \'\xc3\xa0\' 处的任何内容都不匹配
\n
terminal TEXT: !(\'\\r\'|\'\\n\'|\'#\')+;\n这会引发警告
\n\n\n以下标记定义永远无法匹配,因为先前的标记与相同的输入匹配:RULE_INT
\n
(我认为这并不重要)。
\n\n解析失败
\n\n\n[Section1] 处缺少 EOF
\n
terminal TEXT: (\'!\'|\'$\'..\'~\');(涵盖大多数字符,除了#和")\n在词法分析器/解析器的生成过程中没有警告。\n但是解析失败
\n输入“Easy123”不匹配,需要 RULE_TEXT
\n\n外部输入“This”需要 RULE_TEXT
\n\n所需的循环 (...)+ 与 \'is\' 处的任何内容都不匹配
\n
感谢您的帮助(我希望这个语法对其他人也有用)
\n这个语法可以解决这个问题:
\n\ngrammar org.xtext.example.mydsl.MyDsl hidden(SL_COMMENT, WS)\n\ngenerate myDsl "http://www.xtext.org/example/mydsl/MyDsl"\nimport "http://www.eclipse.org/emf/2002/Ecore"\n\nPropertyFile:\n sections+=Section*;\n\nSection:\n \'[\' name=ID \']\' \n (NEWLINE+ properties+=Property)+\n NEWLINE+;\n\nProperty:\n name=ID value=PROPERTY_VALUE;\n\nterminal PROPERTY_VALUE: (\':\' | \'=\') !(\'\\n\' | \'\\r\')*;\n\nterminal WS:\n (\' \' | \'\\t\')+;\n\nterminal NEWLINE:\n// New line on DOS or Unix \n \'\\r\'? \'\\n\';\n\nterminal ID:\n (\'A\'..\'Z\' | \'a\'..\'z\') (\'A\'..\'Z\' | \'a\'..\'z\' | \'_\' | \'-\' | \'0\'..\'9\')*;\n\nterminal SL_COMMENT:\n// Single line comment\n \'#\' !(\'\\n\' | \'\\r\')*;\nRun Code Online (Sandbox Code Playgroud)\n\n关键是,您不要试图仅在语法中涵盖完整的语义,还要考虑其他服务。终端规则PROPERTY_VALUE使用完整的值,包括前导赋值和可选的尾随分号。
现在只需为该语言注册一个值转换器服务并处理输入的无关紧要的部分:
\n\nimport org.eclipse.xtext.conversion.IValueConverter;\nimport org.eclipse.xtext.conversion.ValueConverter;\nimport org.eclipse.xtext.conversion.ValueConverterException;\nimport org.eclipse.xtext.conversion.impl.AbstractDeclarativeValueConverterService;\nimport org.eclipse.xtext.conversion.impl.AbstractIDValueConverter;\nimport org.eclipse.xtext.conversion.impl.AbstractLexerBasedConverter;\nimport org.eclipse.xtext.nodemodel.INode;\nimport org.eclipse.xtext.util.Strings;\n\nimport com.google.inject.Inject;\n\npublic class PropertyConverters extends AbstractDeclarativeValueConverterService {\n @Inject\n private AbstractIDValueConverter idValueConverter;\n\n @ValueConverter(rule = "ID")\n public IValueConverter<String> ID() {\n return idValueConverter;\n }\n\n @Inject\n private PropertyValueConverter propertyValueConverter;\n\n @ValueConverter(rule = "PROPERTY_VALUE")\n public IValueConverter<String> PropertyValue() {\n return propertyValueConverter;\n }\n\n public static class PropertyValueConverter extends AbstractLexerBasedConverter<String> {\n\n @Override\n protected String toEscapedString(String value) {\n return " = " + Strings.convertToJavaString(value, false);\n }\n\n public String toValue(String string, INode node) {\n if (string == null)\n return null;\n try {\n String value = string.substring(1).trim();\n if (value.endsWith(";")) {\n value = value.substring(0, value.length() - 1);\n }\n return value;\n } catch (IllegalArgumentException e) {\n throw new ValueConverterException(e.getMessage(), node, e);\n }\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n在运行时模块中注册服务后,以下测试用例将会成功:
\n\n@Override\npublic Class<? extends IValueConverterService> bindIValueConverterService() {\n return PropertyConverters.class;\n}\nRun Code Online (Sandbox Code Playgroud)\n\n测试用例:
\n\nimport org.junit.runner.RunWith\nimport org.eclipse.xtext.junit4.XtextRunner\nimport org.xtext.example.mydsl.MyDslInjectorProvider\nimport org.eclipse.xtext.junit4.InjectWith\nimport org.junit.Test\nimport org.eclipse.xtext.junit4.util.ParseHelper\nimport com.google.inject.Inject\nimport org.xtext.example.mydsl.myDsl.PropertyFile\nimport static org.junit.Assert.*\n\n@RunWith(typeof(XtextRunner))\n@InjectWith(typeof(MyDslInjectorProvider))\nclass ParserTest {\n\n @Inject\n ParseHelper<PropertyFile> helper\n\n @Test\n def void testSample() {\n val file = helper.parse(\'\'\'\n [Section1]\n a = Easy123\n b : This *is* valid too;\n\n [Section_2]\n # comment\n c = Voil\xc3\xa0 # inline comments are ignored\n \'\'\')\n assertEquals(2, file.sections.size)\n val section1 = file.sections.head\n assertEquals(2, section1.properties.size)\n assertEquals("a", section1.properties.head.name)\n assertEquals("Easy123", section1.properties.head.value)\n assertEquals("b", section1.properties.last.name)\n assertEquals("This *is* valid too", section1.properties.last.value)\n\n val section2 = file.sections.last\n assertEquals(1, section2.properties.size)\n assertEquals("Voil\xc3\xa0 # inline comments are ignored", section2.properties.head.value)\n }\n\n}\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
5389 次 |
| 最近记录: |