Go语言-类型推断解析[2]

367次阅读  |  发布于2年以前

若干年后的纠葛,从茫茫戈壁上的相遇开始,早已冥冥注定...

类型推断依赖编译器的处理能力,编译器执行的过程为:词法解析=>语法分析=>类型检查=>中间代码=>代码优化=>生成机器码。编译阶段的代码位于go/src/cmd/compile文件中

词法解析与语法分析阶段

在词法解析阶段,会将赋值语句右边的常量解析为一个未定义的类型,例如,ImagLit代表复数,FloatLit 代表浮点数,IntLit代表整数。

//go/src/cmd/compile/internal/syntax
const (
 IntLit LitKind = iota
 FloatLit
 ImagLit
 RuneLit
 StringLit
)

Go语言源代码采用UTF-8的编码方式,在进行词法解析时当,遇到需要赋值的常量操作时,会逐个读取后面常量的UTF-8字符。字符串的首字符为",数字的首字符为'0'~'9'。实现位于syntax.next函数中。

// go/src/cmd/compile/internal/syntax
func (s *scanner) next() {
...
switch c {
    case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
        s.number(c)
    case '"':
        s.stdString()
    case '`':
        s.rawString()
    ...

因此对于整数、小数等常量的识别就显得非常简单。如图3-2所示,整数就是字符中全是“0”~“9”的数字,浮点数就是字符中有“.”号的数字,字符串的首字符为"或'。

图 词法解析阶段解析未定义的常量

下面列出的number函数为语法分析阶段处理数字的具体实现。数字首先会分被为小数部分与整数部分,通过字符. 进行区分。如果整数部分是以0开头的,则可能有不同的含义,例如0x代表十六进制数、0b代表二进制数。

// go/src/cmd/compile/internal/syntax
func (s *scanner) number(c rune) {
    // 整数部分
    var ds int
    if c != '.' {
        s.kind = IntLit
        if c == '0' {
            c = s.getr()
            switch lower(c) {
            case 'x':
                c = s.getr()
                base, prefix = 16, 'x'
            case 'o':
                c = s.getr()
                base, prefix = 8, 'o'
            case 'b':
                c = s.getr()
                base, prefix = 2, 'b'
            }
        }
        c, ds = s.digits(c, base, &invalid)
        digsep |= ds
    }
    // 小数部分
    if c == '.' {
        s.kind = FloatLit
        c, ds = s.digits(s.getr(), base, &invalid)
        digsep |= ds
    }

以赋值语句a := 333为例, 完成词法解析与语法分析时, 此赋值语句将以AssignStmt结构表示。

AssignStmt struct {
        Op       Operator 
        Lhs, Rhs Expr
        simpleStmt
    }

其中Op代表操作符,在这里是赋值操作OAS。Lhs与Rhs分别代表左右两个表达式,左边代表变量a,右边代表常量333,此时其类型为intLit。

抽象语法树生成与类型检查

完成语法解析后,进入抽象语法树阶段。在该阶段会将词法解析阶段生成的解析为一个Node,Node结构体是对抽象语法树中节点的抽象。

type Node struct {
    Left  *Node
    Right *Node
    Ninit Nodes
    Nbody Nodes
    List  Nodes
    Rlist Nodes
    Type *types.Type
    E   interface{} 
    ...

其中,Left(左节点)代表左边的变a,Right(右节点)代表整数333,其Op操作为。Right的E接口字段会存储值333,如果前一阶段为IntLit类型,则需要转换为Mpint类型。Mpint类型用于存储整数常量,具体结构如下所示。

// Mpint 代表整数常量
type Mpint struct {
    Val  big.Int
    Ovf  bool 
    Rune bool 
}

从Mpint类型的结构可以看到,在编译时AST阶段整数通过math/big.Int进行高精度存储,浮点数通过big.Float进行高精度存储

在类型检查阶段,右节点中的Type字段存储的类型会变为types.Types[TINT]。存储了不同标识对应的Go语言中的实际类型,其中,types.Types[TINT]对应Go语言内置的int类型。

接着完成最终的赋值操作,并将右边常量的类型赋值给左边变量的类型。具体实现位于typecheckas函数中。

func typecheckas(n *Node) {
    if n.Left.Name != nil && n.Left.Name.Defn == n && n.Left.Name.Param.Ntype == nil {
            n.Right = defaultlit(n.Right, nil)
            n.Left.Type = n.Right.Type
        }
    }
...
}

在SSA阶段,变量a中存储的大数类型的333最终会调用big.Int包中Int64函数将其转换为int64类型的常量,形如:v4 (?) = MOVQconst [333] (a[int])。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8