Compare commits

...

4 Commits

13 changed files with 1955 additions and 809 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,69 @@
use crate::primitive::{NumericPrimitive, Primitive};
use crate::{ExecutionError, Memory, Operand, Operation};
use serde::{Deserialize, Serialize};
/// Instruction to perform arithmetic on values in memory.
#[derive(Deserialize, Serialize)]
pub struct Arithmetic {
/// Apply this operation
pub operation: Operation,
/// First operand for the operation
pub operand0: Operand,
/// Second operand for the operation
pub operand1: Operand,
}
macro_rules! arithmetic_body {
($arith:ident, $mem:ident, $method:ident) => {
match (
$arith.operand0.eval(&$mem)?.clone(),
$arith.operand1.eval(&$mem)?.clone(),
) {
// If both operands are numeric, then do the arithmetic operation.
(Primitive::NumericValue(x), Primitive::NumericValue(y)) => {
let num = match (x, y) {
(NumericPrimitive::Integer(x), NumericPrimitive::Integer(y)) => {
NumericPrimitive::Integer(x.$method(y))
}
(NumericPrimitive::Integer(x), NumericPrimitive::Float(y)) => {
NumericPrimitive::Float((x as f64).$method(y))
}
(NumericPrimitive::Float(x), NumericPrimitive::Integer(y)) => {
NumericPrimitive::Float(x.$method(y as f64))
}
(NumericPrimitive::Float(x), NumericPrimitive::Float(y)) => NumericPrimitive::Float(x.$method(y)),
};
Ok(Primitive::NumericValue(num))
}
// This operation can only be done on numeric types.
_ => Err(ExecutionError::CannotApplyOperation {
op: $arith.operation,
operands: vec![
$arith.operand0.eval(&$mem)?.clone().to_owned(),
$arith.operand1.eval(&$mem)?.clone().to_owned(),
],
}),
}
};
}
impl Arithmetic {
/// Calculate the the arithmetic equation.
/// May read values from the given memory.
pub fn calculate(self, mem: &Memory) -> Result<Primitive, ExecutionError> {
use std::ops::{Add, Div, Mul, Sub};
match self.operation {
Operation::Add => {
arithmetic_body!(self, mem, add)
}
Operation::Mul => {
arithmetic_body!(self, mem, mul)
}
Operation::Sub => {
arithmetic_body!(self, mem, sub)
}
Operation::Div => {
arithmetic_body!(self, mem, div)
}
}
}
}

View File

@ -1,13 +0,0 @@
use crate::{ExecutionError, Value};
mod impls;
/// Types that can be written to or read from KCEP program memory,
/// but require multiple values to store.
/// They get laid out into multiple consecutive memory addresses.
pub trait Composite: Sized {
/// Store the value in memory.
fn into_parts(self) -> Vec<Value>;
/// Read the value from memory.
fn from_parts(values: &[Option<Value>]) -> Result<Self, ExecutionError>;
}

View File

@ -6,19 +6,22 @@
//! You can think of it as a domain-specific language for making KittyCAD API calls and using
//! the results to make other API calls.
use composite::Composite;
use self::arithmetic::Arithmetic;
use self::primitive::Primitive;
use serde::{Deserialize, Serialize};
use std::fmt;
use uuid::Uuid;
use value::Value;
mod composite;
mod arithmetic;
mod primitive;
#[cfg(test)]
mod tests;
mod value;
/// KCEP's program memory. A flat, linear list of values.
#[derive(Debug)]
#[cfg_attr(test, derive(PartialEq))]
pub struct Memory(Vec<Option<Value>>);
pub struct Memory(Vec<Option<Primitive>>);
impl Default for Memory {
fn default() -> Self {
@ -44,12 +47,12 @@ impl From<usize> for Address {
impl Memory {
/// Get a value from KCEP's program memory.
pub fn get(&self, Address(addr): &Address) -> Option<&Value> {
pub fn get(&self, Address(addr): &Address) -> Option<&Primitive> {
self.0[*addr].as_ref()
}
/// Store a value in KCEP's program memory.
pub fn set(&mut self, Address(addr): Address, value: Value) {
pub fn set(&mut self, Address(addr): Address, value: Primitive) {
// If isn't big enough for this value, double the size of memory until it is.
while addr > self.0.len() {
self.0.extend(vec![None; self.0.len()]);
@ -57,113 +60,30 @@ impl Memory {
self.0[addr] = Some(value);
}
/// Store a composite value (i.e. a value which takes up multiple addresses in memory).
/// Store a value value (i.e. a value which takes up multiple addresses in memory).
/// Store its parts in consecutive memory addresses starting at `start`.
pub fn set_composite<T: Composite>(&mut self, composite_value: T, start: Address) {
pub fn set_composite<T: Value>(&mut self, composite_value: T, start: Address) {
let parts = composite_value.into_parts().into_iter();
for (value, addr) in parts.zip(start.0..) {
self.0[addr] = Some(value);
}
}
/// Get a composite value (i.e. a value which takes up multiple addresses in memory).
/// Get a value value (i.e. a value which takes up multiple addresses in memory).
/// Its parts are stored in consecutive memory addresses starting at `start`.
pub fn get_composite<T: Composite>(&self, start: Address) -> Result<T, ExecutionError> {
pub fn get_composite<T: Value>(&self, start: Address) -> Result<T, ExecutionError> {
let values = &self.0[start.0..];
T::from_parts(values)
}
}
/// A value stored in KCEP program memory.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Value {
String(String),
NumericValue(NumericValue),
Uuid(Uuid),
}
impl From<Uuid> for Value {
fn from(u: Uuid) -> Self {
Self::Uuid(u)
}
}
impl From<String> for Value {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<f64> for Value {
fn from(value: f64) -> Self {
Self::NumericValue(NumericValue::Float(value))
}
}
impl TryFrom<Value> for String {
type Error = ExecutionError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
if let Value::String(s) = value {
Ok(s)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "string",
actual: format!("{value:?}"),
})
}
}
}
impl TryFrom<Value> for Uuid {
type Error = ExecutionError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
if let Value::Uuid(u) = value {
Ok(u)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "uuid",
actual: format!("{value:?}"),
})
}
}
}
impl TryFrom<Value> for f64 {
type Error = ExecutionError;
fn try_from(value: Value) -> Result<Self, Self::Error> {
if let Value::NumericValue(NumericValue::Float(x)) = value {
Ok(x)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "float",
actual: format!("{value:?}"),
})
}
}
}
#[cfg(test)]
impl From<usize> for Value {
fn from(value: usize) -> Self {
Self::NumericValue(NumericValue::Integer(value))
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum NumericValue {
Integer(usize),
Float(f64),
}
/// One step of the execution plan.
#[derive(Serialize, Deserialize)]
pub enum Instruction {
/// Call the KittyCAD API.
ApiRequest {
/// Which ModelingCmd to call.
/// It's a composite value starting at the given address.
/// It's a value value starting at the given address.
endpoint: Address,
/// Which address should the response be stored in?
store_response: Option<usize>,
@ -175,7 +95,7 @@ pub enum Instruction {
/// Which memory address to set.
address: Address,
/// What value to set the memory address to.
value: Value,
value: Primitive,
},
/// Perform arithmetic on values in memory.
Arithmetic {
@ -186,66 +106,6 @@ pub enum Instruction {
},
}
/// Instruction to perform arithmetic on values in memory.
#[derive(Deserialize, Serialize)]
pub struct Arithmetic {
/// Apply this operation
pub operation: Operation,
/// First operand for the operation
pub operand0: Operand,
/// Second operand for the operation
pub operand1: Operand,
}
macro_rules! arithmetic_body {
($arith:ident, $mem:ident, $method:ident) => {
match (
$arith.operand0.eval(&$mem)?.clone(),
$arith.operand1.eval(&$mem)?.clone(),
) {
// If both operands are numeric, then do the arithmetic operation.
(Value::NumericValue(x), Value::NumericValue(y)) => {
let num = match (x, y) {
(NumericValue::Integer(x), NumericValue::Integer(y)) => NumericValue::Integer(x.$method(y)),
(NumericValue::Integer(x), NumericValue::Float(y)) => NumericValue::Float((x as f64).$method(y)),
(NumericValue::Float(x), NumericValue::Integer(y)) => NumericValue::Float(x.$method(y as f64)),
(NumericValue::Float(x), NumericValue::Float(y)) => NumericValue::Float(x.$method(y)),
};
Ok(Value::NumericValue(num))
}
// This operation can only be done on numeric types.
_ => Err(ExecutionError::CannotApplyOperation {
op: $arith.operation,
operands: vec![
$arith.operand0.eval(&$mem)?.clone().to_owned(),
$arith.operand1.eval(&$mem)?.clone().to_owned(),
],
}),
}
};
}
impl Arithmetic {
/// Calculate the the arithmetic equation.
/// May read values from the given memory.
fn calculate(self, mem: &Memory) -> Result<Value, ExecutionError> {
use std::ops::{Add, Div, Mul, Sub};
match self.operation {
Operation::Add => {
arithmetic_body!(self, mem, add)
}
Operation::Mul => {
arithmetic_body!(self, mem, mul)
}
Operation::Sub => {
arithmetic_body!(self, mem, sub)
}
Operation::Div => {
arithmetic_body!(self, mem, div)
}
}
}
}
/// Operations that can be applied to values in memory.
#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
pub enum Operation {
@ -270,13 +130,13 @@ impl fmt::Display for Operation {
/// Argument to an operation.
#[derive(Deserialize, Serialize)]
pub enum Operand {
Literal(Value),
Literal(Primitive),
Reference(Address),
}
impl Operand {
/// Evaluate the operand, getting its value.
fn eval(&self, mem: &Memory) -> Result<Value, ExecutionError> {
fn eval(&self, mem: &Memory) -> Result<Primitive, ExecutionError> {
match self {
Operand::Literal(v) => Ok(v.to_owned()),
Operand::Reference(addr) => match mem.get(addr) {
@ -313,7 +173,7 @@ pub enum ExecutionError {
#[error("Memory address {addr} was not set")]
MemoryEmpty { addr: Address },
#[error("Cannot apply operation {op} to operands {operands:?}")]
CannotApplyOperation { op: Operation, operands: Vec<Value> },
CannotApplyOperation { op: Operation, operands: Vec<Primitive> },
#[error("Tried to read a '{expected}' from KCEP program memory, found an '{actual}' instead")]
MemoryWrongType { expected: &'static str, actual: String },
#[error("Wanted {expected} values but did not get enough")]

View File

@ -0,0 +1,98 @@
use crate::ExecutionError;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
/// A value stored in KCEP program memory.
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum Primitive {
String(String),
NumericValue(NumericPrimitive),
Uuid(Uuid),
}
impl From<Uuid> for Primitive {
fn from(u: Uuid) -> Self {
Self::Uuid(u)
}
}
impl From<String> for Primitive {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<f64> for Primitive {
fn from(value: f64) -> Self {
Self::NumericValue(NumericPrimitive::Float(value))
}
}
impl TryFrom<Primitive> for String {
type Error = ExecutionError;
fn try_from(value: Primitive) -> Result<Self, Self::Error> {
if let Primitive::String(s) = value {
Ok(s)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "string",
actual: format!("{value:?}"),
})
}
}
}
impl TryFrom<Primitive> for Uuid {
type Error = ExecutionError;
fn try_from(value: Primitive) -> Result<Self, Self::Error> {
if let Primitive::Uuid(u) = value {
Ok(u)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "uuid",
actual: format!("{value:?}"),
})
}
}
}
impl TryFrom<Primitive> for f64 {
type Error = ExecutionError;
fn try_from(value: Primitive) -> Result<Self, Self::Error> {
if let Primitive::NumericValue(NumericPrimitive::Float(x)) = value {
Ok(x)
} else {
Err(ExecutionError::MemoryWrongType {
expected: "float",
actual: format!("{value:?}"),
})
}
}
}
#[cfg(test)]
impl From<usize> for Primitive {
fn from(value: usize) -> Self {
Self::NumericValue(NumericPrimitive::Integer(value))
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
pub enum NumericPrimitive {
Integer(usize),
Float(f64),
}
impl crate::value::Value for Primitive {
fn into_parts(self) -> Vec<Primitive> {
vec![self]
}
fn from_parts(values: &[Option<Primitive>]) -> Result<Self, ExecutionError> {
let v = values.get(0).ok_or(ExecutionError::MemoryWrongSize { expected: 1 })?;
v.to_owned().ok_or(ExecutionError::MemoryWrongSize { expected: 1 })
}
}

View File

@ -1,4 +1,5 @@
use kittycad_modeling_cmds::{id::ModelingCmdId, shared::Point3d, ModelingCmd, MovePathPen};
use uuid::Uuid;
use super::*;

View File

@ -0,0 +1,13 @@
use crate::{ExecutionError, Primitive};
mod impls;
/// Types that can be written to or read from KCEP program memory.
/// If they require multiple memory addresses, they will be laid out
/// into multiple consecutive memory addresses.
pub trait Value: Sized {
/// Store the value in memory.
fn into_parts(self) -> Vec<Primitive>;
/// Read the value from memory.
fn from_parts(values: &[Option<Primitive>]) -> Result<Self, ExecutionError>;
}

View File

@ -1,21 +1,21 @@
use kittycad_modeling_cmds::{id::ModelingCmdId, shared::Point3d, MovePathPen};
use uuid::Uuid;
use crate::{Address, ExecutionError, Value};
use crate::{Address, ExecutionError, Primitive};
use super::Composite;
use super::Value;
impl<T> Composite for kittycad_modeling_cmds::shared::Point3d<T>
impl<T> Value for kittycad_modeling_cmds::shared::Point3d<T>
where
Value: From<T>,
T: TryFrom<Value, Error = ExecutionError>,
Primitive: From<T>,
T: TryFrom<Primitive, Error = ExecutionError>,
{
fn into_parts(self) -> Vec<Value> {
fn into_parts(self) -> Vec<Primitive> {
let points = [self.x, self.y, self.z];
points.into_iter().map(|component| component.into()).collect()
}
fn from_parts(values: &[Option<Value>]) -> Result<Self, ExecutionError> {
fn from_parts(values: &[Option<Primitive>]) -> Result<Self, ExecutionError> {
let err = ExecutionError::MemoryWrongSize { expected: 3 };
let [x, y, z] = [0, 1, 2].map(|n| values.get(n).ok_or(err.clone()));
let x = x?.to_owned().ok_or(err.clone())?.try_into()?;
@ -28,17 +28,17 @@ where
const START_PATH: &str = "StartPath";
const MOVE_PATH_PEN: &str = "MovePathPen";
impl Composite for MovePathPen {
fn into_parts(self) -> Vec<Value> {
impl Value for MovePathPen {
fn into_parts(self) -> Vec<Primitive> {
let MovePathPen { path, to } = self;
let to = to.into_parts();
let mut vals = Vec::with_capacity(1 + to.len());
vals.push(Value::Uuid(path.into()));
vals.push(Primitive::Uuid(path.into()));
vals.extend(to);
vals
}
fn from_parts(values: &[Option<Value>]) -> Result<Self, ExecutionError> {
fn from_parts(values: &[Option<Primitive>]) -> Result<Self, ExecutionError> {
let path = get_some(values, 0)?;
let path = Uuid::try_from(path)?;
let path = ModelingCmdId::from(path);
@ -51,14 +51,14 @@ impl Composite for MovePathPen {
/// Memory layout for modeling commands:
/// Field 0 is the command's name.
/// Fields 1 onwards are the command's fields.
impl Composite for kittycad_modeling_cmds::ModelingCmd {
fn into_parts(self) -> Vec<Value> {
impl Value for kittycad_modeling_cmds::ModelingCmd {
fn into_parts(self) -> Vec<Primitive> {
let (endpoint_name, params) = match self {
kittycad_modeling_cmds::ModelingCmd::StartPath => (START_PATH, vec![]),
kittycad_modeling_cmds::ModelingCmd::MovePathPen(MovePathPen { path, to }) => {
let to = to.into_parts();
let mut vals = Vec::with_capacity(1 + to.len());
vals.push(Value::Uuid(path.into()));
vals.push(Primitive::Uuid(path.into()));
vals.extend(to);
(MOVE_PATH_PEN, vals)
}
@ -70,7 +70,7 @@ impl Composite for kittycad_modeling_cmds::ModelingCmd {
out
}
fn from_parts(values: &[Option<Value>]) -> Result<Self, ExecutionError> {
fn from_parts(values: &[Option<Primitive>]) -> Result<Self, ExecutionError> {
// Check the array has an element at index 0
let first_memory = values
.get(0)
@ -90,7 +90,7 @@ impl Composite for kittycad_modeling_cmds::ModelingCmd {
}
}
fn get_some(values: &[Option<Value>], i: usize) -> Result<Value, ExecutionError> {
fn get_some(values: &[Option<Primitive>], i: usize) -> Result<Primitive, ExecutionError> {
let addr = Address(0); // TODO: pass the `start` addr in
let v = values.get(i).ok_or(ExecutionError::MemoryEmpty { addr })?.to_owned();
let v = v.ok_or(ExecutionError::MemoryEmpty { addr })?.to_owned();