7.1 import语句

在支持多文件和多包之前,我们先增加对import语句的支持,然后通过import语句导入builtin中的println函数。

7.1.1 builtin包

builtin包提供了println等调试函数,可以不经过import直接使用。为了方便展示import语句的用法,下面的代码显式导入builtin包:

package main

import "builtin"
import pkg "builtin"

func main() {
	builtin.println(42)
	pkg.println(43)
}

两个打印函数最终都映射到ugo_builtin_println函数。

7.1.2 完善token包

以上的例子中出现了import"builtin".几个新的token,对应token包中以下新类型:

package token

// 记号类型
type TokenType int

// ch3中 µGo程序用到的记号类型
const (
	EOF TokenType = iota // = 0
	...
	STRING // "abc"
	IMPORT // import
	PERIOD // .
	...
)

其中STRING表示字符串,对应导入包的路径。IMPORT对应import语句,需要注册到关键字列表中。PERIOD点选择符号目前只用于引用导入包的对象。

7.1.3 完善词法解析

词法解析部分需要增加对点和字符串的解析:

func (p *Lexer) run() (tokens []token.Token) {
	...
	for {
		r := p.src.Read()
		...
		switch {
		case r == '"': // "abc\n"
			p.lexQuote()
		case r == '.':
			p.emit(token.PERIOD)
		...
		}
	}
}

字符串的解析如下:

func (l *Lexer) lexQuote() {
	for {
		switch l.src.Read() {
		case rune(token.EOF):
			l.errorf("unterminated quoted string")
			return
		case '\\':
			l.src.Read()
		case '"':
			l.emit(token.STRING)
			return
		}
	}
}

字符串是双引号包含的字符序列,其中可能有转移字符。

7.1.4 ast包完善

首先在File节点增加import语句信息:

// File 表示 µGo 文件对应的语法树.
type File struct {
	...
	Imports []*ImportSpec // 导入包信息
	...
}

import语句有导入名和导入包路径信息,其中导入名可省略,对应ImportSpec结构:

// ImportSpec 表示一个导入包
type ImportSpec struct {
	ImportPos token.Pos
	Name      *Ident
	Path      string
}

有了导入包之后,可能调用导入包的函数,因此CallExpr除了函数名还需要增加对应包信息:

// CallExpr 表示一个函数调用
type CallExpr struct {
	Pkg *Ident    // 对应的包, 当前包为 nil
	...
}

最后还需要一个SelectorExpr新的选择运算结构,表示导入包的变量:

// SelectorExpr 表示 x.Name 属性选择表达式
type SelectorExpr struct {
	X   Expr
	Sel *Ident
}

目前SelectorExpr.X只有导入包的名字,也是*ast.Ident类型。

7.1.4 import语句解析

func (p *Parser) parseFile() {
	...
	for {
		switch tok := p.PeekToken(); tok.Type {
		...
		case token.IMPORT:
			p.file.Imports = append(p.file.Imports, p.parseImport())
		...
		}
	}
}
// import "path/to/pkg"
// import name "path/to/pkg"
func (p *Parser) parseImport() *ast.ImportSpec {
	tokImport := p.MustAcceptToken(token.IMPORT)

	var importSpec = &ast.ImportSpec{
		ImportPos: tokImport.Pos,
	}

	asName, ok := p.AcceptToken(token.IDENT)
	if ok {
		importSpec.Name = &ast.Ident{
			NamePos: asName.Pos,
			Name:    asName.Literal,
		}
	}

	if pkgPath, ok := p.AcceptToken(token.STRING); ok {
		path, _ := strconv.Unquote(pkgPath.Literal)
		importSpec.Path = path
	}

	return importSpec
}

7.1.5 调用其他包函数解析

func (p *Parser) parseExpr_primary() ast.Expr {
	...
	switch tok := p.PeekToken(); tok.Type {
	case token.IDENT: // call
		p.ReadToken()
		nextTok := p.PeekToken()
		p.UnreadToken()

		switch nextTok.Type {
		case token.LPAREN:
			return p.parseExpr_call()
		case token.PERIOD:
			return p.parseExpr_selector()
		...
		}
	...
	}
}
func (p *Parser) parseExpr_selector() ast.Expr {
	tokX := p.MustAcceptToken(token.IDENT)
	_ = p.MustAcceptToken(token.PERIOD)
	tokSel := p.MustAcceptToken(token.IDENT)

	// pkg.fn(...)
	if nextTok := p.PeekToken(); nextTok.Type == token.LPAREN {
		var arg0 ast.Expr
		tokLparen := p.MustAcceptToken(token.LPAREN)
		if tok := p.PeekToken(); tok.Type != token.RPAREN {
			arg0 = p.parseExpr()
		}
		tokRparen := p.MustAcceptToken(token.RPAREN)

		return &ast.CallExpr{
			Pkg: &ast.Ident{
				NamePos: tokX.Pos,
				Name:    tokX.Literal,
			},
			FuncName: &ast.Ident{
				NamePos: tokSel.Pos,
				Name:    tokSel.Literal,
			},
			Lparen: tokLparen.Pos,
			Args:   []ast.Expr{arg0},
			Rparen: tokRparen.Pos,
		}
	}

	return &ast.SelectorExpr{
		X: &ast.Ident{
			NamePos: tokX.Pos,
			Name:    tokX.Literal,
		},
		Sel: &ast.Ident{
			NamePos: tokSel.Pos,
			Name:    tokSel.Literal,
		},
	}
}

7.1.6 输出LLVM-IR

func (p *Compiler) compileFile(w io.Writer, file *ast.File) {
	defer p.restoreScope(p.scope)
	p.enterScope()

	// import
	for _, x := range file.Imports {
		var mangledName = fmt.Sprintf("@ugo_%s", x.Path)
		if x.Name != nil {
			p.scope.Insert(&Object{
				Name:        x.Name.Name,
				MangledName: mangledName,
				Node:        x,
			})
		} else {
			p.scope.Insert(&Object{
				Name:        x.Path,
				MangledName: mangledName,
				Node:        x,
			})
		}
	}
	...
}
func (p *Compiler) compileExpr(w io.Writer, expr ast.Expr) (localName string) {
	switch expr := expr.(type) {
	...
	case *ast.CallExpr:
		var fnName string
		if expr.Pkg != nil {
			if _, obj := p.scope.Lookup(expr.Pkg.Name); obj != nil {
				fnName = obj.MangledName + "_" + expr.FuncName.Name
			} else {
				panic(fmt.Sprintf("func %s.%s undefined", expr.Pkg.Name, expr.FuncName.Name))
			}
		} else if _, obj := p.scope.Lookup(expr.FuncName.Name); obj != nil {
			fnName = obj.MangledName
		} else {
			panic(fmt.Sprintf("func %s undefined", expr.FuncName.Name))
		}
		...
	default:
		panic(fmt.Sprintf("unknown: %[1]T, %[1]v", expr))
	}
}

7.1.7 测试调用导入包函数

现在可以执行本节开头的例子了:

package main

import "builtin"
import pkg "builtin"

func main() {
	builtin.println(42)
	pkg.println(43)
}

通过以下命令执行:

$ go run main.go run ./_examples/import.ugo
42
43

结果正常。


© 2021-2022 | 柴树杉 保留所有权利