了解Go编译处理(五)—— 顶层声明解析

前言

了解Go编译处理(四)—— go文件的解析一文初步介绍了.go文件的解析过程,本文主要关注具体的解析过程——顶层声明的解析。

更多内容分享,欢迎关注公众号:Go开发笔记

基本信息的获取

变量名/关键字的获取

在scanner的next处理中包含的关键字处理,即ident.

func (s *scanner) next() {
    ...
    if isLetter(c) || c >= utf8.RuneSelf && s.isIdentRune(c, true) {
        s.ident()
        return
    }
    ...
}

ident中包含了具体关键字的匹配处理,这里关键字是保存统一保存在一个string内,再通过一个数组记录所有关键字的起始位置。

tok对应的是关键字的结束位置。tok的处理是计算hash后存入map,在匹配时可根据hash获取对应的tok及tok对应的关键字。

func (s *scanner) ident() {
    s.startLit()

    // accelerate common case (7bit ASCII)
    c := s.getr()
    for isLetter(c) || isDecimal(c) {
        c = s.getr()
    }

    // general case
    if c >= utf8.RuneSelf {
        for s.isIdentRune(c, false) {
            c = s.getr()
        }
    }
    s.ungetr()

    lit := s.stopLit()

    // possibly a keyword
    if len(lit) >= 2 {
        if tok := keywordMap[hash(lit)]; tok != 0 && tokStrFast(tok) == string(lit) {
            s.nlsemi = contains(1<<_Break|1<<_Continue|1<<_Fallthrough|1<<_Return, tok)
            s.tok = tok
            return
        }
    }

    s.nlsemi = true
    s.lit = string(lit)
    s.tok = _Name
}

func tokStrFast(tok token) string {// 获取token对应的关键字
    return _token_name[_token_index[tok-1]:_token_index[tok]]
}

const _token_name = "EOFnameliteralopop=opop=:=<-*([{)]},;:....breakcasechanconstcontinuedefaultdeferelsefallthroughforfuncgogotoifimportinterfacemappackagerangereturnselectstructswitchtypevar"

var _token_index = [...]uint8{0, 3, 7, 14, 16, 19, 23, 24, 26, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 42, 47, 51, 55, 60, 68, 75, 80, 84, 95, 98, 102, 104, 108, 110, 116, 125, 128, 135, 140, 146, 152, 158, 164, 168, 171, 171}

func (i token) String() string {// 获取token对应的关键字
    i -= 1
    if i >= token(len(_token_index)-1) {
        return "token(" + strconv.FormatInt(int64(i+1), 10) + ")"
    }
    return _token_name[_token_index[i]:_token_index[i+1]]
}

func hash(s []byte) uint {
    return (uint(s[0])<<4 ^ uint(s[1]) + uint(len(s))) & uint(len(keywordMap)-1)
}

var keywordMap [1 << 6]token // size must be power of two

func init() {
    // populate keywordMap
    for tok := _Break; tok <= _Var; tok++ {
        h := hash([]byte(tok.String()))
        if keywordMap[h] != 0 {
            panic("imperfect hash")
        }
        keywordMap[h] = tok
    }
}

ident中前半部分是字符组的获取,获取后开始判断是变量名还是关键字。

keywordMap在初始化时,存入了关键字对应hash的tok。

匹配时,确认tok存在且对应的关键字与当前字符串相同,即为关键字;否则为变量名。如果为关键字,还需要标记是否将’\n’或EOF转译为’;’。

name的获取


func (p *parser) name() *Name {
    // no tracing to avoid overly verbose output

    if p.tok == _Name {
        n := p.newName(p.lit)
        p.next()
        return n
    }

    n := p.newName("_")
    p.syntaxError("expecting name")
    p.advance()
    return n
}
// IdentifierList = identifier { "," identifier } .
// The first name must be provided.
func (p *parser) nameList(first *Name) []*Name {
    if trace {
        defer p.trace("nameList")()
    }

    if debug && first == nil {
        panic("first name not provided")
    }

    l := []*Name{first}
    for p.got(_Comma) {
        l = append(l, p.name())
    }

    return l
}

name处理逻辑是在const、type、var之后紧接着就是变量名_Name,否则报错。
nameList则是以’,'分隔的多个声明变量的处理。

获取是否包含赋值

顶层声明中只能使用’=‘而不能使用’:=’,gotAssign是基于’='的处理,获取其后的值。

func (p *parser) gotAssign() bool {
    switch p.tok {
    case _Define:
        p.syntaxError("expecting =")
        fallthrough
    case _Assign:
        p.next()
        return true
    }
    return false
}

获取表达式列表

// ExpressionList = Expression { "," Expression } .
func (p *parser) exprList() Expr {
    if trace {
        defer p.trace("exprList")()
    }

    x := p.expr()
    if p.got(_Comma) {
        list := []Expr{x, p.expr()}
        for p.got(_Comma) {
            list = append(list, p.expr())
        }
        t := new(ListExpr)
        t.pos = x.Pos()
        t.ElemList = list
        x = t
    }
    return x
}

appendGroup

import,const ,type ,var可以将一组数据用括号括起来,其解析过程如下:

// appendGroup(f) = f | "(" { f ";" } ")" . // ";" is optional before ")"
func (p *parser) appendGroup(list []Decl, f func(*Group) Decl) []Decl {
    if p.tok == _Lparen {
        g := new(Group)
        p.list(_Lparen, _Semi, _Rparen, func() bool {
            list = append(list, f(g))
            return false
        })
    } else {
        list = append(list, f(nil))
    }

    if debug {
        for _, d := range list {
            if d == nil {
                panic("nil list entry")
            }
        }
    }

    return list
}
func (p *parser) list(open, sep, close token, f func() bool) Pos {
    p.want(open)

    var done bool
    for p.tok != _EOF && p.tok != close && !done {
        done = f()
        // sep is optional before close
        if !p.got(sep) && p.tok != close {
            p.syntaxError(fmt.Sprintf("expecting %s or %s", tokstring(sep), tokstring(close)))
            p.advance(_Rparen, _Rbrack, _Rbrace)
            if p.tok != close {
                // position could be better but we had an error so we don't care
                return p.pos()
            }
        }
    }

    pos := p.pos()
    p.want(close)
    return pos
}

如果存在’(’,说明存在声明组,处理是以’;‘为分隔,具体为各种类型的解析方式,最后以’('结束。

具体声明方式的处理

importDecl

import的格式: (PackageName) ImportPath

PackageName可忽略,对于指定的PackageName,存在特殊的名字".",这个需要特殊处理。

// ImportSpec = [ "." | PackageName ] ImportPath .
// ImportPath = string_lit .
func (p *parser) importDecl(group *Group) Decl {
    if trace {
        defer p.trace("importDecl")()
    }

    d := new(ImportDecl)
    d.pos = p.pos()

    switch p.tok {
    case _Name:
        d.LocalPkgName = p.name()
    case _Dot:
        d.LocalPkgName = p.newName(".")
        p.next()
    }
    d.Path = p.oliteral()
    if d.Path == nil {
        p.syntaxError("missing import path")
        p.advance(_Semi, _Rparen)
        return nil
    }
    d.Group = group

    return d
}

一次获取指定包名(可不指定)、路径,注意此处名称可以使用'.',即import . "x"'_'已包含在_Name中

constDecl

const的格式:

格式一:

cons1 ,cons2,... consType = expr1, expr2,...

consType可忽略,但每个cons必须要赋值

格式二:

(
    var1 = iota
    var2
    ...
)

存在iota时,除第一个外,其他可以不赋值

// ConstSpec = IdentifierList [ [ Type ] "=" ExpressionList ] .
func (p *parser) constDecl(group *Group) Decl {
    if trace {
        defer p.trace("constDecl")()
    }

    d := new(ConstDecl)
    d.pos = p.pos()

    d.NameList = p.nameList(p.name())
    if p.tok != _EOF && p.tok != _Semi && p.tok != _Rparen {
        d.Type = p.typeOrNil()
        if p.gotAssign() {
            d.Values = p.exprList()
        }
    }
    d.Group = group

    return d
}

依次获取常量名(列表),常量的类型(未指定时,d.Type记录为nil),及常量对应的value(存在赋值的情况下)

typeDecl

type的格式: defined_type = Type

=可以省略,必须要有声明的类型

// TypeSpec = identifier [ "=" ] Type .
func (p *parser) typeDecl(group *Group) Decl {
    if trace {
        defer p.trace("typeDecl")()
    }

    d := new(TypeDecl)
    d.pos = p.pos()

    d.Name = p.name()
    d.Alias = p.gotAssign()
    d.Type = p.typeOrNil()
    if d.Type == nil {
        d.Type = p.badExpr()
        p.syntaxError("in type declaration")
        p.advance(_Semi, _Rparen)
    }
    d.Group = group
    d.Pragma = p.pragma

    return d
}

处理是依次获取名称类型、是否包含赋值、类型Type(必须制定Type,否则非法),并记录在group。

varDecl

var的格式:

格式一

var1 ,var2,... varType = expr1, expr2,...

varType在有赋值的时候(每个var必须都有赋值)可忽略

varType指定时,=及赋值表达式可忽略

// VarSpec = IdentifierList ( Type [ "=" ExpressionList ] | "=" ExpressionList ) .
func (p *parser) varDecl(group *Group) Decl {
    if trace {
        defer p.trace("varDecl")()
    }

    d := new(VarDecl)
    d.pos = p.pos()

    d.NameList = p.nameList(p.name())
    if p.gotAssign() {
        d.Values = p.exprList()
    } else {
        d.Type = p.type_()
        if p.gotAssign() {
            d.Values = p.exprList()
        }
    }
    d.Group = group

    return d
}

处理依次:

  1. 获取变量名(列表)
  2. 如果有赋值(忽略Type),获取对应的表达式(不指定Type)
  3. 如果不忽略Type,获取变量类型(不能为nil),再根据是否有赋值,获取表达式

总结

本文主要介绍了.go文件中顶层声明import、const、type、var等解析的具体过程。这些解析过程实际也是go的规范,必须满足代码规范,才能正确解析。顶层声明中还有func的解析,由于func相对复杂,后面的文章中会单独说明。

公众号

鄙人刚刚开通了公众号,专注于分享Go开发相关内容,望大家感兴趣的支持一下,在此特别感谢。

你可能感兴趣的:(golang,源码分析,Go编译解析)