package eval
import (
"big"
"log"
"go/ast"
"go/token"
)
const (
returnPC = ^uint(0)
badPC = ^uint(1)
)
type stmtCompiler struct {
*blockCompiler
pos token.Position
stmtLabel *label
}
func (a *stmtCompiler) diag(format string, args ...interface{}) {
a.diagAt(&a.pos, format, args)
}
type flowEnt struct {
cond bool
term bool
jumps []*uint
visited bool
}
type flowBlock struct {
target string
block *block
numVars []int
}
type flowBuf struct {
cb *codeBuf
ents map[uint]*flowEnt
gotos map[*token.Position]*flowBlock
labels map[string]*flowBlock
}
func newFlowBuf(cb *codeBuf) *flowBuf {
return &flowBuf{cb, make(map[uint]*flowEnt), make(map[*token.Position]*flowBlock), make(map[string]*flowBlock)}
}
func (f *flowBuf) put(cond bool, term bool, jumps []*uint) {
pc := f.cb.nextPC()
if ent, ok := f.ents[pc]; ok {
log.Crashf("Flow entry already exists at PC %d: %+v", pc, ent)
}
f.ents[pc] = &flowEnt{cond, term, jumps, false}
}
func (f *flowBuf) putTerm() { f.put(false, true, nil) }
func (f *flowBuf) put1(cond bool, jumpPC *uint) {
f.put(cond, false, []*uint{jumpPC})
}
func newFlowBlock(target string, b *block) *flowBlock {
for b.numVars == 0 && b.outer != nil && b.outer.scope == b.scope {
b = b.outer
}
n := 0
for bp := b; bp.scope == b.scope; bp = bp.outer {
n++
}
numVars := make([]int, n)
i := 0
for bp := b; i < n; bp = bp.outer {
numVars[i] = bp.numVars
i++
}
return &flowBlock{target, b, numVars}
}
func (f *flowBuf) putGoto(pos token.Position, target string, b *block) {
f.gotos[&pos] = newFlowBlock(target, b)
}
func (f *flowBuf) putLabel(name string, b *block) {
f.labels[name] = newFlowBlock("", b)
}
func (f *flowBuf) reachesEnd(pc uint) bool {
endPC := f.cb.nextPC()
if pc > endPC {
log.Crashf("Reached bad PC %d past end PC %d", pc, endPC)
}
for ; pc < endPC; pc++ {
ent, ok := f.ents[pc]
if !ok {
continue
}
if ent.visited {
return false
}
ent.visited = true
if ent.term {
return false
}
for _, j := range ent.jumps {
if f.reachesEnd(*j) {
return true
}
}
if ent.cond {
continue
}
return false
}
return true
}
func (f *flowBuf) gotosObeyScopes(a *compiler) {
for pos, src := range f.gotos {
tgt := f.labels[src.target]
numVars := src.numVars
b := src.block
for len(numVars) > 0 && b != tgt.block {
b = b.outer
numVars = numVars[1:]
}
if b != tgt.block {
a.diagAt(pos, "goto causes variables to come into scope")
return
}
tgtNumVars := tgt.numVars
for i := range numVars {
if tgtNumVars[i] > numVars[i] {
a.diagAt(pos, "goto causes variables to come into scope")
return
}
}
}
}
func (a *stmtCompiler) defineVar(ident *ast.Ident, t Type) *Variable {
v, prev := a.block.DefineVar(ident.Name(), ident.Pos(), t)
if prev != nil {
pos := prev.Pos()
if pos.IsValid() {
a.diagAt(ident, "variable %s redeclared in this block\n\tprevious declaration at %s", ident.Name(), &pos)
} else {
a.diagAt(ident, "variable %s redeclared in this block", ident.Name())
}
return nil
}
index := v.Index
if v.Index >= 0 {
a.push(func(v *Thread) { v.f.Vars[index] = t.Zero() })
}
return v
}
func (a *stmtCompiler) compile(s ast.Stmt) {
if a.block.inner != nil {
log.Crash("Child scope still entered")
}
notimpl := false
switch s := s.(type) {
case *ast.BadStmt:
a.silentErrors++
case *ast.DeclStmt:
a.compileDeclStmt(s)
case *ast.EmptyStmt:
case *ast.LabeledStmt:
a.compileLabeledStmt(s)
case *ast.ExprStmt:
a.compileExprStmt(s)
case *ast.IncDecStmt:
a.compileIncDecStmt(s)
case *ast.AssignStmt:
a.compileAssignStmt(s)
case *ast.GoStmt:
notimpl = true
case *ast.DeferStmt:
notimpl = true
case *ast.ReturnStmt:
a.compileReturnStmt(s)
case *ast.BranchStmt:
a.compileBranchStmt(s)
case *ast.BlockStmt:
a.compileBlockStmt(s)
case *ast.IfStmt:
a.compileIfStmt(s)
case *ast.CaseClause:
a.diag("case clause outside switch")
case *ast.SwitchStmt:
a.compileSwitchStmt(s)
case *ast.TypeCaseClause:
notimpl = true
case *ast.TypeSwitchStmt:
notimpl = true
case *ast.CommClause:
notimpl = true
case *ast.SelectStmt:
notimpl = true
case *ast.ForStmt:
a.compileForStmt(s)
case *ast.RangeStmt:
notimpl = true
default:
log.Crashf("unexpected ast node type %T", s)
}
if notimpl {
a.diag("%T statment node not implemented", s)
}
if a.block.inner != nil {
log.Crash("Forgot to exit child scope")
}
}
func (a *stmtCompiler) compileDeclStmt(s *ast.DeclStmt) {
switch decl := s.Decl.(type) {
case *ast.BadDecl:
a.silentErrors++
case *ast.FuncDecl:
if !a.block.global {
log.Crash("FuncDecl at statement level")
}
case *ast.GenDecl:
if decl.Tok == token.IMPORT && !a.block.global {
log.Crash("import at statement level")
}
default:
log.Crashf("Unexpected Decl type %T", s.Decl)
}
a.compileDecl(s.Decl)
}
func (a *stmtCompiler) compileVarDecl(decl *ast.GenDecl) {
for _, spec := range decl.Specs {
spec := spec.(*ast.ValueSpec)
if spec.Values == nil {
if spec.Type == nil {
log.Crash("Type and Values nil")
}
t := a.compileType(a.block, spec.Type)
for _, n := range spec.Names {
a.defineVar(n, t)
}
} else {
lhs := make([]ast.Expr, len(spec.Names))
for i, n := range spec.Names {
lhs[i] = n
}
a.doAssign(lhs, spec.Values, decl.Tok, spec.Type)
}
}
}
func (a *stmtCompiler) compileDecl(decl ast.Decl) {
switch d := decl.(type) {
case *ast.BadDecl:
a.silentErrors++
case *ast.FuncDecl:
decl := a.compileFuncType(a.block, d.Type)
if decl == nil {
return
}
c, prev := a.block.DefineConst(d.Name.Name(), a.pos, decl.Type, decl.Type.Zero())
if prev != nil {
pos := prev.Pos()
if pos.IsValid() {
a.diagAt(d.Name, "identifier %s redeclared in this block\n\tprevious declaration at %s", d.Name.Name(), &pos)
} else {
a.diagAt(d.Name, "identifier %s redeclared in this block", d.Name.Name())
}
}
fn := a.compileFunc(a.block, decl, d.Body)
if c == nil || fn == nil {
return
}
var zeroThread Thread
c.Value.(FuncValue).Set(nil, fn(&zeroThread))
case *ast.GenDecl:
switch d.Tok {
case token.IMPORT:
log.Crashf("%v not implemented", d.Tok)
case token.CONST:
log.Crashf("%v not implemented", d.Tok)
case token.TYPE:
a.compileTypeDecl(a.block, d)
case token.VAR:
a.compileVarDecl(d)
}
default:
log.Crashf("Unexpected Decl type %T", decl)
}
}
func (a *stmtCompiler) compileLabeledStmt(s *ast.LabeledStmt) {
l, ok := a.labels[s.Label.Name()]
if ok {
if l.resolved.IsValid() {
a.diag("label %s redeclared in this block\n\tprevious declaration at %s", s.Label.Name(), &l.resolved)
}
} else {
pc := badPC
l = &label{name: s.Label.Name(), gotoPC: &pc}
a.labels[l.name] = l
}
l.desc = "regular label"
l.resolved = s.Pos()
*l.gotoPC = a.nextPC()
a.flow.putLabel(l.name, a.block)
sc := &stmtCompiler{a.blockCompiler, s.Stmt.Pos(), l}
sc.compile(s.Stmt)
}
func (a *stmtCompiler) compileExprStmt(s *ast.ExprStmt) {
bc := a.enterChild()
defer bc.exit()
e := a.compileExpr(bc.block, false, s.X)
if e == nil {
return
}
if e.exec == nil {
a.diag("%s cannot be used as expression statement", e.desc)
return
}
a.push(e.exec)
}
func (a *stmtCompiler) compileIncDecStmt(s *ast.IncDecStmt) {
bc := a.enterChild()
defer bc.exit()
l := a.compileExpr(bc.block, false, s.X)
if l == nil {
return
}
if l.evalAddr == nil {
l.diag("cannot assign to %s", l.desc)
return
}
if !(l.t.isInteger() || l.t.isFloat()) {
l.diagOpType(s.Tok, l.t)
return
}
var op token.Token
var desc string
switch s.Tok {
case token.INC:
op = token.ADD
desc = "increment statement"
case token.DEC:
op = token.SUB
desc = "decrement statement"
default:
log.Crashf("Unexpected IncDec token %v", s.Tok)
}
effect, l := l.extractEffect(bc.block, desc)
one := l.newExpr(IdealIntType, "constant")
one.pos = s.Pos()
one.eval = func() *big.Int { return big.NewInt(1) }
binop := l.compileBinaryExpr(op, l, one)
if binop == nil {
return
}
assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "", "")
if assign == nil {
log.Crashf("compileAssign type check failed")
}
lf := l.evalAddr
a.push(func(v *Thread) {
effect(v)
assign(lf(v), v)
})
}
func (a *stmtCompiler) doAssign(lhs []ast.Expr, rhs []ast.Expr, tok token.Token, declTypeExpr ast.Expr) {
nerr := a.numError()
rs := make([]*expr, len(rhs))
for i, re := range rhs {
rs[i] = a.compileExpr(a.block, false, re)
}
errOp := "assignment"
if tok == token.DEFINE || tok == token.VAR {
errOp = "declaration"
}
ac, ok := a.checkAssign(a.pos, rs, errOp, "value")
ac.allowMapForms(len(lhs))
if (tok == token.DEFINE || tok == token.VAR) && len(lhs) > len(ac.rmt.Elems) {
a.diag("not enough values for definition")
}
var declType Type
if declTypeExpr != nil {
declType = a.compileType(a.block, declTypeExpr)
}
ls := make([]*expr, len(lhs))
nDefs := 0
for i, le := range lhs {
var ident *ast.Ident
var lt Type
switch tok {
case token.DEFINE:
ident, ok = le.(*ast.Ident)
if !ok {
a.diagAt(le, "left side of := must be a name")
nDefs++
continue
}
if _, ok := a.block.defs[ident.Name()]; ok {
ident = nil
break
}
nDefs++
case token.VAR:
ident = le.(*ast.Ident)
}
if ident != nil {
switch {
case declTypeExpr != nil:
lt = declType
case i >= len(ac.rmt.Elems):
lt = nil
case ac.rmt.Elems[i] == nil:
lt = nil
case ac.rmt.Elems[i].isIdeal():
switch {
case ac.rmt.Elems[i].isInteger():
lt = IntType
case ac.rmt.Elems[i].isFloat():
lt = FloatType
default:
log.Crashf("unexpected ideal type %v", rs[i].t)
}
default:
lt = ac.rmt.Elems[i]
}
}
if ident != nil {
if a.defineVar(ident, lt) == nil {
continue
}
}
ls[i] = a.compileExpr(a.block, false, le)
if ls[i] == nil {
continue
}
if ls[i].evalMapValue != nil {
sub := ls[i]
ls[i] = ls[i].newExpr(sub.t, sub.desc)
ls[i].evalMapValue = sub.evalMapValue
mvf := sub.evalMapValue
et := sub.t
ls[i].evalAddr = func(t *Thread) Value {
m, k := mvf(t)
e := m.Elem(t, k)
if e == nil {
e = et.Zero()
m.SetElem(t, k, e)
}
return e
}
} else if ls[i].evalAddr == nil {
ls[i].diag("cannot assign to %s", ls[i].desc)
continue
}
}
if tok == token.DEFINE && nDefs == 0 {
a.diag("at least one new variable must be declared")
return
}
if nerr != a.numError() {
return
}
if len(ls) == 1 && len(rs) == 2 && ls[0].evalMapValue != nil {
a.diag("a[x] = r, ok form not implemented")
return
}
var lt Type
n := len(lhs)
if n == 1 {
lt = ls[0].t
} else {
lts := make([]Type, len(ls))
for i, l := range ls {
if l != nil {
lts[i] = l.t
}
}
lt = NewMultiType(lts)
}
bc := a.enterChild()
defer bc.exit()
assign := ac.compile(bc.block, lt)
if assign == nil {
return
}
if n == 1 {
lf := ls[0].evalAddr
a.push(func(t *Thread) { assign(lf(t), t) })
} else if tok == token.VAR || (tok == token.DEFINE && nDefs == n) {
lfs := make([]func(*Thread) Value, n)
for i, l := range ls {
lfs[i] = l.evalAddr
}
a.push(func(t *Thread) {
dest := make([]Value, n)
for i, lf := range lfs {
dest[i] = lf(t)
}
assign(multiV(dest), t)
})
} else {
lmt := lt.(*MultiType)
lfs := make([]func(*Thread) Value, n)
for i, l := range ls {
lfs[i] = l.evalAddr
}
a.push(func(t *Thread) {
temp := lmt.Zero().(multiV)
assign(temp, t)
for i := 0; i < n; i++ {
lfs[i](t).Assign(t, temp[i])
}
})
}
}
var assignOpToOp = map[token.Token]token.Token{
token.ADD_ASSIGN: token.ADD,
token.SUB_ASSIGN: token.SUB,
token.MUL_ASSIGN: token.MUL,
token.QUO_ASSIGN: token.QUO,
token.REM_ASSIGN: token.REM,
token.AND_ASSIGN: token.AND,
token.OR_ASSIGN: token.OR,
token.XOR_ASSIGN: token.XOR,
token.SHL_ASSIGN: token.SHL,
token.SHR_ASSIGN: token.SHR,
token.AND_NOT_ASSIGN: token.AND_NOT,
}
func (a *stmtCompiler) doAssignOp(s *ast.AssignStmt) {
if len(s.Lhs) != 1 || len(s.Rhs) != 1 {
a.diag("tuple assignment cannot be combined with an arithmetic operation")
return
}
bc := a.enterChild()
defer bc.exit()
l := a.compileExpr(bc.block, false, s.Lhs[0])
r := a.compileExpr(bc.block, false, s.Rhs[0])
if l == nil || r == nil {
return
}
if l.evalAddr == nil {
l.diag("cannot assign to %s", l.desc)
return
}
effect, l := l.extractEffect(bc.block, "operator-assignment")
binop := r.compileBinaryExpr(assignOpToOp[s.Tok], l, r)
if binop == nil {
return
}
assign := a.compileAssign(s.Pos(), bc.block, l.t, []*expr{binop}, "assignment", "value")
if assign == nil {
log.Crashf("compileAssign type check failed")
}
lf := l.evalAddr
a.push(func(t *Thread) {
effect(t)
assign(lf(t), t)
})
}
func (a *stmtCompiler) compileAssignStmt(s *ast.AssignStmt) {
switch s.Tok {
case token.ASSIGN, token.DEFINE:
a.doAssign(s.Lhs, s.Rhs, s.Tok, nil)
default:
a.doAssignOp(s)
}
}
func (a *stmtCompiler) compileReturnStmt(s *ast.ReturnStmt) {
if a.fnType == nil {
a.diag("cannot return at the top level")
return
}
if len(s.Results) == 0 && (len(a.fnType.Out) == 0 || a.outVarsNamed) {
a.flow.putTerm()
a.push(func(v *Thread) { v.pc = returnPC })
return
}
bc := a.enterChild()
defer bc.exit()
bad := false
rs := make([]*expr, len(s.Results))
for i, re := range s.Results {
rs[i] = a.compileExpr(bc.block, false, re)
if rs[i] == nil {
bad = true
}
}
if bad {
return
}
assign := a.compileAssign(s.Pos(), bc.block, NewMultiType(a.fnType.Out), rs, "return", "value")
start := len(a.fnType.In)
nout := len(a.fnType.Out)
a.flow.putTerm()
a.push(func(t *Thread) {
assign(multiV(t.f.Vars[start:start+nout]), t)
t.pc = returnPC
})
}
func (a *stmtCompiler) findLexicalLabel(name *ast.Ident, pred func(*label) bool, errOp, errCtx string) *label {
bc := a.blockCompiler
for ; bc != nil; bc = bc.parent {
if bc.label == nil {
continue
}
l := bc.label
if name == nil && pred(l) {
return l
}
if name != nil && l.name == name.Name() {
if !pred(l) {
a.diag("cannot %s to %s %s", errOp, l.desc, l.name)
return nil
}
return l
}
}
if name == nil {
a.diag("%s outside %s", errOp, errCtx)
} else {
a.diag("%s label %s not defined", errOp, name.Name())
}
return nil
}
func (a *stmtCompiler) compileBranchStmt(s *ast.BranchStmt) {
var pc *uint
switch s.Tok {
case token.BREAK:
l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.breakPC != nil }, "break", "for loop, switch, or select")
if l == nil {
return
}
pc = l.breakPC
case token.CONTINUE:
l := a.findLexicalLabel(s.Label, func(l *label) bool { return l.continuePC != nil }, "continue", "for loop")
if l == nil {
return
}
pc = l.continuePC
case token.GOTO:
l, ok := a.labels[s.Label.Name()]
if !ok {
pc := badPC
l = &label{name: s.Label.Name(), desc: "unresolved label", gotoPC: &pc, used: s.Pos()}
a.labels[l.name] = l
}
pc = l.gotoPC
a.flow.putGoto(s.Pos(), l.name, a.block)
case token.FALLTHROUGH:
a.diag("fallthrough outside switch")
return
default:
log.Crash("Unexpected branch token %v", s.Tok)
}
a.flow.put1(false, pc)
a.push(func(v *Thread) { v.pc = *pc })
}
func (a *stmtCompiler) compileBlockStmt(s *ast.BlockStmt) {
bc := a.enterChild()
bc.compileStmts(s)
bc.exit()
}
func (a *stmtCompiler) compileIfStmt(s *ast.IfStmt) {
bc := a.enterChild()
defer bc.exit()
if s.Init != nil {
bc.compileStmt(s.Init)
}
elsePC := badPC
endPC := badPC
if s.Cond != nil {
e := bc.compileExpr(bc.block, false, s.Cond)
switch {
case e == nil:
case !e.t.isBoolean():
e.diag("'if' condition must be boolean\n\t%v", e.t)
default:
eval := e.asBool()
a.flow.put1(true, &elsePC)
a.push(func(t *Thread) {
if !eval(t) {
t.pc = elsePC
}
})
}
}
body := bc.enterChild()
body.compileStmts(s.Body)
body.exit()
if s.Else != nil {
a.flow.put1(false, &endPC)
a.push(func(v *Thread) { v.pc = endPC })
elsePC = a.nextPC()
bc.compileStmt(s.Else)
} else {
elsePC = a.nextPC()
}
endPC = a.nextPC()
}
func (a *stmtCompiler) compileSwitchStmt(s *ast.SwitchStmt) {
bc := a.enterChild()
defer bc.exit()
if s.Init != nil {
bc.compileStmt(s.Init)
}
var cond *expr
condbc := bc.enterChild()
if s.Tag != nil {
e := condbc.compileExpr(condbc.block, false, s.Tag)
if e != nil {
var effect func(*Thread)
effect, cond = e.extractEffect(condbc.block, "switch")
a.push(effect)
}
}
ncases := 0
hasDefault := false
for _, c := range s.Body.List {
clause, ok := c.(*ast.CaseClause)
if !ok {
a.diagAt(clause, "switch statement must contain case clauses")
continue
}
if clause.Values == nil {
if hasDefault {
a.diagAt(clause, "switch statement contains more than one default case")
}
hasDefault = true
} else {
ncases += len(clause.Values)
}
}
cases := make([]func(*Thread) bool, ncases)
i := 0
for _, c := range s.Body.List {
clause, ok := c.(*ast.CaseClause)
if !ok {
continue
}
for _, v := range clause.Values {
e := condbc.compileExpr(condbc.block, false, v)
switch {
case e == nil:
case cond == nil && !e.t.isBoolean():
a.diagAt(v, "'case' condition must be boolean")
case cond == nil:
cases[i] = e.asBool()
case cond != nil:
compare := e.compileBinaryExpr(token.EQL, cond, e)
if compare != nil {
cases[i] = compare.asBool()
}
}
i++
}
}
casePCs := make([]*uint, ncases+1)
endPC := badPC
a.flow.put(false, false, casePCs)
a.push(func(t *Thread) {
for i, c := range cases {
if c(t) {
t.pc = *casePCs[i]
return
}
}
t.pc = *casePCs[ncases]
})
condbc.exit()
i = 0
for _, c := range s.Body.List {
clause, ok := c.(*ast.CaseClause)
if !ok {
continue
}
pc := a.nextPC()
if clause.Values != nil {
for _ = range clause.Values {
casePCs[i] = &pc
i++
}
} else {
casePCs[ncases] = &pc
}
fall := false
for j, s := range clause.Body {
if br, ok := s.(*ast.BranchStmt); ok && br.Tok == token.FALLTHROUGH {
for _, s2 := range clause.Body[j+1:] {
if _, ok := s2.(*ast.EmptyStmt); !ok {
a.diagAt(s, "fallthrough statement must be final statement in case")
break
}
}
fall = true
} else {
bc.compileStmt(s)
}
}
if !fall {
a.flow.put1(false, &endPC)
a.push(func(v *Thread) { v.pc = endPC })
}
}
endPC = a.nextPC()
if !hasDefault {
casePCs[ncases] = &endPC
}
}
func (a *stmtCompiler) compileForStmt(s *ast.ForStmt) {
bc := a.enterChild()
defer bc.exit()
if s.Init != nil {
bc.compileStmt(s.Init)
}
bodyPC := badPC
postPC := badPC
checkPC := badPC
endPC := badPC
a.flow.put1(false, &checkPC)
a.push(func(v *Thread) { v.pc = checkPC })
bodyPC = a.nextPC()
body := bc.enterChild()
if a.stmtLabel != nil {
body.label = a.stmtLabel
} else {
body.label = &label{resolved: s.Pos()}
}
body.label.desc = "for loop"
body.label.breakPC = &endPC
body.label.continuePC = &postPC
body.compileStmts(s.Body)
body.exit()
postPC = a.nextPC()
if s.Post != nil {
bc.compileStmt(s.Post)
}
checkPC = a.nextPC()
if s.Cond == nil {
a.flow.put1(false, &bodyPC)
a.push(func(v *Thread) { v.pc = bodyPC })
} else {
e := bc.compileExpr(bc.block, false, s.Cond)
switch {
case e == nil:
case !e.t.isBoolean():
a.diag("'for' condition must be boolean\n\t%v", e.t)
default:
eval := e.asBool()
a.flow.put1(true, &bodyPC)
a.push(func(t *Thread) {
if eval(t) {
t.pc = bodyPC
}
})
}
}
endPC = a.nextPC()
}
func (a *blockCompiler) compileStmt(s ast.Stmt) {
sc := &stmtCompiler{a, s.Pos(), nil}
sc.compile(s)
}
func (a *blockCompiler) compileStmts(block *ast.BlockStmt) {
for _, sub := range block.List {
a.compileStmt(sub)
}
}
func (a *blockCompiler) enterChild() *blockCompiler {
block := a.block.enterChild()
return &blockCompiler{
funcCompiler: a.funcCompiler,
block: block,
parent: a,
}
}
func (a *blockCompiler) exit() { a.block.exit() }
func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) func(*Thread) Func {
bodyScope := b.ChildScope()
defer bodyScope.exit()
for i, t := range decl.Type.In {
if decl.InNames[i] != nil {
bodyScope.DefineVar(decl.InNames[i].Name(), decl.InNames[i].Pos(), t)
} else {
bodyScope.DefineTemp(t)
}
}
for i, t := range decl.Type.Out {
if decl.OutNames[i] != nil {
bodyScope.DefineVar(decl.OutNames[i].Name(), decl.OutNames[i].Pos(), t)
} else {
bodyScope.DefineTemp(t)
}
}
cb := newCodeBuf()
fc := &funcCompiler{
compiler: a,
fnType: decl.Type,
outVarsNamed: len(decl.OutNames) > 0 && decl.OutNames[0] != nil,
codeBuf: cb,
flow: newFlowBuf(cb),
labels: make(map[string]*label),
}
bc := &blockCompiler{
funcCompiler: fc,
block: bodyScope.block,
}
nerr := a.numError()
bc.compileStmts(body)
fc.checkLabels()
if nerr != a.numError() {
return nil
}
if len(decl.Type.Out) > 0 && fc.flow.reachesEnd(0) {
a.diagAt(&body.Rbrace, "function ends without a return statement")
return nil
}
code := fc.get()
maxVars := bodyScope.maxVars
return func(t *Thread) Func { return &evalFunc{t.f, maxVars, code} }
}
func (a *funcCompiler) checkLabels() {
nerr := a.numError()
for _, l := range a.labels {
if !l.resolved.IsValid() {
a.diagAt(&l.used, "label %s not defined", l.name)
}
}
if nerr != a.numError() {
return
}
a.flow.gotosObeyScopes(a.compiler)
}