Skip to main content

grabapl/operation/
builder.rs

1//! This module provides functionality related to building user defined operations.
2//!
3//! The main functionality is provided by the [`OperationBuilder`] type.
4//!
5//! This should be used as the primary backend used by frontends that want to allow end-users to create
6//! their own operations.
7//!
8//! The main method of communication is through atomic "instructions" sent to the builder.
9//! These are flat instructions: any nesting in the resulting user defined operation is a result
10//! of explicit "nesting" instructions, such as [`OperationBuilder::start_query`].
11//!
12//! You can think of these instructions as the HIR (high-level intermediate representation) of
13//! grabapl, which the builder compiles into bytecode (i.e., the final user defined operation) for the interpreter.
14//!
15//! See the [`OperationBuilder`] documentation for the available instructions.
16//!
17//! # Example
18//! Assume we want to build a text-based frontend that allows users to create their own operations.
19//!
20//! We may want to support syntax such as the following:
21//! ```rust
22//! # use grabapl::semantics::example::ExampleSemantics;
23//! # use syntax::grabapl_parse;
24//! # grabapl_parse!(ExampleSemantics,
25//! fn mark_children_as_visited(parent: int) {
26//!     if shape [child: int, parent -> child: *] {
27//!         mark_node<"visited">(child);
28//!         // we found a child, hence we should recurse to find more children
29//!         mark_children_as_visited(parent);
30//!     }
31//! }
32//! # );
33//! ```
34//!
35//! If we leverage this builder, all our frontend would need to do in order to get a finished user defined operation,
36//! is turn the above syntax example into the following sequence of instructions:
37//! 1. [`expect_parameter_node("parent", NodeType::Int)`](OperationBuilder::expect_parameter_node) - the parameter definition
38//! 2. [`start_shape_query("<generated name>")`](OperationBuilder::start_shape_query) - the start of the shape query
39//! 3. [`expect_shape_node("child", NodeType::Int)`](OperationBuilder::expect_shape_node) - the shape query expects a child node of type int
40//! 4. [`expect_shape_edge("parent", "child", EdgeType::Wildcard)`](OperationBuilder::expect_shape_edge) - the shape query expects an edge from parent to child
41//! 5. [`enter_true_branch()`](OperationBuilder::enter_true_branch) - we enter the true branch of the shape query
42//!    * Note how this is a flat instruction: We don't pass the entire true branch as argument to the method.
43//!      Instead, we _change the context_ to indicate the following instructions are part of the true branch.
44//! 6. [`add_operation(LibBuiltinOperation::MarkNode("visited"), vec!["child"])`](OperationBuilder::add_operation) - we add an operation that marks the child node as visited
45//! 7. [`add_operation(Recurse, vec!["parent"])`](OperationBuilder::add_operation) - we add an operation that recurses to find more children
46//! 8. [`end_query()`](OperationBuilder::end_query) - we end the shape query
47//!
48//! After sending these instructions to the builder we can call [`OperationBuilder::build()`](OperationBuilder::build)
49//! to get the final user defined operation that can then be added to a [`OperationContext`](crate::operation::OperationContext) and
50//! executed by the interpreter via [`run_from_concrete`](crate::operation::run_from_concrete).
51//!
52//! # Example Frontends
53//! See [`grabapl_syntax`](https://crates.io/crates/grabapl_syntax) for a text-based syntax
54//! frontend that compiles parsed ASTs into instructions for this builder.
55//! This implements our example from above.
56//!
57//! See `example_clients/simple_semantics/{simple_semantics_ffi, www}` for a basic visual editor that
58//! uses commands from the user to convert into instructions for this builder, and takes the builder's
59//! intermediate state to give visual feedback to the user.
60
61use crate::operation::builtin::LibBuiltinOperation;
62use crate::operation::marker::Marker;
63use crate::operation::query::{BuiltinQuery, GraphShapeQuery, ShapeNodeIdentifier};
64use crate::operation::signature::parameter::{
65    AbstractOperationOutput, AbstractOutputNodeMarker, GraphWithSubstitution, OperationParameter,
66    ParameterSubstitution,
67};
68use crate::operation::signature::parameterbuilder::OperationParameterBuilder;
69use crate::operation::signature::{AbstractSignatureNodeId, OperationSignature};
70use crate::operation::user_defined::{
71    AbstractNodeId, AbstractOperationArgument, AbstractOperationResultMarker,
72    AbstractUserDefinedOperationOutput, NamedMarker, OpLikeInstruction, QueryInstructions,
73    UserDefinedOperation,
74};
75use crate::operation::{
76    BuiltinOperation, Operation, OperationError, OperationResult, get_substitution,
77};
78use crate::prelude::*;
79use crate::semantics::{AbstractGraph, AbstractMatcher};
80use crate::util::bimap::BiMap;
81use crate::util::log;
82use crate::{NodeKey, Semantics, SubstMarker};
83use error_stack::{FutureExt, Result, ResultExt, bail, report};
84use petgraph::dot;
85use petgraph::dot::Dot;
86use std::cell::RefCell;
87use std::collections::{HashMap, HashSet};
88use std::fmt::Debug;
89use std::iter::Peekable;
90use std::marker::PhantomData;
91use std::mem;
92use std::slice::Iter;
93use thiserror::Error;
94
95mod programming_by_demonstration;
96pub mod stack_based_builder;
97/*
98General overview:
99
1001. While building, the builder just stores the messages sent to it.
101We cannot do fancy compile-time checks like "every query has a condition and two branches", because
102every step of that (condition, true branch, false branch) should be interruptible and resumable.
103E.g., a frontend needs to be able to give intermediate feedback to the user, so that the user
104can work with that feedback and send new messages to the builder.
105
106However, to give good feedback for which messages are appropriate, we construct the operation on the fly (TODO: cache this?),
107so that errors like invalid identifiers or ending a query without starting one can be caught immediately at message-time.
108This is the same routine that can provide state feedback to the user like:
109 * right now you're in this branch of that query
110 * the abstract graph looks like this
111 * more ???
112
113The intermediate state returns a graph and a hashmap from nodes and edges to additional metadata, like their abstract node id.
114*/
115
116/// An operation that can be applied abstractly
117enum AbstractOperation<'a, S: Semantics> {
118    Op(Operation<'a, S>),
119    Partial(&'a OperationSignature<S>),
120}
121
122impl<'a, S: Semantics> AbstractOperation<'a, S> {
123    fn parameter(&self) -> OperationParameter<S> {
124        match self {
125            AbstractOperation::Op(op) => op.parameter(),
126            AbstractOperation::Partial(sig) => sig.parameter.clone(),
127        }
128    }
129
130    fn apply_abstract(
131        &self,
132        op_ctx: &OperationContext<S>,
133        g: &mut GraphWithSubstitution<AbstractGraph<S>>,
134    ) -> OperationResult<AbstractOperationOutput<S>> {
135        match self {
136            AbstractOperation::Op(op) => op.apply_abstract(op_ctx, g),
137            AbstractOperation::Partial(sig) => Ok(sig.output.apply_abstract(g)),
138        }
139    }
140
141    // hack to make the Inefficient operation builder still compile without too many changes.
142    // TODO: delete the inefficient operation builder and this method after all TODOs have been moved
143    fn from_operation(op: Operation<'a, S>) -> AbstractOperation<'a, S> {
144        AbstractOperation::Op(op)
145    }
146}
147
148pub enum BuilderOpLike<S: Semantics> {
149    Builtin(S::BuiltinOperation),
150    LibBuiltin(LibBuiltinOperation<S>),
151    FromOperationId(OperationId),
152    Recurse,
153}
154
155impl<S: Semantics> BuilderOpLike<S> {
156    fn as_abstract_operation<'a>(
157        &'a self,
158        op_ctx: &'a OperationContext<S>,
159        partial_self_signature: &'a OperationSignature<S>,
160    ) -> Result<AbstractOperation<'a, S>, OperationBuilderError> {
161        let op = match self {
162            BuilderOpLike::Builtin(op) => AbstractOperation::Op(Operation::Builtin(op)),
163            BuilderOpLike::LibBuiltin(op) => AbstractOperation::Op(Operation::LibBuiltin(op)),
164            BuilderOpLike::FromOperationId(id) => {
165                let op = op_ctx
166                    .get(*id)
167                    .ok_or_else(|| OperationBuilderError::NotFoundOperationId(*id))?;
168                AbstractOperation::Op(op)
169            }
170            BuilderOpLike::Recurse => AbstractOperation::Partial(partial_self_signature),
171        };
172        Ok(op)
173    }
174
175    fn to_op_like_instruction(self, self_op_id: OperationId) -> OpLikeInstruction<S> {
176        match self {
177            BuilderOpLike::Builtin(op) => OpLikeInstruction::Builtin(op),
178            BuilderOpLike::LibBuiltin(op) => OpLikeInstruction::LibBuiltin(op),
179            BuilderOpLike::FromOperationId(id) => OpLikeInstruction::Operation(id),
180            BuilderOpLike::Recurse => OpLikeInstruction::Operation(self_op_id),
181        }
182    }
183}
184
185impl<S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>> Clone for BuilderOpLike<S> {
186    fn clone(&self) -> Self {
187        match self {
188            BuilderOpLike::Builtin(op) => BuilderOpLike::Builtin(op.clone()),
189            BuilderOpLike::LibBuiltin(op) => BuilderOpLike::LibBuiltin(op.clone()),
190            BuilderOpLike::FromOperationId(id) => BuilderOpLike::FromOperationId(*id),
191            BuilderOpLike::Recurse => BuilderOpLike::Recurse,
192        }
193    }
194}
195
196// TODO: rename to BuilderMessage? since Instruction is already used in the user-defined operation context.
197#[derive(derive_more::Debug)]
198pub enum BuilderInstruction<S: Semantics> {
199    #[debug("ExpectParameterNode({_0:?}, ???)")]
200    ExpectParameterNode(SubstMarker, S::NodeAbstract),
201    #[debug("ExpectContextNode({_0:?}, ???)")]
202    ExpectContextNode(SubstMarker, S::NodeAbstract),
203    #[debug("ExpectParameterEdge({_0:?}, {_1:?}, ???)")]
204    ExpectParameterEdge(SubstMarker, SubstMarker, S::EdgeAbstract),
205    #[debug("StartQuery(???, args: {_1:?})")]
206    StartQuery(S::BuiltinQuery, Vec<AbstractNodeId>),
207    #[debug("EnterTrueBranch")]
208    EnterTrueBranch,
209    #[debug("EnterFalseBranch")]
210    EnterFalseBranch,
211    // TODO: think about what happens when we start two shape queries with the same name. the gsq_op_marker if statement below somewhere is a problem.
212    //  specifically, when they're nested (eg one with name "foo", true branch, another one with "foo").
213    //  potentially could be fine to support, but needs implementation work.
214    #[debug("StartShapeQuery({_0:?})")]
215    StartShapeQuery(AbstractOperationResultMarker),
216    #[debug("EndQuery")]
217    EndQuery,
218    #[debug("ExpectShapeNode({_0:?}, ???)")]
219    // TODO: maybe should be renamed to ExpectNewShapeNode?
220    ExpectShapeNode(AbstractOutputNodeMarker, S::NodeAbstract),
221    #[debug("ExpectShapeNodeChange({_0:?}, ???)")]
222    ExpectShapeNodeChange(AbstractNodeId, S::NodeAbstract),
223    #[debug("ExpectShapeEdge({_0:?}, {_1:?}, ???)")]
224    ExpectShapeEdge(AbstractNodeId, AbstractNodeId, S::EdgeAbstract),
225    #[debug("SkipMarker({_0:?})")]
226    SkipMarker(Marker),
227    #[debug("SkipAllMarkers")]
228    SkipAllMarkers,
229    #[debug("AddNamedOperation({_0:?}, ???, args: {_2:?})")]
230    AddNamedOperation(
231        AbstractOperationResultMarker,
232        BuilderOpLike<S>,
233        Vec<AbstractNodeId>,
234    ),
235    // the same as AddNamedOperation, but without enforces the output to have a single node, and uses that node
236    // to create a AbstractNodeId::named node to bind to it.
237    #[debug("AddBangOperation({_0:?}, ???, args: {_2:?})")]
238    AddBangOperation(NamedMarker, BuilderOpLike<S>, Vec<AbstractNodeId>),
239    #[debug("AddOperation(???, args: {_1:?})")]
240    AddOperation(BuilderOpLike<S>, Vec<AbstractNodeId>),
241    #[debug("ReturnNode({_0:?}, {_1:?}, ???)")]
242    ReturnNode(AbstractNodeId, AbstractOutputNodeMarker, S::NodeAbstract),
243    #[debug("ReturnEdge({_0:?}, {_1:?}, ???)")]
244    ReturnEdge(AbstractNodeId, AbstractNodeId, S::EdgeAbstract),
245    #[debug("RenameNode({_0:?}, {_1:?})")]
246    /// Rename a dynamic output marker.
247    /// Invariants in the interpreter require that this is never a parameter node. (E.g., since we may want to return it)
248    RenameNode(AbstractNodeId, NamedMarker),
249    Finalize,
250    /// Asserts that the current operation will return a node with the given abstract value and name.
251    #[debug("SelfReturnNode({_0:?}, ???)")]
252    SelfReturnNode(AbstractOutputNodeMarker, S::NodeAbstract),
253    /// Diverge with a crash message.
254    /// Has a static effect: The branch is considered to never return, hence merges will always take the other branch.
255    #[debug("Diverge({_0})")]
256    Diverge(String),
257}
258
259impl<S: Semantics> BuilderInstruction<S> {
260    /// Returns true if this is an instruction that is valid to break out of a body of query/operation
261    /// instructions.
262    fn can_break_body(&self) -> bool {
263        use BuilderInstruction::*;
264        match self {
265            EnterTrueBranch | EnterFalseBranch | EndQuery | ReturnNode(..) | ReturnEdge(..)
266            | Finalize => true,
267            _ => false,
268        }
269    }
270}
271
272impl<S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>> Clone for BuilderInstruction<S> {
273    fn clone(&self) -> Self {
274        use BuilderInstruction::*;
275        match self {
276            ExpectParameterNode(marker, node) => ExpectParameterNode(marker.clone(), node.clone()),
277            ExpectContextNode(marker, node) => ExpectContextNode(marker.clone(), node.clone()),
278            ExpectParameterEdge(source_marker, target_marker, edge) => {
279                ExpectParameterEdge(source_marker.clone(), target_marker.clone(), edge.clone())
280            }
281            StartQuery(query, args) => StartQuery(query.clone(), args.clone()),
282            EnterTrueBranch => EnterTrueBranch,
283            EnterFalseBranch => EnterFalseBranch,
284            StartShapeQuery(op_marker) => StartShapeQuery(op_marker.clone()),
285            EndQuery => EndQuery,
286            ExpectShapeNode(marker, node) => ExpectShapeNode(marker.clone(), node.clone()),
287            ExpectShapeNodeChange(aid, node) => ExpectShapeNodeChange(aid.clone(), node.clone()),
288            ExpectShapeEdge(source, target, edge) => {
289                ExpectShapeEdge(source.clone(), target.clone(), edge.clone())
290            }
291            SkipMarker(marker) => SkipMarker(marker.clone()),
292            SkipAllMarkers => SkipAllMarkers,
293            AddNamedOperation(name, op, args) => {
294                AddNamedOperation(name.clone(), op.clone(), args.clone())
295            }
296            AddBangOperation(name, op, args) => {
297                AddBangOperation(name.clone(), op.clone(), args.clone())
298            }
299            AddOperation(op, args) => AddOperation(op.clone(), args.clone()),
300            ReturnNode(aid, output_marker, node) => {
301                ReturnNode(aid.clone(), output_marker.clone(), node.clone())
302            }
303            ReturnEdge(src, dst, edge) => ReturnEdge(src.clone(), dst.clone(), edge.clone()),
304            RenameNode(old_aid, new_name) => RenameNode(old_aid.clone(), new_name.clone()),
305            Finalize => Finalize,
306            SelfReturnNode(marker, node) => SelfReturnNode(marker.clone(), node.clone()),
307            Diverge(msg) => Diverge(msg.clone()),
308        }
309    }
310}
311
312#[derive(Error, Debug, Clone)]
313pub enum OperationBuilderError {
314    #[error("Expected a new unique subst marker, found repeat: {0:?}")]
315    ReusedSubstMarker(SubstMarker),
316    #[error("Expected an existing subst marker, but {0:?} was not found")]
317    NotFoundSubstMarker(SubstMarker),
318    #[error("Expected a new unique subst marker, found repeat: {0:?}")]
319    ReusedShapeIdent(ShapeNodeIdentifier),
320    #[error("Cannot call this while in a query context")]
321    InvalidInQuery,
322    #[error("Expected an operation or query")]
323    ExpectedOperationOrQuery,
324    #[error("Already visited the {0} branch of the active query")]
325    AlreadyVisitedBranch(bool),
326    #[error("Could not find abstract node id: {0:?}")]
327    NotFoundAid(AbstractNodeId),
328    #[error("AID {0:?} already exists")]
329    AlreadyExistsAid(AbstractNodeId),
330    #[error("Could not find operation ID: {0}")]
331    NotFoundOperationId(OperationId),
332    #[error("Could not apply operation due to mismatched arguments: {0}")]
333    SubstitutionError(#[from] crate::operation::SubstitutionError),
334    #[error("Could not apply operation due to mismatched arguments")]
335    SubstitutionErrorNew,
336    #[error("Could not abstractly apply operation {0} due to: {1}")]
337    AbstractApplyOperationErrorWithId(OperationId, OperationError),
338    #[error("Could not abstractly apply operation due to: {0}")]
339    AbstractApplyOperationError(OperationError),
340    #[error("Could not abstractly apply operation")]
341    AbstractApplyOperationError2,
342    #[error("Superfluous instruction {0}")]
343    SuperfluousInstruction(String),
344    #[error("Already selected to return node {0:?}")]
345    AlreadySelectedReturnNode(AbstractNodeId),
346    #[error("Already selected to return edge {0:?}->{1:?}")]
347    AlreadySelectedReturnEdge(AbstractNodeId, AbstractNodeId),
348    #[error("Could not find AID {0:?} for return node")]
349    NotFoundReturnNode(AbstractNodeId),
350    #[error("Invalid return node type for AID {0:?}, must be more generic")]
351    InvalidReturnNodeType(AbstractNodeId),
352    // TODO: document why this is not allowed ...
353    //  in general, add lots more documentation.
354    #[error("Returned {0:?} node may have been created by a shape query, which is not allowed")]
355    ReturnNodeMayOriginateFromShapeQuery(AbstractNodeId),
356    #[error("Cannot return a parameter node: {0:?}")]
357    CannotReturnParameter(AbstractNodeId),
358    #[error("Could not find AID {0:?} for return edge source")]
359    NotFoundReturnEdgeSource(AbstractNodeId),
360    #[error("Could not find AID {0:?} for return edge target")]
361    NotFoundReturnEdgeTarget(AbstractNodeId),
362    #[error("Could not statically determine edge {0:?}->{1:?} to be available")]
363    NotFoundReturnEdge(AbstractNodeId, AbstractNodeId),
364    #[error("Invalid return edge type for AID {0:?}->{1:?}, must be more generic")]
365    InvalidReturnEdgeType(AbstractNodeId, AbstractNodeId),
366    #[error(
367        "Return edge {0:?}->{1:?} may have been created by a shape query, which is not allowed"
368    )]
369    ReturnEdgeMayOriginateFromShapeQuery(AbstractNodeId, AbstractNodeId),
370    #[error("internal error: {0}")]
371    InternalError(&'static str),
372    #[error("Explicitly selected input AID not found")]
373    SelectedInputsNotFoundAid,
374    #[error("Shape edge target node not found")]
375    ShapeEdgeTargetNotFound,
376    #[error("Shape edge source node not found")]
377    ShapeEdgeSourceNotFound,
378    #[error(
379        "Cannot rename parameter node {0:?}, only new nodes from operation calls can be renamed"
380    )]
381    CannotRenameParameterNode(AbstractNodeId),
382    #[error("Invalid parameter")]
383    InvalidParameter,
384    // just for testing of the explicit stack-based builder
385    #[error("New builder error")]
386    NewBuilderError,
387}
388
389// type alias to switch between implementations globally
390// pub type OperationBuilder<'a, S> = OperationBuilderInefficient<'a, S>;
391pub type OperationBuilder<'a, S> = stack_based_builder::OperationBuilder2<'a, S>;
392
393pub struct OperationBuilderInefficient<'a, S: Semantics> {
394    op_ctx: &'a OperationContext<S>,
395    self_op_id: OperationId,
396    instructions: Vec<BuilderInstruction<S>>,
397    // hack for recursion
398    previous_user_defined_operation: RefCell<UserDefinedOperation<S>>,
399}
400
401impl<'a, S: Semantics<BuiltinQuery: Clone, BuiltinOperation: Clone>>
402    OperationBuilderInefficient<'a, S>
403{
404    pub fn new(op_ctx: &'a OperationContext<S>, self_op_id: OperationId) -> Self {
405        Self {
406            self_op_id,
407            instructions: Vec::new(),
408            op_ctx,
409            previous_user_defined_operation: RefCell::new(UserDefinedOperation::new_noop()),
410        }
411    }
412
413    pub fn undo_last_instruction(&mut self) {
414        if !self.instructions.is_empty() {
415            self.instructions.pop();
416        }
417        self.check_instructions()
418            .expect("internal error: a prefix slice of instructions should always be valid");
419    }
420
421    pub fn rename_node(
422        &mut self,
423        old_aid: AbstractNodeId,
424        new_name: impl Into<NamedMarker>,
425    ) -> Result<(), OperationBuilderError> {
426        let new_name = new_name.into();
427        self.instructions
428            .push(BuilderInstruction::RenameNode(old_aid, new_name));
429        self.check_instructions_or_rollback()
430    }
431
432    pub fn expect_parameter_node(
433        &mut self,
434        marker: impl Into<SubstMarker>,
435        node: S::NodeAbstract,
436    ) -> Result<(), OperationBuilderError> {
437        let marker = marker.into();
438        self.instructions
439            .push(BuilderInstruction::ExpectParameterNode(marker, node));
440        self.check_instructions_or_rollback()
441    }
442
443    pub fn expect_context_node(
444        &mut self,
445        marker: impl Into<SubstMarker>,
446        node: S::NodeAbstract,
447    ) -> Result<(), OperationBuilderError> {
448        let marker = marker.into();
449        self.instructions
450            .push(BuilderInstruction::ExpectContextNode(marker, node));
451        // TODO: check if subst marker does not exist yet
452        self.check_instructions_or_rollback()
453    }
454
455    pub fn expect_parameter_edge(
456        &mut self,
457        source_marker: impl Into<SubstMarker>,
458        target_marker: impl Into<SubstMarker>,
459        edge: S::EdgeAbstract,
460    ) -> Result<(), OperationBuilderError> {
461        let source_marker = source_marker.into();
462        let target_marker = target_marker.into();
463        self.instructions
464            .push(BuilderInstruction::ExpectParameterEdge(
465                source_marker,
466                target_marker,
467                edge,
468            ));
469        // TODO: check if both subst markers are valid
470        self.check_instructions_or_rollback()
471    }
472
473    pub fn start_query(
474        &mut self,
475        query: S::BuiltinQuery,
476        args: Vec<AbstractNodeId>,
477    ) -> Result<(), OperationBuilderError> {
478        // todo!()
479        self.instructions
480            .push(BuilderInstruction::StartQuery(query, args));
481        self.check_instructions_or_rollback()
482    }
483
484    pub fn enter_true_branch(&mut self) -> Result<(), OperationBuilderError> {
485        // todo!()
486        self.instructions.push(BuilderInstruction::EnterTrueBranch);
487        self.check_instructions_or_rollback()
488    }
489
490    pub fn enter_false_branch(&mut self) -> Result<(), OperationBuilderError> {
491        // todo!()
492        self.instructions.push(BuilderInstruction::EnterFalseBranch);
493        self.check_instructions_or_rollback()
494    }
495
496    // TODO: get rid of AbstractOperationResultMarker requirement. Either completely or make it optional and autogenerate one.
497    //  How to specify which shape node? ==> the shape node markers should be unique per path
498    // TODO: Shape queries cannot shape-test for abstract values of existing nodes yet!
499    // TODO: Also add test for existing edges between existing nodes.
500    pub fn start_shape_query(
501        &mut self,
502        op_marker: impl Into<AbstractOperationResultMarker>,
503    ) -> Result<(), OperationBuilderError> {
504        // todo!()
505        self.instructions
506            .push(BuilderInstruction::StartShapeQuery(op_marker.into()));
507        self.check_instructions_or_rollback()
508    }
509
510    pub fn end_query(&mut self) -> Result<(), OperationBuilderError> {
511        // todo!()
512        self.instructions.push(BuilderInstruction::EndQuery);
513        self.check_instructions_or_rollback()
514    }
515
516    // TODO: should expect_*_node really expect a marker? maybe it should instead return a marker?
517    //  it could also take an Option<Marker> so that it can autogenerate one if it's none so the caller doesn't have to deal with it.
518    pub fn expect_shape_node(
519        &mut self,
520        marker: AbstractOutputNodeMarker,
521        node: S::NodeAbstract,
522    ) -> Result<(), OperationBuilderError> {
523        // TODO: check that any shape nodes are not free floating. maybe this should be in a GraphShapeQuery validator?
524        self.instructions
525            .push(BuilderInstruction::ExpectShapeNode(marker, node));
526        self.check_instructions_or_rollback()
527    }
528
529    pub fn expect_shape_node_change(
530        &mut self,
531        aid: AbstractNodeId,
532        node: S::NodeAbstract,
533    ) -> Result<(), OperationBuilderError> {
534        self.instructions
535            .push(BuilderInstruction::ExpectShapeNodeChange(aid, node));
536        self.check_instructions_or_rollback()
537    }
538
539    pub fn expect_shape_edge(
540        &mut self,
541        source: AbstractNodeId,
542        target: AbstractNodeId,
543        edge: S::EdgeAbstract,
544    ) -> Result<(), OperationBuilderError> {
545        // TODO:
546        self.instructions
547            .push(BuilderInstruction::ExpectShapeEdge(source, target, edge));
548        self.check_instructions_or_rollback()
549    }
550
551    pub fn add_named_operation(
552        &mut self,
553        name: AbstractOperationResultMarker,
554        op: BuilderOpLike<S>,
555        args: Vec<AbstractNodeId>,
556    ) -> Result<(), OperationBuilderError> {
557        // TODO
558        self.instructions
559            .push(BuilderInstruction::AddNamedOperation(name, op, args));
560        self.check_instructions_or_rollback()
561    }
562
563    pub fn add_bang_operation(
564        &mut self,
565        name: impl Into<NamedMarker>,
566        op: BuilderOpLike<S>,
567        args: Vec<AbstractNodeId>,
568    ) -> Result<(), OperationBuilderError> {
569        // TODO
570        self.instructions
571            .push(BuilderInstruction::AddBangOperation(name.into(), op, args));
572        self.check_instructions_or_rollback()
573    }
574
575    pub fn add_operation(
576        &mut self,
577        op: BuilderOpLike<S>,
578        args: Vec<AbstractNodeId>,
579    ) -> Result<(), OperationBuilderError> {
580        // todo!()
581        self.instructions
582            .push(BuilderInstruction::AddOperation(op, args));
583        self.check_instructions_or_rollback()?;
584        Ok(())
585    }
586
587    /// Indicate that a node should be marked in the output with the given abstract value.
588    ///
589    /// Note that the abstract value must be a supertype of the node's statically determined type.
590    /// Also, the node must be visible in the end context of the operation, and must never have
591    /// been statically determined by a shape query.
592    ///
593    /// These instructions must be the very last instructions in the operation builder.
594    pub fn return_node(
595        &mut self,
596        aid: AbstractNodeId,
597        output_marker: AbstractOutputNodeMarker,
598        node: S::NodeAbstract,
599    ) -> Result<(), OperationBuilderError> {
600        // dont support returning parameter nodes
601        if let AbstractNodeId::ParameterMarker(..) = &aid {
602            bail!(OperationBuilderError::CannotReturnParameter(aid));
603        }
604        self.instructions
605            .push(BuilderInstruction::ReturnNode(aid, output_marker, node));
606        self.check_instructions_or_rollback()
607    }
608
609    /// Indicate that an edge should be marked in the output with the given abstract value.
610    ///
611    /// Note that the edge must be a supertype of the edge's statically determined type.
612    /// Also, the edge must be visible in the end context of the operation, and must never have
613    /// been statically determined by a shape query.
614    ///
615    /// Further, new edges may only be returned if both endpoints of the edge are either parameter
616    /// nodes or new nodes also returned by the operation.
617    ///
618    /// These instructions must be the very last instructions in the operation builder.
619    pub fn return_edge(
620        &mut self,
621        src: AbstractNodeId,
622        dst: AbstractNodeId,
623        edge: S::EdgeAbstract,
624    ) -> Result<(), OperationBuilderError> {
625        // TODO: validate that the edge did not already exist in the param graph anyway.
626        self.instructions
627            .push(BuilderInstruction::ReturnEdge(src, dst, edge));
628        self.check_instructions_or_rollback()
629    }
630
631    // TODO: This should run further post processing checks.
632    //  Stuff like Context nodes must be connected, etc.
633    pub fn build(&self) -> Result<UserDefinedOperation<S>, OperationBuilderError> {
634        // Here we would typically finalize the operation and return it.
635        // For now, we just return Ok to indicate success.
636
637        let builder_result = IntermediateStateBuilder::run(&self.instructions, self.op_ctx)?;
638
639        let param = builder_result.operation_parameter;
640        let instructions = builder_result.instructions;
641        let prev_user_ref = self.previous_user_defined_operation.borrow();
642        let mut interpreter = IntermediateInterpreter::new_for_self_op_id(
643            self.self_op_id,
644            param,
645            self.op_ctx,
646            &prev_user_ref,
647        );
648
649        let user_def_op = interpreter.create_user_defined_operation(
650            instructions,
651            builder_result.return_nodes,
652            builder_result.return_edges,
653        )?;
654
655        // TODO: this is bad. we check validity of parameter both here and when encountering the next instructions.
656        //  would be nicer if we had some way of indicating "validate this once current input phase is over"
657        //  e.g., validate parameter once the parameter definition phase is over OR the function needs to be built.
658        // check if the parameter is valid:
659        user_def_op
660            .signature
661            .parameter
662            .check_validity()
663            .change_context(OperationBuilderError::InvalidParameter)?;
664
665        Ok(user_def_op)
666    }
667
668    fn check_instructions_or_rollback(&mut self) -> Result<(), OperationBuilderError> {
669        match self.check_instructions() {
670            Ok(_) => Ok(()),
671            Err(e) => {
672                // If the instructions are invalid, we rollback the last instruction.
673                // This is a simple rollback mechanism, but could be improved.
674                if !self.instructions.is_empty() {
675                    self.instructions.pop();
676                }
677                Err(e)
678            }
679        }
680    }
681
682    fn check_instructions(&self) -> Result<(), OperationBuilderError> {
683        let builder_result = IntermediateStateBuilder::run(&self.instructions, self.op_ctx)?;
684
685        let partial_user_def_op = {
686            let prev_user_ref = self.previous_user_defined_operation.borrow();
687            let mut interpreter = IntermediateInterpreter::new_for_self_op_id(
688                0, // Unused. TODO: make prettier...
689                builder_result.operation_parameter,
690                self.op_ctx,
691                &prev_user_ref,
692            );
693            interpreter.create_user_defined_operation(
694                builder_result.instructions,
695                builder_result.return_nodes,
696                builder_result.return_edges,
697            )?
698        };
699        *self.previous_user_defined_operation.borrow_mut() = partial_user_def_op;
700
701        // in theory we should run it again with the new instruction.
702        Ok(())
703    }
704}
705
706impl<
707    'a,
708    S: Semantics<
709            NodeAbstract: Debug,
710            EdgeAbstract: Debug,
711            BuiltinOperation: Clone,
712            BuiltinQuery: Clone,
713        >,
714> OperationBuilderInefficient<'a, S>
715{
716    fn get_intermediate_state(
717        &self,
718    ) -> Result<(IntermediateState<S>, Vec<IntermediateStatePath>), OperationBuilderError> {
719        let builder_result = IntermediateStateBuilder::run(&self.instructions, self.op_ctx)?;
720        let prev_user_ref = self.previous_user_defined_operation.borrow();
721        let mut interpreter = IntermediateInterpreter::new_for_self_op_id(
722            0, // TODO: use a real operation ID here
723            builder_result.operation_parameter,
724            self.op_ctx,
725            &prev_user_ref,
726        );
727
728        let (_, interp_instructions) =
729            interpreter.interpret_instructions(builder_result.instructions)?;
730        let path = builder_result.state_path;
731        let mut path_iter = path.iter().peekable().cloned();
732
733        let mut intermediate_state = get_state_for_path(
734            &interpreter.initial_state,
735            &interp_instructions,
736            &mut path_iter,
737        )
738        .expect("internal error: Failed to get intermediate state for path");
739
740        let query_path = get_query_path_for_path::<S>(&mut path.iter().peekable().cloned());
741        // TODO: make this prettier. should be automatically computed.
742        intermediate_state.query_path = query_path;
743
744        // let dot = intermediate_state.graph.dot();
745        // let mapping = intermediate_state.node_keys_to_aid.into_inner().0;
746        // let query_path = intermediate_state.query_path;
747
748        Ok((intermediate_state, path))
749    }
750
751    /// Visualizes the current state of the operation builder.
752    /// Provides context on the current nest level as well as the DOT representation of the graph
753    /// at the current cursor.
754    pub fn show_state(&self) -> Result<IntermediateState<S>, OperationBuilderError> {
755        // let (g, subst_to_node_keys) = self.build_debug_graph_at_current_point();
756        // let dot = g.dot();
757        //
758        // let mut result = String::new();
759        //
760        // result.push_str(&"Current Operation Builder State:\n".to_string());
761        // result.push_str(&"Graph at current point:\n".to_string());
762        // result.push_str(&dot);
763        // result
764        // TODO: an error implies the builder contains incorrect partial information.
765        //  in such a case, it should have rolled back the last instruction.
766        //  hence we should be fine to unwrap and not return a Result here.
767        Ok(self.get_intermediate_state()?.0)
768    }
769
770    pub fn format_state(&self) -> String {
771        // TODO: should probably return a Result
772        let (state, path) = self.get_intermediate_state().unwrap();
773        let dot = state.graph.dot();
774        let mapping = state.node_keys_to_aid.into_inner().0;
775        let query_path = state.query_path;
776        format!("\nIntermediate State:\n{dot}\nmapping: {mapping:#?}\nTODO query path")
777    }
778}
779
780struct IntermediateStateBuilder<'a, S: Semantics> {
781    path: Vec<IntermediateStatePath>,
782    _phantom_data: PhantomData<&'a S>,
783}
784
785use super::user_defined::Instruction as UDInstruction;
786
787#[derive(derive_more::Debug)]
788enum IntermediateInstruction<S: Semantics> {
789    OpLike(IntermediateOpLike<S>),
790    // #[debug("GraphShapeQuery({marker:#?}, {graph_instructions:#?}, {query_instructions:#?})")]
791    GraphShapeQuery {
792        /// Is the query finished and should we check it for connectedness?
793        is_finished: bool,
794        marker: AbstractOperationResultMarker,
795        graph_instructions: Vec<GraphShapeQueryInstruction<S>>,
796        query_instructions: IntermediateQueryInstructions<S>,
797    },
798    #[debug("BuiltinQuery(???, {_1:#?}, {_2:#?})")]
799    BuiltinQuery(
800        S::BuiltinQuery,
801        Vec<AbstractNodeId>,
802        IntermediateQueryInstructions<S>,
803    ),
804    // TODO: move to oplike?
805    RenameNode {
806        aid: AbstractNodeId,
807        new_name: NamedMarker,
808    },
809}
810
811#[derive(derive_more::Debug)]
812enum IntermediateOpLike<S: Semantics> {
813    #[debug("Builtin(???, {_1:#?})")]
814    Builtin(S::BuiltinOperation, Vec<AbstractNodeId>),
815    LibBuiltin(LibBuiltinOperation<S>, Vec<AbstractNodeId>),
816    Operation(OperationId, Vec<AbstractNodeId>),
817    Recurse(Vec<AbstractNodeId>),
818}
819
820#[derive(derive_more::Debug)]
821struct IntermediateQueryInstructions<S: Semantics> {
822    #[debug("[{}]", true_branch.iter().map(|(opt, inst)| format!("({opt:#?}, {:#?})", inst)).collect::<Vec<_>>().join(", "))]
823    true_branch: Vec<(
824        Option<AbstractOperationResultMarker>,
825        IntermediateInstruction<S>,
826    )>,
827    #[debug("[{}]", false_branch.iter().map(|(opt, inst)| format!("({opt:#?}, {:#?})", inst)).collect::<Vec<_>>().join(", "))]
828    false_branch: Vec<(
829        Option<AbstractOperationResultMarker>,
830        IntermediateInstruction<S>,
831    )>,
832}
833
834#[derive(derive_more::Debug)]
835enum GraphShapeQueryInstruction<S: Semantics> {
836    #[debug("ExpectShapeNode({_0:#?})")]
837    ExpectShapeNode(AbstractOutputNodeMarker, S::NodeAbstract),
838    #[debug("ExpectShapeNodeChange({_0:#?})")]
839    ExpectShapeNodeChange(AbstractNodeId, S::NodeAbstract),
840    #[debug("ExpectShapeEdge({_0:#?}, {_1:#?})")]
841    ExpectShapeEdge(AbstractNodeId, AbstractNodeId, S::EdgeAbstract),
842}
843
844struct BuilderResult<S: Semantics> {
845    operation_parameter: OperationParameter<S>,
846    instructions: Vec<(
847        Option<AbstractOperationResultMarker>,
848        IntermediateInstruction<S>,
849    )>,
850    state_path: Vec<IntermediateStatePath>,
851    return_nodes: HashMap<AbstractNodeId, (AbstractOutputNodeMarker, S::NodeAbstract)>,
852    return_edges: HashMap<(AbstractNodeId, AbstractNodeId), S::EdgeAbstract>,
853}
854
855// TODO: maybe this is not *intermediate* but actually the final state as well potentially?
856impl<'a, S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>>
857    IntermediateStateBuilder<'a, S>
858{
859    fn run(
860        builder_instructions: &'a [BuilderInstruction<S>],
861        op_ctx: &'a OperationContext<S>,
862    ) -> Result<BuilderResult<S>, OperationBuilderError> {
863        /*
864        General idea:
865        Whenever we see start_query (or start_shape_query), we push a query state onto a stack.
866        When we see
867
868        */
869
870        //
871        // enum QueryBranchState {
872        //     // if we haven't encountered an enter_*_branch message yet
873        //     NoBranch,
874        //     TrueBranch,
875        //     FalseBranch,
876        // }
877        // struct QueryState<S: SemanticsClone> {
878        //     true_instructions: Vec<UDInstruction<S>>,
879        //     false_instructions: Vec<UDInstruction<S>>,
880        //     current_branch: QueryBranchState,
881        // }
882        //
883        // enum StackState {
884        //
885        // }
886        //
887        // #[derive(Clone, Copy, Debug)]
888        // enum State {
889        //     BuildingParameterGraph,
890        //     ExpectingInstruction,
891        //     BuildingQuery,
892        //     BuildingShapeQuery,
893        // }
894        //
895        // let mut current_query_branch_state: Option<QueryBranchState> = None;
896        // let mut current_state = State::BuildingParameterGraph;
897        //
898        // let mut operation_parameter = OperationParameter::<S> {
899        //     explicit_input_nodes: Vec::new(),
900        //     parameter_graph: AbstractGraph::<S>::new(),
901        //     subst_to_node_keys: HashMap::new(),
902        //     node_keys_to_subst: HashMap::new(),
903        // };
904        //
905        // // unsure if we need these.
906        // let mut abstract_graph = AbstractGraph::<S>::new();
907        // let mut aid_to_node_keys: HashMap<AbstractNodeId, NodeKey> = HashMap::new();
908        // let mut node_keys_to_aid: HashMap<NodeKey, AbstractNodeId> = HashMap::new();
909        //
910        // // build a partial UserDefinedOperation.
911        // // This UserDefinedOperation is what we will use to build the partial abstract graph at that state.
912        //
913        // // This is a stack of instruction vectors.
914        // let mut instructions_vec_stack: Vec<Vec<UDInstruction<S>>> = Vec::new();
915        // // We push any new instructions onto this vector.
916        // let mut current_instructions_vec: Vec<UDInstruction<S>> = Vec::new();
917        //
918        //
919        // for instruction in instructions {
920        //     let mut next_state = current_state;
921        //     match (current_state, instruction) {
922        //         (State::BuildingParameterGraph, BuilderInstruction::ExpectParameterNode(marker, node_abstract)) => {
923        //             if operation_parameter.subst_to_node_keys.contains_key(marker) {
924        //                 return Err(OperationBuilderError::ReusedSubstMarker(*marker));
925        //             }
926        //             let key = operation_parameter.parameter_graph.add_node(node_abstract.clone());
927        //             operation_parameter.subst_to_node_keys.insert(*marker, key);
928        //             operation_parameter.node_keys_to_subst.insert(key, *marker);
929        //             operation_parameter.explicit_input_nodes.push(*marker);
930        //         }
931        //         (State::BuildingParameterGraph, BuilderInstruction::ExpectContextNode(marker, node_abstract)) => {
932        //             if operation_parameter.subst_to_node_keys.contains_key(marker) {
933        //                 return Err(OperationBuilderError::ReusedSubstMarker(*marker));
934        //             }
935        //             let key = operation_parameter.parameter_graph.add_node(node_abstract.clone());
936        //             operation_parameter.subst_to_node_keys.insert(*marker, key);
937        //             operation_parameter.node_keys_to_subst.insert(key, *marker);
938        //         }
939        //         (State::BuildingParameterGraph, BuilderInstruction::ExpectParameterEdge(source_marker, target_marker, edge_abstract)) => {
940        //             let source_key = operation_parameter.subst_to_node_keys.get(source_marker)
941        //                 .ok_or(OperationBuilderError::NotFoundSubstMarker(*source_marker))?;
942        //             let target_key = operation_parameter.subst_to_node_keys.get(target_marker)
943        //                 .ok_or(OperationBuilderError::NotFoundSubstMarker(*target_marker))?;
944        //             operation_parameter.parameter_graph.add_edge(*source_key, *target_key, edge_abstract.clone());
945        //         }
946        //         (State::ExpectingInstruction | State::BuildingParameterGraph, BuilderInstruction::AddInstruction(instruction, args)) => {
947        //             next_state = State::ExpectingInstruction;
948        //
949        //             match instruction {
950        //                 Instruction::Builtin(builtin_op) => {
951        //                     // Here we would typically apply the builtin operation to the abstract graph.
952        //                     // For now, we just log it.
953        //                     println!("Applying builtin operation: {:?} args: {args:?}", builtin_op);
954        //
955        //                     current_instructions_vec.push(UDInstruction::Builtin(builtin_op.clone(), args.clone()));
956        //                 }
957        //                 Instruction::FromOperationId(op_id) => {
958        //                     // Here we would typically look up the operation by its ID and apply it.
959        //                     // For now, we just log it.
960        //                     println!("Applying operation with ID: {:?} args: {args:?}", op_id);
961        //
962        //                     current_instructions_vec.push(UDInstruction::Operation(op_id.clone(), args.clone()));
963        //                 }
964        //                 Instruction::Recurse => {
965        //                     // This would typically mean we need to recurse into another operation.
966        //                     // For now, we just log it.
967        //                     println!("Recursing into self with args: {args:?}");
968        //
969        //                     // TODO: somehow denote 'self' instead of 0
970        //                     current_instructions_vec.push(UDInstruction::Operation(0, args.clone()));
971        //                 }
972        //             }
973        //         }
974        //         (State::ExpectingInstruction | State::BuildingParameterGraph, BuilderInstruction::StartQuery(query, args)) => {
975        //             next_state = State::BuildingQuery;
976        //
977        //             // Start a new query state
978        //             current_query_branch_state = Some(QueryBranchState::NoBranch);
979        //             instructions_vec_stack.push(current_instructions_vec);
980        //             current_instructions_vec = Vec::new();
981        //             // TODO: try continue here. the 'parsing' style below is easier, but this stack based version would allow easy caching.
982        //         }
983        //         _ => {}
984        //     }
985        //     current_state = next_state;
986        // }
987
988        let mut iter = builder_instructions.iter().peekable();
989
990        let op_parameter = Self::build_operation_parameter(&mut iter)?;
991
992        // check validity of the parameter if there's more instructions, since that implies the parameter should be done.
993        if iter.peek().is_some() {
994            op_parameter
995                .check_validity()
996                .change_context(OperationBuilderError::InvalidParameter)?;
997        }
998
999        let mut builder = Self {
1000            _phantom_data: PhantomData,
1001            path: Vec::new(),
1002        };
1003
1004        let instructions = builder.build_many_instructions(&mut iter)?;
1005
1006        let mut return_nodes = HashMap::new();
1007        let mut return_edges = HashMap::new();
1008        // if we are outside all queries, check for ReturnNode instructions.
1009        if !builder
1010            .path
1011            .iter()
1012            .any(|i| matches!(i, IntermediateStatePath::StartQuery(..)))
1013        {
1014            // we are outside all queries
1015            (return_nodes, return_edges) = Self::collect_return_instructions(&mut iter)?;
1016            // TODO: validate that we have not encountered a Recurse instruction. In recursive queries we cannot statically return.
1017        }
1018
1019        // assert our iter is empty
1020        if let Some(next_instruction) = iter.peek() {
1021            bail!(OperationBuilderError::SuperfluousInstruction(format!(
1022                "{next_instruction:?}"
1023            )));
1024        }
1025
1026        Ok(BuilderResult {
1027            operation_parameter: op_parameter,
1028            instructions,
1029            state_path: builder.path,
1030            return_nodes,
1031            return_edges,
1032        })
1033    }
1034
1035    fn build_many_instructions(
1036        &mut self,
1037        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1038    ) -> Result<
1039        Vec<(
1040            Option<AbstractOperationResultMarker>,
1041            IntermediateInstruction<S>,
1042        )>,
1043        OperationBuilderError,
1044    > {
1045        let mut instructions = Vec::new();
1046
1047        while let Some(instr) = iter.peek() {
1048            // break on control flow instructions and don't consume
1049            if instr.can_break_body() {
1050                break;
1051            }
1052            instructions.push(self.build_instruction(iter)?);
1053        }
1054        Ok(instructions)
1055    }
1056
1057    fn build_instruction(
1058        &mut self,
1059        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1060    ) -> Result<
1061        (
1062            Option<AbstractOperationResultMarker>,
1063            IntermediateInstruction<S>,
1064        ),
1065        OperationBuilderError,
1066    > {
1067        let next_instruction = iter
1068            .peek()
1069            .expect("should only be called when there is an instruction");
1070        match next_instruction {
1071            BuilderInstruction::AddNamedOperation(_, oplike, args)
1072            | BuilderInstruction::AddOperation(oplike, args) => {
1073                let name =
1074                    if let BuilderInstruction::AddNamedOperation(name, _, _) = next_instruction {
1075                        Some(name.clone())
1076                    } else {
1077                        None
1078                    };
1079                iter.next();
1080
1081                let oplike = match oplike {
1082                    BuilderOpLike::Builtin(builtin_op) => {
1083                        IntermediateOpLike::Builtin(builtin_op.clone(), args.clone())
1084                    }
1085                    BuilderOpLike::LibBuiltin(lib_builtin_op) => {
1086                        IntermediateOpLike::LibBuiltin(lib_builtin_op.clone(), args.clone())
1087                    }
1088                    BuilderOpLike::FromOperationId(op_id) => {
1089                        IntermediateOpLike::Operation(op_id.clone(), args.clone())
1090                    }
1091                    BuilderOpLike::Recurse => IntermediateOpLike::Recurse(args.clone()),
1092                };
1093                self.path.push(IntermediateStatePath::Advance);
1094                Ok((name, IntermediateInstruction::OpLike(oplike)))
1095            }
1096            BuilderInstruction::StartQuery(query, args) => {
1097                iter.next();
1098                // Start a new query state
1099                self.path.push(IntermediateStatePath::StartQuery(None));
1100                let query_instructions = self.build_query_instruction(iter)?;
1101                Ok((
1102                    None,
1103                    IntermediateInstruction::BuiltinQuery(
1104                        query.clone(),
1105                        args.clone(),
1106                        query_instructions,
1107                    ),
1108                ))
1109            }
1110            BuilderInstruction::StartShapeQuery(op_marker) => {
1111                iter.next();
1112                self.path
1113                    .push(IntermediateStatePath::StartQuery(Some(format!(
1114                        "{op_marker:?}"
1115                    ))));
1116                // Start a new shape query state
1117                let (is_finished, gsq_instructions, branch_instructions) =
1118                    self.build_shape_query(iter, op_marker.clone())?;
1119                // Ok((Some(*op_marker), UDInstruction::ShapeQuery()))
1120                Ok((
1121                    Some(op_marker.clone()), // NOTE: this marker is needed as well for the _concrete_ execution
1122                    IntermediateInstruction::GraphShapeQuery {
1123                        is_finished,
1124                        marker: op_marker.clone(),
1125                        graph_instructions: gsq_instructions,
1126                        query_instructions: branch_instructions,
1127                    },
1128                ))
1129            }
1130            BuilderInstruction::RenameNode(old_aid, new_name) => {
1131                iter.next();
1132                self.path.push(IntermediateStatePath::Advance);
1133                Ok((
1134                    None,
1135                    IntermediateInstruction::RenameNode {
1136                        aid: *old_aid,
1137                        new_name: *new_name,
1138                    },
1139                ))
1140            }
1141            _ => bail!(OperationBuilderError::ExpectedOperationOrQuery),
1142        }
1143    }
1144
1145    fn build_shape_query(
1146        &mut self,
1147        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1148        operation_marker: AbstractOperationResultMarker,
1149    ) -> Result<
1150        (
1151            bool,
1152            Vec<GraphShapeQueryInstruction<S>>,
1153            IntermediateQueryInstructions<S>,
1154        ),
1155        OperationBuilderError,
1156    > {
1157        // we just consumed a StartShapeQuery instruction.
1158
1159        // let mut gsq = GraphShapeQuery {
1160        //     parameter: OperationParameter {
1161        //         explicit_input_nodes: vec![],
1162        //         parameter_graph: Graph::new(),
1163        //         subst_to_node_keys: Default::default(),
1164        //         node_keys_to_subst: Default::default(),
1165        //     },
1166        //     expected_graph: Graph::new(),
1167        //     node_keys_to_shape_idents: Default::default(),
1168        //     shape_idents_to_node_keys: Default::default(),
1169        // };
1170
1171        let mut gsq_instructions = vec![];
1172
1173        let mut true_branch_instructions = None;
1174        let mut false_branch_instructions = None;
1175
1176        // did the user finish the query and should we check that it's finished?
1177        // TODO: this doesn't capture the case where the user finishes the entire UDOp without entering a branch or ending the query.
1178        let mut did_query_finish = false;
1179
1180        while let Some(instruction) = iter.peek() {
1181            match instruction {
1182                BuilderInstruction::EnterTrueBranch => {
1183                    iter.next();
1184                    if true_branch_instructions.is_some() {
1185                        bail!(OperationBuilderError::AlreadyVisitedBranch(true));
1186                    }
1187                    // we are entering a true branch, so we remove the false branch from the path
1188                    self.remove_until_branch(false);
1189                    self.path.push(IntermediateStatePath::EnterTrue);
1190                    true_branch_instructions = Some(self.build_many_instructions(iter)?);
1191                    did_query_finish = true;
1192                }
1193                BuilderInstruction::EnterFalseBranch => {
1194                    iter.next();
1195                    if false_branch_instructions.is_some() {
1196                        bail!(OperationBuilderError::AlreadyVisitedBranch(false));
1197                    }
1198                    // we are entering a false branch, so we remove the true branch from the path
1199                    self.remove_until_branch(true);
1200                    self.path.push(IntermediateStatePath::EnterFalse);
1201                    false_branch_instructions = Some(self.build_many_instructions(iter)?);
1202                    did_query_finish = true;
1203                }
1204                BuilderInstruction::EndQuery => {
1205                    iter.next();
1206                    // we are ending the query, so we remove the current query state from the path
1207                    self.remove_until_query_start();
1208                    self.path.push(IntermediateStatePath::SkipQuery);
1209                    did_query_finish = true;
1210                    break;
1211                }
1212                BuilderInstruction::ExpectShapeNode(marker, abstract_value) => {
1213                    // TODO: do we want to assert that we haven't entered any branches yet? probably...
1214                    iter.next();
1215                    // let shape_node_ident = marker.0.into();
1216                    // if gsq.shape_idents_to_node_keys.contains_key(&shape_node_ident) {
1217                    //     return Err(OperationBuilderError::ReusedShapeIdent(shape_node_ident));
1218                    // }
1219                    // let key = gsq.expected_graph.add_node(abstract_value.clone());
1220                    // gsq.node_keys_to_shape_idents.insert(key, shape_node_ident);
1221                    // gsq.shape_idents_to_node_keys.insert(shape_node_ident, key);
1222
1223                    gsq_instructions.push(GraphShapeQueryInstruction::ExpectShapeNode(
1224                        marker.clone(),
1225                        abstract_value.clone(),
1226                    ));
1227                }
1228                BuilderInstruction::ExpectShapeNodeChange(aid, new_av) => {
1229                    iter.next();
1230                    gsq_instructions.push(GraphShapeQueryInstruction::ExpectShapeNodeChange(
1231                        aid.clone(),
1232                        new_av.clone(),
1233                    ));
1234                }
1235                BuilderInstruction::ExpectShapeEdge(source, target, abstract_value) => {
1236                    iter.next();
1237                    // Note: we need a current view of the abstract graph (or, well, AID mappings) so that we can build the GraphShapeQuery here which requires
1238                    //  an actual `Graph`.
1239                    // So we just follow a deferred approach by just passing along the instructions
1240                    gsq_instructions.push(GraphShapeQueryInstruction::ExpectShapeEdge(
1241                        source.clone(),
1242                        target.clone(),
1243                        abstract_value.clone(),
1244                    ));
1245                }
1246                _ => {
1247                    bail!(OperationBuilderError::InvalidInQuery);
1248                }
1249            }
1250        }
1251
1252        Ok((
1253            did_query_finish,
1254            gsq_instructions,
1255            IntermediateQueryInstructions {
1256                true_branch: true_branch_instructions.unwrap_or_default(),
1257                false_branch: false_branch_instructions.unwrap_or_default(),
1258            },
1259        ))
1260    }
1261
1262    fn build_query_instruction(
1263        &mut self,
1264        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1265    ) -> Result<IntermediateQueryInstructions<S>, OperationBuilderError> {
1266        // we just consumed a StartQuery instruction.
1267        let mut true_branch_instructions = None;
1268        let mut false_branch_instructions = None;
1269        while let Some(instruction) = iter.peek() {
1270            match instruction {
1271                BuilderInstruction::EnterTrueBranch => {
1272                    iter.next();
1273                    if true_branch_instructions.is_some() {
1274                        bail!(OperationBuilderError::AlreadyVisitedBranch(true));
1275                    }
1276                    // we are entering a true branch, so we remove the false branch from the path
1277                    self.remove_until_branch(false);
1278                    self.path.push(IntermediateStatePath::EnterTrue);
1279                    true_branch_instructions = Some(self.build_many_instructions(iter)?);
1280                }
1281                BuilderInstruction::EnterFalseBranch => {
1282                    iter.next();
1283                    if false_branch_instructions.is_some() {
1284                        bail!(OperationBuilderError::AlreadyVisitedBranch(false));
1285                    }
1286                    // we are entering a false branch, so we remove the true branch from the path
1287                    self.remove_until_branch(true);
1288                    self.path.push(IntermediateStatePath::EnterFalse);
1289                    false_branch_instructions = Some(self.build_many_instructions(iter)?);
1290                }
1291                BuilderInstruction::EndQuery => {
1292                    iter.next();
1293                    // we are ending the query, so we remove the current query state from the path
1294                    self.remove_until_query_start();
1295                    self.path.push(IntermediateStatePath::SkipQuery);
1296                    break;
1297                }
1298                _ => {
1299                    bail!(OperationBuilderError::InvalidInQuery);
1300                }
1301            }
1302        }
1303        let true_branch = true_branch_instructions.unwrap_or_default();
1304        let false_branch = false_branch_instructions.unwrap_or_default();
1305        Ok(IntermediateQueryInstructions {
1306            true_branch,
1307            false_branch,
1308        })
1309    }
1310
1311    fn build_operation_parameter(
1312        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1313    ) -> Result<OperationParameter<S>, OperationBuilderError> {
1314        let mut builder = OperationParameterBuilder::new();
1315
1316        while let Some(instruction) = iter.peek() {
1317            match instruction {
1318                BuilderInstruction::ExpectParameterNode(marker, node_abstract) => {
1319                    iter.next();
1320                    builder
1321                        .expect_explicit_input_node(*marker, node_abstract.clone())
1322                        .change_context(OperationBuilderError::InvalidParameter)?;
1323                }
1324                BuilderInstruction::ExpectContextNode(marker, node_abstract) => {
1325                    iter.next();
1326                    builder
1327                        .expect_context_node(*marker, node_abstract.clone())
1328                        .change_context(OperationBuilderError::InvalidParameter)?;
1329                }
1330                BuilderInstruction::ExpectParameterEdge(
1331                    source_marker,
1332                    target_marker,
1333                    edge_abstract,
1334                ) => {
1335                    iter.next();
1336                    builder
1337                        .expect_edge(*source_marker, *target_marker, edge_abstract.clone())
1338                        .change_context(OperationBuilderError::InvalidParameter)?;
1339                }
1340                _ => {
1341                    break;
1342                }
1343            }
1344        }
1345
1346        builder
1347            .build()
1348            .change_context(OperationBuilderError::InvalidParameter)
1349    }
1350
1351    // TODO: also collect ReturnEdge
1352    fn collect_return_instructions(
1353        iter: &mut Peekable<Iter<BuilderInstruction<S>>>,
1354    ) -> Result<
1355        (
1356            HashMap<AbstractNodeId, (AbstractOutputNodeMarker, S::NodeAbstract)>,
1357            HashMap<(AbstractNodeId, AbstractNodeId), S::EdgeAbstract>,
1358        ),
1359        OperationBuilderError,
1360    > {
1361        let mut return_nodes = HashMap::new();
1362        let mut return_edges = HashMap::new();
1363        while let Some(instruction) = iter.peek() {
1364            match instruction {
1365                BuilderInstruction::ReturnNode(aid, output_marker, node) => {
1366                    iter.next();
1367                    if return_nodes.contains_key(aid) {
1368                        bail!(OperationBuilderError::AlreadySelectedReturnNode(
1369                            aid.clone(),
1370                        ));
1371                    }
1372                    return_nodes.insert(aid.clone(), (output_marker.clone(), node.clone()));
1373                }
1374                BuilderInstruction::ReturnEdge(source, target, edge) => {
1375                    iter.next();
1376                    if return_edges.contains_key(&(source.clone(), target.clone())) {
1377                        bail!(OperationBuilderError::AlreadySelectedReturnEdge(
1378                            source.clone(),
1379                            target.clone(),
1380                        ));
1381                    }
1382                    return_edges.insert((source.clone(), target.clone()), edge.clone());
1383                }
1384
1385                _ => break,
1386            }
1387        }
1388        Ok((return_nodes, return_edges))
1389    }
1390
1391    fn remove_until_branch(&mut self, branch: bool) {
1392        // need to check that a branch is actually there in the region until the last skip_query
1393        let branch_to_find = if branch {
1394            IntermediateStatePath::EnterTrue
1395        } else {
1396            IntermediateStatePath::EnterFalse
1397        };
1398
1399        let mut found = false;
1400        for last in self.path.iter().rev() {
1401            if last == &branch_to_find {
1402                // we found the branch, so we can stop
1403                found = true;
1404            }
1405            if matches!(last, IntermediateStatePath::StartQuery(..)) {
1406                // we reached the start of the query, so we cannot find the branch
1407                break;
1408            }
1409        }
1410        if !found {
1411            // we did not find the branch, so we cannot remove it
1412            return;
1413        }
1414
1415        while let Some(last) = self.path.pop() {
1416            if (branch && last == IntermediateStatePath::EnterTrue)
1417                || (!branch && last == IntermediateStatePath::EnterFalse)
1418            {
1419                break;
1420            }
1421        }
1422    }
1423
1424    fn remove_until_query_start(&mut self) {
1425        while let Some(last) = self.path.pop() {
1426            if matches!(last, IntermediateStatePath::StartQuery(..)) {
1427                break;
1428            }
1429        }
1430    }
1431}
1432
1433#[derive(Debug, Clone, PartialEq, Eq)]
1434enum IntermediateStatePath {
1435    // advance by one regular instruction. don't go in.
1436    Advance,
1437    EnterTrue,
1438    EnterFalse,
1439    // TODO: is this the same as Advance?
1440    SkipQuery,
1441    StartQuery(Option<String>), // the query name, if any
1442}
1443
1444// TODO: here make the intermediate state interpreter have points at which it knows the state
1445//  eg at every query branch point... hmm maybe it should be passed an argument of _where_ we want to know the state?
1446//  some path like entering the true/false branch, leaving a query...
1447
1448/*
1449What kind of information do we want to give the user when they ask for the current state of the operation?
1450
14511. Current abstract graph
1452 * Realistically, this should be formatted by ignoring NodeKeys and only showing AbstractNodeId
1453 ==> We need a NodeKey => AbstractNodeId mapping
14542. Available AbstractNodeIds and their abstract values
1455 * We can do this by mapping AbstractNodeId to NodeKey and then looking up the node in the graph.
1456 ==> We need an AbstractNodeId => NodeKey mapping
14573. Current query state
1458 * How should this be represented?
1459 * Some path? Can we "visualize" queries?
1460 * then we could have paths like: "GtZero on AID_1 true branch, ShapeQuery Y (Shape queries will be difficult to visualize)
1461   on AID_2 and AID_3 false branch, EqValues on AID_3 and AID_4 no branch yet"
1462
1463
1464How do we store intermediate representation?
1465To do this memory-efficiently, some incremental representation would be nice. Like "this instruction added this AID".
1466But, for time reasons, let's just store a copy of the entire state from above after each instruction.
1467*/
1468
1469#[derive(Clone, Debug, Eq, PartialEq)]
1470pub enum QueryPath {
1471    Query(String),
1472    TrueBranch,
1473    FalseBranch,
1474}
1475
1476#[derive(Clone, Debug, Eq, PartialEq)]
1477struct IntermediateStateAbstractOutputResult {
1478    new_aids: Vec<AbstractNodeId>,
1479    removed_aids: Vec<AbstractNodeId>,
1480}
1481
1482// TODO: Store more information like:
1483//  - Are we still building the parameter graph?
1484//  - If we are inside a query, which branches have we not entered yet?
1485//  - Are we making a shape/non-shape query?
1486
1487// TODO: should this be named "AbstractBuilderState"? since it's the state, which is abstract, which is used by the builder.
1488pub struct IntermediateState<S: Semantics> {
1489    pub graph: AbstractGraph<S>,
1490    pub node_keys_to_aid: BiMap<NodeKey, AbstractNodeId>,
1491    // TODO: Somehow remove AIDs from this set if they're completely overwritten by something non-shape-query.
1492    //  could be done by, whenever adding a new node, unconditionally removing the AID from this set as long as we're not in a shape query.
1493    //  since we have a different state at that point, it would get merged correctly (assuming we take the union).
1494    // *UPDATE*: these are currently being populated, but not used, since I realized nodes from shape queries are actually
1495    //  allowed to be returned. In the future these might be used again, e.g., when a shape query can read-only match an *already existing*
1496    //  outer node. In that case, the node would not be allowed to be returned, since it may exist in the caller.
1497    pub node_may_originate_from_shape_query: HashSet<AbstractNodeId>,
1498    pub edge_may_originate_from_shape_query: HashSet<(AbstractNodeId, AbstractNodeId)>,
1499
1500    /// The most generic abstract type that may be written to each node, if any.
1501    pub node_may_be_written_to: HashMap<AbstractNodeId, S::NodeAbstract>,
1502    /// The most generic abstract type that may be written to each edge, if any.
1503    pub edge_may_be_written_to: HashMap<(AbstractNodeId, AbstractNodeId), S::EdgeAbstract>,
1504
1505    // TODO: make query path
1506    // TODO: should probably remove query_path from the state struct, and add it to a final returned StateWithQueryPath struct?
1507    pub query_path: Vec<QueryPath>,
1508
1509    pub op_marker_counter: u64,
1510
1511    pub has_diverged: bool,
1512}
1513
1514// TODO: unfortunately, we cannot derive Clone, since it implies a `S: Clone` bound.
1515//  - in theory, we could add that bound, since a Semantics as a value does not really store much. So clone should be fine.
1516impl<S: Semantics> Clone for IntermediateState<S> {
1517    fn clone(&self) -> Self {
1518        IntermediateState {
1519            graph: self.graph.clone(),
1520            node_keys_to_aid: self.node_keys_to_aid.clone(),
1521            node_may_originate_from_shape_query: self.node_may_originate_from_shape_query.clone(),
1522            edge_may_originate_from_shape_query: self.edge_may_originate_from_shape_query.clone(),
1523            node_may_be_written_to: self.node_may_be_written_to.clone(),
1524            edge_may_be_written_to: self.edge_may_be_written_to.clone(),
1525            query_path: self.query_path.clone(),
1526            op_marker_counter: self.op_marker_counter,
1527            has_diverged: self.has_diverged,
1528        }
1529    }
1530}
1531
1532impl<S: Semantics> IntermediateState<S> {
1533    fn new() -> Self {
1534        IntermediateState {
1535            graph: AbstractGraph::<S>::new(),
1536            node_keys_to_aid: BiMap::new(),
1537            node_may_originate_from_shape_query: HashSet::new(),
1538            edge_may_originate_from_shape_query: HashSet::new(),
1539            node_may_be_written_to: HashMap::new(),
1540            edge_may_be_written_to: HashMap::new(),
1541            query_path: Vec::new(),
1542            op_marker_counter: 50000,
1543            has_diverged: false,
1544        }
1545    }
1546
1547    fn from_param(param: &OperationParameter<S>) -> Self {
1548        let initial_graph = param.parameter_graph.clone();
1549
1550        let mut initial_mapping = BiMap::new();
1551
1552        for (key, subst) in param.node_keys_to_subst.iter() {
1553            let aid = AbstractNodeId::ParameterMarker(*subst);
1554            initial_mapping.insert(*key, aid);
1555        }
1556
1557        IntermediateState {
1558            graph: initial_graph,
1559            node_keys_to_aid: initial_mapping,
1560            node_may_originate_from_shape_query: HashSet::new(),
1561            edge_may_originate_from_shape_query: HashSet::new(),
1562            node_may_be_written_to: HashMap::new(),
1563            edge_may_be_written_to: HashMap::new(),
1564            query_path: Vec::new(),
1565            op_marker_counter: 50000,
1566            has_diverged: false,
1567        }
1568    }
1569
1570    fn get_next_op_result_marker(&mut self) -> AbstractOperationResultMarker {
1571        let marker = AbstractOperationResultMarker::Implicit(self.op_marker_counter);
1572        self.op_marker_counter += 1;
1573        marker
1574    }
1575
1576    fn add_node(
1577        &mut self,
1578        aid: AbstractNodeId,
1579        node_abstract: S::NodeAbstract,
1580        from_shape_query: bool,
1581    ) {
1582        let node_key = self.graph.add_node(node_abstract);
1583        self.node_keys_to_aid.insert(node_key, aid);
1584        if from_shape_query {
1585            self.node_may_originate_from_shape_query.insert(aid);
1586        } else {
1587            // TODO: might be able to remove the AID from shape query.
1588        }
1589    }
1590
1591    fn add_edge(
1592        &mut self,
1593        source: AbstractNodeId,
1594        target: AbstractNodeId,
1595        edge_abstract: S::EdgeAbstract,
1596        from_shape_query: bool,
1597    ) -> Result<(), OperationBuilderError> {
1598        let source_key = self
1599            .node_keys_to_aid
1600            .get_right(&source)
1601            .ok_or(OperationBuilderError::NotFoundAid(source))?;
1602        let target_key = self
1603            .node_keys_to_aid
1604            .get_right(&target)
1605            .ok_or(OperationBuilderError::NotFoundAid(target))?;
1606
1607        self.graph.add_edge(*source_key, *target_key, edge_abstract);
1608
1609        if from_shape_query {
1610            self.edge_may_originate_from_shape_query
1611                .insert((source, target));
1612        } else {
1613            // TODO: might be able to remove the AID.
1614        }
1615        Ok(())
1616    }
1617
1618    fn set_node_av(
1619        &mut self,
1620        aid: AbstractNodeId,
1621        node_abstract: S::NodeAbstract,
1622    ) -> Result<(), OperationBuilderError> {
1623        let node_key = self
1624            .node_keys_to_aid
1625            .get_right(&aid)
1626            .ok_or(OperationBuilderError::NotFoundAid(aid))?;
1627        self.graph.set_node_attr(*node_key, node_abstract);
1628        Ok(())
1629    }
1630
1631    fn contains_aid(&self, aid: &AbstractNodeId) -> bool {
1632        self.node_keys_to_aid.contains_right(aid)
1633    }
1634
1635    fn contains_edge(&self, source: &AbstractNodeId, target: &AbstractNodeId) -> bool {
1636        let Some(source_key) = self.node_keys_to_aid.get_right(source) else {
1637            return false;
1638        };
1639        let Some(target_key) = self.node_keys_to_aid.get_right(target) else {
1640            return false;
1641        };
1642        self.graph
1643            .get_edge_attr((*source_key, *target_key))
1644            .is_some()
1645    }
1646
1647    pub fn node_av_of_aid(&self, aid: &AbstractNodeId) -> Option<&S::NodeAbstract> {
1648        let node_key = self.node_keys_to_aid.get_right(aid)?;
1649        self.graph.get_node_attr(*node_key)
1650    }
1651
1652    pub fn edge_av_of_aid(
1653        &self,
1654        source: &AbstractNodeId,
1655        target: &AbstractNodeId,
1656    ) -> Option<&S::EdgeAbstract> {
1657        let source_key = self.node_keys_to_aid.get_right(source)?;
1658        let target_key = self.node_keys_to_aid.get_right(target)?;
1659        self.graph.get_edge_attr((*source_key, *target_key))
1660    }
1661
1662    /// Modifies all mappings so that all mentions of `old_aid` are replaced with `new_aid`.
1663    fn rename_aid(
1664        &mut self,
1665        old_aid: AbstractNodeId,
1666        new_aid: AbstractNodeId,
1667    ) -> Result<(), OperationBuilderError> {
1668        // if we already have the new AID, return error
1669        if self.node_keys_to_aid.contains_right(&new_aid) {
1670            bail!(OperationBuilderError::AlreadyExistsAid(new_aid));
1671        }
1672        // Update the mappings
1673        if let Some(node_key) = self.node_keys_to_aid.remove_right(&old_aid) {
1674            self.node_keys_to_aid.insert(node_key, new_aid);
1675        } else {
1676            bail!(OperationBuilderError::NotFoundAid(old_aid));
1677        }
1678
1679        // Update the shape query sets
1680        if self.node_may_originate_from_shape_query.remove(&old_aid) {
1681            self.node_may_originate_from_shape_query.insert(new_aid);
1682        }
1683        // edges too
1684        self.edge_may_originate_from_shape_query = self
1685            .edge_may_originate_from_shape_query
1686            .iter()
1687            .map(|&(src, dst)| {
1688                let new_src = if src == old_aid { new_aid } else { src };
1689                let new_dst = if dst == old_aid { new_aid } else { dst };
1690                (new_src, new_dst)
1691            })
1692            .collect();
1693
1694        // Update writes
1695        if let Some(node_av) = self.node_may_be_written_to.remove(&old_aid) {
1696            self.node_may_be_written_to.insert(new_aid, node_av);
1697        }
1698        self.edge_may_be_written_to = self
1699            .edge_may_be_written_to
1700            .iter()
1701            .map(|(&(src, dst), edge_av)| {
1702                let new_src = if src == old_aid { new_aid } else { src };
1703                let new_dst = if dst == old_aid { new_aid } else { dst };
1704                ((new_src, new_dst), edge_av.clone())
1705            })
1706            .collect();
1707
1708        Ok(())
1709    }
1710
1711    fn diverge(&mut self) {
1712        // set our diverged flag
1713        if !self.has_diverged {
1714            self.has_diverged = true;
1715        }
1716    }
1717
1718    /// Returns the abstract changes from applying the op as well as the new AIDs
1719    fn interpret_op(
1720        &mut self,
1721        op_ctx: &OperationContext<S>,
1722        marker: Option<AbstractOperationResultMarker>,
1723        op: AbstractOperation<S>,
1724        args: Vec<AbstractNodeId>,
1725    ) -> Result<
1726        (
1727            AbstractOperationArgument,
1728            IntermediateStateAbstractOutputResult,
1729        ),
1730        OperationBuilderError,
1731    > {
1732        // if we've diverged, issue a warning
1733        if self.has_diverged {
1734            log::warn!(
1735                "Trying to issue new instruction with name {marker:?} after path has diverged. This may lead to unexpected results regarding available node names."
1736            );
1737        }
1738        let param = op.parameter();
1739        let (subst, abstract_arg) = self.get_substitution(&param, args)?;
1740
1741        // now apply op and store result
1742        let operation_output = {
1743            let mut gws = GraphWithSubstitution::new(&mut self.graph, &subst);
1744            op.apply_abstract(op_ctx, &mut gws)
1745                .change_context(OperationBuilderError::AbstractApplyOperationError2)?
1746        };
1747        let output = self.handle_abstract_output_changes(marker, operation_output)?;
1748
1749        Ok((abstract_arg, output))
1750    }
1751
1752    fn interpret_builtin_query(
1753        &mut self,
1754        query: &S::BuiltinQuery,
1755        args: Vec<AbstractNodeId>,
1756    ) -> Result<AbstractOperationArgument, OperationBuilderError> {
1757        let param = query.parameter();
1758        let (subst, abstract_arg) = self.get_substitution(&param, args)?;
1759        // now apply the query and store result
1760        let mut gws = GraphWithSubstitution::new(&mut self.graph, &subst);
1761        query.apply_abstract(&mut gws);
1762        Ok(abstract_arg)
1763    }
1764
1765    /// Returns the newly added AIDs
1766    fn handle_abstract_output_changes(
1767        &mut self,
1768        marker: Option<AbstractOperationResultMarker>,
1769        operation_output: AbstractOperationOutput<S>,
1770    ) -> Result<IntermediateStateAbstractOutputResult, OperationBuilderError> {
1771        // go over new nodes
1772        let mut new_aids = Vec::new();
1773        for (node_marker, node_key) in operation_output.new_nodes {
1774            if let Some(op_marker) = marker {
1775                let aid = AbstractNodeId::DynamicOutputMarker(op_marker, node_marker);
1776                // TODO: override the may_come_from_shape_query set here! remove the node - it's a non-shape-query node.
1777                self.node_keys_to_aid.insert(node_key, aid);
1778                new_aids.push(aid);
1779            } else {
1780                // we don't keep track of it, so better remove it from the graph
1781                self.graph.remove_node(node_key);
1782            }
1783        }
1784        let mut removed_aids = Vec::new();
1785        for node_key in &operation_output.removed_nodes {
1786            // remove the node from the mapping
1787            if let Some(removed_aid) = self.node_keys_to_aid.remove_left(&node_key) {
1788                removed_aids.push(removed_aid);
1789            }
1790        }
1791
1792        // collect changes
1793        // TODO: What is a good idea regarding changes abstract values?
1794        //  I think it's a good idea to just propagate what we know for a fact _could_ be written (but in its most precise form).
1795        //  If instead we said "merge it with the current value", then we make it potentially join with the parameter.
1796        for (key, node_abstract) in operation_output.changed_abstract_values_nodes {
1797            let aid = self
1798                .get_aid_from_key(&key)
1799                .expect("internal error: changed node not found in mapping");
1800            self.node_may_be_written_to.insert(aid, node_abstract);
1801        }
1802        for ((source, target), edge_abstract) in operation_output.changed_abstract_values_edges {
1803            let source_aid = self
1804                .get_aid_from_key(&source)
1805                .expect("internal error: changed edge source not found in mapping");
1806            let target_aid = self
1807                .get_aid_from_key(&target)
1808                .expect("internal error: changed edge target not found in mapping");
1809            self.edge_may_be_written_to
1810                .insert((source_aid, target_aid), edge_abstract);
1811        }
1812
1813        Ok(IntermediateStateAbstractOutputResult {
1814            new_aids,
1815            removed_aids,
1816        })
1817    }
1818
1819    fn get_substitution(
1820        &self,
1821        param: &OperationParameter<S>,
1822        args: Vec<AbstractNodeId>,
1823    ) -> Result<(ParameterSubstitution, AbstractOperationArgument), OperationBuilderError> {
1824        let selected_inputs = args
1825            .iter()
1826            .map(|aid| self.get_key_from_aid(aid))
1827            .collect::<Result<Vec<_>, _>>()
1828            .change_context(OperationBuilderError::SelectedInputsNotFoundAid)?;
1829        let subst = get_substitution(&self.graph, &param, &selected_inputs)
1830            .change_context(OperationBuilderError::SubstitutionErrorNew)?;
1831        let subst_to_aid = subst.mapping.iter().map(|(subst, key)| {
1832            let aid = self.get_aid_from_key(key)
1833                .change_context(OperationBuilderError::InternalError("node key should be in mapping, because all node keys from the abstract graph should be in the mapping"))
1834                .unwrap();
1835            (subst.clone(), aid)
1836        }).collect();
1837
1838        let abstract_arg = AbstractOperationArgument {
1839            selected_input_nodes: args,
1840            subst_to_aid,
1841        };
1842
1843        Ok((subst, abstract_arg))
1844    }
1845
1846    fn get_key_from_aid(&self, aid: &AbstractNodeId) -> Result<NodeKey, OperationBuilderError> {
1847        self.node_keys_to_aid
1848            .get_right(aid)
1849            .cloned()
1850            .ok_or(report!(OperationBuilderError::NotFoundAid(*aid)))
1851    }
1852
1853    fn get_aid_from_key(&self, key: &NodeKey) -> Result<AbstractNodeId, OperationBuilderError> {
1854        self.node_keys_to_aid.get_left(key).cloned().ok_or(report!(
1855            OperationBuilderError::InternalError("could not find node key")
1856        ))
1857    }
1858
1859    fn as_param_for_shape_query(&self) -> (OperationParameter<S>, AbstractOperationArgument) {
1860        let param_graph = self.graph.clone();
1861
1862        let mut all_node_keys = param_graph
1863            .node_attr_map
1864            .keys()
1865            .cloned()
1866            .collect::<Vec<_>>();
1867        all_node_keys.sort_unstable(); // sort to ensure deterministic order
1868
1869        let mut node_keys_to_subst: BiMap<NodeKey, SubstMarker> = BiMap::new();
1870        let mut explicit_input_nodes = Vec::new();
1871        let mut aid_args = Vec::new();
1872        let mut subst_to_aid = HashMap::new();
1873        for key in all_node_keys {
1874            let subst = SubstMarker::from(format!("{:?}", key));
1875            node_keys_to_subst.insert(key, subst);
1876            explicit_input_nodes.push(subst);
1877            // collect the AID for this key
1878            let aid = self.get_aid_from_key(&key).unwrap();
1879            aid_args.push(aid);
1880            subst_to_aid.insert(subst, aid);
1881        }
1882
1883        let abstract_args = AbstractOperationArgument {
1884            selected_input_nodes: aid_args,
1885            subst_to_aid,
1886        };
1887
1888        (
1889            OperationParameter {
1890                explicit_input_nodes,
1891                parameter_graph: param_graph,
1892                node_keys_to_subst,
1893            },
1894            abstract_args,
1895        )
1896    }
1897}
1898
1899impl<S: Semantics<NodeAbstract: Debug, EdgeAbstract: Debug>> IntermediateState<S> {
1900    pub fn dot_with_aid(&self) -> String {
1901        struct PrettyAid<'a>(&'a AbstractNodeId);
1902
1903        impl Debug for PrettyAid<'_> {
1904            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1905                match self.0 {
1906                    AbstractNodeId::ParameterMarker(subst) => write!(f, "P({})", subst.0),
1907                    AbstractNodeId::DynamicOutputMarker(marker, node_marker) => {
1908                        let op_marker = match marker {
1909                            AbstractOperationResultMarker::Custom(c) => c,
1910                            AbstractOperationResultMarker::Implicit(num) => "<unnamed>",
1911                        };
1912                        write!(f, "O({}, {})", op_marker, node_marker.0)
1913                    }
1914                    AbstractNodeId::Named(name) => {
1915                        write!(f, "{:?}", name)
1916                    }
1917                }
1918            }
1919        }
1920
1921        // TODO: handle edge order...
1922
1923        format!(
1924            "{:?}",
1925            Dot::with_attr_getters(
1926                &self.graph.graph,
1927                &[dot::Config::EdgeNoLabel, dot::Config::NodeNoLabel],
1928                &|g, (src, target, attr)| {
1929                    let dbg_attr_format = format!("{:?}", attr.edge_attr);
1930                    let dbg_attr_replaced = dbg_attr_format.escape_debug();
1931                    format!("label = \"{dbg_attr_replaced}\"")
1932                },
1933                &|g, (node, _)| {
1934                    let aid = self
1935                        .node_keys_to_aid
1936                        .get_left(&node)
1937                        .expect("NodeKey not found in node_keys_to_aid");
1938                    let aid = PrettyAid(aid);
1939                    let aid = format!("{aid:?}");
1940                    let aid_replaced = aid.escape_debug();
1941                    let av = self
1942                        .graph
1943                        .get_node_attr(node)
1944                        .expect("NodeKey not found in graph");
1945                    let dbg_attr_format = format!("{:?}", av);
1946                    let dbg_attr_replaced = dbg_attr_format.escape_debug();
1947
1948                    // format!("label = \"{aid_replaced}|{dbg_attr_replaced}\"")
1949                    // format!("label = \"{dbg_attr_replaced}\", xlabel = \"{aid_replaced}\"")
1950                    format!("shape=Mrecord, label = \"{aid_replaced}|{dbg_attr_replaced}\"")
1951                }
1952            )
1953        )
1954    }
1955}
1956
1957enum InterpretedInstruction<S: Semantics> {
1958    OpLike,
1959    Query(InterpretedQueryInstructions<S>),
1960}
1961
1962struct InterpretedQueryInstructions<S: Semantics> {
1963    initial_state_true_branch: IntermediateState<S>,
1964    initial_state_false_branch: IntermediateState<S>,
1965    true_branch: InterpretedInstructions<S>,
1966    false_branch: InterpretedInstructions<S>,
1967}
1968
1969struct InterpretedInstructionWithState<S: Semantics> {
1970    instruction: InterpretedInstruction<S>,
1971    state_after: IntermediateState<S>,
1972}
1973
1974struct IntermediateInterpreter<'a, S: Semantics> {
1975    self_op_id: OperationId,
1976    op_ctx: &'a OperationContext<S>,
1977    op_param: OperationParameter<S>,
1978    initial_state: IntermediateState<S>,
1979    current_state: IntermediateState<S>,
1980    /// Primarily used for hacking abstract changes due to recursive calls.
1981    partial_self_user_defined_op: &'a UserDefinedOperation<S>,
1982    /// A counter to generate unique operation result markers.
1983    counter: u64,
1984}
1985
1986type UDInstructionsWithMarker<S> = Vec<(Option<AbstractOperationResultMarker>, UDInstruction<S>)>;
1987
1988type InterpretedInstructions<S> = Vec<(
1989    Option<AbstractOperationResultMarker>,
1990    InterpretedInstructionWithState<S>,
1991)>;
1992
1993impl<'a, S: Semantics> IntermediateInterpreter<'a, S> {
1994    fn new_for_self_op_id(
1995        self_op_id: OperationId,
1996        op_param: OperationParameter<S>,
1997        op_ctx: &'a OperationContext<S>,
1998        partial_self_user_defined_op: &'a UserDefinedOperation<S>,
1999    ) -> Self {
2000        let initial_state = IntermediateState::from_param(&op_param);
2001
2002        let current_state = initial_state.clone();
2003
2004        let interpreter = IntermediateInterpreter {
2005            self_op_id,
2006            op_ctx,
2007            op_param,
2008            initial_state,
2009            current_state,
2010            partial_self_user_defined_op,
2011            counter: 0,
2012        };
2013
2014        interpreter
2015    }
2016
2017    fn create_user_defined_operation(
2018        &mut self,
2019        intermediate_instructions: Vec<(
2020            Option<AbstractOperationResultMarker>,
2021            IntermediateInstruction<S>,
2022        )>,
2023        return_nodes: HashMap<AbstractNodeId, (AbstractOutputNodeMarker, S::NodeAbstract)>,
2024        return_edges: HashMap<(AbstractNodeId, AbstractNodeId), S::EdgeAbstract>,
2025    ) -> Result<UserDefinedOperation<S>, OperationBuilderError> {
2026        let (ud_instructions, _interp_instructions) =
2027            self.interpret_instructions(intermediate_instructions)?;
2028
2029        // self.current_state is now the final inferred state.
2030
2031        let (ud_output, signature) = self.determine_signature(return_nodes, return_edges)?;
2032
2033        Ok(UserDefinedOperation {
2034            // parameter: self.op_param.clone(),
2035            instructions: ud_instructions,
2036            output_changes: ud_output,
2037            signature,
2038        })
2039    }
2040
2041    // Note: must be called after interpreting all instructions.
2042    fn determine_signature(
2043        &self,
2044        return_nodes: HashMap<AbstractNodeId, (AbstractOutputNodeMarker, S::NodeAbstract)>,
2045        return_edges: HashMap<(AbstractNodeId, AbstractNodeId), S::EdgeAbstract>,
2046    ) -> Result<(AbstractUserDefinedOperationOutput, OperationSignature<S>), OperationBuilderError>
2047    {
2048        // this struct stores an instruction for user defined operations on *how* to return nodes.
2049        let mut ud_output = AbstractUserDefinedOperationOutput::new();
2050        // this stores in general *what* the operation is doing.
2051        let mut signature = OperationSignature::empty_new("name", self.op_param.clone());
2052
2053        // need to determine validity of return_nodes
2054        for (aid, (output_marker, node_abstract)) in return_nodes {
2055            // make sure type we're deciding to return is a valid supertype
2056            let inferred_av = self
2057                .current_state
2058                .node_av_of_aid(&aid)
2059                .ok_or(OperationBuilderError::NotFoundReturnNode(aid.clone()))?;
2060            if !S::NodeMatcher::matches(inferred_av, &node_abstract) {
2061                bail!(OperationBuilderError::InvalidReturnNodeType(aid));
2062            }
2063            if self
2064                .current_state
2065                .node_may_originate_from_shape_query
2066                .contains(&aid)
2067            {
2068                bail!(OperationBuilderError::ReturnNodeMayOriginateFromShapeQuery(
2069                    aid,
2070                ));
2071            }
2072            ud_output.new_nodes.insert(aid, output_marker);
2073            // Add to signature
2074            signature
2075                .output
2076                .new_nodes
2077                .insert(output_marker, node_abstract);
2078        }
2079
2080        let get_param_or_output_sig_id = |aid: &AbstractNodeId| {
2081            match *aid {
2082                AbstractNodeId::ParameterMarker(s) => Ok(AbstractSignatureNodeId::ExistingNode(s)),
2083                AbstractNodeId::DynamicOutputMarker(_, _) | AbstractNodeId::Named(..) => {
2084                    // we must be returning this node if we want to return an incident edge.
2085                    let Some(output_marker) = ud_output.new_nodes.get(aid) else {
2086                        bail!(OperationBuilderError::NotFoundReturnNode(aid.clone()));
2087                    };
2088                    Ok(AbstractSignatureNodeId::NewNode(output_marker.clone()))
2089                }
2090            }
2091        };
2092
2093        // need to determine validity of return_edges
2094        for ((source_aid, target_aid), edge_abstract) in return_edges {
2095            let Some(source_key) = self.current_state.node_keys_to_aid.get_right(&source_aid)
2096            else {
2097                bail!(OperationBuilderError::NotFoundReturnEdgeSource(source_aid));
2098            };
2099            let Some(target_key) = self.current_state.node_keys_to_aid.get_right(&target_aid)
2100            else {
2101                bail!(OperationBuilderError::NotFoundReturnEdgeTarget(target_aid));
2102            };
2103            let inferred_edge_av = self
2104                .current_state
2105                .edge_av_of_aid(&source_aid, &target_aid)
2106                .ok_or(OperationBuilderError::NotFoundReturnEdge(
2107                    source_aid.clone(),
2108                    target_aid.clone(),
2109                ))?;
2110            if !S::EdgeMatcher::matches(inferred_edge_av, &edge_abstract) {
2111                bail!(OperationBuilderError::InvalidReturnEdgeType(
2112                    source_aid, target_aid,
2113                ));
2114            }
2115            if self
2116                .current_state
2117                .edge_may_originate_from_shape_query
2118                .contains(&(source_aid, target_aid))
2119            {
2120                bail!(OperationBuilderError::ReturnEdgeMayOriginateFromShapeQuery(
2121                    source_aid, target_aid,
2122                ));
2123            }
2124
2125            // Add to signature
2126            let source_sig_id = get_param_or_output_sig_id(&source_aid)?;
2127            let target_sig_id = get_param_or_output_sig_id(&target_aid)?;
2128            signature
2129                .output
2130                .new_edges
2131                .insert((source_sig_id, target_sig_id), edge_abstract.clone());
2132        }
2133
2134        // deleted nodes and edges can be inferred from what's missing from the current state vs. op_param.
2135
2136        let initial_subst_nodes = self
2137            .op_param
2138            .node_keys_to_subst
2139            .right_values()
2140            .cloned()
2141            .collect::<HashSet<_>>();
2142        let current_subst_nodes = self
2143            .current_state
2144            .node_keys_to_aid
2145            .right_values()
2146            .filter_map(|aid| {
2147                if let AbstractNodeId::ParameterMarker(subst) = aid {
2148                    Some(subst.clone())
2149                } else {
2150                    None
2151                }
2152            })
2153            .collect::<HashSet<_>>();
2154
2155        // deleted nodes are those that were in the initial substitution but not in the current state
2156        let deleted_nodes: HashSet<_> = initial_subst_nodes
2157            .difference(&current_subst_nodes)
2158            .cloned()
2159            .collect();
2160        signature.output.maybe_deleted_nodes = deleted_nodes;
2161
2162        let mut initial_edges = HashSet::new();
2163        for (source, target, _) in self.op_param.parameter_graph.graph.all_edges() {
2164            let Some(source_subst) = self.op_param.node_keys_to_subst.get_left(&source) else {
2165                continue; // should not happen, but just in case
2166            };
2167            let Some(target_subst) = self.op_param.node_keys_to_subst.get_left(&target) else {
2168                continue; // should not happen, but just in case
2169            };
2170            initial_edges.insert((*source_subst, *target_subst));
2171        }
2172
2173        let mut current_edges = HashSet::new();
2174        for (source, target, _) in self.current_state.graph.graph.all_edges() {
2175            let Some(source_aid) = self.current_state.node_keys_to_aid.get_left(&source) else {
2176                continue; // should not happen, but just in case
2177            };
2178            let Some(target_aid) = self.current_state.node_keys_to_aid.get_left(&target) else {
2179                continue; // should not happen, but just in case
2180            };
2181            if let (
2182                AbstractNodeId::ParameterMarker(source_subst),
2183                AbstractNodeId::ParameterMarker(target_subst),
2184            ) = (source_aid, target_aid)
2185            {
2186                current_edges.insert((source_subst.clone(), target_subst.clone()));
2187            }
2188        }
2189
2190        // deleted edges are those that were in the initial substitution but not in the current state
2191        let deleted_edges: HashSet<_> = initial_edges.difference(&current_edges).cloned().collect();
2192        signature.output.maybe_deleted_edges = deleted_edges;
2193
2194        // changed nodes and edges must be kept track of during the interpretation, including calls to child operations.
2195
2196        for (aid, node_abstract) in &self.current_state.node_may_be_written_to {
2197            // we care about reporting only subst markers
2198            let AbstractNodeId::ParameterMarker(subst) = aid else {
2199                continue;
2200            };
2201            signature
2202                .output
2203                .maybe_changed_nodes
2204                .insert(*subst, node_abstract.clone());
2205        }
2206
2207        for ((source_aid, target_aid), edge_abstract) in &self.current_state.edge_may_be_written_to
2208        {
2209            // we care about reporting only subst markers
2210            let AbstractNodeId::ParameterMarker(source_subst) = source_aid else {
2211                continue;
2212            };
2213            let AbstractNodeId::ParameterMarker(target_subst) = target_aid else {
2214                continue;
2215            };
2216            signature
2217                .output
2218                .maybe_changed_edges
2219                .insert((*source_subst, *target_subst), edge_abstract.clone());
2220        }
2221
2222        Ok((ud_output, signature))
2223    }
2224
2225    fn interpret_instructions(
2226        &mut self,
2227        intermediate_instructions: Vec<(
2228            Option<AbstractOperationResultMarker>,
2229            IntermediateInstruction<S>,
2230        )>,
2231    ) -> Result<(UDInstructionsWithMarker<S>, InterpretedInstructions<S>), OperationBuilderError>
2232    {
2233        let mut ud_instructions = Vec::new();
2234        let mut interpreted_instructions = Vec::new();
2235        for (marker, instruction) in intermediate_instructions {
2236            let (ud_instruction, interpreted_instruction) =
2237                self.interpret_single_instruction(marker.clone(), instruction)?;
2238            ud_instructions.push((marker.clone(), ud_instruction));
2239            interpreted_instructions.push((
2240                marker,
2241                InterpretedInstructionWithState {
2242                    instruction: interpreted_instruction,
2243                    state_after: self.current_state.clone(),
2244                },
2245            ));
2246        }
2247        Ok((ud_instructions, interpreted_instructions))
2248    }
2249
2250    fn interpret_single_instruction(
2251        &mut self,
2252        marker: Option<AbstractOperationResultMarker>,
2253        instruction: IntermediateInstruction<S>,
2254    ) -> Result<(UDInstruction<S>, InterpretedInstruction<S>), OperationBuilderError> {
2255        match instruction {
2256            IntermediateInstruction::OpLike(oplike) => Ok((
2257                self.interpret_op_like(marker, oplike)
2258                    .attach_printable_lazy(|| format!("Failed OpLike"))?,
2259                InterpretedInstruction::OpLike,
2260            )),
2261            IntermediateInstruction::BuiltinQuery(query, args, query_instructions) => {
2262                self.interpret_builtin_query(query, args, query_instructions)
2263            }
2264            IntermediateInstruction::GraphShapeQuery {
2265                is_finished,
2266                marker,
2267                graph_instructions,
2268                query_instructions,
2269            } => self.interpret_graph_shape_query(
2270                is_finished,
2271                marker,
2272                graph_instructions,
2273                query_instructions,
2274            ),
2275            IntermediateInstruction::RenameNode { aid, new_name } => {
2276                let new_aid = AbstractNodeId::named(new_name);
2277
2278                Ok((
2279                    self.rename_node(aid, new_aid)?,
2280                    InterpretedInstruction::OpLike,
2281                ))
2282            }
2283        }
2284    }
2285
2286    fn rename_node(
2287        &mut self,
2288        old_aid: AbstractNodeId,
2289        new_aid: AbstractNodeId,
2290    ) -> Result<UDInstruction<S>, OperationBuilderError> {
2291        // don't allow renaming ParameterMarker nodes
2292        if let AbstractNodeId::ParameterMarker(_) = old_aid {
2293            bail!(OperationBuilderError::CannotRenameParameterNode(old_aid));
2294        }
2295        self.current_state.rename_aid(old_aid, new_aid)?;
2296        Ok(UDInstruction::RenameNode {
2297            old: old_aid,
2298            new: new_aid,
2299        })
2300    }
2301
2302    fn interpret_op_like(
2303        &mut self,
2304        marker: Option<AbstractOperationResultMarker>,
2305        oplike: IntermediateOpLike<S>,
2306    ) -> Result<UDInstruction<S>, OperationBuilderError> {
2307        match oplike {
2308            IntermediateOpLike::Builtin(builtin_op, args) => {
2309                let op = Operation::Builtin(&builtin_op);
2310                let abstract_arg =
2311                    self.interpret_op(marker, op, args)
2312                        .attach_printable_lazy(|| {
2313                            format!("Failed to interpret builtin operation: {builtin_op:?}")
2314                        })?;
2315
2316                Ok(UDInstruction::OpLike(
2317                    OpLikeInstruction::Builtin(builtin_op),
2318                    abstract_arg,
2319                ))
2320            }
2321            IntermediateOpLike::LibBuiltin(lib_builtin_op, args) => {
2322                let op = Operation::LibBuiltin(&lib_builtin_op);
2323                let abstract_arg =
2324                    self.interpret_op(marker, op, args)
2325                        .attach_printable_lazy(|| {
2326                            format!("Failed to interpret lib builtin operation: {lib_builtin_op:?}")
2327                        })?;
2328
2329                Ok(UDInstruction::OpLike(
2330                    OpLikeInstruction::LibBuiltin(lib_builtin_op),
2331                    abstract_arg,
2332                ))
2333            }
2334            IntermediateOpLike::Operation(id, args) => {
2335                let op = self
2336                    .op_ctx
2337                    .get(id)
2338                    .ok_or(OperationBuilderError::NotFoundOperationId(id))?;
2339                let abstract_arg = self
2340                    .interpret_op(marker, op, args)
2341                    .attach_printable_lazy(|| format!("Failed to interpret operation: {id:?}"))?;
2342
2343                Ok(UDInstruction::OpLike(
2344                    OpLikeInstruction::Operation(id),
2345                    abstract_arg,
2346                ))
2347            }
2348            IntermediateOpLike::Recurse(args) => {
2349                // TODO: recursion is actually tricky. because at this point we have not finished interpreting the current operation yet.
2350                //  So how are we supposed to know the abstract changes?
2351
2352                // TODO: use approach from `problems-testcases.md`
2353
2354                // this should probably use some pre-defined (at the beginning) abstract changes to the graph.
2355
2356                // Hack: pretend the partial user defined operation is the full operation.
2357                // TODO: remove this hack. Make it so only explicit changes via ExpectRecursionChange... instructions are allowed.
2358                //  (note: it's also unsound for now, since changes _after_ the recursion call are ignored. -- actually, not quite. see tests)
2359                let op = Operation::Custom(&self.partial_self_user_defined_op);
2360
2361                let abstract_arg = self
2362                    .interpret_op(marker, op, args)
2363                    .attach_printable_lazy(|| "Failed to interpret recursive call")?;
2364
2365                Ok(UDInstruction::OpLike(
2366                    OpLikeInstruction::Operation(self.self_op_id),
2367                    abstract_arg,
2368                ))
2369            }
2370        }
2371    }
2372
2373    fn interpret_op(
2374        &mut self,
2375        marker: Option<AbstractOperationResultMarker>,
2376        op: Operation<S>,
2377        args: Vec<AbstractNodeId>,
2378    ) -> Result<AbstractOperationArgument, OperationBuilderError> {
2379        self.current_state
2380            .interpret_op(
2381                &self.op_ctx,
2382                marker,
2383                AbstractOperation::from_operation(op),
2384                args,
2385            )
2386            .map(|x| x.0)
2387    }
2388
2389    fn interpret_builtin_query(
2390        &mut self,
2391        query: S::BuiltinQuery,
2392        args: Vec<AbstractNodeId>,
2393        query_instructions: IntermediateQueryInstructions<S>,
2394    ) -> Result<(UDInstruction<S>, InterpretedInstruction<S>), OperationBuilderError> {
2395        let param = query.parameter();
2396        let (subst, arg) = self.get_current_substitution(&param, args)?;
2397
2398        // apply the query to the current graph
2399        query.apply_abstract(&mut GraphWithSubstitution::new(
2400            &mut self.current_state.graph,
2401            &subst,
2402        ));
2403
2404        // TODO: is this right? do we want to snapshot the state _after_ the query?
2405        //  I think so, because right now (weirdly enough) the query can modify. and the modifications
2406        //  are applied to both branches and what comes after.
2407
2408        let state_before = self.current_state.clone();
2409        let false_branch_state = self.current_state.clone();
2410
2411        let initial_true_branch_state = self.current_state.clone();
2412        let initial_false_branch_state = self.current_state.clone();
2413
2414        // interpret the instructions in the true and false branches
2415        let (ud_true_branch, interp_true_branch) =
2416            self.interpret_instructions(query_instructions.true_branch)?;
2417        let after_true_branch_state = mem::replace(&mut self.current_state, false_branch_state);
2418        let (ud_false_branch, interp_false_branch) =
2419            self.interpret_instructions(query_instructions.false_branch)?;
2420        let after_false_branch_state = mem::replace(&mut self.current_state, state_before);
2421
2422        // TODO: update current state etc...
2423
2424        // TODO: reconcile states of both true and false branch!
2425        // TODO: reconciliation should probably be done via having the same AID for the same node in both branches.
2426        //  all other ones will be ignored.
2427        //  ==> we must manually change the abstract graph ourselves here!
2428        //  ==> we must reconcile into self.current_state
2429
2430        let merged_state = merge_states(false, &after_true_branch_state, &after_false_branch_state);
2431        self.current_state = merged_state;
2432
2433        let ud_instr = UDInstruction::BuiltinQuery(
2434            query,
2435            arg,
2436            QueryInstructions {
2437                taken: ud_true_branch,
2438                not_taken: ud_false_branch,
2439            },
2440        );
2441
2442        let interp_instruction = InterpretedInstruction::Query(InterpretedQueryInstructions {
2443            initial_state_true_branch: initial_true_branch_state,
2444            initial_state_false_branch: initial_false_branch_state,
2445            true_branch: interp_true_branch,
2446            false_branch: interp_false_branch,
2447        });
2448
2449        Ok((ud_instr, interp_instruction))
2450    }
2451
2452    // TESTING
2453    fn collect_self_state_to_parameter(
2454        &self,
2455    ) -> (OperationParameter<S>, AbstractOperationArgument) {
2456        self.current_state.as_param_for_shape_query()
2457    }
2458
2459    // see _old for a version that only collects the bare minimum for the parameter.
2460    // this version pretends the current abstract state is the parameter.
2461    // TODO: clean this function up with the above assumptions. right now it's just bare-minimum working.
2462    fn interpret_graph_shape_query(
2463        &mut self,
2464        is_finished: bool,
2465        gsq_op_marker: AbstractOperationResultMarker,
2466        gsq_instructions: Vec<GraphShapeQueryInstruction<S>>,
2467        query_instructions: IntermediateQueryInstructions<S>,
2468    ) -> Result<(UDInstruction<S>, InterpretedInstruction<S>), OperationBuilderError> {
2469        let state_before = self.current_state.clone();
2470
2471        // preparation for false branch
2472        let false_branch_state = self.current_state.clone();
2473        let initial_false_branch_state = false_branch_state.clone();
2474
2475        // first pass: collect the initial graph (the parameter)
2476        let (param, abstract_args) = self.collect_self_state_to_parameter();
2477
2478        // second pass:
2479        // modify to have the expected graph as well as shape ident mappings.
2480        // simultaneously, also modify the *current state graph* to prepare it for the true branch.
2481        // make a copy before that though, for the false branch.
2482
2483        let mut expected_graph = param.parameter_graph.clone();
2484        let mut node_keys_to_shape_idents: BiMap<NodeKey, ShapeNodeIdentifier> = BiMap::new();
2485
2486        // let aid_to_node_key = |aid| -> Result<NodeKey, OperationBuilderError> {
2487        //     arg_aid_to_node_keys.get_left(&aid)
2488        //         .cloned()
2489        //         .or_else(|| {
2490        //             if let AbstractNodeId::DynamicOutputMarker(orm, node_marker) = aid {
2491        //                 if orm == gsq_op_marker {
2492        //                     // this is a new node from the graph shape query.
2493        //                     let sni: ShapeNodeIdentifier = node_marker.0.into();
2494        //                     node_keys_to_shape_idents.get_right(&sni).copied()
2495        //                 } else {
2496        //                     None
2497        //                 }
2498        //             } else {
2499        //                 None
2500        //             }
2501        //         })
2502        //         .ok_or(OperationBuilderError::NotFoundAid(aid))
2503        // };
2504
2505        // TODO: ugly. fix. needed because the above closure approach does not work due to borrowing issues.
2506        macro_rules! aid_to_node_key_hack {
2507            ($aid:expr) => {
2508                arg_aid_to_node_keys
2509                    .get_left(&$aid)
2510                    .cloned()
2511                    .or_else(|| {
2512                        if let AbstractNodeId::DynamicOutputMarker(orm, node_marker) = $aid {
2513                            if orm == gsq_op_marker {
2514                                // this is a new node from the graph shape query.
2515                                let sni: ShapeNodeIdentifier = node_marker.0.into();
2516                                node_keys_to_shape_idents.get_right(&sni).copied()
2517                            } else {
2518                                None
2519                            }
2520                        } else {
2521                            None
2522                        }
2523                    })
2524                    .ok_or(OperationBuilderError::NotFoundAid($aid))
2525            };
2526        }
2527
2528        for instruction in gsq_instructions {
2529            match instruction {
2530                GraphShapeQueryInstruction::ExpectShapeNode(marker, av) => {
2531                    let key = expected_graph.add_node(av.clone());
2532                    let shape_node_ident = marker.0.clone().into();
2533                    // TODO: insert is panicking and therefore we should return an error instead here.
2534                    // TODO: make bimap::insert fallible? return a must_use Option<()>?
2535                    node_keys_to_shape_idents.insert(key, shape_node_ident);
2536
2537                    // now update the state for the true branch.
2538                    let state_key = self.current_state.graph.add_node(av);
2539                    let aid =
2540                        AbstractNodeId::DynamicOutputMarker(gsq_op_marker.clone(), marker.clone());
2541                    self.current_state
2542                        .node_keys_to_aid
2543                        .insert(state_key, aid.clone());
2544                    self.current_state
2545                        .node_may_originate_from_shape_query
2546                        .insert(aid.clone());
2547                }
2548                GraphShapeQueryInstruction::ExpectShapeNodeChange(aid, av) => {
2549                    // set the expected av
2550                    let key = self.get_current_key_from_aid(aid.clone())?;
2551                    expected_graph.set_node_attr(key, av.clone());
2552
2553                    // now update the state for the true branch.
2554                    let state_key = self
2555                        .get_current_key_from_aid(aid)
2556                        .change_context(OperationBuilderError::NotFoundAid(aid))?;
2557                    self.current_state.graph.set_node_attr(state_key, av);
2558                }
2559                GraphShapeQueryInstruction::ExpectShapeEdge(src, target, av) => {
2560                    let src_key = self.get_current_key_from_aid(src.clone())?;
2561                    let target_key = self.get_current_key_from_aid(target.clone())?;
2562                    expected_graph.add_edge(src_key, target_key, av.clone());
2563
2564                    // now update the state for the true branch.
2565                    let state_src_key = self
2566                        .get_current_key_from_aid(src)
2567                        .change_context(OperationBuilderError::ShapeEdgeSourceNotFound)?;
2568                    let state_target_key = self
2569                        .get_current_key_from_aid(target)
2570                        .change_context(OperationBuilderError::ShapeEdgeTargetNotFound)?;
2571                    self.current_state
2572                        .graph
2573                        .add_edge(state_src_key, state_target_key, av);
2574                    self.current_state
2575                        .edge_may_originate_from_shape_query
2576                        .insert((src.clone(), target.clone()));
2577                }
2578            }
2579        }
2580
2581        let gsq = GraphShapeQuery::new(param, expected_graph, node_keys_to_shape_idents);
2582
2583        // TODO: need to validate GSQ somewhere.
2584        //  Most importantly, that there are no free floating shape nodes.
2585        // TODO: do this with under the is_finished flag.
2586
2587        let initial_true_branch_state = self.current_state.clone();
2588
2589        let (ud_true_branch, interp_true_branch) =
2590            self.interpret_instructions(query_instructions.true_branch)?;
2591        // switch back to the other state
2592        let after_true_branch_state = mem::replace(&mut self.current_state, false_branch_state);
2593        let (ud_false_branch, interp_false_branch) =
2594            self.interpret_instructions(query_instructions.false_branch)?;
2595        let after_false_branch_state = mem::replace(&mut self.current_state, state_before);
2596        // TODO: reconcile the states of both branches. same as in query.
2597
2598        // current situation: self.current_state is before both branches, and we have the true and false branch states
2599        // available to reconcile.
2600
2601        let merged_state = merge_states(true, &after_true_branch_state, &after_false_branch_state);
2602        self.current_state = merged_state;
2603
2604        let ud_instruction = UDInstruction::ShapeQuery(
2605            gsq,
2606            abstract_args,
2607            QueryInstructions {
2608                taken: ud_true_branch,
2609                not_taken: ud_false_branch,
2610            },
2611        );
2612
2613        let interp_instruction = InterpretedInstruction::Query(InterpretedQueryInstructions {
2614            initial_state_true_branch: initial_true_branch_state,
2615            initial_state_false_branch: initial_false_branch_state,
2616            true_branch: interp_true_branch,
2617            false_branch: interp_false_branch,
2618        });
2619
2620        Ok((ud_instruction, interp_instruction))
2621    }
2622
2623    fn interpret_graph_shape_query_old(
2624        &mut self,
2625        is_finished: bool,
2626        gsq_op_marker: AbstractOperationResultMarker,
2627        gsq_instructions: Vec<GraphShapeQueryInstruction<S>>,
2628        query_instructions: IntermediateQueryInstructions<S>,
2629    ) -> Result<(UDInstruction<S>, InterpretedInstruction<S>), OperationBuilderError> {
2630        let state_before = self.current_state.clone();
2631
2632        // preparation for false branch
2633        let false_branch_state = self.current_state.clone();
2634        let initial_false_branch_state = false_branch_state.clone();
2635
2636        // first pass: collect the initial graph (the parameter)
2637        let mut param_builder = OperationParameterBuilder::new();
2638
2639        let mut abstract_args = Vec::new();
2640
2641        let mut arg_aid_to_param_subst: BiMap<AbstractNodeId, SubstMarker> = BiMap::new();
2642        let mut arg_aid_to_node_keys: BiMap<AbstractNodeId, NodeKey> = BiMap::new();
2643
2644        // Collects the AID and adds it to all relevant mappings.
2645        // The passed AID is a node that is part of the pre-existing graph.
2646        let mut collect_aid = |aid: AbstractNodeId| -> Result<(), OperationBuilderError> {
2647            if arg_aid_to_param_subst.contains_left(&aid) {
2648                // we already processed this
2649                return Ok(());
2650            }
2651            // invent a new subst marker for this AID.
2652            let subst_marker = param_builder.next_subst_marker();
2653            let key = self.get_current_key_from_aid(aid)?;
2654            let abstract_value = self
2655                .current_state
2656                .graph
2657                .get_node_attr(key)
2658                .expect(
2659                    "internal error: node key should be in state graph since it is in the mapping",
2660                )
2661                .clone();
2662            // the shape query will expect the same AV
2663            // context-matching here is against the purpose of shape queries, so every argument is explicit
2664            param_builder
2665                .expect_explicit_input_node(subst_marker, abstract_value)
2666                .change_context(OperationBuilderError::InternalError(
2667                    "node should not be in param",
2668                ))?;
2669            // we need to push in the same sequence as expected in explicit_input_nodes
2670            abstract_args.push(aid.clone());
2671            arg_aid_to_param_subst.insert(aid.clone(), subst_marker.clone());
2672            arg_aid_to_node_keys.insert(aid.clone(), key);
2673            Ok(())
2674        };
2675
2676        // Collects the AID if it is part of the pre-existing graph.
2677        let mut collect_non_shape_ident =
2678            |&aid: &AbstractNodeId| -> Result<(), OperationBuilderError> {
2679                match aid {
2680                    AbstractNodeId::ParameterMarker(_) => {
2681                        // we need this.
2682                        collect_aid(aid)?;
2683                    }
2684                    AbstractNodeId::DynamicOutputMarker(orm, node_marker) => {
2685                        // we need this, but only if it is not from the current graph shape query.
2686                        if orm != gsq_op_marker {
2687                            collect_aid(aid)?;
2688                        }
2689                    }
2690                    AbstractNodeId::Named(..) => {
2691                        // same as above dynamic, except we know that it is not from the current graph shape query since we couldn't have
2692                        // renamed the matched node yet.
2693                        collect_aid(aid)?;
2694                    }
2695                }
2696                Ok(())
2697            };
2698
2699        for instruction in &gsq_instructions {
2700            match instruction {
2701                GraphShapeQueryInstruction::ExpectShapeNode(_, _) => {
2702                    // Skip. this does not affect the initial graph.
2703                }
2704                GraphShapeQueryInstruction::ExpectShapeEdge(src, target, _) => {
2705                    // we need both src and target to be in the initial graph, assuming they dont come from `gsq_op_marker`
2706                    // TODO: really we need to collect the entire connected components associated with src and target here?
2707                    collect_non_shape_ident(src)?;
2708                    collect_non_shape_ident(target)?;
2709                }
2710                GraphShapeQueryInstruction::ExpectShapeNodeChange(aid, _) => {
2711                    // we need this node to be in the initial graph.
2712                    collect_non_shape_ident(aid)?;
2713                }
2714            }
2715        }
2716
2717        let param = param_builder
2718            .build()
2719            .change_context(OperationBuilderError::InternalError(
2720                "Failed to build operation parameter for graph shape query",
2721            ))?;
2722
2723        // second pass:
2724        // modify to have the expected graph as well as shape ident mappings.
2725        // simultaneously, also modify the *current state graph* to prepare it for the true branch.
2726        // make a copy before that though, for the false branch.
2727
2728        let mut expected_graph = param.parameter_graph.clone();
2729        let mut node_keys_to_shape_idents: BiMap<NodeKey, ShapeNodeIdentifier> = BiMap::new();
2730
2731        // let aid_to_node_key = |aid| -> Result<NodeKey, OperationBuilderError> {
2732        //     arg_aid_to_node_keys.get_left(&aid)
2733        //         .cloned()
2734        //         .or_else(|| {
2735        //             if let AbstractNodeId::DynamicOutputMarker(orm, node_marker) = aid {
2736        //                 if orm == gsq_op_marker {
2737        //                     // this is a new node from the graph shape query.
2738        //                     let sni: ShapeNodeIdentifier = node_marker.0.into();
2739        //                     node_keys_to_shape_idents.get_right(&sni).copied()
2740        //                 } else {
2741        //                     None
2742        //                 }
2743        //             } else {
2744        //                 None
2745        //             }
2746        //         })
2747        //         .ok_or(OperationBuilderError::NotFoundAid(aid))
2748        // };
2749
2750        // TODO: ugly. fix. needed because the above closure approach does not work due to borrowing issues.
2751        macro_rules! aid_to_node_key_hack {
2752            ($aid:expr) => {
2753                arg_aid_to_node_keys
2754                    .get_left(&$aid)
2755                    .cloned()
2756                    .or_else(|| {
2757                        if let AbstractNodeId::DynamicOutputMarker(orm, node_marker) = $aid {
2758                            if orm == gsq_op_marker {
2759                                // this is a new node from the graph shape query.
2760                                let sni: ShapeNodeIdentifier = node_marker.0.into();
2761                                node_keys_to_shape_idents.get_right(&sni).copied()
2762                            } else {
2763                                None
2764                            }
2765                        } else {
2766                            None
2767                        }
2768                    })
2769                    .ok_or(OperationBuilderError::NotFoundAid($aid))
2770            };
2771        }
2772
2773        for instruction in gsq_instructions {
2774            match instruction {
2775                GraphShapeQueryInstruction::ExpectShapeNode(marker, av) => {
2776                    let key = expected_graph.add_node(av.clone());
2777                    let shape_node_ident = marker.0.clone().into();
2778                    // TODO: insert is panicking and therefore we should return an error instead here.
2779                    // TODO: make bimap::insert fallible? return a must_use Option<()>?
2780                    node_keys_to_shape_idents.insert(key, shape_node_ident);
2781
2782                    // now update the state for the true branch.
2783                    let state_key = self.current_state.graph.add_node(av);
2784                    let aid =
2785                        AbstractNodeId::DynamicOutputMarker(gsq_op_marker.clone(), marker.clone());
2786                    self.current_state
2787                        .node_keys_to_aid
2788                        .insert(state_key, aid.clone());
2789                    self.current_state
2790                        .node_may_originate_from_shape_query
2791                        .insert(aid.clone());
2792                }
2793                GraphShapeQueryInstruction::ExpectShapeNodeChange(aid, av) => {
2794                    // set the expected av
2795                    let key = aid_to_node_key_hack!(aid.clone())?;
2796                    expected_graph.set_node_attr(key, av.clone());
2797
2798                    // now update the state for the true branch.
2799                    let state_key = self
2800                        .get_current_key_from_aid(aid)
2801                        .change_context(OperationBuilderError::NotFoundAid(aid))?;
2802                    self.current_state.graph.set_node_attr(state_key, av);
2803                }
2804                GraphShapeQueryInstruction::ExpectShapeEdge(src, target, av) => {
2805                    let src_key = aid_to_node_key_hack!(src.clone())?;
2806                    let target_key = aid_to_node_key_hack!(target.clone())?;
2807                    expected_graph.add_edge(src_key, target_key, av.clone());
2808
2809                    // now update the state for the true branch.
2810                    let state_src_key = self
2811                        .get_current_key_from_aid(src)
2812                        .change_context(OperationBuilderError::ShapeEdgeSourceNotFound)?;
2813                    let state_target_key = self
2814                        .get_current_key_from_aid(target)
2815                        .change_context(OperationBuilderError::ShapeEdgeTargetNotFound)?;
2816                    self.current_state
2817                        .graph
2818                        .add_edge(state_src_key, state_target_key, av);
2819                    self.current_state
2820                        .edge_may_originate_from_shape_query
2821                        .insert((src.clone(), target.clone()));
2822                }
2823            }
2824        }
2825
2826        let gsq = GraphShapeQuery::new(param, expected_graph, node_keys_to_shape_idents);
2827
2828        // TODO: need to validate GSQ somewhere.
2829        //  Most importantly, that there are no free floating shape nodes.
2830        // TODO: do this with under the is_finished flag.
2831
2832        let initial_true_branch_state = self.current_state.clone();
2833
2834        let (ud_true_branch, interp_true_branch) =
2835            self.interpret_instructions(query_instructions.true_branch)?;
2836        // switch back to the other state
2837        let after_true_branch_state = mem::replace(&mut self.current_state, false_branch_state);
2838        let (ud_false_branch, interp_false_branch) =
2839            self.interpret_instructions(query_instructions.false_branch)?;
2840        let after_false_branch_state = mem::replace(&mut self.current_state, state_before);
2841        // TODO: reconcile the states of both branches. same as in query.
2842
2843        // current situation: self.current_state is before both branches, and we have the true and false branch states
2844        // available to reconcile.
2845
2846        let merged_state = merge_states(true, &after_true_branch_state, &after_false_branch_state);
2847        self.current_state = merged_state;
2848
2849        let ud_instruction = UDInstruction::ShapeQuery(
2850            gsq,
2851            AbstractOperationArgument {
2852                selected_input_nodes: abstract_args,
2853                subst_to_aid: arg_aid_to_param_subst.into_right_map(),
2854            },
2855            QueryInstructions {
2856                taken: ud_true_branch,
2857                not_taken: ud_false_branch,
2858            },
2859        );
2860
2861        let interp_instruction = InterpretedInstruction::Query(InterpretedQueryInstructions {
2862            initial_state_true_branch: initial_true_branch_state,
2863            initial_state_false_branch: initial_false_branch_state,
2864            true_branch: interp_true_branch,
2865            false_branch: interp_false_branch,
2866        });
2867
2868        Ok((ud_instruction, interp_instruction))
2869    }
2870
2871    fn get_current_substitution(
2872        &self,
2873        param: &OperationParameter<S>,
2874        args: Vec<AbstractNodeId>,
2875    ) -> Result<(ParameterSubstitution, AbstractOperationArgument), OperationBuilderError> {
2876        self.current_state.get_substitution(param, args)
2877    }
2878
2879    fn get_new_unnamed_abstract_operation_marker(&mut self) -> AbstractOperationResultMarker {
2880        let val = self.counter;
2881        self.counter += 1;
2882        AbstractOperationResultMarker::Implicit(val)
2883    }
2884
2885    fn get_current_key_from_aid(
2886        &self,
2887        aid: AbstractNodeId,
2888    ) -> Result<NodeKey, OperationBuilderError> {
2889        self.current_state.get_key_from_aid(&aid)
2890    }
2891
2892    fn get_current_aid_from_key(
2893        &self,
2894        key: NodeKey,
2895    ) -> Result<AbstractNodeId, OperationBuilderError> {
2896        self.current_state.get_aid_from_key(&key)
2897    }
2898}
2899
2900fn get_state_for_path<S: Semantics>(
2901    initial_state: &IntermediateState<S>,
2902    interpreted_instructions: &InterpretedInstructions<S>,
2903    path: &mut impl Iterator<Item = IntermediateStatePath>,
2904) -> Option<IntermediateState<S>> {
2905    let mut current_state = initial_state;
2906
2907    for (_, instruction) in interpreted_instructions {
2908        match path.next() {
2909            None => {
2910                // no more path, we are done
2911                return Some(current_state.clone());
2912            }
2913            Some(path_element) => {
2914                match path_element {
2915                    IntermediateStatePath::Advance | IntermediateStatePath::SkipQuery => {
2916                        current_state = &instruction.state_after;
2917                    }
2918                    IntermediateStatePath::EnterTrue | IntermediateStatePath::EnterFalse => {
2919                        // this should not happen
2920                        panic!(
2921                            "internal error: unexpected path element: {:?}",
2922                            path_element
2923                        );
2924                    }
2925                    IntermediateStatePath::StartQuery(..) => {
2926                        if let InterpretedInstruction::Query(query_instructions) =
2927                            &instruction.instruction
2928                        {
2929                            // we are entering a query, so we need to check the true branch
2930                            // TODO: perhaps here we should have a third option .state_inside_query_view ?
2931                            current_state = &query_instructions.initial_state_true_branch;
2932
2933                            // now we need either enter true or enter false
2934                            match path.next() {
2935                                Some(IntermediateStatePath::EnterTrue) => {
2936                                    // we are entering the true branch, so we need to check the true branch instructions
2937                                    return get_state_for_path(
2938                                        &current_state,
2939                                        &query_instructions.true_branch,
2940                                        path,
2941                                    );
2942                                }
2943                                Some(IntermediateStatePath::EnterFalse) => {
2944                                    // we are entering the false branch, so we need to check the false branch instructions
2945                                    current_state = &query_instructions.initial_state_false_branch;
2946                                    return get_state_for_path(
2947                                        &current_state,
2948                                        &query_instructions.false_branch,
2949                                        path,
2950                                    );
2951                                }
2952                                _ => {
2953                                    // we are not entering any branch, so we just return the current state
2954                                    return Some(current_state.clone());
2955                                }
2956                            }
2957                        } else {
2958                            // this should not happen, since we only enter queries here
2959                            return None;
2960                        }
2961                    }
2962                }
2963            }
2964        }
2965    }
2966
2967    Some(current_state.clone())
2968}
2969
2970fn get_query_path_for_path<S: Semantics>(
2971    path: &mut impl Iterator<Item = IntermediateStatePath>,
2972) -> Vec<QueryPath> {
2973    let mut query_path = Vec::new();
2974
2975    for pe in path {
2976        match pe {
2977            IntermediateStatePath::EnterTrue => query_path.push(QueryPath::TrueBranch),
2978            IntermediateStatePath::EnterFalse => query_path.push(QueryPath::FalseBranch),
2979            IntermediateStatePath::StartQuery(name) => {
2980                query_path.push(QueryPath::Query(
2981                    name.unwrap_or("<unnamed query>".to_string()),
2982                ));
2983            }
2984            _ => {}
2985        }
2986    }
2987
2988    query_path
2989}
2990
2991/// Takes two intermediate states and computes the smallest subgraph and most general abstract values
2992/// such that the resulting state is a sound approximation of the two states.
2993///
2994/// Nodes are only merged if they have exactly the same abstract node ID in both branches.
2995///
2996/// Also, abstract type merging is fallible, so if two nodes are incompatible with each other, they don't appear in the resulting state.
2997///
2998/// # Example:
2999/// 1. Initial state is `P(0)|String`
3000/// 2. We branch:
3001/// 2a. True branch ends with graph `P(0)|String -> O(c1)|String -> O(c2)|String`
3002/// 2b. False branch ends with graph `P(0)|String -> O(c1)|Integer -> O(c3)|Object`
3003/// 3. The resulting state will be `P(0)|String -> O(c1)|Object`
3004///
3005/// Note how the second added node from the true branch is not present *with the same name* in the false branch, and
3006/// therefore is not present in the resulting state. Same for `O(c3)` from the false branch.
3007/// Also note how the node that exists in both branches, `O(c1)`, is present in the resulting state with the
3008/// least common supertype of the two branches, which is `Object` in this case.
3009fn merge_states<S: Semantics>(
3010    is_true_shape: bool,
3011    state_true: &IntermediateState<S>,
3012    state_false: &IntermediateState<S>,
3013) -> IntermediateState<S> {
3014    merge_states_result(is_true_shape, state_true, state_false).merged_state
3015}
3016
3017struct MergeStatesResult<S: Semantics> {
3018    merged_state: IntermediateState<S>,
3019    /// The AIDs that are present in the true state but not in the merged state.
3020    missing_from_true: HashSet<AbstractNodeId>,
3021    /// The AIDs that are present in the false state but not in the merged state.
3022    missing_from_false: HashSet<AbstractNodeId>,
3023}
3024
3025fn merge_states_result<S: Semantics>(
3026    is_true_shape: bool,
3027    state_true: &IntermediateState<S>,
3028    state_false: &IntermediateState<S>,
3029) -> MergeStatesResult<S> {
3030    // TODO: handle `is_true_shape`.
3031    //  ^ actually, we're doing that in interpret_graph_shape_query, so we don't need to do it here, I think.
3032
3033    // check if either is diverged, if so, copy the other
3034    // TODO: warning, if divergence can ever be 'recovered', then we need to make sure the effects of
3035    //  the diverged branch are not lost _up to divergence point_.
3036    if state_true.has_diverged {
3037        return MergeStatesResult {
3038            merged_state: state_false.clone(),
3039            // everything from true is "missing". these IDs will get a ForgetAid inserted into the true branch.
3040            // since true has diverged, that shouldn't be a problem.
3041            missing_from_true: state_true
3042                .node_keys_to_aid
3043                .right_values()
3044                .copied()
3045                .collect(),
3046            missing_from_false: HashSet::new(),
3047        };
3048    }
3049    if state_false.has_diverged {
3050        return MergeStatesResult {
3051            merged_state: state_true.clone(),
3052            // everything from false is "missing". these IDs will get a ForgetAid inserted into the false branch.
3053            // since false has diverged, that shouldn't be a problem.
3054            missing_from_true: HashSet::new(),
3055            missing_from_false: state_false
3056                .node_keys_to_aid
3057                .right_values()
3058                .copied()
3059                .collect(),
3060        };
3061    }
3062
3063    let mut new_state = IntermediateState::new();
3064
3065    let mut common_aids = HashSet::new();
3066    // First, collect all AIDs that are present in both states.
3067    for aid in state_true.node_keys_to_aid.right_values() {
3068        if state_false.node_keys_to_aid.contains_right(aid) {
3069            common_aids.insert(aid.clone());
3070        }
3071    }
3072
3073    // Now, for each common AID, we need to merge the nodes and info from both states.
3074    for aid in common_aids {
3075        let key_true = *state_true
3076            .node_keys_to_aid
3077            .get_right(&aid)
3078            .expect("internal error: AID should be in mapping");
3079        let key_false = *state_false
3080            .node_keys_to_aid
3081            .get_right(&aid)
3082            .expect("internal error: AID should be in mapping");
3083
3084        // Get the abstract values from both states.
3085        let av_true = state_true
3086            .graph
3087            .get_node_attr(key_true)
3088            .expect("internal error: Key should be in graph");
3089        let av_false = state_false
3090            .graph
3091            .get_node_attr(key_false)
3092            .expect("internal error: Key should be in graph");
3093
3094        // Merge the abstract values.
3095        let Some(merged_av) = S::join_nodes(av_true, av_false) else {
3096            // If we cannot merge the abstract values, we skip this AID.
3097            continue;
3098        };
3099
3100        // Add the merged node to the new state.
3101        let new_key = new_state.graph.add_node(merged_av);
3102        new_state.node_keys_to_aid.insert(new_key, aid.clone());
3103        // Keep track of the node originating from a shape query...
3104        if state_true
3105            .node_may_originate_from_shape_query
3106            .contains(&aid)
3107            || state_false
3108                .node_may_originate_from_shape_query
3109                .contains(&aid)
3110        {
3111            new_state.node_may_originate_from_shape_query.insert(aid);
3112        }
3113        // ... as well as the written types.
3114        // We take the join-union of the written types from both states.
3115        let written_av_true = state_true.node_may_be_written_to.get(&aid).cloned();
3116        let written_av_false = state_false.node_may_be_written_to.get(&aid).cloned();
3117        let merged_written_av = match (written_av_true, written_av_false) {
3118            (Some(av_true), Some(av_false)) => {
3119                // Note: we need this to be some, since we've already inserted the node in the new graph.
3120                // for more detail, see the comment in the edges section below.
3121                Some(S::join_nodes(&av_true, &av_false).expect(
3122                    "client semantics error: expected to be able to merge written node attributes",
3123                ))
3124            }
3125            (Some(av_true), None) => Some(av_true),
3126            (None, Some(av_false)) => Some(av_false),
3127            (None, None) => None,
3128        };
3129        if let Some(merged_av) = merged_written_av {
3130            new_state.node_may_be_written_to.insert(aid, merged_av);
3131        }
3132    }
3133
3134    // Now we merge the edges.
3135    for (from_key_true, to_key_true, attr) in state_true.graph.graph.all_edges() {
3136        let from_aid = state_true
3137            .node_keys_to_aid
3138            .get_left(&from_key_true)
3139            .expect("internal error: from key should be in mapping");
3140        let to_aid = state_true
3141            .node_keys_to_aid
3142            .get_left(&to_key_true)
3143            .expect("internal error: to key should be in mapping");
3144        let Some(from_key_merged) = new_state.node_keys_to_aid.get_right(from_aid) else {
3145            // If the from AID has not been merged, we skip this edge.
3146            continue;
3147        };
3148        let Some(to_key_merged) = new_state.node_keys_to_aid.get_right(to_aid) else {
3149            // If the to AID has not been merged, we skip this edge.
3150            continue;
3151        };
3152        let av_true = state_true
3153            .graph
3154            .get_edge_attr((from_key_true, to_key_true))
3155            .expect("internal error: edge should be in graph");
3156
3157        // Skip edges whose endpoints are not in the common AIDs.
3158        // because of the above new_state let else check, this should always succeed, though.
3159        let Some(from_key_false) = state_false.node_keys_to_aid.get_right(from_aid) else {
3160            continue;
3161        };
3162        let Some(to_key_false) = state_false.node_keys_to_aid.get_right(to_aid) else {
3163            continue;
3164        };
3165
3166        // Check if the edge exists in the false state.
3167        let Some(av_false) = state_false
3168            .graph
3169            .get_edge_attr((*from_key_false, *to_key_false))
3170        else {
3171            // If the edge does not exist in the false state, we skip it.
3172            continue;
3173        };
3174        let Some(merged_av) = S::join_edges(av_true, av_false) else {
3175            // If we cannot merge the edges, we skip this edge.
3176            continue;
3177        };
3178        // Add the merged edge to the new state.
3179        new_state
3180            .graph
3181            .add_edge(*from_key_merged, *to_key_merged, merged_av);
3182        // Keep track of the edge originating from a shape query.
3183        let edge = (from_aid.clone(), to_aid.clone());
3184        if state_true
3185            .edge_may_originate_from_shape_query
3186            .contains(&edge)
3187            || state_false
3188                .edge_may_originate_from_shape_query
3189                .contains(&edge)
3190        {
3191            new_state.edge_may_originate_from_shape_query.insert(edge);
3192        }
3193
3194        let written_av_true = state_true
3195            .edge_may_be_written_to
3196            .get(&(from_aid.clone(), to_aid.clone()))
3197            .cloned();
3198        let written_av_false = state_false
3199            .edge_may_be_written_to
3200            .get(&(from_aid.clone(), to_aid.clone()))
3201            .cloned();
3202        let merged_written_av = match (written_av_true, written_av_false) {
3203            (Some(av_true), Some(av_false)) => {
3204                // Note: this must be Some, because we have the edge in our merged graph for a fact.
3205                // If we were to ignore it *just for edge_may_be_written_to* if the written values could not be merged,
3206                // we'd unsoundly skip returning information about potential changes to the edge.
3207                // I.e., we expect client semantics to support written-av merges if the branch merge succeeded in the first place.
3208                // this should generally be the case.
3209                // Update: Below cannot panic for transitive client type systems.
3210                //  since we were able to join the edge, that means the inferred AVs for both branches
3211                //  were compatible. We *know* that a written_av *must be* a subtype of the inferred AVs,
3212                //  since whenever we write we join that write to the current abstract graph AV.
3213                //  So, we know that some super type of av_true and some super type of av_false
3214                //  were joinable, and in a transitive type system, that means that av_true and av_false
3215                //  must be joinable as well. QED.
3216                Some(S::join_edges(&av_true, &av_false).expect(
3217                    "client semantics error: expected to be able to merge written edge attributes",
3218                ))
3219            }
3220            (Some(av_true), None) => Some(av_true),
3221            (None, Some(av_false)) => Some(av_false),
3222            (None, None) => None,
3223        };
3224        if let Some(merged_av) = merged_written_av {
3225            new_state
3226                .edge_may_be_written_to
3227                .insert((from_aid.clone(), to_aid.clone()), merged_av);
3228        }
3229
3230        // TODO: edge orders need to be handled here.
3231    }
3232
3233    // which AIDs are actually present in the merged state, taking into account everything (names, type join)
3234    let final_merged_state_aids = new_state
3235        .node_keys_to_aid
3236        .right_values()
3237        .cloned()
3238        .collect::<HashSet<_>>();
3239
3240    MergeStatesResult {
3241        merged_state: new_state,
3242        missing_from_true: state_true
3243            .node_keys_to_aid
3244            .right_values()
3245            .cloned()
3246            .filter(|aid| !final_merged_state_aids.contains(aid))
3247            .collect(),
3248        missing_from_false: state_false
3249            .node_keys_to_aid
3250            .right_values()
3251            .cloned()
3252            .filter(|aid| !final_merged_state_aids.contains(aid))
3253            .collect(),
3254    }
3255}