From 88e33308566cfcb71f625c241d33b4a7d08d669b Mon Sep 17 00:00:00 2001 From: Chuck Smith Date: Tue, 2 Apr 2024 14:32:03 -0400 Subject: [PATCH] optimizations --- internal/ast/ast.go | 15 ++++ internal/builtins/len.go | 3 + internal/compiler/compiler.go | 8 +- internal/compiler/compiler_test.go | 72 +++++++++++++++++ internal/evaluator/evaluator.go | 35 +++++++++ internal/evaluator/evaluator_test.go | 53 ++++++++++++- internal/lexer/lexer.go | 18 +++-- internal/lexer/lexer_test.go | 32 ++++++++ internal/object/float.go | 111 +++++++++++++++++++++++++++ internal/object/object.go | 3 + internal/parser/parser.go | 16 ++++ internal/parser/parser_test.go | 31 ++++++++ internal/token/token.go | 6 +- internal/vm/frame.go | 4 - internal/vm/vm.go | 90 ++++++++++++++++++++++ internal/vm/vm_test.go | 15 ++++ profile.go | 14 ++-- repl.go | 15 ++-- 18 files changed, 510 insertions(+), 31 deletions(-) create mode 100644 internal/object/float.go diff --git a/internal/ast/ast.go b/internal/ast/ast.go index 0f61463..afe9cc2 100644 --- a/internal/ast/ast.go +++ b/internal/ast/ast.go @@ -127,6 +127,7 @@ func (b *Boolean) expressionNode() {} func (b *Boolean) TokenLiteral() string { return b.Token.Literal } func (b *Boolean) String() string { return b.Token.Literal } +// IntegerLiteral represents a literal integer number and holds an int64 value type IntegerLiteral struct { Token token.Token Value int64 @@ -136,6 +137,20 @@ func (il *IntegerLiteral) expressionNode() {} func (il *IntegerLiteral) TokenLiteral() string { return il.Token.Literal } func (il *IntegerLiteral) String() string { return il.Token.Literal } +// FloatLiteral represents a literal floating point number and holds an float64 value +type FloatLiteral struct { + Token token.Token + Value float64 +} + +func (fl *FloatLiteral) expressionNode() {} + +// TokenLiteral prints the literal value of the token associated with this node +func (fl *FloatLiteral) TokenLiteral() string { return fl.Token.Literal } + +// String returns a stringified version of the AST for debugging +func (fl *FloatLiteral) String() string { return fl.Token.Literal } + type PrefixExpression struct { Token token.Token // The prefix token, e.g. ! Operator string diff --git a/internal/builtins/len.go b/internal/builtins/len.go index 80e14f7..a93d412 100644 --- a/internal/builtins/len.go +++ b/internal/builtins/len.go @@ -23,6 +23,9 @@ func Len(ctx context.Context, args ...object.Object) object.Object { case *object.Hash: return object.Integer{Value: int64(obj.Len())} default: + if obj == nil { + return newError("TypeError: object is nil") + } return newError("TypeError: object of type '%s' has no len()", args[0].Type()) } } diff --git a/internal/compiler/compiler.go b/internal/compiler/compiler.go index 776641c..a217210 100644 --- a/internal/compiler/compiler.go +++ b/internal/compiler/compiler.go @@ -173,8 +173,12 @@ func (c *Compiler) Compile(node ast.Node) error { } case *ast.IntegerLiteral: - integer := object.Integer{Value: node.Value} - c.emit(code.OpConstant, c.addConstant(integer)) + i := object.Integer{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(i)) + + case *ast.FloatLiteral: + f := object.Float{Value: node.Value} + c.emit(code.OpConstant, c.addConstant(f)) case *ast.Null: c.emit(code.OpNull) diff --git a/internal/compiler/compiler_test.go b/internal/compiler/compiler_test.go index 2c98948..156b242 100644 --- a/internal/compiler/compiler_test.go +++ b/internal/compiler/compiler_test.go @@ -165,6 +165,78 @@ func TestIntegerArithmetic(t *testing.T) { runCompilerTests(t, tests) } +func TestFloatingPointArithmetic(t *testing.T) { + tests := []compilerTestCase{ + { + input: "1.2 + 2.3", + expectedConstants: []interface{}{1.2, 2.3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpAdd), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + { + input: "1.2; 2.3", + expectedConstants: []interface{}{1.2, 2.3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpPop), + code.Make(code.OpConstant, 1), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + { + input: "2.5 - 0.5", + expectedConstants: []interface{}{2.5, 0.5}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpSub), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + { + input: "1.2 * 2.3", + expectedConstants: []interface{}{1.2, 2.3}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpMul), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + { + input: "5.0 / 2.0", + expectedConstants: []interface{}{5.0, 2.0}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpConstant, 1), + code.Make(code.OpDiv), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + { + input: "-2.5", + expectedConstants: []interface{}{2.5}, + expectedInstructions: []code.Instructions{ + code.Make(code.OpConstant, 0), + code.Make(code.OpMinus), + code.Make(code.OpPop), + code.Make(code.OpHalt), + }, + }, + } + + runCompilerTests(t, tests) +} + func TestBooleanExpressions(t *testing.T) { tests := []compilerTestCase{ { diff --git a/internal/evaluator/evaluator.go b/internal/evaluator/evaluator.go index c662304..5d0edfe 100644 --- a/internal/evaluator/evaluator.go +++ b/internal/evaluator/evaluator.go @@ -133,6 +133,9 @@ func Eval(ctx context.Context, node ast.Node, env *object.Environment) object.Ob case *ast.IntegerLiteral: return object.Integer{Value: node.Value} + case *ast.FloatLiteral: + return object.Float{Value: node.Value} + case *ast.Boolean: return nativeBoolToBooleanObject(node.Value) @@ -401,6 +404,8 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje return evalBooleanInfixExpression(operator, left, right) case left.Type() == object.IntegerType && right.Type() == object.IntegerType: return evalIntegerInfixExpression(operator, left, right) + case left.Type() == right.Type() && left.Type() == object.FloatType: + return evalFloatInfixExpression(operator, left, right) case left.Type() == object.StringType && right.Type() == object.StringType: return evalStringInfixExpression(operator, left, right) @@ -409,6 +414,36 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje } } +func evalFloatInfixExpression(operator string, left, right object.Object) object.Object { + leftVal := left.(object.Float).Value + rightVal := right.(object.Float).Value + + switch operator { + case "+": + return object.Float{Value: leftVal + rightVal} + case "-": + return object.Float{Value: leftVal - rightVal} + case "*": + return object.Float{Value: leftVal * rightVal} + case "/": + return object.Float{Value: leftVal / rightVal} + case "<": + return nativeBoolToBooleanObject(leftVal < rightVal) + case "<=": + return nativeBoolToBooleanObject(leftVal <= rightVal) + case ">": + return nativeBoolToBooleanObject(leftVal > rightVal) + case ">=": + return nativeBoolToBooleanObject(leftVal >= rightVal) + case "==": + return nativeBoolToBooleanObject(leftVal == rightVal) + case "!=": + return nativeBoolToBooleanObject(leftVal != rightVal) + default: + return NULL + } +} + func evalBooleanInfixExpression(operator string, left, right object.Object) object.Object { leftVal := left.(object.Boolean).Value rightVal := right.(object.Boolean).Value diff --git a/internal/evaluator/evaluator_test.go b/internal/evaluator/evaluator_test.go index a77eeaf..1f873db 100644 --- a/internal/evaluator/evaluator_test.go +++ b/internal/evaluator/evaluator_test.go @@ -57,7 +57,7 @@ func assertEvaluated(t *testing.T, expected interface{}, actual object.Object) { func TestEvalExpressions(t *testing.T) { tests := []struct { input string - expected interface{} + expected any }{ {"5", 5}, {"10", 10}, @@ -84,12 +84,15 @@ func TestEvalExpressions(t *testing.T) { {`4 * " "`, " "}, {"1 << 2", 4}, {"4 >> 2", 1}, + {"5.0 / 2.0", 2.5}, } for _, tt := range tests { evaluated := testEval(tt.input) if expected, ok := tt.expected.(int64); ok { testIntegerObject(t, evaluated, expected) + } else if expected, ok := tt.expected.(float64); ok { + testFloatObject(t, evaluated, expected) } else if expected, ok := tt.expected.(bool); ok { testBooleanObject(t, evaluated, expected) } @@ -405,17 +408,45 @@ func TestClosures(t *testing.T) { testIntegerObject(t, testEval(input), 4) } +func TestIntegerLiteral(t *testing.T) { + input := `2` + + evaluated := testEval(input) + obj, ok := evaluated.(object.Integer) + if !ok { + t.Fatalf("object is not Integer. got=%T (%+v)", evaluated, evaluated) + } + + if obj.Value != 2 { + t.Errorf("Integer has wrong value. got=%q", obj.Value) + } +} + +func TestFloatLiteral(t *testing.T) { + input := `2.5` + + evaluated := testEval(input) + obj, ok := evaluated.(object.Float) + if !ok { + t.Fatalf("object is not Float. got=%T (%+v)", evaluated, evaluated) + } + + if obj.Value != 2.5 { + t.Errorf("Float has wrong value. got=%f", obj.Value) + } +} + func TestStringLiteral(t *testing.T) { input := `"Hello World!"` evaluated := testEval(input) - str, ok := evaluated.(object.String) + obj, ok := evaluated.(object.String) if !ok { t.Fatalf("object is not String. got=%T (+%v)", evaluated, evaluated) } - if str.Value != "Hello World!" { - t.Errorf("String has wrong value. got=%q", str.Value) + if obj.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", obj.Value) } } @@ -803,6 +834,20 @@ func testEval(input string) object.Object { return Eval(context.New(), program, env) } +func testFloatObject(t *testing.T, obj object.Object, expected float64) bool { + result, ok := obj.(object.Float) + if !ok { + t.Errorf("object is not Float. got=%T (%+v)", obj, obj) + return false + } + if result.Value != expected { + t.Errorf("object has wrong value. got=%f, want=%f", + result.Value, expected) + return false + } + return true +} + func testIntegerObject(t *testing.T, obj object.Object, expected int64) bool { result, ok := obj.(object.Integer) if !ok { diff --git a/internal/lexer/lexer.go b/internal/lexer/lexer.go index 7fb3f5e..4ae25f3 100644 --- a/internal/lexer/lexer.go +++ b/internal/lexer/lexer.go @@ -182,9 +182,7 @@ func (l *Lexer) NextToken() token.Token { tok.Type = token.LookupIdent(tok.Literal) return tok } else if isDigit(l.ch) { - tok.Type = token.INT - tok.Literal = l.readNumber() - return tok + return l.readNumber() } else { tok = newToken(token.ILLEGAL, l.ch) } @@ -209,12 +207,22 @@ func (l *Lexer) readIdentifier() string { return l.input[position:l.position] } -func (l *Lexer) readNumber() string { +func (l *Lexer) readNumber() token.Token { position := l.position for isDigit(l.ch) { l.readChar() } - return l.input[position:l.position] + intPart := l.input[position:l.position] + if l.ch == '.' { + l.readChar() + position := l.position + for isDigit(l.ch) { + l.readChar() + } + fracPart := l.input[position:l.position] + return token.Token{Type: token.FLOAT, Literal: intPart + "." + fracPart} + } + return token.Token{Type: token.INT, Literal: intPart} } func isLetter(ch byte) bool { diff --git a/internal/lexer/lexer_test.go b/internal/lexer/lexer_test.go index 55b0b6d..04b339c 100644 --- a/internal/lexer/lexer_test.go +++ b/internal/lexer/lexer_test.go @@ -210,3 +210,35 @@ c := "\r\n\t" } } + +func TestFloats(t *testing.T) { + input := `2.5 +3.14 +` + + tests := []struct { + expectedType token.TokenType + expectedLiteral string + }{ + {token.FLOAT, "2.5"}, + {token.FLOAT, "3.14"}, + {token.EOF, ""}, + } + + lexer := New(input) + + for i, test := range tests { + token := lexer.NextToken() + + if token.Type != test.expectedType { + t.Fatalf("tests[%d] - token type wrong. expected=%q, got=%q", + i, test.expectedType, token.Type) + } + + if token.Literal != test.expectedLiteral { + t.Fatalf("tests[%d] - literal wrong. expected=%q, got=%q", + i, test.expectedLiteral, token.Literal) + } + } + +} diff --git a/internal/object/float.go b/internal/object/float.go new file mode 100644 index 0000000..82964f2 --- /dev/null +++ b/internal/object/float.go @@ -0,0 +1,111 @@ +package object + +import ( + "fmt" + "math" +) + +// Float is the floating-point number type used to represent float literals and holds +// an internal float64 value +type Float struct { + Value float64 +} + +func (f Float) Bool() bool { + return f.Value != 0 +} + +func (f Float) Add(other Object) (Object, error) { + switch obj := other.(type) { + case Float: + return Float{f.Value + obj.Value}, nil + default: + return nil, NewBinaryOpError(f, other, "+") + } +} + +func (f Float) Sub(other Object) (Object, error) { + switch obj := other.(type) { + case Float: + return Float{f.Value - obj.Value}, nil + default: + return nil, NewBinaryOpError(f, other, "-") + } +} + +func (f Float) Mul(other Object) (Object, error) { + switch obj := other.(type) { + case Float: + return Float{f.Value * obj.Value}, nil + case String: + return obj.Mul(f) + case *Array: + return obj.Mul(f) + default: + return nil, NewBinaryOpError(f, other, "*") + } +} + +func (f Float) Div(other Object) (Object, error) { + switch obj := other.(type) { + case Float: + if obj.Value == 0 { + return nil, NewDivisionByZeroError(f) + } + return Float{f.Value / obj.Value}, nil + default: + return nil, NewBinaryOpError(f, other, "/") + } +} + +func (f Float) Mod(other Object) (Object, error) { + switch obj := other.(type) { + case Float: + return Float{math.Mod(f.Value, obj.Value)}, nil + default: + return nil, NewBinaryOpError(f, other, "%") + } +} + +func (f Float) LogicalNot() Object { + return Boolean{!f.Bool()} +} + +func (f Float) Negate() Object { + return Float{-f.Value} +} + +func (f Float) Compare(other Object) int { + switch obj := other.(type) { + case Float: + if f.Value < obj.Value { + return -1 + } + if f.Value > obj.Value { + return 1 + } + return 0 + default: + return -1 + } +} + +func (f Float) String() string { + return f.Inspect() +} + +// Copy implements the Copyable interface +func (i Float) Copy() Object { + return Float{Value: i.Value} +} + +// Hash implements the Hasher interface +func (i Float) Hash() HashKey { + return HashKey{Type: i.Type(), Value: uint64(i.Value)} +} + +// Type returns the type of the object +func (i Float) Type() Type { return FloatType } + +// Inspect returns a stringified version of the object for debugging +func (i Float) Inspect() string { return fmt.Sprintf("%f", i.Value) } diff --git a/internal/object/object.go b/internal/object/object.go index b571648..38bbf29 100644 --- a/internal/object/object.go +++ b/internal/object/object.go @@ -11,6 +11,7 @@ type Type int const ( NullType = iota IntegerType + FloatType StringType BooleanType ReturnType @@ -30,6 +31,8 @@ func (t Type) String() string { return "null" case IntegerType: return "int" + case FloatType: + return "float" case StringType: return "str" case BooleanType: diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 537638d..344d3f0 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -82,6 +82,7 @@ func New(fn string, l *lexer.Lexer) *Parser { p.prefixParseFns = make(map[token.TokenType]prefixParseFn) p.registerPrefix(token.IDENT, p.parseIdentifier) p.registerPrefix(token.INT, p.parseIntegerLiteral) + p.registerPrefix(token.FLOAT, p.parseFloatLiteral) p.registerPrefix(token.MINUS, p.parsePrefixExpression) p.registerPrefix(token.TRUE, p.parseBoolean) p.registerPrefix(token.FALSE, p.parseBoolean) @@ -622,6 +623,21 @@ func (p *Parser) parseImportExpression() ast.Expression { return expression } +func (p *Parser) parseFloatLiteral() ast.Expression { + lit := &ast.FloatLiteral{Token: p.curToken} + + value, err := strconv.ParseFloat(p.curToken.Literal, 64) + if err != nil { + msg := fmt.Sprintf("could not parse %q as float", p.curToken.Literal) + p.errors = append(p.errors, msg) + return nil + } + + lit.Value = value + + return lit +} + // Parse parses the input source into a top-level AST for either evaluation // or compilation to bytecode. The parameter fn denotes the filename the source // originated from. diff --git a/internal/parser/parser_test.go b/internal/parser/parser_test.go index 9da3e18..95d01e0 100644 --- a/internal/parser/parser_test.go +++ b/internal/parser/parser_test.go @@ -126,6 +126,37 @@ func TestIntegerLiteralExpression(t *testing.T) { } } +func TestFloatLiteralExpression(t *testing.T) { + input := "2.5;" + + l := lexer.New(input) + p := New("", l) + program := p.ParseProgram() + checkParserErrors(t, p) + + if len(program.Statements) != 1 { + t.Fatalf("program has not enough statements. got=%d", + len(program.Statements)) + } + stmt, ok := program.Statements[0].(*ast.ExpressionStatement) + if !ok { + t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", + program.Statements[0]) + } + + literal, ok := stmt.Expression.(*ast.FloatLiteral) + if !ok { + t.Fatalf("exp not *ast.FloatLiteral. got=%T", stmt.Expression) + } + if literal.Value != 2.5 { + t.Errorf("literal.Value not %f. got=%f", 2.5, literal.Value) + } + if literal.TokenLiteral() != "2.5" { + t.Errorf("literal.TokenLiteral not %s. got=%s", "2.5", + literal.TokenLiteral()) + } +} + func TestParsingPrefixExpressions(t *testing.T) { prefixTests := []struct { input string diff --git a/internal/token/token.go b/internal/token/token.go index da6ff0e..2a020ed 100644 --- a/internal/token/token.go +++ b/internal/token/token.go @@ -15,8 +15,10 @@ const ( COMMENT = "COMMENT" // Identifiers + literals - IDENT = "IDENT" // add, foobar, x, y - INT = "INT" // 123456 + IDENT = "IDENT" // add, foobar, x, y + INT = "INT" // 123456 + // FLOAT is a floating point, e.g: 1.24 + FLOAT = "FLOAT" STRING = "STRING" // Operators diff --git a/internal/vm/frame.go b/internal/vm/frame.go index 3ce2d2f..5b3dc2d 100644 --- a/internal/vm/frame.go +++ b/internal/vm/frame.go @@ -55,7 +55,3 @@ func (f *frame) ReadUint16() uint16 { f.ip += 2 return n } - -func (f frame) Instructions() code.Instructions { - return f.cl.Fn.Instructions -} diff --git a/internal/vm/vm.go b/internal/vm/vm.go index 438f18a..a699443 100644 --- a/internal/vm/vm.go +++ b/internal/vm/vm.go @@ -2,6 +2,7 @@ package vm import ( "fmt" + "log" "monkey/internal/builtins" "monkey/internal/code" "monkey/internal/compiler" @@ -12,6 +13,8 @@ import ( "monkey/internal/utils" "os" "path/filepath" + "sort" + "time" "unicode" ) @@ -335,6 +338,12 @@ func (vm *VM) executeAdd() error { return err } return vm.push(val) + case object.Float: + val, err := obj.Add(right) + if err != nil { + return err + } + return vm.push(val) case object.String: val, err := obj.Add(right) if err != nil { @@ -368,6 +377,12 @@ func (vm *VM) executeSub() error { return err } return vm.push(val) + case object.Float: + val, err := obj.Sub(right) + if err != nil { + return err + } + return vm.push(val) default: return fmt.Errorf("unsupported types for unary operation: -%s", left.Type()) } @@ -389,6 +404,12 @@ func (vm *VM) executeMul() error { return err } return vm.push(val) + case object.Float: + val, err := obj.Mul(right) + if err != nil { + return err + } + return vm.push(val) case object.String: val, err := obj.Mul(right) if err != nil { @@ -410,6 +431,12 @@ func (vm *VM) executeDiv() error { return err } return vm.push(val) + case object.Float: + val, err := obj.Div(right) + if err != nil { + return err + } + return vm.push(val) default: return object.NewBinaryOpError(left, right, "/") } @@ -591,6 +618,8 @@ func (vm *VM) executeNot() error { return vm.push(obj.LogicalNot()) case object.Integer: return vm.push(obj.LogicalNot()) + case object.Float: + return vm.push(obj.LogicalNot()) case object.Null: return vm.push(obj.LogicalNot()) default: @@ -604,6 +633,8 @@ func (vm *VM) executeMinus() error { switch obj := left.(type) { case object.Integer: return vm.push(obj.Negate()) + case object.Float: + return vm.push(obj.Negate()) default: return fmt.Errorf("unsupported types for unary operation: -%s", left.Type()) } @@ -714,6 +745,7 @@ func (vm *VM) callClosure(cl *object.Closure, numArgs int) error { func (vm *VM) callBuiltin(builtin object.Builtin, numArgs int) error { args := vm.stack[vm.sp-numArgs : vm.sp] + log.Printf("args: %+v", args) result := builtin.Fn(vm.ctx, args...) vm.sp = vm.sp - numArgs - 1 @@ -764,10 +796,29 @@ func (vm *VM) LastPoppedStackElem() object.Object { } func (vm *VM) Run() (err error) { + start := time.Now() + freqs := make([]int, 255) for err == nil { op := vm.currentFrame().ReadNextOp() + if vm.debug { + freqs[op]++ + } + + if vm.trace { + log.Printf( + "%-25s M:%-20s\n", + fmt.Sprintf( + "%04d %s", vm.currentFrame().ip, op, + ), + fmt.Sprintf( + "[ip=%02d fp=%02d, sp=%02d]", + vm.currentFrame().ip, vm.fp-1, vm.sp, + ), + ) + } + switch op { case code.OpConstant: constIndex := vm.currentFrame().ReadUint16() @@ -925,6 +976,45 @@ func (vm *VM) Run() (err error) { default: err = fmt.Errorf("unhandled opcode: %s", op) } + + if vm.trace { + log.Printf( + "%-25s [ip=%02d fp=%02d, sp=%02d]", + "", vm.currentFrame().ip, vm.fp-1, vm.sp, + ) + var globals []object.Object + for _, obj := range vm.state.Globals { + if obj != nil { + globals = append(globals, obj) + } + } + log.Printf("%-25s G:%+v", "", globals) + log.Printf("%-25s S:%+v", "", vm.stack[:vm.sp]) + log.Printf("%-25s E:%+v", "", err) + } + } + + if vm.debug { + end := time.Now().Sub(start) + + total := 0 + opcodes := make([]code.Opcode, 0, len(freqs)) + + for i, count := range freqs { + opcode := code.Opcode(i) + opcodes = append(opcodes, opcode) + total += count + } + + sort.SliceStable(opcodes, func(i, j int) bool { + return freqs[opcodes[i]] > freqs[opcodes[j]] + }) + + log.Printf("%d instructions executed in %s %d/µs", total, end, total/int(end.Microseconds())) + log.Print("Top Instructions:") + for _, opcode := range opcodes[:min(len(opcodes), 10)] { + log.Printf("%10d %s", freqs[opcode], opcode) + } } return diff --git a/internal/vm/vm_test.go b/internal/vm/vm_test.go index 0fb5a2f..10459b6 100644 --- a/internal/vm/vm_test.go +++ b/internal/vm/vm_test.go @@ -242,6 +242,21 @@ func TestIntegerArithmetic(t *testing.T) { runVmTests(t, tests) } +func TestFloatingPointArithmetic(t *testing.T) { + tests := []vmTestCase{ + {"1.2", 1.2}, + {"2.3", 2.3}, + {"1.2 + 2.3", 3.5}, + {"1.2 - 2.4", -1.2}, + {"1.2 * 2.4", 2.88}, + {"5.0 / 2.0", 2.5}, + {"-2.5", -2.5}, + {"!1.0", false}, + } + + runVmTests(t, tests) +} + func TestBooleanExpressions(t *testing.T) { tests := []vmTestCase{ {"true", true}, diff --git a/profile.go b/profile.go index f67510d..d80ffb2 100644 --- a/profile.go +++ b/profile.go @@ -7,7 +7,6 @@ import ( "fmt" "log" "monkey/internal/compiler" - "monkey/internal/object" "monkey/internal/parser" "monkey/internal/vm" "os" @@ -24,7 +23,7 @@ fib := fn(n) { return fib(n-1) + fib(n-2) } -print(fib(%d))` +println(fib(%d))` func checkErr(err error) { if err != nil { @@ -39,13 +38,12 @@ func readFile(path string) string { return string(b) } -func fileOrDefault() string { +func fileOrDefault() (string, []string) { if flag.NArg() > 0 { log.Printf("Using %s as program input...", flag.Arg(0)) - object.Args = flag.Args()[1:] - return readFile(flag.Arg(0)) + return readFile(flag.Arg(0)), flag.Args()[1:] } - return defaultCode + return defaultCode, flag.Args()[:] } func main() { @@ -60,7 +58,7 @@ func main() { log.Printf("Using %d as n", *n) - code := fileOrDefault() + code, args := fileOrDefault() if strings.Count(code, "%d") > 0 { code = fmt.Sprintf(code, *n) } @@ -78,7 +76,7 @@ func main() { checkErr(c.Compile(tree)) checkErr(pprof.StartCPUProfile(cpuf)) defer pprof.StopCPUProfile() - mvm := vm.New("", c.Bytecode()) + mvm := vm.New("", c.Bytecode(), vm.WithContext(context.New(context.WithArgs(args)))) checkErr(mvm.Run()) runtime.GC() checkErr(pprof.WriteHeapProfile(heapf)) diff --git a/repl.go b/repl.go index 24a0d48..3bde071 100644 --- a/repl.go +++ b/repl.go @@ -7,6 +7,7 @@ import ( "io" "log" "os" + "runtime/debug" "strings" "golang.org/x/term" @@ -32,10 +33,11 @@ func REPL(opts *Options) error { return fmt.Errorf("error opening terminal: %w", err) } defer func() { - if err := recover(); err != nil { - log.Printf("recovered from panic: %s", err) - } term.Restore(0, initState) + if err := recover(); err != nil { + log.Printf("recovered from panic: %s\n", err) + log.Printf("stack trace:\n%s", debug.Stack()) + } }() atexit.Register(func() { term.Restore(0, initState) @@ -83,7 +85,7 @@ func REPL(opts *Options) error { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } - mvm := vm.New("", c.Bytecode(), opts.ToVMOptions()...) + mvm := vm.New("", c.Bytecode(), append(opts.ToVMOptions(), vm.WithState(state))...) if err := mvm.Run(); err != nil { fmt.Fprintf(t, "runtime error: %v\n", err) @@ -151,7 +153,8 @@ func SimpleREPL(opts *Options) { defer func() { if err := recover(); err != nil { - log.Printf("recovered from panic: %s", err) + log.Printf("recovered from panic: %s\n", err) + log.Printf("stack trace: %s\n", debug.Stack()) } }() @@ -196,7 +199,7 @@ func SimpleREPL(opts *Options) { log.Printf("Bytecode:\n%s\n", c.Bytecode()) } - mvm := vm.New("", c.Bytecode(), opts.ToVMOptions()...) + mvm := vm.New("", c.Bytecode(), append(opts.ToVMOptions(), vm.WithState(state))...) if err := mvm.Run(); err != nil { fmt.Printf("runtime error: %v\n", err)