Change assignment into expressions
This commit is contained in:
@@ -246,23 +246,50 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
}
|
||||
|
||||
case *ast.AssignmentStatement:
|
||||
symbol, ok := c.symbolTable.Resolve(node.Name.Value)
|
||||
if !ok {
|
||||
return fmt.Errorf("undefined variable %s", node.Value)
|
||||
}
|
||||
case *ast.AssignmentExpression:
|
||||
if ident, ok := node.Left.(*ast.Identifier); ok {
|
||||
symbol, ok := c.symbolTable.Resolve(ident.Value)
|
||||
if !ok {
|
||||
return fmt.Errorf("undefined variable %s", ident.Value)
|
||||
}
|
||||
|
||||
c.l++
|
||||
err := c.Compile(node.Value)
|
||||
c.l--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.l++
|
||||
err := c.Compile(node.Value)
|
||||
c.l--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if symbol.Scope == GlobalScope {
|
||||
c.emit(code.OpAssignGlobal, symbol.Index)
|
||||
if symbol.Scope == GlobalScope {
|
||||
c.emit(code.OpAssignGlobal, symbol.Index)
|
||||
} else {
|
||||
c.emit(code.OpAssignLocal, symbol.Index)
|
||||
}
|
||||
} else if ie, ok := node.Left.(*ast.IndexExpression); ok {
|
||||
c.l++
|
||||
err := c.Compile(ie.Left)
|
||||
c.l--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.l++
|
||||
err = c.Compile(ie.Index)
|
||||
c.l--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.l++
|
||||
err = c.Compile(node.Value)
|
||||
c.l--
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(code.OpSetItem)
|
||||
} else {
|
||||
c.emit(code.OpAssignLocal, symbol.Index)
|
||||
return fmt.Errorf("expected identifier or index expression got=%s", node.Left)
|
||||
}
|
||||
|
||||
case *ast.LetStatement:
|
||||
@@ -349,7 +376,7 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.emit(code.OpIndex)
|
||||
c.emit(code.OpGetItem)
|
||||
|
||||
case *ast.FunctionLiteral:
|
||||
c.enterScope()
|
||||
@@ -446,9 +473,12 @@ func (c *Compiler) Compile(node ast.Node) error {
|
||||
}
|
||||
c.l--
|
||||
|
||||
// Pop off the LoadNull(s) from ast.BlockStatement(s)
|
||||
c.emit(code.OpPop)
|
||||
|
||||
c.emit(code.OpJump, jumpConditionPos)
|
||||
|
||||
afterConsequencePos := c.emit(code.OpNoop)
|
||||
afterConsequencePos := c.emit(code.OpNull)
|
||||
c.changeOperand(jumpIfFalsePos, afterConsequencePos)
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package compiler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"monkey/ast"
|
||||
"monkey/code"
|
||||
"monkey/lexer"
|
||||
@@ -16,6 +17,12 @@ type compilerTestCase struct {
|
||||
expectedInstructions []code.Instructions
|
||||
}
|
||||
|
||||
type compilerTestCase2 struct {
|
||||
input string
|
||||
constants []interface{}
|
||||
instructions string
|
||||
}
|
||||
|
||||
func TestIntegerArithmetic(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
@@ -264,34 +271,30 @@ func TestConditionals(t *testing.T) {
|
||||
// 0006
|
||||
code.Make(code.OpTrue),
|
||||
// 0007
|
||||
code.Make(code.OpJumpNotTruthy, 20),
|
||||
code.Make(code.OpJumpNotTruthy, 19),
|
||||
// 0010
|
||||
code.Make(code.OpConstant, 1),
|
||||
// 0013
|
||||
code.Make(code.OpAssignGlobal, 0),
|
||||
// 0016
|
||||
// 0018
|
||||
code.Make(code.OpJump, 20),
|
||||
// 0019
|
||||
code.Make(code.OpNull),
|
||||
// 0017
|
||||
code.Make(code.OpJump, 21),
|
||||
// 0020
|
||||
code.Make(code.OpNull),
|
||||
// 0021
|
||||
code.Make(code.OpPop),
|
||||
// 0022
|
||||
// 0021
|
||||
code.Make(code.OpFalse),
|
||||
// 0023
|
||||
code.Make(code.OpJumpNotTruthy, 36),
|
||||
// 0025
|
||||
// 0022
|
||||
code.Make(code.OpJumpNotTruthy, 34),
|
||||
// 0024
|
||||
code.Make(code.OpConstant, 2),
|
||||
// 0029
|
||||
// 0028
|
||||
code.Make(code.OpAssignGlobal, 0),
|
||||
// 0032
|
||||
code.Make(code.OpJump, 35),
|
||||
// 0035
|
||||
code.Make(code.OpNull),
|
||||
// 0033
|
||||
code.Make(code.OpJump, 37),
|
||||
// 0036
|
||||
code.Make(code.OpNull),
|
||||
// 0037
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
@@ -475,7 +478,7 @@ func TestIndexExpressions(t *testing.T) {
|
||||
code.Make(code.OpConstant, 3),
|
||||
code.Make(code.OpConstant, 4),
|
||||
code.Make(code.OpAdd),
|
||||
code.Make(code.OpIndex),
|
||||
code.Make(code.OpGetItem),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
@@ -489,7 +492,7 @@ func TestIndexExpressions(t *testing.T) {
|
||||
code.Make(code.OpConstant, 2),
|
||||
code.Make(code.OpConstant, 3),
|
||||
code.Make(code.OpSub),
|
||||
code.Make(code.OpIndex),
|
||||
code.Make(code.OpGetItem),
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
@@ -823,6 +826,21 @@ func TestFunctionCalls(t *testing.T) {
|
||||
runCompilerTests(t, tests)
|
||||
}
|
||||
|
||||
func TestAssignmentExpressions(t *testing.T) {
|
||||
tests := []compilerTestCase2{
|
||||
{
|
||||
input: `
|
||||
let x = 1
|
||||
x = 2
|
||||
`,
|
||||
constants: []interface{}{1, 2},
|
||||
instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpConstant 1\n0009 OpAssignGlobal 0\n0012 OpPop\n",
|
||||
},
|
||||
}
|
||||
|
||||
runCompilerTests2(t, tests)
|
||||
}
|
||||
|
||||
func TestAssignmentStatementScopes(t *testing.T) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
@@ -1085,14 +1103,16 @@ func TestIteration(t *testing.T) {
|
||||
// 0000
|
||||
code.Make(code.OpTrue),
|
||||
// 0001
|
||||
code.Make(code.OpJumpNotTruthy, 10),
|
||||
code.Make(code.OpJumpNotTruthy, 11),
|
||||
// 0004
|
||||
code.Make(code.OpConstant, 0),
|
||||
// 0007
|
||||
code.Make(code.OpPop),
|
||||
// 0008
|
||||
code.Make(code.OpJump, 0),
|
||||
// 0010
|
||||
code.Make(code.OpNoop),
|
||||
// 0011
|
||||
code.Make(code.OpNull),
|
||||
// 0012
|
||||
code.Make(code.OpPop),
|
||||
},
|
||||
},
|
||||
@@ -1127,6 +1147,25 @@ func runCompilerTests(t *testing.T, tests []compilerTestCase) {
|
||||
}
|
||||
}
|
||||
|
||||
func runCompilerTests2(t *testing.T, tests []compilerTestCase2) {
|
||||
t.Helper()
|
||||
|
||||
assert := assert.New(t)
|
||||
|
||||
for _, tt := range tests {
|
||||
program := parse(tt.input)
|
||||
|
||||
compiler := New()
|
||||
err := compiler.Compile(program)
|
||||
assert.NoError(err)
|
||||
|
||||
bytecode := compiler.Bytecode()
|
||||
assert.Equal(tt.instructions, bytecode.Instructions.String())
|
||||
|
||||
testConstants2(t, tt.constants, bytecode.Constants)
|
||||
}
|
||||
}
|
||||
|
||||
func parse(input string) *ast.Program {
|
||||
l := lexer.New(input)
|
||||
p := parser.New(l)
|
||||
@@ -1193,6 +1232,28 @@ func testConstants(t *testing.T, expected []interface{}, actual []object.Object)
|
||||
return nil
|
||||
}
|
||||
|
||||
func testConstants2(t *testing.T, expected []interface{}, actual []object.Object) {
|
||||
assert := assert.New(t)
|
||||
|
||||
assert.Equal(len(expected), len(actual))
|
||||
|
||||
for i, constant := range expected {
|
||||
switch constant := constant.(type) {
|
||||
|
||||
case []code.Instructions:
|
||||
fn, ok := actual[i].(*object.CompiledFunction)
|
||||
assert.True(ok)
|
||||
assert.Equal(constant, fn.Instructions.String())
|
||||
|
||||
case string:
|
||||
assert.Equal(constant, actual[i].(*object.String).Value)
|
||||
|
||||
case int:
|
||||
assert.Equal(int64(constant), actual[i].(*object.Integer).Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIntegerObject(expected int64, actual object.Object) interface{} {
|
||||
result, ok := actual.(*object.Integer)
|
||||
if !ok {
|
||||
|
||||
Reference in New Issue
Block a user