compiler: add basic support for function literals

Implement basic support for function literals.
Nested function literals and variables from closure are not supported for now.
This commit is contained in:
Evgenii Stratonikov 2020-05-06 18:10:20 +03:00
parent 1d3fb3d6f5
commit 75fb3af48f
2 changed files with 51 additions and 7 deletions

View file

@ -37,6 +37,9 @@ type codegen struct {
// A mapping of func identifiers with their scope. // A mapping of func identifiers with their scope.
funcs map[string]*funcScope funcs map[string]*funcScope
// A mapping of lambda functions into their scope.
lambda map[string]*funcScope
// Current funcScope being converted. // Current funcScope being converted.
scope *funcScope scope *funcScope
@ -204,8 +207,8 @@ func (c *codegen) convertGlobals(f ast.Node) {
func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
var ( var (
f *funcScope f *funcScope
ok bool ok, isLambda bool
) )
f, ok = c.funcs[decl.Name.Name] f, ok = c.funcs[decl.Name.Name]
@ -215,6 +218,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
return return
} }
c.setLabel(f.label) c.setLabel(f.label)
} else if f, ok = c.lambda[decl.Name.Name]; ok {
isLambda = ok
c.setLabel(f.label)
} else { } else {
f = c.newFunc(decl) f = c.newFunc(decl)
} }
@ -275,6 +281,13 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) {
} }
f.rng.End = uint16(c.prog.Len() - 1) f.rng.End = uint16(c.prog.Len() - 1)
if !isLambda {
for _, f := range c.lambda {
c.convertFuncDecl(file, f.decl)
}
c.lambda = make(map[string]*funcScope)
}
} }
func (c *codegen) Visit(node ast.Node) ast.Visitor { func (c *codegen) Visit(node ast.Node) ast.Visitor {
@ -521,6 +534,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return nil return nil
case *ast.FuncLit:
l := c.newLabel()
c.newLambda(l, n)
buf := make([]byte, 4)
binary.LittleEndian.PutUint16(buf, l)
emit.Instruction(c.prog.BinWriter, opcode.PUSHA, buf)
return nil
case *ast.BasicLit: case *ast.BasicLit:
c.emitLoadConst(c.typeInfo.Types[n]) c.emitLoadConst(c.typeInfo.Types[n])
return nil return nil
@ -646,6 +667,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
var ( var (
f *funcScope f *funcScope
ok bool ok bool
name string
numArgs = len(n.Args) numArgs = len(n.Args)
isBuiltin = isBuiltin(n.Fun) isBuiltin = isBuiltin(n.Fun)
) )
@ -654,8 +676,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.Ident: case *ast.Ident:
f, ok = c.funcs[fun.Name] f, ok = c.funcs[fun.Name]
if !ok && !isBuiltin { if !ok && !isBuiltin {
c.prog.Err = fmt.Errorf("could not resolve function %s", fun.Name) name = fun.Name
return nil
} }
case *ast.SelectorExpr: case *ast.SelectorExpr:
// If this is a method call we need to walk the AST to load the struct locally. // If this is a method call we need to walk the AST to load the struct locally.
@ -700,6 +721,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// Use the ident to check, builtins are not in func scopes. // Use the ident to check, builtins are not in func scopes.
// We can be sure builtins are of type *ast.Ident. // We can be sure builtins are of type *ast.Ident.
c.convertBuiltin(n) c.convertBuiltin(n)
case name != "":
// Function was not found thus is can be only an invocation of func-typed variable.
c.emitLoadLocal(name)
emit.Opcode(c.prog.BinWriter, opcode.CALLA)
case isSyscall(f): case isSyscall(f):
c.convertSyscall(n, f.selector.Name, f.name) c.convertSyscall(n, f.selector.Name, f.name)
default: default:
@ -1264,6 +1289,15 @@ func (c *codegen) newFunc(decl *ast.FuncDecl) *funcScope {
return f return f
} }
func (c *codegen) newLambda(u uint16, lit *ast.FuncLit) {
name := fmt.Sprintf("lambda@%d", u)
c.lambda[name] = newFuncScope(&ast.FuncDecl{
Name: ast.NewIdent(name),
Type: lit.Type,
Body: lit.Body,
}, u)
}
func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
// Resolve the entrypoint of the program. // Resolve the entrypoint of the program.
main, mainFile := resolveEntryPoint(mainIdent, pkg) main, mainFile := resolveEntryPoint(mainIdent, pkg)
@ -1319,6 +1353,7 @@ func newCodegen(info *buildInfo, pkg *loader.PackageInfo) *codegen {
prog: io.NewBufBinWriter(), prog: io.NewBufBinWriter(),
l: []int{}, l: []int{},
funcs: map[string]*funcScope{}, funcs: map[string]*funcScope{},
lambda: map[string]*funcScope{},
labels: map[labelWithType]uint16{}, labels: map[labelWithType]uint16{},
typeInfo: &pkg.Info, typeInfo: &pkg.Info,
@ -1364,7 +1399,7 @@ func (c *codegen) writeJumps(b []byte) error {
case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL, case opcode.JMPL, opcode.JMPIFL, opcode.JMPIFNOTL,
opcode.JMPEQL, opcode.JMPNEL, opcode.JMPEQL, opcode.JMPNEL,
opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL, opcode.JMPGTL, opcode.JMPGEL, opcode.JMPLEL, opcode.JMPLTL,
opcode.CALLL: opcode.CALLL, opcode.PUSHA:
// we can't use arg returned by ctx.Next() because it is copied // we can't use arg returned by ctx.Next() because it is copied
nextIP := ctx.NextIP() nextIP := ctx.NextIP()
arg := b[nextIP-4:] arg := b[nextIP-4:]
@ -1373,7 +1408,12 @@ func (c *codegen) writeJumps(b []byte) error {
if int(index) > len(c.l) { if int(index) > len(c.l) {
return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l)) return fmt.Errorf("unexpected label number: %d (max %d)", index, len(c.l))
} }
offset := c.l[index] - nextIP + 5 var offset int
if op == opcode.PUSHA {
offset = c.l[index]
} else {
offset = c.l[index] - nextIP + 5
}
if offset > math.MaxInt32 || offset < math.MinInt32 { if offset > math.MaxInt32 || offset < math.MinInt32 {
return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)", return fmt.Errorf("label offset is too big at the instruction %d: %d (max %d, min %d)",
nextIP-5, offset, math.MaxInt32, math.MinInt32) nextIP-5, offset, math.MaxInt32, math.MinInt32)

View file

@ -42,8 +42,12 @@ type funcScope struct {
} }
func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope { func newFuncScope(decl *ast.FuncDecl, label uint16) *funcScope {
var name string
if decl.Name != nil {
name = decl.Name.Name
}
return &funcScope{ return &funcScope{
name: decl.Name.Name, name: name,
decl: decl, decl: decl,
label: label, label: label,
locals: map[string]int{}, locals: map[string]int{},