Plan 9 from Bell Labs’s /usr/web/sources/contrib/ericvh/go-plan9/src/pkg/exp/eval/stmt.go

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package eval

import (
	"bignum";
	"log";
	"go/ast";
	"go/token";
)

const (
	returnPC	= ^uint(0);
	badPC		= ^uint(1);
)

/*
 * Statement compiler
 */

type stmtCompiler struct {
	*blockCompiler;
	pos	token.Position;
	// This statement's label, or nil if it is not labeled.
	stmtLabel	*label;
}

func (a *stmtCompiler) diag(format string, args ...) {
	a.diagAt(&a.pos, format, args)
}

/*
 * Flow checker
 */

type flowEnt struct {
	// Whether this flow entry is conditional.  If true, flow can
	// continue to the next PC.
	cond	bool;
	// True if this will terminate flow (e.g., a return statement).
	// cond must be false and jumps must be nil if this is true.
	term	bool;
	// PC's that can be reached from this flow entry.
	jumps	[]*uint;
	// Whether this flow entry has been visited by reachesEnd.
	visited	bool;
}

type flowBlock struct {
	// If this is a goto, the target label.
	target	string;
	// The inner-most block containing definitions.
	block	*block;
	// The numVars from each block leading to the root of the
	// scope, starting at block.
	numVars	[]int;
}

type flowBuf struct {
	cb	*codeBuf;
	// ents is a map from PC's to flow entries.  Any PC missing
	// from this map is assumed to reach only PC+1.
	ents	map[uint]*flowEnt;
	// gotos is a map from goto positions to information on the
	// block at the point of the goto.
	gotos	map[*token.Position]*flowBlock;
	// labels is a map from label name to information on the block
	// at the point of the label.  labels are tracked by name,
	// since mutliple labels at the same PC can have different
	// blocks.
	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)}
}

// put creates a flow control point for the next PC in the code buffer.
// This should be done before pushing the instruction into the code buffer.
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};
}

// putTerm creates a flow control point at the next PC that
// unconditionally terminates execution.
func (f *flowBuf) putTerm()	{ f.put(false, true, nil) }

// put1 creates a flow control point at the next PC that jumps to one
// PC and, if cond is true, can also continue to the PC following the
// next PC.
func (f *flowBuf) put1(cond bool, jumpPC *uint) {
	f.put(cond, false, []*uint{jumpPC})
}

func newFlowBlock(target string, b *block) *flowBlock {
	// Find the inner-most block containing definitions
	for b.numVars == 0 && b.outer != nil && b.outer.scope == b.scope {
		b = b.outer
	}

	// Count parents leading to the root of the scope
	n := 0;
	for bp := b; bp.scope == b.scope; bp = bp.outer {
		n++
	}

	// Capture numVars from each block to the root of the scope
	numVars := make([]int, n);
	i := 0;
	for bp := b; i < n; bp = bp.outer {
		numVars[i] = bp.numVars;
		i++;
	}

	return &flowBlock{target, b, numVars};
}

// putGoto captures the block at a goto statement.  This should be
// called in addition to putting a flow control point.
func (f *flowBuf) putGoto(pos token.Position, target string, b *block) {
	f.gotos[&pos] = newFlowBlock(target, b)
}

// putLabel captures the block at a label.
func (f *flowBuf) putLabel(name string, b *block) {
	f.labels[name] = newFlowBlock("", b)
}

// reachesEnd returns true if the end of f's code buffer can be
// reached from the given program counter.  Error reporting is the
// caller's responsibility.
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
		}

		// If anything can reach the end, we can reach the end
		// from pc.
		for _, j := range ent.jumps {
			if f.reachesEnd(*j) {
				return true
			}
		}
		// If the jump was conditional, we can reach the next
		// PC, so try reaching the end from it.
		if ent.cond {
			continue
		}
		return false;
	}
	return true;
}

// gotosObeyScopes returns true if no goto statement causes any
// variables to come into scope that were not in scope at the point of
// the goto.  Reports any errors using the given compiler.
func (f *flowBuf) gotosObeyScopes(a *compiler) {
	for pos, src := range f.gotos {
		tgt := f.labels[src.target];

		// The target block must be a parent of this block
		numVars := src.numVars;
		b := src.block;
		for len(numVars) > 0 && b != tgt.block {
			b = b.outer;
			numVars = numVars[1:];
		}
		if b != tgt.block {
			// We jumped into a deeper block
			a.diagAt(pos, "goto causes variables to come into scope");
			return;
		}

		// There must be no variables in the target block that
		// did not exist at the jump
		tgtNumVars := tgt.numVars;
		for i := range numVars {
			if tgtNumVars[i] > numVars[i] {
				a.diagAt(pos, "goto causes variables to come into scope");
				return;
			}
		}
	}
}

/*
 * Statement generation helpers
 */

func (a *stmtCompiler) defineVar(ident *ast.Ident, t Type) *Variable {
	v, prev := a.block.DefineVar(ident.Value, ident.Pos(), t);
	if prev != nil {
		// TODO(austin) It's silly that we have to capture
		// Pos() in a variable.
		pos := prev.Pos();
		if pos.IsValid() {
			a.diagAt(ident, "variable %s redeclared in this block\n\tprevious declaration at %s", ident.Value, &pos)
		} else {
			a.diagAt(ident, "variable %s redeclared in this block", ident.Value)
		}
		return nil;
	}

	// Initialize the variable
	index := v.Index;
	if v.Index >= 0 {
		a.push(func(v *Thread) { v.f.Vars[index] = t.Zero() })
	}
	return v;
}

// TODO(austin) Move doAssign to here

/*
 * Statement compiler
 */

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:
		// Error already reported by parser.
		a.silentErrors++

	case *ast.DeclStmt:
		a.compileDeclStmt(s)

	case *ast.EmptyStmt:
		// Do nothing.

	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:
		// Do nothing.  Already reported by parser.
		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 {
			// Declaration without assignment
			if spec.Type == nil {
				// Parser should have caught
				log.Crash("Type and Values nil")
			}
			t := a.compileType(a.block, spec.Type);
			// Define placeholders even if type compile failed
			for _, n := range spec.Names {
				a.defineVar(n, t)
			}
		} else {
			// Declaration with assignment
			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:
		// Do nothing.  Already reported by parser.
		a.silentErrors++

	case *ast.FuncDecl:
		decl := a.compileFuncType(a.block, d.Type);
		if decl == nil {
			return
		}
		// Declare and initialize v before compiling func
		// so that body can refer to itself.
		c, prev := a.block.DefineConst(d.Name.Value, 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.Value, &pos)
			} else {
				a.diagAt(d.Name, "identifier %s redeclared in this block", d.Name.Value)
			}
		}
		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) {
	// Define label
	l, ok := a.labels[s.Label.Value];
	if ok {
		if l.resolved.IsValid() {
			a.diag("label %s redeclared in this block\n\tprevious declaration at %s", s.Label.Value, &l.resolved)
		}
	} else {
		pc := badPC;
		l = &label{name: s.Label.Value, gotoPC: &pc};
		a.labels[l.name] = l;
	}
	l.desc = "regular label";
	l.resolved = s.Pos();

	// Set goto PC
	*l.gotoPC = a.nextPC();

	// Define flow entry so we can check for jumps over declarations.
	a.flow.putLabel(l.name, a.block);

	// Compile the statement.  Reuse our stmtCompiler for simplicity.
	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) {
	// Create temporary block for extractEffect
	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() *bignum.Integer { return bignum.Int(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();

	// Compile right side first so we have the types when
	// compiling the left side and so we don't see definitions
	// made on the left side.
	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 this is a definition and the LHS is too big, we won't be
	// able to produce the usual error message because we can't
	// begin to infer the types of the LHS.
	if (tok == token.DEFINE || tok == token.VAR) && len(lhs) > len(ac.rmt.Elems) {
		a.diag("not enough values for definition")
	}

	// Compile left type if there is one
	var declType Type;
	if declTypeExpr != nil {
		declType = a.compileType(a.block, declTypeExpr)
	}

	// Compile left side
	ls := make([]*expr, len(lhs));
	nDefs := 0;
	for i, le := range lhs {
		// If this is a definition, get the identifier and its type
		var ident *ast.Ident;
		var lt Type;
		switch tok {
		case token.DEFINE:
			// Check that it's an identifier
			ident, ok = le.(*ast.Ident);
			if !ok {
				a.diagAt(le, "left side of := must be a name");
				// Suppress new defitions errors
				nDefs++;
				continue;
			}

			// Is this simply an assignment?
			if _, ok := a.block.defs[ident.Value]; ok {
				ident = nil;
				break;
			}
			nDefs++;

		case token.VAR:
			ident = le.(*ast.Ident)
		}

		// If it's a definition, get or infer its type.
		if ident != nil {
			// Compute the identifier's type from the RHS
			// type.  We use the computed MultiType so we
			// don't have to worry about unpacking.
			switch {
			case declTypeExpr != nil:
				// We have a declaration type, use it.
				// If declType is nil, we gave an
				// error when we compiled it.
				lt = declType

			case i >= len(ac.rmt.Elems):
				// Define a placeholder.  We already
				// gave the "not enough" error above.
				lt = nil

			case ac.rmt.Elems[i] == nil:
				// We gave the error when we compiled
				// the RHS.
				lt = nil

			case ac.rmt.Elems[i].isIdeal():
				// If the type is absent and the
				// corresponding expression is a
				// constant expression of ideal
				// integer or ideal float type, the
				// type of the declared variable is
				// int or float respectively.
				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 it's a definition, define the identifier
		if ident != nil {
			if a.defineVar(ident, lt) == nil {
				continue
			}
		}

		// Compile LHS
		ls[i] = a.compileExpr(a.block, false, le);
		if ls[i] == nil {
			continue
		}

		if ls[i].evalMapValue != nil {
			// Map indexes are not generally addressable,
			// but they are assignable.
			//
			// TODO(austin) Now that the expression
			// compiler uses semantic values, this might
			// be easier to implement as a function call.
			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;
		}
	}

	// A short variable declaration may redeclare variables
	// provided they were originally declared in the same block
	// with the same type, and at least one of the variables is
	// new.
	if tok == token.DEFINE && nDefs == 0 {
		a.diag("at least one new variable must be declared");
		return;
	}

	// If there have been errors, our arrays are full of nil's so
	// get out of here now.
	if nerr != a.numError() {
		return
	}

	// Check for 'a[x] = r, ok'
	if len(ls) == 1 && len(rs) == 2 && ls[0].evalMapValue != nil {
		a.diag("a[x] = r, ok form not implemented");
		return;
	}

	// Create assigner
	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
	}

	// Compile
	if n == 1 {
		// Don't need temporaries and can avoid []Value.
		lf := ls[0].evalAddr;
		a.push(func(t *Thread) { assign(lf(t), t) });
	} else if tok == token.VAR || (tok == token.DEFINE && nDefs == n) {
		// Don't need temporaries
		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 {
		// Need temporaries
		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);
			// Copy to destination
			for i := 0; i < n; i++ {
				// TODO(austin) Need to evaluate LHS
				// before RHS
				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;
	}

	// Create temporary block for extractEffect
	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) {
		// Simple case.  Simply exit from the function.
		a.flow.putTerm();
		a.push(func(v *Thread) { v.pc = returnPC });
		return;
	}

	bc := a.enterChild();
	defer bc.exit();

	// Compile expressions
	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
	}

	// Create assigner

	// However, if the expression list in the "return" statement
	// is a single call to a multi-valued function, the values
	// returned from the called function will be returned from
	// this one.
	assign := a.compileAssign(s.Pos(), bc.block, NewMultiType(a.fnType.Out), rs, "return", "value");

	// XXX(Spec) "The result types of the current function and the
	// called function must match."  Match is fuzzy.  It should
	// say that they must be assignment compatible.

	// Compile
	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.Value {
			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.Value)
	}
	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.Value];
		if !ok {
			pc := badPC;
			l = &label{name: s.Label.Value, 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) {
	// The scope of any variables declared by [the init] statement
	// extends to the end of the "if" statement and the variables
	// are initialized once before the statement is entered.
	//
	// XXX(Spec) What this really wants to say is that there's an
	// implicit scope wrapping every if, for, and switch
	// statement.  This is subtly different from what it actually
	// says when there's a non-block else clause, because that
	// else claus has to execute in a scope that is *not* the
	// surrounding scope.
	bc := a.enterChild();
	defer bc.exit();

	// Compile init statement, if any
	if s.Init != nil {
		bc.compileStmt(s.Init)
	}

	elsePC := badPC;
	endPC := badPC;

	// Compile condition, if any.  If there is no condition, we
	// fall through to the body.
	if s.Cond != nil {
		e := bc.compileExpr(bc.block, false, s.Cond);
		switch {
		case e == nil:
			// Error reported by compileExpr
		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
				}
			});
		}
	}

	// Compile body
	body := bc.enterChild();
	body.compileStmts(s.Body);
	body.exit();

	// Compile else
	if s.Else != nil {
		// Skip over else if we executed the body
		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) {
	// Create implicit scope around switch
	bc := a.enterChild();
	defer bc.exit();

	// Compile init statement, if any
	if s.Init != nil {
		bc.compileStmt(s.Init)
	}

	// Compile condition, if any, and extract its effects
	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);
		}
	}

	// Count cases
	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)
		}
	}

	// Compile case expressions
	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:
				// Error reported by compileExpr
			case cond == nil && !e.t.isBoolean():
				a.diagAt(v, "'case' condition must be boolean")
			case cond == nil:
				cases[i] = e.asBool()
			case cond != nil:
				// Create comparison
				// TOOD(austin) This produces bad error messages
				compare := e.compileBinaryExpr(token.EQL, cond, e);
				if compare != nil {
					cases[i] = compare.asBool()
				}
			}
			i++;
		}
	}

	// Emit condition
	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();

	// Compile cases
	i = 0;
	for _, c := range s.Body.List {
		clause, ok := c.(*ast.CaseClause);
		if !ok {
			continue
		}

		// Save jump PC's
		pc := a.nextPC();
		if clause.Values != nil {
			for _ = range clause.Values {
				casePCs[i] = &pc;
				i++;
			}
		} else {
			// Default clause
			casePCs[ncases] = &pc
		}

		// Compile body
		fall := false;
		for j, s := range clause.Body {
			if br, ok := s.(*ast.BranchStmt); ok && br.Tok == token.FALLTHROUGH {
				// println("Found fallthrough");
				// It may be used only as the final
				// non-empty statement in a case or
				// default clause in an expression
				// "switch" statement.
				for _, s2 := range clause.Body[j+1:] {
					// XXX(Spec) 6g also considers
					// empty blocks to be empty
					// statements.
					if _, ok := s2.(*ast.EmptyStmt); !ok {
						a.diagAt(s, "fallthrough statement must be final statement in case");
						break;
					}
				}
				fall = true;
			} else {
				bc.compileStmt(s)
			}
		}
		// Jump out of switch, unless there was a fallthrough
		if !fall {
			a.flow.put1(false, &endPC);
			a.push(func(v *Thread) { v.pc = endPC });
		}
	}

	// Get end PC
	endPC = a.nextPC();
	if !hasDefault {
		casePCs[ncases] = &endPC
	}
}

func (a *stmtCompiler) compileForStmt(s *ast.ForStmt) {
	// Wrap the entire for in a block.
	bc := a.enterChild();
	defer bc.exit();

	// Compile init statement, if any
	if s.Init != nil {
		bc.compileStmt(s.Init)
	}

	bodyPC := badPC;
	postPC := badPC;
	checkPC := badPC;
	endPC := badPC;

	// Jump to condition check.  We generate slightly less code by
	// placing the condition check after the body.
	a.flow.put1(false, &checkPC);
	a.push(func(v *Thread) { v.pc = checkPC });

	// Compile body
	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();

	// Compile post, if any
	postPC = a.nextPC();
	if s.Post != nil {
		// TODO(austin) Does the parser disallow short
		// declarations in s.Post?
		bc.compileStmt(s.Post)
	}

	// Compile condition check, if any
	checkPC = a.nextPC();
	if s.Cond == nil {
		// If the condition is absent, it is equivalent to true.
		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:
			// Error reported by compileExpr
		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();
}

/*
 * Block compiler
 */

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() }

/*
 * Function compiler
 */

func (a *compiler) compileFunc(b *block, decl *FuncDecl, body *ast.BlockStmt) (func(*Thread) Func) {
	// Create body scope
	//
	// The scope of a parameter or result is the body of the
	// corresponding function.
	bodyScope := b.ChildScope();
	defer bodyScope.exit();
	for i, t := range decl.Type.In {
		if decl.InNames[i] != nil {
			bodyScope.DefineVar(decl.InNames[i].Value, 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].Value, decl.OutNames[i].Pos(), t)
		} else {
			bodyScope.DefineTemp(t)
		}
	}

	// Create block context
	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,
	};

	// Compile body
	nerr := a.numError();
	bc.compileStmts(body);
	fc.checkLabels();
	if nerr != a.numError() {
		return nil
	}

	// Check that the body returned if necessary.  We only check
	// this if there were no errors compiling the body.
	if len(decl.Type.Out) > 0 && fc.flow.reachesEnd(0) {
		// XXX(Spec) Not specified.
		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} };
}

// Checks that labels were resolved and that all jumps obey scoping
// rules.  Reports an error and set fc.err if any check fails.
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() {
		// Don't check scopes if we have unresolved labels
		return
	}

	// Executing the "goto" statement must not cause any variables
	// to come into scope that were not already in scope at the
	// point of the goto.
	a.flow.gotosObeyScopes(a.compiler);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.