reassign values
Some checks failed
Build / build (push) Failing after 1m46s
Test / build (push) Has been cancelled

This commit is contained in:
Chuck Smith
2024-03-15 15:24:48 -04:00
parent c818591f79
commit 2988719c9c
13 changed files with 327 additions and 17 deletions

View File

@@ -379,3 +379,29 @@ func (we *WhileExpression) String() string {
return out.String() return out.String()
} }
// AssignmentStatement the `=` statement represents the AST node that rebinds
// an expression to an identifier (assigning a new value).
type AssignmentStatement struct {
Token token.Token // the token.ASSIGN token
Name *Identifier
Value Expression
}
func (as AssignmentStatement) TokenLiteral() string {
return as.Token.Literal
}
func (as AssignmentStatement) String() string {
var out bytes.Buffer
out.WriteString(as.Name.String())
out.WriteString(as.TokenLiteral() + " ")
out.WriteString(as.Value.String())
out.WriteString(";")
return out.String()
}
func (as AssignmentStatement) statementNode() {}

View File

@@ -27,6 +27,7 @@ const (
OpJumpNotTruthy OpJumpNotTruthy
OpJump OpJump
OpNull OpNull
OpAssign
OpGetGlobal OpGetGlobal
OpSetGlobal OpSetGlobal
OpArray OpArray
@@ -66,6 +67,7 @@ var definitions = map[Opcode]*Definition{
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}}, OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
OpJump: {"OpJump", []int{2}}, OpJump: {"OpJump", []int{2}},
OpNull: {"OpNull", []int{}}, OpNull: {"OpNull", []int{}},
OpAssign: {"OpAssign", []int{}},
OpGetGlobal: {"OpGetGlobal", []int{2}}, OpGetGlobal: {"OpGetGlobal", []int{2}},
OpSetGlobal: {"OpSetGlobal", []int{2}}, OpSetGlobal: {"OpSetGlobal", []int{2}},
OpArray: {"OpArray", []int{2}}, OpArray: {"OpArray", []int{2}},

View File

@@ -198,6 +198,25 @@ func (c *Compiler) Compile(node ast.Node) error {
} }
} }
case *ast.AssignmentStatement:
symbol, ok := c.symbolTable.Resolve(node.Name.Value)
if !ok {
return fmt.Errorf("undefined variable %s", node.Value)
}
err := c.Compile(node.Value)
if err != nil {
return err
}
if symbol.Scope == GlobalScope {
c.emit(code.OpGetGlobal, symbol.Index)
} else {
c.emit(code.OpGetLocal, symbol.Index)
}
c.emit(code.OpAssign)
case *ast.LetStatement: case *ast.LetStatement:
symbol, ok := c.symbolTable.Resolve(node.Name.Value) symbol, ok := c.symbolTable.Resolve(node.Name.Value)
if !ok { if !ok {

View File

@@ -767,6 +767,102 @@ func TestFunctionCalls(t *testing.T) {
runCompilerTests(t, tests) runCompilerTests(t, tests)
} }
func TestAssignmentStatementScopes(t *testing.T) {
tests := []compilerTestCase{
{
input: `
let num = 0;
fn() { num = 55; }
`,
expectedConstants: []interface{}{
0,
55,
[]code.Instructions{
code.Make(code.OpConstant, 1),
code.Make(code.OpGetGlobal, 0),
code.Make(code.OpAssign),
code.Make(code.OpReturn),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpSetGlobal, 0),
code.Make(code.OpClosure, 2, 0),
code.Make(code.OpPop),
},
},
{
input: `
fn() {
let num = 55;
num
}
`,
expectedConstants: []interface{}{
55,
[]code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpSetLocal, 0),
code.Make(code.OpGetLocal, 0),
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpClosure, 1, 0),
code.Make(code.OpPop),
},
},
{
input: `
fn() {
let a = 55;
let b = 77;
a + b
}
`,
expectedConstants: []interface{}{
55,
77,
[]code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpSetLocal, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpSetLocal, 1),
code.Make(code.OpGetLocal, 0),
code.Make(code.OpGetLocal, 1),
code.Make(code.OpAdd),
code.Make(code.OpReturnValue),
},
},
expectedInstructions: []code.Instructions{
code.Make(code.OpClosure, 2, 0),
code.Make(code.OpPop),
},
},
{
input: `
let a = 0;
let a = a + 1;
`,
expectedConstants: []interface{}{
0,
1,
},
expectedInstructions: []code.Instructions{
code.Make(code.OpConstant, 0),
code.Make(code.OpSetGlobal, 0),
code.Make(code.OpGetGlobal, 0),
code.Make(code.OpConstant, 1),
code.Make(code.OpAdd),
code.Make(code.OpSetGlobal, 0),
},
},
}
runCompilerTests(t, tests)
}
func TestLetStatementScopes(t *testing.T) { func TestLetStatementScopes(t *testing.T) {
tests := []compilerTestCase{ tests := []compilerTestCase{
{ {

View File

@@ -45,6 +45,26 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
} }
return &object.ReturnValue{Value: val} return &object.ReturnValue{Value: val}
case *ast.AssignmentStatement:
ident := evalIdentifier(node.Name, env)
if isError(ident) {
return ident
}
val := Eval(node.Value, env)
if isError(val) {
return val
}
obj, ok := ident.(object.Mutable)
if !ok {
return newError("cannot assign to %s", ident.Type())
}
obj.Set(val)
return val
case *ast.LetStatement: case *ast.LetStatement:
val := Eval(node.Value, env) val := Eval(node.Value, env)
if isError(val) { if isError(val) {

View File

@@ -238,6 +238,26 @@ func TestErrorHandling(t *testing.T) {
} }
} }
func TestAssignmentStatements(t *testing.T) {
tests := []struct {
input string
expected int64
}{
{"let a = 0; a = 5;", 5},
{"let a = 0; a = 5; a;", 5},
{"let a = 0; a = 5 * 5;", 25},
{"let a = 0; a = 5 * 5; a;", 25},
{"let a = 0; a = 5; let b = 0; b = a;", 5},
{"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;", 15},
{"let a = 0; a = 5; let b = 0; b = a; let c = 0; c = a + b + 5; c;", 15},
}
for _, tt := range tests {
testIntegerObject(t, testEval(tt.input), tt.expected)
}
}
func TestLetStatements(t *testing.T) { func TestLetStatements(t *testing.T) {
tests := []struct { tests := []struct {
input string input string
@@ -560,10 +580,8 @@ func TestWhileExpressions(t *testing.T) {
{"while (false) { }", nil}, {"while (false) { }", nil},
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, {"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 = 10; while (n > 0) { let n = n - 1 }; n", 0},
// FIXME: let is an expression statement and bind new values {"let n = 0; while (n < 10) { n = n + 1 }", 10},
// there is currently no assignment expressions :/ {"let n = 10; while (n > 0) { n = n - 1 }", 0},
{"let n = 0; while (n < 10) { let n = n + 1 }", nil},
{"let n = 10; while (n > 0) { let n = n - 1 }", nil},
} }
for _, tt := range tests { for _, tt := range tests {

2
examples/foo.monkey Normal file
View File

@@ -0,0 +1,2 @@
let a = 0;
let a = a + 1;

View File

@@ -26,6 +26,12 @@ const (
CLOSURE_OBJ = "CLOSURE" CLOSURE_OBJ = "CLOSURE"
) )
// Mutable is the interface for all mutable objects which must implement
// the Set() method which rebinds its internal value for assignment statements
type Mutable interface {
Set(obj Object)
}
type Object interface { type Object interface {
Type() ObjectType Type() ObjectType
Inspect() string Inspect() string
@@ -47,6 +53,9 @@ func (i *Integer) Type() ObjectType {
func (i *Integer) Inspect() string { func (i *Integer) Inspect() string {
return fmt.Sprintf("%d", i.Value) return fmt.Sprintf("%d", i.Value)
} }
func (i *Integer) Set(obj Object) {
i.Value = obj.(*Integer).Value
}
type Boolean struct { type Boolean struct {
Value bool Value bool
@@ -55,10 +64,12 @@ type Boolean struct {
func (b *Boolean) Type() ObjectType { func (b *Boolean) Type() ObjectType {
return BOOLEAN_OBJ return BOOLEAN_OBJ
} }
func (b *Boolean) Inspect() string { func (b *Boolean) Inspect() string {
return fmt.Sprintf("%t", b.Value) return fmt.Sprintf("%t", b.Value)
} }
func (i *Boolean) Set(obj Object) {
i.Value = obj.(*Boolean).Value
}
type Null struct{} type Null struct{}
@@ -88,10 +99,12 @@ type Error struct {
func (e *Error) Type() ObjectType { func (e *Error) Type() ObjectType {
return ERROR_OBJ return ERROR_OBJ
} }
func (e *Error) Inspect() string { func (e *Error) Inspect() string {
return "Error: " + e.Message return "Error: " + e.Message
} }
func (e *Error) Set(obj Object) {
e.Message = obj.(*Error).Message
}
type Function struct { type Function struct {
Parameters []*ast.Identifier Parameters []*ast.Identifier
@@ -128,10 +141,12 @@ type String struct {
func (s *String) Type() ObjectType { func (s *String) Type() ObjectType {
return STRING_OBJ return STRING_OBJ
} }
func (s *String) Inspect() string { func (s *String) Inspect() string {
return s.Value return s.Value
} }
func (s *String) Set(obj Object) {
s.Value = obj.(*String).Value
}
type BuiltinFunction func(args ...Object) Object type BuiltinFunction func(args ...Object) Object
@@ -150,11 +165,10 @@ type Array struct {
Elements []Object Elements []Object
} }
func (ao Array) Type() ObjectType { func (ao *Array) Type() ObjectType {
return ARRAY_OBJ return ARRAY_OBJ
} }
func (ao *Array) Inspect() string {
func (ao Array) Inspect() string {
var out bytes.Buffer var out bytes.Buffer
elements := []string{} elements := []string{}
@@ -168,6 +182,9 @@ func (ao Array) Inspect() string {
return out.String() return out.String()
} }
func (ao *Array) Set(obj Object) {
ao.Elements = obj.(*Array).Elements
}
type HashKey struct { type HashKey struct {
Type ObjectType Type ObjectType
@@ -213,7 +230,6 @@ type Hash struct {
func (h *Hash) Type() ObjectType { func (h *Hash) Type() ObjectType {
return HASH_OBJ return HASH_OBJ
} }
func (h *Hash) Inspect() string { func (h *Hash) Inspect() string {
var out bytes.Buffer var out bytes.Buffer
@@ -228,6 +244,9 @@ func (h *Hash) Inspect() string {
return out.String() return out.String()
} }
func (h *Hash) Set(obj Object) {
h.Pairs = obj.(*Hash).Pairs
}
func (cf *CompiledFunction) Type() ObjectType { func (cf *CompiledFunction) Type() ObjectType {
return COMPILED_FUNCTION_OBJ return COMPILED_FUNCTION_OBJ

View File

@@ -127,6 +127,11 @@ func (p *Parser) noPrefixParseFnError(t token.TokenType) {
p.errors = append(p.errors, msg) p.errors = append(p.errors, msg)
} }
func (p *Parser) noInfixParseFnError(t token.TokenType) {
msg := fmt.Sprintf("no infix parse function for %s found", t)
p.errors = append(p.errors, msg)
}
func (p *Parser) ParseProgram() *ast.Program { func (p *Parser) ParseProgram() *ast.Program {
program := &ast.Program{} program := &ast.Program{}
program.Statements = []ast.Statement{} program.Statements = []ast.Statement{}
@@ -143,6 +148,10 @@ func (p *Parser) ParseProgram() *ast.Program {
} }
func (p *Parser) parseStatement() ast.Statement { func (p *Parser) parseStatement() ast.Statement {
if p.peekToken.Type == token.ASSIGN {
return p.parseAssignmentStatement()
}
switch p.curToken.Type { switch p.curToken.Type {
case token.LET: case token.LET:
return p.parseLetStatement() return p.parseLetStatement()
@@ -218,7 +227,8 @@ func (p *Parser) parseExpression(precedence int) ast.Expression {
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() { for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
infix := p.infixParseFns[p.peekToken.Type] infix := p.infixParseFns[p.peekToken.Type]
if infix == nil { if infix == nil {
return leftExp p.noInfixParseFnError(p.peekToken.Type)
//return leftExp
} }
p.nextToken() p.nextToken()
@@ -516,3 +526,19 @@ func (p *Parser) parseWhileExpression() ast.Expression {
return expression return expression
} }
func (p *Parser) parseAssignmentStatement() ast.Statement {
stmt := &ast.AssignmentStatement{Token: p.peekToken}
stmt.Name = &ast.Identifier{Token: p.curToken, Value: p.curToken.Literal}
p.nextToken()
p.nextToken()
stmt.Value = p.parseExpression(LOWEST)
if p.peekTokenIs(token.SEMICOLON) {
p.nextToken()
}
return stmt
}

View File

@@ -921,6 +921,40 @@ func TestWhileExpression(t *testing.T) {
} }
} }
func TestAssignmentStatements(t *testing.T) {
tests := []struct {
input string
expectedIdentifier string
expectedValue interface{}
}{
{"x = 5;", "x", 5},
{"y = true;", "y", true},
{"foobar = y;", "foobar", "y"},
}
for _, tt := range tests {
l := lexer.New(tt.input)
p := New(l)
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 !testAssignmentStatement(t, stmt, tt.expectedIdentifier) {
return
}
val := stmt.(*ast.AssignmentStatement).Value
if !testLiteralExpression(t, val, tt.expectedValue) {
return
}
}
}
func testLetStatement(t *testing.T, s ast.Statement, name string) bool { func testLetStatement(t *testing.T, s ast.Statement, name string) bool {
if s.TokenLiteral() != "let" { if s.TokenLiteral() != "let" {
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral()) t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())
@@ -1066,3 +1100,29 @@ func checkParserErrors(t *testing.T, p *Parser) {
} }
t.FailNow() t.FailNow()
} }
func testAssignmentStatement(t *testing.T, s ast.Statement, name string) bool {
if s.TokenLiteral() != "=" {
t.Errorf("s.TokenLiteral not '='. got=%q", s.TokenLiteral())
return false
}
assignStmt, ok := s.(*ast.AssignmentStatement)
if !ok {
t.Errorf("s not *ast.AssignmentStatement. got=%T", s)
return false
}
if assignStmt.Name.Value != name {
t.Errorf("assignStmt.Name.Value not '%s'. got=%s", name, assignStmt.Name.Value)
return false
}
if assignStmt.Name.TokenLiteral() != name {
t.Errorf("assignStmt.Name.TokenLiteral() not '%s'. got=%s",
name, assignStmt.Name.TokenLiteral())
return false
}
return true
}

View File

@@ -18,7 +18,7 @@ syntax keyword xKeyword let fn if else return while
syntax keyword xFunction len input print first last rest push pop exit syntax keyword xFunction len input print first last rest push pop exit
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

@@ -158,6 +158,17 @@ func (vm *VM) Run() error {
vm.globals[globalIndex] = vm.pop() vm.globals[globalIndex] = vm.pop()
case code.OpAssign:
ident := vm.pop()
val := vm.pop()
obj, ok := ident.(object.Mutable)
if !ok {
return fmt.Errorf("cannot assign to %s", ident.Type())
}
obj.Set(val)
case code.OpGetGlobal: case code.OpGetGlobal:
globalIndex := code.ReadUint16(ins[ip+1:]) globalIndex := code.ReadUint16(ins[ip+1:])
vm.currentFrame().ip += 2 vm.currentFrame().ip += 2

View File

@@ -801,10 +801,21 @@ func TestIterations(t *testing.T) {
{"while (false) { }", nil}, {"while (false) { }", nil},
{"let n = 0; while (n < 10) { let n = n + 1 }; n", 10}, {"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 = 10; while (n > 0) { let n = n - 1 }; n", 0},
// FIXME: let is an expression statement and bind new values {"let n = 0; while (n < 10) { n = n + 1 }; n", 10},
// there is currently no assignment expressions :/ {"let n = 10; while (n > 0) { n = n - 1 }; n", 0},
{"let n = 0; while (n < 10) { let n = n + 1 }", nil}, {"let n = 0; while (n < 10) { n = n + 1 }", nil},
{"let n = 10; while (n > 0) { let n = n - 1 }", nil}, {"let n = 10; while (n > 0) { n = n - 1 }", nil},
}
runVmTests(t, tests)
}
func TestAssignmentStatements(t *testing.T) {
tests := []vmTestCase{
{"let one = 0; one = 1", 1},
{"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},
} }
runVmTests(t, tests) runVmTests(t, tests)