kittycad_execution_plan/
instruction.rs

1use kittycad_execution_plan_traits::{
2    InMemory, ListHeader, MemoryError, NumericPrimitive, ObjectHeader, Primitive, ReadMemory, Value,
3};
4use kittycad_modeling_cmds::{
5    ok_response::OkModelingCmdResponse, output::ImportedGeometry, shared::Point2d, websocket::ModelingBatch,
6};
7use serde::{Deserialize, Serialize};
8
9use crate::{
10    events::{Event, EventWriter, Severity},
11    sketch_types::{self},
12    Address, ApiRequest, BinaryArithmetic, Destination, ExecutionError, ImportFiles, Memory, Operand, Result,
13    UnaryArithmetic,
14};
15
16/// One step of the execution plan.
17#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
18pub struct Instruction {
19    /// What kind of instruction is it?
20    pub kind: InstructionKind,
21    /// Which range in the high-level source code does this instruction correspond to?
22    /// Useful for error reporting, syntax highlighting, etc.
23    pub source_range: Option<SourceRange>,
24}
25
26/// A range of high-level source code.
27#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, PartialOrd, Ord, Deserialize, Serialize)]
28pub struct SourceRange(pub [usize; 2]);
29
30impl Instruction {
31    /// Instantiate an Instruction corresponding to a certain place in the high-level source language.
32    pub fn from_range(kind: InstructionKind, source_range: SourceRange) -> Self {
33        Self {
34            kind,
35            source_range: Some(source_range),
36        }
37    }
38    /// Execute the instruction.
39    pub async fn execute(
40        self,
41        mem: &mut Memory,
42        session: &mut Option<kittycad_modeling_session::Session>,
43        events: &mut EventWriter,
44        batch_queue: &mut ModelingBatch,
45    ) -> Result<()> {
46        self.kind.execute(mem, session, events, batch_queue).await
47    }
48}
49
50impl From<InstructionKind> for Instruction {
51    fn from(kind: InstructionKind) -> Self {
52        Self {
53            kind,
54            source_range: None,
55        }
56    }
57}
58
59/// One step of the execution plan.
60#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
61pub enum InstructionKind {
62    /// Call the KittyCAD API.
63    ApiRequest(ApiRequest),
64    /// Import a geometry file.
65    ImportFiles(ImportFiles),
66    /// Set a primitive to a memory address.
67    SetPrimitive {
68        /// Which memory address to set.
69        address: Address,
70        /// What value to set the memory address to.
71        value: Primitive,
72    },
73    /// Lay out a multi-address value in memory.
74    SetValue {
75        /// Which memory address to set.
76        address: Address,
77        /// What values to put into memory.
78        value_parts: Vec<Primitive>,
79    },
80    /// Find an element/property of an array/object.
81    /// Push the element/property's address onto the stack.
82    /// Assumes the object/list is formatted according to [`Self::SetList`] documentation.
83    AddrOfMember {
84        /// Starting address of the array/object.
85        start: Operand,
86        /// Element index or property name.
87        member: Operand,
88    },
89    /// Set a list of elements into memory.
90    /// # Format
91    /// Lists have this format (each line represents a memory address starting at `start`):
92    ///
93    /// ```nocode
94    /// <number of elements>
95    /// <n = size of element 0>
96    /// <element 0, address 0>
97    /// <...>
98    /// <element 0, address n>
99    /// <n = size of element 1>
100    /// <element 1, address 0>
101    /// <...>
102    /// <element 1, address n>
103    /// ```
104    /// etc etc for each element.
105    SetList {
106        /// List will start at this element.
107        start: Address,
108        /// Each element
109        elements: Vec<Vec<Primitive>>,
110    },
111    /// Perform arithmetic on values in memory.
112    BinaryArithmetic {
113        /// What to do.
114        arithmetic: BinaryArithmetic,
115        /// Write the output to this memory address.
116        destination: Destination,
117    },
118    /// Perform arithmetic on a value in memory.
119    UnaryArithmetic {
120        /// What to do.
121        arithmetic: UnaryArithmetic,
122        /// Write the output to this memory address.
123        destination: Destination,
124    },
125    /// Push this data onto the stack.
126    StackPush {
127        /// Data that will be pushed.
128        data: Vec<Primitive>,
129    },
130    /// Pop data off the stack into memory.
131    StackPop {
132        /// If Some, the value popped will be stored at the destination.
133        /// If None, the value won't be stored anywhere.
134        destination: Option<Destination>,
135    },
136    /// Add the given primitives to whatever is on top of the stack.
137    /// If the stack is empty, runtime error.
138    StackExtend {
139        /// Extend whatever is on top of the stack with this new data.
140        data: Vec<Primitive>,
141    },
142    /// Copy from one address to the other.
143    Copy {
144        /// Copy from here.
145        source: Address,
146        /// How many addresses to copy.
147        length: usize,
148        /// Copy to here.
149        destination: Destination,
150    },
151    /// Copy data from a range of addresses, into another range of addresses.
152    /// The first address in the source range is the length (how many addresses to copy).
153    /// If that address contains a uint, that uint is the length.
154    /// If that address contains a List/Object header, the `size` field is the length.
155    /// Source range is evaluated before destination range (this is only relevant if both source
156    /// and destination come from the stack).
157    CopyLen {
158        /// Start copying from this address.
159        source_range: Operand,
160        /// Start copying into this address.
161        destination_range: Operand,
162    },
163    /// Write the SketchGroup to its special storage.
164    SketchGroupSet {
165        /// What to write.
166        sketch_group: sketch_types::SketchGroup,
167        /// Index into the SketchGroup storage vec.
168        destination: usize,
169    },
170    /// Add a path to a SketchGroup.
171    SketchGroupAddSegment {
172        /// Address of a PathSegment which will be added to the SketchGroup.
173        segment: InMemory,
174        /// Where the SketchGroup to modify begins.
175        /// This is an index into the `SketchGroup` storage of the memory.
176        source: usize,
177        /// Where the modified SketchGroup should be written to.
178        destination: usize,
179    },
180    /// Set the base path of a SketchGroup.
181    SketchGroupSetBasePath {
182        /// Where the SketchGroup to modify begins.
183        /// This is an index into the `SketchGroup` storage of the memory.
184        source: usize,
185        /// Where the base path starts.
186        from: InMemory,
187        /// Where the base path ends.
188        to: InMemory,
189        /// The name of the base path.
190        name: Option<InMemory>,
191    },
192    /// Copy data from a SketchGroup.
193    SketchGroupCopyFrom {
194        /// Index into the SketchGroup array.
195        source: usize,
196        /// Which offset into the SketchGroup's Vec<Primitive> should copying start at?
197        offset: usize,
198        /// How many primitives should be copied?
199        length: usize,
200        /// Where to copy them to.
201        destination: Destination,
202    },
203    /// Get the `to` end of the last path segment, i.e. the point from which the next segment will start.
204    SketchGroupGetLastPoint {
205        /// Which SketchGroup to examine.
206        source: usize,
207        /// Where to copy the data.
208        destination: Destination,
209    },
210    /// Does nothing. Used for debugging.
211    NoOp {
212        /// Debug message.
213        comment: String,
214    },
215    /// Transform the response of an API call to ImportFiles,
216    /// into an OkWebSocketResponse::ImportGeometry.
217    TransformImportFiles {
218        /// Where the API response was stored. Read first.
219        source_import_files_response: InMemory,
220        /// Where the filenames are stored. Read second.
221        source_file_paths: InMemory,
222        /// Where to write the `ImportGeometry`.
223        destination: Destination,
224    },
225}
226
227impl InstructionKind {
228    /// Execute the instruction
229    pub async fn execute(
230        self,
231        mem: &mut Memory,
232        session: &mut Option<kittycad_modeling_session::Session>,
233        events: &mut EventWriter,
234        batch_queue: &mut ModelingBatch,
235    ) -> Result<()> {
236        match self {
237            Self::NoOp { comment: _ } => {}
238            Self::ApiRequest(req) => {
239                if let Some(session) = session {
240                    req.execute(session, mem, events, batch_queue).await?;
241                } else {
242                    return Err(ExecutionError::NoApiClient);
243                }
244            }
245            Self::ImportFiles(req) => {
246                req.execute(mem).await?;
247            }
248            Self::SetPrimitive { address, value } => {
249                events.push(Event {
250                    text: format!("Writing output to address {address}"),
251                    severity: crate::events::Severity::Info,
252                    related_addresses: vec![address],
253                });
254                mem.set(address, value);
255            }
256            Self::Copy {
257                source,
258                length,
259                destination,
260            } => {
261                let sources: Vec<_> = (0..length).map(|i| source + i).collect();
262                // Read the value
263                events.push(Event {
264                    text: "Reading value".to_owned(),
265                    severity: Severity::Debug,
266                    related_addresses: sources.clone(),
267                });
268
269                let data = sources
270                    .iter()
271                    .map(|i| mem.get(i).cloned().ok_or(ExecutionError::MemoryEmpty { addr: source }))
272                    .collect::<Result<Vec<_>>>()?;
273                write_to_dst(data, destination, mem, events)?;
274            }
275            Self::SetValue { address, value_parts } => {
276                value_parts.into_iter().enumerate().for_each(|(i, part)| {
277                    mem.set(address.offset(i), part);
278                });
279            }
280            Self::BinaryArithmetic {
281                arithmetic,
282                destination,
283            } => {
284                let out = arithmetic.calculate(mem, events)?;
285                match destination {
286                    Destination::Address(addr) => {
287                        events.push(Event {
288                            text: format!("Writing output to address {addr}"),
289                            severity: crate::events::Severity::Info,
290                            related_addresses: vec![addr],
291                        });
292                        mem.set(addr, out);
293                    }
294                    Destination::StackPush => {
295                        mem.stack.push(vec![out]);
296                    }
297                    Destination::StackExtend => {
298                        mem.stack.extend(vec![out])?;
299                    }
300                };
301            }
302            Self::UnaryArithmetic {
303                arithmetic,
304                destination,
305            } => {
306                let out = arithmetic.calculate(mem, events)?;
307                match destination {
308                    Destination::Address(addr) => mem.set(addr, out),
309                    Destination::StackPush => mem.stack.push(vec![out]),
310                    Destination::StackExtend => mem.stack.extend(vec![out])?,
311                };
312            }
313            Self::SetList { start, elements } => {
314                // Store size of list.
315                let mut curr = start;
316                curr += 1;
317                let n = elements.len();
318                for element in elements {
319                    // Store each element's size
320                    mem.set(curr, element.len().into());
321                    curr += 1;
322                    // Then store each primitive of the element.
323                    for primitive in element {
324                        mem.set(curr, primitive);
325                        curr += 1
326                    }
327                }
328                mem.set(
329                    start,
330                    Primitive::from(ListHeader {
331                        count: n,
332                        size: (curr - start) - 1,
333                    }),
334                );
335            }
336            Self::AddrOfMember { start, member } => {
337                // Read the member.
338                let member_primitive: Primitive = match member {
339                    Operand::Literal(p) => p,
340                    Operand::Reference(addr) => mem.get(&addr).ok_or(ExecutionError::MemoryEmpty { addr })?.clone(),
341                    Operand::StackPop => mem.stack.pop_single()?,
342                };
343                events.push(Event {
344                    text: format!("Property is '{member_primitive:?}'"),
345                    severity: Severity::Debug,
346                    related_addresses: Vec::new(),
347                });
348
349                // Read the structure.
350                events.push(Event {
351                    text: format!("Resolving start address {start:?}"),
352                    severity: Severity::Debug,
353                    related_addresses: Vec::new(),
354                });
355                let start_address = match start {
356                    Operand::Literal(Primitive::Address(a)) => a,
357                    Operand::Literal(other) => {
358                        return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
359                            expected: "address",
360                            actual: format!("{other:?}"),
361                        }))
362                    }
363                    Operand::Reference(addr) => mem.get_primitive(&addr)?,
364                    Operand::StackPop => {
365                        let data = mem.stack.pop_single()?;
366                        data.try_into()?
367                    }
368                };
369                events.push(Event {
370                    text: "Resolved start address".to_owned(),
371                    severity: Severity::Debug,
372                    related_addresses: vec![start_address],
373                });
374                let structure = mem
375                    .get(&start_address)
376                    .cloned()
377                    .ok_or(ExecutionError::MemoryEmpty { addr: start_address })?;
378
379                // Look up the member in this structure. What number member is it?
380                let (index, member_display) = match structure {
381                    // Structure is an array
382                    Primitive::ListHeader(ListHeader { count, size: _ }) => match member_primitive {
383                        Primitive::NumericValue(NumericPrimitive::Integer(i)) if i >= 0 => {
384                            let i = i as usize;
385                            // Bounds check
386                            if i < count {
387                                events.push(Event {
388                                    text: format!("Property is index {i}"),
389                                    severity: Severity::Info,
390                                    related_addresses: Vec::new(),
391                                });
392                                (i, i.to_string())
393                            } else {
394                                return Err(ExecutionError::ListIndexOutOfBounds { count, index: i });
395                            }
396                        }
397                        Primitive::NumericValue(NumericPrimitive::UInteger(i)) => {
398                            // Bounds check
399                            if i < count {
400                                events.push(Event {
401                                    text: format!("Property is index {i}"),
402                                    severity: Severity::Info,
403                                    related_addresses: Vec::new(),
404                                });
405                                (i, i.to_string())
406                            } else {
407                                return Err(ExecutionError::ListIndexOutOfBounds { count, index: i });
408                            }
409                        }
410                        other_index => {
411                            return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
412                                expected: "uint",
413                                actual: format!("{other_index:?}"),
414                            }));
415                        }
416                    },
417                    // Structure is an object
418                    Primitive::ObjectHeader(ObjectHeader { properties, size: _ }) => match member_primitive {
419                        Primitive::String(s) => {
420                            // Property check
421                            if let Some(i) = properties.iter().position(|prop| prop == &s) {
422                                events.push(Event {
423                                    text: format!("Property is index {i}"),
424                                    severity: Severity::Info,
425                                    related_addresses: Vec::new(),
426                                });
427                                (i, s.clone())
428                            } else {
429                                return Err(ExecutionError::UndefinedProperty {
430                                    property: s,
431                                    address: start_address,
432                                });
433                            }
434                        }
435                        other_index => {
436                            return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
437                                expected: "uint",
438                                actual: format!("{other_index:?}"),
439                            }))
440                        }
441                    },
442                    other_structure => {
443                        return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
444                            expected: "list or object header",
445                            actual: format!("{other_structure:?}"),
446                        }))
447                    }
448                };
449
450                // Find the address of the given member.
451                let mut curr = start_address + 1;
452                for _ in 0..index {
453                    let size_of_element: usize = match mem.get(&curr).ok_or(MemoryError::MemoryWrongSize)? {
454                        Primitive::NumericValue(NumericPrimitive::UInteger(size)) => *size,
455                        Primitive::ListHeader(ListHeader { count: _, size }) => *size,
456                        Primitive::ObjectHeader(ObjectHeader { properties: _, size }) => *size,
457                        other => {
458                            return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
459                                expected: "ListHeader, ObjectHeader, or usize",
460                                actual: format!("{other:?}"),
461                            }))
462                        }
463                    };
464                    curr += size_of_element + 1;
465                }
466                events.push(Event {
467                    text: format!("Member '{member_display}' begins at addr {curr}"),
468                    severity: crate::events::Severity::Info,
469                    related_addresses: vec![curr],
470                });
471                // Push the member onto the stack.
472                // This first address will be its length.
473                // The length is followed by that many addresses worth of data.
474                mem.stack.push(vec![Primitive::Address(curr)]);
475            }
476            Self::StackPush { data } => {
477                mem.stack.push(data);
478            }
479            Self::StackExtend { data } => {
480                mem.stack.extend(data)?;
481            }
482            Self::StackPop { destination } => {
483                let data = mem.stack.pop()?;
484                let Some(destination) = destination else { return Ok(()) };
485                write_to_dst(data, destination, mem, events)?;
486            }
487            Self::CopyLen {
488                source_range,
489                destination_range,
490            } => {
491                let src_addr = match source_range.eval(mem)? {
492                    Primitive::Address(a) => a,
493                    other => {
494                        return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
495                            expected: "address",
496                            actual: format!("{other:?}"),
497                        }))
498                    }
499                };
500                let dst_addr = match destination_range.eval(mem)? {
501                    Primitive::Address(a) => a,
502                    other => {
503                        return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
504                            expected: "address",
505                            actual: format!("{other:?}"),
506                        }))
507                    }
508                };
509
510                let len = match mem
511                    .get(&src_addr)
512                    .ok_or(ExecutionError::MemoryEmpty { addr: src_addr })?
513                {
514                    Primitive::NumericValue(NumericPrimitive::UInteger(n)) => n,
515                    Primitive::ObjectHeader(ObjectHeader { size, .. }) => size,
516                    Primitive::ListHeader(ListHeader { size, .. }) => size,
517                    other => {
518                        return Err(ExecutionError::MemoryError(MemoryError::MemoryWrongType {
519                            expected: "uint or obj/list header",
520                            actual: format!("{other:?}"),
521                        }))
522                    }
523                };
524                for i in 0..*len {
525                    let src = src_addr + i + 1;
526                    let dst = dst_addr + i;
527                    let val = mem.get(&src).ok_or(ExecutionError::MemoryEmpty { addr: src })?;
528                    mem.set(dst, val.clone());
529                }
530            }
531            Self::SketchGroupSet {
532                sketch_group,
533                destination,
534            } => {
535                mem.sketch_group_set(sketch_group, destination)?;
536            }
537            Self::SketchGroupSetBasePath { source, from, to, name } => {
538                let mut sg = mem
539                    .sketch_groups
540                    .get(source)
541                    .ok_or(ExecutionError::NoSketchGroup { index: source })?
542                    .clone();
543                let from: Point2d<f64> = mem.get_in_memory(from, "from", events)?.0;
544                let to: Point2d<f64> = mem.get_in_memory(to, "to", events)?.0;
545                let name: String = match name {
546                    Some(name) => mem.get_in_memory(name, "name", events)?.0,
547                    None => String::new(),
548                };
549                let base_path = sketch_types::BasePath { from, to, name };
550                sg.path_first = base_path;
551                mem.sketch_group_set(sg, source)?;
552            }
553            Self::SketchGroupAddSegment {
554                segment,
555                source,
556                destination,
557            } => {
558                let mut sg = mem
559                    .sketch_groups
560                    .get(source)
561                    .ok_or(ExecutionError::NoSketchGroup { index: source })?
562                    .clone();
563                let (segment, _count) = mem.get_in_memory(segment, "segment", events)?;
564                sg.path_rest.push(segment);
565                mem.sketch_group_set(sg, destination)?;
566            }
567            Self::SketchGroupGetLastPoint { source, destination } => {
568                let sg = mem
569                    .sketch_groups
570                    .get(source)
571                    .ok_or(ExecutionError::NoSketchGroup { index: source })?
572                    .clone();
573                let p = sg.last_point();
574                write_to_dst(p.into_parts(), destination, mem, events)?;
575            }
576            Self::SketchGroupCopyFrom {
577                source,
578                offset,
579                length,
580                destination,
581            } => {
582                let sg = mem
583                    .sketch_groups
584                    .get(source)
585                    .ok_or(ExecutionError::NoSketchGroup { index: source })?
586                    .clone()
587                    .into_parts();
588                let data = sg.into_iter().skip(offset).take(length).collect();
589                write_to_dst(data, destination, mem, events)?;
590            }
591            Self::TransformImportFiles {
592                source_import_files_response,
593                source_file_paths,
594                destination,
595            } => {
596                let resp: OkModelingCmdResponse = mem
597                    .get_in_memory(source_import_files_response, "import files response", events)?
598                    .0;
599                let OkModelingCmdResponse::ImportFiles(resp) = resp else {
600                    return Err(ExecutionError::General {
601                        reason: "Should have been ::ImportFiles variant".to_owned(),
602                    });
603                };
604                let filepaths: Vec<String> = mem.get_in_memory(source_file_paths, "import files response", events)?.0;
605                let geometry = OkModelingCmdResponse::ImportedGeometry(ImportedGeometry {
606                    id: resp.object_id,
607                    value: filepaths,
608                });
609                write_to_dst(geometry.into_parts(), destination, mem, events)?;
610            }
611        }
612        Ok(())
613    }
614}
615
616fn write_to_dst(
617    data: Vec<Primitive>,
618    destination: Destination,
619    mem: &mut Memory,
620    events: &mut EventWriter,
621) -> std::result::Result<(), MemoryError> {
622    match destination {
623        Destination::Address(dst) => {
624            events.push(Event {
625                text: "Writing value".to_owned(),
626                severity: Severity::Debug,
627                related_addresses: (0..data.len()).map(|i| dst + i).collect(),
628            });
629            for (i, v) in data.into_iter().enumerate() {
630                mem.set(dst + i, v);
631            }
632            Ok(())
633        }
634        Destination::StackPush => {
635            mem.stack.push(data);
636            Ok(())
637        }
638        Destination::StackExtend => mem.stack.extend(data),
639    }
640}