diff --git a/code/code.go b/code/code.go index 282a348..24d0e4b 100644 --- a/code/code.go +++ b/code/code.go @@ -26,6 +26,13 @@ const ( OpMul OpDiv OpMod + OpOr + OpAnd + OpNot + OpBitwiseOR + OpBitwiseXOR + OpBitwiseAND + OpBitwiseNOT OpTrue OpFalse OpEqual @@ -33,7 +40,6 @@ const ( OpGreaterThan OpGreaterThanEqual OpMinus - OpBang OpJumpNotTruthy OpJump OpNull @@ -71,6 +77,13 @@ var definitions = map[Opcode]*Definition{ OpMul: {"OpMul", []int{}}, OpDiv: {"OpDiv", []int{}}, OpMod: {"OpMod", []int{}}, + OpOr: {"OpOr", []int{}}, + OpAnd: {"OpAnd", []int{}}, + OpNot: {"OpNot", []int{}}, + OpBitwiseOR: {"OpBitwiseOR", []int{}}, + OpBitwiseXOR: {"OpBitwiseXOR", []int{}}, + OpBitwiseAND: {"OpBitwiseAND", []int{}}, + OpBitwiseNOT: {"OpBitwiseNOT", []int{}}, OpTrue: {"OpTrue", []int{}}, OpFalse: {"OpFalse", []int{}}, OpEqual: {"OpEqual", []int{}}, @@ -78,7 +91,6 @@ var definitions = map[Opcode]*Definition{ OpGreaterThan: {"OpGreaterThan", []int{}}, OpGreaterThanEqual: {"OpGreaterThanEqual", []int{}}, OpMinus: {"OpMinus", []int{}}, - OpBang: {"OpBang", []int{}}, OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, OpJump: {"OpJump", []int{2}}, OpNull: {"OpNull", []int{}}, diff --git a/compiler/compiler.go b/compiler/compiler.go index 240bf9f..1382f4f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -141,6 +141,16 @@ func (c *Compiler) Compile(node ast.Node) error { c.emit(code.OpDiv) case "%": c.emit(code.OpMod) + case "|": + c.emit(code.OpBitwiseOR) + case "^": + c.emit(code.OpBitwiseXOR) + case "&": + c.emit(code.OpBitwiseAND) + case "||": + c.emit(code.OpOr) + case "&&": + c.emit(code.OpAnd) case ">": c.emit(code.OpGreaterThan) case ">=": @@ -177,7 +187,9 @@ func (c *Compiler) Compile(node ast.Node) error { switch node.Operator { case "!": - c.emit(code.OpBang) + c.emit(code.OpNot) + case "~": + c.emit(code.OpBitwiseNOT) case "-": c.emit(code.OpMinus) default: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 4c05314..771aa2f 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -96,6 +96,38 @@ func TestIntegerArithmetic(t *testing.T) { code.Make(code.OpPop), }, }, + { + input: "5 | 2", + expectedConstants: []interface{}{5, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpBitwiseOR), + code.Make(code.OpPop), + }, + }, + + { + input: "5 ^ 2", + expectedConstants: []interface{}{5, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpBitwiseXOR), + code.Make(code.OpPop), + }, + }, + + { + input: "5 & 2", + expectedConstants: []interface{}{5, 2}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpBitwiseAND), + code.Make(code.OpPop), + }, + }, } runCompilerTests(t, tests) @@ -207,12 +239,32 @@ func TestBooleanExpressions(t *testing.T) { code.Make(code.OpPop), }, }, + { + input: "true && false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpAnd), + code.Make(code.OpPop), + }, + }, + { + input: "true || false", + expectedConstants: []interface{}{}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpTrue), + code.Make(code.OpFalse), + code.Make(code.OpOr), + code.Make(code.OpPop), + }, + }, { input: "!true", expectedConstants: []interface{}{}, expectedInstructions: []code.Instructions{ code.Make(code.OpTrue), - code.Make(code.OpBang), + code.Make(code.OpNot), code.Make(code.OpPop), }, }, @@ -228,20 +280,20 @@ func TestConditionals(t *testing.T) { if (true) { 10 }; 3333; `, constants: []interface{}{10, 3333}, - instructions: "0000 LoadTrue\n0001 JumpIfFalse 10\n0004 LoadConstant 0\n0007 Jump 11\n0010 LoadNull\n0011 Pop\n0012 LoadConstant 1\n0015 Pop\n", + instructions: "0000 OpTrue\n0001 OpJumpNotTruthy 10\n0004 OpConstant 0\n0007 OpJump 11\n0010 OpNull\n0011 OpPop\n0012 OpConstant 1\n0015 OpPop\n", }, { input: ` if (true) { 10 } else { 20 }; 3333; `, constants: []interface{}{10, 20, 3333}, - instructions: "0000 LoadTrue\n0001 JumpIfFalse 10\n0004 LoadConstant 0\n0007 Jump 13\n0010 LoadConstant 1\n0013 Pop\n0014 LoadConstant 2\n0017 Pop\n", + instructions: "0000 OpTrue\n0001 OpJumpNotTruthy 10\n0004 OpConstant 0\n0007 OpJump 13\n0010 OpConstant 1\n0013 OpPop\n0014 OpConstant 2\n0017 OpPop\n", }, { input: ` - let x = 0; if (true) { x = 1; }; if (false) { x = 2; } + x := 0; if (true) { x = 1; }; if (false) { x = 2; } `, constants: []interface{}{0, 1, 2}, - instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadTrue\n0008 JumpIfFalse 20\n0011 LoadConstant 1\n0014 AssignGlobal 0\n0017 Jump 21\n0020 LoadNull\n0021 Pop\n0022 LoadFalse\n0023 JumpIfFalse 35\n0026 LoadConstant 2\n0029 AssignGlobal 0\n0032 Jump 36\n0035 LoadNull\n0036 Pop\n", + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpTrue\n0008 OpJumpNotTruthy 20\n0011 OpConstant 1\n0014 OpAssignGlobal 0\n0017 OpJump 21\n0020 OpNull\n0021 OpPop\n0022 OpFalse\n0023 OpJumpNotTruthy 35\n0026 OpConstant 2\n0029 OpAssignGlobal 0\n0032 OpJump 36\n0035 OpNull\n0036 OpPop\n", }, } @@ -255,7 +307,8 @@ func TestGlobalBindExpressions(t *testing.T) { one := 1; two := 2; `, - instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadConstant 1\n0010 BindGlobal 1\n0013 Pop\n", + constants: []interface{}{1, 2}, + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpConstant 1\n0010 OpSetGlobal 1\n0013 OpPop\n", }, { input: ` @@ -263,7 +316,7 @@ func TestGlobalBindExpressions(t *testing.T) { one; `, constants: []interface{}{1}, - instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadGlobal 0\n0010 Pop\n", + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpGetGlobal 0\n0010 OpPop\n", }, { input: ` @@ -272,7 +325,7 @@ func TestGlobalBindExpressions(t *testing.T) { two; `, constants: []interface{}{1}, - instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadGlobal 0\n0010 BindGlobal 1\n0013 Pop\n0014 LoadGlobal 1\n0017 Pop\n", + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpGetGlobal 0\n0010 OpSetGlobal 1\n0013 OpPop\n0014 OpGetGlobal 1\n0017 OpPop\n", }, } @@ -490,31 +543,20 @@ func TestFunctionsWithoutReturn(t *testing.T) { } func TestClosures(t *testing.T) { - tests := []compilerTestCase{ + tests := []compilerTestCase2{ { - input: `fn(a) { - return fn(b) { - return a + b - } - } - `, - expectedConstants: []interface{}{ - []code.Instructions{ - code.Make(code.OpGetFree, 0), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpAdd), - code.Make(code.OpReturn), - }, - []code.Instructions{ - code.Make(code.OpGetLocal, 0), - code.Make(code.OpClosure, 0, 1), - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 1, 0), - code.Make(code.OpPop), + input: ` + fn(a) { + return fn(b) { + return a + b + } + } + `, + constants: []interface{}{ + Instructions("0000 OpGetFree 0\n0002 OpGetLocal 0\n0004 OpAdd\n0005 OpReturn\n"), + Instructions("0000 OpGetLocal 0\n0002 OpClosure 0 1\n0006 OpReturn\n"), }, + instructions: "0000 OpClosure 1 0\n0004 OpPop\n", }, { input: ` @@ -526,93 +568,45 @@ func TestClosures(t *testing.T) { } }; `, - expectedConstants: []interface{}{ - []code.Instructions{ - code.Make(code.OpGetFree, 0), - code.Make(code.OpGetFree, 1), - code.Make(code.OpAdd), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpAdd), - code.Make(code.OpReturn), - }, - []code.Instructions{ - code.Make(code.OpGetFree, 0), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpClosure, 0, 2), - code.Make(code.OpReturn), - }, - []code.Instructions{ - code.Make(code.OpGetLocal, 0), - code.Make(code.OpClosure, 1, 1), - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 2, 0), - code.Make(code.OpPop), + constants: []interface{}{ + Instructions("0000 OpGetFree 0\n0002 OpGetFree 1\n0004 OpAdd\n0005 OpGetLocal 0\n0007 OpAdd\n0008 OpReturn\n"), + Instructions("0000 OpGetFree 0\n0002 OpGetLocal 0\n0004 OpClosure 0 2\n0008 OpReturn\n"), + Instructions("0000 OpGetLocal 0\n0002 OpClosure 1 1\n0006 OpReturn\n"), }, + instructions: "0000 OpClosure 2 0\n0004 OpPop\n", }, { input: ` - let global = 55; + global := 55; fn() { - let a = 66; + a := 66; return fn() { - let b = 77; + b := 77; return fn() { - let c = 88; + c := 88; return global + a + b + c; } } } `, - expectedConstants: []interface{}{ + constants: []interface{}{ 55, 66, 77, 88, - []code.Instructions{ - code.Make(code.OpConstant, 3), - code.Make(code.OpSetLocal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpGetFree, 0), - code.Make(code.OpAdd), - code.Make(code.OpGetFree, 1), - code.Make(code.OpAdd), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpAdd), - code.Make(code.OpReturn), - }, - []code.Instructions{ - code.Make(code.OpConstant, 2), - code.Make(code.OpSetLocal, 0), - code.Make(code.OpGetFree, 0), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpClosure, 4, 2), - code.Make(code.OpReturn), - }, - []code.Instructions{ - code.Make(code.OpConstant, 1), - code.Make(code.OpSetLocal, 0), - code.Make(code.OpGetLocal, 0), - code.Make(code.OpClosure, 5, 1), - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpConstant, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpClosure, 6, 0), - code.Make(code.OpPop), + Instructions("0000 OpConstant 3\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetGlobal 0\n0009 OpGetFree 0\n0011 OpAdd\n0012 OpGetFree 1\n0014 OpAdd\n0015 OpGetLocal 0\n0017 OpAdd\n0018 OpReturn\n"), + Instructions("0000 OpConstant 2\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetFree 0\n0008 OpGetLocal 0\n0010 OpClosure 4 2\n0014 OpReturn\n"), + Instructions("0000 OpConstant 1\n0003 OpSetLocal 0\n0005 OpPop\n0006 OpGetLocal 0\n0008 OpClosure 5 1\n0012 OpReturn\n"), }, + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpClosure 6 0\n0011 OpPop\n", }, } - runCompilerTests(t, tests) + runCompilerTests2(t, tests) } func TestCompilerScopes(t *testing.T) { @@ -667,9 +661,9 @@ func TestFunctionCalls(t *testing.T) { input: `fn() { return 24 }();`, constants: []interface{}{ 24, - Instructions("0000 LoadConstant 0\n0003 Return\n"), + Instructions("0000 OpConstant 0\n0003 OpReturn\n"), }, - instructions: "0000 MakeClosure 1 0\n0004 Call 0\n0006 Pop\n", + instructions: "0000 OpClosure 1 0\n0004 OpCall 0\n0006 OpPop\n", }, { input: ` @@ -678,9 +672,9 @@ func TestFunctionCalls(t *testing.T) { `, constants: []interface{}{ 24, - Instructions("0000 SetSelf 0\n0002 LoadConstant 0\n0005 Return\n"), + Instructions("0000 OpConstant 0\n0003 OpReturn\n"), }, - instructions: "0000 LoadGlobal 0\n0003 MakeClosure 1 1\n0007 BindGlobal 0\n0010 Pop\n0011 LoadGlobal 0\n0014 Call 0\n0016 Pop\n", + instructions: "0000 OpClosure 1 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpCall 0\n0013 OpPop\n", }, { input: ` @@ -688,10 +682,10 @@ func TestFunctionCalls(t *testing.T) { oneArg(24); `, constants: []interface{}{ - Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Return\n"), + Instructions("0000 OpGetLocal 0\n0002 OpReturn\n"), 24, }, - instructions: "0000 LoadGlobal 0\n0003 MakeClosure 0 1\n0007 BindGlobal 0\n0010 Pop\n0011 LoadGlobal 0\n0014 LoadConstant 1\n0017 Call 1\n0019 Pop\n", + instructions: "0000 OpClosure 0 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpConstant 1\n0014 OpCall 1\n0016 OpPop\n", }, { input: ` @@ -699,12 +693,12 @@ func TestFunctionCalls(t *testing.T) { manyArg(24, 25, 26); `, constants: []interface{}{ - Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Pop\n0005 LoadLocal 1\n0007 Pop\n0008 LoadLocal 2\n0010 Return\n"), + Instructions("0000 OpGetLocal 0\n0002 OpPop\n0003 OpGetLocal 1\n0005 OpPop\n0006 OpGetLocal 2\n0008 OpReturn\n"), 24, 25, 26, }, - instructions: "0000 LoadGlobal 0\n0003 MakeClosure 0 1\n0007 BindGlobal 0\n0010 Pop\n0011 LoadGlobal 0\n0014 LoadConstant 1\n0017 LoadConstant 2\n0020 LoadConstant 3\n0023 Call 3\n0025 Pop\n", + instructions: "0000 OpClosure 0 0\n0004 OpSetGlobal 0\n0007 OpPop\n0008 OpGetGlobal 0\n0011 OpConstant 1\n0014 OpConstant 2\n0017 OpConstant 3\n0020 OpCall 3\n0022 OpPop\n", }, } @@ -715,11 +709,11 @@ func TestAssignmentExpressions(t *testing.T) { tests := []compilerTestCase2{ { input: ` - let x = 1 + x := 1 x = 2 `, constants: []interface{}{1, 2}, - instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpConstant 1\n0009 OpAssignGlobal 0\n0012 OpPop\n", + instructions: "0000 OpConstant 0\n0003 OpSetGlobal 0\n0006 OpPop\n0007 OpConstant 1\n0010 OpAssignGlobal 0\n0013 OpPop\n", }, } @@ -730,7 +724,7 @@ func TestAssignmentStatementScopes(t *testing.T) { tests := []compilerTestCase{ { input: ` - let num = 0; + num := 0; fn() { num = 55; } `, expectedConstants: []interface{}{ @@ -746,13 +740,14 @@ func TestAssignmentStatementScopes(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), code.Make(code.OpClosure, 2, 0), code.Make(code.OpPop), }, }, { input: ` - fn() { let num = 0; num = 55; } + fn() { num := 0; num = 55; } `, expectedConstants: []interface{}{ 0, @@ -760,6 +755,7 @@ func TestAssignmentStatementScopes(t *testing.T) { []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetLocal, 0), + code.Make(code.OpPop), code.Make(code.OpConstant, 1), code.Make(code.OpAssignLocal, 0), code.Make(code.OpNull), @@ -780,7 +776,7 @@ func TestLetStatementScopes(t *testing.T) { tests := []compilerTestCase{ { input: ` - let num = 55; + num := 55; fn() { return num } `, expectedConstants: []interface{}{ @@ -793,6 +789,7 @@ func TestLetStatementScopes(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), code.Make(code.OpClosure, 1, 0), code.Make(code.OpPop), }, @@ -800,7 +797,7 @@ func TestLetStatementScopes(t *testing.T) { { input: ` fn() { - let num = 55; + num := 55; return num } `, @@ -809,6 +806,7 @@ func TestLetStatementScopes(t *testing.T) { []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetLocal, 0), + code.Make(code.OpPop), code.Make(code.OpGetLocal, 0), code.Make(code.OpReturn), }, @@ -821,8 +819,8 @@ func TestLetStatementScopes(t *testing.T) { { input: ` fn() { - let a = 55; - let b = 77; + a := 55; + b := 77; return a + b } `, @@ -832,8 +830,10 @@ func TestLetStatementScopes(t *testing.T) { []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetLocal, 0), + code.Make(code.OpPop), code.Make(code.OpConstant, 1), code.Make(code.OpSetLocal, 1), + code.Make(code.OpPop), code.Make(code.OpGetLocal, 0), code.Make(code.OpGetLocal, 1), code.Make(code.OpAdd), @@ -847,8 +847,8 @@ func TestLetStatementScopes(t *testing.T) { }, { input: ` - let a = 0; - let a = a + 1; + a := 0; + a := a + 1; `, expectedConstants: []interface{}{ 0, @@ -857,10 +857,12 @@ func TestLetStatementScopes(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpConstant, 0), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), code.Make(code.OpGetGlobal, 0), code.Make(code.OpConstant, 1), code.Make(code.OpAdd), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), }, }, } @@ -912,7 +914,7 @@ func TestRecursiveFunctions(t *testing.T) { tests := []compilerTestCase{ { input: ` - let countDown = fn(x) { return countDown(x - 1); }; + countDown := fn(x) { return countDown(x - 1); }; countDown(1); `, expectedConstants: []interface{}{ @@ -930,6 +932,7 @@ func TestRecursiveFunctions(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpClosure, 1, 0), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), code.Make(code.OpGetGlobal, 0), code.Make(code.OpConstant, 2), code.Make(code.OpCall, 1), @@ -938,8 +941,8 @@ func TestRecursiveFunctions(t *testing.T) { }, { input: ` - let wrapper = fn() { - let countDown = fn(x) { return countDown(x - 1); }; + wrapper := fn() { + countDown := fn(x) { return countDown(x - 1); }; return countDown(1); }; wrapper(); @@ -958,6 +961,7 @@ func TestRecursiveFunctions(t *testing.T) { []code.Instructions{ code.Make(code.OpClosure, 1, 0), code.Make(code.OpSetLocal, 0), + code.Make(code.OpPop), code.Make(code.OpGetLocal, 0), code.Make(code.OpConstant, 2), code.Make(code.OpCall, 1), @@ -967,6 +971,7 @@ func TestRecursiveFunctions(t *testing.T) { expectedInstructions: []code.Instructions{ code.Make(code.OpClosure, 3, 0), code.Make(code.OpSetGlobal, 0), + code.Make(code.OpPop), code.Make(code.OpGetGlobal, 0), code.Make(code.OpCall, 0), code.Make(code.OpPop), diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index abf9725..9e7aa37 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -256,15 +256,22 @@ func nativeBoolToBooleanObject(input bool) object.Object { func evalPrefixExpression(operator string, right object.Object) object.Object { switch operator { case "!": - return evalBangOperatorExpression(right) - case "-": - return evalMinusPrefixOperatorExpression(right) + if right.Type() == object.BOOLEAN_OBJ { + return evalBooleanPrefixOperatorExpression(operator, right) + } + return evalIntegerPrefixOperatorExpression(operator, right) + case "~", "-": + return evalIntegerPrefixOperatorExpression(operator, right) default: return newError("unknown operator: %s%s", operator, right.Type()) } } -func evalBangOperatorExpression(right object.Object) object.Object { +func evalBooleanPrefixOperatorExpression(operator string, right object.Object) object.Object { + if right.Type() != object.BOOLEAN_OBJ { + return newError("unknown operator: %s%s", operator, right.Type()) + } + switch right { case TRUE: return FALSE @@ -277,17 +284,28 @@ func evalBangOperatorExpression(right object.Object) object.Object { } } -func evalMinusPrefixOperatorExpression(right object.Object) object.Object { +func evalIntegerPrefixOperatorExpression(operator string, right object.Object) object.Object { if right.Type() != object.INTEGER_OBJ { return newError("unknown operator: -%s", right.Type()) } value := right.(*object.Integer).Value - return &object.Integer{Value: -value} + switch operator { + case "!": + return FALSE + case "~": + return &object.Integer{Value: ^value} + case "-": + return &object.Integer{Value: -value} + default: + return newError("unknown operator: %s", operator) + } } func evalInfixExpression(operator string, left, right object.Object) object.Object { switch { + case left.Type() == object.BOOLEAN_OBJ && right.Type() == object.BOOLEAN_OBJ: + return evalBooleanInfixExpression(operator, left, right) case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: return evalIntegerInfixExpression(operator, left, right) case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: @@ -303,6 +321,24 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje } } +func evalBooleanInfixExpression(operator string, left, right object.Object) object.Object { + leftVal := left.(*object.Boolean).Value + rightVal := right.(*object.Boolean).Value + + switch operator { + case "==": + return nativeBoolToBooleanObject(left == right) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + case "&&": + return &object.Boolean{Value: leftVal && rightVal} + case "||": + return &object.Boolean{Value: leftVal || rightVal} + default: + return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } +} + func evalStringInfixExpression(operator string, left object.Object, right object.Object) object.Object { leftVal := left.(*object.String).Value rightVal := right.(*object.String).Value @@ -338,6 +374,12 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje return &object.Integer{Value: leftVal / rightVal} case "%": return &object.Integer{Value: leftVal % rightVal} + case "|": + return &object.Integer{Value: leftVal | rightVal} + case "^": + return &object.Integer{Value: leftVal ^ rightVal} + case "&": + return &object.Integer{Value: leftVal & rightVal} case "<": return nativeBoolToBooleanObject(leftVal < rightVal) case "<=": diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index a5fad86..c47767a 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -13,7 +13,7 @@ import ( func TestEvalIntegerExpression(t *testing.T) { tests := []struct { input string - expected int64 + expected interface{} }{ {"5", 5}, {"10", 10}, @@ -30,12 +30,21 @@ func TestEvalIntegerExpression(t *testing.T) { {"3 * 3 * 3 + 10", 37}, {"3 * (3 * 3) + 10", 37}, {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + {"!1", false}, + {"~1", -2}, {"5 % 2", 1}, + {"1 | 2", 3}, + {"2 ^ 4", 6}, + {"3 & 6", 2}, } for _, tt := range tests { evaluated := testEval(tt.input) - testIntegerObject(t, evaluated, tt.expected) + if expected, ok := tt.expected.(int64); ok { + testIntegerObject(t, evaluated, expected) + } else if expected, ok := tt.expected.(bool); ok { + testBooleanObject(t, evaluated, expected) + } } } @@ -46,6 +55,16 @@ func TestEvalBooleanExpression(t *testing.T) { }{ {"true", true}, {"false", false}, + {"!true", false}, + {"!false", true}, + {"true && true", true}, + {"false && true", false}, + {"true && false", false}, + {"false && false", false}, + {"true || true", true}, + {"false || true", true}, + {"true || false", true}, + {"false || false", false}, {"1 < 2", true}, {"1 > 2", false}, {"1 < 1", false}, @@ -78,25 +97,6 @@ func TestEvalBooleanExpression(t *testing.T) { } } -func TestBangOperator(t *testing.T) { - tests := []struct { - input string - expected bool - }{ - {"!true", false}, - {"!false", true}, - {"!5", false}, - {"!!true", true}, - {"!!false", false}, - {"!!5", true}, - } - - for _, tt := range tests { - evaluated := testEval(tt.input) - testBooleanObject(t, evaluated, tt.expected) - } -} - func TestIfElseExpression(t *testing.T) { tests := []struct { input string diff --git a/examples/fizzbuzz.monkey b/examples/fizzbuzz.monkey new file mode 100644 index 0000000..1218a91 --- /dev/null +++ b/examples/fizzbuzz.monkey @@ -0,0 +1,19 @@ +#!./monkey-lang + +test := fn(n) { + if (n % 15 == 0) { + return "FizzBuzz" + } else if (n % 5 == 0) { + return "Buzz" + } else if (n % 3 == 0) { + return "Fizz" + } else { + return str(n) + } +} + +n := 1 +while (n <= 100) { + print(test(n)) + n = n + 1 +} \ No newline at end of file diff --git a/lexer/lexer.go b/lexer/lexer.go index 6ee2eca..80cb710 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -74,7 +74,7 @@ func (l *Lexer) NextToken() token.Token { Literal: literal, } } else { - tok = newToken(token.BANG, l.ch) + tok = newToken(token.NOT, l.ch) } case '/': @@ -83,12 +83,34 @@ func (l *Lexer) NextToken() token.Token { tok.Type = token.COMMENT tok.Literal = l.readLine() } else { - tok = newToken(token.SLASH, l.ch) + tok = newToken(token.DIVIDE, l.ch) } case '*': - tok = newToken(token.ASTERISK, l.ch) + tok = newToken(token.MULTIPLY, l.ch) case '%': - tok = newToken(token.PERCENT, l.ch) + tok = newToken(token.MODULO, l.ch) + case '&': + if l.peekChar() == '&' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.AND, Literal: literal} + } else { + tok = newToken(token.BITWISE_AND, l.ch) + } + case '|': + if l.peekChar() == '|' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.OR, Literal: literal} + } else { + tok = newToken(token.BITWISE_OR, l.ch) + } + case '^': + tok = newToken(token.BITWISE_XOR, l.ch) + case '~': + tok = newToken(token.BITWISE_NOT, l.ch) case '<': if l.peekChar() == '=' { l.readChar() diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index 2283bc9..1be85df 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -33,6 +33,8 @@ func TestNextToken(t *testing.T) { [1, 2]; {"foo": "bar"} d.foo + &|^~ + !&&|| ` tests := []struct { @@ -74,10 +76,10 @@ func TestNextToken(t *testing.T) { {token.RPAREN, ")"}, {token.SEMICOLON, ";"}, - {token.BANG, "!"}, + {token.NOT, "!"}, {token.MINUS, "-"}, - {token.SLASH, "/"}, - {token.ASTERISK, "*"}, + {token.DIVIDE, "/"}, + {token.MULTIPLY, "*"}, {token.INT, "5"}, {token.SEMICOLON, ";"}, {token.INT, "5"}, @@ -137,6 +139,13 @@ func TestNextToken(t *testing.T) { {token.IDENT, "d"}, {token.DOT, "."}, {token.IDENT, "foo"}, + {token.BitwiseAND, "&"}, + {token.BitwiseOR, "|"}, + {token.BitwiseXOR, "^"}, + {token.BitwiseNOT, "~"}, + {token.NOT, "!"}, + {token.AND, "&&"}, + {token.OR, "||"}, {token.EOF, ""}, } diff --git a/parser/parser.go b/parser/parser.go index d09a804..52f6d01 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -11,9 +11,15 @@ import ( const ( _ int = iota LOWEST + OR + AND + NOT ASSIGN // := or = EQUALS // == LESSGREATER // > or < + BITWISE_OR // | + BITWISE_XOR // ^ + BITWISE_AND // & SUM // + or - PRODUCT // * or / or % PREFIX // -X or !X @@ -22,22 +28,28 @@ const ( ) var precedences = map[token.TokenType]int{ - token.BIND: ASSIGN, - token.ASSIGN: ASSIGN, - token.EQ: EQUALS, - token.NOT_EQ: EQUALS, - token.LT: LESSGREATER, - token.GT: LESSGREATER, - token.LTE: LESSGREATER, - token.GTE: LESSGREATER, - token.PLUS: SUM, - token.MINUS: SUM, - token.SLASH: PRODUCT, - token.ASTERISK: PRODUCT, - token.PERCENT: PRODUCT, - token.LPAREN: CALL, - token.LBRACKET: INDEX, - token.DOT: INDEX, + token.OR: OR, + token.AND: AND, + token.NOT: NOT, + token.BIND: ASSIGN, + token.ASSIGN: ASSIGN, + token.EQ: EQUALS, + token.NOT_EQ: EQUALS, + token.LT: LESSGREATER, + token.GT: LESSGREATER, + token.LTE: LESSGREATER, + token.GTE: LESSGREATER, + token.BITWISE_OR: BITWISE_OR, + token.BITWISE_XOR: BITWISE_XOR, + token.BITWISE_AND: BITWISE_AND, + token.PLUS: SUM, + token.MINUS: SUM, + token.DIVIDE: PRODUCT, + token.MULTIPLY: PRODUCT, + token.MODULO: PRODUCT, + token.LPAREN: CALL, + token.LBRACKET: INDEX, + token.DOT: INDEX, } type ( @@ -65,7 +77,6 @@ func New(l *lexer.Lexer) *Parser { p.prefixParseFns = make(map[token.TokenType]prefixParseFn) p.registerPrefix(token.IDENT, p.parseIdentifier) p.registerPrefix(token.INT, p.parseIntegerLiteral) - p.registerPrefix(token.BANG, p.parsePrefixExpression) p.registerPrefix(token.MINUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) p.registerPrefix(token.FALSE, p.parseBoolean) @@ -81,9 +92,9 @@ func New(l *lexer.Lexer) *Parser { p.infixParseFns = make(map[token.TokenType]infixParseFn) p.registerInfix(token.PLUS, p.parseInfixExpression) p.registerInfix(token.MINUS, p.parseInfixExpression) - p.registerInfix(token.SLASH, p.parseInfixExpression) - p.registerInfix(token.ASTERISK, p.parseInfixExpression) - p.registerInfix(token.PERCENT, p.parseInfixExpression) + p.registerInfix(token.DIVIDE, p.parseInfixExpression) + p.registerInfix(token.MULTIPLY, p.parseInfixExpression) + p.registerInfix(token.MODULO, p.parseInfixExpression) p.registerInfix(token.EQ, p.parseInfixExpression) p.registerInfix(token.NOT_EQ, p.parseInfixExpression) p.registerInfix(token.LT, p.parseInfixExpression) @@ -96,6 +107,15 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.DOT, p.parseSelectorExpression) + p.registerPrefix(token.BITWISE_NOT, p.parsePrefixExpression) + p.registerInfix(token.BITWISE_OR, p.parseInfixExpression) + p.registerInfix(token.BITWISE_XOR, p.parseInfixExpression) + p.registerInfix(token.BITWISE_AND, p.parseInfixExpression) + + p.registerPrefix(token.NOT, p.parsePrefixExpression) + p.registerInfix(token.OR, p.parseInfixExpression) + p.registerInfix(token.AND, p.parseInfixExpression) + // Read two tokens, so curToken and peekToken are both set p.nextToken() p.nextToken() diff --git a/parser/parser_test.go b/parser/parser_test.go index da05cb5..9e5f60c 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -138,6 +138,7 @@ func TestParsingPrefixExpressions(t *testing.T) { {"-foobar;", "-", "foobar"}, {"!true;", "!", true}, {"!false;", "!", false}, + {"~5;", "~", 5}, } for _, tt := range prefixTests { diff --git a/token/token.go b/token/token.go index f5f269f..4b36d9f 100644 --- a/token/token.go +++ b/token/token.go @@ -24,10 +24,31 @@ const ( ASSIGN = "=" PLUS = "+" MINUS = "-" - BANG = "!" - ASTERISK = "*" - SLASH = "/" - PERCENT = "%" + MULTIPLY = "*" + DIVIDE = "/" + MODULO = "%" + + // Bitwise / Logical operators + + // BITWISE_AND AND + BITWISE_AND = "&" + // BITWISE_OR OR + BITWISE_OR = "|" + // BITWISE_XOR XOR + BITWISE_XOR = "^" + // BITWISE_NOT NOT + BITWISE_NOT = "~" + + // + // Comparision operators + // + + // NOT the not operator + NOT = "!" + // AND the and operator + AND = "&&" + // OR the or operator + OR = "||" LT = "<" LTE = "<=" diff --git a/vm/vm.go b/vm/vm.go index 5e01216..8828bcb 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -112,7 +112,8 @@ func (vm *VM) Run() error { return err } - case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod: + case code.OpAdd, code.OpSub, code.OpMul, code.OpDiv, code.OpMod, code.OpOr, + code.OpAnd, code.OpBitwiseOR, code.OpBitwiseXOR, code.OpBitwiseAND: err := vm.executeBinaryOperation(op) if err != nil { return err @@ -139,8 +140,14 @@ func (vm *VM) Run() error { return err } - case code.OpBang: - err := vm.executeBangOperator() + case code.OpNot: + err := vm.executeNotOperator() + if err != nil { + return err + } + + case code.OpBitwiseNOT: + err := vm.executeBitwiseNotOperator() if err != nil { return err } @@ -516,18 +523,38 @@ func (vm *VM) executeBinaryOperation(op code.Opcode) error { left := vm.pop() leftType := left.Type() - rightRight := right.Type() + rightType := right.Type() switch { - case leftType == object.INTEGER_OBJ && rightRight == object.INTEGER_OBJ: + case leftType == object.BOOLEAN_OBJ && rightType == object.BOOLEAN_OBJ: + return vm.executeBinaryBooleanOperation(op, left, right) + case leftType == object.INTEGER_OBJ && rightType == object.INTEGER_OBJ: return vm.executeBinaryIntegerOperation(op, left, right) - case leftType == object.STRING_OBJ && rightRight == object.STRING_OBJ: + case leftType == object.STRING_OBJ && rightType == object.STRING_OBJ: return vm.executeBinaryStringOperation(op, left, right) default: - return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightRight) + return fmt.Errorf("unsupported types for binary operation: %s %s", leftType, rightType) } } +func (vm *VM) executeBinaryBooleanOperation(op code.Opcode, left, right object.Object) error { + leftValue := left.(*object.Boolean).Value + rightValue := right.(*object.Boolean).Value + + var result bool + + switch op { + case code.OpOr: + result = leftValue || rightValue + case code.OpAnd: + result = leftValue && rightValue + default: + return fmt.Errorf("unknown boolean operator: %d", op) + } + + return vm.push(&object.Boolean{Value: result}) +} + func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.Object) error { leftValue := left.(*object.Integer).Value rightValue := right.(*object.Integer).Value @@ -545,6 +572,12 @@ func (vm *VM) executeBinaryIntegerOperation(op code.Opcode, left, right object.O result = leftValue / rightValue case code.OpMod: result = leftValue % rightValue + case code.OpBitwiseOR: + result = leftValue | rightValue + case code.OpBitwiseXOR: + result = leftValue ^ rightValue + case code.OpBitwiseAND: + result = leftValue & rightValue default: return fmt.Errorf("unknown integer operator: %d", op) @@ -604,7 +637,15 @@ func (vm *VM) executeIntegerComparison(op code.Opcode, left, right object.Object } } -func (vm *VM) executeBangOperator() error { +func (vm *VM) executeBitwiseNotOperator() error { + operand := vm.pop() + if i, ok := operand.(*object.Integer); ok { + return vm.push(&object.Integer{Value: ^i.Value}) + } + return fmt.Errorf("expected int got=%T", operand) +} + +func (vm *VM) executeNotOperator() error { operand := vm.pop() switch operand { @@ -622,12 +663,11 @@ func (vm *VM) executeBangOperator() error { func (vm *VM) executeMinusOperator() error { operand := vm.pop() - if operand.Type() != object.INTEGER_OBJ { - return fmt.Errorf("unsupported type for negation: %s", operand.Type()) + if i, ok := operand.(*object.Integer); ok { + return vm.push(&object.Integer{Value: -i.Value}) } - value := operand.(*object.Integer).Value - return vm.push(&object.Integer{Value: -value}) + return fmt.Errorf("expected int got=%T", operand) } func (vm *VM) executeCall(numArgs int) error { diff --git a/vm/vm_test.go b/vm/vm_test.go index b992267..831972a 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -224,7 +224,12 @@ func TestIntegerArithmetic(t *testing.T) { {"-10", -10}, {"-50 + 100 + -50", 0}, {"(5 + 10 * 2 + 15 / 3) * 2 + -10", 50}, + {"!1", false}, + {"~1", -2}, {"5 % 2", 1}, + {"1 | 2", 3}, + {"2 ^ 4", 6}, + {"3 & 6", 2}, } runVmTests(t, tests) @@ -237,6 +242,37 @@ func TestBooleanExpressions(t *testing.T) { {"null", nil}, {"!true", false}, {"!false", true}, + {"true && true", true}, + {"false && true", false}, + {"true && false", false}, + {"false && false", false}, + {"true || true", true}, + {"false || true", true}, + {"true || false", true}, + {"false || false", false}, + {"1 < 2", true}, + {"1 > 2", false}, + {"1 < 1", false}, + {"1 > 1", false}, + {"1 == 1", true}, + {"1 != 1", false}, + {"1 == 2", false}, + {"1 != 2", true}, + {"true == true", true}, + {"false == false", true}, + {"true == false", false}, + {"true != false", true}, + {"false != true", true}, + {"(1 < 2) == true", true}, + {"(1 < 2) == false", false}, + {"(1 > 2) == true", false}, + {"(1 > 2) == false", true}, + {"(1 <= 2) == true", true}, + {"(1 <= 2) == false", false}, + {"(1 >= 2) == true", false}, + {"(1 >= 2) == false", true}, + {"!true", false}, + {"!false", true}, {"!5", false}, {"!!true", true}, {"!!false", false},