juic: intended implementation of record ctors, working for now

This commit is contained in:
Lephenixnoir 2024-08-28 19:55:38 +02:00
parent 5f198ff6b0
commit aa736bd12a
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
3 changed files with 75 additions and 96 deletions

View file

@ -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:

View file

@ -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:

View file

@ -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 };