optimizations
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
111
internal/object/float.go
Normal file
111
internal/object/float.go
Normal file
@@ -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) }
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -126,6 +126,37 @@ func TestIntegerLiteralExpression(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFloatLiteralExpression(t *testing.T) {
|
||||
input := "2.5;"
|
||||
|
||||
l := lexer.New(input)
|
||||
p := New("<test>", 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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},
|
||||
|
||||
14
profile.go
14
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("<profiler>", c.Bytecode())
|
||||
mvm := vm.New("<profiler>", c.Bytecode(), vm.WithContext(context.New(context.WithArgs(args))))
|
||||
checkErr(mvm.Run())
|
||||
runtime.GC()
|
||||
checkErr(pprof.WriteHeapProfile(heapf))
|
||||
|
||||
15
repl.go
15
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("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
|
||||
mvm := vm.New("<stdin>", 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("<stdin>", c.Bytecode(), opts.ToVMOptions()...)
|
||||
mvm := vm.New("<stdin>", c.Bytecode(), append(opts.ToVMOptions(), vm.WithState(state))...)
|
||||
|
||||
if err := mvm.Run(); err != nil {
|
||||
fmt.Printf("runtime error: %v\n", err)
|
||||
|
||||
Reference in New Issue
Block a user