mirror of
https://git.planet-casio.com/Lephenixnoir/JustUI.git
synced 2024-12-28 20:43:40 +01:00
juic: intended implementation of record ctors, working for now
This commit is contained in:
parent
5f198ff6b0
commit
aa736bd12a
3 changed files with 75 additions and 96 deletions
|
@ -65,7 +65,7 @@ class Record:
|
||||||
|
|
||||||
JuiValue = Union[None, bool, int, float, str, CXXQualid, list,
|
JuiValue = Union[None, bool, int, float, str, CXXQualid, list,
|
||||||
"juic.eval.PartialRecordSnapshot", BuiltinFunction, Function,
|
"juic.eval.PartialRecordSnapshot", BuiltinFunction, Function,
|
||||||
RecordType, Record]
|
RecordType, RecordCtor, Record]
|
||||||
|
|
||||||
def juiIsArith(value):
|
def juiIsArith(value):
|
||||||
return type(value) in [bool, int, float]
|
return type(value) in [bool, int, float]
|
||||||
|
@ -113,9 +113,15 @@ def juiValueString(v):
|
||||||
return "[" + ", ".join(juiValueString(x) for x in v) + "]"
|
return "[" + ", ".join(juiValueString(x) for x in v) + "]"
|
||||||
case BuiltinFunction():
|
case BuiltinFunction():
|
||||||
return str(v)
|
return str(v)
|
||||||
case Function():
|
case Function() as f:
|
||||||
p = v.params + (["..." + v.variadic] if v.variadic else [])
|
p = f.params + (["..." + f.variadic] if f.variadic else [])
|
||||||
s = "fun(" + ", ".join(p) + ") => " + str(v.body)
|
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)"
|
s += " (in some closure)"
|
||||||
return s
|
return s
|
||||||
case RecordType() as rt:
|
case RecordType() as rt:
|
||||||
|
|
153
juic/eval.py
153
juic/eval.py
|
@ -264,6 +264,10 @@ class Context:
|
||||||
params = [name for (name, v) in params if not v],
|
params = [name for (name, v) in params if not v],
|
||||||
variadic = variadic)
|
variadic = variadic)
|
||||||
|
|
||||||
|
def makeRecordCtor(self, params: list[Tuple[str, bool]], body: Node) \
|
||||||
|
-> RecordCtor:
|
||||||
|
return RecordCtor(func=self.makeFunction(params, body))
|
||||||
|
|
||||||
#=== Main evaluation functions ===#
|
#=== Main evaluation functions ===#
|
||||||
|
|
||||||
# Expression evaluator; this function is pure. It can cause thunks to be
|
# Expression evaluator; this function is pure. It can cause thunks to be
|
||||||
|
@ -385,7 +389,7 @@ class Context:
|
||||||
case Node.T.REC_ATTR, _:
|
case Node.T.REC_ATTR, _:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
case Node.T.REC_VALUE, _:
|
case Node.T.REC_VALUE, args:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
raise Exception("invalid expr o(x_x)o: " + str(node))
|
raise Exception("invalid expr o(x_x)o: " + str(node))
|
||||||
|
@ -416,8 +420,9 @@ class Context:
|
||||||
self.addDefinition(name, self.makeFunction(params, body))
|
self.addDefinition(name, self.makeFunction(params, body))
|
||||||
return None
|
return None
|
||||||
|
|
||||||
case Node.T.REC_DECL, _:
|
case Node.T.REC_DECL, [name, params, body]:
|
||||||
raise NotImplementedError
|
self.addDefinition(name, self.makeRecordCtor(params, body))
|
||||||
|
return None
|
||||||
|
|
||||||
case Node.T.SET_STMT, _:
|
case Node.T.SET_STMT, _:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
@ -496,20 +501,9 @@ class Context:
|
||||||
def evalRecordConstructor(self, ctor: JuiValue, entries: list[Node]) \
|
def evalRecordConstructor(self, ctor: JuiValue, entries: list[Node]) \
|
||||||
-> JuiValue:
|
-> JuiValue:
|
||||||
|
|
||||||
# Collect indices of fields and arguments separately; keep the order
|
|
||||||
args = []
|
|
||||||
attr = []
|
|
||||||
for i, e in enumerate(entries):
|
|
||||||
if e.ctor == Node.T.REC_ATTR:
|
|
||||||
attr.append(i)
|
|
||||||
elif e.ctor == Node.T.REC_VALUE:
|
|
||||||
args.append(i)
|
|
||||||
else:
|
|
||||||
assert False and "record node has weird children o(x_x)o"
|
|
||||||
|
|
||||||
# Base record constructor: starts out with an empty record. All
|
# Base record constructor: starts out with an empty record. All
|
||||||
# arguments are children. Easy.
|
# arguments are children. Easy.
|
||||||
if type(ctor) == RecordType:
|
if isinstance(ctor, RecordType):
|
||||||
r = Record(base=ctor, attr=dict(), children=[])
|
r = Record(base=ctor, attr=dict(), children=[])
|
||||||
|
|
||||||
# Create thunks for all entries while providing them with
|
# Create thunks for all entries while providing them with
|
||||||
|
@ -520,7 +514,7 @@ class Context:
|
||||||
if e.ctor == Node.T.REC_ATTR:
|
if e.ctor == Node.T.REC_ATTR:
|
||||||
name, label, node = e.args
|
name, label, node = e.args
|
||||||
elif e.ctor == Node.T.REC_VALUE:
|
elif e.ctor == Node.T.REC_VALUE:
|
||||||
name, label, node = None, None, *e.args
|
name, label, node = None, None, e.args[0]
|
||||||
|
|
||||||
th = Thunk(ast=node, closure=self.currentStateClosure())
|
th = Thunk(ast=node, closure=self.currentStateClosure())
|
||||||
th.thisReference = prs.copy()
|
th.thisReference = prs.copy()
|
||||||
|
@ -533,93 +527,68 @@ class Context:
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
assert isinstance(ctor, Function)
|
# NOTE: NO WAY TO SPECIFY AN ATTRIBUTE IN A NON-STATIC WAY.
|
||||||
|
|
||||||
|
# Create thunks for all entries that have everything but the "this".
|
||||||
|
entry_thunks = []
|
||||||
|
for e in entries:
|
||||||
|
if e.ctor == Node.T.REC_ATTR:
|
||||||
|
name, label, node = e.args
|
||||||
|
elif e.ctor == Node.T.REC_VALUE:
|
||||||
|
name, label, node = None, None, e.args[0]
|
||||||
|
th = Thunk(ast=node, closure=self.currentStateClosure())
|
||||||
|
entry_thunks.append(th)
|
||||||
|
|
||||||
|
# Collect arguments to the constructor and build a thunk the call.
|
||||||
|
args = [entry_thunks[i]
|
||||||
|
for i, e in enumerate(entries) if e.ctor == Node.T.REC_VALUE]
|
||||||
|
|
||||||
|
# TODO: Merge with an internal version of evalCall()
|
||||||
|
#---
|
||||||
|
|
||||||
|
assert isinstance(ctor, RecordCtor)
|
||||||
|
f = ctor.func
|
||||||
|
|
||||||
# TODO: Factor this with function. In fact, this should reduce to a call
|
|
||||||
# Check number of arguments
|
# Check number of arguments
|
||||||
req = str(len(ctor.params)) + ("+" if ctor.variadic is not None else "")
|
req = str(len(f.params)) + ("+" if f.variadic is not None else "")
|
||||||
if len(args) < len(ctor.params):
|
if len(args) < len(f.params):
|
||||||
raise JuiRuntimeError(f"not enough args (need {req}, got {len(args)})")
|
raise JuiRuntimeError(f"not enough args (need {req}, got {len(args)})")
|
||||||
if len(args) > len(ctor.params) and ctor.variadic is None:
|
if len(args) > len(f.params) and f.variadic is None:
|
||||||
raise JuiRuntimeError(f"too many args (need {req}, got {len(args)})")
|
raise JuiRuntimeError(f"too many args (need {req}, got {len(args)})")
|
||||||
|
|
||||||
# TODO: In order to build variadic set I need a LIST node
|
# TODO: In order to build variadic set I need a LIST node
|
||||||
if ctor.variadic is not None:
|
if f.variadic is not None:
|
||||||
raise NotImplementedError("list node for building varargs o(x_x)o")
|
raise NotImplementedError("list node for building varargs o(x_x)o")
|
||||||
|
|
||||||
# Otherwise, it's a function so it might use
|
# Run into the function's scope to build the thunk
|
||||||
raise NotImplementedError
|
with self.contextSwitchAndPushNewScope(f.closure):
|
||||||
|
for name, th in zip(f.params, args):
|
||||||
|
self.addDefinition(name, th)
|
||||||
|
assert f.body.ctor == Node.T.SCOPE_EXPR
|
||||||
|
call_thunk = Thunk(ast=f.body.args[0],
|
||||||
|
closure=self.currentStateClosure())
|
||||||
|
#---
|
||||||
|
|
||||||
assert type(r) == Function and \
|
# Use the call as base for a PRS and assign "this" in all thunks.
|
||||||
"evalRecordConstructor: not a record type nor a function"
|
prs = PartialRecordSnapshot()
|
||||||
|
prs.base = call_thunk
|
||||||
|
|
||||||
# Thunks should be created from a closure of the entire environment
|
for i, e in enumerate(entries):
|
||||||
# -> This environment is always known, _except_ for
|
entry_thunks[i].thisReference = prs.copy()
|
||||||
# partially-constructed records
|
if e.ctor == Node.T.REC_ATTR:
|
||||||
# So, naturally... patch the PRS for "this" later on?
|
name, label, node = e.args
|
||||||
#
|
prs.fieldThunks[name] = th
|
||||||
# ## In a normal function call
|
|
||||||
#
|
|
||||||
# Invariant: context of evaluating the expression that has the call is
|
|
||||||
# complete. Use it for all the arguments. Done.
|
|
||||||
#
|
|
||||||
# ## In a record constructor with no parameters
|
|
||||||
#
|
|
||||||
# All attribute values have the same context when excluding "this",
|
|
||||||
# it's just the context surrounding the record constructor, and
|
|
||||||
# (invariant) it is complete. Use it to create all of the thunks.
|
|
||||||
#
|
|
||||||
# Later, iterate through the thunks in top-down order and patch the
|
|
||||||
# "this" by constructing PRSs.
|
|
||||||
#
|
|
||||||
# Invariant is satisified because each thunk's closure is complete and
|
|
||||||
# thus the context for evaluating their contents will also be
|
|
||||||
# complete.
|
|
||||||
#
|
|
||||||
# ## In a record constructor with parameters
|
|
||||||
#
|
|
||||||
# Again, the entire context except for "this" is well-defined and
|
|
||||||
# complete. Use it to create thunks for all record entries, inline or
|
|
||||||
# not.
|
|
||||||
#
|
|
||||||
# Prepare a thunk for the call's evaluation, based on the function's
|
|
||||||
# environment, assigning parameter-related thunks (or lists thereof) as
|
|
||||||
# definitions for the function's arguments.
|
|
||||||
#
|
|
||||||
# Use the call's thunk as base for the PRS and sweep top-to-bottom
|
|
||||||
# assigning PRSs in every record-entry thunk.
|
|
||||||
#
|
|
||||||
# Return a literal record with all non-parameter entries as elements.
|
|
||||||
# Erm, remove duplicates.
|
|
||||||
#
|
|
||||||
# NOTE: NO WAY TO SPECIFY AN ATTRIBUTE IN A NON-STATIC WAY.
|
|
||||||
|
|
||||||
# - Build thunks for all entries, with one layer of "this" data
|
baseRecord = self.evalThunk(call_thunk)
|
||||||
# - Call ctor1, pass it the argument thunks
|
if not isinstance(baseRecord, Record):
|
||||||
# - Move into ctor1's scope, add new scope with arguments
|
raise Exception("record ctor did not return a record")
|
||||||
# - Build a thunk for ctor1's body, this is second layer of "this"
|
|
||||||
# /!\ It's not per-field! It's not per-field!
|
for i, e in enumerate(entries):
|
||||||
# - Go back to toplevel, add second layer of "this" to all thunks
|
if e.ctor == Node.T.REC_ATTR:
|
||||||
# - Return a PartialRecord object that has ctor1's returned thunk as
|
name, label, node = e.args
|
||||||
# base and the other fields as overrides.
|
baseRecord.attr[name] = entry_thunks[i]
|
||||||
#
|
|
||||||
# ... Looks too opaque at ctor1's level.
|
return baseRecord
|
||||||
# ... But then again for lazy evaluation all we need to know is it's a
|
|
||||||
# record, and then we evaluated only when we project fields. If the
|
|
||||||
# same thing happens with <{} there should be no problem.
|
|
||||||
# ... Having exactly two layers is suspicious. What happens when we try
|
|
||||||
# to get a field?
|
|
||||||
#
|
|
||||||
# 1. Lookup the field in the PartialRecord. If it has it, it's been
|
|
||||||
# added by the latest constructor, and we know which it is. Evaluate
|
|
||||||
# the thunk inside.
|
|
||||||
# 2. Otherwise, evaluate the base thunk. In the "f <| rec { ... }" it's
|
|
||||||
# a record update. This will yield another PartialRecord. Use it.
|
|
||||||
# 3. This should work, but we need a PartialRecord.
|
|
||||||
#
|
|
||||||
# When do we convert back to a full Record?
|
|
||||||
# How about that's what Record does, and it just has accessors?
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def force(self, v: JuiValue | Thunk) -> JuiValue:
|
def force(self, v: JuiValue | Thunk) -> JuiValue:
|
||||||
match v:
|
match v:
|
||||||
|
|
|
@ -46,6 +46,10 @@ jwidget {
|
||||||
// fun test(x, y, ...all) = x + y + sum(all);
|
// fun test(x, y, ...all) = x + y + sum(all);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
rec jlabel2(str) = jwidget { text: str };
|
||||||
|
|
||||||
|
jlabel2 {"Hello"};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
fun _(fx, cg) = if(param("FX")) fx else cg;
|
fun _(fx, cg) = if(param("FX")) fx else cg;
|
||||||
fun stack(elem) = elem <{ layout: stack };
|
fun stack(elem) = elem <{ layout: stack };
|
||||||
|
|
Loading…
Reference in a new issue