anglais/core/values.go
2026-09-07 17:41:04 +02:00

848 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 interface{}) 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]interface{}:
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)
// Clone create a clone of the value. The returned value is a pointer to a new value of the same type as the value.
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) Clone() 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) Clone() 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) Clone() Value {
m := make(map[string]Value, len(v.Members))
for name, mem := range v.Members {
m[name] = mem.Clone()
}
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) Clone() 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) Clone() 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:len(str)]})
}
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) Clone() 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 {
out := "["
for i, item := range v.Items {
if i != 0 {
out += ", "
}
out += item.DebugString()
}
out += "]"
return out
}
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) Clone() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &ListValue{
n,
}
}
type TupleValue struct {
Items []Value
}
func (v *TupleValue) Type() ValueType {
return TupleValueType
}
func (v *TupleValue) String() string {
out := "("
for i, item := range v.Items {
if i != 0 {
out += ", "
}
out += item.DebugString()
}
if len(v.Items) <= 1 {
out += ","
}
out += ")"
return out
}
func (v *TupleValue) DebugString() string {
return v.String()
}
func (v *TupleValue) Clone() Value {
n := make([]Value, len(v.Items))
for i, item := range v.Items {
n[i] = item.Clone()
}
return &TupleValue{
n,
}
}
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": &BuiltinFunctionValue{
"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) Clone() 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) Clone() 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) Clone() 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
}