如何让Ragel解析由(space *“:”space *)分隔的两个名字?

时间:2012-01-23 06:37:08

标签: c parsing parser-generator lexical-analysis ragel

我想解析以下内容:

name:name

其中名称以alnum开头和结尾,并且可以包含alnum和space内部的任意组合。它们也可能是空白的。我的规则是:

identifier = alnum (space* alnum)*;
name       = (identifier | zlen) >sName $pName %fName;

名称可以用冒号分隔,也可以选择名称和冒号之间的空格。我的规则是:

sep = space* ":" space*;
main := name sep name;

这不起作用,因为显然space*中的identifierspace*中的sep会混淆解析器。我最终在名称的每个空格中执行了fName操作。

如果我将sep更改为:

sep = ":";
然后一切都很好。如何修改这些规则以便解析器完成我想要的操作?

此问题的源代码:https://gist.github.com/1661150

1 个答案:

答案 0 :(得分:10)

这类问题有两种基本解决方案。

  1. 定义操作,以便可以安全地多次执行,
  2. 更改语法,以便仅执行一次操作。
  3. 在这种情况下,我会选择混合方法。使用操作记录name的开始和结束位置:这些操作可以安全地执行多次,因为它们只是记录位置。一旦确定已超出名称,请执行仅执行一次的其他操作。

    /* C code */
    char *name_start, *name_end;
    
    /* Ragel code */
    action markNameStart { name_start = p; }
    action markNameEnd { name_end = p; }
    action nameAction {
        /* Clumsy since name is not nul-terminated */
        fputs("Name = ", stdout);
        fwrite(name_start, 1, name_end - name_start, stdout);
        fputc('\n', stdout);
    }
    
    name = space* %markNameStart
           (alnum+ %markNameEnd <: space*)+
           %nameAction ;
    main := name ":" name ;
    

    这里,name的语法包括任意空格和至少一个字母数字字符。遇到第一个字母数字字符时,其位置将保存在name_start中。每当字母数字字符的运行结束时,后续字符的位置将保存在name_end中。 <:在技术上是不必要的,但它会减少执行markNameEnd操作的频率。

    请确保不要在任何空格旁边放置这样的表达式。

    我没有测试过上面的代码。在使用之前,您应该查看状态机的Graphviz可视化。

    Ragel正在做什么

    使用原始代码,我们假设输入如下:

    Hello world : Goodbye world
    

    Ragel机器从左向右扫描,找到name的开头,并扫描字母数字字符。

    Hello world : Goodbye world
        ↑
    

    下一个角色是一个空格。所以我们要么在单词中遇到空格,要么在单词结尾后遇到第一个空格。 Ragel如何选择?

    Ragel同时选择这两个选项。这非常重要。 Ragel试图模拟非确定性有限自动机,但由于您的计算机是确定性的,最简单的方法是将NFA转换为DFA,它可以并行模拟无限数量的NFA。由于NFA具有有限数量的状态(因此名称),因此DFA也具有有限数量的状态,因此该技术起作用。

    遇到空格后,您有一个NFA处于以下状态,正在寻找name的其余部分:

    identifier = alnum (space* alnum)*;
                        ↑
    
    main := name sep name;
            ↑
    

    第二个NFA处于以下状态,它假定name已经结束(并且此NFA过早地执行了fName操作“):

    sep = space* ":" space*;
          ↑
    
    main := name sep name;
                 ↑
    

    对你来说很明显,对我来说很明显只有第一个NFA是正确的。但是使用Ragel创建的机器一次只能看一个字符,他们不会向前看,看看哪个选项是正确的。第二个NFA最终将遇到一个字母数字字符,它期望看到":",并且由于这是不允许的,第二个NFA将会消失。

    查看Ragel文档

    以下是%的说明:

    expr % action
    
         

    离开动作操作符将一个动作排队,以嵌入到转出的过渡中   通过最终状态的机器。

    对于不一定有助于成功解析的转换执行操作。有关Ragel中非确定性的更多信息,请参阅Ragel指南,第4章“控制非确定性”,尽管第4章中的技术在这种特殊情况下无法帮助您,因为您机器中的操作只能通过未绑定的前瞻来消除歧义,在有限状态机中是不允许的。