Skip to content

Commit f20cf2a

Browse files
kaidesuclaude
andauthored
replace decimal.Decimal with dual int64/float64 number representation (#138)
Removes the shopspring/decimal dependency and replaces all number handling with a dual int64/float64 internal representation (Lua 5.3 style). Integer operations stay int64, float operations use float64, and division always promotes to float (Python 3 style). This eliminates the massive allocation overhead from arbitrary-precision math in hot loops — the primary performance bottleneck for game engine workloads in Lumen. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 10ac313 commit f20cf2a

22 files changed

Lines changed: 384 additions & 194 deletions

ast/number.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ package ast
22

33
import (
44
"ghostlang.org/x/ghost/token"
5-
"github.com/shopspring/decimal"
65
)
76

87
type Number struct {
98
ExpressionNode
10-
Token token.Token
11-
Value decimal.Decimal
9+
Token token.Token
10+
IntValue int64
11+
FloatValue float64
12+
IsFloat bool
1213
}

evaluator/assign.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func evaluateIndexAssignment(node *ast.Index, assignmentValue object.Object, sco
4444

4545
switch obj := left.(type) {
4646
case *object.List:
47-
idx := int(index.(*object.Number).Value.IntPart())
47+
idx := int(index.(*object.Number).Int64())
4848
elements := obj.Elements
4949

5050
if idx < 0 {

evaluator/evaluator_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,8 @@ func isNumberObject(t *testing.T, obj object.Object, expected int64) bool {
552552
return false
553553
}
554554

555-
if number.Value.IntPart() != expected {
556-
t.Errorf("object has wrong value. got=%d, expected=%d", number.Value.IntPart(), expected)
555+
if number.Int64() != expected {
556+
t.Errorf("object has wrong value. got=%d, expected=%d", number.Int64(), expected)
557557
return false
558558
}
559559

evaluator/for_in.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package evaluator
33
import (
44
"ghostlang.org/x/ghost/ast"
55
"ghostlang.org/x/ghost/object"
6-
"github.com/shopspring/decimal"
76
)
87

98
func evaluateForIn(node *ast.ForIn, scope *object.Scope) object.Object {
@@ -33,7 +32,7 @@ func evaluateForIn(node *ast.ForIn, scope *object.Scope) object.Object {
3332
switch obj := iterable.(type) {
3433
case *object.List:
3534
for k, v := range obj.Elements {
36-
scope.Environment.Set(node.Key.Value, &object.Number{Value: decimal.NewFromInt(int64(k))})
35+
scope.Environment.Set(node.Key.Value, object.NewInt(int64(k)))
3736
scope.Environment.Set(node.Value.Value, v)
3837

3938
block := Evaluate(node.Block, scope)

evaluator/index.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func evaluateIndex(node *ast.Index, scope *object.Scope) object.Object {
3333

3434
func evaluateListIndex(node *ast.Index, left, index object.Object) object.Object {
3535
list := left.(*object.List)
36-
idx := index.(*object.Number).Value.IntPart()
36+
idx := index.(*object.Number).Int64()
3737
max := int64(len(list.Elements) - 1)
3838

3939
if idx < 0 || idx > max {
@@ -63,7 +63,7 @@ func evaluateMapIndex(node *ast.Index, left, index object.Object) object.Object
6363

6464
func evaluateStringIndex(node *ast.Index, left, index object.Object) object.Object {
6565
str := left.(*object.String)
66-
idx := index.(*object.Number).Value.IntPart()
66+
idx := index.(*object.Number).Int64()
6767
max := int64(len(str.Value) - 1)
6868

6969
if idx < 0 || idx > max {

evaluator/number.go

Lines changed: 24 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,57 +3,54 @@ package evaluator
33
import (
44
"ghostlang.org/x/ghost/ast"
55
"ghostlang.org/x/ghost/object"
6-
"github.com/shopspring/decimal"
76
)
87

98
func evaluateNumber(node *ast.Number, scope *object.Scope) object.Object {
10-
return &object.Number{Value: node.Value}
9+
if node.IsFloat {
10+
return object.NewFloat(node.FloatValue)
11+
}
12+
return object.NewInt(node.IntValue)
1113
}
1214

1315
func evaluateNumberInfix(node *ast.Infix, left object.Object, right object.Object) object.Object {
14-
leftValue := left.(*object.Number).Value
15-
rightValue := right.(*object.Number).Value
16+
leftNum := left.(*object.Number)
17+
rightNum := right.(*object.Number)
1618

1719
switch node.Operator {
1820
case "+":
19-
return &object.Number{Value: leftValue.Add(rightValue)}
21+
return leftNum.Add(rightNum)
2022
case "-":
21-
return &object.Number{Value: leftValue.Sub(rightValue)}
23+
return leftNum.Sub(rightNum)
2224
case "*":
23-
return &object.Number{Value: leftValue.Mul(rightValue)}
25+
return leftNum.Mul(rightNum)
2426
case "/":
25-
return &object.Number{Value: leftValue.Div(rightValue)}
27+
return leftNum.Div(rightNum)
2628
case "%":
27-
return &object.Number{Value: leftValue.Mod(rightValue)}
29+
return leftNum.Mod(rightNum)
2830
case "<":
29-
return toBooleanValue(leftValue.LessThan(rightValue))
31+
return toBooleanValue(leftNum.LessThan(rightNum))
3032
case "<=":
31-
return toBooleanValue(leftValue.LessThanOrEqual(rightValue))
33+
return toBooleanValue(leftNum.LessThanOrEqual(rightNum))
3234
case ">":
33-
return toBooleanValue(leftValue.GreaterThan(rightValue))
35+
return toBooleanValue(leftNum.GreaterThan(rightNum))
3436
case ">=":
35-
return toBooleanValue(leftValue.GreaterThanOrEqual(rightValue))
37+
return toBooleanValue(leftNum.GreaterThanOrEqual(rightNum))
3638
case "==":
37-
return toBooleanValue(leftValue.Equal(rightValue))
39+
return toBooleanValue(leftNum.Equal(rightNum))
3840
case "!=":
39-
return toBooleanValue(!leftValue.Equal(rightValue))
41+
return toBooleanValue(!leftNum.Equal(rightNum))
4042
case "..":
41-
numbers := make([]object.Object, 0)
42-
one := decimal.NewFromInt(1)
43-
number := leftValue
43+
start := leftNum.Int64()
44+
end := rightNum.Int64()
4445

45-
if leftValue.GreaterThan(rightValue) {
46-
return &object.List{Elements: numbers}
46+
if start > end {
47+
return &object.List{Elements: []object.Object{}}
4748
}
4849

49-
for {
50-
numbers = append(numbers, &object.Number{Value: number})
51-
52-
if number.GreaterThanOrEqual(rightValue) {
53-
break
54-
}
50+
numbers := make([]object.Object, 0, end-start+1)
5551

56-
number = number.Add(one)
52+
for i := start; i <= end; i++ {
53+
numbers = append(numbers, object.NewInt(i))
5754
}
5855

5956
return &object.List{Elements: numbers}

evaluator/postfix.go

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package evaluator
33
import (
44
"ghostlang.org/x/ghost/ast"
55
"ghostlang.org/x/ghost/object"
6-
"github.com/shopspring/decimal"
76
)
87

98
func evaluatePostfix(node *ast.Postfix, scope *object.Scope) object.Object {
@@ -19,11 +18,7 @@ func evaluatePostfix(node *ast.Postfix, scope *object.Scope) object.Object {
1918
return newError("%d:%d:%s: runtime error: identifier is not a number: %s", node.Token.Line, node.Token.Column, node.Token.File, node.Token.Lexeme)
2019
}
2120

22-
one := decimal.NewFromInt(1)
23-
24-
newValue := &object.Number{
25-
Value: value.(*object.Number).Value.Add(one),
26-
}
21+
newValue := value.(*object.Number).Increment()
2722

2823
scope.Environment.Set(node.Token.Lexeme, newValue)
2924

@@ -39,11 +34,7 @@ func evaluatePostfix(node *ast.Postfix, scope *object.Scope) object.Object {
3934
return newError("%d:%d:%s: runtime error: identifier is not a number: %s", node.Token.Line, node.Token.Column, node.Token.File, node.Token.Lexeme)
4035
}
4136

42-
one := decimal.NewFromInt(1)
43-
44-
newValue := &object.Number{
45-
Value: value.(*object.Number).Value.Sub(one),
46-
}
37+
newValue := value.(*object.Number).Decrement()
4738

4839
scope.Environment.Set(node.Token.Lexeme, newValue)
4940

evaluator/prefix.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,7 @@ func evaluatePrefix(node *ast.Prefix, scope *object.Scope) object.Object {
3131
return newError("%d:%d:%s: runtime error: unknown operator: -%s", node.Token.Line, node.Token.Column, node.Token.File, right.Type())
3232
}
3333

34-
numberValue := right.(*object.Number).Value.Neg()
35-
36-
return &object.Number{Value: numberValue}
34+
return right.(*object.Number).Neg()
3735
}
3836

3937
return newError("%d:%d:%s: runtime error: unknown operator: %s%s", node.Token.Line, node.Token.Column, node.Token.File, node.Operator, right.Type())

go.mod

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ module ghostlang.org/x/ghost
22

33
go 1.21.1
44

5-
require (
6-
github.com/peterh/liner v1.2.1
7-
github.com/shopspring/decimal v1.3.1
8-
)
5+
require github.com/peterh/liner v1.2.1
96

107
require github.com/mattn/go-runewidth v0.0.3 // indirect

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,3 @@ github.com/mattn/go-runewidth v0.0.3 h1:a+kO+98RDGEfo6asOGMmpodZq4FNtnGP54yps8Bz
22
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
33
github.com/peterh/liner v1.2.1 h1:O4BlKaq/LWu6VRWmol4ByWfzx6MfXc5Op5HETyIy5yg=
44
github.com/peterh/liner v1.2.1/go.mod h1:CRroGNssyjTd/qIG2FyxByd2S8JEAZXBl4qUrZf8GS0=
5-
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
6-
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=

0 commit comments

Comments
 (0)