mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-28 20:43: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 *
|
from juic.eval import *
|
||||||
|
|
||||||
def juiPrint(*args):
|
def juiPrint(*args):
|
||||||
print("[print]", *args)
|
print("[print]", *(juiValueString(a) for a in args))
|
||||||
|
|
||||||
def juiLen(x):
|
def juiLen(x):
|
||||||
if type(x) not in [str, list]:
|
if type(x) not in [str, list]:
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
from dataclasses import dataclass, field
|
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:
|
# Mapping of Jui types to Python types:
|
||||||
# Null None
|
# Null None
|
||||||
|
@ -62,9 +66,9 @@ class Record:
|
||||||
# A record type if it's pure, or a thunk with the base object otherwise.
|
# A record type if it's pure, or a thunk with the base object otherwise.
|
||||||
base: Union[RecordType, "juic.eval.Thunk"]
|
base: Union[RecordType, "juic.eval.Thunk"]
|
||||||
# Standard key-value attribute pairs
|
# Standard key-value attribute pairs
|
||||||
attr: dict[str, "juic.value.Thunk"]
|
attr: dict[str, "juic.eval.Thunk"]
|
||||||
# Children elements
|
# Children elements
|
||||||
children: list["juic.value.Thunk"]
|
children: list["juic.eval.Thunk"]
|
||||||
# TODO: Keep track of variables that are not fields, i.e. "methods"?
|
# TODO: Keep track of variables that are not fields, i.e. "methods"?
|
||||||
# scope: dict[str, "JuiValue"]
|
# scope: dict[str, "JuiValue"]
|
||||||
# TODO: Labels
|
# TODO: Labels
|
||||||
|
@ -93,7 +97,7 @@ def juiIsCallable(value):
|
||||||
return type(value) in [BuiltinFunction, Function]
|
return type(value) in [BuiltinFunction, Function]
|
||||||
|
|
||||||
def juiIsProjectable(value):
|
def juiIsProjectable(value):
|
||||||
return type(value) in [ThisRef, Record]
|
return type(value) in [Record, juic.eval.PartialRecordSnapshot]
|
||||||
|
|
||||||
def juiIsConstructible(value):
|
def juiIsConstructible(value):
|
||||||
return type(value) in [RecordType, Function]
|
return type(value) in [RecordType, Function]
|
||||||
|
@ -133,15 +137,25 @@ def juiValueString(v):
|
||||||
s += "; ".join(juiValueString(x) for x in r.children)
|
s += "; ".join(juiValueString(x) for x in r.children)
|
||||||
return s + "}"
|
return s + "}"
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
case Thunk() as th:
|
case juic.eval.Thunk() as th:
|
||||||
return str(th)
|
return str(th)
|
||||||
case PartialRecordSnapshot() as prs:
|
case juic.eval.PartialRecordSnapshot() as prs:
|
||||||
return str(prs)
|
return str(prs)
|
||||||
case _:
|
case _:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
# Check whether two *forced* values are equal
|
# Check whether two *forced* values are equal
|
||||||
def juiValuesEqual(v1, v2):
|
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:
|
match v1, v2:
|
||||||
case None, None:
|
case None, None:
|
||||||
return True
|
return True
|
||||||
|
@ -177,11 +191,8 @@ def juiValuesEqual(v1, v2):
|
||||||
for v1, v2 in zip(r1.children, r2.children)):
|
for v1, v2 in zip(r1.children, r2.children)):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
case Thunk() as th1, Thunk() as th2:
|
case juic.eval.PartialRecordSnapshot(), \
|
||||||
assert th1.evaluated and not th1.invalid
|
juic.eval.PartialRecordSnapshot():
|
||||||
assert th2.evaluated and not th2.invalid
|
|
||||||
return juiValuesEqual(th1.result, th2.result)
|
|
||||||
case PartialRecordSnapshot(), PartialRecordSnapshot():
|
|
||||||
raise Exception("cannot compare PRSs")
|
raise Exception("cannot compare PRSs")
|
||||||
case _, _:
|
case _, _:
|
||||||
raise Exception(f"invalid types for comparison: {type(v1)}, {type(v2)}")
|
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):
|
def __str__(self):
|
||||||
s = "Thunk("
|
s = "Thunk("
|
||||||
if th._running:
|
if self._running:
|
||||||
s += "running, "
|
s += "running, "
|
||||||
if th.evaluated:
|
if self.evaluated:
|
||||||
s += "evaluated"
|
s += "evaluated"
|
||||||
if th.invalid:
|
if self.invalid:
|
||||||
s += ", invalid"
|
s += ", invalid"
|
||||||
else:
|
else:
|
||||||
s += " = " + juiValueString(th.result)
|
s += " = " + juiValueString(self.result)
|
||||||
else:
|
else:
|
||||||
s += "unevaluated"
|
s += "unevaluated"
|
||||||
s += "){ " + str(th.ast) + " }"
|
s += "){ " + str(self.ast) + " }"
|
||||||
return s
|
return s
|
||||||
|
|
||||||
# Object representing the interpretation of a partially-built record. In a
|
# Object representing the interpretation of a partially-built record. In a
|
||||||
|
@ -137,7 +137,7 @@ class PartialRecordSnapshot:
|
||||||
base: None | Thunk = None
|
base: None | Thunk = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "<A PRS AAAAAAH>" # TODO
|
return "<PartialRecordSnapshot>" # TODO
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return PartialRecordSnapshot(self.fieldThunks.copy(), self.base)
|
return PartialRecordSnapshot(self.fieldThunks.copy(), self.base)
|
||||||
|
@ -344,10 +344,21 @@ class Context:
|
||||||
return self.evalCall(f, [X])
|
return self.evalCall(f, [X])
|
||||||
|
|
||||||
case Node.T.THIS, []:
|
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]:
|
case Node.T.CALL, [F, *A]:
|
||||||
f = self.evalExpr(F)
|
f = self.evalExpr(F)
|
||||||
|
@ -376,6 +387,22 @@ class Context:
|
||||||
case _, _:
|
case _, _:
|
||||||
raise Exception("invalid expr o(x_x)o: " + str(node))
|
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:
|
def execStmt(self, node: Node) -> JuiValue:
|
||||||
match node.ctor, node.args:
|
match node.ctor, node.args:
|
||||||
case Node.T.LET_DECL, [name, X]:
|
case Node.T.LET_DECL, [name, X]:
|
||||||
|
@ -397,9 +424,9 @@ class Context:
|
||||||
ve = self.force(ve)
|
ve = self.force(ve)
|
||||||
if not juiValuesEqual(vs, ve):
|
if not juiValuesEqual(vs, ve):
|
||||||
print("unit test failed:")
|
print("unit test failed:")
|
||||||
print(" " + str(vs))
|
print(" " + juiValueString(vs))
|
||||||
print("vs.")
|
print("vs.")
|
||||||
print(" " + str(ve))
|
print(" " + juiValueString(ve))
|
||||||
|
|
||||||
case _, _:
|
case _, _:
|
||||||
return self.evalExpr(node)
|
return self.evalExpr(node)
|
||||||
|
|
|
@ -27,3 +27,16 @@ let z = 4;
|
||||||
let helloworld = "Hello, World!";
|
let helloworld = "Hello, World!";
|
||||||
len("xyz" + helloworld) + 1;
|
len("xyz" + helloworld) + 1;
|
||||||
//^ 17;
|
//^ 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)
|
parser = juic.parser.JuiParser(lexer)
|
||||||
ast = parser.scope()
|
ast = parser.scope()
|
||||||
if opts.get("--debug") == "parser":
|
if opts.get("--debug") == "parser":
|
||||||
for e in ast.args:
|
for node in ast.args:
|
||||||
e.dump()
|
node.dump()
|
||||||
print("---")
|
print("---")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -69,13 +69,13 @@ def main(argv):
|
||||||
ctx = juic.eval.Context(juic.builtins.builtinClosure)
|
ctx = juic.eval.Context(juic.builtins.builtinClosure)
|
||||||
|
|
||||||
if "--unit-tests" in opts:
|
if "--unit-tests" in opts:
|
||||||
for e in ast.args:
|
for node in ast.args:
|
||||||
ctx.execStmt(e)
|
ctx.execStmt(node)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
for e in ast.args:
|
for node in ast.args:
|
||||||
e.dump()
|
node.dump()
|
||||||
v = ctx.execStmt(e)
|
v = ctx.execStmt(node)
|
||||||
v = ctx.force(v)
|
v = ctx.force(v)
|
||||||
print(">>>>>>>", juic.eval.juiValueString(v))
|
print(">>>>>>>", juic.eval.juiValueString(v))
|
||||||
|
|
||||||
|
|
|
@ -391,7 +391,7 @@ class JuiParser(LL1Parser):
|
||||||
node = Node(Node.T.CALL, [node, *args])
|
node = Node(Node.T.CALL, [node, *args])
|
||||||
|
|
||||||
# Postfix update or record creation operation
|
# 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()
|
entries = self.record_literal()
|
||||||
node = Node(Node.T.RECORD, [node, *entries])
|
node = Node(Node.T.RECORD, [node, *entries])
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue