bind expression (:=) instead of let
Some checks failed
Build / build (push) Successful in 10m26s
Test / build (push) Failing after 16m44s

This commit is contained in:
Chuck Smith
2024-03-21 17:43:03 -04:00
parent 66d5453ecc
commit 6282075e66
36 changed files with 425 additions and 583 deletions

View File

@@ -47,31 +47,6 @@ func (p *Program) String() string {
return out.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 { type ReturnStatement struct {
Token token.Token // the 'return' token Token token.Token // the 'return' token
ReturnValue Expression ReturnValue Expression
@@ -284,23 +259,23 @@ type StringLiteral struct {
Value string Value string
} }
func (sl StringLiteral) TokenLiteral() string { func (sl *StringLiteral) TokenLiteral() string {
return sl.Token.Literal return sl.Token.Literal
} }
func (sl StringLiteral) String() string { func (sl *StringLiteral) String() string {
return sl.Token.Literal return sl.Token.Literal
} }
func (sl StringLiteral) expressionNode() {} func (sl *StringLiteral) expressionNode() {}
type ArrayLiteral struct { type ArrayLiteral struct {
Token token.Token // the '[' token Token token.Token // the '[' token
Elements []Expression Elements []Expression
} }
func (al ArrayLiteral) TokenLiteral() string { func (al *ArrayLiteral) TokenLiteral() string {
return al.Token.Literal return al.Token.Literal
} }
func (al ArrayLiteral) String() string { func (al *ArrayLiteral) String() string {
var out bytes.Buffer var out bytes.Buffer
elements := []string{} elements := []string{}
@@ -314,7 +289,7 @@ func (al ArrayLiteral) String() string {
return out.String() return out.String()
} }
func (al ArrayLiteral) expressionNode() {} func (al *ArrayLiteral) expressionNode() {}
type IndexExpression struct { type IndexExpression struct {
Token token.Token // The [ token Token token.Token // The [ token
@@ -322,11 +297,11 @@ type IndexExpression struct {
Index Expression Index Expression
} }
func (ie IndexExpression) TokenLiteral() string { func (ie *IndexExpression) TokenLiteral() string {
return ie.Token.Literal return ie.Token.Literal
} }
func (ie IndexExpression) String() string { func (ie *IndexExpression) String() string {
var out bytes.Buffer var out bytes.Buffer
out.WriteString("(") out.WriteString("(")
@@ -338,18 +313,18 @@ func (ie IndexExpression) String() string {
return out.String() return out.String()
} }
func (ie IndexExpression) expressionNode() {} func (ie *IndexExpression) expressionNode() {}
type HashLiteral struct { type HashLiteral struct {
Token token.Token // the '{' token Token token.Token // the '{' token
Pairs map[Expression]Expression Pairs map[Expression]Expression
} }
func (hl HashLiteral) TokenLiteral() string { func (hl *HashLiteral) TokenLiteral() string {
return hl.Token.Literal return hl.Token.Literal
} }
func (hl HashLiteral) String() string { func (hl *HashLiteral) String() string {
var out bytes.Buffer var out bytes.Buffer
pairs := []string{} pairs := []string{}
@@ -364,7 +339,7 @@ func (hl HashLiteral) String() string {
return out.String() return out.String()
} }
func (hl HashLiteral) expressionNode() {} func (hl *HashLiteral) expressionNode() {}
type WhileExpression struct { type WhileExpression struct {
Token token.Token // The 'while' token Token token.Token // The 'while' token
@@ -389,6 +364,28 @@ func (we *WhileExpression) String() string {
return out.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: // AssignmentExpression represents an assignment expression of the form:
// x = 1 or xs[1] = 2 // x = 1 or xs[1] = 2
type AssignmentExpression struct { type AssignmentExpression struct {
@@ -397,10 +394,10 @@ type AssignmentExpression struct {
Value Expression Value Expression
} }
func (as AssignmentExpression) TokenLiteral() string { func (as *AssignmentExpression) TokenLiteral() string {
return as.Token.Literal return as.Token.Literal
} }
func (as AssignmentExpression) String() string { func (as *AssignmentExpression) String() string {
var out bytes.Buffer var out bytes.Buffer
out.WriteString(as.Left.String()) out.WriteString(as.Left.String())
@@ -409,7 +406,7 @@ func (as AssignmentExpression) String() string {
return out.String() return out.String()
} }
func (as AssignmentExpression) expressionNode() {} func (as *AssignmentExpression) expressionNode() {}
// Comment a comment // Comment a comment
type Comment struct { type Comment struct {

View File

@@ -1,6 +1,7 @@
package ast package ast
import ( import (
"github.com/stretchr/testify/assert"
"monkey/token" "monkey/token"
"testing" "testing"
) )
@@ -8,21 +9,22 @@ import (
func TestString(t *testing.T) { func TestString(t *testing.T) {
program := &Program{ program := &Program{
Statements: []Statement{ Statements: []Statement{
&LetStatement{ &ExpressionStatement{
Token: token.Token{Type: token.LET, Literal: "let"}, Token: token.Token{Type: token.IDENT, Literal: "myVar"},
Name: &Identifier{ Expression: &BindExpression{
Token: token.Token{Type: token.IDENT, Literal: "myVar"}, Token: token.Token{Type: token.BIND, Literal: ":="},
Value: "myVar", Left: &Identifier{
}, Token: token.Token{Type: token.IDENT, Literal: "myVar"},
Value: &Identifier{ Value: "myVar",
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"}, },
Value: "anotherVar", Value: &Identifier{
Token: token.Token{Type: token.IDENT, Literal: "anotherVar"},
Value: "anotherVar",
},
}, },
}, },
}, },
} }
if program.String() != "let myVar = anotherVar;" { assert.Equal(t, "myVar:=anotherVar", program.String())
t.Errorf("program.String wrong. got=%q", program.String())
}
} }

View File

@@ -292,23 +292,36 @@ func (c *Compiler) Compile(node ast.Node) error {
return fmt.Errorf("expected identifier or index expression got=%s", node.Left) return fmt.Errorf("expected identifier or index expression got=%s", node.Left)
} }
case *ast.LetStatement: case *ast.BindExpression:
symbol, ok := c.symbolTable.Resolve(node.Name.Value) var symbol Symbol
if !ok {
symbol = c.symbolTable.Define(node.Name.Value)
}
c.l++ if ident, ok := node.Left.(*ast.Identifier); ok {
err := c.Compile(node.Value) symbol, ok = c.symbolTable.Resolve(ident.Value)
c.l-- if !ok {
if err != nil { symbol = c.symbolTable.Define(ident.Value)
return err } 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 { } else {
c.emit(code.OpSetLocal, symbol.Index) return fmt.Errorf("expected identifier got=%s", node.Left)
} }
case *ast.Identifier: case *ast.Identifier:

View File

@@ -11,6 +11,8 @@ import (
"testing" "testing"
) )
type Instructions string
type compilerTestCase struct { type compilerTestCase struct {
input string input string
expectedConstants []interface{} expectedConstants []interface{}
@@ -210,146 +212,61 @@ func TestBooleanExpressions(t *testing.T) {
} }
func TestConditionals(t *testing.T) { func TestConditionals(t *testing.T) {
tests := []compilerTestCase{ tests := []compilerTestCase2{
{ {
input: ` input: `
if (true) { 10 }; 3333; if (true) { 10 }; 3333;
`, `,
expectedConstants: []interface{}{10, 3333}, constants: []interface{}{10, 3333},
expectedInstructions: []code.Instructions{ instructions: "0000 LoadTrue\n0001 JumpIfFalse 10\n0004 LoadConstant 0\n0007 Jump 11\n0010 LoadNull\n0011 Pop\n0012 LoadConstant 1\n0015 Pop\n",
// 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),
},
}, { }, {
input: ` input: `
if (true) { 10 } else { 20 }; 3333; if (true) { 10 } else { 20 }; 3333;
`, `,
expectedConstants: []interface{}{10, 20, 3333}, constants: []interface{}{10, 20, 3333},
expectedInstructions: []code.Instructions{ instructions: "0000 LoadTrue\n0001 JumpIfFalse 10\n0004 LoadConstant 0\n0007 Jump 13\n0010 LoadConstant 1\n0013 Pop\n0014 LoadConstant 2\n0017 Pop\n",
// 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),
},
}, },
{ {
input: ` input: `
let x = 0; if (true) { x = 1; }; if (false) { x = 2; } let x = 0; if (true) { x = 1; }; if (false) { x = 2; }
`, `,
expectedConstants: []interface{}{0, 1, 2}, constants: []interface{}{0, 1, 2},
expectedInstructions: []code.Instructions{ 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",
// 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),
},
}, },
} }
runCompilerTests(t, tests) runCompilerTests2(t, tests)
} }
func TestGlobalLetStatements(t *testing.T) { func TestGlobalBindExpressions(t *testing.T) {
tests := []compilerTestCase{ tests := []compilerTestCase2{
{ {
input: ` input: `
let one = 1; one := 1;
let two = 2; two := 2;
`, `,
expectedConstants: []interface{}{1, 2}, instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadConstant 1\n0010 BindGlobal 1\n0013 Pop\n",
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpSetGlobal, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpSetGlobal, 1),
},
}, },
{ {
input: ` input: `
let one = 1; one := 1;
one; one;
`, `,
expectedConstants: []interface{}{1}, constants: []interface{}{1},
expectedInstructions: []code.Instructions{ instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadGlobal 0\n0010 Pop\n",
code.Make(code.OpConstant, 0),
code.Make(code.OpSetGlobal, 0),
code.Make(code.OpGetGlobal, 0),
code.Make(code.OpPop),
},
}, },
{ {
input: ` input: `
let one = 1; one := 1;
let two = one; two := one;
two; two;
`, `,
expectedConstants: []interface{}{1}, constants: []interface{}{1},
expectedInstructions: []code.Instructions{ instructions: "0000 LoadConstant 0\n0003 BindGlobal 0\n0006 Pop\n0007 LoadGlobal 0\n0010 BindGlobal 1\n0013 Pop\n0014 LoadGlobal 1\n0017 Pop\n",
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),
},
}, },
} }
runCompilerTests(t, tests) runCompilerTests2(t, tests)
} }
func TestStringExpressions(t *testing.T) { func TestStringExpressions(t *testing.T) {
@@ -735,95 +652,53 @@ func TestCompilerScopes(t *testing.T) {
} }
func TestFunctionCalls(t *testing.T) { func TestFunctionCalls(t *testing.T) {
tests := []compilerTestCase{ tests := []compilerTestCase2{
{ {
input: `fn() { return 24 }();`, input: `fn() { return 24 }();`,
expectedConstants: []interface{}{ constants: []interface{}{
24, 24,
[]code.Instructions{ Instructions("0000 LoadConstant 0\n0003 Return\n"),
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 MakeClosure 1 0\n0004 Call 0\n0006 Pop\n",
}, },
{ {
input: ` input: `
let noArg = fn() { return 24 }; noArg := fn() { return 24 };
noArg(); noArg();
`, `,
expectedConstants: []interface{}{ constants: []interface{}{
24, 24,
[]code.Instructions{ Instructions("0000 SetSelf 0\n0002 LoadConstant 0\n0005 Return\n"),
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 LoadGlobal 0\n0003 MakeClosure 1 1\n0007 BindGlobal 0\n0010 Pop\n0011 LoadGlobal 0\n0014 Call 0\n0016 Pop\n",
}, },
{ {
input: ` input: `
let oneArg = fn(a) { return a }; oneArg := fn(a) { return a };
oneArg(24); oneArg(24);
`, `,
expectedConstants: []interface{}{ constants: []interface{}{
[]code.Instructions{ Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Return\n"),
code.Make(code.OpGetLocal, 0),
code.Make(code.OpReturn),
},
24, 24,
}, },
expectedInstructions: []code.Instructions{ 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",
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),
},
}, },
{ {
input: ` input: `
let manyArg = fn(a, b, c) { a; b; return c }; manyArg := fn(a, b, c) { a; b; return c };
manyArg(24, 25, 26); manyArg(24, 25, 26);
`, `,
expectedConstants: []interface{}{ constants: []interface{}{
[]code.Instructions{ Instructions("0000 SetSelf 0\n0002 LoadLocal 0\n0004 Pop\n0005 LoadLocal 1\n0007 Pop\n0008 LoadLocal 2\n0010 Return\n"),
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),
},
24, 24,
25, 25,
26, 26,
}, },
expectedInstructions: []code.Instructions{ 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",
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),
},
}, },
} }
runCompilerTests(t, tests) runCompilerTests2(t, tests)
} }
func TestAssignmentExpressions(t *testing.T) { func TestAssignmentExpressions(t *testing.T) {
@@ -1240,10 +1115,11 @@ func testConstants2(t *testing.T, expected []interface{}, actual []object.Object
for i, constant := range expected { for i, constant := range expected {
switch constant := constant.(type) { switch constant := constant.(type) {
case []code.Instructions: case Instructions:
fn, ok := actual[i].(*object.CompiledFunction) assert.Equal(
assert.True(ok) string(constant),
assert.Equal(constant, fn.Instructions.String()) actual[i].(*object.CompiledFunction).Instructions.String(),
)
case string: case string:
assert.Equal(constant, actual[i].(*object.String).Value) assert.Equal(constant, actual[i].(*object.String).Value)

View File

@@ -45,6 +45,23 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
} }
return &object.ReturnValue{Value: val} 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: case *ast.AssignmentExpression:
left := Eval(node.Left, env) left := Eval(node.Left, env)
if isError(left) { if isError(left) {
@@ -94,18 +111,6 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return NULL 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: case *ast.Identifier:
return evalIdentifier(node, env) return evalIdentifier(node, env)

View File

@@ -147,7 +147,7 @@ func TestReturnStatements(t *testing.T) {
}, },
{ {
` `
let f = fn(x) { f := fn(x) {
return x; return x;
x + 10; x + 10;
}; };
@@ -156,8 +156,8 @@ func TestReturnStatements(t *testing.T) {
}, },
{ {
` `
let f = fn(x) { f := fn(x) {
let result = x + 10; result := x + 10;
return result; return result;
return 10; return 10;
}; };
@@ -249,7 +249,7 @@ func TestIndexAssignmentStatements(t *testing.T) {
input string input string
expected int64 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 { for _, tt := range tests {
@@ -263,16 +263,16 @@ func TestAssignmentStatements(t *testing.T) {
input string input string
expected interface{} expected interface{}
}{ }{
{"let a = 0; a = 5;", nil}, {"a := 0; a = 5;", nil},
{"let a = 0; a = 5; a;", 5}, {"a := 0; a = 5; a;", 5},
{"let a = 0; a = 5 * 5;", nil}, {"a := 0; a = 5 * 5;", nil},
{"let a = 0; a = 5 * 5; a;", 25}, {"a := 0; a = 5 * 5; a;", 25},
{"let a = 0; a = 5; let b = 0; b = a;", nil}, {"a := 0; a = 5; b := 0; b = a;", nil},
{"let a = 0; a = 5; let b = 0; b = a; b;", 5}, {"a := 0; a = 5; b := 0; b = a; b;", 5},
{"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5;", nil}, {"a := 0; a = 5; b := 0; b = a; 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}, {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15},
{"let a = 5; let b = a; a = 0;", nil}, {"a := 5; b := a; a = 0;", nil},
{"let a = 5; let b = a; a = 0; b;", 5}, {"a := 5; b := a; a = 0; b;", 5},
} }
for _, tt := range tests { 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 { tests := []struct {
input string input string
expected int64 expected int64
}{ }{
{"let a = 5; a;", 5}, {"a := 5; a;", 5},
{"let a = 5 * 5; a;", 25}, {"a := 5 * 5; a;", 25},
{"let a = 5; let b = a; b;", 5}, {"a := 5; b := a; b;", 5},
{"let a = 5; let b = a; let c = a + b + 5; c;", 15}, {"a := 5; b := a; c := a + b + 5; c;", 15},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -331,11 +331,11 @@ func TestFunctionApplication(t *testing.T) {
input string input string
expected int64 expected int64
}{ }{
{"let identity = fn(x) { x; }; identity(5);", 5}, {"identity := fn(x) { x; }; identity(5);", 5},
{"let identity = fn(x) { return x; }; identity(5);", 5}, {"identity := fn(x) { return x; }; identity(5);", 5},
{"let double = fn(x) { x * 2; }; double(5);", 10}, {"double := fn(x) { x * 2; }; double(5);", 10},
{"let add = fn(x, y) { x + y; }; add(5, 5);", 10}, {"add := fn(x, y) { x + y; }; add(5, 5);", 10},
{"let add = fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20}, {"add := fn(x, y) { x + y; }; add(5 + 5, add(5, 5));", 20},
{"fn(x) { x; }(5)", 5}, {"fn(x) { x; }(5)", 5},
} }
@@ -346,11 +346,11 @@ func TestFunctionApplication(t *testing.T) {
func TestClosures(t *testing.T) { func TestClosures(t *testing.T) {
input := ` input := `
let newAdder = fn(x) { newAdder := fn(x) {
fn(y) { x + y }; fn(y) { x + y };
}; };
let addTwo = newAdder(2); addTwo := newAdder(2);
addTwo(2);` addTwo(2);`
testIntegerObject(t, testEval(input), 4) testIntegerObject(t, testEval(input), 4)
@@ -496,7 +496,7 @@ func TestArrayIndexExpressions(t *testing.T) {
3, 3,
}, },
{ {
"let i = 0; [1][i];", "i := 0; [1][i];",
1, 1,
}, },
{ {
@@ -504,15 +504,15 @@ func TestArrayIndexExpressions(t *testing.T) {
3, 3,
}, },
{ {
"let myArray = [1, 2, 3]; myArray[2];", "myArray := [1, 2, 3]; myArray[2];",
3, 3,
}, },
{ {
"let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];", "myArray := [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
6, 6,
}, },
{ {
"let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]", "myArray := [1, 2, 3]; i := myArray[0]; myArray[i]",
2, 2,
}, },
{ {
@@ -538,7 +538,7 @@ func TestArrayIndexExpressions(t *testing.T) {
} }
func TestHashLiterals(t *testing.T) { func TestHashLiterals(t *testing.T) {
input := `let two = "two"; input := `two := "two";
{ {
"one": 10 - 9, "one": 10 - 9,
two: 1 + 1, two: 1 + 1,
@@ -621,7 +621,7 @@ func TestHashIndexExpressions(t *testing.T) {
nil, nil,
}, },
{ {
`let key = "foo"; {"foo": 5}[key]`, `key := "foo"; {"foo": 5}[key]`,
5, 5,
}, },
{ {
@@ -660,14 +660,14 @@ func TestWhileExpressions(t *testing.T) {
expected interface{} expected interface{}
}{ }{
{"while (false) { }", nil}, {"while (false) { }", nil},
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, {"n := 0; while (n < 10) { n := n + 1 }; n", 10},
{"let n = 10; while (n > 0) { let n = n - 1 }; n", 0}, {"n := 10; while (n > 0) { n := n - 1 }; n", 0},
{"let n = 0; while (n < 10) { let n = n + 1 }", nil}, {"n := 0; while (n < 10) { n := n + 1 }", nil},
{"let n = 10; while (n > 0) { let n = n - 1 }", nil}, {"n := 10; while (n > 0) { n := n - 1 }", nil},
{"let n = 0; while (n < 10) { n = n + 1 }; n", 10}, {"n := 0; while (n < 10) { n = n + 1 }; n", 10},
{"let n = 10; while (n > 0) { n = n - 1 }; n", 0}, {"n := 10; while (n > 0) { n = n - 1 }; n", 0},
{"let n = 0; while (n < 10) { n = n + 1 }", nil}, {"n := 0; while (n < 10) { n = n + 1 }", nil},
{"let n = 10; while (n > 0) { n = n - 1 }", nil}, {"n := 10; while (n > 0) { n = n - 1 }", nil},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -778,7 +778,7 @@ func TestStringIndexExpressions(t *testing.T) {
"c", "c",
}, },
{ {
`let i = 0; "abc"[i];`, `i := 0; "abc"[i];`,
"a", "a",
}, },
{ {
@@ -786,7 +786,7 @@ func TestStringIndexExpressions(t *testing.T) {
"c", "c",
}, },
{ {
`let myString = "abc"; myString[0] + myString[1] + myString[2];`, `myString := "abc"; myString[0] + myString[1] + myString[2];`,
"abc", "abc",
}, },
{ {

View File

@@ -1,7 +1,7 @@
#!./monkey-lang #!./monkey-lang
let fill = fn(x, i) { fill := fn(x, i) {
let xs = [] xs := []
while (i > 0) { while (i > 0) {
xs = push(xs, x) xs = push(xs, x)
i = i - 1 i = i - 1
@@ -9,17 +9,17 @@ let fill = fn(x, i) {
return xs return xs
} }
let buildJumpMap = fn(program) { buildJumpMap := fn(program) {
let stack = [] stack := []
let map = {} map := {}
let n = 0 n := 0
while (n < len(program)) { while (n < len(program)) {
if (program[n] == "[") { if (program[n] == "[") {
stack = push(stack, n) stack = push(stack, n)
} }
if (program[n] == "]") { if (program[n] == "]") {
let start = pop(stack) start := pop(stack)
map[start] = n map[start] = n
map[n] = start map[n] = start
} }
@@ -29,13 +29,13 @@ let buildJumpMap = fn(program) {
return map 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] return ascii_table[s]
} }
let chr = fn(x) { chr := fn(x) {
if (x < 0) { if (x < 0) {
return "??" return "??"
} }
@@ -45,27 +45,27 @@ let chr = fn(x) {
return ascii_table[x] return ascii_table[x]
} }
let read = fn() { read := fn() {
let buf = input() buf := input()
if (len(buf) > 0) { if (len(buf) > 0) {
return ord(buf[0]) return ord(buf[0])
} }
return 0 return 0
} }
let write = fn(x) { write := fn(x) {
print(chr(x)) print(chr(x))
} }
let VM = fn(program) { VM := fn(program) {
let jumps = buildJumpMap(program) jumps := buildJumpMap(program)
let ip = 0 ip := 0
let dp = 0 dp := 0
let memory = fill(0, 32) memory := fill(0, 32)
while (ip < len(program)) { while (ip < len(program)) {
let op = program[ip] op := program[ip]
if (op == ">") { if (op == ">") {
dp = dp + 1 dp = dp + 1
@@ -108,9 +108,9 @@ let VM = fn(program) {
} }
// Hello World // Hello World
let program = "++++++++ [ >++++ [ >++ >+++ >+++ >+ <<<<- ] >+ >+ >- >>+ [<] <- ] >>. >---. +++++++..+++. >>. <-. <. +++.------.--------. >>+. >++." program := "++++++++ [ >++++ [ >++ >+++ >+++ >+ <<<<- ] >+ >+ >- >>+ [<] <- ] >>. >---. +++++++..+++. >>. <-. <. +++.------.--------. >>+. >++."
// 2 + 5 // 2 + 5
// let program = "++> +++++ [<+>-] ++++++++ [<++++++>-] < ." // program := "++> +++++ [<+>-] ++++++++ [<++++++>-] < ."
VM(program) VM(program)

View File

@@ -1,21 +1,21 @@
let name = "Monkey"; name := "Monkey";
let age = 1; age := 1;
let inspirations = ["Scheme", "Lisp", "JavaScript", "Clojure"]; inspirations := ["Scheme", "Lisp", "JavaScript", "Clojure"];
let book = { book := {
"title": "Writing A Compiler In Go", "title": "Writing A Compiler In Go",
"author": "Thorsten Ball", "author": "Thorsten Ball",
"prequel": "Writing An Interpreter In Go" "prequel": "Writing An Interpreter In Go"
}; };
let printBookName = fn(book) { printBookName := fn(book) {
let title = book["title"]; title := book["title"];
let author = book["author"]; author := book["author"];
print(author + " - " + title); print(author + " - " + title);
}; };
printBookName(book); printBookName(book);
let fibonacci = fn(x) { fibonacci := fn(x) {
if (x == 0) { if (x == 0) {
return 0 return 0
} else { } else {
@@ -27,8 +27,8 @@ let fibonacci = fn(x) {
} }
}; };
let map = fn(arr, f) { map := fn(arr, f) {
let iter = fn(arr, accumulated) { iter := fn(arr, accumulated) {
if (len(arr) == 0) { if (len(arr) == 0) {
return accumulated return accumulated
} else { } else {
@@ -39,5 +39,5 @@ let map = fn(arr, f) {
return iter(arr, []); 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); map(numbers, fibonacci);

View File

@@ -1,4 +1,4 @@
let fact = fn(n) { fact := fn(n) {
if (n == 0) { if (n == 0) {
return 1 return 1
} }

View File

@@ -1,4 +1,4 @@
let fact = fn(n, a) { fact := fn(n, a) {
if (n == 0) { if (n == 0) {
return a return a
} }

View File

@@ -1,4 +1,4 @@
let fib = fn(x) { fib := fn(x) {
if (x == 0) { if (x == 0) {
return 0 return 0
} }

View File

@@ -1,11 +1,11 @@
let fib = fn(n) { fib := fn(n) {
if (n < 3) { if (n < 3) {
return 1 return 1
} }
let a = 1 a := 1
let b = 1 b := 1
let c = 0 c := 0
let i = 0 i := 0
while (i < n - 2) { while (i < n - 2) {
c = a + b c = a + b
b = a b = a

View File

@@ -1,4 +1,4 @@
let fib = fn(n, a, b) { fib := fn(n, a, b) {
if (n == 0) { if (n == 0) {
return a return a
} }

View File

@@ -1,3 +1,3 @@
let name = input("What is your name? ") name := input("What is your name? ")
print("Hello " + name) print("Hello " + name)

View File

@@ -134,7 +134,14 @@ func (l *Lexer) NextToken() token.Token {
case ']': case ']':
tok = newToken(token.RBRACKET, l.ch) tok = newToken(token.RBRACKET, l.ch)
case ':': 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: default:
if isLetter(l.ch) { if isLetter(l.ch) {
tok.Literal = l.readIdentifier() tok.Literal = l.readIdentifier()

View File

@@ -7,15 +7,15 @@ import (
func TestNextToken(t *testing.T) { func TestNextToken(t *testing.T) {
input := `#!./monkey-lang input := `#!./monkey-lang
let five = 5; five := 5;
let ten = 10; ten := 10;
let add = fn(x, y) { add := fn(x, y) {
x + y; x + y;
}; };
# this is a comment # this is a comment
let result = add(five, ten); result := add(five, ten);
!-/*5; !-/*5;
5 < 10 > 5; 5 < 10 > 5;
@@ -40,19 +40,16 @@ func TestNextToken(t *testing.T) {
expectedLiteral string expectedLiteral string
}{ }{
{token.COMMENT, "!./monkey-lang"}, {token.COMMENT, "!./monkey-lang"},
{token.LET, "let"},
{token.IDENT, "five"}, {token.IDENT, "five"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.INT, "5"}, {token.INT, "5"},
{token.SEMICOLON, ";"}, {token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "ten"}, {token.IDENT, "ten"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.INT, "10"}, {token.INT, "10"},
{token.SEMICOLON, ";"}, {token.SEMICOLON, ";"},
{token.LET, "let"},
{token.IDENT, "add"}, {token.IDENT, "add"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.FUNCTION, "fn"}, {token.FUNCTION, "fn"},
{token.LPAREN, "("}, {token.LPAREN, "("},
{token.IDENT, "x"}, {token.IDENT, "x"},
@@ -67,9 +64,8 @@ func TestNextToken(t *testing.T) {
{token.RBRACE, "}"}, {token.RBRACE, "}"},
{token.SEMICOLON, ";"}, {token.SEMICOLON, ";"},
{token.COMMENT, " this is a comment"}, {token.COMMENT, " this is a comment"},
{token.LET, "let"},
{token.IDENT, "result"}, {token.IDENT, "result"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.IDENT, "add"}, {token.IDENT, "add"},
{token.LPAREN, "("}, {token.LPAREN, "("},
{token.IDENT, "five"}, {token.IDENT, "five"},
@@ -163,9 +159,9 @@ func TestNextToken(t *testing.T) {
func TestStringEscapes(t *testing.T) { func TestStringEscapes(t *testing.T) {
input := `#!./monkey-lang input := `#!./monkey-lang
let a = "\"foo\"" a := "\"foo\""
let b = "\x00\x0a\x7f" b := "\x00\x0a\x7f"
let c = "\r\n\t" c := "\r\n\t"
` `
tests := []struct { tests := []struct {
@@ -173,17 +169,14 @@ let c = "\r\n\t"
expectedLiteral string expectedLiteral string
}{ }{
{token.COMMENT, "!./monkey-lang"}, {token.COMMENT, "!./monkey-lang"},
{token.LET, "let"},
{token.IDENT, "a"}, {token.IDENT, "a"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.STRING, "\"foo\""}, {token.STRING, "\"foo\""},
{token.LET, "let"},
{token.IDENT, "b"}, {token.IDENT, "b"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.STRING, "\x00\n\u007f"}, {token.STRING, "\x00\n\u007f"},
{token.LET, "let"},
{token.IDENT, "c"}, {token.IDENT, "c"},
{token.ASSIGN, "="}, {token.BIND, ":="},
{token.STRING, "\r\n\t"}, {token.STRING, "\r\n\t"},
{token.EOF, ""}, {token.EOF, ""},
} }

View File

@@ -11,7 +11,7 @@ import (
const ( const (
_ int = iota _ int = iota
LOWEST LOWEST
ASSIGN // = ASSIGN // := or =
EQUALS // == EQUALS // ==
LESSGREATER // > or < LESSGREATER // > or <
SUM // + SUM // +
@@ -22,6 +22,7 @@ const (
) )
var precedences = map[token.TokenType]int{ var precedences = map[token.TokenType]int{
token.BIND: ASSIGN,
token.ASSIGN: ASSIGN, token.ASSIGN: ASSIGN,
token.EQ: EQUALS, token.EQ: EQUALS,
token.NOT_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.GTE, p.parseInfixExpression)
p.registerInfix(token.LPAREN, p.parseCallExpression) p.registerInfix(token.LPAREN, p.parseCallExpression)
p.registerInfix(token.LBRACKET, p.parseIndexExpression) p.registerInfix(token.LBRACKET, p.parseIndexExpression)
p.registerInfix(token.BIND, p.parseBindExpression)
p.registerInfix(token.ASSIGN, p.parseAssignmentExpression) p.registerInfix(token.ASSIGN, p.parseAssignmentExpression)
p.registerInfix(token.DOT, p.parseSelectorExpression) p.registerInfix(token.DOT, p.parseSelectorExpression)
@@ -161,8 +163,6 @@ func (p *Parser) parseStatement() ast.Statement {
switch p.curToken.Type { switch p.curToken.Type {
case token.COMMENT: case token.COMMENT:
return p.parseComment() return p.parseComment()
case token.LET:
return p.parseLetStatement()
case token.RETURN: case token.RETURN:
return p.parseReturnStatement() return p.parseReturnStatement()
default: 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 { func (p *Parser) parseReturnStatement() *ast.ReturnStatement {
stmt := &ast.ReturnStatement{Token: p.curToken} 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} index := &ast.StringLiteral{Token: p.curToken, Value: p.curToken.Literal}
return &ast.IndexExpression{Left: expression, Index: index} 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
}

View File

@@ -8,15 +8,14 @@ import (
"testing" "testing"
) )
func TestLetStatements(t *testing.T) { func TestBindExpressions(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
expectedIdentifier string expected string
expectedValue interface{}
}{ }{
{"let x = 5;", "x", 5}, {"x := 5;", "x:=5"},
{"let y = true;", "y", true}, {"y := true;", "y:=true"},
{"let foobar = y;", "foobar", "y"}, {"foobar := y;", "foobar:=y"},
} }
for _, tt := range tests { for _, tt := range tests {
@@ -25,20 +24,7 @@ func TestLetStatements(t *testing.T) {
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
if len(program.Statements) != 1 { assert.Equal(t, tt.expected, program.String())
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
}
} }
} }
@@ -994,35 +980,17 @@ func TestParsingHashLiteralsWithExpressions(t *testing.T) {
} }
} }
func TestFunctionLiteralWithName(t *testing.T) { func TestFunctionDefinitionParsing(t *testing.T) {
input := `let myFunction = fn() { };` assert := assert.New(t)
input := `add := fn(x, y) { x + y; }`
l := lexer.New(input) l := lexer.New(input)
p := New(l) p := New(l)
program := p.ParseProgram() program := p.ParseProgram()
checkParserErrors(t, p) checkParserErrors(t, p)
if len(program.Statements) != 1 { assert.Equal("add:=fn<add>(x, y) (x + y)", program.String())
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)
}
} }
func TestWhileExpression(t *testing.T) { 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{}, func testInfixExpression(t *testing.T, exp ast.Expression, left interface{},
operator string, right interface{}) bool { operator string, right interface{}) bool {
@@ -1249,7 +1191,7 @@ func TestComments(t *testing.T) {
{"#!monkey", "!monkey"}, {"#!monkey", "!monkey"},
{"# foo", " foo"}, {"# foo", " foo"},
{" # foo", " foo"}, {" # foo", " foo"},
{" // let x = 1", " let x = 1"}, {" // x := 1", " x := 1"},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@@ -1,2 +1,2 @@
let xs = [1, 2, 3] xs := [1, 2, 3]
xs[0] + xs[1] + xs[2] xs[0] + xs[1] + xs[2]

View File

@@ -1,3 +1,3 @@
let x = 1 x := 1
x = 2 x = 2
x = x + 1 x = x + 1

3
testdata/binding.monkey vendored Normal file
View File

@@ -0,0 +1,3 @@
x := 1
y := 2
z := x

View File

@@ -1,4 +1,4 @@
let xs = [1, 2, 3] xs := [1, 2, 3]
len(xs) len(xs)
first(xs) first(xs)
@@ -9,5 +9,5 @@ pop(xs)
len("foo") len("foo")
let x = input() x := input()
print(x) print(x)

View File

@@ -1,2 +1,2 @@
let f = fn(x) { fn() { x + 1 } } f := fn(x) { fn() { x + 1 } }
f(2) f(2)

View File

@@ -1,2 +1,2 @@
let f = fn(x, y) { x * y }; f := fn(x, y) { x * y };
f(2, 4) f(2, 4)

View File

@@ -1,2 +1,2 @@
let d = {"a": 1, "b": 2} d := {"a": 1, "b": 2}
d["a"] + d["b"] d["a"] + d["b"]

2
testdata/if.monkey vendored
View File

@@ -1,4 +1,4 @@
let x = 1 x := 1
if (x == 1) { if (x == 1) {
x = 2 x = 2
x x

3
testdata/let.monkey vendored
View File

@@ -1,3 +0,0 @@
let x = 1
let y = 2
let z = x

View File

@@ -1 +0,0 @@
let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; n = n - 1; }; x;

View File

@@ -1 +0,0 @@
let n = 2; let x = 0; while (n > 0) { if (n > 1) { x = x + 1 }; let n = n - 1; }; x;

View File

@@ -1 +0,0 @@
let x = 1; if (x == 1) { let x = 2 }

View File

@@ -1,4 +1,4 @@
let d = {"foo": 1, "bar": 2} d := {"foo": 1, "bar": 2}
assert(d.foo == 1, "d.foo != 1") assert(d.foo == 1, "d.foo != 1")
assert(d.bar == 2, "d.bar != 2") assert(d.bar == 2, "d.bar != 2")

View File

@@ -1,2 +1,2 @@
let s = "hello" s := "hello"
s + " " + "world" s + " " + "world"

View File

@@ -20,6 +20,7 @@ const (
STRING = "STRING" STRING = "STRING"
// Operators // Operators
BIND = ":="
ASSIGN = "=" ASSIGN = "="
PLUS = "+" PLUS = "+"
MINUS = "-" MINUS = "-"
@@ -50,7 +51,6 @@ const (
// Keywords // Keywords
FUNCTION = "FUNCTION" FUNCTION = "FUNCTION"
LET = "LET"
TRUE = "TRUE" TRUE = "TRUE"
FALSE = "FALSE" FALSE = "FALSE"
NULL = "NULL" NULL = "NULL"
@@ -62,7 +62,6 @@ const (
var keywords = map[string]TokenType{ var keywords = map[string]TokenType{
"fn": FUNCTION, "fn": FUNCTION,
"let": LET,
"true": TRUE, "true": TRUE,
"false": FALSE, "false": FALSE,
"if": IF, "if": IF,

View File

@@ -13,12 +13,13 @@ syntax case match
syntax keyword xType true false null 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 xFunction len input print first last rest push pop exit assert
syntax keyword xOperator == != < > ! syntax keyword xOperator == != < > !
syntax keyword xOperator + - * / = syntax keyword xOperator + - * /
syntax keyword xOperator := =
syntax region xString start=/"/ skip=/\\./ end=/"/ syntax region xString start=/"/ skip=/\\./ end=/"/

View File

@@ -181,6 +181,11 @@ func (vm *VM) Run() error {
vm.globals[globalIndex] = ref vm.globals[globalIndex] = ref
} }
err := vm.push(Null)
if err != nil {
return err
}
case code.OpAssignGlobal: case code.OpAssignGlobal:
globalIndex := code.ReadUint16(ins[ip+1:]) globalIndex := code.ReadUint16(ins[ip+1:])
vm.currentFrame().ip += 2 vm.currentFrame().ip += 2
@@ -284,7 +289,17 @@ func (vm *VM) Run() error {
frame := vm.currentFrame() 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: case code.OpGetLocal:
localIndex := code.ReadUint8(ins[ip+1:]) localIndex := code.ReadUint8(ins[ip+1:])

View File

@@ -261,12 +261,12 @@ func TestConditionals(t *testing.T) {
{"if (1 > 2) { 10 }", Null}, {"if (1 > 2) { 10 }", Null},
{"if (false) { 10 }", Null}, {"if (false) { 10 }", Null},
{"if ((if (false) { 10 })) { 10 } else { 20 }", 20}, {"if ((if (false) { 10 })) { 10 } else { 20 }", 20},
{"if (true) { let a = 5; }", Null}, {"if (true) { a := 5; }", Null},
{"if (true) { 10; let a = 5; }", Null}, {"if (true) { 10; a := 5; }", Null},
{"if (false) { 10 } else { let b = 5; }", Null}, {"if (false) { 10 } else { b := 5; }", Null},
{"if (false) { 10 } else { 10; let b = 5; }", Null}, {"if (false) { 10 } else { 10; b := 5; }", Null},
{"if (true) { let a = 5; } else { 10 }", Null}, {"if (true) { a := 5; } else { 10 }", Null},
{"let x = 0; if (true) { x = 1; }; if (false) { x = 2; }; x", 1}, {"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 }", 10},
{"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30}, {"if (1 > 2) { 10 } else if (1 == 2) { 20 } else { 30 }", 30},
} }
@@ -274,11 +274,11 @@ func TestConditionals(t *testing.T) {
runVmTests(t, tests) runVmTests(t, tests)
} }
func TestGlobalLetStatements(t *testing.T) { func TestGlobalBindExpressions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{"let one = 1; one", 1}, {"one := 1; one", 1},
{"let one = 1; let two = 2; one + two", 3}, {"one := 1; two := 2; one + two", 3},
{"let one = 1; let two = one + one; one + two", 3}, {"one := 1; two := one + one; one + two", 3},
} }
runVmTests(t, tests) runVmTests(t, tests)
@@ -364,24 +364,24 @@ func TestCallingFunctionsWithoutArguments(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let fivePlusTen = fn() { return 5 + 10; }; fivePlusTen := fn() { return 5 + 10; };
fivePlusTen(); fivePlusTen();
`, `,
expected: 15, expected: 15,
}, },
{ {
input: ` input: `
let one = fn() { return 1; }; one := fn() { return 1; };
let two = fn() { return 2; }; two := fn() { return 2; };
one() + two() one() + two()
`, `,
expected: 3, expected: 3,
}, },
{ {
input: ` input: `
let a = fn() { return 1 }; a := fn() { return 1 };
let b = fn() { return a() + 1 }; b := fn() { return a() + 1 };
let c = fn() { return b() + 1 }; c:= fn() { return b() + 1 };
c(); c();
`, `,
expected: 3, expected: 3,
@@ -395,14 +395,14 @@ func TestFunctionsWithReturnStatements(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let earlyExit = fn() { return 99; 100; }; earlyExit := fn() { return 99; 100; };
earlyExit(); earlyExit();
`, `,
expected: 99, expected: 99,
}, },
{ {
input: ` input: `
let earlyExit = fn() { return 99; return 100; }; earlyExit := fn() { return 99; return 100; };
earlyExit(); earlyExit();
`, `,
expected: 99, expected: 99,
@@ -416,15 +416,15 @@ func TestFunctionsWithoutReturnValue(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let noReturn = fn() { }; noReturn := fn() { };
noReturn(); noReturn();
`, `,
expected: Null, expected: Null,
}, },
{ {
input: ` input: `
let noReturn = fn() { }; noReturn := fn() { };
let noReturnTwo = fn() { noReturn(); }; noReturnTwo := fn() { noReturn(); };
noReturn(); noReturn();
noReturnTwo(); noReturnTwo();
`, `,
@@ -439,16 +439,16 @@ func TestFirstClassFunctions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let returnsOne = fn() { return 1; }; returnsOne := fn() { return 1; };
let returnsOneReturner = fn() { return returnsOne; }; returnsOneReturner := fn() { return returnsOne; };
returnsOneReturner()(); returnsOneReturner()();
`, `,
expected: 1, expected: 1,
}, },
{ {
input: ` input: `
let returnsOneReturner = fn() { returnsOneReturner := fn() {
let returnsOne = fn() { return 1; }; returnsOne := fn() { return 1; };
return returnsOne; return returnsOne;
}; };
returnsOneReturner()(); returnsOneReturner()();
@@ -464,43 +464,43 @@ func TestCallingFunctionsWithBindings(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
//{ //{
// input: ` // input: `
// let one = fn() { let one = 1; return one }; // one := fn() { one := 1; return one };
// one(); // one();
// `, // `,
// expected: 1, // expected: 1,
//}, //},
{ {
input: ` input: `
let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; oneAndTwo := fn() { one := 1; two := 2; return one + two; };
oneAndTwo(); oneAndTwo();
`, `,
expected: 3, expected: 3,
}, },
{ {
input: ` input: `
let oneAndTwo = fn() { let one = 1; let two = 2; return one + two; }; oneAndTwo := fn() { one := 1; two := 2; return one + two; };
let threeAndFour = fn() { let three = 3; let four = 4; return three + four; }; threeAndFour := fn() { three := 3; four := 4; return three + four; };
oneAndTwo() + threeAndFour(); oneAndTwo() + threeAndFour();
`, `,
expected: 10, expected: 10,
}, },
{ {
input: ` input: `
let firstFoobar = fn() { let foobar = 50; return foobar; }; firstFoobar := fn() { foobar := 50; return foobar; };
let secondFoobar = fn() { let foobar = 100; return foobar; }; secondFoobar := fn() { foobar := 100; return foobar; };
firstFoobar() + secondFoobar(); firstFoobar() + secondFoobar();
`, `,
expected: 150, expected: 150,
}, },
{ {
input: ` input: `
let globalSeed = 50; globalSeed := 50;
let minusOne = fn() { minusOne := fn() {
let num = 1; num := 1;
return globalSeed - num; return globalSeed - num;
} }
let minusTwo = fn() { minusTwo := fn() {
let num = 2; num := 2;
return globalSeed - num; return globalSeed - num;
} }
minusOne() + minusTwo(); minusOne() + minusTwo();
@@ -516,22 +516,22 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let identity = fn(a) { return a; }; identity := fn(a) { return a; };
identity(4); identity(4);
`, `,
expected: 4, expected: 4,
}, },
{ {
input: ` input: `
let sum = fn(a, b) { return a + b; }; sum := fn(a, b) { return a + b; };
sum(1, 2); sum(1, 2);
`, `,
expected: 3, expected: 3,
}, },
{ {
input: ` input: `
let sum = fn(a, b) { sum := fn(a, b) {
let c = a + b; c := a + b;
return c; return c;
}; };
sum(1, 2); sum(1, 2);
@@ -540,8 +540,8 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) {
}, },
{ {
input: ` input: `
let sum = fn(a, b) { sum := fn(a, b) {
let c = a + b; c := a + b;
return c; return c;
}; };
sum(1, 2) + sum(3, 4);`, sum(1, 2) + sum(3, 4);`,
@@ -549,11 +549,11 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) {
}, },
{ {
input: ` input: `
let sum = fn(a, b) { sum := fn(a, b) {
let c = a + b; c := a + b;
return c; return c;
}; };
let outer = fn() { outer := fn() {
return sum(1, 2) + sum(3, 4); return sum(1, 2) + sum(3, 4);
}; };
outer(); outer();
@@ -562,14 +562,14 @@ func TestCallingFunctionsWithArgumentsAndBindings(t *testing.T) {
}, },
{ {
input: ` input: `
let globalNum = 10; globalNum := 10;
let sum = fn(a, b) { sum := fn(a, b) {
let c = a + b; c := a + b;
return c + globalNum; return c + globalNum;
}; };
let outer = fn() { outer := fn() {
return sum(1, 2) + sum(3, 4) + globalNum; return sum(1, 2) + sum(3, 4) + globalNum;
}; };
@@ -697,72 +697,72 @@ func TestClosures(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let newClosure = fn(a) { newClosure := fn(a) {
return fn() { return a; }; return fn() { return a; };
}; };
let closure = newClosure(99); closure := newClosure(99);
closure(); closure();
`, `,
expected: 99, expected: 99,
}, },
{ {
input: ` input: `
let newAdder = fn(a, b) { newAdder := fn(a, b) {
return fn(c) { return a + b + c }; return fn(c) { return a + b + c };
}; };
let adder = newAdder(1, 2); adder := newAdder(1, 2);
adder(8); adder(8);
`, `,
expected: 11, expected: 11,
}, },
{ {
input: ` input: `
let newAdder = fn(a, b) { newAdder := fn(a, b) {
let c = a + b; c := a + b;
return fn(d) { return c + d }; return fn(d) { return c + d };
}; };
let adder = newAdder(1, 2); adder := newAdder(1, 2);
adder(8); adder(8);
`, `,
expected: 11, expected: 11,
}, },
{ {
input: ` input: `
let newAdderOuter = fn(a, b) { newAdderOuter := fn(a, b) {
let c = a + b; c := a + b;
return fn(d) { return fn(d) {
let e = d + c; e := d + c;
return fn(f) { return e + f; }; return fn(f) { return e + f; };
}; };
}; };
let newAdderInner = newAdderOuter(1, 2) newAdderInner := newAdderOuter(1, 2)
let adder = newAdderInner(3); adder := newAdderInner(3);
adder(8); adder(8);
`, `,
expected: 14, expected: 14,
}, },
{ {
input: ` input: `
let a = 1; a := 1;
let newAdderOuter = fn(b) { newAdderOuter := fn(b) {
return fn(c) { return fn(c) {
return fn(d) { return a + b + c + d }; return fn(d) { return a + b + c + d };
}; };
}; };
let newAdderInner = newAdderOuter(2) newAdderInner := newAdderOuter(2)
let adder = newAdderInner(3); adder := newAdderInner(3);
adder(8); adder(8);
`, `,
expected: 14, expected: 14,
}, },
{ {
input: ` input: `
let newClosure = fn(a, b) { newClosure := fn(a, b) {
let one = fn() { return a; }; one := fn() { return a; };
let two = fn() { return b; }; two := fn() { return b; };
return fn() { return one() + two(); }; return fn() { return one() + two(); };
}; };
let closure = newClosure(9, 90); closure := newClosure(9, 90);
closure(); closure();
`, `,
expected: 99, expected: 99,
@@ -776,7 +776,7 @@ func TestRecursiveFunctions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let countDown = fn(x) { countDown := fn(x) {
if (x == 0) { if (x == 0) {
return 0; return 0;
} else { } else {
@@ -789,14 +789,14 @@ func TestRecursiveFunctions(t *testing.T) {
}, },
{ {
input: ` input: `
let countDown = fn(x) { countDown := fn(x) {
if (x == 0) { if (x == 0) {
return 0; return 0;
} else { } else {
return countDown(x - 1); return countDown(x - 1);
} }
}; };
let wrapper = fn() { wrapper := fn() {
return countDown(1); return countDown(1);
}; };
wrapper(); wrapper();
@@ -805,8 +805,8 @@ func TestRecursiveFunctions(t *testing.T) {
}, },
{ {
input: ` input: `
let wrapper = fn() { wrapper := fn() {
let countDown = fn(x) { countDown := fn(x) {
if (x == 0) { if (x == 0) {
return 0; return 0;
} else { } else {
@@ -828,7 +828,7 @@ func TestRecursiveFibonacci(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let fibonacci = fn(x) { fibonacci := fn(x) {
if (x == 0) { if (x == 0) {
return 0; return 0;
} else { } else {
@@ -851,14 +851,14 @@ func TestRecursiveFibonacci(t *testing.T) {
func TestIterations(t *testing.T) { func TestIterations(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{"while (false) { }", nil}, {"while (false) { }", nil},
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, {"n := 0; while (n < 10) { n := n + 1 }; n", 10},
{"let n = 10; while (n > 0) { let n = n - 1 }; n", 0}, {"n := 10; while (n > 0) { n := n - 1 }; n", 0},
{"let n = 0; while (n < 10) { let n = n + 1 }", nil}, {"n := 0; while (n < 10) { n := n + 1 }", nil},
{"let n = 10; while (n > 0) { let n = n - 1 }", nil}, {"n := 10; while (n > 0) { n := n - 1 }", nil},
{"let n = 0; while (n < 10) { n = n + 1 }; n", 10}, {"n := 0; while (n < 10) { n = n + 1 }; n", 10},
{"let n = 10; while (n > 0) { n = n - 1 }; n", 0}, {"n := 10; while (n > 0) { n = n - 1 }; n", 0},
{"let n = 0; while (n < 10) { n = n + 1 }", nil}, {"n := 0; while (n < 10) { n = n + 1 }", nil},
{"let n = 10; while (n > 0) { n = n - 1 }", nil}, {"n := 10; while (n > 0) { n = n - 1 }", nil},
} }
runVmTests(t, tests) runVmTests(t, tests)
@@ -866,7 +866,7 @@ func TestIterations(t *testing.T) {
func TestIndexAssignmentStatements(t *testing.T) { func TestIndexAssignmentStatements(t *testing.T) {
tests := []vmTestCase{ 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) runVmTests(t, tests)
@@ -874,20 +874,20 @@ func TestIndexAssignmentStatements(t *testing.T) {
func TestAssignmentExpressions(t *testing.T) { func TestAssignmentExpressions(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{"let a = 0; a = 5;", nil}, {"a := 0; a = 5;", nil},
{"let a = 0; a = 5; a;", 5}, {"a := 0; a = 5; a;", 5},
{"let a = 0; a = 5 * 5;", nil}, {"a := 0; a = 5 * 5;", nil},
{"let a = 0; a = 5 * 5; a;", 25}, {"a := 0; a = 5 * 5; a;", 25},
{"let a = 0; a = 5; let b = 0; b = a;", nil}, {"a := 0; a = 5; b := 0; b = a;", nil},
{"let a = 0; a = 5; let b = 0; b = a; b;", 5}, {"a := 0; a = 5; b := 0; b = a; b;", 5},
{"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5;", nil}, {"a := 0; a = 5; b := 0; b = a; 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}, {"a := 0; a = 5; b := 0; b = a; c := 0; c = a + b + 5; c;", 15},
{"let a = 5; let b = a; a = 0;", nil}, {"a := 5; b := a; a = 0;", nil},
{"let a = 5; let b = a; a = 0; b;", 5}, {"a := 5; b := a; a = 0; b;", 5},
{"let one = 0; one = 1", nil}, {"one := 0; one = 1", nil},
{"let one = 0; one = 1; one", 1}, {"one := 0; one = 1; one", 1},
{"let one = 0; one = 1; let two = 0; two = 2; one + two", 3}, {"one := 0; one = 1; two := 0; two = 2; one + two", 3},
{"let one = 0; one = 1; let two = 0; two = one + one; one + two", 3}, {"one := 0; one = 1; two := 0; two = one + one; one + two", 3},
} }
runVmTests(t, tests) runVmTests(t, tests)
@@ -897,7 +897,7 @@ func TestTailCalls(t *testing.T) {
tests := []vmTestCase{ tests := []vmTestCase{
{ {
input: ` input: `
let fact = fn(n, a) { fact := fn(n, a) {
if (n == 0) { if (n == 0) {
return a return a
} }
@@ -912,7 +912,7 @@ func TestTailCalls(t *testing.T) {
// without tail recursion optimization this will cause a stack overflow // without tail recursion optimization this will cause a stack overflow
{ {
input: ` input: `
let iter = fn(n, max) { iter := fn(n, max) {
if (n == max) { if (n == max) {
return n return n
} }
@@ -971,14 +971,14 @@ func TestIntegration(t *testing.T) {
func BenchmarkFibonacci(b *testing.B) { func BenchmarkFibonacci(b *testing.B) {
tests := map[string]string{ tests := map[string]string{
"iterative": ` "iterative": `
let fib = fn(n) { fib := fn(n) {
if (n < 3) { if (n < 3) {
return 1 return 1
} }
let a = 1 a := 1
let b = 1 b := 1
let c = 0 c := 0
let i = 0 i := 0
while (i < n - 2) { while (i < n - 2) {
c = a + b c = a + b
b = a b = a
@@ -991,7 +991,7 @@ func BenchmarkFibonacci(b *testing.B) {
fib(35) fib(35)
`, `,
"recursive": ` "recursive": `
let fib = fn(x) { fib := fn(x) {
if (x == 0) { if (x == 0) {
return 0 return 0
} }
@@ -1004,7 +1004,7 @@ func BenchmarkFibonacci(b *testing.B) {
fib(35) fib(35)
`, `,
"tail-recursive": ` "tail-recursive": `
let fib = fn(n, a, b) { fib := fn(n, a, b) {
if (n == 0) { if (n == 0) {
return a return a
} }