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()
}
// 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 {

View File

@@ -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())
}

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)
}
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:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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",
},
{

View File

@@ -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)

View File

@@ -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);

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

@@ -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()

View File

@@ -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, ""},
}

View File

@@ -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
}

View File

@@ -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<add>(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 {

View File

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

View File

@@ -1,3 +1,3 @@
let x = 1
x := 1
x = 2
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)
first(xs)
@@ -9,5 +9,5 @@ pop(xs)
len("foo")
let x = input()
x := input()
print(x)

View File

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

View File

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

View File

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

2
testdata/if.monkey vendored
View File

@@ -1,4 +1,4 @@
let x = 1
x := 1
if (x == 1) {
x = 2
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.bar == 2, "d.bar != 2")

View File

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

View File

@@ -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,

View File

@@ -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=/"/

View File

@@ -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:])

View File

@@ -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
}