use crate::operation::builtin::LibBuiltinOperation;
use crate::operation::query::{BuiltinQuery, GraphShapeQuery, run_builtin_query, run_shape_query};
use crate::operation::signature::OperationSignature;
use crate::operation::signature::parameter::{
AbstractOperationOutput, AbstractOutputNodeMarker, GraphWithSubstitution, OperationArgument,
OperationOutput, OperationParameter, ParameterSubstitution,
};
use crate::operation::{
OperationError, OperationResult, run_builtin_operation, run_lib_builtin_operation,
run_operation,
};
use crate::prelude::*;
use crate::semantics::{AbstractGraph, ConcreteGraph};
use crate::util::{InternString, log};
use crate::{NodeKey, Semantics, SubstMarker, interned_string_newtype};
use derive_more::with_trait::From;
use error_stack::{FutureExt, ResultExt, bail, report};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, From)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AbstractOperationResultMarker {
Custom(InternString),
#[from(ignore)]
Implicit(u64),
}
interned_string_newtype!(
AbstractOperationResultMarker,
AbstractOperationResultMarker::Custom
);
#[derive(derive_more::Debug, Clone, Copy, Hash, Eq, PartialEq, From)]
#[debug("N({_0})")]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct NamedMarker(InternString);
interned_string_newtype!(NamedMarker);
#[derive(Clone, Copy, From, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum AbstractNodeId {
ParameterMarker(SubstMarker),
DynamicOutputMarker(AbstractOperationResultMarker, AbstractOutputNodeMarker),
Named(NamedMarker),
}
impl AbstractNodeId {
pub fn param(m: impl Into<SubstMarker>) -> Self {
AbstractNodeId::ParameterMarker(m.into())
}
pub fn dynamic_output(
output_id: impl Into<AbstractOperationResultMarker>,
output_marker: impl Into<AbstractOutputNodeMarker>,
) -> Self {
let output_id = output_id.into();
let output_marker = output_marker.into();
AbstractNodeId::DynamicOutputMarker(output_id, output_marker)
}
pub fn named(name: impl Into<NamedMarker>) -> Self {
AbstractNodeId::Named(name.into())
}
}
impl FromStr for AbstractNodeId {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some(stripped) = s.strip_prefix("P(").and_then(|s| s.strip_suffix(')')) {
Ok(AbstractNodeId::param(stripped))
} else if let Some(stripped) = s.strip_prefix("O(").and_then(|s| s.strip_suffix(')')) {
let mut parts = stripped.split(',');
if let (Some(output_id), Some(output_marker), None) =
(parts.next(), parts.next(), parts.next())
{
let output_id = output_id.trim();
let output_marker = output_marker.trim();
Ok(AbstractNodeId::dynamic_output(output_id, output_marker))
} else {
Err(())
}
} else if let Some(stripped) = s.strip_prefix("N(").and_then(|s| s.strip_suffix(')')) {
let name = stripped.trim();
Ok(AbstractNodeId::named(name))
} else {
Err(())
}
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AbstractOperationArgument {
pub selected_input_nodes: Vec<AbstractNodeId>,
pub subst_to_aid: HashMap<SubstMarker, AbstractNodeId>,
}
impl AbstractOperationArgument {
pub fn new() -> Self {
AbstractOperationArgument {
selected_input_nodes: Vec::new(),
subst_to_aid: HashMap::new(),
}
}
pub fn new_for_shape_query(explicit_nodes: Vec<AbstractNodeId>) -> Self {
AbstractOperationArgument {
selected_input_nodes: explicit_nodes,
subst_to_aid: HashMap::new(),
}
}
pub fn infer_explicit_for_param(
selected_nodes: Vec<AbstractNodeId>,
param: &OperationParameter<impl Semantics>,
) -> OperationResult<Self> {
if param.explicit_input_nodes.len() != selected_nodes.len() {
bail!(OperationError::InvalidOperationArgumentCount {
expected: param.explicit_input_nodes.len(),
actual: selected_nodes.len(),
});
}
let subst = param
.explicit_input_nodes
.iter()
.zip(selected_nodes.iter())
.map(|(subst_marker, node_key)| (subst_marker.clone(), node_key.clone()))
.collect();
Ok(AbstractOperationArgument {
selected_input_nodes: selected_nodes,
subst_to_aid: subst,
})
}
}
#[derive(derive_more::Debug)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(bound = "S: crate::serde::SemanticsSerde")
)]
pub enum OpLikeInstruction<S: Semantics> {
#[debug("Builtin(???)")]
Builtin(S::BuiltinOperation),
#[debug("LibBuiltin({_0:?})")]
LibBuiltin(LibBuiltinOperation<S>),
#[debug("Operation({_0:#?})")]
Operation(OperationId),
}
impl<S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>> Clone for OpLikeInstruction<S> {
fn clone(&self) -> Self {
match self {
OpLikeInstruction::Builtin(op) => OpLikeInstruction::Builtin(op.clone()),
OpLikeInstruction::LibBuiltin(op) => OpLikeInstruction::LibBuiltin(op.clone()),
OpLikeInstruction::Operation(id) => OpLikeInstruction::Operation(*id),
}
}
}
#[derive(derive_more::Debug)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(bound = "S: crate::serde::SemanticsSerde")
)]
pub enum Instruction<S: Semantics> {
#[debug("OpLike({_0:#?}, {_1:#?})")]
OpLike(OpLikeInstruction<S>, AbstractOperationArgument),
#[debug("BuiltinQuery(???, {_1:#?}, {_2:#?})")]
BuiltinQuery(
S::BuiltinQuery,
AbstractOperationArgument,
QueryInstructions<S>,
),
#[debug("ShapeQuery(???, {_1:#?}, {_2:#?})")]
ShapeQuery(
GraphShapeQuery<S>,
AbstractOperationArgument,
QueryInstructions<S>,
),
#[debug("RenameNode({old:#?} ==> {new:#?})")]
RenameNode {
old: AbstractNodeId,
new: AbstractNodeId,
},
ForgetAid {
aid: AbstractNodeId,
},
Diverge {
crash_message: String,
},
}
impl<S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>> Clone for Instruction<S> {
fn clone(&self) -> Self {
match self {
Instruction::OpLike(oplike, arg) => Instruction::OpLike(oplike.clone(), arg.clone()),
Instruction::BuiltinQuery(query, arg, query_instr) => {
Instruction::BuiltinQuery(query.clone(), arg.clone(), query_instr.clone())
}
Instruction::ShapeQuery(query, arg, query_instr) => {
Instruction::ShapeQuery(query.clone(), arg.clone(), query_instr.clone())
}
Instruction::RenameNode { old, new } => Instruction::RenameNode {
old: *old,
new: *new,
},
Instruction::ForgetAid { aid } => Instruction::ForgetAid { aid: *aid },
Instruction::Diverge { crash_message } => Instruction::Diverge {
crash_message: crash_message.clone(),
},
}
}
}
#[derive(derive_more::Debug)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(bound = "S: crate::serde::SemanticsSerde")
)]
pub struct QueryInstructions<S: Semantics> {
#[debug("[{}]", taken.iter().map(|(opt, inst)| format!("({opt:#?}, {:#?})", inst)).collect::<Vec<_>>().join(", "))]
pub taken: Vec<InstructionWithResultMarker<S>>,
#[debug("[{}]", not_taken.iter().map(|(opt, inst)| format!("({opt:#?}, {:#?})", inst)).collect::<Vec<_>>().join(", "))]
pub not_taken: Vec<InstructionWithResultMarker<S>>,
}
impl<S: Semantics<BuiltinOperation: Clone, BuiltinQuery: Clone>> Clone for QueryInstructions<S> {
fn clone(&self) -> Self {
QueryInstructions {
taken: self.taken.clone(),
not_taken: self.not_taken.clone(),
}
}
}
pub type InstructionWithResultMarker<S> = (Option<AbstractOperationResultMarker>, Instruction<S>);
#[derive(Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AbstractUserDefinedOperationOutput {
#[serde(with = "serde_json_any_key::any_key_map")]
pub new_nodes: HashMap<AbstractNodeId, AbstractOutputNodeMarker>,
}
impl AbstractUserDefinedOperationOutput {
pub fn new() -> Self {
AbstractUserDefinedOperationOutput {
new_nodes: HashMap::new(),
}
}
}
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(bound = "S: crate::serde::SemanticsSerde")
)]
pub struct UserDefinedOperation<S: Semantics> {
pub signature: OperationSignature<S>,
pub instructions: Vec<InstructionWithResultMarker<S>>,
pub output_changes: AbstractUserDefinedOperationOutput,
}
impl<S: Semantics<BuiltinQuery: Clone, BuiltinOperation: Clone>> Clone for UserDefinedOperation<S> {
fn clone(&self) -> Self {
UserDefinedOperation {
signature: self.signature.clone(),
instructions: self.instructions.clone(),
output_changes: self.output_changes.clone(),
}
}
}
impl<S: Semantics> UserDefinedOperation<S> {
pub fn new_noop() -> Self {
let signature = OperationSignature::new_noop("noop");
UserDefinedOperation {
signature,
instructions: Vec::new(),
output_changes: AbstractUserDefinedOperationOutput::new(),
}
}
pub fn new(
parameter: OperationParameter<S>,
instructions: Vec<InstructionWithResultMarker<S>>,
) -> Self {
let signature = OperationSignature::empty_new("some_name", parameter.clone());
UserDefinedOperation {
signature,
instructions,
output_changes: AbstractUserDefinedOperationOutput::new(),
}
}
pub(crate) fn apply_abstract(
&self,
op_ctx: &OperationContext<S>,
g: &mut GraphWithSubstitution<AbstractGraph<S>>,
) -> OperationResult<AbstractOperationOutput<S>> {
Ok(self.signature.output.apply_abstract(g))
}
pub(crate) fn apply(
&self,
op_ctx: &OperationContext<S>,
g: &mut ConcreteGraph<S>,
arg: OperationArgument,
) -> OperationResult<OperationOutput> {
let mut runner = Runner::new(op_ctx, g, &arg);
runner.run(&self.instructions)?;
let our_output_map = self
.output_changes
.new_nodes
.iter()
.map(|(aid, name)| Ok((*name, runner.aid_to_node_key(*aid)?)))
.collect::<OperationResult<_>>()
.attach_printable_lazy(|| "error while building output map")?;
Ok(OperationOutput {
new_nodes: our_output_map,
removed_nodes: vec![],
})
}
pub fn signature(&self) -> OperationSignature<S> {
self.signature.clone()
}
}
struct Runner<'a, 'arg, S: Semantics> {
op_ctx: &'a OperationContext<S>,
g: &'a mut ConcreteGraph<S>,
arg: &'a OperationArgument<'arg>,
abstract_to_concrete: HashMap<AbstractNodeId, NodeKey>,
forgotten_params: HashSet<NodeKey>,
}
impl<'a, 'arg, S: Semantics> Runner<'a, 'arg, S> {
pub fn new(
op_ctx: &'a OperationContext<S>,
g: &'a mut ConcreteGraph<S>,
arg: &'a OperationArgument<'arg>,
) -> Self {
Runner {
op_ctx,
g,
arg,
abstract_to_concrete: arg
.subst
.mapping
.iter()
.map(|(s, n)| (AbstractNodeId::ParameterMarker(*s), *n))
.collect(),
forgotten_params: HashSet::new(),
}
}
fn run(&mut self, instructions: &[InstructionWithResultMarker<S>]) -> OperationResult<()> {
for (abstract_output_id, instruction) in instructions {
match instruction {
Instruction::OpLike(oplike, arg) => {
let concrete_arg = self.abstract_to_concrete_arg(arg)?;
log::trace!("Resulting concrete arg: {concrete_arg:#?}");
let output = match oplike {
OpLikeInstruction::Operation(op_id) => {
run_operation::<S>(self.g, self.op_ctx, *op_id, concrete_arg)?
}
OpLikeInstruction::Builtin(op) => {
run_builtin_operation::<S>(self.g, op, concrete_arg)?
}
OpLikeInstruction::LibBuiltin(op) => {
run_lib_builtin_operation(self.g, op, concrete_arg)?
}
};
if let Some(abstract_output_id) = abstract_output_id {
self.extend_abstract_mapping(abstract_output_id.clone(), output.new_nodes);
}
}
Instruction::BuiltinQuery(query, arg, query_instr) => {
let concrete_arg = self.abstract_to_concrete_arg(arg)?;
let result = run_builtin_query::<S>(self.g, query, concrete_arg)?;
let next_instr = if result.taken {
&query_instr.taken
} else {
&query_instr.not_taken
};
self.run(next_instr)?
}
Instruction::ShapeQuery(query, arg, query_instr) => {
let concrete_arg = self.abstract_to_concrete_arg(&arg)?;
let result = run_shape_query(
self.g,
query,
&concrete_arg.selected_input_nodes,
&concrete_arg.hidden_nodes,
&concrete_arg.marker_set.borrow(),
)?;
let next_instr =
if let Some(shape_idents_to_node_keys) = result.shape_idents_to_node_keys {
let mut query_result_map = HashMap::new();
for (ident, node_key) in shape_idents_to_node_keys {
let output_marker = AbstractOutputNodeMarker(ident.into());
query_result_map.insert(output_marker, node_key);
}
if let Some(abstract_output_id) = abstract_output_id {
self.extend_abstract_mapping(
abstract_output_id.clone(),
query_result_map,
);
}
&query_instr.taken
} else {
&query_instr.not_taken
};
self.run(next_instr)?;
}
Instruction::RenameNode { old, new } => {
let Some(key) = self.abstract_to_concrete.remove(old) else {
return Err(report!(OperationError::UnknownAID(*old)))
.attach_printable_lazy(|| {
format!("Cannot rename node {old:#?} to {new:#?}, since it is not in the mapping: {:#?}", self.abstract_to_concrete)
});
};
self.abstract_to_concrete.insert(*new, key);
}
Instruction::ForgetAid { aid } => {
let Some(removed_key) = self.abstract_to_concrete.remove(aid) else {
return Err(report!(OperationError::UnknownAID(*aid)))
.attach_printable_lazy(|| {
format!("Cannot forget aid {aid:?}, since it is not in the mapping: {:#?}", self.abstract_to_concrete)
});
};
if let AbstractNodeId::ParameterMarker(_) = aid {
self.forgotten_params.insert(removed_key);
}
log::trace!(
"Forgot aid {aid:?} from mapping: {:#?}",
self.abstract_to_concrete
);
}
Instruction::Diverge { crash_message } => {
return Err(report!(OperationError::UserCrash(crash_message.clone())));
}
}
}
Ok(())
}
fn extend_abstract_mapping(
&mut self,
abstract_output_id: AbstractOperationResultMarker,
output_map: HashMap<AbstractOutputNodeMarker, NodeKey>,
) {
for (marker, node_key) in output_map {
self.abstract_to_concrete.insert(
AbstractNodeId::DynamicOutputMarker(abstract_output_id, marker),
node_key,
);
}
}
fn aid_to_node_key(&self, aid: AbstractNodeId) -> OperationResult<NodeKey> {
self.abstract_to_concrete
.get(&aid)
.copied()
.ok_or_else(|| report!(OperationError::UnknownAID(aid)))
.attach_printable_lazy(|| {
format!(
"Cannot find concrete node key for abstract node id {aid:?} in mapping: {:#?}",
self.abstract_to_concrete
)
})
}
fn abstract_to_concrete_arg(
&self,
arg: &AbstractOperationArgument,
) -> OperationResult<OperationArgument<'arg>> {
log::trace!(
"Getting concrete arg of abstract arg: {arg:#?} previous_results: {:#?}, our operation's argument: {:#?}",
&self.abstract_to_concrete,
&self.arg,
);
let selected_keys: Vec<NodeKey> = arg
.selected_input_nodes
.iter()
.map(|arg| {
self.aid_to_node_key(*arg).attach_printable_lazy(
|| "while converting abstract selected input nodes to concrete keys",
)
})
.collect::<OperationResult<_>>()?;
let new_subst = ParameterSubstitution::new(
arg.subst_to_aid
.iter()
.map(|(subst_marker, abstract_node_id)| {
Ok((
subst_marker.clone(),
self.aid_to_node_key(abstract_node_id.clone())
.attach_printable_lazy(|| format!("while trying to map abstract subtitution for marker {subst_marker:?}"))?,
))
})
.collect::<OperationResult<_>>()?,
);
let mut hidden_nodes: HashSet<_> = self
.abstract_to_concrete
.values()
.copied()
.chain(self.arg.hidden_nodes.iter().copied())
.collect();
for key in self.forgotten_params.iter() {
hidden_nodes.remove(key);
}
Ok(OperationArgument {
selected_input_nodes: selected_keys.into(),
subst: new_subst,
hidden_nodes,
marker_set: self.arg.marker_set,
})
}
}
pub struct QueryTaken<S: Semantics> {
pub instructions: Vec<Instruction<S>>,
}
pub enum PatternChange<S: Semantics> {
ExpectNode(NodeChangePattern<S>),
ExpectEdge(EdgeChangePattern<S>),
}
pub enum NodeChangePattern<S: Semantics> {
NewNode(SubstMarker, S::NodeAbstract),
}
pub enum EdgeChangePattern<S: Semantics> {
NewEdge {
from: SubstMarker,
to: SubstMarker,
abstract_value: S::EdgeAbstract,
},
}