2024-08-28 08:52:07 +02:00
|
|
|
from dataclasses import dataclass, field
|
2024-08-28 13:31:17 +02:00
|
|
|
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:
|
2024-08-28 10:43:23 +02:00
|
|
|
# Record name
|
|
|
|
name: str
|
|
|
|
# Base type for inheritance/subconstructor logic
|
|
|
|
base: Union[None, "RecordType"]
|
2024-08-28 08:52:07 +02:00
|
|
|
|
2024-08-28 10:43:23 +02:00
|
|
|
# TODO: Record prototypes?
|
2024-08-28 08:52:07 +02:00
|
|
|
|
2024-08-28 15:18:39 +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
|
2024-08-28 15:18:39 +02:00
|
|
|
attr: dict[str, Union["JuiValue", "juic.eval.Thunk"]]
|
2024-08-28 08:52:07 +02:00
|
|
|
# Children elements
|
2024-08-28 15:18:39 +02:00
|
|
|
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,
|
2024-08-28 19:55:38 +02:00
|
|
|
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):
|
2024-08-28 13:31:17 +02:00
|
|
|
return type(value) in [Record, juic.eval.PartialRecordSnapshot]
|
2024-08-28 08:52:07 +02:00
|
|
|
|
|
|
|
def juiIsConstructible(value):
|
2024-08-28 15:18:39 +02:00
|
|
|
return type(value) in [RecordType, RecordCtor]
|
2024-08-28 08:52:07 +02:00
|
|
|
|
|
|
|
def juiIsRecordUpdatable(value):
|
|
|
|
return type(value) == Record
|
2024-08-28 10:43:23 +02:00
|
|
|
|
|
|
|
# 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)
|
2024-08-28 19:55:38 +02:00
|
|
|
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)
|
2024-08-28 10:43:23 +02:00
|
|
|
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())
|
2024-08-28 15:18:39 +02:00
|
|
|
if len(r.attr) and len(r.children):
|
|
|
|
s += "; "
|
2024-08-28 10:43:23 +02:00
|
|
|
s += "; ".join(juiValueString(x) for x in r.children)
|
|
|
|
return s + "}"
|
|
|
|
raise NotImplementedError
|
2024-08-28 13:31:17 +02:00
|
|
|
case juic.eval.Thunk() as th:
|
2024-08-28 10:43:23 +02:00
|
|
|
return str(th)
|
2024-08-28 13:31:17 +02:00
|
|
|
case juic.eval.PartialRecordSnapshot() as prs:
|
2024-08-28 10:43:23 +02:00
|
|
|
return str(prs)
|
|
|
|
case _:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
# Check whether two *forced* values are equal
|
|
|
|
def juiValuesEqual(v1, v2):
|
2024-08-28 13:31:17 +02:00
|
|
|
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)
|
2024-08-28 10:43:23 +02:00
|
|
|
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
|
2024-08-28 13:31:17 +02:00
|
|
|
case juic.eval.PartialRecordSnapshot(), \
|
|
|
|
juic.eval.PartialRecordSnapshot():
|
2024-08-28 10:43:23 +02:00
|
|
|
raise Exception("cannot compare PRSs")
|
|
|
|
case _, _:
|
|
|
|
raise Exception(f"invalid types for comparison: {type(v1)}, {type(v2)}")
|