Introduce scopes (#1053)

* Implement let binding

* Add lambda inputs

* Fix tests

* Fix proto network formatting

* Generate a template Scoped network by default

* Add comment to explain the lambda parameter

* Move binding wrapping out of the template

* Fix errors cause by image frames
This commit is contained in:
Dennis Kobert
2023-03-02 17:13:28 +01:00
committed by Keavon Chambers
parent 0b813805d2
commit 7254c008f9
12 changed files with 366 additions and 118 deletions

View File

@@ -43,17 +43,17 @@ pub struct DocumentNode {
}
impl DocumentNode {
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize) {
pub fn populate_first_network_input(&mut self, node_id: NodeId, output_index: usize, offset: usize, lambda: bool) {
let input = self
.inputs
.iter()
.enumerate()
.filter(|(_, input)| matches!(input, NodeInput::Network(_)))
.nth(offset)
.expect("no network input");
.unwrap_or_else(|| panic!("no network input found for {self:#?} and offset: {offset}"));
let index = input.0;
self.inputs[index] = NodeInput::Node { node_id, output_index };
self.inputs[index] = NodeInput::Node { node_id, output_index, lambda };
}
fn resolve_proto_node(mut self) -> ProtoNode {
@@ -62,12 +62,12 @@ impl DocumentNode {
if let DocumentNodeImplementation::Unresolved(fqn) = self.implementation {
let (input, mut args) = match first {
NodeInput::Value { tagged_value, .. } => {
assert_eq!(self.inputs.len(), 0);
assert_eq!(self.inputs.len(), 0, "{}, {:?}", &self.name, &self.inputs);
(ProtoNodeInput::None, ConstructionArgs::Value(tagged_value))
}
NodeInput::Node { node_id, output_index } => {
NodeInput::Node { node_id, output_index, lambda } => {
assert_eq!(output_index, 0, "Outputs should be flattened before converting to protonode.");
(ProtoNodeInput::Node(node_id), ConstructionArgs::Nodes(vec![]))
(ProtoNodeInput::Node(node_id, lambda), ConstructionArgs::Nodes(vec![]))
}
NodeInput::Network(ty) => (ProtoNodeInput::Network(ty), ConstructionArgs::Nodes(vec![])),
};
@@ -81,7 +81,7 @@ impl DocumentNode {
if let ConstructionArgs::Nodes(nodes) = &mut args {
nodes.extend(self.inputs.iter().map(|input| match input {
NodeInput::Node { node_id, .. } => *node_id,
NodeInput::Node { node_id, lambda, .. } => (*node_id, *lambda),
_ => unreachable!(),
}));
}
@@ -103,11 +103,15 @@ impl DocumentNode {
P: Fn(String, usize) -> Option<NodeInput>,
{
for (index, input) in self.inputs.iter_mut().enumerate() {
let &mut NodeInput::Node{node_id: id, output_index} = input else {
let &mut NodeInput::Node{node_id: id, output_index, lambda} = input else {
continue;
};
if let Some(&new_id) = new_ids.get(&id) {
*input = NodeInput::Node { node_id: new_id, output_index };
*input = NodeInput::Node {
node_id: new_id,
output_index,
lambda,
};
} else if let Some(new_input) = default_input(self.name.clone(), index) {
*input = new_input;
} else {
@@ -121,21 +125,28 @@ impl DocumentNode {
#[derive(Debug, Clone, PartialEq, Hash, specta::Type)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum NodeInput {
Node { node_id: NodeId, output_index: usize },
Node { node_id: NodeId, output_index: usize, lambda: bool },
Value { tagged_value: value::TaggedValue, exposed: bool },
Network(Type),
}
impl NodeInput {
pub const fn node(node_id: NodeId, output_index: usize) -> Self {
Self::Node { node_id, output_index }
Self::Node { node_id, output_index, lambda: false }
}
pub const fn lambda(node_id: NodeId, output_index: usize) -> Self {
Self::Node { node_id, output_index, lambda: true }
}
pub const fn value(tagged_value: value::TaggedValue, exposed: bool) -> Self {
Self::Value { tagged_value, exposed }
}
fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
if let &mut NodeInput::Node { node_id, output_index } = self {
*self = NodeInput::Node { node_id: f(node_id), output_index }
if let &mut NodeInput::Node { node_id, output_index, lambda } = self {
*self = NodeInput::Node {
node_id: f(node_id),
output_index,
lambda,
}
}
}
pub fn is_exposed(&self) -> bool {
@@ -246,7 +257,7 @@ impl NodeNetwork {
}
for input in &mut node.inputs {
let &mut NodeInput::Node { node_id, output_index} = input else {
let &mut NodeInput::Node { node_id, output_index, .. } = input else {
continue;
};
// Use the initial node when getting the first output
@@ -362,9 +373,9 @@ impl NodeNetwork {
for (document_input, network_input) in node.inputs.into_iter().zip(inner_network.inputs.iter()) {
let offset = network_offsets.entry(network_input).or_insert(0);
match document_input {
NodeInput::Node { node_id, output_index } => {
NodeInput::Node { node_id, output_index, lambda } => {
let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(node_id, output_index, *offset);
network_input.populate_first_network_input(node_id, output_index, *offset, lambda);
}
NodeInput::Value { tagged_value, exposed } => {
// Skip formatting very large values for seconds in performance speedup
@@ -386,7 +397,7 @@ impl NodeNetwork {
assert!(!self.nodes.contains_key(&new_id));
self.nodes.insert(new_id, value_node);
let network_input = self.nodes.get_mut(network_input).unwrap();
network_input.populate_first_network_input(new_id, 0, *offset);
network_input.populate_first_network_input(new_id, 0, *offset, false);
}
NodeInput::Network(_) => {
*network_offsets.get_mut(network_input).unwrap() += 1;
@@ -403,6 +414,7 @@ impl NodeNetwork {
.map(|&NodeOutput { node_id, node_output_index }| NodeInput::Node {
node_id,
output_index: node_output_index,
lambda: false,
})
.collect();
@@ -660,7 +672,7 @@ mod test {
let reference = ProtoNode {
identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![0]),
construction_args: ConstructionArgs::Nodes(vec![(0, false)]),
};
assert_eq!(proto_node, reference);
}
@@ -675,7 +687,7 @@ mod test {
1,
ProtoNode {
identifier: "graphene_core::ops::IdNode".into(),
input: ProtoNodeInput::Node(11),
input: ProtoNodeInput::Node(11, false),
construction_args: ConstructionArgs::Nodes(vec![]),
},
),
@@ -684,14 +696,14 @@ mod test {
ProtoNode {
identifier: "graphene_core::structural::ConsNode".into(),
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![14]),
construction_args: ConstructionArgs::Nodes(vec![(14, false)]),
},
),
(
11,
ProtoNode {
identifier: "graphene_core::ops::AddNode".into(),
input: ProtoNodeInput::Node(10),
input: ProtoNodeInput::Node(10, false),
construction_args: ConstructionArgs::Nodes(vec![]),
},
),

View File

@@ -43,7 +43,7 @@ impl core::fmt::Display for ProtoNetwork {
match &node.input {
ProtoNodeInput::None => f.write_str("None")?,
ProtoNodeInput::Network(ty) => f.write_fmt(format_args!("Network (type = {:?})", ty))?,
ProtoNodeInput::Node(_) => f.write_str("Node")?,
ProtoNodeInput::Node(_, _) => f.write_str("Node")?,
}
f.write_str("\n")?;
@@ -54,7 +54,7 @@ impl core::fmt::Display for ProtoNetwork {
}
ConstructionArgs::Nodes(nodes) => {
for id in nodes {
write_node(f, network, *id, indent + 1)?;
write_node(f, network, id.0, indent + 1)?;
}
}
}
@@ -71,7 +71,8 @@ impl core::fmt::Display for ProtoNetwork {
#[derive(Debug, Clone)]
pub enum ConstructionArgs {
Value(value::TaggedValue),
Nodes(Vec<NodeId>),
// the bool indicates whether to treat the node as lambda node
Nodes(Vec<(NodeId, bool)>),
}
impl PartialEq for ConstructionArgs {
@@ -101,7 +102,7 @@ impl Hash for ConstructionArgs {
impl ConstructionArgs {
pub fn new_function_args(&self) -> Vec<String> {
match self {
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{}", n)).collect(),
ConstructionArgs::Nodes(nodes) => nodes.iter().map(|n| format!("n{}", n.0)).collect(),
ConstructionArgs::Value(value) => vec![format!("{:?}", value)],
}
}
@@ -118,13 +119,14 @@ pub struct ProtoNode {
pub enum ProtoNodeInput {
None,
Network(Type),
Node(NodeId),
// the bool indicates whether to treat the node as lambda node
Node(NodeId, bool),
}
impl ProtoNodeInput {
pub fn unwrap_node(self) -> NodeId {
match self {
ProtoNodeInput::Node(id) => id,
ProtoNodeInput::Node(id, _) => id,
_ => panic!("tried to unwrap id from non node input \n node: {:#?}", self),
}
}
@@ -142,7 +144,7 @@ impl ProtoNode {
"network".hash(&mut hasher);
ty.hash(&mut hasher);
}
ProtoNodeInput::Node(id) => id.hash(&mut hasher),
ProtoNodeInput::Node(id, lambda) => (id, lambda).hash(&mut hasher),
};
Some(hasher.finish() as NodeId)
}
@@ -155,16 +157,18 @@ impl ProtoNode {
}
}
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId) {
if let ProtoNodeInput::Node(id) = self.input {
self.input = ProtoNodeInput::Node(f(id))
pub fn map_ids(&mut self, f: impl Fn(NodeId) -> NodeId, skip_lambdas: bool) {
if let ProtoNodeInput::Node(id, lambda) = self.input {
if !(skip_lambdas && lambda) {
self.input = ProtoNodeInput::Node(f(id), lambda)
}
}
if let ConstructionArgs::Nodes(ids) = &mut self.construction_args {
ids.iter_mut().for_each(|id| *id = f(*id));
ids.iter_mut().filter(|(_, lambda)| !(skip_lambdas && *lambda)).for_each(|(id, _)| *id = f(*id));
}
}
pub fn unwrap_construction_nodes(&self) -> Vec<NodeId> {
pub fn unwrap_construction_nodes(&self) -> Vec<(NodeId, bool)> {
match &self.construction_args {
ConstructionArgs::Nodes(nodes) => nodes.clone(),
_ => panic!("tried to unwrap nodes from non node construction args \n node: {:#?}", self),
@@ -186,12 +190,12 @@ impl ProtoNetwork {
pub fn collect_outwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id) = &node.input {
if let ProtoNodeInput::Node(ref_id, _) = &node.input {
self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id)
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for ref_id in ref_nodes {
for (ref_id, _) in ref_nodes {
self.check_ref(ref_id, id);
edges.entry(*ref_id).or_default().push(*id)
}
@@ -210,7 +214,7 @@ impl ProtoNetwork {
let mut lookup = self.nodes.iter().map(|(id, _)| (*id, *id)).collect::<HashMap<_, _>>();
if let Some(sni) = self.nodes[index].1.stable_node_id() {
lookup.insert(self.nodes[index].0, sni);
self.replace_node_references(&lookup);
self.replace_node_references(&lookup, false);
self.nodes[index].0 = sni;
sni
} else {
@@ -221,12 +225,12 @@ impl ProtoNetwork {
pub fn collect_inwards_edges(&self) -> HashMap<NodeId, Vec<NodeId>> {
let mut edges: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
for (id, node) in &self.nodes {
if let ProtoNodeInput::Node(ref_id) = &node.input {
if let ProtoNodeInput::Node(ref_id, _) = &node.input {
self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id)
}
if let ConstructionArgs::Nodes(ref_nodes) = &node.construction_args {
for ref_id in ref_nodes {
for (ref_id, _) in ref_nodes {
self.check_ref(ref_id, id);
edges.entry(*id).or_default().push(*ref_id)
}
@@ -248,21 +252,22 @@ impl ProtoNetwork {
let resolved_lookup = resolved.clone();
if let Some((input_node, id, input)) = self.nodes.iter_mut().filter(|(id, _)| !resolved_lookup.contains(id)).find_map(|(id, node)| {
if let ProtoNodeInput::Node(input_node) = node.input {
if let ProtoNodeInput::Node(input_node, false) = node.input {
resolved.insert(*id);
let pre_node_input = inputs.get(input_node as usize).expect("input node should exist");
Some((input_node, *id, pre_node_input.clone()))
} else {
resolved.insert(*id);
None
}
}) {
lookup.insert(id, compose_node_id);
self.replace_node_references(&lookup);
self.replace_node_references(&lookup, true);
self.nodes.push((
compose_node_id,
ProtoNode {
identifier: NodeIdentifier::new("graphene_core::structural::ComposeNode<_, _, _>"),
construction_args: ConstructionArgs::Nodes(vec![input_node, id]),
construction_args: ConstructionArgs::Nodes(vec![(input_node, false), (id, true)]),
input,
},
));
@@ -338,13 +343,13 @@ impl ProtoNetwork {
(pos as NodeId, node)
})
.collect();
self.replace_node_references(&lookup);
self.replace_node_references(&lookup, false);
assert_eq!(order.len(), self.nodes.len());
}
fn replace_node_references(&mut self, lookup: &HashMap<u64, u64>) {
fn replace_node_references(&mut self, lookup: &HashMap<u64, u64>, skip_lambdas: bool) {
self.nodes.iter_mut().for_each(|(_, node)| {
node.map_ids(|id| *lookup.get(&id).expect("node not found in lookup table"));
node.map_ids(|id| *lookup.get(&id).expect("node not found in lookup table"), skip_lambdas);
});
self.inputs = self.inputs.iter().filter_map(|id| lookup.get(id).copied()).collect();
self.output = *lookup.get(&self.output).unwrap();
@@ -403,7 +408,7 @@ impl TypingContext {
// If the node has nodes as parameters we can infer the types from the node outputs
ConstructionArgs::Nodes(ref nodes) => nodes
.iter()
.map(|id| {
.map(|(id, _)| {
self.inferred
.get(id)
.ok_or(format!("Inferring type of {node_id} depends on {id} which is not present in the typing context"))
@@ -416,7 +421,7 @@ impl TypingContext {
let input = match node.input {
ProtoNodeInput::None => concrete!(()),
ProtoNodeInput::Network(ref ty) => ty.clone(),
ProtoNodeInput::Node(id) => {
ProtoNodeInput::Node(id, _) => {
let input = self
.inferred
.get(&id)
@@ -573,7 +578,7 @@ mod test {
println!("{:#?}", construction_network);
assert_eq!(construction_network.nodes[0].1.identifier.name.as_ref(), "value");
assert_eq!(construction_network.nodes.len(), 6);
assert_eq!(construction_network.nodes[5].1.construction_args, ConstructionArgs::Nodes(vec![3, 4]));
assert_eq!(construction_network.nodes[5].1.construction_args, ConstructionArgs::Nodes(vec![(3, false), (4, true)]));
}
#[test]
@@ -589,11 +594,11 @@ mod test {
ids,
vec![
15907139529964845467,
14192092348022507362,
14714934190542167928,
4518275895314664278,
13912679582583718470,
3236993912700824422
1552706903207877482,
15211082859148708110,
3361684226823984981,
16609475913638361514,
5640155373642511298
]
);
}
@@ -607,7 +612,7 @@ mod test {
7,
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(11),
input: ProtoNodeInput::Node(11, false),
construction_args: ConstructionArgs::Nodes(vec![]),
},
),
@@ -615,7 +620,7 @@ mod test {
1,
ProtoNode {
identifier: "id".into(),
input: ProtoNodeInput::Node(11),
input: ProtoNodeInput::Node(11, false),
construction_args: ConstructionArgs::Nodes(vec![]),
},
),
@@ -624,14 +629,14 @@ mod test {
ProtoNode {
identifier: "cons".into(),
input: ProtoNodeInput::Network(concrete!(u32)),
construction_args: ConstructionArgs::Nodes(vec![14]),
construction_args: ConstructionArgs::Nodes(vec![(14, false)]),
},
),
(
11,
ProtoNode {
identifier: "add".into(),
input: ProtoNodeInput::Node(10),
input: ProtoNodeInput::Node(10, false),
construction_args: ConstructionArgs::Nodes(vec![]),
},
),