C#中的语法生产类实现

时间:2010-10-21 11:36:20

标签: c# grammar nlp context-free-grammar

语法定义包含产品,非常简单的语法示例:

E -> E + E
E -> n

我想在c#中实现语法类,但我不知道如何存储产品,例如如何区分终端和非终端符号。 我在考虑:

struct Production
{
   String Left;       // for example E
   String Right;      // for example +
}

左边将永远是非终端符号(它是关于无上下文的语法) 但是生产的右侧可以包含终端和终端。非终端符号

所以现在我想到了两种实施方式:

  1. 非终端符号将使用括号编写,例如:

    E + E将表示为字符串“[E] + [E]”

  2. 创建其他数据结构NonTerminal

    struct NonTerminal {   字符串符号; }

  3. 和E + E将表示为数组/列表:

    [new NonTerminal("E"), "+", new NonTerminal("E")]
    

    但是认为有更好的想法,听到一些回应会很有帮助

3 个答案:

答案 0 :(得分:4)

我用

 Dictionary<NonTerminalSymbol,Set<List<Symbol>>> 

允许通过Nonterminal查找与非终结符相关联的生产规则右侧(本身表示为终端/非终端符号列表)的。 (OP的问题表明非终结者E可能与两个规则相关联,但如果我们有左侧,我们只需要右侧)。

此表示仅适用于香草BNF语法定义,其中对于常见的语法定义习语没有语法糖。这些习语通常包括 choice Kleene star / plus ,......当它们在定义语法时可用时,你会得到一个所谓的扩展BNF或EBNF。如果我们只编写EBNF,只允许 | 表示选择,那么以OP为例暗示的平面形式的表达式语法就是:

         E = S ;
         S = P | S + P | S - P ; 
         P = T | P * T | P / T ;
         T = T ** M | ( E ) | Number | ID ;

我的第一个建议可以代表这一点,因为交替仅用于显示不同的规则右侧。但是,它不代表这一点:

         E = S ;
         S = P A* ;
         A = + P | - P ;
         P = T M+ ; -- to be different
         M = * T | / T ;
         T = T ** M | ( E ) | Number | ID | ID ( E  ( # | C) * ) ; -- function call with skipped parameters
         C = , E ;

这个附加符号引入的关键问题是能够在子语法定义上重复组合WBNF运算符,这就是EBNF的重点。

要表示EBNF,您必须将产品存储为 trees ,它们代表EBNF的表达式结构(实际上,这与表示任何表达式语法基本相同)。

要表示EBNF(表达式)树,您需要定义EBNF的树结构。 您需要树节点:

  • 符号(终端与否)
  • 轮换(列出备选方案)
  • Kleene *
  • Kleene +
  • “可选”?
  • 您认为您的EBNF具有运算符的其他人(例如,逗号列表,一种说法,其中一个语法元素列表由选定的“逗号”字符分隔,或以选定的“分号”字符结束,...)

最简单的方法是首先为EBNF本身编写EBNF语法:

EBNF = RULE+ ;
RULE = LHS "=" TERM* ";" ;
TERM = STRING | SYMBOL | TERM "*" 
       | TERM "+" | ';' STRING TERM | "," TERM STRING 
      "(" TERM* ")" ;

请注意,我已经在EBNF中添加了逗号和分号列表(扩展,还记得吗?)

现在我们可以简单地检查EBNF来决定需要什么。 你现在需要的是一组记录(好的,C#呃的类)来表示这些规则。 所以:

  • 包含一组规则的EBNF类
  • 具有LHS符号和LIST
  • 的规则的类
  • TERM的抽象基类,有几个具体的变体,每个替代TERM一个(所谓的“区分联合”通常通过继承实现,而instance_of检查使用OO语言)。

请注意,某些具体变体可以引用表示中的其他类类型,即获取树的方式。例如:

   KleeneStar inherits_from TERM {
        T: TERM:
   }

详细信息留待读者对其余部分进行编码。

这为OP引发了一个未声明的问题:你如何使用这个grammmar表示来驱动字符串的解析?

简单的答案是获取解析器生成器,这意味着您需要弄清楚它所使用的EBNF 。 (在这种情况下,将EBNF作为文本存储并将其交给该解析器生成器可能更容易,这使整个讨论变得没有意义。)

如果你不能得到一个(?),或想要建立自己的一个,那么,现在你有了你需要爬过来构建它的代表。另一种方法是构建一个由此表示驱动的递归下降解析器来进行解析。这样做的方法太大而无法包含在这个答案的边缘,但对于那些有递归经验的人来说很简单。

编辑10/22:OP澄清他坚持解析所有上下文无关语法和“特别是NL”。对于所有上下文无关语法,他将需要一个非常强大的解析引擎(Earley,GLR,完全回溯,......)。对于自然语言,他需要比那些更强大的解析器;几十年来,人们一直试图建立这样的解析器,只有一些,但绝对不容易,成功。这两个要求中的任何一个似乎都使代表语法的讨论毫无意义;如果他确实代表了一个直接的上下文无关语法,它就不会解析自然语言(这些人已经尝试了几十年证明了),如果他想要一个更强大的NL解析器,他需要简单地使用前沿类型的东西产生的。除非他决定成为NL解析领域的真正专家,否则请指望我对他可能成功的悲观主义。

答案 1 :(得分:2)

这是我存储制作的想法:

Dictionary<NonTerminalSymbol, List<Symbol>>

,其中

SymbolNonTerminalSymbolTerminalSymbolProduction

的父(抽象?)类

所以在你的例子中,字典会有一个键(“E”)和相应列表中的两个值(“[E] + [E]”和“n”)。

答案 2 :(得分:0)

对第二种方法使用扩展方法可能会有所帮助:

static class StringEx
{
   public static NonTerminal NonTerminal(this string obj)
   {
       return new NonTerminal(obj);
   }
}

所以看起来像这样

["E".NonTerminal(), "+", "E".NotTerminal()]

这种方法的优点是可以很容易地修改代码