juic: add some record processing, start checking with mypy

This commit is contained in:
Lephenixnoir 2024-08-28 13:31:17 +02:00
parent e79ba0056b
commit 4579acc0f4
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
6 changed files with 82 additions and 31 deletions

View file

@ -2,7 +2,7 @@ from juic.datatypes import *
from juic.eval import *
def juiPrint(*args):
print("[print]", *args)
print("[print]", *(juiValueString(a) for a in args))
def juiLen(x):
if type(x) not in [str, list]:

View file

@ -1,5 +1,9 @@
from dataclasses import dataclass, field
from typing import Callable, Union
from typing import Callable, Union, TYPE_CHECKING
import juic
if TYPE_CHECKING:
import juic.eval
# Mapping of Jui types to Python types:
# Null None
@ -62,9 +66,9 @@ 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, "juic.value.Thunk"]
attr: dict[str, "juic.eval.Thunk"]
# Children elements
children: list["juic.value.Thunk"]
children: list["juic.eval.Thunk"]
# TODO: Keep track of variables that are not fields, i.e. "methods"?
# scope: dict[str, "JuiValue"]
# TODO: Labels
@ -93,7 +97,7 @@ def juiIsCallable(value):
return type(value) in [BuiltinFunction, Function]
def juiIsProjectable(value):
return type(value) in [ThisRef, Record]
return type(value) in [Record, juic.eval.PartialRecordSnapshot]
def juiIsConstructible(value):
return type(value) in [RecordType, Function]
@ -133,15 +137,25 @@ def juiValueString(v):
s += "; ".join(juiValueString(x) for x in r.children)
return s + "}"
raise NotImplementedError
case Thunk() as th:
case juic.eval.Thunk() as th:
return str(th)
case PartialRecordSnapshot() as prs:
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
@ -177,11 +191,8 @@ def juiValuesEqual(v1, v2):
for v1, v2 in zip(r1.children, r2.children)):
return False
return True
case Thunk() as th1, Thunk() as th2:
assert th1.evaluated and not th1.invalid
assert th2.evaluated and not th2.invalid
return juiValuesEqual(th1.result, th2.result)
case PartialRecordSnapshot(), PartialRecordSnapshot():
case juic.eval.PartialRecordSnapshot(), \
juic.eval.PartialRecordSnapshot():
raise Exception("cannot compare PRSs")
case _, _:
raise Exception(f"invalid types for comparison: {type(v1)}, {type(v2)}")

View file

@ -106,17 +106,17 @@ class Thunk:
def __str__(self):
s = "Thunk("
if th._running:
if self._running:
s += "running, "
if th.evaluated:
if self.evaluated:
s += "evaluated"
if th.invalid:
if self.invalid:
s += ", invalid"
else:
s += " = " + juiValueString(th.result)
s += " = " + juiValueString(self.result)
else:
s += "unevaluated"
s += "){ " + str(th.ast) + " }"
s += "){ " + str(self.ast) + " }"
return s
# Object representing the interpretation of a partially-built record. In a
@ -137,7 +137,7 @@ class PartialRecordSnapshot:
base: None | Thunk = None
def __str__(self):
return "<A PRS AAAAAAH>" # TODO
return "<PartialRecordSnapshot>" # TODO
def copy(self):
return PartialRecordSnapshot(self.fieldThunks.copy(), self.base)
@ -344,10 +344,21 @@ class Context:
return self.evalCall(f, [X])
case Node.T.THIS, []:
raise NotImplementedError
v = self.lookup("this")
if v is None:
raise JuiNameError(f"no 'this' in current context")
assert isinstance(v, PartialRecordSnapshot)
return v
case Node.T.PROJ, [R, field]:
r = self.evalExpr(R)
requireType(r, juiIsProjectable)
f = self.project(r, field)
if isinstance(f, Thunk):
return self.evalThunk(f)
else:
return f
case Node.T.PROJ, []:
raise NotImplementedError
case Node.T.CALL, [F, *A]:
f = self.evalExpr(F)
@ -376,6 +387,22 @@ class Context:
case _, _:
raise Exception("invalid expr o(x_x)o: " + str(node))
def project(self, v: JuiValue, field: str) -> JuiValue:
match v:
case Record() as r:
if field not in r.attr:
raise Exception(f"access to undefined field {field}")
return r.attr[field]
case PartialRecordSnapshot() as prs:
if field in prs.fieldThunks:
return prs.fieldThunks[field]
elif prs.base is None:
raise Exception(f"access to undefined field {field} of 'this'")
else:
return self.project(prs.base, v)
case _:
raise NotImplementedError # unreachable
def execStmt(self, node: Node) -> JuiValue:
match node.ctor, node.args:
case Node.T.LET_DECL, [name, X]:
@ -397,9 +424,9 @@ class Context:
ve = self.force(ve)
if not juiValuesEqual(vs, ve):
print("unit test failed:")
print(" " + str(vs))
print(" " + juiValueString(vs))
print("vs.")
print(" " + str(ve))
print(" " + juiValueString(ve))
case _, _:
return self.evalExpr(node)

View file

@ -27,3 +27,16 @@ let z = 4;
let helloworld = "Hello, World!";
len("xyz" + helloworld) + 1;
//^ 17;
record {};
//^ record {};
let r = record {x: 2; y: 3+5};
r;
//^ record {y:8; x:2};
r.x;
//^ 2;
record {x: r.x; y: this.x + 2; x: 4; z: this.x + 5};
//^ record {x: 4; y: 4; z: 9};

View file

@ -56,8 +56,8 @@ def main(argv):
parser = juic.parser.JuiParser(lexer)
ast = parser.scope()
if opts.get("--debug") == "parser":
for e in ast.args:
e.dump()
for node in ast.args:
node.dump()
print("---")
return 0
@ -69,13 +69,13 @@ def main(argv):
ctx = juic.eval.Context(juic.builtins.builtinClosure)
if "--unit-tests" in opts:
for e in ast.args:
ctx.execStmt(e)
for node in ast.args:
ctx.execStmt(node)
return 0
for e in ast.args:
e.dump()
v = ctx.execStmt(e)
for node in ast.args:
node.dump()
v = ctx.execStmt(node)
v = ctx.force(v)
print(">>>>>>>", juic.eval.juiValueString(v))

View file

@ -391,7 +391,7 @@ class JuiParser(LL1Parser):
node = Node(Node.T.CALL, [node, *args])
# Postfix update or record creation operation
while self.la.type in ["{", "<{"]:
while self.la is not None and self.la.type in ["{", "<{"]:
entries = self.record_literal()
node = Node(Node.T.RECORD, [node, *entries])