Parsing 如何实现一个BNF语法树来解析GO中的输入?
类型语言的语法如下所示:Parsing 如何实现一个BNF语法树来解析GO中的输入?,parsing,types,go,grammar,bnf,Parsing,Types,Go,Grammar,Bnf,类型语言的语法如下所示: TYPE ::= TYPEVAR | PRIMITIVE_TYPE | FUNCTYPE | LISTTYPE; PRIMITIVE_TYPE ::= ‘int’ | ‘float’ | ‘long’ | ‘string’; TYPEVAR ::= ‘`’ VARNAME; // Note, the character is a backwards apostrophe! VARNAME ::= [a-zA-Z][a-zA-Z0-9]*; // Initial lett
TYPE ::= TYPEVAR | PRIMITIVE_TYPE | FUNCTYPE | LISTTYPE;
PRIMITIVE_TYPE ::= ‘int’ | ‘float’ | ‘long’ | ‘string’;
TYPEVAR ::= ‘`’ VARNAME; // Note, the character is a backwards apostrophe!
VARNAME ::= [a-zA-Z][a-zA-Z0-9]*; // Initial letter, then can have numbers
FUNCTYPE ::= ‘(‘ ARGLIST ‘)’ -> TYPE | ‘(‘ ‘)’ -> TYPE;
ARGLIST ::= TYPE ‘,’ ARGLIST | TYPE;
LISTTYPE ::= ‘[‘ TYPE ‘]’;
我的输入是这样的:输入
例如,如果我输入(int,int)->float,这是有效的。如果我输入([int],int),它是一个错误的类型并且无效
我需要解析来自键盘的输入,并确定它在该语法下是否有效(用于以后的类型推断)。然而,我不知道如何用go构建这个语法,以及如何按每个字节解析输入。是否有任何提示或类似的实现?这将非常有用。出于您的目的,类型语法看起来足够简单,您应该能够编写一个与语法形状大致匹配的语法 作为一个具体的例子,假设我们正在识别一种类似的语言
TYPE ::= PRIMITIVETYPE | TUPLETYPE
PRIMITIVETYPE ::= 'int'
TUPLETYPE ::= '(' ARGLIST ')'
ARGLIST ::= TYPE ARGLIST | TYPE
与您原来的问题不完全相同,但您应该能够看到相似之处
递归下降解析器由每个产生式规则的函数组成
func ParseType(???) error {
???
}
func ParsePrimitiveType(???) error {
???
}
func ParseTupleType(???) error {
???
}
func ParseArgList(???) error {
???
}
在这里,我们将把我们不太清楚的东西表示为?*,直到我们到达那里。至少现在我们会说,如果我们不能解析,我们会得到一个错误
每个函数的输入是一些令牌流。在我们的例子中,这些令牌由以下序列组成:
"int"
"("
")"
我们可以想象,流
可能满足以下条件:
type Stream interface {
Peek() string // peek at next token, stay where we are
Next() string // pick next token, move forward
}
让我们按顺序遍历令牌流
lexer负责获取字符串或io.Reader之类的内容,并生成此字符串标记流。lexer非常容易编写:您可以想象使用regexp或类似的工具将字符串分解为令牌
假设我们有一个令牌流,那么解析器只需要处理该流和一组非常有限的可能性。如前所述,每个产生式规则对应于一个解析函数。在产生式规则中,每个备选方案都是一个条件分支。如果语法特别简单(就像你的一样!),我们就可以知道应该使用哪个条件分支
例如,让我们看一下TYPE
及其相应的ParseType
函数:
TYPE ::= PRIMITIVETYPE | TUPLETYPE
PRIMITIVETYPE ::= 'int'
TUPLETYPE ::= '(' ARGLIST ')'
这与ParseType
的定义如何对应
该产品表示有两种可能性:它可以是(1)原语,也可以是(2)元组。我们可以查看令牌流:如果我们看到“int”
,那么我们知道它是原始的。如果我们看到一个”(“
”,那么由于唯一的可能性是它是元组类型,我们可以调用tupletype解析器函数,让它做一些不必要的工作
重要的是要注意:如果我们既看不到”(“
也看不到“int”
),那么就出现了可怕的错误!我们从语法上就知道了这一点。我们可以看到,每种类型都必须首先从这两个标记之一开始解析
好的,让我们编写代码
func ParseType(s Stream) error {
peeked := s.Peek()
if peeked == "int" {
return ParsePrimitiveType(s)
}
if peeked == "(" {
return ParseTupleType(s)
}
return fmt.Errorf("ParseType on %#v", peeked)
}
分析原语类型和元组类型同样直接
func ParsePrimitiveType(s Stream) error {
next := s.Next()
if next == "int" {
return nil
}
return fmt.Errorf("ParsePrimitiveType on %#v", next)
}
func ParseTupleType(s Stream) error {
lparen := s.Next()
if lparen != "(" {
return fmt.Errorf("ParseTupleType on %#v", lparen)
}
err := ParseArgList(s)
if err != nil {
return err
}
rparen := s.Next()
if rparen != ")" {
return fmt.Errorf("ParseTupleType on %#v", rparen)
}
return nil
}
唯一可能导致一些问题的是参数列表的解析器
ARGLIST ::= TYPE ARGLIST | TYPE
如果我们试图编写函数ParseArgList
,我们可能会陷入困境,因为我们还不知道该做什么选择。我们选择第一个还是第二个选择
好吧,让我们至少解析出两个备选方案的共同部分:类型部分
func ParseArgList(s Stream) error {
err := ParseType(s)
if err != nil {
return err
}
/// ... FILL ME IN. Do we call ParseArgList() again, or stop?
}
我们已经分析了前缀。如果是第二种情况,我们就完成了。但是如果是第一种情况呢?那么我们仍然需要阅读其他类型列表
啊,但是如果我们继续阅读其他类型,那么流必须首先从另一个类型开始。我们知道所有类型都首先从“int”
或(“
)开始。因此我们可以查看流。我们选择第一个还是第二个选择取决于此
func ParseArgList(s Stream) error {
err := ParseType(s)
if err != nil {
return err
}
peeked := s.Peek()
if peeked == "int" || peeked == "(" {
// alternative 1
return ParseArgList(s)
}
// alternative 2
return nil
}
信不信由你,这差不多就是我们所需要的了
当然,这是一个玩具解析器,因为它不能很好地处理某些类型的错误(比如输入过早结束),和标记不仅应该包括它们的文本内容,还应该包括它们的源位置,以便进行良好的错误报告。出于您自己的目的,您还需要扩展解析器,以便它们不仅返回错误
,而且还从解析中返回某种有用的结果
这个答案只是一个关于递归下降解析器如何工作的草图。但你们真的应该读一本好的编译器书籍来获得细节,因为你们需要它们。例如,the,the,the,the,the,the,the,至少花了一个很好的章节来讲述如何编写递归下降解析器,其中包含大量的技术细节。特别是,你们想知道这个概念第一组的t(我暗示过),因为在编写每个解析器函数时,您需要了解它们,以选择合适的替代方案。这个问题似乎离题了,因为它要求我们做家庭作业。非常感谢您的详细回答!您的代码非常明确,这对我很有帮助,因为我是新手,只需学习就可以了请告诉我编译器理论的基本知识。我会根据你的提示进行练习,看看是否还有其他问题。再次感谢你!@dyoo:请看后续问题。你正在做一个学生的家庭作业。@peterSO:是的,我故意简化和更改语法,就是为了这个原因,这样复制和粘贴就不起作用了。这是一个卑鄙的s-express离子分析器。我有点失望,学生似乎没有做任何自己的工作。
package main
import "fmt"
type Stream interface {
Peek() string
Next() string
}
type TokenSlice []string
func (s *TokenSlice) Peek() string {
return (*s)[0]
}
func (s *TokenSlice) Next() string {
result := (*s)[0]
*s = (*s)[1:]
return result
}
func ParseType(s Stream) error {
peeked := s.Peek()
if peeked == "int" {
return ParsePrimitiveType(s)
}
if peeked == "(" {
return ParseTupleType(s)
}
return fmt.Errorf("ParseType on %#v", peeked)
}
func ParsePrimitiveType(s Stream) error {
next := s.Next()
if next == "int" {
return nil
}
return fmt.Errorf("ParsePrimitiveType on %#v", next)
}
func ParseTupleType(s Stream) error {
lparen := s.Next()
if lparen != "(" {
return fmt.Errorf("ParseTupleType on %#v", lparen)
}
err := ParseArgList(s)
if err != nil {
return err
}
rparen := s.Next()
if rparen != ")" {
return fmt.Errorf("ParseTupleType on %#v", rparen)
}
return nil
}
func ParseArgList(s Stream) error {
err := ParseType(s)
if err != nil {
return err
}
peeked := s.Peek()
if peeked == "int" || peeked == "(" {
// alternative 1
return ParseArgList(s)
}
// alternative 2
return nil
}
func main() {
fmt.Println(ParseType(&TokenSlice{"int"}))
fmt.Println(ParseType(&TokenSlice{"(", "int", ")"}))
fmt.Println(ParseType(&TokenSlice{"(", "int", "int", ")"}))
fmt.Println(ParseType(&TokenSlice{"(", "(", "int", ")", "(", "int", ")", ")"}))
// Should show error:
fmt.Println(ParseType(&TokenSlice{"(", ")"}))
}