【问题标题】:Generating a parser for expressions为表达式生成解析器
【发布时间】:2021-10-21 17:02:00
【问题描述】:

我正在尝试为Microsoft article 中描述的 ebnf 语法生成一个 javascript 解析器。文章中指定的 ebnf 在我使用它时不起作用,因此我尝试对其进行简化以使其与 REx 解析器生成器一起使用。

目标是在 Javascript 中能够解析和评估这样的表达式为 True 或 False:

  • AttributeA > 2 - AttributeA的值大于2
  • HasCategory(Assembly) - 节点有分类组装
  • Assembly - 节点有分类组装
  • HasValue(AttributeA) - 属性 AttributeA 有一个值。它不是未定义的。
  • AttributeA < AttributeB - 属性 AttributeA 的值小于属性 Attribute B 的值
  • IsReference - 属性 IsReference 的值为 True
  • AttributeA + 2 > 5 and AttributeB - 5 != 7
  • AttributeA * 1.25 >= 500

我在这里在线使用 REx 解析器生成器:https://bottlecaps.de/rex/。如果您对生成 JavaScript 的其他解析器生成器有任何建议,我将不胜感激一些指向我可以找到它们的链接。

我正在努力解决的问题是 MethodCall 的定义。我尝试了很多不同的定义,但都失败了。当我删除 MethodCall 和 MethodArgs 定义时,REx 解析器生成器会生成一个解析器。

因此,如果您能帮我解决这个问题,我将不胜感激。

以下是我所能得到的语法。

Expression
    ::= BinaryExpression | MethodCall | "(" Expression ")" | Number
BinaryExpression
    ::= RelationalExpression ( ( '=' | '!=' ) RelationalExpression )*
RelationalExpression
    ::= AdditiveExpression ( ( '<' | '>' | '<=' | '>=' | 'and' | 'or' ) AdditiveExpression )*
AdditiveExpression
    ::= MultiplicativeExpression ( ( '+' | '-' ) MultiplicativeExpression )*
MultiplicativeExpression
    ::= UnaryExpression ( ( '*' | '/' | '%' ) UnaryExpression )*
UnaryExpression
    ::= "!" Identifier | "+" Identifier | "-" Identifier | Identifier

MethodCall
    ::= Identifier "(" MethodArgs* ")"
MethodArgs
    ::= Expression | Expression "," MethodArgs

Identifier
    ::= Letter ( Letter | Digit | "_" )*
Number
    ::= Digit ('.' Digit) | (Digit)*

<?TOKENS?>

Letter
    ::= [A-Za-z]
Digit
    ::= [0-9]

以下是我尝试过的 MethodCall 定义的一些不同版本,但都失败了。

MethodCall
    ::= Identifier "(" MethodArgs? ")"
MethodArgs
    ::= Expression ("," MethodArgs)*

MethodCall
    ::= Identifier "(" MethodArgs ")"
MethodArgs
    ::= ( Expression ("," MethodArgs)* )?

MethodCall
    ::= MethodName "(" MethodArgs? ")"
MethodName
    ::= Identifier
MethodArgs
    ::= Expression ("," MethodArgs)*

MethodCall
    ::= Identifier "(" MethodArgs ")"
MethodArgs
    ::= Expression ("," MethodArgs)* | ""

MethodCall
    ::= Identifier MethodArgs
MethodArgs
    ::= "(" (Expression ("," MethodArgs)* | "")  ")"

MethodCall
    ::= Identifier "(" MethodArgs ")"
MethodArgs
    ::= Expression | Expression "," MethodArgs | ""

MethodCall
    ::= Identifier "(" Expression ")"

我试图从其他一些语言中获得灵感,看看它们是如何做到的,但到目前为止还没有运气:

更新

只是想让您知道结果如何。我努力让 EBNF 语法做我需要做的事情,所以我像@rici 建议的那样查看了Nearley,并将我的语法转换为Nearley 语法并让工具工作。这对我的项目来说是一个更好的选择。文档非常好,工具也很棒,错误消息也很有帮助。非常感谢 @rici 建议 Nearley。

下面是我实现的语法。我已经使用以下输入进行了测试:

'2 + 4', '2 + 4 - 6', '(2 + 4)', '!true', '!(true)', 'hasCategory(test)', 'hasCategory(test,test2 )', 'hasCategory(test, test2)', 'hasCategory(test,test2, test3)', 'IsReference', 'IsReference()', '2 * 4', '(2 / 4)', '2 * 4 + 2'、'(2 / 4) + 2'、'2 > 4'、'2 >= 2'、'2 = 4'、'2 == 2'、'2 != 4'、'2 !== 2', '(2 * 4 + 2) > 4', '(2 * 4 + 2) > (4 + 10)', 'true', 'true or false', 'true || false', 'true and false', 'true && false', '(true or false)', '!(true or false)', '2 != 1+1', '2 != (1+1) '、'2 != (1+2)'、'(2 > 2 或 (2 != 1+1))'、

@builtin "whitespace.ne" # `_` means arbitrary amount of whitespace
@builtin "number.ne"     # `int`, `decimal`, and `percentage` number primitives
@builtin "postprocessors.ne"

@{%
function methodCall(nameId, argsId = -1) {
  return function(data) {
      return {
          type: 'methodCall',
          name: data[nameId],
          args: argsId == -1 ? [] : data[argsId]
      };
    }
}

function value() {
  return function(data) {
      return {
          type: 'value',
          value: data[0]
      };
    }
}
%}
expression -> 
    methodCall {% id %}
  | relationalExpression {% value() %}
  | booleanExpression  {% value() %}
  | _ identifier _  {% methodCall(1) %}

booleanExpression ->
      parentheses {% id %}
    | parentheses _ "and"i _ parentheses {% d => d[0] && d[4] %}
    | parentheses _ "&&" _ parentheses {% d => d[0] && d[4] %}
    | parentheses _ "or"i _ parentheses {% d => d[0] || d[4] %}
    | parentheses _ "||" _ parentheses {% d => d[0] || d[4] %}

parentheses ->
    _ "(" relationalExpression ")" _ {% nth(2) %}
  |  _ "(" booleanExpression ")" _ {% nth(2) %}
  | unaryExpression {% id %}

relationalExpression -> 
      _ additiveExpression _ {% nth(1) %}
    | relationalExpression _ "=" _ additiveExpression {% d => d[0] == d[4] %}
    | relationalExpression _ "==" _ additiveExpression {% d => d[0] == d[4] %}
    | relationalExpression _ "!=" _ additiveExpression {% d => d[0] != d[4] %}
    | relationalExpression _ "!==" _ additiveExpression {% d => d[0] != d[4] %}
    | relationalExpression _ "<" _ additiveExpression {% d => d[0] < d[4] %}
    | relationalExpression _ ">" _ additiveExpression {% d => d[0] > d[4] %}
    | relationalExpression _ "<=" _ additiveExpression {% d => d[0] <= d[4] %}
    | relationalExpression _ ">=" _ additiveExpression {% d => d[0] >= d[4] %}

additiveExpression -> 
    _ multiplicativeExpression _ {% nth(1) %}
  | additiveExpression _ "+" _ multiplicativeExpression {% d => d[0] + d[4] %}
  | additiveExpression _ "-" _ multiplicativeExpression {% d => d[0] - d[4] %}

multiplicativeExpression ->
    _ parentheses _  {% nth(1) %}
  | parentheses _ "*" _ parentheses {% d => d[0] * d[4] %}
  | parentheses _ "/" _ parentheses {% d => d[0] / d[4] %}
  | parentheses _ "%" _ parentheses {% d => d[0] % d[4] %}

unaryExpression ->  
    _ "!" _ expression _ {% d => !d[3] %}
  | _ decimal _ {% nth(1) %}
  | _ unsigned_int _ {% nth(1) %}
  | _ boolean _ {% nth(1) %}
  | _ identifier _ {% nth(1) %}

methodCall -> 
      identifier "(" methodArgs ")" {% methodCall(0, 2) %}
    | identifier "(" _ ")" {% methodCall(0) %}

methodArgs ->
    _ identifier _  {% d => [d[1]] %}
  | _ identifier _ "," _ methodArgs _ {% d => [d[1]].concat(d[5]) %}

boolean ->
    "true"i {% () => true %} 
  | "false"i {% () => false %}

identifier -> 
  [A-Za-z0-9_]:+ {% (data, l, reject) => {
    var ident = data[0].join('');
    if (ident.toLowerCase() === 'true' || ident.toLowerCase() === 'false') {
      return reject;
    } else {
      return ident;
    }
  }
   %}

【问题讨论】:

  • 正确的定义是MethodCall ::= Identifier "(" MethodArgs? ")"MethodArgs ::= Expression ("," Expression)*。你所有的例子都不正确;大多数也是模棱两可的。请注意,您还必须更改 Number 以使其与空虚不匹配。我建议Number ::= Digit+ ("." Digit*)? | "." Digit+。但它需要与标识符一起位于令牌部分。您还需要定义一个空格规则;否则andor 将很难匹配。还有很多其他的错误,不是所有的都在 kaby76 的回答中。
  • 您可能想查看nearly,它包含大量文档,包括教程。 (我用的不多,但我正在考虑从jison切换。)

标签: parsing grammar ebnf nearley


【解决方案1】:

你的语法有一些问题,但大部分都很好。

  • 'and' 和 'or' 与 Identifier 冲突。因此,在其规则中从 Identifier 中减去这些字符串文字。
  • Number 缺少括号。应该是Number ::= Digit ( ('.' Digit) | (Digit)* )
  • 您缺少 EOF 规则。我知道的几乎每个解析器生成器都需要一个底部/EOF 规则来强制消耗整个输入。我添加了“输入”规则。
  • 确保单击“配置”框,然后单击“回溯”。您的语法不明确,这很好,但需要您告诉解析器生成器来处理它。

解析器生成器的“EBNF”语法略有不同,这是 REx 所采用的。 REx 添加了一个&lt;?TOKENS?&gt; 字符串来表示解析器和词法分析器规则之间的边界。微软说语法是“BNF”,但这并不是因为它使用了 Kleene 运算符&lt;Identifier&gt; ::= [^. ]*,这是一个 EBNF 结构。它还用散文捏造了&lt;Literal&gt;&lt;Number&gt; 的定义。

我还没有测试生成的解析器,但它似乎是一个简单的递归下降实现。我熟悉且流行的解析器生成器列在conversion 页面中。 (我正在为所有这些以及更多内容编写转换器。)

试试这个:

Input ::= Expression EOF

Expression
    ::= BinaryExpression | MethodCall | "(" Expression ")" | Number

BinaryExpression
    ::= RelationalExpression ( ( '=' | '!=' ) RelationalExpression )*
RelationalExpression
    ::= AdditiveExpression ( ( '<' | '>' | '<=' | '>=' | 'and' | 'or' ) AdditiveExpression )*
AdditiveExpression
    ::= MultiplicativeExpression ( ( '+' | '-' ) MultiplicativeExpression )*
MultiplicativeExpression
    ::= UnaryExpression ( ( '*' | '/' | '%' ) UnaryExpression )*
UnaryExpression
    ::= "!" Identifier | "+" Identifier | "-" Identifier | Identifier

MethodCall
    ::= Identifier "(" MethodArgs* ")"
MethodArgs
    ::= Expression | Expression "," MethodArgs

<?TOKENS?>

Identifier
    ::= ( ( Letter ( Letter | Digit | "_" )* ) - ( 'and' | 'or' ) )
Number ::= Digit ( ('.' Digit) | (Digit)* )

Letter
    ::= [A-Za-z]
Digit
    ::= [0-9]

EOF      ::= $

【讨论】:

  • 非常感谢@kaby76,你的帮助让我再次行动起来。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-20
  • 2023-02-02
  • 1970-01-01
  • 1970-01-01
  • 2017-12-08
  • 2016-04-27
  • 1970-01-01
相关资源
最近更新 更多