reassign values
This commit is contained in:
26
ast/ast.go
26
ast/ast.go
@@ -379,3 +379,29 @@ func (we *WhileExpression) String() 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() {}
|
||||
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
OpJumpNotTruthy
|
||||
OpJump
|
||||
OpNull
|
||||
OpAssign
|
||||
OpGetGlobal
|
||||
OpSetGlobal
|
||||
OpArray
|
||||
@@ -66,6 +67,7 @@ var definitions = map[Opcode]*Definition{
|
||||
OpJumpNotTruthy: {"OpJumpNotTruthy", []int{2}},
|
||||
OpJump: {"OpJump", []int{2}},
|
||||
OpNull: {"OpNull", []int{}},
|
||||
OpAssign: {"OpAssign", []int{}},
|
||||
OpGetGlobal: {"OpGetGlobal", []int{2}},
|
||||
OpSetGlobal: {"OpSetGlobal", []int{2}},
|
||||
OpArray: {"OpArray", []int{2}},
|
||||
|
||||
@@ -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:
|
||||
symbol, ok := c.symbolTable.Resolve(node.Name.Value)
|
||||
if !ok {
|
||||
|
||||
@@ -767,6 +767,102 @@ func TestFunctionCalls(t *testing.T) {
|
||||
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) {
|
||||
tests := []compilerTestCase{
|
||||
{
|
||||
|
||||
@@ -45,6 +45,26 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
|
||||
}
|
||||
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:
|
||||
val := Eval(node.Value, env)
|
||||
if isError(val) {
|
||||
|
||||
@@ -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) {
|
||||
tests := []struct {
|
||||
input string
|
||||
@@ -560,10 +580,8 @@ func TestWhileExpressions(t *testing.T) {
|
||||
{"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},
|
||||
// FIXME: let is an expression statement and bind new values
|
||||
// there is currently no assignment expressions :/
|
||||
{"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 }", 10},
|
||||
{"let n = 10; while (n > 0) { n = n - 1 }", 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
2
examples/foo.monkey
Normal file
2
examples/foo.monkey
Normal file
@@ -0,0 +1,2 @@
|
||||
let a = 0;
|
||||
let a = a + 1;
|
||||
@@ -26,6 +26,12 @@ const (
|
||||
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() ObjectType
|
||||
Inspect() string
|
||||
@@ -47,6 +53,9 @@ func (i *Integer) Type() ObjectType {
|
||||
func (i *Integer) Inspect() string {
|
||||
return fmt.Sprintf("%d", i.Value)
|
||||
}
|
||||
func (i *Integer) Set(obj Object) {
|
||||
i.Value = obj.(*Integer).Value
|
||||
}
|
||||
|
||||
type Boolean struct {
|
||||
Value bool
|
||||
@@ -55,10 +64,12 @@ type Boolean struct {
|
||||
func (b *Boolean) Type() ObjectType {
|
||||
return BOOLEAN_OBJ
|
||||
}
|
||||
|
||||
func (b *Boolean) Inspect() string {
|
||||
return fmt.Sprintf("%t", b.Value)
|
||||
}
|
||||
func (i *Boolean) Set(obj Object) {
|
||||
i.Value = obj.(*Boolean).Value
|
||||
}
|
||||
|
||||
type Null struct{}
|
||||
|
||||
@@ -88,10 +99,12 @@ type Error struct {
|
||||
func (e *Error) Type() ObjectType {
|
||||
return ERROR_OBJ
|
||||
}
|
||||
|
||||
func (e *Error) Inspect() string {
|
||||
return "Error: " + e.Message
|
||||
}
|
||||
func (e *Error) Set(obj Object) {
|
||||
e.Message = obj.(*Error).Message
|
||||
}
|
||||
|
||||
type Function struct {
|
||||
Parameters []*ast.Identifier
|
||||
@@ -128,10 +141,12 @@ type String struct {
|
||||
func (s *String) Type() ObjectType {
|
||||
return STRING_OBJ
|
||||
}
|
||||
|
||||
func (s *String) Inspect() string {
|
||||
return s.Value
|
||||
}
|
||||
func (s *String) Set(obj Object) {
|
||||
s.Value = obj.(*String).Value
|
||||
}
|
||||
|
||||
type BuiltinFunction func(args ...Object) Object
|
||||
|
||||
@@ -150,11 +165,10 @@ type Array struct {
|
||||
Elements []Object
|
||||
}
|
||||
|
||||
func (ao Array) Type() ObjectType {
|
||||
func (ao *Array) Type() ObjectType {
|
||||
return ARRAY_OBJ
|
||||
}
|
||||
|
||||
func (ao Array) Inspect() string {
|
||||
func (ao *Array) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
elements := []string{}
|
||||
@@ -168,6 +182,9 @@ func (ao Array) Inspect() string {
|
||||
|
||||
return out.String()
|
||||
}
|
||||
func (ao *Array) Set(obj Object) {
|
||||
ao.Elements = obj.(*Array).Elements
|
||||
}
|
||||
|
||||
type HashKey struct {
|
||||
Type ObjectType
|
||||
@@ -213,7 +230,6 @@ type Hash struct {
|
||||
func (h *Hash) Type() ObjectType {
|
||||
return HASH_OBJ
|
||||
}
|
||||
|
||||
func (h *Hash) Inspect() string {
|
||||
var out bytes.Buffer
|
||||
|
||||
@@ -228,6 +244,9 @@ func (h *Hash) Inspect() string {
|
||||
|
||||
return out.String()
|
||||
}
|
||||
func (h *Hash) Set(obj Object) {
|
||||
h.Pairs = obj.(*Hash).Pairs
|
||||
}
|
||||
|
||||
func (cf *CompiledFunction) Type() ObjectType {
|
||||
return COMPILED_FUNCTION_OBJ
|
||||
|
||||
@@ -127,6 +127,11 @@ func (p *Parser) noPrefixParseFnError(t token.TokenType) {
|
||||
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 {
|
||||
program := &ast.Program{}
|
||||
program.Statements = []ast.Statement{}
|
||||
@@ -143,6 +148,10 @@ func (p *Parser) ParseProgram() *ast.Program {
|
||||
}
|
||||
|
||||
func (p *Parser) parseStatement() ast.Statement {
|
||||
if p.peekToken.Type == token.ASSIGN {
|
||||
return p.parseAssignmentStatement()
|
||||
}
|
||||
|
||||
switch p.curToken.Type {
|
||||
case token.LET:
|
||||
return p.parseLetStatement()
|
||||
@@ -218,7 +227,8 @@ func (p *Parser) parseExpression(precedence int) ast.Expression {
|
||||
for !p.peekTokenIs(token.SEMICOLON) && precedence < p.peekPrecedence() {
|
||||
infix := p.infixParseFns[p.peekToken.Type]
|
||||
if infix == nil {
|
||||
return leftExp
|
||||
p.noInfixParseFnError(p.peekToken.Type)
|
||||
//return leftExp
|
||||
}
|
||||
|
||||
p.nextToken()
|
||||
@@ -516,3 +526,19 @@ func (p *Parser) parseWhileExpression() ast.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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
if s.TokenLiteral() != "let" {
|
||||
t.Errorf("s.TokenLiteral not 'let'. got=%q", s.TokenLiteral())
|
||||
@@ -1066,3 +1100,29 @@ func checkParserErrors(t *testing.T, p *Parser) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@@ -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 xOperator == != < > !
|
||||
syntax keyword xOperator + - * /
|
||||
syntax keyword xOperator + - * / =
|
||||
|
||||
syntax region xString start=/"/ skip=/\\./ end=/"/
|
||||
|
||||
|
||||
11
vm/vm.go
11
vm/vm.go
@@ -158,6 +158,17 @@ func (vm *VM) Run() error {
|
||||
|
||||
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:
|
||||
globalIndex := code.ReadUint16(ins[ip+1:])
|
||||
vm.currentFrame().ip += 2
|
||||
|
||||
@@ -801,10 +801,21 @@ func TestIterations(t *testing.T) {
|
||||
{"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},
|
||||
// FIXME: let is an expression statement and bind new values
|
||||
// there is currently no assignment expressions :/
|
||||
{"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},
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user