go语言的ast生成简述(解析,函数参数和返回值部分) | 青训营笔记

443 阅读3分钟

这是我参与「第五届青训营」伴学笔记创作活动的第 9 天

接下来以对函数声明的编译解析parser的执行逻辑。从func (p *parser) parseFuncDecl() *ast.FuncDecl开始。

// 函数声明前的注释作为文档
doc := p.leadComment
// 函数声明一定是func开头的
pos := p.expect(token.FUNC)

接下来开始判断这是一个函数还是一个方法,如果是一个方法的话他会有一个圆括号token内部是结构体对象的名字和结构体的类型(是否是类型的指针)。

var recv *ast.FieldList
if p.tok == token.LPAREN {
    _, recv = p.parseParameters(false)
}

接下来会读取一个标识符token代表函数或方法的名字,这个时可能有人有疑问,go语言不是函数为first class value的吗为什么会直接读取一个标识符方法,读到其他的认为发生了错误?这是因为对于可以作为语句的函数使用的是语句的读取方法进行语法分析。方法名读取完成之后就是读取泛型参数、函数参数和返回值列表,这里这些值是挨个调用相应方法构建,在直接读取函数类型时还有专门的一个方法用来读取函数方法,可以来对应函数声明ast中的函数签名。

// 读取函数名
ident := p.parseIdent()

// 读取泛型参数和函数参数
tparams, params := p.parseParameters(true)
if recv != nil && tparams != nil {
    // 方法声明不能有泛型
    // better error message and improved error recovery.
    p.error(tparams.Opening, "method must have no type parameters")
    tparams = nil
}
// 读取返回值列表,因为可以有命名返回值语法,因此
results := p.parseResult()

接下来的代码就是读取整个函数的函数体部分,也就是块语句部分。这部分对于一个函数声明是可选的,这是因为函数声明也可以存在于接口声明中,所以也可能函数的返回值后直接接一个分号。

var body *ast.BlockStmt
switch p.tok {
    // 紧接一个左花括号,因为换行会被词法分析器插入一个分号
    case token.LBRACE:
        body = p.parseBody()
        p.expectSemi()
    case token.SEMICOLON:
        p.next()
        if p.tok == token.LBRACE {
            // 发现一个分号后接一个左花括号,认为块语句换行了,记录错误
            p.error(p.pos, "unexpected semicolon or newline before {")
            body = p.parseBody()
            p.expectSemi()
        }
    default:
        p.expectSemi()
}

最后就是将所有语法分析得到的结果装入函数声明的ast中返回结果即可。

decl := &ast.FuncDecl{
    Doc:  doc,
    Recv: recv,
    Name: ident,
    Type: &ast.FuncType{
	Func:       pos,
        // 下面三个变量都是*ast.FieldList
        // 因为都可能有名字的标识符
	TypeParams: tparams,
	Params:     params,
	Results:    results,
    },
    Body: body,
}
return decl

接下来看一下是如何分析类型参数和函数参数的,这部分代码是调用Parser结构体的方法func (p *parser) parseParameters(acceptTParams bool) (tparams, params *ast.FieldList)它会先根据传入的参数判断是否允许并支持开启泛型,如果允许并支持泛型的话,就先解析一个由方括号括起来的泛型的类型参数。当处理完泛型的类型参数接下来就是解析函数参数,这部分是由圆括号括起来的。在检查完括号类型来区分完参数种类之后,两者都会调用同一个函数parseParameterList。这个函数比较长,大概就是根据参数中给出的结束括号符号判断是哪种参数,并将参数放入列表,这部分还要解析是否是标识符加类型,还是纯类型。这部分判断是在函数parseParamDecl中实现的,他会先读入一个标识符,如果标识符后跟的还是一个标识符或是与类型相关的标识符,那后面一定是一个类型,否则可能是接逗号,那么就只是一个类型。这个方法还有很多其他情况的处理,这里不一一列举了。

然后看解析返回值得函数parseResult,这个函数的逻辑就比较简单了,内部就是看是不是由括号括起的多个返回值,如果是直接调用前面的parseParameters解析即可,否则就尝试解析一个类型为返回值的类型。