Fix values

This commit is contained in:
Neemek 2025-03-16 20:09:36 +01:00
parent ae7d6c5359
commit 5edad332cd
Signed by: neemek
GPG key ID: 28360A8951CD0E9B
19 changed files with 611 additions and 331 deletions

View file

@ -147,7 +147,7 @@ func (cmd *RunCmd) Run(ctx *Context) error {
log.Println("=v= output =v=") log.Println("=v= output =v=")
} }
// execute order 66 // execute order 66
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
return nil return nil

View file

@ -16,7 +16,7 @@ func GetAllTestCases() map[string]AllTestCase {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(1), &NumberValue{1},
0, 0,
}, },
}, },
@ -26,7 +26,7 @@ func GetAllTestCases() map[string]AllTestCase {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"sum", "sum",
FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []string{"a", "b"},
Chunk: &Chunk{ Chunk: &Chunk{
@ -38,7 +38,7 @@ func GetAllTestCases() map[string]AllTestCase {
InstructionReturn, InstructionReturn,
InstructionAscend, InstructionAscend,
}, },
Constants: []Value{StringValue("a"), StringValue("b")}, Constants: []Value{&StringValue{"a"}, &StringValue{"b"}},
}, },
}, },
0, 0,
@ -86,7 +86,7 @@ func TestAll(t *testing.T) {
vm := NewVM(c.Chunk, 256, 256) vm := NewVM(c.Chunk, 256, 256)
t.Log("Running bytecode") t.Log("Running bytecode")
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
t.Log("Comparing stacks") t.Log("Comparing stacks")
@ -112,7 +112,7 @@ func BenchmarkAll(b *testing.B) {
vm := NewVM(c.Chunk, 256, 256) vm := NewVM(c.Chunk, 256, 256)
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
} }
}) })

View file

@ -1,5 +1,9 @@
package core package core
import (
"fmt"
)
type Compiler struct { type Compiler struct {
Chunk *Chunk Chunk *Chunk
ip Pos ip Pos
@ -45,7 +49,7 @@ func (c *Compiler) add(instruction Bytecode) {
func (c *Compiler) addConstant(value Value) { func (c *Compiler) addConstant(value Value) {
chunk := c.Chunk chunk := c.Chunk
for i := 0; i < len(chunk.Constants); i++ { for i := 0; i < len(chunk.Constants); i++ {
if chunk.Constants[i] == value { if chunk.Constants[i].Equals(value) {
c.add(Bytecode(i)) c.add(Bytecode(i))
return return
@ -57,26 +61,41 @@ func (c *Compiler) addConstant(value Value) {
c.add(Bytecode(len(chunk.Constants) - 1)) c.add(Bytecode(len(chunk.Constants) - 1))
} }
func (c *Compiler) Compile(tree Node) { func (c *Compiler) Compile(tree Node) error {
if tree == nil { if tree == nil {
panic("nil value parse tree node") panic("compile called with nil value")
} }
switch tree.Type() { switch tree.Type() {
case StringNodeType: case StringNodeType:
c.add(InstructionConstant) c.add(InstructionConstant)
c.addConstant(StringValue(tree.(*StringNode).value)) c.addConstant(&StringValue{
tree.(*StringNode).value,
})
case NumberNodeType: case NumberNodeType:
c.add(InstructionConstant) c.add(InstructionConstant)
c.addConstant(tree.(*NumberNode).value) c.addConstant(&NumberValue{tree.(*NumberNode).value})
case ListNodeType: case ListNodeType:
v := tree.(*ListNode).items l := tree.(*ListNode)
c.add(InstructionNewList)
for _, n := range v { if len(l.items) == 0 {
c.Compile(n) c.add(InstructionNewList)
c.add(InstructionAppend) } else if c.isTreeConstant(l) {
v, err := c.compute(l)
if err != nil {
panic(err) // this shouldn't happen
}
c.add(InstructionConstant)
c.addConstant(v)
} else {
for _, n := range l.items {
c.Compile(n)
}
c.add(InstructionFormList)
c.addU16(uint16(len(l.items)))
} }
case ReferenceNodeType: case ReferenceNodeType:
@ -201,12 +220,17 @@ func (c *Compiler) Compile(tree Node) {
for _, p := range n.params { for _, p := range n.params {
c.registerVar(p) c.registerVar(p)
} }
c.Compile(n.logic)
err := c.Compile(n.logic)
if err != nil {
return err
}
if n.logic.Type() != BlockNodeType { if n.logic.Type() != BlockNodeType {
c.stack.Pop() c.stack.Pop()
} }
mc.Constants[fi] = FunctionValue{ mc.Constants[fi] = &FunctionValue{
n.name, n.name,
n.params, n.params,
c.Chunk, c.Chunk,
@ -219,9 +243,14 @@ func (c *Compiler) Compile(tree Node) {
case AccessNodeType: case AccessNodeType:
n := tree.(*AccessNode) n := tree.(*AccessNode)
c.Compile(n.source) err := c.Compile(n.source)
if err != nil {
return err
}
c.add(InstructionAccessProperty) c.add(InstructionAccessProperty)
c.addConstant(StringValue(n.property)) c.addConstant(&StringValue{
n.property,
})
case ImportNodeType: case ImportNodeType:
n := tree.(*ImportNode) n := tree.(*ImportNode)
@ -229,21 +258,46 @@ func (c *Compiler) Compile(tree Node) {
t := c.resolveImport(n.path).(*BlockNode) t := c.resolveImport(n.path).(*BlockNode)
for _, statement := range t.statements { for _, statement := range t.statements {
c.Compile(statement) err := c.Compile(statement)
if err != nil {
return err
}
} }
case ReturnNodeType: case ReturnNodeType:
c.Compile(tree.(*ReturnNode).value) err := c.Compile(tree.(*ReturnNode).value)
if err != nil {
return err
}
c.add(InstructionReturn) c.add(InstructionReturn)
case BreakpointNodeType: case BreakpointNodeType:
c.add(InstructionBreakpoint) c.add(InstructionBreakpoint)
} }
return nil
} }
func (c *Compiler) compileBinary(binary *BinaryNode) { func (c *Compiler) compileBinary(binary *BinaryNode) error {
c.Compile(binary.Left) if c.isTreeConstant(binary) {
c.Compile(binary.Right) v, err := c.compute(binary)
if err != nil {
return err
}
c.add(InstructionConstant)
c.addConstant(v)
return nil
}
err := c.Compile(binary.Left)
if err != nil {
return err
}
err = c.Compile(binary.Right)
if err != nil {
return err
}
switch binary.BinaryOperation { switch binary.BinaryOperation {
case BinaryAddition: case BinaryAddition:
@ -271,20 +325,29 @@ func (c *Compiler) compileBinary(binary *BinaryNode) {
case BinaryOr: case BinaryOr:
c.add(InstructionOr) c.add(InstructionOr)
} }
return nil
} }
func (c *Compiler) getVar(name string) { func (c *Compiler) getVar(name string) {
if c.isGlobal(name) { if c.isGlobal(name) {
c.add(InstructionGetGlobal) c.add(InstructionGetGlobal)
c.addConstant(StringValue(name)) c.addConstant(&StringValue{
name,
})
} else { } else {
c.add(InstructionGetLocal) c.add(InstructionGetLocal)
c.addConstant(StringValue(name)) c.addConstant(&StringValue{
name,
})
} }
} }
func (c *Compiler) setVar(name string, value Node, declare bool) { func (c *Compiler) setVar(name string, value Node, declare bool) error {
c.Compile(value) err := c.Compile(value)
if err != nil {
return err
}
if declare { if declare {
c.add(InstructionDeclareLocal) c.add(InstructionDeclareLocal)
@ -293,7 +356,11 @@ func (c *Compiler) setVar(name string, value Node, declare bool) {
c.add(InstructionSetLocal) c.add(InstructionSetLocal)
} }
c.addConstant(StringValue(name)) c.addConstant(&StringValue{
name,
})
return nil
} }
// keep track that a variable is declared but doesn't necessarily have a deducible type // keep track that a variable is declared but doesn't necessarily have a deducible type
@ -314,6 +381,112 @@ func (c *Compiler) isLocal(name string) bool {
return false return false
} }
// isTreeConstant check if a node tree is constant (predictable)
func (c *Compiler) isTreeConstant(tree Node) bool {
switch tree.Type() {
case StringNodeType, NumberNodeType, BooleanNodeType, NilNodeType:
return true
case ListNodeType:
for _, item := range tree.(*ListNode).items {
if !c.isTreeConstant(item) {
return false
}
}
return true
case BinaryNodeType:
return c.isTreeConstant(tree.(*BinaryNode).Left) && c.isTreeConstant(tree.(*BinaryNode).Right)
case BlockNodeType, ConditionalNodeType, LoopNodeType, AssignNodeType, CallNodeType, FunctionNodeType,
ReturnNodeType, AccessNodeType, BreakpointNodeType, ImportNodeType, ReferenceNodeType:
return false
default:
panic(fmt.Sprintf("unexpected node %s", tree))
}
}
func (c *Compiler) compute(tree Node) (Value, error) {
switch n := tree.(type) {
case *StringNode:
return &StringValue{
n.value,
}, nil
case *NumberNode:
return &NumberValue{
n.value,
}, nil
case *BooleanNode:
return &BoolValue{
n.value,
}, nil
case *NilNode:
return &NilValue{}, nil
case *ListNode:
items := make([]Value, len(n.items))
var err error
for i, item := range n.items {
items[i], err = c.compute(item)
if err != nil {
return nil, err
}
}
return &ListValue{
items,
}, nil
case *BinaryNode:
return c.computeBinary(n)
default:
panic(fmt.Sprintf("unexpected node %s, %T", tree.String(), tree))
}
}
func (c *Compiler) computeBinary(n *BinaryNode) (Value, error) {
l, err := c.compute(n.Left)
if err != nil {
return nil, err
}
r, err := c.compute(n.Right)
if err != nil {
return nil, err
}
var v interface{}
switch n.BinaryOperation {
case BinaryAddition:
v = l.(*NumberValue).float64 + r.(*NumberValue).float64
case BinarySubtraction:
v = l.(*NumberValue).float64 - r.(*NumberValue).float64
case BinaryMultiplication:
v = l.(*NumberValue).float64 * r.(*NumberValue).float64
case BinaryDivision:
v = l.(*NumberValue).float64 / r.(*NumberValue).float64
case BinaryAnd:
v = l.(*BoolValue).bool && r.(*BoolValue).bool
case BinaryOr:
v = l.(*BoolValue).bool && r.(*BoolValue).bool
case BinaryEquality:
v = l.Equals(r)
case BinaryInequality:
v = !l.Equals(r.(*BoolValue))
case BinaryLess:
v = l.(*NumberValue).float64 < r.(*NumberValue).float64
case BinaryGreater:
v = l.(*NumberValue).float64 > r.(*NumberValue).float64
case BinaryLessEqual:
v = l.(*NumberValue).float64 <= r.(*NumberValue).float64
case BinaryGreaterEqual:
v = l.(*NumberValue).float64 >= r.(*NumberValue).float64
}
return GoToValue(v), nil
}
// isGlobal whether a variable is defined in the standard global environment // isGlobal whether a variable is defined in the standard global environment
func (c *Compiler) isGlobal(name string) bool { func (c *Compiler) isGlobal(name string) bool {
return DefaultGlobals[name] != nil return DefaultGlobals[name] != nil

View file

@ -40,7 +40,7 @@ func GetCompileTestData() map[string]CompileTestData {
"\"Hello world!\"", "\"Hello world!\"",
}, },
[]Value{ []Value{
StringValue("Hello world!"), &StringValue{"Hello world!"},
}, },
}, },
"conditional_false": { "conditional_false": {
@ -75,7 +75,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(0), &NumberValue{0},
0, 0,
}, },
}, },
@ -112,7 +112,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(1), &NumberValue{1},
0, 0,
}, },
}, },
@ -159,7 +159,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(2), &NumberValue{2},
0, 0,
}, },
}, },
@ -206,7 +206,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(1), &NumberValue{1},
0, 0,
}, },
}, },
@ -222,7 +222,7 @@ func GetCompileTestData() map[string]CompileTestData {
}, },
}, },
[]Value{ []Value{
NumberValue(3), &NumberValue{3},
}, },
}, },
"sum_function": {&BlockNode{ "sum_function": {&BlockNode{
@ -251,7 +251,8 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"sum", "sum",
FunctionValue{
&FunctionValue{
"sum", "sum",
[]string{"a", "b"}, []string{"a", "b"},
NewChunk( NewChunk(
@ -264,7 +265,7 @@ func GetCompileTestData() map[string]CompileTestData {
InstructionAscend, InstructionAscend,
}, },
[]Value{ []Value{
StringValue("a"), StringValue("b"), &StringValue{"a"}, &StringValue{"b"},
}, },
), ),
nil, nil,
@ -308,7 +309,7 @@ func GetCompileTestData() map[string]CompileTestData {
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
FunctionValue{ &FunctionValue{
"a", "a",
[]string{}, []string{},
NewChunk( NewChunk(
@ -321,7 +322,7 @@ func GetCompileTestData() map[string]CompileTestData {
InstructionAscend, InstructionAscend,
}, },
[]Value{ []Value{
NumberValue(1), StringValue("b"), &NumberValue{1}, &StringValue{"b"},
}, },
), ),
nil, nil,
@ -344,7 +345,7 @@ func printChunk(t *testing.T, name string, chunk *Chunk) {
for i, ct := range chunk.Constants { for i, ct := range chunk.Constants {
t.Logf("c=%d \t%s", i, ct) t.Logf("c=%d \t%s", i, ct)
f, ok := ct.(FunctionValue) f, ok := ct.(*FunctionValue)
if ok { if ok {
printChunk(t, f.Name, f.Chunk) printChunk(t, f.Name, f.Chunk)
} }
@ -371,7 +372,7 @@ func TestCompile(t *testing.T) {
printChunk(t, name, c.Chunk) printChunk(t, name, c.Chunk)
t.Log("Executing bytecode") t.Log("Executing bytecode")
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
t.Log("Executed bytecode") t.Log("Executed bytecode")
@ -437,7 +438,7 @@ func TestCompiler_CleanStack(t *testing.T) {
c.Compile(tc.tree) c.Compile(tc.tree)
vm := NewVM(c.Chunk, 256, 256) vm := NewVM(c.Chunk, 256, 256)
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
// make sure stack has only assigned values // make sure stack has only assigned values

View file

@ -151,6 +151,8 @@ func (t TokenType) String() string {
return "open bracket" return "open bracket"
case TokenCloseBracket: case TokenCloseBracket:
return "close bracket" return "close bracket"
case TokenImport:
return "import"
} }
return "UNDEFINED TOKENTYPE STRING CONVERSION" return "UNDEFINED TOKENTYPE STRING CONVERSION"

View file

@ -101,7 +101,7 @@ func (n StringNode) String() string {
} }
type NumberNode struct { type NumberNode struct {
value NumberValue value float64
} }
func (n NumberNode) Type() NodeType { func (n NumberNode) Type() NodeType {
@ -109,7 +109,7 @@ func (n NumberNode) Type() NodeType {
} }
func (n NumberNode) String() string { func (n NumberNode) String() string {
return strconv.FormatFloat(float64(n.value), 'g', -1, NumberSize) return strconv.FormatFloat(n.value, 'g', -1, NumberSize)
} }
// ListNode a list or sequence of values (items) // ListNode a list or sequence of values (items)

View file

@ -158,7 +158,7 @@ func (p *Parser) factor() (Node, error) {
} }
return &NumberNode{ return &NumberNode{
NumberValue(num), num,
}, nil }, nil
case TokenTrue: case TokenTrue:
@ -209,7 +209,7 @@ func (p *Parser) factor() (Node, error) {
} }
return &BinaryNode{ return &BinaryNode{
BinarySubtraction, BinarySubtraction,
&NumberNode{NumberValue(0)}, &NumberNode{0},
f, f,
}, nil }, nil

View file

@ -65,10 +65,10 @@ func GetTokenTestData() map[string]TokenTestData {
&BinaryNode{ &BinaryNode{
BinaryAddition, BinaryAddition,
&NumberNode{ &NumberNode{
value: NumberValue(1), 1,
}, },
&NumberNode{ &NumberNode{
value: NumberValue(2), 2,
}, },
}, },
false, false,
@ -170,22 +170,22 @@ func GetTokenTestData() map[string]TokenTestData {
&NumberNode{2}, &NumberNode{2},
&NumberNode{1}, &NumberNode{1},
}, },
&NumberNode{NumberValue(5)}, &NumberNode{5},
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{NumberValue(3)}, &NumberNode{3},
&BinaryNode{ &BinaryNode{
BinarySubtraction, BinarySubtraction,
&NumberNode{NumberValue(6)}, &NumberNode{6},
&NumberNode{NumberValue(2)}, &NumberNode{2},
}, },
}, },
}, },
&BinaryNode{ &BinaryNode{
BinaryDivision, BinaryDivision,
&NumberNode{NumberValue(10)}, &NumberNode{10},
&NumberNode{NumberValue(2)}, &NumberNode{2},
}, },
}, },
false, false,
@ -242,7 +242,7 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
}, },
&NumberNode{ &NumberNode{
NumberValue(0), 0,
}, },
}, },
do: &BlockNode{ do: &BlockNode{
@ -250,7 +250,7 @@ func GetTokenTestData() map[string]TokenTestData {
&AssignNode{ &AssignNode{
"b", "b",
&NumberNode{ &NumberNode{
NumberValue(1), 1,
}, },
false, false,
}, },
@ -288,7 +288,7 @@ func GetTokenTestData() map[string]TokenTestData {
"a", "a",
}, },
&NumberNode{ &NumberNode{
NumberValue(0), 0,
}, },
}, },
do: &BlockNode{ do: &BlockNode{
@ -296,7 +296,7 @@ func GetTokenTestData() map[string]TokenTestData {
&AssignNode{ &AssignNode{
"b", "b",
&NumberNode{ &NumberNode{
NumberValue(1), 1,
}, },
false, false,
}, },
@ -307,7 +307,7 @@ func GetTokenTestData() map[string]TokenTestData {
&AssignNode{ &AssignNode{
"b", "b",
&NumberNode{ &NumberNode{
NumberValue(0), 0,
}, },
false, false,
}, },

View file

@ -4,7 +4,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"reflect" "reflect"
"slices"
"strconv" "strconv"
"strings" "strings"
) )
@ -48,24 +47,34 @@ func (v ValueType) String() string {
return "undefined" return "undefined"
} }
// GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, string, slices, maps) // GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, int, string, slices, maps)
func GoToValue(gov interface{}) Value { func GoToValue(gov interface{}) Value {
switch v := gov.(type) { switch v := gov.(type) {
case nil: case nil:
return NilValue{} return &NilValue{}
case bool: case bool:
return BoolValue(v) return &BoolValue{
v,
}
case int:
return &NumberValue{
float64(v),
}
case float64: case float64:
return NumberValue(v) return &NumberValue{
v,
}
case string: case string:
return StringValue(v) return &StringValue{
v,
}
case []interface{}: case []interface{}:
values := make([]Value, len(v)) values := make([]Value, len(v))
for i, value := range v { for i, value := range v {
values[i] = GoToValue(value) values[i] = GoToValue(value)
} }
return ListValue{ return &ListValue{
values, values,
} }
case map[string]interface{}: case map[string]interface{}:
@ -74,7 +83,7 @@ func GoToValue(gov interface{}) Value {
values[key] = GoToValue(value) values[key] = GoToValue(value)
} }
return ObjectValue{ return &ObjectValue{
values, values,
} }
} }
@ -101,49 +110,51 @@ type Value interface {
type NilValue struct{} type NilValue struct{}
func (v NilValue) Type() ValueType { func (v *NilValue) Type() ValueType {
return NilValueType return NilValueType
} }
func (v NilValue) String() string { func (v *NilValue) String() string {
return "nil" return "nil"
} }
func (v NilValue) DebugString() string { func (v *NilValue) DebugString() string {
return v.String() return v.String()
} }
func (v NilValue) Equals(other Value) bool { func (v *NilValue) Equals(other Value) bool {
return other.Type() == NilValueType return other.Type() == NilValueType
} }
func (v NilValue) Get(key string) (Value, error) { func (v *NilValue) Get(_ string) (Value, error) {
return nil, errors.New("nil has no properties") return nil, errors.New("nil has no properties")
} }
type BoolValue bool type BoolValue struct {
bool
}
func (v BoolValue) Type() ValueType { func (v *BoolValue) Type() ValueType {
return BoolValueType return BoolValueType
} }
func (v BoolValue) String() string { func (v *BoolValue) String() string {
if v { if v.bool {
return "true" return "true"
} else { } else {
return "false" return "false"
} }
} }
func (v BoolValue) DebugString() string { func (v *BoolValue) DebugString() string {
return v.String() return v.String()
} }
func (v BoolValue) Equals(other Value) bool { func (v *BoolValue) Equals(other Value) bool {
return other.Type() == BoolValueType && bool(other.(BoolValue)) == bool(v) return other.Type() == BoolValueType && other.(*BoolValue).bool == v.bool
} }
func (v BoolValue) Get(key string) (Value, error) { func (v *BoolValue) Get(key string) (Value, error) {
return nil, errors.New("booleans have no properties") return nil, errors.New("booleans have no properties")
} }
@ -152,11 +163,11 @@ type ObjectValue struct {
members map[string]Value members map[string]Value
} }
func (v ObjectValue) Type() ValueType { func (v *ObjectValue) Type() ValueType {
return ObjectValueType return ObjectValueType
} }
func (v ObjectValue) String() string { func (v *ObjectValue) String() string {
out := "{" out := "{"
for key, value := range v.members { for key, value := range v.members {
if out != "{" { if out != "{" {
@ -170,74 +181,111 @@ func (v ObjectValue) String() string {
return out return out
} }
func (v ObjectValue) DebugString() string { func (v *ObjectValue) DebugString() string {
return v.String() return v.String()
} }
func (v ObjectValue) Equals(other Value) bool { func (v *ObjectValue) Equals(other Value) bool {
// TODO implement object equality check object, ok := other.(*ObjectValue)
return false if !ok {
return false
}
for key, value := range v.members {
if !object.members[key].Equals(value) {
return false
}
}
return true
} }
func (v ObjectValue) Get(key string) (Value, error) { var ObjectPrototype = map[string]Value{
if member := v.members[key]; member == nil { "set": &BuiltinFunctionValue{
return nil, errors.New("no property found with name \"" + key + "\"") "set",
} else { []string{"property", "value"},
func(vm *VM, _this Value, params map[string]Value) (Value, error) {
this := _this.(*ObjectValue)
p := params["property"]
v, ok := params["value"].(*StringValue)
if !ok {
return nil, errors.New("property is not a string")
}
this.members[v.string] = p
return &NilValue{}, nil
},
nil,
},
}
func (v *ObjectValue) Get(key string) (Value, error) {
if member, ok := v.members[key]; ok {
return member, nil return member, nil
} else if p, ok := ObjectPrototype[key]; ok {
return p, nil
} else {
return nil, errors.New("no property found with name \"" + key + "\"")
} }
} }
// NumberValue Integer or floating-point values // NumberValue Integer or floating-point values
type NumberValue float64 type NumberValue struct {
float64
}
const NumberSize int = 64 const NumberSize int = 64
func (v NumberValue) Type() ValueType { func (v *NumberValue) Type() ValueType {
return NumberValueType return NumberValueType
} }
func (v NumberValue) String() string { func (v *NumberValue) String() string {
return strconv.FormatFloat(float64(v), 'g', -1, NumberSize) return strconv.FormatFloat(v.float64, 'g', -1, NumberSize)
} }
func (v NumberValue) DebugString() string { func (v *NumberValue) DebugString() string {
return v.String() return v.String()
} }
func (v NumberValue) Equals(other Value) bool { func (v *NumberValue) Equals(other Value) bool {
return other.Type() == NumberValueType && float64(other.(NumberValue)) == float64(v) return other.Type() == NumberValueType && other.(*NumberValue).float64 == v.float64
} }
func (v NumberValue) Get(key string) (Value, error) { func (v *NumberValue) Get(key string) (Value, error) {
// TODO maybe add standard functions for number values? // TODO maybe add standard functions for number values?
return nil, errors.New("numbers have no properties") return nil, errors.New("numbers have no properties")
} }
type StringValue string type StringValue struct {
string
}
func (v StringValue) Type() ValueType { func (v *StringValue) Type() ValueType {
return StringValueType return StringValueType
} }
func (v StringValue) String() string { func (v *StringValue) String() string {
return string(v) return v.string
} }
func (v StringValue) DebugString() string { func (v *StringValue) DebugString() string {
return "\"" + v.String() + "\"" return "\"" + v.String() + "\""
} }
func (v StringValue) Equals(other Value) bool { func (v *StringValue) Equals(other Value) bool {
return other.Type() == StringValueType && string(other.(StringValue)) == string(v) return other.Type() == StringValueType && other.(*StringValue).string == v.string
} }
var StringPrototype = map[string]BuiltinFunctionValue{ var StringPrototype = map[string]*BuiltinFunctionValue{
"split": { "split": {
"split", "split",
[]string{"seperator"}, []string{"seperator"},
func(vm *VM, this Value, m map[string]Value) (Value, error) { func(vm *VM, this Value, m map[string]Value) (Value, error) {
str := this.(StringValue).String() str := this.(*StringValue).String()
sep := m["seperator"].(StringValue).String() sep := m["seperator"].(*StringValue).String()
var out []string var out []string
tmp := strings.Builder{} tmp := strings.Builder{}
@ -256,7 +304,7 @@ var StringPrototype = map[string]BuiltinFunctionValue{
}, },
} }
func (v StringValue) Get(key string) (Value, error) { func (v *StringValue) Get(key string) (Value, error) {
if prop, ok := StringPrototype[key]; ok { if prop, ok := StringPrototype[key]; ok {
return prop, nil return prop, nil
} }
@ -269,11 +317,11 @@ type ListValue struct {
items []Value items []Value
} }
func (v ListValue) Type() ValueType { func (v *ListValue) Type() ValueType {
return ListValueType return ListValueType
} }
func (v ListValue) String() string { func (v *ListValue) String() string {
out := "[" out := "["
for i, item := range v.items { for i, item := range v.items {
if i != 0 { if i != 0 {
@ -286,23 +334,37 @@ func (v ListValue) String() string {
return out return out
} }
func (v ListValue) DebugString() string { func (v *ListValue) DebugString() string {
return v.String() return v.String()
} }
func (v ListValue) Equals(other Value) bool { func (v *ListValue) Equals(other Value) bool {
return other.Type() == ListValueType && if other.Type() != ListValueType {
slices.Equal(v.items, other.(ListValue).items) return false
}
l := other.(*ListValue)
if len(v.items) != len(l.items) {
return false
}
for i, item := range l.items {
if !item.Equals(l.items[i]) {
return false
}
}
return true
} }
var ListPrototype = map[string]BuiltinFunctionValue{ var ListPrototype = map[string]*BuiltinFunctionValue{
"append": { "append": {
"append", "append",
[]string{"item"}, []string{"item"},
func(_ *VM, this Value, p map[string]Value) (Value, error) { func(_ *VM, this Value, p map[string]Value) (Value, error) {
return ListValue{ this.(*ListValue).items = append(this.(*ListValue).items, p["item"])
append(this.(ListValue).items, p["item"]), return &NilValue{}, nil
}, nil
}, },
nil, nil,
}, },
@ -310,8 +372,8 @@ var ListPrototype = map[string]BuiltinFunctionValue{
"at", "at",
[]string{"index"}, []string{"index"},
func(_ *VM, this Value, p map[string]Value) (Value, error) { func(_ *VM, this Value, p map[string]Value) (Value, error) {
index := int(p["index"].(NumberValue)) items := this.(*ListValue).items
items := this.(ListValue).items index := int(p["index"].(*NumberValue).float64)
if index >= len(items) { if index >= len(items) {
return nil, errors.New(fmt.Sprintf("list index %x out of range", index)) return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
@ -325,7 +387,7 @@ var ListPrototype = map[string]BuiltinFunctionValue{
"length", "length",
[]string{}, []string{},
func(_ *VM, this Value, p map[string]Value) (Value, error) { func(_ *VM, this Value, p map[string]Value) (Value, error) {
return NumberValue(len(this.(ListValue).items)), nil return GoToValue(len(this.(*ListValue).items)), nil
}, },
nil, nil,
}, },
@ -333,13 +395,13 @@ var ListPrototype = map[string]BuiltinFunctionValue{
"map", "map",
[]string{"f"}, []string{"f"},
func(vm *VM, value Value, m map[string]Value) (Value, error) { func(vm *VM, value Value, m map[string]Value) (Value, error) {
list := value.(ListValue) list := value.(*ListValue)
v := m["f"] v := m["f"]
var f Value var f Value
f, ok := v.(FunctionValue) f, ok := v.(*FunctionValue)
if !ok { if !ok {
f, ok = v.(BuiltinFunctionValue) f, ok = v.(*BuiltinFunctionValue)
if !ok { if !ok {
return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v)) return nil, errors.New(fmt.Sprintf("not a function to apply: %s", v))
@ -365,7 +427,7 @@ var ListPrototype = map[string]BuiltinFunctionValue{
"reduce", "reduce",
[]string{"f", "start"}, []string{"f", "start"},
func(vm *VM, value Value, m map[string]Value) (Value, error) { func(vm *VM, value Value, m map[string]Value) (Value, error) {
list := value.(ListValue) list := value.(*ListValue)
f := m["f"] f := m["f"]
sum := m["start"] sum := m["start"]
@ -383,7 +445,7 @@ var ListPrototype = map[string]BuiltinFunctionValue{
}, },
} }
func (v ListValue) Get(key string) (Value, error) { func (v *ListValue) Get(key string) (Value, error) {
if prop, ok := ListPrototype[key]; ok { if prop, ok := ListPrototype[key]; ok {
return prop, nil return prop, nil
} }
@ -398,25 +460,25 @@ type FunctionValue struct {
Parent Value Parent Value
} }
func (v FunctionValue) Type() ValueType { func (v *FunctionValue) Type() ValueType {
return FunctionValueType return FunctionValueType
} }
func (v FunctionValue) String() string { func (v *FunctionValue) String() string {
return fmt.Sprintf("<function name=%s>", v.Name) return fmt.Sprintf("<function name=%s>", v.Name)
} }
func (v FunctionValue) DebugString() string { func (v *FunctionValue) DebugString() string {
return v.String() return v.String()
} }
func (v FunctionValue) Equals(other Value) bool { func (v *FunctionValue) Equals(other Value) bool {
return other.Type() == FunctionValueType && return other.Type() == FunctionValueType &&
v.Name == other.(FunctionValue).Name && v.Name == other.(*FunctionValue).Name &&
v.Chunk == other.(FunctionValue).Chunk v.Chunk == other.(*FunctionValue).Chunk
} }
func (v FunctionValue) Get(_ string) (Value, error) { func (v *FunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
@ -431,20 +493,20 @@ func (v BuiltinFunctionValue) Type() ValueType {
return BuiltinFunctionValueType return BuiltinFunctionValueType
} }
func (v BuiltinFunctionValue) String() string { func (v *BuiltinFunctionValue) String() string {
return fmt.Sprintf("<function name=%s builtin>", v.Name) return fmt.Sprintf("<function name=%s builtin>", v.Name)
} }
func (v BuiltinFunctionValue) DebugString() string { func (v *BuiltinFunctionValue) DebugString() string {
return v.String() return v.String()
} }
func (v BuiltinFunctionValue) Equals(other Value) bool { func (v *BuiltinFunctionValue) Equals(other Value) bool {
return other.Type() == BuiltinFunctionValueType && return other.Type() == BuiltinFunctionValueType &&
v.Name == other.(BuiltinFunctionValue).Name v.Name == other.(*BuiltinFunctionValue).Name
} }
func (v BuiltinFunctionValue) Get(_ string) (Value, error) { func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
return nil, errors.New("functions have no properties") return nil, errors.New("functions have no properties")
} }
@ -455,27 +517,27 @@ type VariableValue struct {
scope Pos scope Pos
} }
func (v VariableValue) Type() ValueType { func (v *VariableValue) Type() ValueType {
return VariableValueType return VariableValueType
} }
func (v VariableValue) String() string { func (v *VariableValue) String() string {
return fmt.Sprintf("<variable name=%s value=%s scope=%d>", v.name, v.value, v.scope) return fmt.Sprintf("<variable name=%s value=%s scope=%d>", v.name, v.value, v.scope)
// variables should not be accessed on the stack; normal values should be pushed and popped predictably // variables should not be accessed on the stack; normal values should be pushed and popped predictably
//panic("tried getting string value of a unreachable value") //panic("tried getting string value of a unreachable value")
} }
func (v VariableValue) DebugString() string { func (v *VariableValue) DebugString() string {
return v.String() return v.String()
} }
func (v VariableValue) Equals(other Value) bool { func (v *VariableValue) Equals(other Value) bool {
return other.Type() == VariableValueType && return other.Type() == VariableValueType &&
v.name == other.(VariableValue).name && v.name == other.(*VariableValue).name &&
v.value.Equals(other.(VariableValue).value) v.value.Equals(other.(*VariableValue).value)
} }
func (v VariableValue) Get(_ string) (Value, error) { func (v *VariableValue) Get(_ string) (Value, error) {
return nil, errors.New("variables have no properties") return nil, errors.New("variables have no properties")
} }

View file

@ -16,26 +16,26 @@ func CompareValues(t *testing.T, got Value, want Value) {
t.Logf("Both are nil") t.Logf("Both are nil")
return return
case BoolValueType: case BoolValueType:
if got.(BoolValue) != want.(BoolValue) { if got.(*BoolValue).bool != want.(*BoolValue).bool {
t.Errorf("bool value mismatch: got %v, want %v", got.(BoolValue), want.(BoolValue)) t.Errorf("bool value mismatch: got %v, want %v", got.(*BoolValue), want.(*BoolValue))
} else { } else {
t.Logf("Both are same boolean (%s)", want.(BoolValue).String()) t.Logf("Both are same boolean (%s)", want.(*BoolValue).String())
} }
case NumberValueType: case NumberValueType:
if got.(NumberValue) != want.(NumberValue) { if got.(*NumberValue).float64 != want.(*NumberValue).float64 {
t.Errorf("number value mismatch: got %v, want %v", got.(NumberValue), want.(NumberValue)) t.Errorf("number value mismatch: got %v, want %v", got.(*NumberValue), want.(*NumberValue))
} else { } else {
t.Logf("Both are same number (%s)", got.(NumberValue).String()) t.Logf("Both are same number (%s)", got.(*NumberValue).String())
} }
case StringValueType: case StringValueType:
if got.(StringValue) != want.(StringValue) { if got.(*StringValue).string != want.(*StringValue).string {
t.Errorf("string value mismatch: got %v, want %v", got.(StringValue), want.(StringValue)) t.Errorf("string value mismatch: got %v, want %v", got.(*StringValue), want.(*StringValue))
} else { } else {
t.Logf("Both are same string (%s)", got.(StringValue).String()) t.Logf("Both are same string (%s)", got.(*StringValue).String())
} }
case FunctionValueType: case FunctionValueType:
n := got.(FunctionValue) n := got.(*FunctionValue)
m := want.(FunctionValue) m := want.(*FunctionValue)
if n.Name != m.Name { if n.Name != m.Name {
t.Errorf("function name mismatch: got %v, want %v", n.Name, m.Name) t.Errorf("function name mismatch: got %v, want %v", n.Name, m.Name)
@ -53,8 +53,8 @@ func CompareValues(t *testing.T, got Value, want Value) {
CompareChunks(t, n.Chunk, m.Chunk) CompareChunks(t, n.Chunk, m.Chunk)
case BuiltinFunctionValueType: case BuiltinFunctionValueType:
n := got.(BuiltinFunctionValue) n := got.(*BuiltinFunctionValue)
m := want.(BuiltinFunctionValue) m := want.(*BuiltinFunctionValue)
if n.Name != m.Name { if n.Name != m.Name {
t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name) t.Errorf("builtin function name mismatch: got %v, want %v", n.Name, m.Name)

View file

@ -96,7 +96,8 @@ const (
// InstructionAppend Append to a list. stack: (... > list > item) => (... > list) // InstructionAppend Append to a list. stack: (... > list > item) => (... > list)
InstructionAppend InstructionAppend
// InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of // InstructionFormList Form items on the stack into a list. The 2 bytes after the instructions are the amount of
// items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) // items to include minus one. (value of 0 => 1 item, value of 1 => 2 items, etc.) The order is reversed compared
// to on the stack; the top value on the stack is the last in the list.
InstructionFormList InstructionFormList
// InstructionBreakpoint for debugging purposes // InstructionBreakpoint for debugging purposes
@ -203,7 +204,7 @@ func (c Chunk) String() string {
for i, ct := range c.Constants { for i, ct := range c.Constants {
b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct)) b.WriteString(fmt.Sprintf("c=%d \t%s\n", i, ct))
f, ok := ct.(FunctionValue) f, ok := ct.(*FunctionValue)
if ok { if ok {
b.WriteString(f.Chunk.String()) b.WriteString(f.Chunk.String())
} }
@ -219,9 +220,10 @@ func NewChunk(bytecode []Bytecode, constants []Value) *Chunk {
} }
func RegisterGOBTypes() { func RegisterGOBTypes() {
gob.Register(StringValue("")) gob.Register(&StringValue{""})
gob.Register(NumberValue(0)) gob.Register(&BoolValue{false})
gob.Register(FunctionValue{ gob.Register(&NumberValue{0})
gob.Register(&FunctionValue{
Name: "", Name: "",
Params: nil, Params: nil,
Chunk: nil, Chunk: nil,
@ -283,7 +285,7 @@ type Call struct {
} }
var DefaultGlobals = map[string]Value{ var DefaultGlobals = map[string]Value{
"write": BuiltinFunctionValue{ "write": &BuiltinFunctionValue{
"write", // always remember where you come from... "write", // always remember where you come from...
[]string{"value"}, []string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) { func(_ *VM, this Value, v map[string]Value) (Value, error) {
@ -292,7 +294,7 @@ var DefaultGlobals = map[string]Value{
}, },
nil, nil,
}, },
"print": BuiltinFunctionValue{ "print": &BuiltinFunctionValue{
"print", "print",
[]string{"value"}, []string{"value"},
func(_ *VM, this Value, v map[string]Value) (Value, error) { func(_ *VM, this Value, v map[string]Value) (Value, error) {
@ -301,15 +303,43 @@ var DefaultGlobals = map[string]Value{
}, },
nil, nil,
}, },
"assert": BuiltinFunctionValue{ "format": &BuiltinFunctionValue{
"assert", "format",
[]string{"condition"}, []string{"format_string", "values"},
func(vm *VM, value Value, m map[string]Value) (Value, error) {
valuies := m["values"].(*ListValue).items
return GoToValue(fmt.Sprintf(m["format_string"].String(), valuies)), nil
},
nil,
},
"assertEq": &BuiltinFunctionValue{
"assertEq",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) { func(vm *VM, this Value, params map[string]Value) (Value, error) {
if !params["condition"].(BoolValue) { a := params["a"]
return nil, errors.New("assertion failed") b := params["b"]
if !a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
} }
return NilValue{}, nil return &NilValue{}, nil
},
nil,
},
"assertNotEq": &BuiltinFunctionValue{
"assertNotEq",
[]string{"a", "b"},
func(vm *VM, this Value, params map[string]Value) (Value, error) {
a := params["a"]
b := params["b"]
if a.Equals(b) {
return nil, errors.New(fmt.Sprintf("assertion failed: %s does not equal %s", a, b))
}
return &NilValue{}, nil
}, },
nil, nil,
}, },
@ -330,6 +360,10 @@ func NewVM(chunk *Chunk, stackSize Pos, callstackSize Pos) *VM {
// Next execute instruction // Next execute instruction
// returns true if more instructions should be executed // returns true if more instructions should be executed
func (vm *VM) Next() bool { func (vm *VM) Next() bool {
if !vm.HasNext() {
return false
}
switch vm.NextByte() { switch vm.NextByte() {
case InstructionReturn: case InstructionReturn:
if vm.call.Current == 0 { if vm.call.Current == 0 {
@ -359,81 +393,81 @@ func (vm *VM) Next() bool {
vm.stack.Push(vm.ReadConstant()) vm.stack.Push(vm.ReadConstant())
case InstructionAdd: case InstructionAdd:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(l + r) vm.stack.Push(&NumberValue{l + r})
case InstructionSub: case InstructionSub:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(l - r) vm.stack.Push(&NumberValue{l - r})
case InstructionMul: case InstructionMul:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(l * r) vm.stack.Push(&NumberValue{l * r})
case InstructionDiv: case InstructionDiv:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(l / r) vm.stack.Push(&NumberValue{l / r})
case InstructionEquals: case InstructionEquals:
vm.stack.Push( vm.stack.Push(
BoolValue(vm.stack.Pop().Equals(vm.stack.Pop())), &BoolValue{vm.stack.Pop().Equals(vm.stack.Pop())},
) )
case InstructionNotEqual: case InstructionNotEqual:
vm.stack.Push( vm.stack.Push(
BoolValue(!vm.stack.Pop().Equals(vm.stack.Pop())), &BoolValue{!vm.stack.Pop().Equals(vm.stack.Pop())},
) )
case InstructionNot: case InstructionNot:
b := vm.stack.Pop().(BoolValue) b := vm.stack.Pop().(*BoolValue).bool
vm.stack.Push(!b) vm.stack.Push(&BoolValue{!b})
case InstructionAnd: case InstructionAnd:
r := vm.stack.Pop().(BoolValue) r := vm.stack.Pop().(*BoolValue).bool
l := vm.stack.Pop().(BoolValue) l := vm.stack.Pop().(*BoolValue).bool
vm.stack.Push(l && r) vm.stack.Push(&BoolValue{l && r})
case InstructionOr: case InstructionOr:
r := vm.stack.Pop().(BoolValue) r := vm.stack.Pop().(*BoolValue).bool
l := vm.stack.Pop().(BoolValue) l := vm.stack.Pop().(*BoolValue).bool
vm.stack.Push(r || l) vm.stack.Push(&BoolValue{l || r})
case InstructionLess: case InstructionLess:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(BoolValue(l < r)) vm.stack.Push(&BoolValue{l < r})
case InstructionLessOrEqual: case InstructionLessOrEqual:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(BoolValue(l <= r)) vm.stack.Push(&BoolValue{l <= r})
case InstructionGreater: case InstructionGreater:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(BoolValue(l > r)) vm.stack.Push(&BoolValue{l > r})
case InstructionGreaterOrEqual: case InstructionGreaterOrEqual:
r := vm.stack.Pop().(NumberValue) r := vm.stack.Pop().(*NumberValue).float64
l := vm.stack.Pop().(NumberValue) l := vm.stack.Pop().(*NumberValue).float64
vm.stack.Push(BoolValue(l >= r)) vm.stack.Push(&BoolValue{l >= r})
case InstructionCall: case InstructionCall:
v := vm.stack.Pop() v := vm.stack.Pop()
switch f := v.(type) { switch f := v.(type) {
case FunctionValue: case *FunctionValue:
vm.call.Push(Call{ vm.call.Push(Call{
chunk: vm.chunk, chunk: vm.chunk,
ip: vm.ip, ip: vm.ip,
@ -459,7 +493,7 @@ func (vm *VM) Next() bool {
vm.chunk = f.Chunk vm.chunk = f.Chunk
vm.ip = 0 vm.ip = 0
case BuiltinFunctionValue: case *BuiltinFunctionValue:
args := map[string]Value{} args := map[string]Value{}
for i := len(f.Parameters) - 1; i >= 0; i-- { for i := len(f.Parameters) - 1; i >= 0; i-- {
@ -473,7 +507,7 @@ func (vm *VM) Next() bool {
vm.stack.Push(v) vm.stack.Push(v)
default: default:
vm.error(fmt.Sprintf("value called is not a function (%s)", v.DebugString())) vm.error(fmt.Sprintf("value called is not a function (%s, type %T)", v.DebugString(), v))
return false return false
} }
@ -485,13 +519,13 @@ func (vm *VM) Next() bool {
case InstructionJumpFalse: case InstructionJumpFalse:
n := vm.NextU16() n := vm.NextU16()
if !vm.stack.Pop().(BoolValue) { if !vm.stack.Pop().(*BoolValue).bool {
vm.ip += Pos(n) vm.ip += Pos(n)
} }
case InstructionGetLocal: case InstructionGetLocal:
name := vm.GetConstant(vm.NextByte()).(StringValue) name := vm.GetConstant(vm.NextByte()).(*StringValue).string
v := vm.getVar(string(name)) v := vm.getVar(name)
if v == nil { if v == nil {
vm.error(fmt.Sprintf("cannot get local: undefined variable %s", name)) vm.error(fmt.Sprintf("cannot get local: undefined variable %s", name))
@ -502,9 +536,9 @@ func (vm *VM) Next() bool {
case InstructionSetLocal: case InstructionSetLocal:
value := vm.stack.Pop().(Value) value := vm.stack.Pop().(Value)
name := vm.GetConstant(vm.NextByte()).(StringValue) name := vm.GetConstant(vm.NextByte()).(*StringValue).string
v := vm.getVar(string(name)) v := vm.getVar(name)
if v == nil { if v == nil {
vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name)) vm.error(fmt.Sprintf("cannot set local: undefined variable %s", name))
@ -514,33 +548,39 @@ func (vm *VM) Next() bool {
case InstructionDeclareLocal: case InstructionDeclareLocal:
vm.addVar( vm.addVar(
string(vm.GetConstant(vm.NextByte()).(StringValue)), vm.GetConstant(vm.NextByte()).(*StringValue).string,
vm.stack.Pop().(Value), vm.stack.Pop().(Value),
) )
case InstructionGetGlobal: case InstructionGetGlobal:
vm.stack.Push(vm.globals[string(vm.GetConstant(vm.NextByte()).(StringValue))]) vm.stack.Push(vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string])
case InstructionSetGlobal: case InstructionSetGlobal:
vm.globals[string(vm.GetConstant(vm.NextByte()).(StringValue))] = vm.stack.Pop() vm.globals[vm.GetConstant(vm.NextByte()).(*StringValue).string] = vm.stack.Pop()
case InstructionTrue: case InstructionTrue:
vm.stack.Push(BoolValue(true)) vm.stack.Push(&BoolValue{true})
case InstructionFalse: case InstructionFalse:
vm.stack.Push(BoolValue(false)) vm.stack.Push(&BoolValue{false})
case InstructionNil: case InstructionNil:
vm.stack.Push(NilValue{}) vm.stack.Push(&NilValue{})
case InstructionFormList: case InstructionFormList:
n := int(vm.NextU16())
items := make([]Value, n+1)
for i := 0; i <= n; i++ {
items[n-i] = vm.stack.Pop()
}
case InstructionNewList: case InstructionNewList:
vm.stack.Push(ListValue{[]Value{}}) vm.stack.Push(&ListValue{[]Value{}})
case InstructionAppend: case InstructionAppend:
value := vm.stack.Pop() value := vm.stack.Pop()
list := vm.stack.Pop().(ListValue) list := vm.stack.Pop().(*ListValue)
list.items = append(list.items, value) list.items = append(list.items, value)
vm.stack.Push(list) vm.stack.Push(list)
@ -552,13 +592,13 @@ func (vm *VM) Next() bool {
case InstructionStringConversion: case InstructionStringConversion:
v := vm.stack.Pop() v := vm.stack.Pop()
vm.stack.Push(StringValue(v.String())) vm.stack.Push(&StringValue{v.String()})
case InstructionStringConcatenation: case InstructionStringConcatenation:
r := vm.stack.Pop().(StringValue) r := vm.stack.Pop().(*StringValue).string
l := vm.stack.Pop().(StringValue) l := vm.stack.Pop().(*StringValue).string
vm.stack.Push(l + r) vm.stack.Push(&StringValue{l + r})
case InstructionSwap: case InstructionSwap:
r := vm.stack.Pop() r := vm.stack.Pop()
@ -570,20 +610,16 @@ func (vm *VM) Next() bool {
source := vm.stack.Pop() source := vm.stack.Pop()
property := vm.ReadConstant() property := vm.ReadConstant()
member, err := source.Get(property.(StringValue).String()) member, err := source.Get(property.(*StringValue).String())
if err != nil { if err != nil {
vm.error(err.Error()) vm.error(err.Error())
} }
// add parent if function with a little switcheroo // add parent if function
if member.Type() == FunctionValueType { if member.Type() == FunctionValueType {
f := member.(FunctionValue) member.(*FunctionValue).Parent = source
f.Parent = source
member = f
} else if member.Type() == BuiltinFunctionValueType { } else if member.Type() == BuiltinFunctionValueType {
f := member.(BuiltinFunctionValue) member.(*BuiltinFunctionValue).Parent = source
f.Parent = source
member = f
} }
vm.stack.Push(member) vm.stack.Push(member)
@ -599,7 +635,7 @@ func (vm *VM) Next() bool {
func (vm *VM) Call(v Value, args []Value) (Value, error) { func (vm *VM) Call(v Value, args []Value) (Value, error) {
switch f := v.(type) { switch f := v.(type) {
case FunctionValue: case *FunctionValue:
vm.call.Push(Call{ vm.call.Push(Call{
chunk: vm.chunk, chunk: vm.chunk,
ip: vm.ip, ip: vm.ip,
@ -630,7 +666,7 @@ func (vm *VM) Call(v Value, args []Value) (Value, error) {
return vm.stack.Pop(), nil return vm.stack.Pop(), nil
case BuiltinFunctionValue: case *BuiltinFunctionValue:
argies := map[string]Value{} argies := map[string]Value{}
for i, arg := range args { for i, arg := range args {

View file

@ -11,7 +11,7 @@ func CompareChunks(t *testing.T, got *Chunk, want *Chunk) {
} }
for i, v := range got.Constants { for i, v := range got.Constants {
if v != want.Constants[i] { if i < len(want.Constants) && !v.Equals(want.Constants[i]) {
t.Errorf("constant %d does not match (%s and %s)", i, v.String(), want.Constants[i].String()) t.Errorf("constant %d does not match (%s and %s)", i, v.String(), want.Constants[i].String())
} }
} }
@ -49,7 +49,7 @@ func TestNewVM(t *testing.T) {
chunk := NewChunk([]Bytecode{ chunk := NewChunk([]Bytecode{
InstructionConstant, 0, InstructionConstant, 0,
}, []Value{ }, []Value{
NumberValue(0), &NumberValue{0},
}) })
stackSize := Pos(256) stackSize := Pos(256)
callstackSize := Pos(256) callstackSize := Pos(256)
@ -107,10 +107,10 @@ func GetExecutionTestData() map[string]struct {
InstructionAdd, InstructionAdd,
}, },
[]Value{ []Value{
NumberValue(1), NumberValue(2), &NumberValue{1}, &NumberValue{2},
}), }),
[]Value{ []Value{
NumberValue(3), &NumberValue{3},
}, },
}, },
"push_constant": { "push_constant": {
@ -119,11 +119,11 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 0, InstructionConstant, 0,
}, },
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
), ),
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
}, },
"push_true": { "push_true": {
@ -134,7 +134,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{ []Value{
BoolValue(true), &BoolValue{true},
}, },
}, },
"push_false": { "push_false": {
@ -145,7 +145,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{ []Value{
BoolValue(false), &BoolValue{false},
}, },
}, },
"push_nil": { "push_nil": {
@ -156,7 +156,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{ []Value{
NilValue{}, &NilValue{},
}, },
}, },
"empty": { "empty": {
@ -181,11 +181,11 @@ func GetExecutionTestData() map[string]struct {
InstructionDiv, InstructionDiv,
}, },
[]Value{ []Value{
NumberValue(2), NumberValue(1), NumberValue(5), NumberValue(6), &NumberValue{2}, &NumberValue{1}, &NumberValue{5}, &NumberValue{6},
}, },
), ),
[]Value{ []Value{
NumberValue((2.0 + 1.0) * 5.0 / (6.0 - 2.0)), &NumberValue{3.75},
}, },
}, },
"equality_true": { "equality_true": {
@ -196,11 +196,11 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals, InstructionEquals,
}, },
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
), ),
[]Value{ []Value{
BoolValue(true), &BoolValue{true},
}, },
}, },
"equality_false": { "equality_false": {
@ -211,11 +211,11 @@ func GetExecutionTestData() map[string]struct {
InstructionEquals, InstructionEquals,
}, },
[]Value{ []Value{
NumberValue(1), NumberValue(2), &NumberValue{1}, &NumberValue{2},
}, },
), ),
[]Value{ []Value{
BoolValue(false), &BoolValue{false},
}, },
}, },
"inequality_false": { "inequality_false": {
@ -226,11 +226,11 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual, InstructionNotEqual,
}, },
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
), ),
[]Value{ []Value{
BoolValue(false), &BoolValue{false},
}, },
}, },
"inequality_true": { "inequality_true": {
@ -241,11 +241,11 @@ func GetExecutionTestData() map[string]struct {
InstructionNotEqual, InstructionNotEqual,
}, },
[]Value{ []Value{
NumberValue(1), NumberValue(2), &NumberValue{1}, &NumberValue{2},
}, },
), ),
[]Value{ []Value{
BoolValue(true), &BoolValue{true},
}, },
}, },
"not_true": { "not_true": {
@ -257,7 +257,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{ []Value{
BoolValue(false), &BoolValue{false},
}, },
}, },
"not_false": { "not_false": {
@ -269,7 +269,7 @@ func GetExecutionTestData() map[string]struct {
[]Value{}, []Value{},
), ),
[]Value{ []Value{
BoolValue(true), &BoolValue{true},
}, },
}, },
"jump": { "jump": {
@ -280,11 +280,11 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), &NumberValue{0}, &NumberValue{1},
}, },
), ),
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
}, },
"jump_false/false": { "jump_false/false": {
@ -296,11 +296,11 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), &NumberValue{0}, &NumberValue{1},
}, },
), ),
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
}, },
}, },
"jump_false/true": { "jump_false/true": {
@ -312,11 +312,11 @@ func GetExecutionTestData() map[string]struct {
InstructionConstant, 1, // should execute InstructionConstant, 1, // should execute
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), &NumberValue{0}, &NumberValue{1},
}, },
), ),
[]Value{ []Value{
NumberValue(0), NumberValue(1), &NumberValue{0}, &NumberValue{1},
}, },
}, },
"declare_local": { "declare_local": {
@ -326,13 +326,13 @@ func GetExecutionTestData() map[string]struct {
InstructionDeclareLocal, 1, InstructionDeclareLocal, 1,
}, },
[]Value{ []Value{
NumberValue(0), StringValue("a"), &NumberValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(0), &NumberValue{0},
0, 0,
}, },
}, },
@ -346,13 +346,13 @@ func GetExecutionTestData() map[string]struct {
InstructionSetLocal, 1, // reassign InstructionSetLocal, 1, // reassign
}, },
[]Value{ []Value{
NumberValue(0), StringValue("a"), NumberValue(1), &NumberValue{0}, &StringValue{"a"}, &NumberValue{1},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(1), &NumberValue{1},
0, 0,
}, },
}, },
@ -365,16 +365,16 @@ func GetExecutionTestData() map[string]struct {
InstructionGetLocal, 1, // reassign InstructionGetLocal, 1, // reassign
}, },
[]Value{ []Value{
NumberValue(0), StringValue("a"), &NumberValue{0}, &StringValue{"a"},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(0), &NumberValue{0},
0, 0,
}, },
NumberValue(0), &NumberValue{0},
}, },
}, },
"get_reassigned_local": { "get_reassigned_local": {
@ -388,17 +388,17 @@ func GetExecutionTestData() map[string]struct {
InstructionGetLocal, 1, InstructionGetLocal, 1,
}, },
[]Value{ []Value{
NumberValue(0), StringValue("a"), NumberValue(1), &NumberValue{0}, &StringValue{"a"}, &NumberValue{1},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(1), &NumberValue{1},
0, 0,
}, },
NumberValue(0), &NumberValue{0},
NumberValue(1), &NumberValue{1},
}, },
}, },
"variable_scope": { "variable_scope": {
@ -416,15 +416,15 @@ func GetExecutionTestData() map[string]struct {
InstructionAscend, InstructionAscend,
}, },
[]Value{ []Value{
NumberValue(0), StringValue("a"), &NumberValue{0}, &StringValue{"a"},
NumberValue(1), StringValue("b"), &NumberValue{1}, &StringValue{"b"},
NumberValue(2), StringValue("c"), &NumberValue{2}, &StringValue{"c"},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"a", "a",
NumberValue(0), &NumberValue{0},
0, 0,
}, },
}, },
@ -438,9 +438,9 @@ func GetExecutionTestData() map[string]struct {
InstructionCall, InstructionCall,
}, },
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
NumberValue(2), &NumberValue{2},
FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []string{"a", "b"},
Chunk: NewChunk( Chunk: NewChunk(
@ -451,14 +451,14 @@ func GetExecutionTestData() map[string]struct {
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
StringValue("a"), StringValue("b"), &StringValue{"a"}, &StringValue{"b"},
}, },
), ),
}, },
}, },
), ),
[]Value{ []Value{
NumberValue(3), &NumberValue{3},
}, },
}, },
"function_calling_function": { "function_calling_function": {
@ -472,9 +472,9 @@ func GetExecutionTestData() map[string]struct {
InstructionCall, InstructionCall,
}, },
[]Value{ []Value{
NumberValue(1), &NumberValue{1},
NumberValue(2), &NumberValue{2},
FunctionValue{ &FunctionValue{
Name: "sum", Name: "sum",
Params: []string{"a", "b"}, Params: []string{"a", "b"},
Chunk: NewChunk( Chunk: NewChunk(
@ -487,11 +487,11 @@ func GetExecutionTestData() map[string]struct {
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
StringValue("a"), StringValue("b"), StringValue("square"), &StringValue{"a"}, &StringValue{"b"}, &StringValue{"square"},
}, },
), ),
}, },
FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []string{"n"},
Chunk: NewChunk( Chunk: NewChunk(
@ -502,17 +502,17 @@ func GetExecutionTestData() map[string]struct {
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
StringValue("n"), &StringValue{"n"},
}, },
), ),
}, },
StringValue("square"), &StringValue{"square"},
}, },
), ),
[]Value{ []Value{
&VariableValue{ &VariableValue{
"square", "square",
FunctionValue{ &FunctionValue{
Name: "square", Name: "square",
Params: []string{"n"}, Params: []string{"n"},
Chunk: NewChunk( Chunk: NewChunk(
@ -523,13 +523,13 @@ func GetExecutionTestData() map[string]struct {
InstructionReturn, InstructionReturn,
}, },
[]Value{ []Value{
StringValue("n"), &StringValue{"n"},
}, },
), ),
}, },
0, 0,
}, },
NumberValue(5), &NumberValue{5},
}, },
}, },
} }
@ -542,7 +542,7 @@ func TestVM_Execution(t *testing.T) {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
vm := NewVM(test.chunk, 256, 256) vm := NewVM(test.chunk, 256, 256)
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
CompareStacks(t, test.resultingStack, vm.stack) CompareStacks(t, test.resultingStack, vm.stack)
@ -557,7 +557,7 @@ func BenchmarkVM_Execution(b *testing.B) {
b.Run(name, func(b *testing.B) { b.Run(name, func(b *testing.B) {
for n := 0; n < b.N; n++ { for n := 0; n < b.N; n++ {
vm := NewVM(test.chunk, 256, 256) vm := NewVM(test.chunk, 256, 256)
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
} }
}) })
@ -571,7 +571,7 @@ func TestVM_NextByte(t *testing.T) {
InstructionConstant, 0, InstructionConstant, 0,
}, },
[]Value{ []Value{
NumberValue(0), &NumberValue{0},
}, },
), ),
16, 16,
@ -682,7 +682,7 @@ func TestVM_Jump(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), NumberValue(2), &NumberValue{0}, &NumberValue{1}, &NumberValue{2},
}, },
), ),
16, 16,
@ -707,7 +707,7 @@ func TestVM_JumpFalse(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), NumberValue(2), &NumberValue{0}, &NumberValue{1}, &NumberValue{2},
}, },
), ),
16, 16,
@ -733,7 +733,7 @@ func TestVM_DontJumpFalse(t *testing.T) {
InstructionConstant, 2, InstructionConstant, 2,
}, },
[]Value{ []Value{
NumberValue(0), NumberValue(1), NumberValue(2), &NumberValue{0}, &NumberValue{1}, &NumberValue{2},
}, },
), ),
16, 16,

View file

@ -1,5 +1,5 @@
assert(1+1 == 2) assertEq(1+1, 2)
assert(3*2 == 6) assertEq(3*2, 6)
assert(3/4 == 0.75) assertEq(3/4, 0.75)
assert(2 - 5 == -3) assertEq(2 - 5, -3)

View file

@ -1,11 +1,11 @@
# Basic equality # Basic equality
assert(1 == 1) assertEq(1, 1)
assert(0 == 0) assertEq(0, 0)
assert("" == "") assertEq("", "")
assert([] == []) assertEq([], [])
assert([3, 1, 4, 1] == [3, 1, 4, 1]) assertEq([3, 1, 4, 1], [3, 1, 4, 1])
assert([true, 1024, nil, "Hello world!"] == [true, 1024, nil, "Hello world!"]) assertEq([true, 1024, nil, "Hello world!"], [true, 1024, nil, "Hello world!"])
# Inequality # Inequality
assert(2 != 3) assertNotEq(2, 3)

View file

@ -2,10 +2,10 @@ list := []
x := 1 x := 1
while x <= 1000 { while x <= 1000 {
list = list.append(x) list.append(x)
assert(list.reduce(func(tot, a){ assertEq(list.reduce(func(tot, a){
return tot + a return tot + a
}, 0) == x*(x + 1)/2) }, 0), x*(x + 1)/2)
x = x + 1 x = x + 1
} }
@ -17,8 +17,8 @@ func sum(a, b) {
list = [] list = []
x = 1 x = 1
while x <= 100 { while x <= 100 {
list = list.append(2*x - 1) list.append(2*x - 1)
assert(list.reduce(sum, 0) == x*x) assertEq(list.reduce(sum, 0), x*x)
x = x + 1 x = x + 1
} }

View file

@ -3,8 +3,7 @@ fibonacci_numbers := [
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377,
610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657,
46368, 75025, 121393, 196418, 317811, 514229, 832040, 46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352
24157817, 39088169, 63245986, 102334155
] ]
func fib(n) { func fib(n) {
@ -15,14 +14,21 @@ func fib(n) {
return fib(n-1) + fib(n-2) return fib(n-1) + fib(n-2)
} }
n := 0
while n < fibonacci_numbers.length() {
print("_")
n = n + 1
}
write("")
x := 0 x := 0
while x < fibonacci_numbers.length() { while x < fibonacci_numbers.length() {
n := fib(x) n := fib(x)
print("assert ") assertEq(n, fibonacci_numbers.at(x))
print(n) print("*")
print(" == ")
write(fibonacci_numbers.at(x))
assert(n == fibonacci_numbers.at(x))
x = x + 1 x = x + 1
} }
write("")

View file

@ -3,10 +3,10 @@ a := 2
{ {
a := 3 a := 3
assert(a == 3) assertEq(a, 3)
a = 4 a = 4
assert(a == 4) assertEq(a, 4)
} }
assert(a == 2) assertEq(a, 2)

View file

@ -5,8 +5,8 @@ func sum(a, b) {
breakpoint breakpoint
assert(sum(1, 2) == 3) assertEq(sum(1, 2), 3)
breakpoint breakpoint
assert(sum(3, 3) == 6) assertEq(sum(3, 3), 6)
breakpoint breakpoint

View file

@ -96,7 +96,7 @@ func run(this js.Value, args []js.Value) interface{} {
vm := core.NewVM(compiler.Chunk, 256, 256) vm := core.NewVM(compiler.Chunk, 256, 256)
// overwrite output // overwrite output
vm.SetGlobal("write", core.BuiltinFunctionValue{ vm.SetGlobal("write", &core.BuiltinFunctionValue{
Name: "write", Name: "write",
Parameters: []string{"value"}, Parameters: []string{"value"},
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) { F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
@ -105,7 +105,7 @@ func run(this js.Value, args []js.Value) interface{} {
return nil, nil return nil, nil
}, },
}) })
vm.SetGlobal("print", core.BuiltinFunctionValue{ vm.SetGlobal("print", &core.BuiltinFunctionValue{
Name: "print", Name: "print",
Parameters: []string{"value"}, Parameters: []string{"value"},
F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) { F: func(vm *core.VM, this core.Value, v map[string]core.Value) (core.Value, error) {
@ -115,7 +115,7 @@ func run(this js.Value, args []js.Value) interface{} {
}, },
}) })
for vm.HasNext() && vm.Next() { for vm.Next() {
} }
log.Println("Finished executing") log.Println("Finished executing")