了解Go编译处理(三)—— 初识go compile一文中,我们对接go compile的过程进行了初步的了解,从本篇起开始关注具体的处理部分。
本篇主要关注的是.go
文件的解析——parseFiles
。
更多内容分享,欢迎关注公众号:Go开发笔记
func Main(archInit func(*Arch)) {
...
timings.Start("fe", "parse")
lines := parseFiles(flag.Args())
timings.Stop()
...
}
parseFiles是在gc.Main
正式进行语法检查前进行的。
parseFiles负责具体的.go
文件解析。
// parseFiles concurrently parses files into *syntax.File structures.
// Each declaration in every *syntax.File is converted to a syntax tree
// and its root represented by *Node is appended to xtop.
// Returns the total count of parsed lines.
func parseFiles(filenames []string) uint {
noders := make([]*noder, 0, len(filenames))
// Limit the number of simultaneously open files.
sem := make(chan struct{}, runtime.GOMAXPROCS(0)+10)
for _, filename := range filenames {
p := &noder{
basemap: make(map[*syntax.PosBase]*src.PosBase),
err: make(chan syntax.Error),
}
noders = append(noders, p)
go func(filename string) {
sem <- struct{}{}
defer func() { <-sem }()
defer close(p.err)
base := syntax.NewFileBase(filename)
f, err := os.Open(filename)
if err != nil {
p.error(syntax.Error{Pos: syntax.MakePos(base, 0, 0), Msg: err.Error()})
return
}
defer f.Close()
p.file, _ = syntax.Parse(base, f, p.error, p.pragma, syntax.CheckBranches) // errors are tracked via p.error
}(filename)
}
var lines uint
for _, p := range noders {
for e := range p.err {
p.yyerrorpos(e.Pos, "%s", e.Msg)
}
p.node()
lines += p.file.Lines
p.file = nil // release memory
if nsyntaxerrors != 0 {
errorexit()
}
// Always run testdclstack here, even when debug_dclstack is not set, as a sanity measure.
testdclstack()
}
localpkg.Height = myheight
return lines
}
文件解析采用并发形式进行的,可以加快解析的速度,为了避免过多的文件打开占用资源,此处限制了打开文件的数量为设置的最大CPU核数+10
。
解析的结果通过带缓存的channel noders
进行传递,缓存大小为len(filenames),足以同时存储所有的解析结果。
通过noders
读取解析结果,若正常则构造node,然后释放资源,期间存在语法错误则会报错并退出,删除已生成文件。
具体的解析处理在syntax.Parse中进行。
先根据注释了解下Parse的处理。
Parse负责解析单个.go文件,然后返回对应的语法树。如果有错误,Parse将返回发现的第一个错误,以及可能部分构造的语法树,或者nil。
如果errh != nil,则在遇到每个错误时调用它,Parse将处理尽可能多的源代码。在这种情况下,仅当找不到正确的package子句时,返回的语法树才为nil。
如果errh为nil,则遇到第一个错误时,解析将立即终止,并且返回的语法树为nil。
如果pragh!= nil,则在遇到每个编译指示时都会调用它。
// Parse parses a single Go source file from src and returns the corresponding
// syntax tree. If there are errors, Parse will return the first error found,
// and a possibly partially constructed syntax tree, or nil.
//
// If errh != nil, it is called with each error encountered, and Parse will
// process as much source as possible. In this case, the returned syntax tree
// is only nil if no correct package clause was found.
// If errh is nil, Parse will terminate immediately upon encountering the first
// error, and the returned syntax tree is nil.
//
// If pragh != nil, it is called with each pragma encountered.
//
func Parse(base *PosBase, src io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) (_ *File, first error) {
defer func() {
if p := recover(); p != nil {
if err, ok := p.(Error); ok {
first = err
return
}
panic(p)
}
}()
var p parser
p.init(base, src, errh, pragh, mode)
p.next()
return p.fileOrNil(), p.first
}
Parse分为两步,init中将文件存入parser的src,next中负责对src中的内容一个个rune的进行解析。
p.init负责参数及scanner的初始化及error的处理func
func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh PragmaHandler, mode Mode) {
p.file = file
p.errh = errh
p.mode = mode
p.scanner.init(
r,
// Error and directive handler for scanner.
// Because the (line, col) positions passed to the
// handler is always at or after the current reading
// position, it is safe to use the most recent position
// base to compute the corresponding Pos value.
func(line, col uint, msg string) {
if msg[0] != '/' {
p.errorAt(p.posAt(line, col), msg)
return
}
// otherwise it must be a comment containing a line or go: directive
text := commentText(msg)
if strings.HasPrefix(text, "line ") {
var pos Pos // position immediately following the comment
if msg[1] == '/' {
// line comment (newline is part of the comment)
pos = MakePos(p.file, line+1, colbase)
} else {
// regular comment
// (if the comment spans multiple lines it's not
// a valid line directive and will be discarded
// by updateBase)
pos = MakePos(p.file, line, col+uint(len(msg)))
}
p.updateBase(pos, line, col+2+5, text[5:]) // +2 to skip over // or /*
return
}
// go: directive (but be conservative and test)
if pragh != nil && strings.HasPrefix(text, "go:") {
p.pragma |= pragh(p.posAt(line, col+2), text) // +2 to skip over // or /*
}
},
directives,
)
p.base = file
p.first = nil
p.errcnt = 0
p.pragma = 0
p.fnest = 0
p.xnest = 0
p.indent = nil
}
p.next负责依次对满足条件的rune进行解析,解析结束后会记录解析处的文件、行数、列数的信息。对于解析错误的语句提供具体的位置信息,交由init中的errorh负责。
注意:单次p.next只能获取满足的条件的rune,一个完整语句可能要调用多次p.next。对于一个go文件,最先解析到的信息,应该是package信息。
func (s *scanner) next() {
nlsemi := s.nlsemi
s.nlsemi = false
redo:
// skip white space
c := s.getr()//获取下一个rune
for c == ' ' || c == '\t' || c == '\n' && !nlsemi || c == '\r' {//如果是空格、tab、换行、回车,则重新获取下一个
c = s.getr()
}
// token start
// 记录行与列数
s.line, s.col = s.source.line0, s.source.col0
// 关键字、变量名(以字母或下划线开头,不能以数字开头)的处理,如package、var、type、等
if isLetter(c) || c >= utf8.RuneSelf && s.isIdentRune(c, true) {
s.ident()
return
}
// 特殊字符的处理
switch c {
case -1:
if nlsemi {
s.lit = "EOF"
s.tok = _Semi
break
}
s.tok = _EOF
case '\n': // 换行符
s.lit = "newline"
s.tok = _Semi
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': //数字
s.number(c)
case '"': // 字符串的引号
s.stdString()
case '`':// raw字符串的引号
s.rawString()
case '\'': // rune
s.rune()
case '(':
s.tok = _Lparen
case '[':
s.tok = _Lbrack
case '{':
s.tok = _Lbrace
case ',':
s.tok = _Comma
case ';':
s.lit = "semicolon"
s.tok = _Semi
case ')':
s.nlsemi = true
s.tok = _Rparen
case ']':
s.nlsemi = true
s.tok = _Rbrack
case '}':
s.nlsemi = true
s.tok = _Rbrace
case ':':
if s.getr() == '=' { // 对 := 的处理
s.tok = _Define //定义
break
}
s.ungetr()
s.tok = _Colon // :
case '.':
c = s.getr()
if isDecimal(c) { // 如果是float
s.ungetr()
s.unread(1) // correct position of '.' (needed by startLit in number)
s.number('.')
break
}
if c == '.' {
c = s.getr()
if c == '.' {
s.tok = _DotDotDot // ...
break
}
s.unread(1)
}
s.ungetr()
s.tok = _Dot // .
case '+':
s.op, s.prec = Add, precAdd // +
c = s.getr()
if c != '+' {
goto assignop
}
s.nlsemi = true
s.tok = _IncOp // ++
case '-':
s.op, s.prec = Sub, precAdd // -
c = s.getr()
if c != '-' {
goto assignop
}
s.nlsemi = true
s.tok = _IncOp // --
case '*':
s.op, s.prec = Mul, precMul
// don't goto assignop - want _Star token
if s.getr() == '=' { // *=
s.tok = _AssignOp
break
}
s.ungetr()
s.tok = _Star // *
case '/':// 注释
c = s.getr()
if c == '/' { // //注释
s.lineComment()
goto redo
}
if c == '*' { // /*注释
s.fullComment()
if s.source.line > s.line && nlsemi {
// A multi-line comment acts like a newline;
// it translates to a ';' if nlsemi is set.
s.lit = "newline"
s.tok = _Semi
break
}
goto redo
}
s.op, s.prec = Div, precMul
goto assignop
case '%':
s.op, s.prec = Rem, precMul
c = s.getr()
goto assignop
case '&':
c = s.getr()
if c == '&' { // &&
s.op, s.prec = AndAnd, precAndAnd
s.tok = _Operator
break
}
s.op, s.prec = And, precMul // &
if c == '^' { // &^
s.op = AndNot
c = s.getr()
}
goto assignop
case '|':
c = s.getr()
if c == '|' { // ||
s.op, s.prec = OrOr, precOrOr
s.tok = _Operator
break
}
s.op, s.prec = Or, precAdd // |
goto assignop
case '^':
s.op, s.prec = Xor, precAdd
c = s.getr()
goto assignop
case '<':
c = s.getr()
if c == '=' { // <=
s.op, s.prec = Leq, precCmp
s.tok = _Operator
break
}
if c == '<' { // <<
s.op, s.prec = Shl, precMul
c = s.getr()
goto assignop
}
if c == '-' { // <-
s.tok = _Arrow
break
}
s.ungetr()
s.op, s.prec = Lss, precCmp // <
s.tok = _Operator
case '>':
c = s.getr()
if c == '=' { // >=
s.op, s.prec = Geq, precCmp
s.tok = _Operator
break
}
if c == '>' { // >>
s.op, s.prec = Shr, precMul
c = s.getr()
goto assignop
}
s.ungetr()
s.op, s.prec = Gtr, precCmp // >
s.tok = _Operator
case '=':
if s.getr() == '=' { // ==
s.op, s.prec = Eql, precCmp
s.tok = _Operator
break
}
s.ungetr()
s.tok = _Assign // =
case '!':
if s.getr() == '=' { // !=
s.op, s.prec = Neq, precCmp
s.tok = _Operator
break
}
s.ungetr()
s.op, s.prec = Not, 0 // !
s.tok = _Operator
default:
s.tok = 0
s.errorf("invalid character %#U", c)
goto redo
}
return
assignop: // 赋值操作
if c == '=' {
s.tok = _AssignOp
return
}
s.ungetr()
s.tok = _Operator
}
扫描的过程是一个个rune进行的。如果是空格、tab、换行、回车,会直接忽略,重新获取下一个rune;如果是数字、字母、下划线(即变量名或关键字)等,则获取完整的名称。特殊的操作符则添加标识说明。最后在fileOrNil中继续获取下一个rune,直到文件结束。
扫描的过程中包含了我们熟悉的各种的符合的处理和标记。
正常情况下,第一次p.next获取到的应该是package
,如果不是说明.go
文件内容有问题。
在next还可以看到,在换行、文件等结束会默认添加分号;
,所以fileOrNil中会在语法的结束时校验_Semi
。
.go
文件的内容基本格式:
package package_name
import "import_package_1"
import name2 "import_package_2"
// or
import (
"import_package_3"
name4 "import_package_4"
)
var variables ...
var (
...
)
type defined_types ...
type (
...
)
func functions ...
fileOrNil就是是从文件开头检查整个文件,首先检查package及import,然后循环检查顶级声明(const、type、var、func),直至文件结束(或发生错误)。
具体解析时,循环查找时,确认关键字,然后根据是否存在’(’,确定是否是多个值的组合,进行单个或多个值的解析。func还会对func body内部进行解析。
// SourceFile = PackageClause ";" { ImportDecl ";" } { TopLevelDecl ";" } .
func (p *parser) fileOrNil() *File {
if trace {
defer p.trace("file")()
}
f := new(File)
f.pos = p.pos()
// PackageClause
// go文件从package开始,因此先检查package
if !p.got(_Package) {// 检查并获取package name
p.syntaxError("package statement must be first")
return nil
}
// 获取package名称
f.PkgName = p.name()
p.want(_Semi) // 检查';'
// don't bother continuing if package clause has errors
if p.first != nil {
return nil
}
// { ImportDecl ";" }
// import的处理
for p.got(_Import) {
f.DeclList = p.appendGroup(f.DeclList, p.importDecl)
p.want(_Semi)
}
// { TopLevelDecl ";" }
// 顶级声明的获取,包含const、type、var、func
// 将获取到的const、type、var、func的name、value等参数封装后存入声明列表DeclList
for p.tok != _EOF {// 一直到文件结束
switch p.tok {
case _Const:// const
p.next()
f.DeclList = p.appendGroup(f.DeclList, p.constDecl)
case _Type: // type
p.next()
f.DeclList = p.appendGroup(f.DeclList, p.typeDecl)
case _Var: // var
p.next()
f.DeclList = p.appendGroup(f.DeclList, p.varDecl)
case _Func: // func
p.next()
if d := p.funcDeclOrNil(); d != nil {
f.DeclList = append(f.DeclList, d)
}
default:// 错误
if p.tok == _Lbrace && len(f.DeclList) > 0 && isEmptyFuncDecl(f.DeclList[len(f.DeclList)-1]) {
// opening { of function declaration on next line
p.syntaxError("unexpected semicolon or newline before {")
} else {
p.syntaxError("non-declaration statement outside function body")
}
p.advance(_Const, _Type, _Var, _Func)
continue
}
// Reset p.pragma BEFORE advancing to the next token (consuming ';')
// since comments before may set pragmas for the next function decl.
p.pragma = 0
if p.tok != _EOF && !p.got(_Semi) {
p.syntaxError("after top level declaration")
p.advance(_Const, _Type, _Var, _Func)
}
}
// p.tok == _EOF
f.Lines = p.source.line
return f
}
Parse负责对具体文件进行解析,根据.go文件的格式要求,需要先检查package,imports,然后循环获取顶级声明的const、type、var、func(func会进一步解析func body)并解析封装在对应的Decl中,最后存入DeclList数组中,供解析的下一步node的构建做数据的准备。
鄙人刚刚开通了公众号,专注于分享Go开发相关内容,望大家感兴趣的支持一下,在此特别感谢。