844 lines
15 KiB
Go
844 lines
15 KiB
Go
package core
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type ValueType int
|
|
|
|
const (
|
|
NilValueType ValueType = iota
|
|
BoolValueType
|
|
FloatValueType
|
|
IntegerValueType
|
|
StringValueType
|
|
ListValueType
|
|
TupleValueType
|
|
ObjectValueType
|
|
FunctionValueType
|
|
BuiltinFunctionValueType
|
|
RecordValueType
|
|
)
|
|
|
|
func (v ValueType) String() string {
|
|
switch v {
|
|
case NilValueType:
|
|
return "nil"
|
|
case BoolValueType:
|
|
return "bool"
|
|
case ObjectValueType:
|
|
return "object"
|
|
case FloatValueType:
|
|
return "float"
|
|
case IntegerValueType:
|
|
return "int"
|
|
case StringValueType:
|
|
return "string"
|
|
case ListValueType:
|
|
return "list"
|
|
case TupleValueType:
|
|
return "tuple"
|
|
case FunctionValueType:
|
|
return "function"
|
|
case BuiltinFunctionValueType:
|
|
return "builtin function"
|
|
case RecordValueType:
|
|
return "record"
|
|
}
|
|
|
|
return "undefined"
|
|
}
|
|
|
|
// GoToValue convert go values to anglais VM-values. Works for some values (nil, bool, float64, int, string, slices, maps)
|
|
func GoToValue(gov any) Value {
|
|
switch v := gov.(type) {
|
|
case nil:
|
|
return &NilValue{}
|
|
case bool:
|
|
return &BoolValue{
|
|
v,
|
|
}
|
|
case int:
|
|
return &IntegerValue{
|
|
new(big.Int).SetInt64(int64(v)),
|
|
}
|
|
case *big.Int:
|
|
return &IntegerValue{
|
|
v,
|
|
}
|
|
case float64:
|
|
return &FloatValue{
|
|
v,
|
|
}
|
|
case string:
|
|
return &StringValue{
|
|
v,
|
|
}
|
|
case map[string]any:
|
|
values := map[string]Value{}
|
|
for key, value := range v {
|
|
values[key] = GoToValue(value)
|
|
}
|
|
|
|
return &ObjectValue{
|
|
values,
|
|
}
|
|
case Value:
|
|
return v
|
|
default:
|
|
if reflect.TypeOf(v).Kind() == reflect.Slice {
|
|
return &ListValue{
|
|
v.([]Value),
|
|
}
|
|
}
|
|
}
|
|
|
|
panic(fmt.Sprintf("unsupported automatic type conversion: %v (%s)", gov, reflect.TypeOf(gov)))
|
|
}
|
|
|
|
type Value interface {
|
|
// Type get the type of the value (a ValueType)
|
|
Type() ValueType
|
|
|
|
// String Convert this value to a string fit for human consumption
|
|
String() string
|
|
|
|
// DebugString get a debug string of this value. Used in lists.
|
|
DebugString() string
|
|
|
|
// Equals Check if two values are equal
|
|
Equals(Value) bool
|
|
|
|
// Get a member from the value. An error is returned if the member does not exist
|
|
Get(string) (Value, error)
|
|
|
|
// Copy create a copy of the value. A copy is a direct copy for small data (numbers) and a copy of pointer
|
|
// for bigger data (lists, tuples, dicts)
|
|
Copy() Value
|
|
|
|
// Clone create a clone of the value. A clone is new data for all data.
|
|
//Clone() Value
|
|
}
|
|
|
|
type NilValue struct{}
|
|
|
|
func (v *NilValue) Type() ValueType {
|
|
return NilValueType
|
|
}
|
|
|
|
func (v *NilValue) String() string {
|
|
return "nil"
|
|
}
|
|
|
|
func (v *NilValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *NilValue) Equals(other Value) bool {
|
|
return other.Type() == NilValueType
|
|
}
|
|
|
|
func (v *NilValue) Get(_ string) (Value, error) {
|
|
return nil, errors.New("nil has no properties")
|
|
}
|
|
|
|
func (v *NilValue) Copy() Value {
|
|
return &NilValue{}
|
|
}
|
|
|
|
type BoolValue struct {
|
|
Boolean bool
|
|
}
|
|
|
|
func (v *BoolValue) Type() ValueType {
|
|
return BoolValueType
|
|
}
|
|
|
|
func (v *BoolValue) String() string {
|
|
if v.Boolean {
|
|
return "true"
|
|
} else {
|
|
return "false"
|
|
}
|
|
}
|
|
|
|
func (v *BoolValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *BoolValue) Equals(other Value) bool {
|
|
return other.Type() == BoolValueType && other.(*BoolValue).Boolean == v.Boolean
|
|
}
|
|
|
|
func (v *BoolValue) Get(_ string) (Value, error) {
|
|
return nil, errors.New("booleans have no properties")
|
|
}
|
|
|
|
func (v *BoolValue) Copy() Value {
|
|
return &BoolValue{
|
|
v.Boolean,
|
|
}
|
|
}
|
|
|
|
// ObjectValue An object with any number of members (key-value pairs)
|
|
type ObjectValue struct {
|
|
Members map[string]Value
|
|
}
|
|
|
|
func (v *ObjectValue) Type() ValueType {
|
|
return ObjectValueType
|
|
}
|
|
|
|
func (v *ObjectValue) String() string {
|
|
out := "{"
|
|
for key, value := range v.Members {
|
|
if out != "{" {
|
|
out += ", "
|
|
}
|
|
|
|
out += fmt.Sprintf("%q=%s", key, value.DebugString())
|
|
}
|
|
out += "}"
|
|
|
|
return out
|
|
}
|
|
|
|
func (v *ObjectValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *ObjectValue) Equals(other Value) bool {
|
|
object, ok := other.(*ObjectValue)
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
for key, value := range v.Members {
|
|
if !object.Members[key].Equals(value) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
var ObjectPrototype = map[string]*BuiltinFunctionValue{
|
|
"set": {
|
|
"set",
|
|
&FunctionSignature{
|
|
[]TypeSignature{&StringSignature{}, &ListSignature{}},
|
|
&NilSignature{},
|
|
},
|
|
func(vm *VM, _this Value, params []Value) (Value, error) {
|
|
this := _this.(*ObjectValue)
|
|
|
|
p := params[1]
|
|
v, ok := params[0].(*StringValue)
|
|
if !ok {
|
|
return nil, errors.New("property is not a string")
|
|
}
|
|
|
|
this.Members[v.Text] = p
|
|
|
|
return &NilValue{}, nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
|
|
func (v *ObjectValue) Get(key string) (Value, error) {
|
|
if member, ok := v.Members[key]; ok {
|
|
return member, nil
|
|
} else if p, ok := ObjectPrototype[key]; ok {
|
|
return p, nil
|
|
} else {
|
|
return nil, errors.New("no property found with name \"" + key + "\"")
|
|
}
|
|
}
|
|
|
|
func (v *ObjectValue) Copy() Value {
|
|
m := make(map[string]Value, len(v.Members))
|
|
|
|
for name, mem := range v.Members {
|
|
m[name] = mem.Copy()
|
|
}
|
|
|
|
return &ObjectValue{
|
|
m,
|
|
}
|
|
}
|
|
|
|
// FloatValue floating-point values
|
|
type FloatValue struct {
|
|
Number float64
|
|
}
|
|
|
|
const FloatSize int = 64
|
|
|
|
func (v *FloatValue) Type() ValueType {
|
|
return FloatValueType
|
|
}
|
|
|
|
func (v *FloatValue) String() string {
|
|
s := strconv.FormatFloat(v.Number, 'g', -1, FloatSize)
|
|
if strings.Index(s, ".") == -1 {
|
|
s += ".0"
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
func (v *FloatValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *FloatValue) Equals(other Value) bool {
|
|
return other.Type() == FloatValueType && other.(*FloatValue).Number == v.Number
|
|
}
|
|
|
|
func (v *FloatValue) Get(_ string) (Value, error) {
|
|
// TODO maybe add standard functions for number values?
|
|
return nil, errors.New("numbers have no properties")
|
|
}
|
|
|
|
func (v *FloatValue) Copy() Value {
|
|
return &FloatValue{
|
|
v.Number,
|
|
}
|
|
}
|
|
|
|
// IntegerValue whole number/integer values
|
|
type IntegerValue struct {
|
|
Number *big.Int
|
|
}
|
|
|
|
func (v *IntegerValue) Type() ValueType {
|
|
return IntegerValueType
|
|
}
|
|
|
|
func (v *IntegerValue) String() string {
|
|
return v.Number.String()
|
|
}
|
|
|
|
func (v *IntegerValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *IntegerValue) Equals(other Value) bool {
|
|
return other.Type() == IntegerValueType && other.(*IntegerValue).Number.Cmp(v.Number) == 0
|
|
}
|
|
|
|
func (v *IntegerValue) Get(_ string) (Value, error) {
|
|
return nil, errors.New("numbers have no properties")
|
|
}
|
|
|
|
func (v *IntegerValue) Copy() Value {
|
|
return &IntegerValue{
|
|
new(big.Int).Set(v.Number),
|
|
}
|
|
}
|
|
|
|
type StringValue struct {
|
|
Text string
|
|
}
|
|
|
|
func (v *StringValue) Type() ValueType {
|
|
return StringValueType
|
|
}
|
|
|
|
func (v *StringValue) String() string {
|
|
return v.Text
|
|
}
|
|
|
|
func (v *StringValue) DebugString() string {
|
|
return "\"" + v.String() + "\""
|
|
}
|
|
|
|
func (v *StringValue) Equals(other Value) bool {
|
|
return other.Type() == StringValueType && other.(*StringValue).Text == v.Text
|
|
}
|
|
|
|
var StringPrototype = map[string]*BuiltinFunctionValue{
|
|
"split": {
|
|
"split",
|
|
&FunctionSignature{
|
|
[]TypeSignature{&StringSignature{}},
|
|
&ListSignature{
|
|
&StringSignature{},
|
|
},
|
|
},
|
|
func(vm *VM, this Value, v []Value) (Value, error) {
|
|
str := this.(*StringValue).String()
|
|
sep := v[0].(*StringValue).String()
|
|
|
|
prev := 0
|
|
var out []Value
|
|
for i := 1; i < len(str)-len(sep); i++ {
|
|
if str[i:i+len(sep)] == sep {
|
|
out = append(out, &StringValue{str[prev:i]})
|
|
prev = i + len(sep)
|
|
}
|
|
}
|
|
|
|
if prev != len(str) {
|
|
out = append(out, &StringValue{str[prev:]})
|
|
}
|
|
|
|
return &ListValue{out}, nil
|
|
},
|
|
nil,
|
|
true,
|
|
},
|
|
"length": {
|
|
Name: "length",
|
|
Signature: &FunctionSignature{
|
|
[]TypeSignature{},
|
|
&IntegerSignature{},
|
|
},
|
|
F: func(vm *VM, this Value, _ []Value) (Value, error) {
|
|
return GoToValue(len(this.(*StringValue).Text)), nil
|
|
},
|
|
},
|
|
"at": {
|
|
Name: "at",
|
|
Signature: &FunctionSignature{
|
|
[]TypeSignature{&IntegerSignature{}},
|
|
&StringSignature{},
|
|
},
|
|
F: func(vm *VM, this Value, args []Value) (Value, error) {
|
|
i := int(args[0].(*IntegerValue).Number.Int64())
|
|
|
|
if i < 0 || i >= len(this.(*StringValue).Text) {
|
|
return nil, errors.New("index is out of range")
|
|
}
|
|
|
|
return GoToValue(string(this.(*StringValue).Text[i])), nil
|
|
},
|
|
},
|
|
}
|
|
|
|
func (v *StringValue) Get(key string) (Value, error) {
|
|
if prop, ok := StringPrototype[key]; ok {
|
|
return prop, nil
|
|
}
|
|
|
|
return nil, errors.New(fmt.Sprintf("string has no property \"%s\"", key))
|
|
}
|
|
|
|
func (v *StringValue) Copy() Value {
|
|
return &StringValue{
|
|
v.Text,
|
|
}
|
|
}
|
|
|
|
// ListValue a dynamic list of values
|
|
type ListValue struct {
|
|
Items []Value
|
|
}
|
|
|
|
func (v *ListValue) Type() ValueType {
|
|
return ListValueType
|
|
}
|
|
|
|
func (v *ListValue) String() string {
|
|
var out strings.Builder
|
|
out.WriteString("[")
|
|
for i, item := range v.Items {
|
|
if i != 0 {
|
|
out.WriteString(", ")
|
|
}
|
|
out.WriteString(item.DebugString())
|
|
}
|
|
out.WriteString("]")
|
|
|
|
return out.String()
|
|
}
|
|
|
|
func (v *ListValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *ListValue) Equals(other Value) bool {
|
|
if other.Type() != ListValueType {
|
|
return false
|
|
}
|
|
|
|
l := other.(*ListValue)
|
|
|
|
if len(v.Items) != len(l.Items) {
|
|
return false
|
|
}
|
|
|
|
for i, item := range v.Items {
|
|
if !item.Equals(l.Items[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
var ListPrototype = map[string]*BuiltinFunctionValue{
|
|
"append": {
|
|
"append",
|
|
&FunctionSignature{
|
|
[]TypeSignature{&AnySignature{}},
|
|
&NilSignature{},
|
|
},
|
|
func(_ *VM, this Value, v []Value) (Value, error) {
|
|
this.(*ListValue).Items = append(this.(*ListValue).Items, v[0])
|
|
return &NilValue{}, nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
"at": {
|
|
"at",
|
|
&FunctionSignature{
|
|
[]TypeSignature{
|
|
&IntegerSignature{},
|
|
},
|
|
&InnerSignature{},
|
|
},
|
|
func(_ *VM, this Value, p []Value) (Value, error) {
|
|
items := this.(*ListValue).Items
|
|
index := int(p[0].(*IntegerValue).Number.Int64())
|
|
|
|
if index >= len(items) {
|
|
return nil, errors.New(fmt.Sprintf("list index %x out of range", index))
|
|
}
|
|
|
|
return items[index], nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
"put": {
|
|
"put",
|
|
&FunctionSignature{
|
|
[]TypeSignature{
|
|
&FloatSignature{}, &InnerSignature{},
|
|
},
|
|
&NilSignature{},
|
|
},
|
|
func(_ *VM, this Value, args []Value) (Value, error) {
|
|
l := this.(*ListValue)
|
|
i := int(args[0].(*FloatValue).Number)
|
|
v := args[1]
|
|
|
|
// bounds check
|
|
if i < 0 || i >= len(l.Items) {
|
|
return nil, errors.New(fmt.Sprintf("index %x out of range", i))
|
|
}
|
|
|
|
l.Items[i] = v
|
|
|
|
return &NilValue{}, nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
"length": {
|
|
"length",
|
|
&FunctionSignature{
|
|
[]TypeSignature{},
|
|
&IntegerSignature{},
|
|
},
|
|
func(_ *VM, this Value, _ []Value) (Value, error) {
|
|
return GoToValue(len(this.(*ListValue).Items)), nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
"reduce": {
|
|
"reduce",
|
|
&FunctionSignature{
|
|
[]TypeSignature{
|
|
&FunctionSignature{
|
|
[]TypeSignature{
|
|
&AnySignature{},
|
|
&AnySignature{},
|
|
},
|
|
&AnySignature{},
|
|
},
|
|
&AnySignature{},
|
|
},
|
|
&AnySignature{},
|
|
},
|
|
func(vm *VM, value Value, m []Value) (Value, error) {
|
|
list := value.(*ListValue)
|
|
f := m[0]
|
|
sum := m[1]
|
|
|
|
for _, v := range list.Items {
|
|
result, err := vm.Call(f, []Value{sum, v})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
sum = result
|
|
}
|
|
|
|
return sum, nil
|
|
},
|
|
nil,
|
|
false,
|
|
},
|
|
}
|
|
|
|
func (v *ListValue) Get(key string) (Value, error) {
|
|
if prop, ok := ListPrototype[key]; ok {
|
|
return prop, nil
|
|
}
|
|
|
|
return nil, errors.New(fmt.Sprintf("list has no property \"%s\"", key))
|
|
}
|
|
|
|
func (v *ListValue) Copy() Value {
|
|
return &ListValue{
|
|
v.Items,
|
|
}
|
|
}
|
|
|
|
type TupleValue struct {
|
|
Items []Value
|
|
}
|
|
|
|
func (v *TupleValue) Type() ValueType {
|
|
return TupleValueType
|
|
}
|
|
|
|
func (v *TupleValue) String() string {
|
|
var out strings.Builder
|
|
out.WriteString("(")
|
|
for i, item := range v.Items {
|
|
if i != 0 {
|
|
out.WriteString(", ")
|
|
}
|
|
out.WriteString(item.DebugString())
|
|
}
|
|
|
|
if len(v.Items) <= 1 {
|
|
out.WriteString(",")
|
|
}
|
|
out.WriteString(")")
|
|
|
|
return out.String()
|
|
}
|
|
|
|
func (v *TupleValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *TupleValue) Copy() Value {
|
|
return &TupleValue{
|
|
v.Items,
|
|
}
|
|
}
|
|
|
|
func (v *TupleValue) Equals(other Value) bool {
|
|
if other.Type() != TupleValueType {
|
|
return false
|
|
}
|
|
|
|
t := other.(*TupleValue).Items
|
|
for i, item := range v.Items {
|
|
if !item.Equals(t[i]) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
var TuplePrototype = map[string]*BuiltinFunctionValue{
|
|
"at": {
|
|
"at",
|
|
&FunctionSignature{
|
|
[]TypeSignature{&IntegerSignature{}},
|
|
&InnerSignature{},
|
|
},
|
|
func(vm *VM, this Value, args []Value) (Value, error) {
|
|
i := args[0].(*IntegerValue).Number.Int64()
|
|
|
|
if i < 0 || i >= int64(len(this.(*TupleValue).Items)) {
|
|
return nil, errors.New(fmt.Sprintf("index %x out of range", i))
|
|
}
|
|
|
|
return this.(*TupleValue).Items[i], nil
|
|
},
|
|
nil,
|
|
true,
|
|
},
|
|
}
|
|
|
|
func (v *TupleValue) Get(key string) (Value, error) {
|
|
if prop, ok := TuplePrototype[key]; ok {
|
|
return prop, nil
|
|
}
|
|
return nil, errors.New(fmt.Sprintf("tuple has no property \"%s\"", key))
|
|
}
|
|
|
|
type FunctionValue struct {
|
|
Name string
|
|
Params []FunctionParameter
|
|
Yield TypeSignature
|
|
Chunk *Chunk
|
|
Parent Value
|
|
Scope *Scope
|
|
}
|
|
|
|
func (v *FunctionValue) Type() ValueType {
|
|
return FunctionValueType
|
|
}
|
|
|
|
func (v *FunctionValue) String() string {
|
|
return fmt.Sprintf("<function name=%s block=%p>", v.Name, v.Chunk)
|
|
}
|
|
|
|
func (v *FunctionValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *FunctionValue) Equals(other Value) bool {
|
|
return other.Type() == FunctionValueType &&
|
|
v.Chunk == other.(*FunctionValue).Chunk
|
|
}
|
|
|
|
func (v *FunctionValue) Get(_ string) (Value, error) {
|
|
return nil, errors.New("functions have no properties")
|
|
}
|
|
|
|
func (v *FunctionValue) Copy() Value {
|
|
return &FunctionValue{
|
|
v.Name,
|
|
v.Params,
|
|
v.Yield,
|
|
v.Chunk,
|
|
v.Parent,
|
|
v.Scope,
|
|
}
|
|
}
|
|
|
|
type BuiltinFunctionValue struct {
|
|
Name string
|
|
Signature *FunctionSignature
|
|
F func(*VM, Value, []Value) (Value, error)
|
|
Parent Value
|
|
Constant bool
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) Type() ValueType {
|
|
return BuiltinFunctionValueType
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) String() string {
|
|
return fmt.Sprintf("<function builtin name=%s>", v.Name)
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) Equals(other Value) bool {
|
|
return other.Type() == BuiltinFunctionValueType &&
|
|
v.Name == other.(*BuiltinFunctionValue).Name
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) Get(_ string) (Value, error) {
|
|
return nil, errors.New("functions have no properties")
|
|
}
|
|
|
|
func (v *BuiltinFunctionValue) Copy() Value {
|
|
return &BuiltinFunctionValue{
|
|
v.Name,
|
|
v.Signature,
|
|
v.F,
|
|
v.Parent,
|
|
v.Constant,
|
|
}
|
|
}
|
|
|
|
type RecordValue struct {
|
|
Entries map[string]Value
|
|
}
|
|
|
|
func (v *RecordValue) Type() ValueType {
|
|
return RecordValueType
|
|
}
|
|
|
|
func (v *RecordValue) String() string {
|
|
sb := strings.Builder{}
|
|
|
|
sb.WriteString("(")
|
|
|
|
n := 0
|
|
for prop, value := range v.Entries {
|
|
if n != 0 {
|
|
sb.WriteString(", ")
|
|
}
|
|
|
|
sb.WriteString(prop)
|
|
sb.WriteString(": ")
|
|
sb.WriteString(value.DebugString())
|
|
|
|
n += 1
|
|
}
|
|
|
|
if n == 1 {
|
|
sb.WriteString(",")
|
|
}
|
|
|
|
sb.WriteString(")")
|
|
|
|
return sb.String()
|
|
}
|
|
|
|
func (v *RecordValue) DebugString() string {
|
|
return v.String()
|
|
}
|
|
|
|
func (v *RecordValue) Copy() Value {
|
|
return &RecordValue{
|
|
v.Entries,
|
|
}
|
|
}
|
|
|
|
func (v *RecordValue) Equals(other Value) bool {
|
|
if other.Type() != RecordValueType {
|
|
return false
|
|
}
|
|
|
|
r := other.(*RecordValue)
|
|
|
|
if len(r.Entries) != len(v.Entries) {
|
|
return false
|
|
}
|
|
|
|
for key, val := range v.Entries {
|
|
oth, ok := r.Entries[key]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
if !val.Equals(oth) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (v *RecordValue) Get(key string) (Value, error) {
|
|
val, ok := v.Entries[key]
|
|
if !ok {
|
|
return nil, errors.New(fmt.Sprintf("record has no property \"%s\"", key))
|
|
}
|
|
|
|
return val, nil
|
|
}
|