【问题标题】:How to deal with overlapping character groups in different tokens in an EBNF grammar?如何处理EBNF语法中不同标记中的重叠字符组?
【发布时间】:2010-06-15 14:26:37
【问题描述】:

我正在使用 LL(k) EBNF 语法来解析字符流。我需要三种不同类型的令牌:

CHARACTERS

  letter = 'A'..'Z' + 'a'..'z' .
  digit = "0123456789" .
  messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')' .

TOKENS

  num = ['-'] digit { digit } [ '.' digit { digit } ] .
  ident = letter { letter | digit | '_' } .
  message = messageChar { messageChar } .

前两个标记声明很好,因为它们不共享任何公共字符。

但是第三个 message 无效,因为某些字符串可能同时是 nummessage(例如 "123"),而其他字符串可能同时是 ident 和 @ 987654328@(如"Hello")。因此,分词器无法正确区分。

另一个例子是区分整数和实数。除非您要求所有实数至少有一位小数(意味着 1 需要编码为 1.0,这对我来说不是一个选项),否则我无法在语法中获得这两个数字之间差异的支持类型。我必须将所有值都表示为实数并在该点之后进行检查。这很好,但不是最理想的。我真正的问题是message 令牌。我找不到解决方法。

所以问题是,我可以用 LL(k) EBNF 语法来做到这一点吗?我正在使用CoCo/R 来生成解析器和扫描器。

如果我不能用 LL(k) EBNF 做到这一点,那么我还可以考虑哪些其他选择?

编辑这是我从 CoCo/R 得到的输出:

可可/R(2010 年 4 月 23 日) 令牌双重和消息无法区分 令牌 ident 和 message 无法区分 ... 检测到 9 个错误

【问题讨论】:

    标签: parsing language-agnostic token grammar ebnf


    【解决方案1】:

    试试这个:

    CHARACTERS
    
        letter = 'A'..'Z' + 'a'..'z' .
        digit = "0123456789" .
        messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')'  .
    
    TOKENS
    
        double = ['-'] digit { digit } [ '.' digit { digit } ] .
        ident = letter { letter | digit | '_' } .
        message = messageChar { messageChar } CONTEXT (")") .
    

    哦,我必须指出 '\u0020' 是 unicode SPACE,随后您将使用“- ' '”将其删除。哦,如果你不需要多个字符的前瞻,你可以使用CONTEXT (')')。这在您的情况下不起作用,因为上面的所有标记都可以出现在 ')' 之前。

    FWIW:CONTEXT 不使用封闭的序列,您仍必须在生产中使用它。

    编辑:

    好的,这似乎可行。真的,这次我是认真的:)

    CHARACTERS
        letter = 'A'..'Z' + 'a'..'z' .
        digit = "0123456789" .
    //    messageChar = '\u0020'..'\u007e' - ' ' - '(' - ')'  .
    
    TOKENS
    
        double = ['-'] digit { digit } [ '.' digit { digit } ] .
        ident = letter { letter | digit | '_' } .
    //    message = letter { messageChar } CONTEXT (')') .
    
    // MessageText<out string m> = message               (. m = t.val; .)
    // .
    
    HearExpr<out HeardMessage message> =
        (.
            TimeSpan time; 
            Angle direction = Angle.NaN; 
            string messageText = ""; 
        .)
        "(hear" 
        TimeSpan<out time>
            ( "self" | AngleInDegrees<out direction> )
    //         MessageText<out messageText>
        {
            ANY (. messageText += t.val; .)
        }
        ')'
        (. 
            message = new HeardMessage(time, direction, new Message(messageText)); 
        .)
        .
    

    ANY 将读取字符,直到它命中 ')' 或空格。我把它放在一个连接每个值的循环中,但你可能不想这样做。您可能希望将它放在一个循环中,这样当它看到“这里”而是“这里”时它不会返回“过度”。 您可以对 messageText 进行简单的长度检查,或进行其他有效性检查,例如将 t.val 添加到 List 并检查计数。真的什么都有。您还可以使用 RegEx 进行测试,以确保它符合您需要检查的任何模式。

    编辑(2011 年 4 月 8 日): 使用带有整数和实数的 Coco/R 示例

    COMPILER Calculator
    CHARACTERS
        digit       = "0123456789".
    
    TOKENS
        intNumber    = ['-'] digit { digit } .
        realNumber   = ['-'] { digit } "." digit { digit } 
                             [("e" | "E") ["+" | "-"] digit {digit}] .
    
    PRODUCTIONS
        Calculator  = { Expression "=" } .
        Expression  = Term { "+" Term | "-" Term }.
        Term        = Factor { "*" Factor | "/" Factor }.
        Factor      = intNumber | realNumber .
    
    END Calculator.
    

    编辑(2011 年 4 月 9 日)

    Factor<out double value>
        (. value = 0.0; .)
    = 
        ( 
            intNumber 
            (. value = Convert.ToDouble(t.val); .)
            | 
            realNumber 
            (. value = Convert.ToDouble(t.val); .)
        ) 
        | "(" Expression<out value> ")"         
    .
    

    Factor<out double value>
        (. value = 0.0; .)
    =
        ( intNumber | realNumber ) 
        (. value = Convert.ToDouble(t.val); .)
        | "(" Expression<out value> ")"
    .
    

    【讨论】:

    • 嗨,安德烈,谢谢。我刚刚开始重新审视这个问题并测试你的代码。我尝试了很多东西,但你的答案是唯一有效的。 CoCo/R 扫描仪似乎相当有限。例如,int 和 float 类型不可能有标记,因为它们以相同的方式重叠。无论如何,再次感谢!
    • Coco/R 的限制在于它是 LL(1),但您可以使用整数和浮点数的标记,如我添加的示例所示。你只需要一种区分的方法。
    • 啊,好吧,我明白我错过了什么。我想我希望的是realNumber 令牌可能有一个可选的小数位和实部。在您的示例中,1234 不是实数,即使在数学上它是。我想您可以在更新中定义与Factor 具有相同定义的产品Real。我很快就会玩这个。再次感谢您对此的专家帮助。如果可以的话,我会投票给你两次。
    • @Drew:在这个级别上,您并没有真正受到类型的约束。您可以定义一个与整数、实数等匹配的数字标记。然后在解析器中或通过遍历 AST 来计算类型。您可以在有意义的情况下将整数提升为实数。我再举一个例子。
    • @Andre,感谢您的更新。你所展示的与我现在所做的相差不远。我希望在需要 int 的地方捕捉到实数无效的事实,并让 CoCo/R 抛出某种错误,而不必在我的属性中明确这样做。我可能会玩这个,但是当我在解析 s 表达式时,我希望我能找到比 CoCo/R 更好的解决方案(特别是在解析之前不需要加载整个字符串的解决方案。)跨度>
    【解决方案2】:

    您可能想要研究具有上下文敏感标记化的 PEG 生成器。

    http://en.wikipedia.org/wiki/Parsing_expression_grammar

    我想不出你可以使用 COCO/R 或类似方法来解决这个问题,因为每个令牌都需要明确。

    如果消息被引号或其他消除歧义的方式包围,那么您不会有问题。我真的认为 PEG 可能是你的答案,因为它也有有序的选择(第一场比赛)。

    还可以看看:

    http://tinlizzie.org/ometa/

    【讨论】:

    • 太棒了。这听起来完全符合我的需要。我设法把它推迟到现在,所以你的答案是完美的。我正在考虑将所有标记合并到一个通用的“符号”定义中,但这听起来要好得多。会让你知道我是怎么过的。您能否评论任何潜在的性能影响?
    • 您可能会发现它稍慢,具体取决于解析器生成器。如果速度是一个问题,手工制作应该很容易。如果您能告诉我您打算针对哪种语言/平台构建(例如 Java/JVM、C#/.NET、C++),那么我可能会提出一些建议。
    • @Drew:如果您可以为要处理的输入提供一个经过净化的示例,那么这也可能会有所帮助。当我设计 DSL 时,我倾向于先编写一些示例,然后再从那里开始工作(这些示例也可以作为某些单元测试的输入)。
    • @Andre:其实我是在解析别人的格式。这是一系列 SExpressions。每个 SExpression 都应转换为不同的对象类型。我问了一个关于显式解析 SExpressions 的不同问题 (stackoverflow.com/questions/3051254),因为对于这种简单的结构化数据格式,可能不需要成熟的语法。您可以在此处查看数据示例:simspark.sourceforge.net/wiki/index.php/Perceptors 有几种重复模式。例如,(pol &lt;d&gt; &lt;phi&gt; &lt;theta&gt;) 应该映射到我的 PolarCoordinate 类型。
    • 我有一个 SExpressions 字符流:(...)(...)(...)...。理想情况下,我想直接处理流,并为系列中的每个表达式吐出一个对象。
    【解决方案3】:

    尽管有标题,但这一切似乎都与扫描仪有关,而不是解析器。我没有使用过 CoCo/R,所以我不能直接评论它,但是在一个典型的(例如,lex/Flex)扫描器中,规则是按顺序考虑的,所以选择的规则/模式是第一个火柴。我写的大多数扫描仪都包含一个“。” (即,匹配 anything)作为它们的最后一个模式,如果有一些输入不匹配任何其他规则,则显示错误消息。

    【讨论】:

    • 在 CoCo/R 中,您可以在一个文件中指定标记和语法。 CoCo/R 似乎正在检查这种歧义。我尝试重新排序我的声明,但没有看到任何区别。我再试几次。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-01
    • 1970-01-01
    • 2023-03-25
    • 2017-08-22
    • 1970-01-01
    • 2015-08-10
    • 1970-01-01
    相关资源
    最近更新 更多