]> Cypherpunks repositories - gostls13.git/commitdiff
Implement switch statement. Can now extract effects from
authorAustin Clements <aclements@csail.mit.edu>
Mon, 10 Aug 2009 23:27:54 +0000 (16:27 -0700)
committerAustin Clements <aclements@csail.mit.edu>
Mon, 10 Aug 2009 23:27:54 +0000 (16:27 -0700)
non-addressable expressions.

R=rsc
APPROVED=rsc
DELTA=241  (202 added, 15 deleted, 24 changed)
OCL=32790
CL=32995

usr/austin/eval/expr.go
usr/austin/eval/stmt.go

index 49882247394c7f4c83f59346cd8ab134562eff8d..6168dfc4175750ed30265e38c8fbfe23c9c72b87 100644 (file)
@@ -546,6 +546,10 @@ func (a *exprCompiler) DoFloatLit(x *ast.FloatLit) {
 }
 
 func (a *exprCompiler) doString(s string) {
+       // Ideal strings don't have a named type but they are
+       // compatible with type string.
+
+       // TODO(austin) Use unnamed string type.
        a.t = StringType;
        a.evalString = func(*Frame) string { return s };
 }
@@ -678,7 +682,10 @@ func (a *exprCompiler) DoSelectorExpr(x *ast.SelectorExpr) {
                // If it's a struct type, check fields and embedded types
                var builder func(*exprCompiler);
                if t, ok := t.(*StructType); ok {
-                       for i, f := range t.Elems {
+                       // TODO(austin) Work around := range bug
+                       var i int;
+                       var f StructField;
+                       for i, f = range t.Elems {
                                var this *exprCompiler;
                                var sub func(*exprCompiler);
                                switch {
@@ -1465,31 +1472,47 @@ func (a *compiler) compileExpr(b *block, expr ast.Expr, constant bool) *exprComp
 // extractEffect separates out any effects that the expression may
 // have, returning a function that will perform those effects and a
 // new exprCompiler that is guaranteed to be side-effect free.  These
-// are the moral equivalents of "temp := &expr" and "*temp".  Because
-// this creates a temporary variable, the caller should create a
-// temporary block for the compilation of this expression and the
-// evaluation of the results.
-//
-// Implementation limit: The expression must be addressable.
-func (a *exprCompiler) extractEffect() (func(f *Frame), *exprCompiler) {
-       if a.evalAddr == nil {
-               // This is a much easier case, but the code is
-               // completely different.
-               log.Crash("extractEffect only implemented for addressable expressions");
-       }
-
-       // Create temporary
+// are the moral equivalents of "temp := expr" and "temp" (or "temp :=
+// &expr" and "*temp" for addressable exprs).  Because this creates a
+// temporary variable, the caller should create a temporary block for
+// the compilation of this expression and the evaluation of the
+// results.
+func (a *exprCompiler) extractEffect(errOp string) (func(f *Frame), *exprCompiler) {
+       // Create "&a" if a is addressable
+       rhs := a;
+       if a.evalAddr != nil {
+               rhs = a.copy();
+               rhs.t = NewPtrType(a.t);
+               rhs.genUnaryAddrOf(a);
+       }
+
+       // Create temp
        tempBlock := a.block;
-       tempType := NewPtrType(a.t);
+       ac, ok := a.checkAssign(a.pos, []*exprCompiler{rhs}, errOp, "");
+       if !ok {
+               return nil, nil;
+       }
+       if len(ac.rmt.Elems) != 1 {
+               a.diag("multi-valued expression not allowed in %s", errOp);
+               return nil, nil;
+       }
+       tempType := ac.rmt.Elems[0];
+       if tempType.isIdeal() {
+               // It's too bad we have to duplicate this rule.
+               switch {
+               case tempType.isInteger():
+                       tempType = IntType;
+               case tempType.isFloat():
+                       tempType = FloatType;
+               default:
+                       log.Crashf("unexpected ideal type %v", tempType);
+               }
+       }
        temp := tempBlock.DefineSlot(tempType);
        tempIdx := temp.Index;
 
-       // Generate "temp := &e"
-       addr := a.copy();
-       addr.t = tempType;
-       addr.genUnaryAddrOf(a);
-
-       assign := a.compileAssign(a.pos, tempType, []*exprCompiler{addr}, "", "");
+       // Create "temp := rhs"
+       assign := ac.compile(tempType);
        if assign == nil {
                log.Crashf("compileAssign type check failed");
        }
@@ -1500,15 +1523,17 @@ func (a *exprCompiler) extractEffect() (func(f *Frame), *exprCompiler) {
                assign(tempVal, f);
        };
 
-       // Generate "*temp"
+       // Generate "temp" or "*temp"
        getTemp := a.copy();
        getTemp.t = tempType;
        getTemp.genIdentOp(0, tempIdx);
+       if a.evalAddr == nil {
+               return effect, getTemp;
+       }
 
        deref := a.copy();
        deref.t = a.t;
        deref.genStarOp(getTemp);
-
        return effect, deref;
 }
 
index 2bd7f8574e4598ac8c2c91722c4a4472a1662e6d..2b401a1ba5891b7794c0ca5499fc27f60b9a716e 100644 (file)
@@ -394,23 +394,26 @@ func (a *stmtCompiler) DoIncDecStmt(s *ast.IncDecStmt) {
                return;
        }
 
-       effect, l := l.extractEffect();
-
-       one := l.copy();
-       one.pos = s.Pos();
-       one.t = IdealIntType;
-       one.evalIdealInt = func() *bignum.Integer { return bignum.Int(1) };
-
        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(desc);
+
+       one := l.copy();
+       one.pos = s.Pos();
+       one.t = IdealIntType;
+       one.evalIdealInt = func() *bignum.Integer { return bignum.Int(1) };
+
        binop := l.copy();
        binop.pos = s.Pos();
        binop.doBinaryExpr(op, l, one);
@@ -673,7 +676,7 @@ func (a *stmtCompiler) doAssignOp(s *ast.AssignStmt) {
                return;
        }
 
-       effect, l := l.extractEffect();
+       effect, l := l.extractEffect("operator-assignment");
 
        binop := r.copy();
        binop.pos = s.TokPos;
@@ -822,7 +825,8 @@ func (a *stmtCompiler) DoBranchStmt(s *ast.BranchStmt) {
                a.flow.putGoto(s.Pos(), l.name, a.block);
 
        case token.FALLTHROUGH:
-               log.Crash("fallthrough not implemented");
+               a.diag("fallthrough outside switch");
+               return;
 
        default:
                log.Crash("Unexpected branch token %v", s.Tok);
@@ -910,11 +914,169 @@ func (a *stmtCompiler) DoIfStmt(s *ast.IfStmt) {
 }
 
 func (a *stmtCompiler) DoCaseClause(s *ast.CaseClause) {
-       log.Crash("Not implemented");
+       a.diag("case clause outside switch");
 }
 
 func (a *stmtCompiler) DoSwitchStmt(s *ast.SwitchStmt) {
-       log.Crash("Not implemented");
+       // 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 *exprCompiler;
+       condbc := bc.enterChild();
+       bad := false;
+       if s.Tag != nil {
+               e := condbc.compileExpr(condbc.block, s.Tag, false);
+               if e == nil {
+                       bad = true;
+               } else {
+                       var effect func(f *Frame);
+                       effect, cond = e.extractEffect("switch");
+                       if effect == nil {
+                               bad = true;
+                       }
+                       a.push(func(v *vm) { effect(v.f) });
+               }
+       }
+
+       // Count cases
+       ncases := 0;
+       hasDefault := false;
+       for i, c := range s.Body.List {
+               clause, ok := c.(*ast.CaseClause);
+               if !ok {
+                       a.diagAt(clause, "switch statement must contain case clauses");
+                       bad = true;
+                       continue;
+               }
+               if clause.Values == nil {
+                       if hasDefault {
+                               a.diagAt(clause, "switch statement contains more than one default case");
+                               bad = true;
+                       }
+                       hasDefault = true;
+               } else {
+                       ncases += len(clause.Values);
+               }
+       }
+
+       // Compile case expressions
+       cases := make([]func(f *Frame) 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, v, false);
+                       switch {
+                       case e == nil:
+                               bad = true;
+                       case cond == nil && !e.t.isBoolean():
+                               a.diagAt(v, "'case' condition must be boolean");
+                               bad = true;
+                       case cond == nil:
+                               cases[i] = e.asBool();
+                       case cond != nil:
+                               // Create comparison
+                               compare := e.copy();
+                               // TOOD(austin) This produces bad error messages
+                               compare.doBinaryExpr(token.EQL, cond, e);
+                               if compare.t == nil {
+                                       bad = true;
+                               } else {
+                                       cases[i] = compare.asBool();
+                               }
+                       }
+                       i++;
+               }
+       }
+
+       // Emit condition
+       casePCs := make([]*uint, ncases+1);
+       endPC := badPC;
+
+       if !bad {
+               a.flow.put(false, false, casePCs);
+               a.push(func(v *vm) {
+                       for i, c := range cases {
+                               if c(v.f) {
+                                       v.pc = *casePCs[i];
+                                       return;
+                               }
+                       }
+                       v.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 _, v := 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:len(clause.Body)] {
+                                       // 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");
+                                               bad = true;
+                                               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 *vm) { v.pc = endPC });
+               }
+       }
+
+       // Get end PC
+       endPC = a.nextPC();
+       if !hasDefault {
+               casePCs[ncases] = &endPC;
+       }
+
+       if !bad {
+               a.err = false;
+       }
 }
 
 func (a *stmtCompiler) DoTypeCaseClause(s *ast.TypeCaseClause) {