Browse Source

Support for labeled statements. Fixes for statement return values followed by break or continue. Closes #57.

Dmitry Panov 7 years ago
parent
commit
eab79f83e8
4 changed files with 309 additions and 73 deletions
  1. 2 9
      compiler.go
  2. 1 0
      compiler_expr.go
  3. 173 51
      compiler_stmt.go
  4. 133 13
      compiler_test.go

+ 2 - 9
compiler.go

@@ -258,15 +258,8 @@ func (c *compiler) compile(in *ast.Program) {
 	c.compileDeclList(in.DeclarationList, false)
 	c.compileDeclList(in.DeclarationList, false)
 	c.compileFunctions(in.DeclarationList)
 	c.compileFunctions(in.DeclarationList)
 
 
-	if len(in.Body) > 0 {
-		for _, st := range in.Body[:len(in.Body)-1] {
-			c.compileStatement(st, false)
-		}
-
-		c.compileStatement(in.Body[len(in.Body)-1], true)
-	} else {
-		c.compileStatement(&ast.EmptyStatement{}, true)
-	}
+	c.markBlockStart()
+	c.compileStatements(in.Body, true)
 
 
 	c.p.code = append(c.p.code, halt)
 	c.p.code = append(c.p.code, halt)
 	code := c.p.code
 	code := c.p.code

+ 1 - 0
compiler_expr.go

@@ -782,6 +782,7 @@ func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) {
 	}
 	}
 
 
 	e.c.compileFunctions(e.expr.DeclarationList)
 	e.c.compileFunctions(e.expr.DeclarationList)
+	e.c.markBlockStart()
 	e.c.compileStatement(e.expr.Body, false)
 	e.c.compileStatement(e.expr.Body, false)
 
 
 	if e.c.blockStart >= len(e.c.p.code)-1 || e.c.p.code[len(e.c.p.code)-1] != ret {
 	if e.c.blockStart >= len(e.c.p.code)-1 || e.c.p.code[len(e.c.p.code)-1] != ret {

+ 173 - 51
compiler_stmt.go

@@ -44,9 +44,7 @@ func (c *compiler) compileStatement(v ast.Statement, needResult bool) {
 	case *ast.LabelledStatement:
 	case *ast.LabelledStatement:
 		c.compileLabeledStatement(v, needResult)
 		c.compileLabeledStatement(v, needResult)
 	case *ast.EmptyStatement:
 	case *ast.EmptyStatement:
-		if needResult {
-			c.emit(loadUndef)
-		}
+		c.compileEmptyStatement(needResult)
 	case *ast.WithStatement:
 	case *ast.WithStatement:
 		c.compileWithStatement(v, needResult)
 		c.compileWithStatement(v, needResult)
 	case *ast.DebuggerStatement:
 	case *ast.DebuggerStatement:
@@ -63,8 +61,6 @@ func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult
 		}
 		}
 	}
 	}
 	switch s := v.Statement.(type) {
 	switch s := v.Statement.(type) {
-	case *ast.BlockStatement:
-		c.compileLabeledBlockStatement(s, needResult, label)
 	case *ast.ForInStatement:
 	case *ast.ForInStatement:
 		c.compileLabeledForInStatement(s, needResult, label)
 		c.compileLabeledForInStatement(s, needResult, label)
 	case *ast.ForStatement:
 	case *ast.ForStatement:
@@ -74,7 +70,7 @@ func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult
 	case *ast.DoWhileStatement:
 	case *ast.DoWhileStatement:
 		c.compileLabeledDoWhileStatement(s, needResult, label)
 		c.compileLabeledDoWhileStatement(s, needResult, label)
 	default:
 	default:
-		c.compileStatement(v.Statement, needResult)
+		c.compileGenericLabeledStatement(v.Statement, needResult, label)
 	}
 	}
 }
 }
 
 
@@ -209,20 +205,21 @@ func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult b
 
 
 func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label string) {
 func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label string) {
 	c.block = &block{
 	c.block = &block{
-		typ:   blockLoop,
-		outer: c.block,
-		label: label,
+		typ:        blockLoop,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
 	}
 	}
 
 
 	if needResult {
 	if needResult {
-		c.emit(loadUndef)
+		c.emit(jump(2))
 	}
 	}
 	start := len(c.p.code)
 	start := len(c.p.code)
-	c.markBlockStart()
-	c.compileStatement(v.Body, needResult)
 	if needResult {
 	if needResult {
-		c.emit(rdupN(1), pop)
+		c.emit(pop)
 	}
 	}
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
 	c.block.cont = len(c.p.code)
 	c.block.cont = len(c.p.code)
 	c.emitExpr(c.compileExpression(v.Test), true)
 	c.emitExpr(c.compileExpression(v.Test), true)
 	c.emit(jeq(start - len(c.p.code)))
 	c.emit(jeq(start - len(c.p.code)))
@@ -245,7 +242,7 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
 		c.compileExpression(v.Initializer).emitGetter(false)
 		c.compileExpression(v.Initializer).emitGetter(false)
 	}
 	}
 	if needResult {
 	if needResult {
-		c.emit(loadUndef)
+		c.emit(loadUndef) // initial result
 	}
 	}
 	start := len(c.p.code)
 	start := len(c.p.code)
 	c.markBlockStart()
 	c.markBlockStart()
@@ -281,10 +278,11 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
 			c.emit(nil)
 			c.emit(nil)
 		}
 		}
 	}
 	}
-	c.compileStatement(v.Body, needResult)
 	if needResult {
 	if needResult {
-		c.emit(rdupN(1), pop)
+		c.emit(pop) // remove last result
 	}
 	}
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
 	c.block.cont = len(c.p.code)
 	c.block.cont = len(c.p.code)
 	if v.Update != nil {
 	if v.Update != nil {
 		c.compileExpression(v.Update).emitGetter(false)
 		c.compileExpression(v.Update).emitGetter(false)
@@ -323,10 +321,11 @@ func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResul
 	c.emit(nil)
 	c.emit(nil)
 	c.compileExpression(v.Into).emitSetter(&c.enumGetExpr)
 	c.compileExpression(v.Into).emitSetter(&c.enumGetExpr)
 	c.emit(pop)
 	c.emit(pop)
-	c.compileStatement(v.Body, needResult)
 	if needResult {
 	if needResult {
-		c.emit(rdupN(1), pop)
+		c.emit(pop) // remove last result
 	}
 	}
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
 	c.emit(jump(start - len(c.p.code)))
 	c.emit(jump(start - len(c.p.code)))
 	c.p.code[start] = enumNext(len(c.p.code) - start)
 	c.p.code[start] = enumNext(len(c.p.code) - start)
 	c.leaveBlock()
 	c.leaveBlock()
@@ -340,9 +339,10 @@ func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool)
 
 
 func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) {
 func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label string) {
 	c.block = &block{
 	c.block = &block{
-		typ:   blockLoop,
-		outer: c.block,
-		label: label,
+		typ:        blockLoop,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
 	}
 	}
 
 
 	if needResult {
 	if needResult {
@@ -374,10 +374,11 @@ func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResul
 		j = len(c.p.code)
 		j = len(c.p.code)
 		c.emit(nil)
 		c.emit(nil)
 	}
 	}
-	c.compileStatement(v.Body, needResult)
 	if needResult {
 	if needResult {
-		c.emit(rdupN(1), pop)
+		c.emit(pop)
 	}
 	}
+	c.markBlockStart()
+	c.compileStatement(v.Body, needResult)
 	c.emit(jump(start - len(c.p.code)))
 	c.emit(jump(start - len(c.p.code)))
 	if !testTrue {
 	if !testTrue {
 		c.p.code[j] = jne(len(c.p.code) - j)
 		c.p.code[j] = jne(len(c.p.code) - j)
@@ -387,14 +388,16 @@ end:
 	c.markBlockStart()
 	c.markBlockStart()
 }
 }
 
 
-func (c *compiler) compileBranchStatement(v *ast.BranchStatement, needResult bool) {
+func (c *compiler) compileEmptyStatement(needResult bool) {
 	if needResult {
 	if needResult {
-		if c.p.code[len(c.p.code)-1] != pop {
-			// panic("Not pop")
-		} else {
-			c.p.code = c.p.code[:len(c.p.code)-1]
+		if len(c.p.code) == c.blockStart {
+			// first statement in block, use undefined as result
+			c.emit(loadUndef)
 		}
 		}
 	}
 	}
+}
+
+func (c *compiler) compileBranchStatement(v *ast.BranchStatement, needResult bool) {
 	switch v.Token {
 	switch v.Token {
 	case token.BREAK:
 	case token.BREAK:
 		c.compileBreak(v.Label, v.Idx)
 		c.compileBreak(v.Label, v.Idx)
@@ -405,6 +408,60 @@ func (c *compiler) compileBranchStatement(v *ast.BranchStatement, needResult boo
 	}
 	}
 }
 }
 
 
+func (c *compiler) findBranchBlock(st *ast.BranchStatement) *block {
+	switch st.Token {
+	case token.BREAK:
+		return c.findBreakBlock(st.Label)
+	case token.CONTINUE:
+		return c.findContinueBlock(st.Label)
+	}
+	return nil
+}
+
+func (c *compiler) findContinueBlock(label *ast.Identifier) (block *block) {
+	if label != nil {
+		for b := c.block; b != nil; b = b.outer {
+			if b.typ == blockLoop && b.label == label.Name {
+				block = b
+				break
+			}
+		}
+	} else {
+		// find the nearest loop
+		for b := c.block; b != nil; b = b.outer {
+			if b.typ == blockLoop {
+				block = b
+				break
+			}
+		}
+	}
+
+	return
+}
+
+func (c *compiler) findBreakBlock(label *ast.Identifier) (block *block) {
+	if label != nil {
+		for b := c.block; b != nil; b = b.outer {
+			if b.label == label.Name {
+				block = b
+				break
+			}
+		}
+	} else {
+		// find the nearest loop or switch
+	L:
+		for b := c.block; b != nil; b = b.outer {
+			switch b.typ {
+			case blockLoop, blockSwitch:
+				block = b
+				break L
+			}
+		}
+	}
+
+	return
+}
+
 func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 	var block *block
 	var block *block
 	if label != nil {
 	if label != nil {
@@ -437,8 +494,8 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 	}
 	}
 
 
 	if block != nil {
 	if block != nil {
-		if block.needResult {
-			c.emit(pop, loadUndef)
+		if len(c.p.code) == c.blockStart && block.needResult {
+			c.emit(loadUndef)
 		}
 		}
 		block.breaks = append(block.breaks, len(c.p.code))
 		block.breaks = append(block.breaks, len(c.p.code))
 		c.emit(nil)
 		c.emit(nil)
@@ -450,7 +507,6 @@ func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) {
 func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 	var block *block
 	var block *block
 	if label != nil {
 	if label != nil {
-
 		for b := c.block; b != nil; b = b.outer {
 		for b := c.block; b != nil; b = b.outer {
 			if b.typ == blockTry {
 			if b.typ == blockTry {
 				c.emit(halt)
 				c.emit(halt)
@@ -472,6 +528,9 @@ func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) {
 	}
 	}
 
 
 	if block != nil {
 	if block != nil {
+		if len(c.p.code) == c.blockStart && block.needResult {
+			c.emit(loadUndef)
+		}
 		block.conts = append(block.conts, len(c.p.code))
 		block.conts = append(block.conts, len(c.p.code))
 		c.emit(nil)
 		c.emit(nil)
 	} else {
 	} else {
@@ -489,10 +548,12 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
 			return
 			return
 		}
 		}
 		if r.ToBoolean() {
 		if r.ToBoolean() {
+			c.markBlockStart()
 			c.compileStatement(v.Consequent, needResult)
 			c.compileStatement(v.Consequent, needResult)
 			if v.Alternate != nil {
 			if v.Alternate != nil {
 				p := c.p
 				p := c.p
 				c.p = &Program{}
 				c.p = &Program{}
+				c.markBlockStart()
 				c.compileStatement(v.Alternate, false)
 				c.compileStatement(v.Alternate, false)
 				c.p = p
 				c.p = p
 			}
 			}
@@ -515,6 +576,7 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
 	test.emitGetter(true)
 	test.emitGetter(true)
 	jmp := len(c.p.code)
 	jmp := len(c.p.code)
 	c.emit(nil)
 	c.emit(nil)
+	c.markBlockStart()
 	c.compileStatement(v.Consequent, needResult)
 	c.compileStatement(v.Consequent, needResult)
 	if v.Alternate != nil {
 	if v.Alternate != nil {
 		jmp1 := len(c.p.code)
 		jmp1 := len(c.p.code)
@@ -557,15 +619,60 @@ func (c *compiler) compileVariableStatement(v *ast.VariableStatement, needResult
 	}
 	}
 }
 }
 
 
+func (c *compiler) getFirstNonEmptyStatement(st ast.Statement) ast.Statement {
+	switch st := st.(type) {
+	case *ast.BlockStatement:
+		return c.getFirstNonEmptyStatementList(st.List)
+	case *ast.LabelledStatement:
+		return c.getFirstNonEmptyStatement(st.Statement)
+	}
+	return st
+}
+
+func (c *compiler) getFirstNonEmptyStatementList(list []ast.Statement) ast.Statement {
+	for _, st := range list {
+		switch st := st.(type) {
+		case *ast.EmptyStatement:
+			continue
+		case *ast.BlockStatement:
+			return c.getFirstNonEmptyStatementList(st.List)
+		case *ast.LabelledStatement:
+			return c.getFirstNonEmptyStatement(st.Statement)
+		}
+		return st
+	}
+	return nil
+}
+
 func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
 func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
 	if len(list) > 0 {
 	if len(list) > 0 {
-		for _, s := range list[:len(list)-1] {
-			c.compileStatement(s, needResult)
-			if needResult {
-				c.emit(pop)
+		cur := list[0]
+		for idx := 0; idx < len(list); {
+			var next ast.Statement
+			// find next non-empty statement
+			for idx++; idx < len(list); idx++ {
+				if _, empty := list[idx].(*ast.EmptyStatement); !empty {
+					next = list[idx]
+					break
+				}
+			}
+
+			if next != nil {
+				bs := c.getFirstNonEmptyStatement(next)
+				if bs, ok := bs.(*ast.BranchStatement); ok {
+					block := c.findBranchBlock(bs)
+					if block != nil {
+						c.compileStatement(cur, block.needResult)
+						cur = next
+						continue
+					}
+				}
+				c.compileStatement(cur, false)
+				cur = next
+			} else {
+				c.compileStatement(cur, needResult)
 			}
 			}
 		}
 		}
-		c.compileStatement(list[len(list)-1], needResult)
 	} else {
 	} else {
 		if needResult {
 		if needResult {
 			c.emit(loadUndef)
 			c.emit(loadUndef)
@@ -573,13 +680,14 @@ func (c *compiler) compileStatements(list []ast.Statement, needResult bool) {
 	}
 	}
 }
 }
 
 
-func (c *compiler) compileLabeledBlockStatement(v *ast.BlockStatement, needResult bool, label string) {
+func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label string) {
 	c.block = &block{
 	c.block = &block{
-		typ:   blockBranch,
-		outer: c.block,
-		label: label,
+		typ:        blockBranch,
+		outer:      c.block,
+		label:      label,
+		needResult: needResult,
 	}
 	}
-	c.compileBlockStatement(v, needResult)
+	c.compileStatement(v, needResult)
 	c.leaveBlock()
 	c.leaveBlock()
 }
 }
 
 
@@ -604,8 +712,9 @@ func (c *compiler) compileWithStatement(v *ast.WithStatement, needResult bool) {
 	c.compileExpression(v.Object).emitGetter(true)
 	c.compileExpression(v.Object).emitGetter(true)
 	c.emit(enterWith)
 	c.emit(enterWith)
 	c.block = &block{
 	c.block = &block{
-		outer: c.block,
-		typ:   blockWith,
+		outer:      c.block,
+		typ:        blockWith,
+		needResult: needResult,
 	}
 	}
 	c.newScope()
 	c.newScope()
 	c.scope.dynamic = true
 	c.scope.dynamic = true
@@ -618,12 +727,9 @@ func (c *compiler) compileWithStatement(v *ast.WithStatement, needResult bool) {
 
 
 func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult bool) {
 func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult bool) {
 	c.block = &block{
 	c.block = &block{
-		typ:   blockSwitch,
-		outer: c.block,
-	}
-
-	if needResult {
-		c.emit(loadUndef)
+		typ:        blockSwitch,
+		outer:      c.block,
+		needResult: needResult,
 	}
 	}
 
 
 	c.compileExpression(v.Discriminant).emitGetter(true)
 	c.compileExpression(v.Discriminant).emitGetter(true)
@@ -658,13 +764,29 @@ func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult boo
 			c.p.code[jumps[i]] = jump(len(c.p.code) - jumps[i])
 			c.p.code[jumps[i]] = jump(len(c.p.code) - jumps[i])
 			c.markBlockStart()
 			c.markBlockStart()
 		}
 		}
+		nr := false
+		c.markBlockStart()
 		if needResult {
 		if needResult {
-			c.emit(pop)
+			if i < len(v.Body)-1 {
+				st := c.getFirstNonEmptyStatementList(v.Body[i+1].Consequent)
+				if st, ok := st.(*ast.BranchStatement); ok && st.Token == token.BREAK {
+					if c.findBreakBlock(st.Label) != nil {
+						stmts := append(s.Consequent, st)
+						c.compileStatements(stmts, false)
+						continue
+					}
+				}
+			} else {
+				nr = true
+			}
 		}
 		}
-		c.compileStatements(s.Consequent, needResult)
+		c.compileStatements(s.Consequent, nr)
 	}
 	}
 	if jumpNoMatch != -1 {
 	if jumpNoMatch != -1 {
 		c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch)
 		c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch)
+		if len(v.Body) == 0 && needResult {
+			c.emit(loadUndef)
+		}
 	}
 	}
 	c.leaveBlock()
 	c.leaveBlock()
 	c.markBlockStart()
 	c.markBlockStart()

+ 133 - 13
compiler_test.go

@@ -488,6 +488,38 @@ func TestIfElseRetVal(t *testing.T) {
 	testScript1(SCRIPT, asciiString("passed"), t)
 	testScript1(SCRIPT, asciiString("passed"), t)
 }
 }
 
 
+func TestWhileReturnValue(t *testing.T) {
+	const SCRIPT = `
+	var x = 0;
+	while(true) {
+		x = 1;
+		break;
+	}
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestIfElseLabel(t *testing.T) {
+	const SCRIPT = `
+	var x = 0;
+	abc: if (true) {
+		x = 1;
+		break abc;
+	}
+	`
+	testScript1(SCRIPT, intToValue(1), t)
+}
+
+func TestIfMultipleLabels(t *testing.T) {
+	const SCRIPT = `
+	var x = 0;
+	xyz:abc: if (true) {
+		break xyz;
+	}
+	`
+	testScript1(SCRIPT, _undefined, t)
+}
+
 func TestBreakOutOfTry(t *testing.T) {
 func TestBreakOutOfTry(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
 	function A() {
 	function A() {
@@ -902,9 +934,79 @@ func TestEvalFunctionExpr(t *testing.T) {
 	testScript1(SCRIPT, intToValue(42), t)
 	testScript1(SCRIPT, intToValue(42), t)
 }
 }
 
 
-func TestLoopRet(t *testing.T) {
+func TestForLoopRet(t *testing.T) {
+	const SCRIPT = `
+	for (var i = 0; i < 20; i++) { if (i > 2) {break;} else { i }}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestForLoopRet1(t *testing.T) {
+	const SCRIPT = `
+	for (var i = 0; i < 20; i++) { if (i > 2) {42;; {L:{break;}}} else { i }}
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
+func TestForInLoopRet(t *testing.T) {
+	const SCRIPT = `
+	var o = [1, 2, 3, 4];
+	for (var i in o) { if (i > 2) {break;} else { i }}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestForInLoopRet1(t *testing.T) {
+	const SCRIPT = `
+	var o = {};
+	o.x = 1;
+	o.y = 2;
+	for (var i in o) {
+		true;
+	}
+
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestDoWhileLoopRet(t *testing.T) {
+	const SCRIPT = `
+	var i = 0;
+	do {
+		if (i > 2) {
+			break;
+		} else {
+			i;
+		}
+	} while (i++ < 20);
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
+
+func TestDoWhileContinueRet(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
-	for (var i = 0; i < 20; i++) { if (i > 1) {break;} else { i }}
+	var i = 0;
+	do {
+		if (i > 2) {
+			true;
+			continue;
+		} else {
+			i;
+		}
+	} while (i++ < 20);
+	`
+
+	testScript1(SCRIPT, valueTrue, t)
+}
+
+func TestWhileLoopRet(t *testing.T) {
+	const SCRIPT = `
+	var i; while (i < 20) { if (i > 2) {break;} else { i++ }}
 	`
 	`
 
 
 	testScript1(SCRIPT, _undefined, t)
 	testScript1(SCRIPT, _undefined, t)
@@ -1381,24 +1483,26 @@ func TestForInLoop(t *testing.T) {
 	testScript1(SCRIPT, valueTrue, t)
 	testScript1(SCRIPT, valueTrue, t)
 }
 }
 
 
-func TestForInLoopRet(t *testing.T) {
+func TestWhileLoopResult(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
-	var o = {};
-	o.x = 1;
-	o.y = 2;
-	for (var i in o) {
-		true;
-	}
+	while(false);
 
 
 	`
 	`
 
 
-	testScript1(SCRIPT, valueTrue, t)
+	testScript1(SCRIPT, _undefined, t)
 }
 }
 
 
-func TestWhileLoopResult(t *testing.T) {
+func TestEmptySwitch(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
-	while(false);
+	switch(1){}
+	`
+
+	testScript1(SCRIPT, _undefined, t)
+}
 
 
+func TestEmptyDoWhile(t *testing.T) {
+	const SCRIPT = `
+	do {} while(false)
 	`
 	`
 
 
 	testScript1(SCRIPT, _undefined, t)
 	testScript1(SCRIPT, _undefined, t)
@@ -1475,16 +1579,32 @@ func TestSwitchResult(t *testing.T) {
 	default:
 	default:
 		"default";
 		"default";
 	}
 	}
+	`
+
+	testScript1(SCRIPT, asciiString("two"), t)
+}
 
 
+func TestSwitchResult1(t *testing.T) {
+	const SCRIPT = `
+	var x = 0;
+	switch (x) { case 0: "two"; case 1: break}
 	`
 	`
 
 
 	testScript1(SCRIPT, asciiString("two"), t)
 	testScript1(SCRIPT, asciiString("two"), t)
 }
 }
 
 
+func TestIfBreakResult(t *testing.T) {
+	const SCRIPT = `
+	L: {if (true) {42;} break L;}
+	`
+
+	testScript1(SCRIPT, intToValue(42), t)
+}
+
 func TestSwitchNoMatch(t *testing.T) {
 func TestSwitchNoMatch(t *testing.T) {
 	const SCRIPT = `
 	const SCRIPT = `
-	var x = 5;
 	var result;
 	var result;
+	var x;
 	switch (x) {
 	switch (x) {
 	case 0:
 	case 0:
 		result = "2";
 		result = "2";