mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-28 04:23:40 +01:00
juic: add some record processing, start checking with mypy
This commit is contained in:
parent
e79ba0056b
commit
4579acc0f4
6 changed files with 82 additions and 31 deletions
|
@ -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]:
|
||||
|
|
|
@ -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)}")
|
||||
|
|
49
juic/eval.py
49
juic/eval.py
|
@ -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)
|
||||
|
|
|
@ -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};
|
||||
|
|
14
juic/main.py
14
juic/main.py
|
@ -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))
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
||||
|
|
Loading…
Reference in a new issue