1use events::{Event, EventWriter};
10use kittycad_execution_plan_traits::events;
11use kittycad_execution_plan_traits::Address;
12use kittycad_execution_plan_traits::{MemoryError, Primitive, ReadMemory};
13use kittycad_modeling_cmds::websocket::ModelingBatch;
14use kittycad_modeling_session::{RunCommandError, Session as ModelingSession};
15pub use memory::{Memory, Stack, StaticMemoryInitializer};
16use serde::{Deserialize, Serialize};
17
18use self::api_request::ApiRequest;
19pub use self::arithmetic::{
20 operator::{BinaryOperation, Operation, UnaryOperation},
21 BinaryArithmetic, UnaryArithmetic,
22};
23use self::import_files::ImportFiles;
24pub use self::instruction::{Instruction, InstructionKind};
25
26pub mod api_request;
27mod arithmetic;
28pub mod constants;
30pub mod import_files;
32pub mod instruction;
34mod memory;
35pub mod sketch_types;
36#[cfg(test)]
37mod tests;
38
39#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
41pub enum Destination {
42 Address(Address),
44 StackPush,
46 StackExtend,
48}
49
50impl std::fmt::Display for Destination {
51 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52 match self {
53 Destination::Address(a) => a.fmt(f),
54 Destination::StackPush => "StackPush".fmt(f),
55 Destination::StackExtend => "StackExtend".fmt(f),
56 }
57 }
58}
59
60#[derive(Deserialize, Serialize, Debug, PartialEq, Clone)]
62pub enum Operand {
63 Literal(Primitive),
65 Reference(Address),
67 StackPop,
69}
70
71impl Operand {
72 fn eval(&self, mem: &mut Memory) -> Result<Primitive> {
74 match self {
75 Operand::Literal(v) => Ok(v.to_owned()),
76 Operand::Reference(addr) => match mem.get(addr) {
77 None => Err(ExecutionError::MemoryEmpty { addr: *addr }),
78 Some(v) => Ok(v.to_owned()),
79 },
80 Operand::StackPop => mem.stack.pop_single(),
81 }
82 }
83}
84
85#[derive(Debug)]
87pub struct ExecutionFailed {
88 pub error: ExecutionError,
90 pub instruction: Option<Instruction>,
92 pub instruction_index: usize,
94}
95
96pub async fn execute(
98 mem: &mut Memory,
99 plan: Vec<Instruction>,
100 session: &mut Option<ModelingSession>,
101) -> std::result::Result<(), ExecutionFailed> {
102 let mut events = EventWriter::default();
103 let mut batch_queue = ModelingBatch::default();
104 let n = plan.len();
105 for (i, instruction) in plan.into_iter().enumerate() {
106 if let Err(e) = instruction
107 .clone()
108 .execute(mem, session, &mut events, &mut batch_queue)
109 .await
110 {
111 return Err(ExecutionFailed {
112 error: e,
113 instruction: Some(instruction),
114 instruction_index: i,
115 });
116 }
117 }
118 cleanup(session, batch_queue, &mut events, n).await?;
119 Ok(())
120}
121
122async fn cleanup(
123 session: &mut Option<ModelingSession>,
124 batch_queue: ModelingBatch,
125 events: &mut EventWriter,
126 n: usize,
127) -> std::result::Result<(), ExecutionFailed> {
128 if batch_queue.is_empty() {
129 return Ok(());
130 }
131 let Some(session) = session else {
132 return Err(ExecutionFailed {
133 error: ExecutionError::NoApiClient,
134 instruction: None,
135 instruction_index: n,
136 });
137 };
138 crate::api_request::flush_batch_queue(session, batch_queue, events)
139 .await
140 .map_err(|e| ExecutionFailed {
141 error: e,
142 instruction: None,
143 instruction_index: n,
144 })?;
145 Ok(())
146}
147
148pub struct ExecutionState {
150 pub mem: Memory,
152 pub active_instruction: usize,
154 pub events: Vec<Event>,
156}
157
158pub async fn execute_time_travel(
163 mem: &mut Memory,
164 plan: Vec<Instruction>,
165 session: &mut Option<ModelingSession>,
166) -> (Vec<ExecutionState>, usize) {
167 let mut out = Vec::new();
168 let mut events = EventWriter::default();
169 let mut batch_queue = Default::default();
170 let n = plan.len();
171 for (active_instruction, instruction) in plan.into_iter().enumerate() {
172 let res = instruction.execute(mem, session, &mut events, &mut batch_queue).await;
173
174 let mut crashed = false;
175 if let Err(e) = res {
176 events.push(Event {
177 text: e.to_string(),
178 severity: events::Severity::Error,
179 related_addresses: Vec::new(),
180 });
181 crashed = true;
182 }
183 let state = ExecutionState {
184 mem: mem.clone(),
185 active_instruction,
186 events: events.drain(),
187 };
188
189 out.push(state);
190 if crashed {
191 return (out, active_instruction);
192 }
193 }
194 if let Err(e) = cleanup(session, batch_queue, &mut events, n).await {
195 events.push(Event {
196 text: e.error.to_string(),
197 severity: events::Severity::Error,
198 related_addresses: Default::default(),
199 });
200 out.push(ExecutionState {
201 mem: mem.clone(),
202 active_instruction: n - 1,
203 events: events.drain(),
204 });
205 }
206 (out, n - 1)
207}
208
209type Result<T> = std::result::Result<T, ExecutionError>;
210
211#[derive(Debug, thiserror::Error)]
213pub enum ExecutionError {
214 #[error("Memory address {addr} was not set")]
216 MemoryEmpty {
217 addr: Address,
219 },
220 #[error("Cannot apply operation {op} to operands {operands:?}")]
222 CannotApplyOperation {
223 op: Operation,
225 operands: Vec<Primitive>,
227 },
228 #[error("No endpoint {name} recognized")]
230 UnrecognizedEndpoint {
231 name: String,
233 },
234 #[error("Error sending command to API: {0}")]
236 ModelingApiError(#[from] RunCommandError),
237 #[error("{0}")]
239 MemoryError(#[from] MemoryError),
240 #[error("you tried to access element {index} in a list of size {count}")]
242 ListIndexOutOfBounds {
243 count: usize,
245 index: usize,
247 },
248 #[error("could not make API call because no KittyCAD API client was provided")]
250 NoApiClient,
251 #[error("No property '{property}' exists in the object starting at {address}")]
253 UndefinedProperty {
254 property: String,
256 address: Address,
258 },
259 #[error("No SketchGroup exists at index {index}")]
261 NoSketchGroup {
262 index: usize,
264 },
265 #[error(
267 "You tried to set a SketchGroup into destination {destination} but no such index exists. The last slot available is {len}."
268 )]
269 SketchGroupNoGaps {
270 destination: usize,
272 len: usize,
274 },
275 #[error("An argument of the wrong type was used.")]
277 BadArg {
278 reason: String,
280 },
281 #[error("A general execution error.")]
283 General {
284 reason: String,
286 },
287}