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{"(", ")"}))
}