【问题标题】:How to write a recursive descent parser? [closed]如何编写递归下降解析器? [关闭]
【发布时间】:2013-02-10 03:06:41
【问题描述】:

C++ 我不知道如何为以下语法编写递归下降解析器:

<Datalog Program>     ->  Schemes : <Scheme List>     
                         Facts   : <Fact List>
                         Rules   : <Rule List>
                         Queries : <Query List>
                         <EOF>
<Scheme List>         ->  <Scheme> <Scheme List Tail>
<Scheme List Tail>    ->  <Scheme List> | ε
<Scheme>              ->  <Identifier> ( <Identifier List> )
<Identifier List>     ->  <Identifier> <Identifier List Tail>
<Identifier List Tail>->  , <Identifier List> | ε
<Fact List>           ->  <Fact> <Fact List> | ε
<Fact>                ->  <Identifier> ( <Constant List> ) .
<Constant List>       ->  <String> <Constant List Tail>
<Constant List Tail>  ->  , <Constant List> | ε
<Rule List>           ->  <Rule> <Rule List> | ε
<Rule>                ->  <Head Predicate> :- <Predicate List> .
<Head Predicate>      ->  <Identifier> ( <Identifier List> )
<Predicate List>      ->  <Predicate> <Predicate List Tail>
<Predicate List Tail> ->  , <Predicate List> | ε
<Predicate>           ->  <Identifier> ( <Parameter List> )
<Parameter List>      ->  <Parameter> <Parameter List Tail>
<Parameter List Tail> ->  , <Parameter List> | ε
<Parameter>           ->  <String> | <Identifier> | <Expression>
<Expression>          -> ( <Parameter> <Operator> <Parameter> )
<Operator>            -> + | *
<Query List>          ->  <Query> <Query List Tail>
<Query List Tail>     ->  <Query List> | ε
<Query>               ->  <Predicate> ?

这是一个简单的类似数据记录的语法。我在尝试编写解析器时完全迷失了。我已经编写了一个词法分析器,它输出一个带有所有标记的向量。我知道我需要为每个产品编写方法,但我不知道如何将标记连接到解析树中(因此我可以在树完成后运行 toString() 函数)。我需要指出正确的方向。谢谢。

【问题讨论】:

  • 这件事有很多文本,它被处理为e.g.在神话龙的书中,Aho、Sethi、Ullman 的《编译器:原理、技术和工具》。 IIRC 在 Wirth 的“算法 + 数据结构 = 程序”(这可能是他的另一本书)中有一个关于构建递归下降解析器的非常清晰的解释。
  • 愚蠢的问题:你不能使用像 bison 或 byacc 这样的工具吗?以这种方式编写/维护解析器的源代码要容易得多。这些(和其他)工具提供独立的 C 程序(或 C++ 也用于野牛)。
  • 老实说,正确分解语法并删除左递归是困难的部分,看起来你已经在那里了。在这一点上,这个东西应该几乎写成它自己。假设您有一个合适的词法分析器,您可以将每个产生式视为一个要调用的函数,并在此过程中撒上令牌获取以验证下一个产生式路径。做出正确的决定,在首先充分发挥作用之前,不要走捷径。
  • @vonbrand 我要去看看 GNU bison - 谢谢。
  • @WhozCraig 我知道此时大部分工作已经完成——但我无法完全理解递归下降解析器。生产方法内部发生了什么?完整的树是如何构建和连接的?

标签: c++ parsing compiler-construction


【解决方案1】:

鉴于你已经写过,它主要是从 EBNF 到 C++ 源代码的机械翻译,所以(例如)这样的规则:

<Scheme>              ->  <Identifier> ( <Identifier List> ) 
<Identifier List>     ->  <Identifier> <Identifier List Tail>
<Identifier List Tail>->  , <Identifier List> | ε

翻译成这个一般顺序的东西(虽然要小心 - 我只是在脑海中输入这个,所以它根本没有经过测试;把它想象成类似 C 的伪代码而不是按原样编译)。

bool scheme() { 
    return identifier()        &&
           check_input('(')    &&
           identifier_list()   &&
           check_input(')');
}

bool identifier_list() { 
    if (identifier()) {
        if (check_input(','))
            return identifier_list();
        else
            return true;
    }
    return false;
}

bool identifier() { 
    // you haven't said what's a legal identifier, so for the moment I'm assuming
    // rules roughly like C's.
    int ch;
    std::string id;

    if (!((ch=getinput()) == '_' || isalapha(ch))
        return false;

    do { 
        id += ch;
        ch = getinput();
    } while (isalpha(ch) || isdigit(ch) || ch == '_');
    putback(ch);
    return true;
}

一旦你让它正确识别输入(并构建标识符等,就像我在上面所做的那样,即使我什么也没做),你就可以开始构建一个解析树(或其他)添加东西当你认出它们时,就到树上。如果你在 C 中这样做,你通常使用联合。在 C++ 中,您可能希望使用类层次结构来代替(尽管它会是一个非常短而复杂的层次结构——事实上,很容易所有其他层次结构都直接从基类派生而来)。

【讨论】:

    【解决方案2】:

    要查看语法中的内容并不容易,至少如果一个人不知道 Datalog 或任何一种语言。

    如果您对终端和制作使用了不同的标记,它会容易得多 - 不仅可以为不知情的人阅读,而且还可以为您自己阅读。找找找找找找找找找谁是肯定是有可能的,但是好像让我有点头晕。

    看看你的语法,你似乎不会得到一个相当复杂的树。因为所有物品都已订购。

    我的建议是根据您的语法对树进行建模,即数据结构: DatalogProgram Schmema Fact Rule 和 Query 以及 Expression HeadPredicate 和 Predicate

    这样你就可以让你的 Parser 工作看起来像

    DatalogProgram parse_datalog_progrem (pos){
       DatalogProgram program = new
    
       while ( Scheme s = parse_scheme () )
           program.append_scheme (s);
    
       while ( Fact f = parse_fact () )
           program.append_scheme (f);
    
       while ( Rule r = parse_rule () )
           program.append_rule (r);
    
       while ( Query q = parse_query () )
           program.append_query (q);
    
       return program; 
    }
    
    Scheme parse_scheme () {
       Scheme s = new ...
       s.id = parse_identifier ();
    
       parse ('(')
    
       while (Identifier i = parse_identifier) {
         s.append_id_list (i);
         if (lookahead != ',')
            break;
         parse (',');
       }
       parse (')');
       return s;
    }
    
    ....
    

    ...等等。我想你明白了。

    似乎仍有一件事或另一件事需要解决,但我认为这是最好的方法。你例如可能想考虑在数据结构中使用联合,例如

    struct Parameter {
      enum ParamType type;
      union {
         String  str;
         Identifier id;
         Expression expr;
      }
    }
    

    还有很长的路要走,但希望你喜欢这个方向。

    .. 哦和 DatalogProgram 之类的:

    struct DatalogProgram {
      struct Scheme *schemes;
      struct Fact *facts;
      struct Rule *rules;
      struct Query *queries;
    }
    

    用于分析树的根。

    【讨论】:

      猜你喜欢
      • 2014-06-01
      • 1970-01-01
      • 2015-08-07
      • 1970-01-01
      • 1970-01-01
      • 2012-05-21
      • 1970-01-01
      • 2016-07-06
      相关资源
      最近更新 更多