From 6282075e660431c2846e3efcc48c1f07a0088b17 Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Thu, 21 Mar 2024 17:43:03 -0400 Subject: [PATCH] bind expression (:=) instead of let --- ast/ast.go | 77 ++++----- ast/ast_test.go | 26 +-- compiler/compiler.go | 41 +++-- compiler/compiler_test.go | 214 +++++------------------- evaluator/evaluator.go | 29 ++-- evaluator/evaluator_test.go | 84 +++++----- examples/bf.monkey | 42 ++--- examples/demo.monkey | 22 +-- examples/fact.monkey | 2 +- examples/factt.monkey | 2 +- examples/fib.monkey | 2 +- examples/fibi.monkey | 10 +- examples/fibt.monkey | 2 +- examples/input.monkey | 2 +- lexer/lexer.go | 9 +- lexer/lexer_test.go | 35 ++-- parser/parser.go | 57 +++---- parser/parser_test.go | 84 ++-------- testdata/arrays.monkey | 2 +- testdata/assign.monkey | 2 +- testdata/binding.monkey | 3 + testdata/builtins.monkey | 4 +- testdata/closures.monkey | 2 +- testdata/functions.monkey | 2 +- testdata/hashes.monkey | 2 +- testdata/if.monkey | 2 +- testdata/let.monkey | 3 - testdata/popbug1.monkey | 1 - testdata/popbug2.monkey | 1 - testdata/popbug3.monkey | 1 - {examples => testdata}/selectors.monkey | 2 +- testdata/strings.monkey | 2 +- token/token.go | 3 +- vim/monkey.vim | 5 +- vm/vm.go | 17 +- vm/vm_test.go | 214 ++++++++++++------------ 36 files changed, 425 insertions(+), 583 deletions(-) create mode 100644 testdata/binding.monkey delete mode 100644 testdata/let.monkey delete mode 100644 testdata/popbug1.monkey delete mode 100644 testdata/popbug2.monkey delete mode 100644 testdata/popbug3.monkey rename {examples => testdata}/selectors.monkey (78%) diff --git a/ast/ast.go b/ast/ast.go index 460b595..0035e27 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -47,31 +47,6 @@ func (p *Program) String() string { return out.String() } -// Statements -type LetStatement struct { - Token token.Token // the token.LET token - Name *Identifier - Value Expression -} - -func (ls *LetStatement) statementNode() {} -func (ls *LetStatement) TokenLiteral() string { return ls.Token.Literal } -func (ls *LetStatement) String() string { - var out bytes.Buffer - - out.WriteString(ls.TokenLiteral() + " ") - out.WriteString(ls.Name.String()) - out.WriteString(" = ") - - if ls.Value != nil { - out.WriteString(ls.Value.String()) - } - - out.WriteString(";") - - return out.String() -} - type ReturnStatement struct { Token token.Token // the 'return' token ReturnValue Expression @@ -284,23 +259,23 @@ type StringLiteral struct { Value string } -func (sl StringLiteral) TokenLiteral() string { +func (sl *StringLiteral) TokenLiteral() string { return sl.Token.Literal } -func (sl StringLiteral) String() string { +func (sl *StringLiteral) String() string { return sl.Token.Literal } -func (sl StringLiteral) expressionNode() {} +func (sl *StringLiteral) expressionNode() {} type ArrayLiteral struct { Token token.Token // the '[' token Elements []Expression } -func (al ArrayLiteral) TokenLiteral() string { +func (al *ArrayLiteral) TokenLiteral() string { return al.Token.Literal } -func (al ArrayLiteral) String() string { +func (al *ArrayLiteral) String() string { var out bytes.Buffer elements := []string{} @@ -314,7 +289,7 @@ func (al ArrayLiteral) String() string { return out.String() } -func (al ArrayLiteral) expressionNode() {} +func (al *ArrayLiteral) expressionNode() {} type IndexExpression struct { Token token.Token // The [ token @@ -322,11 +297,11 @@ type IndexExpression struct { Index Expression } -func (ie IndexExpression) TokenLiteral() string { +func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal } -func (ie IndexExpression) String() string { +func (ie *IndexExpression) String() string { var out bytes.Buffer out.WriteString("(") @@ -338,18 +313,18 @@ func (ie IndexExpression) String() string { return out.String() } -func (ie IndexExpression) expressionNode() {} +func (ie *IndexExpression) expressionNode() {} type HashLiteral struct { Token token.Token // the '{' token Pairs map[Expression]Expression } -func (hl HashLiteral) TokenLiteral() string { +func (hl *HashLiteral) TokenLiteral() string { return hl.Token.Literal } -func (hl HashLiteral) String() string { +func (hl *HashLiteral) String() string { var out bytes.Buffer pairs := []string{} @@ -364,7 +339,7 @@ func (hl HashLiteral) String() string { return out.String() } -func (hl HashLiteral) expressionNode() {} +func (hl *HashLiteral) expressionNode() {} type WhileExpression struct { Token token.Token // The 'while' token @@ -389,6 +364,28 @@ func (we *WhileExpression) String() string { return out.String() } +// BindExpression represents an assignment expression of the form: +// x := 1 +type BindExpression struct { + Token token.Token // the := token + Left Expression + Value Expression +} + +func (be *BindExpression) TokenLiteral() string { + return be.Token.Literal +} +func (be *BindExpression) String() string { + var out bytes.Buffer + + out.WriteString(be.Left.String()) + out.WriteString(be.TokenLiteral()) + out.WriteString(be.Value.String()) + + return out.String() +} +func (be *BindExpression) expressionNode() {} + // AssignmentExpression represents an assignment expression of the form: // x = 1 or xs[1] = 2 type AssignmentExpression struct { @@ -397,10 +394,10 @@ type AssignmentExpression struct { Value Expression } -func (as AssignmentExpression) TokenLiteral() string { +func (as *AssignmentExpression) TokenLiteral() string { return as.Token.Literal } -func (as AssignmentExpression) String() string { +func (as *AssignmentExpression) String() string { var out bytes.Buffer out.WriteString(as.Left.String()) @@ -409,7 +406,7 @@ func (as AssignmentExpression) String() string { return out.String() } -func (as AssignmentExpression) expressionNode() {} +func (as *AssignmentExpression) expressionNode() {} // Comment a comment type Comment struct { diff --git a/ast/ast_test.go b/ast/ast_test.go index e1bea83..f0bc121 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -1,6 +1,7 @@ package ast import ( + "github.com/stretchr/testify/assert" "monkey/token" "testing" ) @@ -8,21 +9,22 @@ import ( func TestString(t *testing.T) { program := &Program{ Statements: []Statement{ - &LetStatement{ - Token: token.Token{Type: token.LET, Literal: "let"}, - Name: &Identifier{ - Token: token.Token{Type: token.IDENT, Literal: "myVar"}, - Value: "myVar", - }, - Value: &Identifier{ - Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, - Value: "anotherVar", + &ExpressionStatement{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Expression: &BindExpression{ + Token: token.Token{Type: token.BIND, Literal: ":="}, + Left: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "myVar"}, + Value: "myVar", + }, + Value: &Identifier{ + Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, + Value: "anotherVar", + }, }, }, }, } - if program.String() != "let myVar = anotherVar;" { - t.Errorf("program.String wrong. got=%q", program.String()) - } + assert.Equal(t, "myVar:=anotherVar", program.String()) } diff --git a/compiler/compiler.go b/compiler/compiler.go index aac2cee..d4f012d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -292,23 +292,36 @@ func (c *Compiler) Compile(node ast.Node) error { return fmt.Errorf("expected identifier or index expression got=%s", node.Left) } - case *ast.LetStatement: - symbol, ok := c.symbolTable.Resolve(node.Name.Value) - if !ok { - symbol = c.symbolTable.Define(node.Name.Value) - } + case *ast.BindExpression: + var symbol Symbol - c.l++ - err := c.Compile(node.Value) - c.l-- - if err != nil { - return err - } + if ident, ok := node.Left.(*ast.Identifier); ok { + symbol, ok = c.symbolTable.Resolve(ident.Value) + if !ok { + symbol = c.symbolTable.Define(ident.Value) + } else { + // Local shadowing of previously defined "free" variable in a + // function now being rebound to a locally scoped variable. + if symbol.Scope == FreeScope { + symbol = c.symbolTable.Define(ident.Value) + } + } + + c.l++ + err := c.Compile(node.Value) + c.l-- + if err != nil { + return err + } + + if symbol.Scope == GlobalScope { + c.emit(code.OpSetGlobal, symbol.Index) + } else { + c.emit(code.OpSetLocal, symbol.Index) + } - if symbol.Scope == GlobalScope { - c.emit(code.OpSetGlobal, symbol.Index) } else { - c.emit(code.OpSetLocal, symbol.Index) + return fmt.Errorf("expected identifier got=%s", node.Left) } case *ast.Identifier: diff --git a/compiler/compiler_test.go b/compiler/compiler_test.go index 4d3702e..82ffd41 100644 --- a/compiler/compiler_test.go +++ b/compiler/compiler_test.go @@ -11,6 +11,8 @@ import ( "testing" ) +type Instructions string + type compilerTestCase struct { input string expectedConstants []interface{} @@ -210,146 +212,61 @@ func TestBooleanExpressions(t *testing.T) { } func TestConditionals(t *testing.T) { - tests := []compilerTestCase{ + tests := []compilerTestCase2{ { input: ` if (true) { 10 }; 3333; `, - expectedConstants: []interface{}{10, 3333}, - expectedInstructions: []code.Instructions{ - // 0000 - code.Make(code.OpTrue), - // 0001 - code.Make(code.OpJumpNotTruthy, 10), - // 0004 - code.Make(code.OpConstant, 0), - // 0007 - code.Make(code.OpJump, 11), - // 0010 - code.Make(code.OpNull), - // 0011 - code.Make(code.OpPop), - // 0012 - code.Make(code.OpConstant, 1), - // 0015 - code.Make(code.OpPop), - }, + 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", }, { input: ` if (true) { 10 } else { 20 }; 3333; `, - expectedConstants: []interface{}{10, 20, 3333}, - expectedInstructions: []code.Instructions{ - // 0000 - code.Make(code.OpTrue), - // 0001 - code.Make(code.OpJumpNotTruthy, 10), - // 0004 - code.Make(code.OpConstant, 0), - // 0008 - code.Make(code.OpJump, 13), - // 0011 - code.Make(code.OpConstant, 1), - // 0013 - code.Make(code.OpPop), - // 0014 - code.Make(code.OpConstant, 2), - // 0018 - code.Make(code.OpPop), - }, + 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", }, { input: ` let x = 0; if (true) { x = 1; }; if (false) { x = 2; } `, - expectedConstants: []interface{}{0, 1, 2}, - expectedInstructions: []code.Instructions{ - // 0000 - code.Make(code.OpConstant, 0), - // 0003 - code.Make(code.OpSetGlobal, 0), - // 0006 - code.Make(code.OpTrue), - // 0007 - code.Make(code.OpJumpNotTruthy, 19), - // 0010 - code.Make(code.OpConstant, 1), - // 0013 - code.Make(code.OpAssignGlobal, 0), - // 0018 - code.Make(code.OpJump, 20), - // 0019 - code.Make(code.OpNull), - // 0020 - code.Make(code.OpPop), - // 0021 - code.Make(code.OpFalse), - // 0022 - code.Make(code.OpJumpNotTruthy, 34), - // 0024 - code.Make(code.OpConstant, 2), - // 0028 - code.Make(code.OpAssignGlobal, 0), - // 0032 - code.Make(code.OpJump, 35), - // 0035 - code.Make(code.OpNull), - // 0036 - code.Make(code.OpPop), - }, + 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", }, } - runCompilerTests(t, tests) + runCompilerTests2(t, tests) } -func TestGlobalLetStatements(t *testing.T) { - tests := []compilerTestCase{ +func TestGlobalBindExpressions(t *testing.T) { + tests := []compilerTestCase2{ { input: ` - let one = 1; - let two = 2; + one := 1; + two := 2; `, - expectedConstants: []interface{}{1, 2}, - expectedInstructions: []code.Instructions{ - code.Make(code.OpConstant, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpConstant, 1), - code.Make(code.OpSetGlobal, 1), - }, + instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadConstant 1\n0010 BindGlobal 1\n0013 Pop\n", }, { input: ` - let one = 1; + one := 1; one; `, - expectedConstants: []interface{}{1}, - expectedInstructions: []code.Instructions{ - code.Make(code.OpConstant, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpPop), - }, + constants: []interface{}{1}, + instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadGlobal 0\n0010 Pop\n", }, { input: ` - let one = 1; - let two = one; + one := 1; + two := one; two; `, - expectedConstants: []interface{}{1}, - expectedInstructions: []code.Instructions{ - code.Make(code.OpConstant, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpSetGlobal, 1), - code.Make(code.OpGetGlobal, 1), - code.Make(code.OpPop), - }, + 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", }, } - runCompilerTests(t, tests) + runCompilerTests2(t, tests) } func TestStringExpressions(t *testing.T) { @@ -735,95 +652,53 @@ func TestCompilerScopes(t *testing.T) { } func TestFunctionCalls(t *testing.T) { - tests := []compilerTestCase{ + tests := []compilerTestCase2{ { input: `fn() { return 24 }();`, - expectedConstants: []interface{}{ + constants: []interface{}{ 24, - []code.Instructions{ - code.Make(code.OpConstant, 0), // The literal "24" - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 1, 0), // The compiled function - code.Make(code.OpCall, 0), - code.Make(code.OpPop), + Instructions("0000 LoadConstant 0\n0003 Return\n"), }, + instructions: "0000 MakeClosure 1 0\n0004 Call 0\n0006 Pop\n", }, { input: ` - let noArg = fn() { return 24 }; + noArg := fn() { return 24 }; noArg(); `, - expectedConstants: []interface{}{ + constants: []interface{}{ 24, - []code.Instructions{ - code.Make(code.OpConstant, 0), // The literal "24" - code.Make(code.OpReturn), - }, - }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 1, 0), // The compiled function - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpCall, 0), - code.Make(code.OpPop), + Instructions("0000 SetSelf 0\n0002 LoadConstant 0\n0005 Return\n"), }, + instructions: "0000 LoadGlobal 0\n0003 MakeClosure 1 1\n0007 BindGlobal 0\n0010 Pop\n0011 LoadGlobal 0\n0014 Call 0\n0016 Pop\n", }, { input: ` - let oneArg = fn(a) { return a }; + oneArg := fn(a) { return a }; oneArg(24); `, - expectedConstants: []interface{}{ - []code.Instructions{ - code.Make(code.OpGetLocal, 0), - code.Make(code.OpReturn), - }, + constants: []interface{}{ + Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Return\n"), 24, }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 0, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpConstant, 1), - code.Make(code.OpCall, 1), - code.Make(code.OpPop), - }, + 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", }, { input: ` - let manyArg = fn(a, b, c) { a; b; return c }; + manyArg := fn(a, b, c) { a; b; return c }; manyArg(24, 25, 26); `, - expectedConstants: []interface{}{ - []code.Instructions{ - code.Make(code.OpGetLocal, 0), - code.Make(code.OpPop), - code.Make(code.OpGetLocal, 1), - code.Make(code.OpPop), - code.Make(code.OpGetLocal, 2), - code.Make(code.OpReturn), - }, + constants: []interface{}{ + Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Pop\n0005 LoadLocal 1\n0007 Pop\n0008 LoadLocal 2\n0010 Return\n"), 24, 25, 26, }, - expectedInstructions: []code.Instructions{ - code.Make(code.OpClosure, 0, 0), - code.Make(code.OpSetGlobal, 0), - code.Make(code.OpGetGlobal, 0), - code.Make(code.OpConstant, 1), - code.Make(code.OpConstant, 2), - code.Make(code.OpConstant, 3), - code.Make(code.OpCall, 3), - code.Make(code.OpPop), - }, + 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", }, } - runCompilerTests(t, tests) + runCompilerTests2(t, tests) } func TestAssignmentExpressions(t *testing.T) { @@ -1240,10 +1115,11 @@ func testConstants2(t *testing.T, expected []interface{}, actual []object.Object 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 Instructions: + assert.Equal( + string(constant), + actual[i].(*object.CompiledFunction).Instructions.String(), + ) case string: assert.Equal(constant, actual[i].(*object.String).Value) diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 73ed21d..fec0b29 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -45,6 +45,23 @@ func Eval(node ast.Node, env *object.Environment) object.Object { } return &object.ReturnValue{Value: val} + case *ast.BindExpression: + value := Eval(node.Value, env) + if isError(value) { + return value + } + + if ident, ok := node.Left.(*ast.Identifier); ok { + if immutable, ok := value.(object.Immutable); ok { + env.Set(ident.Value, immutable.Clone()) + } else { + env.Set(ident.Value, value) + } + + return NULL + } + return newError("expected identifier on left got=%T", node.Left) + case *ast.AssignmentExpression: left := Eval(node.Left, env) if isError(left) { @@ -94,18 +111,6 @@ func Eval(node ast.Node, env *object.Environment) object.Object { return NULL - case *ast.LetStatement: - val := Eval(node.Value, env) - if isError(val) { - return val - } - - if immutable, ok := val.(object.Immutable); ok { - env.Set(node.Name.Value, immutable.Clone()) - } else { - env.Set(node.Name.Value, val) - } - case *ast.Identifier: return evalIdentifier(node, env) diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index b5d9d90..f633891 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -147,7 +147,7 @@ func TestReturnStatements(t *testing.T) { }, { ` - let f = fn(x) { + f := fn(x) { return x; x + 10; }; @@ -156,8 +156,8 @@ func TestReturnStatements(t *testing.T) { }, { ` - let f = fn(x) { - let result = x + 10; + f := fn(x) { + result := x + 10; return result; return 10; }; @@ -249,7 +249,7 @@ func TestIndexAssignmentStatements(t *testing.T) { input string expected int64 }{ - {"let xs = [1, 2, 3]; xs[1] = 4; xs[1];", 4}, + {"xs := [1, 2, 3]; xs[1] = 4; xs[1];", 4}, } for _, tt := range tests { @@ -263,16 +263,16 @@ func TestAssignmentStatements(t *testing.T) { input string expected interface{} }{ - {"let a = 0; a = 5;", nil}, - {"let a = 0; a = 5; a;", 5}, - {"let a = 0; a = 5 * 5;", nil}, - {"let a = 0; a = 5 * 5; a;", 25}, - {"let a = 0; a = 5; let b = 0; b = a;", nil}, - {"let a = 0; a = 5; let b = 0; b = a; b;", 5}, - {"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5;", nil}, - {"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5; c;", 15}, - {"let a = 5; let b = a; a = 0;", nil}, - {"let a = 5; let b = a; a = 0; b;", 5}, + {"a := 0; a = 5;", nil}, + {"a := 0; a = 5; a;", 5}, + {"a := 0; a = 5 * 5;", nil}, + {"a := 0; a = 5 * 5; a;", 25}, + {"a := 0; a = 5; b := 0; b = a;", nil}, + {"a := 0; a = 5; b := 0; b = a; b;", 5}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5;", nil}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15}, + {"a := 5; b := a; a = 0;", nil}, + {"a := 5; b := a; a = 0; b;", 5}, } for _, tt := range tests { @@ -286,15 +286,15 @@ func TestAssignmentStatements(t *testing.T) { } } -func TestLetStatements(t *testing.T) { +func TestBindExpressions(t *testing.T) { tests := []struct { input string expected int64 }{ - {"let a = 5; a;", 5}, - {"let a = 5 * 5; a;", 25}, - {"let a = 5; let b = a; b;", 5}, - {"let a = 5; let b = a; let c = a + b + 5; c;", 15}, + {"a := 5; a;", 5}, + {"a := 5 * 5; a;", 25}, + {"a := 5; b := a; b;", 5}, + {"a := 5; b := a; c := a + b + 5; c;", 15}, } for _, tt := range tests { @@ -331,11 +331,11 @@ func TestFunctionApplication(t *testing.T) { input string expected int64 }{ - {"let identity = fn(x) { x; }; identity(5);", 5}, - {"let identity = fn(x) { return x; }; identity(5);", 5}, - {"let double = fn(x) { x * 2; }; double(5);", 10}, - {"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, - {"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, + {"identity := fn(x) { x; }; identity(5);", 5}, + {"identity := fn(x) { return x; }; identity(5);", 5}, + {"double := fn(x) { x * 2; }; double(5);", 10}, + {"add := fn(x, y) { x + y; }; add(5, 5);", 10}, + {"add := fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, {"fn(x) { x; }(5)", 5}, } @@ -346,11 +346,11 @@ func TestFunctionApplication(t *testing.T) { func TestClosures(t *testing.T) { input := ` - let newAdder = fn(x) { + newAdder := fn(x) { fn(y) { x + y }; }; - let addTwo = newAdder(2); + addTwo := newAdder(2); addTwo(2);` testIntegerObject(t, testEval(input), 4) @@ -496,7 +496,7 @@ func TestArrayIndexExpressions(t *testing.T) { 3, }, { - "let i = 0; [1][i];", + "i := 0; [1][i];", 1, }, { @@ -504,15 +504,15 @@ func TestArrayIndexExpressions(t *testing.T) { 3, }, { - "let myArray = [1, 2, 3]; myArray[2];", + "myArray := [1, 2, 3]; myArray[2];", 3, }, { - "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", + "myArray := [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", 6, }, { - "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", + "myArray := [1, 2, 3]; i := myArray[0]; myArray[i]", 2, }, { @@ -538,7 +538,7 @@ func TestArrayIndexExpressions(t *testing.T) { } func TestHashLiterals(t *testing.T) { - input := `let two = "two"; + input := `two := "two"; { "one": 10 - 9, two: 1 + 1, @@ -621,7 +621,7 @@ func TestHashIndexExpressions(t *testing.T) { nil, }, { - `let key = "foo"; {"foo": 5}[key]`, + `key := "foo"; {"foo": 5}[key]`, 5, }, { @@ -660,14 +660,14 @@ func TestWhileExpressions(t *testing.T) { expected interface{} }{ {"while (false) { }", nil}, - {"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, - {"let n = 10; while (n > 0) { let n = n - 1 }; n", 0}, - {"let n = 0; while (n < 10) { let n = n + 1 }", nil}, - {"let n = 10; while (n > 0) { let n = n - 1 }", nil}, - {"let n = 0; while (n < 10) { n = n + 1 }; n", 10}, - {"let n = 10; while (n > 0) { n = n - 1 }; n", 0}, - {"let n = 0; while (n < 10) { n = n + 1 }", nil}, - {"let n = 10; while (n > 0) { n = n - 1 }", nil}, + {"n := 0; while (n < 10) { n := n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n := n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n := n + 1 }", nil}, + {"n := 10; while (n > 0) { n := n - 1 }", nil}, + {"n := 0; while (n < 10) { n = n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n = n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n = n + 1 }", nil}, + {"n := 10; while (n > 0) { n = n - 1 }", nil}, } for _, tt := range tests { @@ -778,7 +778,7 @@ func TestStringIndexExpressions(t *testing.T) { "c", }, { - `let i = 0; "abc"[i];`, + `i := 0; "abc"[i];`, "a", }, { @@ -786,7 +786,7 @@ func TestStringIndexExpressions(t *testing.T) { "c", }, { - `let myString = "abc"; myString[0] + myString[1] + myString[2];`, + `myString := "abc"; myString[0] + myString[1] + myString[2];`, "abc", }, { diff --git a/examples/bf.monkey b/examples/bf.monkey index 231e01d..3eb5f30 100644 --- a/examples/bf.monkey +++ b/examples/bf.monkey @@ -1,7 +1,7 @@ #!./monkey-lang -let fill = fn(x, i) { - let xs = [] +fill := fn(x, i) { + xs := [] while (i > 0) { xs = push(xs, x) i = i - 1 @@ -9,17 +9,17 @@ let fill = fn(x, i) { return xs } -let buildJumpMap = fn(program) { - let stack = [] - let map = {} +buildJumpMap := fn(program) { + stack := [] + map := {} - let n = 0 + n := 0 while (n < len(program)) { if (program[n] == "[") { stack = push(stack, n) } if (program[n] == "]") { - let start = pop(stack) + start := pop(stack) map[start] = n map[n] = start } @@ -29,13 +29,13 @@ let buildJumpMap = fn(program) { return map } -let ascii_table = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" +ascii_table := "\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" -let ord = fn(s) { +ord := fn(s) { return ascii_table[s] } -let chr = fn(x) { +chr := fn(x) { if (x < 0) { return "??" } @@ -45,27 +45,27 @@ let chr = fn(x) { return ascii_table[x] } -let read = fn() { - let buf = input() +read := fn() { + buf := input() if (len(buf) > 0) { return ord(buf[0]) } return 0 } -let write = fn(x) { +write := fn(x) { print(chr(x)) } -let VM = fn(program) { - let jumps = buildJumpMap(program) +VM := fn(program) { + jumps := buildJumpMap(program) - let ip = 0 - let dp = 0 - let memory = fill(0, 32) + ip := 0 + dp := 0 + memory := fill(0, 32) while (ip < len(program)) { - let op = program[ip] + op := program[ip] if (op == ">") { dp = dp + 1 @@ -108,9 +108,9 @@ let VM = fn(program) { } // Hello World -let program = "++++++++ [ >++++ [ >++ >+++ >+++ >+ <<<<- ] >+ >+ >- >>+ [<] <- ] >>. >---. +++++++..+++. >>. <-. <. +++.------.--------. >>+. >++." +program := "++++++++ [ >++++ [ >++ >+++ >+++ >+ <<<<- ] >+ >+ >- >>+ [<] <- ] >>. >---. +++++++..+++. >>. <-. <. +++.------.--------. >>+. >++." // 2 + 5 -// let program = "++> +++++ [<+>-] ++++++++ [<++++++>-] < ." +// program := "++> +++++ [<+>-] ++++++++ [<++++++>-] < ." VM(program) \ No newline at end of file diff --git a/examples/demo.monkey b/examples/demo.monkey index 7c1d635..e107beb 100644 --- a/examples/demo.monkey +++ b/examples/demo.monkey @@ -1,21 +1,21 @@ -let name = "Monkey"; -let age = 1; -let inspirations = ["Scheme", "Lisp", "JavaScript", "Clojure"]; -let book = { +name := "Monkey"; +age := 1; +inspirations := ["Scheme", "Lisp", "JavaScript", "Clojure"]; +book := { "title": "Writing A Compiler In Go", "author": "Thorsten Ball", "prequel": "Writing An Interpreter In Go" }; -let printBookName = fn(book) { - let title = book["title"]; - let author = book["author"]; +printBookName := fn(book) { + title := book["title"]; + author := book["author"]; print(author + " - " + title); }; printBookName(book); -let fibonacci = fn(x) { +fibonacci := fn(x) { if (x == 0) { return 0 } else { @@ -27,8 +27,8 @@ let fibonacci = fn(x) { } }; -let map = fn(arr, f) { - let iter = fn(arr, accumulated) { +map := fn(arr, f) { + iter := fn(arr, accumulated) { if (len(arr) == 0) { return accumulated } else { @@ -39,5 +39,5 @@ let map = fn(arr, f) { return iter(arr, []); }; -let numbers = [1, 1 + 1, 4 - 1, 2 * 2, 2 + 3, 12 / 2]; +numbers := [1, 1 + 1, 4 - 1, 2 * 2, 2 + 3, 12 / 2]; map(numbers, fibonacci); \ No newline at end of file diff --git a/examples/fact.monkey b/examples/fact.monkey index 498a135..61b40cf 100644 --- a/examples/fact.monkey +++ b/examples/fact.monkey @@ -1,4 +1,4 @@ -let fact = fn(n) { +fact := fn(n) { if (n == 0) { return 1 } diff --git a/examples/factt.monkey b/examples/factt.monkey index 5939894..afe0bcf 100644 --- a/examples/factt.monkey +++ b/examples/factt.monkey @@ -1,4 +1,4 @@ - let fact = fn(n, a) { +fact := fn(n, a) { if (n == 0) { return a } diff --git a/examples/fib.monkey b/examples/fib.monkey index 51b2b60..651d9fa 100644 --- a/examples/fib.monkey +++ b/examples/fib.monkey @@ -1,4 +1,4 @@ -let fib = fn(x) { +fib := fn(x) { if (x == 0) { return 0 } diff --git a/examples/fibi.monkey b/examples/fibi.monkey index e41ce7d..bb31a98 100644 --- a/examples/fibi.monkey +++ b/examples/fibi.monkey @@ -1,11 +1,11 @@ -let fib = fn(n) { +fib := fn(n) { if (n < 3) { return 1 } - let a = 1 - let b = 1 - let c = 0 - let i = 0 + a := 1 + b := 1 + c := 0 + i := 0 while (i < n - 2) { c = a + b b = a diff --git a/examples/fibt.monkey b/examples/fibt.monkey index fdbdca4..c7f6cfb 100644 --- a/examples/fibt.monkey +++ b/examples/fibt.monkey @@ -1,4 +1,4 @@ -let fib = fn(n, a, b) { +fib := fn(n, a, b) { if (n == 0) { return a } diff --git a/examples/input.monkey b/examples/input.monkey index 8085a1e..8c9e9bd 100644 --- a/examples/input.monkey +++ b/examples/input.monkey @@ -1,3 +1,3 @@ -let name = input("What is your name? ") +name := input("What is your name? ") print("Hello " + name) \ No newline at end of file diff --git a/lexer/lexer.go b/lexer/lexer.go index 6f8191e..3daef73 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -134,7 +134,14 @@ func (l *Lexer) NextToken() token.Token { case ']': tok = newToken(token.RBRACKET, l.ch) case ':': - tok = newToken(token.COLON, l.ch) + if l.peekChar() == '=' { + ch := l.ch + l.readChar() + literal := string(ch) + string(l.ch) + tok = token.Token{Type: token.BIND, Literal: literal} + } else { + tok = newToken(token.COLON, l.ch) + } default: if isLetter(l.ch) { tok.Literal = l.readIdentifier() diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index 712787e..2283bc9 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -7,15 +7,15 @@ import ( func TestNextToken(t *testing.T) { input := `#!./monkey-lang - let five = 5; - let ten = 10; + five := 5; + ten := 10; - let add = fn(x, y) { + add := fn(x, y) { x + y; }; # this is a comment - let result = add(five, ten); + result := add(five, ten); !-/*5; 5 < 10 > 5; @@ -40,19 +40,16 @@ func TestNextToken(t *testing.T) { expectedLiteral string }{ {token.COMMENT, "!./monkey-lang"}, - {token.LET, "let"}, {token.IDENT, "five"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.INT, "5"}, {token.SEMICOLON, ";"}, - {token.LET, "let"}, {token.IDENT, "ten"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.INT, "10"}, {token.SEMICOLON, ";"}, - {token.LET, "let"}, {token.IDENT, "add"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.FUNCTION, "fn"}, {token.LPAREN, "("}, {token.IDENT, "x"}, @@ -67,9 +64,8 @@ func TestNextToken(t *testing.T) { {token.RBRACE, "}"}, {token.SEMICOLON, ";"}, {token.COMMENT, " this is a comment"}, - {token.LET, "let"}, {token.IDENT, "result"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.IDENT, "add"}, {token.LPAREN, "("}, {token.IDENT, "five"}, @@ -163,9 +159,9 @@ func TestNextToken(t *testing.T) { func TestStringEscapes(t *testing.T) { input := `#!./monkey-lang -let a = "\"foo\"" -let b = "\x00\x0a\x7f" -let c = "\r\n\t" +a := "\"foo\"" +b := "\x00\x0a\x7f" +c := "\r\n\t" ` tests := []struct { @@ -173,17 +169,14 @@ let c = "\r\n\t" expectedLiteral string }{ {token.COMMENT, "!./monkey-lang"}, - {token.LET, "let"}, {token.IDENT, "a"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.STRING, "\"foo\""}, - {token.LET, "let"}, {token.IDENT, "b"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.STRING, "\x00\n\u007f"}, - {token.LET, "let"}, {token.IDENT, "c"}, - {token.ASSIGN, "="}, + {token.BIND, ":="}, {token.STRING, "\r\n\t"}, {token.EOF, ""}, } diff --git a/parser/parser.go b/parser/parser.go index b9376b0..deb1c01 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -11,7 +11,7 @@ import ( const ( _ int = iota LOWEST - ASSIGN // = + ASSIGN // := or = EQUALS // == LESSGREATER // > or < SUM // + @@ -22,6 +22,7 @@ const ( ) var precedences = map[token.TokenType]int{ + token.BIND: ASSIGN, token.ASSIGN: ASSIGN, token.EQ: EQUALS, token.NOT_EQ: EQUALS, @@ -89,6 +90,7 @@ func New(l *lexer.Lexer) *Parser { p.registerInfix(token.GTE, p.parseInfixExpression) p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression) + p.registerInfix(token.BIND, p.parseBindExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.DOT, p.parseSelectorExpression) @@ -161,8 +163,6 @@ func (p *Parser) parseStatement() ast.Statement { switch p.curToken.Type { case token.COMMENT: return p.parseComment() - case token.LET: - return p.parseLetStatement() case token.RETURN: return p.parseReturnStatement() default: @@ -170,34 +170,6 @@ func (p *Parser) parseStatement() ast.Statement { } } -func (p *Parser) parseLetStatement() *ast.LetStatement { - stmt := &ast.LetStatement{Token: p.curToken} - - if !p.expectPeek(token.IDENT) { - return nil - } - - stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal} - - if !p.expectPeek(token.ASSIGN) { - return nil - } - - p.nextToken() - - stmt.Value = p.parseExpression(LOWEST) - - if fl, ok := stmt.Value.(*ast.FunctionLiteral); ok { - fl.Name = stmt.Name.Value - } - - if p.peekTokenIs(token.SEMICOLON) { - p.nextToken() - } - - return stmt -} - func (p *Parser) parseReturnStatement() *ast.ReturnStatement { stmt := &ast.ReturnStatement{Token: p.curToken} @@ -578,3 +550,26 @@ func (p *Parser) parseSelectorExpression(expression ast.Expression) ast.Expressi index := &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal} return &ast.IndexExpression{Left: expression, Index: index} } + +func (p *Parser) parseBindExpression(expression ast.Expression) ast.Expression { + switch node := expression.(type) { + case *ast.Identifier: + default: + msg := fmt.Sprintf("expected identifier opn left but got %T %#v", node, expression) + p.errors = append(p.errors, msg) + return nil + } + + be := &ast.BindExpression{Token: p.curToken, Left: expression} + + p.nextToken() + + be.Value = p.parseExpression(LOWEST) + + if fl, ok := be.Value.(*ast.FunctionLiteral); ok { + ident := be.Left.(*ast.Identifier) + fl.Name = ident.Value + } + + return be +} diff --git a/parser/parser_test.go b/parser/parser_test.go index 57f09c8..da05cb5 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -8,15 +8,14 @@ import ( "testing" ) -func TestLetStatements(t *testing.T) { +func TestBindExpressions(t *testing.T) { tests := []struct { - input string - expectedIdentifier string - expectedValue interface{} + input string + expected string }{ - {"let x = 5;", "x", 5}, - {"let y = true;", "y", true}, - {"let foobar = y;", "foobar", "y"}, + {"x := 5;", "x:=5"}, + {"y := true;", "y:=true"}, + {"foobar := y;", "foobar:=y"}, } for _, tt := range tests { @@ -25,20 +24,7 @@ func TestLetStatements(t *testing.T) { program := p.ParseProgram() checkParserErrors(t, p) - if len(program.Statements) != 1 { - t.Fatalf("program.Statements does not contain 1 statements. got=%d", - len(program.Statements)) - } - - stmt := program.Statements[0] - if !testLetStatement(t, stmt, tt.expectedIdentifier) { - return - } - - val := stmt.(*ast.LetStatement).Value - if !testLiteralExpression(t, val, tt.expectedValue) { - return - } + assert.Equal(t, tt.expected, program.String()) } } @@ -994,35 +980,17 @@ func TestParsingHashLiteralsWithExpressions(t *testing.T) { } } -func TestFunctionLiteralWithName(t *testing.T) { - input := `let myFunction = fn() { };` +func TestFunctionDefinitionParsing(t *testing.T) { + assert := assert.New(t) + + input := `add := fn(x, y) { x + y; }` l := lexer.New(input) p := New(l) program := p.ParseProgram() checkParserErrors(t, p) - if len(program.Statements) != 1 { - t.Fatalf("program.Body does not contain %d statements. got=%d\n", - 1, len(program.Statements)) - } - - stmt, ok := program.Statements[0].(*ast.LetStatement) - if !ok { - t.Fatalf("program.Statements[0] is not ast.LetStatement. got=%T", - program.Statements[0]) - } - - function, ok := stmt.Value.(*ast.FunctionLiteral) - if !ok { - t.Fatalf("stmt.Value is not ast.FunctionLiteral. got=%T", - stmt.Value) - } - - if function.Name != "myFunction" { - t.Fatalf("function literal name wrong. want 'myFunction', got=%q\n", - function.Name) - } + assert.Equal("add:=fn(x, y) (x + y)", program.String()) } func TestWhileExpression(t *testing.T) { @@ -1094,32 +1062,6 @@ func TestAssignmentExpressions(t *testing.T) { } } -func testLetStatement(t *testing.T, s ast.Statement, name string) bool { - if s.TokenLiteral() != "let" { - t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) - return false - } - - letStmt, ok := s.(*ast.LetStatement) - if !ok { - t.Errorf("s not *ast.LetStatement. got=%T", s) - return false - } - - if letStmt.Name.Value != name { - t.Errorf("letStmt.Name.Value not '%s'. got=%s", name, letStmt.Name.Value) - return false - } - - if letStmt.Name.TokenLiteral() != name { - t.Errorf("letStmt.Name.TokenLiteral() not '%s'. got=%s", - name, letStmt.Name.TokenLiteral()) - return false - } - - return true -} - func testInfixExpression(t *testing.T, exp ast.Expression, left interface{}, operator string, right interface{}) bool { @@ -1249,7 +1191,7 @@ func TestComments(t *testing.T) { {"#!monkey", "!monkey"}, {"# foo", " foo"}, {" # foo", " foo"}, - {" // let x = 1", " let x = 1"}, + {" // x := 1", " x := 1"}, } for _, tt := range tests { diff --git a/testdata/arrays.monkey b/testdata/arrays.monkey index 5e54d70..607a4d0 100644 --- a/testdata/arrays.monkey +++ b/testdata/arrays.monkey @@ -1,2 +1,2 @@ -let xs = [1, 2, 3] +xs := [1, 2, 3] xs[0] + xs[1] + xs[2] \ No newline at end of file diff --git a/testdata/assign.monkey b/testdata/assign.monkey index 8e13df9..49ea7a5 100644 --- a/testdata/assign.monkey +++ b/testdata/assign.monkey @@ -1,3 +1,3 @@ -let x = 1 +x := 1 x = 2 x = x + 1 \ No newline at end of file diff --git a/testdata/binding.monkey b/testdata/binding.monkey new file mode 100644 index 0000000..e940ca1 --- /dev/null +++ b/testdata/binding.monkey @@ -0,0 +1,3 @@ +x := 1 +y := 2 +z := x \ No newline at end of file diff --git a/testdata/builtins.monkey b/testdata/builtins.monkey index 97c1ae8..aef6ac0 100644 --- a/testdata/builtins.monkey +++ b/testdata/builtins.monkey @@ -1,4 +1,4 @@ -let xs = [1, 2, 3] +xs := [1, 2, 3] len(xs) first(xs) @@ -9,5 +9,5 @@ pop(xs) len("foo") -let x = input() +x := input() print(x) \ No newline at end of file diff --git a/testdata/closures.monkey b/testdata/closures.monkey index 0be84a7..0b9908e 100644 --- a/testdata/closures.monkey +++ b/testdata/closures.monkey @@ -1,2 +1,2 @@ -let f = fn(x) { fn() { x + 1 } } +f := fn(x) { fn() { x + 1 } } f(2) \ No newline at end of file diff --git a/testdata/functions.monkey b/testdata/functions.monkey index 3caa4a5..6585ff4 100644 --- a/testdata/functions.monkey +++ b/testdata/functions.monkey @@ -1,2 +1,2 @@ -let f = fn(x, y) { x * y }; +f := fn(x, y) { x * y }; f(2, 4) \ No newline at end of file diff --git a/testdata/hashes.monkey b/testdata/hashes.monkey index 4aad8db..dd173c3 100644 --- a/testdata/hashes.monkey +++ b/testdata/hashes.monkey @@ -1,2 +1,2 @@ -let d = {"a": 1, "b": 2} +d := {"a": 1, "b": 2} d["a"] + d["b"] \ No newline at end of file diff --git a/testdata/if.monkey b/testdata/if.monkey index ed7e345..df86479 100644 --- a/testdata/if.monkey +++ b/testdata/if.monkey @@ -1,4 +1,4 @@ -let x = 1 +x := 1 if (x == 1) { x = 2 x diff --git a/testdata/let.monkey b/testdata/let.monkey deleted file mode 100644 index d44cd88..0000000 --- a/testdata/let.monkey +++ /dev/null @@ -1,3 +0,0 @@ -let x = 1 -let y = 2 -let z = x \ No newline at end of file diff --git a/testdata/popbug1.monkey b/testdata/popbug1.monkey deleted file mode 100644 index 4431c96..0000000 --- a/testdata/popbug1.monkey +++ /dev/null @@ -1 +0,0 @@ -let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; n = n - 1; }; x; \ No newline at end of file diff --git a/testdata/popbug2.monkey b/testdata/popbug2.monkey deleted file mode 100644 index 64d053c..0000000 --- a/testdata/popbug2.monkey +++ /dev/null @@ -1 +0,0 @@ -let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; let n = n - 1; }; x; \ No newline at end of file diff --git a/testdata/popbug3.monkey b/testdata/popbug3.monkey deleted file mode 100644 index ca3968b..0000000 --- a/testdata/popbug3.monkey +++ /dev/null @@ -1 +0,0 @@ -let x = 1; if (x == 1) { let x = 2 } \ No newline at end of file diff --git a/examples/selectors.monkey b/testdata/selectors.monkey similarity index 78% rename from examples/selectors.monkey rename to testdata/selectors.monkey index 436f3fc..acc41c2 100644 --- a/examples/selectors.monkey +++ b/testdata/selectors.monkey @@ -1,4 +1,4 @@ -let d = {"foo": 1, "bar": 2} +d := {"foo": 1, "bar": 2} assert(d.foo == 1, "d.foo != 1") assert(d.bar == 2, "d.bar != 2") diff --git a/testdata/strings.monkey b/testdata/strings.monkey index f8ae683..8c39ccb 100644 --- a/testdata/strings.monkey +++ b/testdata/strings.monkey @@ -1,2 +1,2 @@ -let s = "hello" +s := "hello" s + " " + "world" \ No newline at end of file diff --git a/token/token.go b/token/token.go index ad7ac78..282a839 100644 --- a/token/token.go +++ b/token/token.go @@ -20,6 +20,7 @@ const ( STRING = "STRING" // Operators + BIND = ":=" ASSIGN = "=" PLUS = "+" MINUS = "-" @@ -50,7 +51,6 @@ const ( // Keywords FUNCTION = "FUNCTION" - LET = "LET" TRUE = "TRUE" FALSE = "FALSE" NULL = "NULL" @@ -62,7 +62,6 @@ const ( var keywords = map[string]TokenType{ "fn": FUNCTION, - "let": LET, "true": TRUE, "false": FALSE, "if": IF, diff --git a/vim/monkey.vim b/vim/monkey.vim index 2a1c877..951f052 100644 --- a/vim/monkey.vim +++ b/vim/monkey.vim @@ -13,12 +13,13 @@ syntax case match syntax keyword xType true false null -syntax keyword xKeyword let fn if else return while +syntax keyword xKeyword fn if else return while syntax keyword xFunction len input print first last rest push pop exit assert syntax keyword xOperator == != < > ! -syntax keyword xOperator + - * / = +syntax keyword xOperator + - * / +syntax keyword xOperator := = syntax region xString start=/"/ skip=/\\./ end=/"/ diff --git a/vm/vm.go b/vm/vm.go index 053432d..828f386 100644 --- a/vm/vm.go +++ b/vm/vm.go @@ -181,6 +181,11 @@ func (vm *VM) Run() error { vm.globals[globalIndex] = ref } + err := vm.push(Null) + if err != nil { + return err + } + case code.OpAssignGlobal: globalIndex := code.ReadUint16(ins[ip+1:]) vm.currentFrame().ip += 2 @@ -284,7 +289,17 @@ func (vm *VM) Run() error { frame := vm.currentFrame() - vm.stack[frame.basePointer+int(localIndex)] = vm.pop() + ref := vm.pop() + if immutable, ok := ref.(object.Immutable); ok { + vm.stack[frame.basePointer+int(localIndex)] = immutable.Clone() + } else { + vm.stack[frame.basePointer+int(localIndex)] = ref + } + + err := vm.push(Null) + if err != nil { + return err + } case code.OpGetLocal: localIndex := code.ReadUint8(ins[ip+1:]) diff --git a/vm/vm_test.go b/vm/vm_test.go index 436f013..2e5390d 100644 --- a/vm/vm_test.go +++ b/vm/vm_test.go @@ -261,12 +261,12 @@ func TestConditionals(t *testing.T) { {"if (1 > 2) { 10 }", Null}, {"if (false) { 10 }", Null}, {"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, - {"if (true) { let a = 5; }", Null}, - {"if (true) { 10; let a = 5; }", Null}, - {"if (false) { 10 } else { let b = 5; }", Null}, - {"if (false) { 10 } else { 10; let b = 5; }", Null}, - {"if (true) { let a = 5; } else { 10 }", Null}, - {"let x = 0; if (true) { x = 1; }; if (false) { x = 2; }; x", 1}, + {"if (true) { a := 5; }", Null}, + {"if (true) { 10; a := 5; }", Null}, + {"if (false) { 10 } else { b := 5; }", Null}, + {"if (false) { 10 } else { 10; b := 5; }", Null}, + {"if (true) { a := 5; } else { 10 }", Null}, + {"x := 0; if (true) { x = 1; }; if (false) { x = 2; }; x", 1}, {"if (1 < 2) { 10 } else if (1 == 2) { 20 }", 10}, {"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30}, } @@ -274,11 +274,11 @@ func TestConditionals(t *testing.T) { runVmTests(t, tests) } -func TestGlobalLetStatements(t *testing.T) { +func TestGlobalBindExpressions(t *testing.T) { tests := []vmTestCase{ - {"let one = 1; one", 1}, - {"let one = 1; let two = 2; one + two", 3}, - {"let one = 1; let two = one + one; one + two", 3}, + {"one := 1; one", 1}, + {"one := 1; two := 2; one + two", 3}, + {"one := 1; two := one + one; one + two", 3}, } runVmTests(t, tests) @@ -364,24 +364,24 @@ func TestCallingFunctionsWithoutArguments(t *testing.T) { tests := []vmTestCase{ { input: ` - let fivePlusTen = fn() { return 5 + 10; }; + fivePlusTen := fn() { return 5 + 10; }; fivePlusTen(); `, expected: 15, }, { input: ` - let one = fn() { return 1; }; - let two = fn() { return 2; }; + one := fn() { return 1; }; + two := fn() { return 2; }; one() + two() `, expected: 3, }, { input: ` - let a = fn() { return 1 }; - let b = fn() { return a() + 1 }; - let c = fn() { return b() + 1 }; + a := fn() { return 1 }; + b := fn() { return a() + 1 }; + c:= fn() { return b() + 1 }; c(); `, expected: 3, @@ -395,14 +395,14 @@ func TestFunctionsWithReturnStatements(t *testing.T) { tests := []vmTestCase{ { input: ` - let earlyExit = fn() { return 99; 100; }; + earlyExit := fn() { return 99; 100; }; earlyExit(); `, expected: 99, }, { input: ` - let earlyExit = fn() { return 99; return 100; }; + earlyExit := fn() { return 99; return 100; }; earlyExit(); `, expected: 99, @@ -416,15 +416,15 @@ func TestFunctionsWithoutReturnValue(t *testing.T) { tests := []vmTestCase{ { input: ` - let noReturn = fn() { }; + noReturn := fn() { }; noReturn(); `, expected: Null, }, { input: ` - let noReturn = fn() { }; - let noReturnTwo = fn() { noReturn(); }; + noReturn := fn() { }; + noReturnTwo := fn() { noReturn(); }; noReturn(); noReturnTwo(); `, @@ -439,16 +439,16 @@ func TestFirstClassFunctions(t *testing.T) { tests := []vmTestCase{ { input: ` - let returnsOne = fn() { return 1; }; - let returnsOneReturner = fn() { return returnsOne; }; + returnsOne := fn() { return 1; }; + returnsOneReturner := fn() { return returnsOne; }; returnsOneReturner()(); `, expected: 1, }, { input: ` - let returnsOneReturner = fn() { - let returnsOne = fn() { return 1; }; + returnsOneReturner := fn() { + returnsOne := fn() { return 1; }; return returnsOne; }; returnsOneReturner()(); @@ -464,43 +464,43 @@ func TestCallingFunctionsWithBindings(t *testing.T) { tests := []vmTestCase{ //{ // input: ` - // let one = fn() { let one = 1; return one }; + // one := fn() { one := 1; return one }; // one(); // `, // expected: 1, //}, { input: ` - let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; + oneAndTwo := fn() { one := 1; two := 2; return one + two; }; oneAndTwo(); `, expected: 3, }, { input: ` - let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; - let threeAndFour = fn() { let three = 3; let four = 4; return three + four; }; + oneAndTwo := fn() { one := 1; two := 2; return one + two; }; + threeAndFour := fn() { three := 3; four := 4; return three + four; }; oneAndTwo() + threeAndFour(); `, expected: 10, }, { input: ` - let firstFoobar = fn() { let foobar = 50; return foobar; }; - let secondFoobar = fn() { let foobar = 100; return foobar; }; + firstFoobar := fn() { foobar := 50; return foobar; }; + secondFoobar := fn() { foobar := 100; return foobar; }; firstFoobar() + secondFoobar(); `, expected: 150, }, { input: ` - let globalSeed = 50; - let minusOne = fn() { - let num = 1; + globalSeed := 50; + minusOne := fn() { + num := 1; return globalSeed - num; } - let minusTwo = fn() { - let num = 2; + minusTwo := fn() { + num := 2; return globalSeed - num; } minusOne() + minusTwo(); @@ -516,22 +516,22 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { tests := []vmTestCase{ { input: ` - let identity = fn(a) { return a; }; + identity := fn(a) { return a; }; identity(4); `, expected: 4, }, { input: ` - let sum = fn(a, b) { return a + b; }; + sum := fn(a, b) { return a + b; }; sum(1, 2); `, expected: 3, }, { input: ` - let sum = fn(a, b) { - let c = a + b; + sum := fn(a, b) { + c := a + b; return c; }; sum(1, 2); @@ -540,8 +540,8 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { }, { input: ` - let sum = fn(a, b) { - let c = a + b; + sum := fn(a, b) { + c := a + b; return c; }; sum(1, 2) + sum(3, 4);`, @@ -549,11 +549,11 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { }, { input: ` - let sum = fn(a, b) { - let c = a + b; + sum := fn(a, b) { + c := a + b; return c; }; - let outer = fn() { + outer := fn() { return sum(1, 2) + sum(3, 4); }; outer(); @@ -562,14 +562,14 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) { }, { input: ` - let globalNum = 10; + globalNum := 10; - let sum = fn(a, b) { - let c = a + b; + sum := fn(a, b) { + c := a + b; return c + globalNum; }; - let outer = fn() { + outer := fn() { return sum(1, 2) + sum(3, 4) + globalNum; }; @@ -697,72 +697,72 @@ func TestClosures(t *testing.T) { tests := []vmTestCase{ { input: ` - let newClosure = fn(a) { + newClosure := fn(a) { return fn() { return a; }; }; - let closure = newClosure(99); + closure := newClosure(99); closure(); `, expected: 99, }, { input: ` - let newAdder = fn(a, b) { + newAdder := fn(a, b) { return fn(c) { return a + b + c }; }; - let adder = newAdder(1, 2); + adder := newAdder(1, 2); adder(8); `, expected: 11, }, { input: ` - let newAdder = fn(a, b) { - let c = a + b; + newAdder := fn(a, b) { + c := a + b; return fn(d) { return c + d }; }; - let adder = newAdder(1, 2); + adder := newAdder(1, 2); adder(8); `, expected: 11, }, { input: ` - let newAdderOuter = fn(a, b) { - let c = a + b; + newAdderOuter := fn(a, b) { + c := a + b; return fn(d) { - let e = d + c; + e := d + c; return fn(f) { return e + f; }; }; }; - let newAdderInner = newAdderOuter(1, 2) - let adder = newAdderInner(3); + newAdderInner := newAdderOuter(1, 2) + adder := newAdderInner(3); adder(8); `, expected: 14, }, { input: ` - let a = 1; - let newAdderOuter = fn(b) { + a := 1; + newAdderOuter := fn(b) { return fn(c) { return fn(d) { return a + b + c + d }; }; }; - let newAdderInner = newAdderOuter(2) - let adder = newAdderInner(3); + newAdderInner := newAdderOuter(2) + adder := newAdderInner(3); adder(8); `, expected: 14, }, { input: ` - let newClosure = fn(a, b) { - let one = fn() { return a; }; - let two = fn() { return b; }; + newClosure := fn(a, b) { + one := fn() { return a; }; + two := fn() { return b; }; return fn() { return one() + two(); }; }; - let closure = newClosure(9, 90); + closure := newClosure(9, 90); closure(); `, expected: 99, @@ -776,7 +776,7 @@ func TestRecursiveFunctions(t *testing.T) { tests := []vmTestCase{ { input: ` - let countDown = fn(x) { + countDown := fn(x) { if (x == 0) { return 0; } else { @@ -789,14 +789,14 @@ func TestRecursiveFunctions(t *testing.T) { }, { input: ` - let countDown = fn(x) { + countDown := fn(x) { if (x == 0) { return 0; } else { return countDown(x - 1); } }; - let wrapper = fn() { + wrapper := fn() { return countDown(1); }; wrapper(); @@ -805,8 +805,8 @@ func TestRecursiveFunctions(t *testing.T) { }, { input: ` - let wrapper = fn() { - let countDown = fn(x) { + wrapper := fn() { + countDown := fn(x) { if (x == 0) { return 0; } else { @@ -828,7 +828,7 @@ func TestRecursiveFibonacci(t *testing.T) { tests := []vmTestCase{ { input: ` - let fibonacci = fn(x) { + fibonacci := fn(x) { if (x == 0) { return 0; } else { @@ -851,14 +851,14 @@ func TestRecursiveFibonacci(t *testing.T) { func TestIterations(t *testing.T) { tests := []vmTestCase{ {"while (false) { }", nil}, - {"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, - {"let n = 10; while (n > 0) { let n = n - 1 }; n", 0}, - {"let n = 0; while (n < 10) { let n = n + 1 }", nil}, - {"let n = 10; while (n > 0) { let n = n - 1 }", nil}, - {"let n = 0; while (n < 10) { n = n + 1 }; n", 10}, - {"let n = 10; while (n > 0) { n = n - 1 }; n", 0}, - {"let n = 0; while (n < 10) { n = n + 1 }", nil}, - {"let n = 10; while (n > 0) { n = n - 1 }", nil}, + {"n := 0; while (n < 10) { n := n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n := n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n := n + 1 }", nil}, + {"n := 10; while (n > 0) { n := n - 1 }", nil}, + {"n := 0; while (n < 10) { n = n + 1 }; n", 10}, + {"n := 10; while (n > 0) { n = n - 1 }; n", 0}, + {"n := 0; while (n < 10) { n = n + 1 }", nil}, + {"n := 10; while (n > 0) { n = n - 1 }", nil}, } runVmTests(t, tests) @@ -866,7 +866,7 @@ func TestIterations(t *testing.T) { func TestIndexAssignmentStatements(t *testing.T) { tests := []vmTestCase{ - {"let xs = [1, 2, 3]; xs[1] = 4; xs[1];", 4}, + {"xs := [1, 2, 3]; xs[1] = 4; xs[1];", 4}, } runVmTests(t, tests) @@ -874,20 +874,20 @@ func TestIndexAssignmentStatements(t *testing.T) { func TestAssignmentExpressions(t *testing.T) { tests := []vmTestCase{ - {"let a = 0; a = 5;", nil}, - {"let a = 0; a = 5; a;", 5}, - {"let a = 0; a = 5 * 5;", nil}, - {"let a = 0; a = 5 * 5; a;", 25}, - {"let a = 0; a = 5; let b = 0; b = a;", nil}, - {"let a = 0; a = 5; let b = 0; b = a; b;", 5}, - {"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5;", nil}, - {"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5; c;", 15}, - {"let a = 5; let b = a; a = 0;", nil}, - {"let a = 5; let b = a; a = 0; b;", 5}, - {"let one = 0; one = 1", nil}, - {"let one = 0; one = 1; one", 1}, - {"let one = 0; one = 1; let two = 0; two = 2; one + two", 3}, - {"let one = 0; one = 1; let two = 0; two = one + one; one + two", 3}, + {"a := 0; a = 5;", nil}, + {"a := 0; a = 5; a;", 5}, + {"a := 0; a = 5 * 5;", nil}, + {"a := 0; a = 5 * 5; a;", 25}, + {"a := 0; a = 5; b := 0; b = a;", nil}, + {"a := 0; a = 5; b := 0; b = a; b;", 5}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5;", nil}, + {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15}, + {"a := 5; b := a; a = 0;", nil}, + {"a := 5; b := a; a = 0; b;", 5}, + {"one := 0; one = 1", nil}, + {"one := 0; one = 1; one", 1}, + {"one := 0; one = 1; two := 0; two = 2; one + two", 3}, + {"one := 0; one = 1; two := 0; two = one + one; one + two", 3}, } runVmTests(t, tests) @@ -897,7 +897,7 @@ func TestTailCalls(t *testing.T) { tests := []vmTestCase{ { input: ` - let fact = fn(n, a) { + fact := fn(n, a) { if (n == 0) { return a } @@ -912,7 +912,7 @@ func TestTailCalls(t *testing.T) { // without tail recursion optimization this will cause a stack overflow { input: ` - let iter = fn(n, max) { + iter := fn(n, max) { if (n == max) { return n } @@ -971,14 +971,14 @@ func TestIntegration(t *testing.T) { func BenchmarkFibonacci(b *testing.B) { tests := map[string]string{ "iterative": ` - let fib = fn(n) { + fib := fn(n) { if (n < 3) { return 1 } - let a = 1 - let b = 1 - let c = 0 - let i = 0 + a := 1 + b := 1 + c := 0 + i := 0 while (i < n - 2) { c = a + b b = a @@ -991,7 +991,7 @@ func BenchmarkFibonacci(b *testing.B) { fib(35) `, "recursive": ` - let fib = fn(x) { + fib := fn(x) { if (x == 0) { return 0 } @@ -1004,7 +1004,7 @@ func BenchmarkFibonacci(b *testing.B) { fib(35) `, "tail-recursive": ` - let fib = fn(n, a, b) { + fib := fn(n, a, b) { if (n == 0) { return a }