- KusonStack一站式可编程配置技术栈(Go): https://github.com/KusionStack/kusion
- KCL 配置编程语言(Rust): https://github.com/KusionStack/KCLVM
- 凹语言™: https://github.com/wa-lang/wa
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
结果正常。