JustUI/juic/datatypes.py

196 lines
6 KiB
Python
Raw Normal View History

2024-08-28 08:52:07 +02:00
from dataclasses import dataclass, field
from typing import Callable, Union, TYPE_CHECKING
import juic
if TYPE_CHECKING:
import juic.eval
2024-08-28 08:52:07 +02:00
# Mapping of Jui types to Python types:
# Null None
# Booleans bool
# Integers int
# Floats float
# Strings str
# C/C++ refs CXXQualid
# List list
# "this" ThisRef
# Functions Function, BuiltinFunction
# Record types RecordType
# Records Record
@dataclass
class CXXQualid:
qualid: str
@dataclass
class BuiltinFunction:
func: Callable
@dataclass
class Function:
# Context in which the function gets evaluated
closure: "juic.eval.Closure"
# Expression node to evaluate when calling the function
body: "juic.parser.Node"
# Parameter names, must all be unique. May be empty
2024-08-28 13:58:27 +02:00
params: list[str] = field(default_factory=list)
2024-08-28 08:52:07 +02:00
# Name of variadic argument if one; must also be unique
variadic: str | None = None
@dataclass
class RecordType:
# Record name
name: str
# Base type for inheritance/subconstructor logic
base: Union[None, "RecordType"]
2024-08-28 08:52:07 +02:00
# TODO: Record prototypes?
2024-08-28 08:52:07 +02:00
@dataclass
class RecordCtor:
func: Function
2024-08-28 08:52:07 +02:00
@dataclass
class Record:
# A record type if it's pure, or a thunk with the base object otherwise.
base: Union[RecordType, "juic.eval.Thunk"]
# Standard key-value attribute pairs
attr: dict[str, Union["JuiValue", "juic.eval.Thunk"]]
2024-08-28 08:52:07 +02:00
# Children elements
children: list[Union["JuiValue", "juic.eval.Thunk"]]
2024-08-28 08:52:07 +02:00
# TODO: Keep track of variables that are not fields, i.e. "methods"?
# scope: dict[str, "JuiValue"]
# TODO: Labels
# labels: dict[str, "JuiValue"]
JuiValue = Union[None, bool, int, float, str, CXXQualid, list,
"juic.eval.PartialRecordSnapshot", BuiltinFunction, Function,
RecordType, RecordCtor, Record]
2024-08-28 08:52:07 +02:00
def juiIsArith(value):
return type(value) in [bool, int, float]
def juiIsAddable(value):
return juiIsArith(value) or type(value) in [str, list]
def juiIsComparable(value):
return type(value) in [None, bool, int, float, str]
def juiIsLogical(value):
return type(value) == bool
def juiIsUnpackable(value):
return type(value) == list
def juiIsCallable(value):
return type(value) in [BuiltinFunction, Function]
def juiIsProjectable(value):
return type(value) in [Record, juic.eval.PartialRecordSnapshot]
2024-08-28 08:52:07 +02:00
def juiIsConstructible(value):
return type(value) in [RecordType, RecordCtor]
2024-08-28 08:52:07 +02:00
def juiIsRecordUpdatable(value):
return type(value) == Record
# Generic functions on values
# String representation of a value
def juiValueString(v):
match v:
case None:
return "null"
case bool():
return str(v).lower()
case int() | float():
return str(v)
case str():
return repr(v)
case CXXQualid():
return "&" + v.qualid
case list():
return "[" + ", ".join(juiValueString(x) for x in v) + "]"
case BuiltinFunction():
return str(v)
case Function() as f:
p = f.params + (["..." + f.variadic] if f.variadic else [])
s = "fun(" + ", ".join(p) + ") => " + str(f.body)
s += " (in some closure)"
return s
case RecordCtor() as rc:
f = rc.func
p = f.params + (["..." + f.variadic] if f.variadic else [])
s = "rec(" + ", ".join(p) + ") => " + str(f.body)
s += " (in some closure)"
return s
case RecordType() as rt:
return str(rt)
case Record() as r:
s = r.base.name + " {"
s += "; ".join(x + ": " + juiValueString(y) for x, y in r.attr.items())
if len(r.attr) and len(r.children):
s += "; "
s += "; ".join(juiValueString(x) for x in r.children)
return s + "}"
raise NotImplementedError
case juic.eval.Thunk() as th:
return str(th)
case juic.eval.PartialRecordSnapshot() as prs:
return str(prs)
case _:
raise NotImplementedError
# Check whether two *forced* values are equal
def juiValuesEqual(v1, v2):
def unwrapThunk(v):
match v:
case juic.eval.Thunk() as th:
assert th.evaluated and not th.invalid
return unwrapThunk(th.result)
case _:
return v
v1 = unwrapThunk(v1)
v2 = unwrapThunk(v2)
match v1, v2:
case None, None:
return True
case bool(), bool():
return v1 == v2
case int(), int():
return v1 == v2
case float(), float():
return v1 == v2
case str(), str():
return v1 == v2
case CXXQualid(), CXXQualid():
return v1.qualid == v2.qualid
case list() as l1, list() as l2:
return len(l1) == len(l2) and \
all(juiValuesEqual(v1, v2) for v1, v2 in zip(l1, l2))
case BuiltinFunction(), BuiltinFunction():
return id(v1) == id(v2)
case Function(), Function():
raise Exception("cannot compare functions")
case RecordType(), RecordType():
return id(v1) == id(v2)
case Record() as r1, Record() as r2:
if not juiValuesEqual(r1.base, r2.base):
return False
if r1.attr.keys() != r2.attr.keys():
return False
if any(not juiValuesEqual(r1.attr[k], r2.attr[k]) for k in r1.attr):
return False
if len(r1.children) != len(r2.children):
return False
if any(not juiValuesEqual(v1, v2)
for v1, v2 in zip(r1.children, r2.children)):
return False
return True
case juic.eval.PartialRecordSnapshot(), \
juic.eval.PartialRecordSnapshot():
raise Exception("cannot compare PRSs")
case _, _:
raise Exception(f"invalid types for comparison: {type(v1)}, {type(v2)}")