2023-08-24 15:34:51 -07:00
|
|
|
|
//! The executor for the AST.
|
|
|
|
|
|
2023-11-08 20:23:59 -06:00
|
|
|
|
use std::{collections::HashMap, sync::Arc};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
use anyhow::Result;
|
2023-11-09 09:58:20 -06:00
|
|
|
|
use async_recursion::async_recursion;
|
2023-10-05 14:27:48 -07:00
|
|
|
|
use parse_display::{Display, FromStr};
|
2023-08-25 13:41:04 -07:00
|
|
|
|
use schemars::JsonSchema;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use serde::{Deserialize, Serialize};
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
use serde_json::Value as JValue;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
use tower_lsp::lsp_types::{Position as LspPosition, Range as LspRange};
|
2023-08-25 13:41:04 -07:00
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use crate::{
|
2024-06-24 14:45:07 -07:00
|
|
|
|
ast::types::{BodyItem, FunctionExpression, KclNone, Program, TagDeclarator, Value},
|
2024-03-13 12:56:46 -07:00
|
|
|
|
engine::EngineManager,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
errors::{KclError, KclErrorDetails},
|
2024-02-12 12:18:37 -08:00
|
|
|
|
fs::FileManager,
|
2024-06-18 14:38:25 -05:00
|
|
|
|
settings::types::UnitLength,
|
2024-07-28 23:49:28 -07:00
|
|
|
|
std::{FnAsArg, StdLib},
|
2023-08-24 15:34:51 -07:00
|
|
|
|
};
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ProgramMemory {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
pub environments: Vec<Environment>,
|
|
|
|
|
pub current_env: EnvironmentRef,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "return")]
|
|
|
|
|
pub return_: Option<ProgramReturn>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ProgramMemory {
|
|
|
|
|
pub fn new() -> Self {
|
|
|
|
|
Self {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
environments: vec![Environment::root()],
|
|
|
|
|
current_env: EnvironmentRef::root(),
|
|
|
|
|
return_: None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn new_env_for_call(&mut self, parent: EnvironmentRef) -> EnvironmentRef {
|
|
|
|
|
let new_env_ref = EnvironmentRef(self.environments.len());
|
|
|
|
|
let new_env = Environment::new(parent);
|
|
|
|
|
self.environments.push(new_env);
|
|
|
|
|
new_env_ref
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Add to the program memory in the current scope.
|
|
|
|
|
pub fn add(&mut self, key: &str, value: MemoryItem, source_range: SourceRange) -> Result<(), KclError> {
|
|
|
|
|
if self.environments[self.current_env.index()].contains_key(key) {
|
|
|
|
|
return Err(KclError::ValueAlreadyDefined(KclErrorDetails {
|
|
|
|
|
message: format!("Cannot redefine `{}`", key),
|
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
self.environments[self.current_env.index()].insert(key.to_string(), value);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub fn update_tag(&mut self, tag: &str, value: TagIdentifier) -> Result<(), KclError> {
|
|
|
|
|
self.environments[self.current_env.index()].insert(tag.to_string(), MemoryItem::TagIdentifier(Box::new(value)));
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
/// Get a value from the program memory.
|
|
|
|
|
/// Return Err if not found.
|
|
|
|
|
pub fn get(&self, var: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
|
|
|
|
let mut env_ref = self.current_env;
|
|
|
|
|
loop {
|
|
|
|
|
let env = &self.environments[env_ref.index()];
|
|
|
|
|
if let Some(item) = env.bindings.get(var) {
|
|
|
|
|
return Ok(item);
|
|
|
|
|
}
|
|
|
|
|
if let Some(parent) = env.parent {
|
|
|
|
|
env_ref = parent;
|
|
|
|
|
} else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(KclError::UndefinedValue(KclErrorDetails {
|
|
|
|
|
message: format!("memory item key `{}` is not defined", var),
|
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Find all extrude groups in the memory that are on a specific sketch group id.
|
|
|
|
|
/// This does not look inside closures. But as long as we do not allow
|
|
|
|
|
/// mutation of variables in KCL, closure memory should be a subset of this.
|
|
|
|
|
pub fn find_extrude_groups_on_sketch_group(&self, sketch_group_id: uuid::Uuid) -> Vec<Box<ExtrudeGroup>> {
|
|
|
|
|
self.environments
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|env| {
|
|
|
|
|
env.bindings
|
|
|
|
|
.values()
|
|
|
|
|
.filter_map(|item| match item {
|
|
|
|
|
MemoryItem::ExtrudeGroup(eg) if eg.sketch_group.id == sketch_group_id => Some(eg.clone()),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ProgramMemory {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An index pointing to an environment.
|
|
|
|
|
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
pub struct EnvironmentRef(usize);
|
|
|
|
|
|
|
|
|
|
impl EnvironmentRef {
|
|
|
|
|
pub fn root() -> Self {
|
|
|
|
|
Self(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn index(&self) -> usize {
|
|
|
|
|
self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
pub struct Environment {
|
|
|
|
|
bindings: HashMap<String, MemoryItem>,
|
|
|
|
|
parent: Option<EnvironmentRef>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Environment {
|
|
|
|
|
pub fn root() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
// Prelude
|
|
|
|
|
bindings: HashMap::from([
|
2024-02-11 18:26:09 -08:00
|
|
|
|
(
|
|
|
|
|
"ZERO".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(0)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"QUARTER_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(90)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"HALF_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(180)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"THREE_QUARTER_TURN".to_string(),
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: serde_json::Value::Number(serde_json::value::Number::from(270)),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
}),
|
|
|
|
|
),
|
|
|
|
|
]),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
parent: None,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
pub fn new(parent: EnvironmentRef) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
bindings: HashMap::new(),
|
|
|
|
|
parent: Some(parent),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get(&self, key: &str, source_range: SourceRange) -> Result<&MemoryItem, KclError> {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
self.bindings.get(key).ok_or_else(|| {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
KclError::UndefinedValue(KclErrorDetails {
|
|
|
|
|
message: format!("memory item key `{}` is not defined", key),
|
|
|
|
|
source_ranges: vec![source_range],
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-06-23 19:19:24 -07:00
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
pub fn insert(&mut self, key: String, value: MemoryItem) {
|
|
|
|
|
self.bindings.insert(key, value);
|
2024-06-23 19:19:24 -07:00
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
pub fn contains_key(&self, key: &str) -> bool {
|
|
|
|
|
self.bindings.contains_key(key)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase", untagged)]
|
|
|
|
|
pub enum ProgramReturn {
|
2024-03-05 14:53:29 -06:00
|
|
|
|
Arguments,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Value(MemoryItem),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ProgramReturn> for Vec<SourceRange> {
|
|
|
|
|
fn from(item: ProgramReturn) -> Self {
|
|
|
|
|
match item {
|
2024-03-05 14:53:29 -06:00
|
|
|
|
ProgramReturn::Arguments => Default::default(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
ProgramReturn::Value(v) => v.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ProgramReturn {
|
|
|
|
|
pub fn get_value(&self) -> Result<MemoryItem, KclError> {
|
|
|
|
|
match self {
|
|
|
|
|
ProgramReturn::Value(v) => Ok(v.clone()),
|
2024-03-05 14:53:29 -06:00
|
|
|
|
ProgramReturn::Arguments => Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: "Cannot get value from arguments".to_owned(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 15:08:54 -08:00
|
|
|
|
/// A memory item.
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub enum MemoryItem {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
UserVal(UserVal),
|
2024-06-24 14:45:07 -07:00
|
|
|
|
TagIdentifier(Box<TagIdentifier>),
|
|
|
|
|
TagDeclarator(Box<TagDeclarator>),
|
2023-10-05 14:27:48 -07:00
|
|
|
|
Plane(Box<Plane>),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
Face(Box<Face>),
|
2023-09-19 14:20:14 -07:00
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
SketchGroups {
|
|
|
|
|
value: Vec<Box<SketchGroup>>,
|
|
|
|
|
},
|
2023-09-19 14:20:14 -07:00
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
ExtrudeGroups {
|
|
|
|
|
value: Vec<Box<ExtrudeGroup>>,
|
|
|
|
|
},
|
2024-02-12 12:18:37 -08:00
|
|
|
|
ImportedGeometry(ImportedGeometry),
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(skip)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Function {
|
|
|
|
|
#[serde(skip)]
|
|
|
|
|
func: Option<MemoryFunction>,
|
|
|
|
|
expression: Box<FunctionExpression>,
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory: Box<ProgramMemory>,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
meta: Vec<Metadata>,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-21 16:44:31 -07:00
|
|
|
|
impl MemoryItem {
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn get_sketch_group_set(&self) -> Result<SketchGroupSet> {
|
2024-06-21 16:44:31 -07:00
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::SketchGroup(s) => Ok(SketchGroupSet::SketchGroup(s.clone())),
|
|
|
|
|
MemoryItem::SketchGroups { value } => Ok(SketchGroupSet::SketchGroups(value.clone())),
|
|
|
|
|
MemoryItem::UserVal(value) => {
|
|
|
|
|
let sg: Vec<Box<SketchGroup>> = serde_json::from_value(value.value.clone())
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to deserialize array of sketch groups from JSON: {}", e))?;
|
2024-06-23 23:04:32 -07:00
|
|
|
|
Ok(sg.into())
|
2024-06-21 16:44:31 -07:00
|
|
|
|
}
|
|
|
|
|
_ => anyhow::bail!("Not a sketch group or sketch groups: {:?}", self),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn get_extrude_group_set(&self) -> Result<ExtrudeGroupSet> {
|
2024-06-21 16:44:31 -07:00
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::ExtrudeGroup(e) => Ok(ExtrudeGroupSet::ExtrudeGroup(e.clone())),
|
|
|
|
|
MemoryItem::ExtrudeGroups { value } => Ok(ExtrudeGroupSet::ExtrudeGroups(value.clone())),
|
|
|
|
|
MemoryItem::UserVal(value) => {
|
|
|
|
|
let eg: Vec<Box<ExtrudeGroup>> = serde_json::from_value(value.value.clone())
|
|
|
|
|
.map_err(|e| anyhow::anyhow!("Failed to deserialize array of extrude groups from JSON: {}", e))?;
|
2024-06-23 23:04:32 -07:00
|
|
|
|
Ok(eg.into())
|
2024-06-21 16:44:31 -07:00
|
|
|
|
}
|
|
|
|
|
_ => anyhow::bail!("Not a extrude group or extrude groups: {:?}", self),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 23:04:32 -07:00
|
|
|
|
impl From<SketchGroupSet> for MemoryItem {
|
|
|
|
|
fn from(sg: SketchGroupSet) -> Self {
|
|
|
|
|
match sg {
|
|
|
|
|
SketchGroupSet::SketchGroup(sg) => MemoryItem::SketchGroup(sg),
|
|
|
|
|
SketchGroupSet::SketchGroups(sgs) => MemoryItem::SketchGroups { value: sgs },
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<Box<SketchGroup>>> for MemoryItem {
|
|
|
|
|
fn from(sg: Vec<Box<SketchGroup>>) -> Self {
|
|
|
|
|
if sg.len() == 1 {
|
|
|
|
|
MemoryItem::SketchGroup(sg[0].clone())
|
|
|
|
|
} else {
|
|
|
|
|
MemoryItem::SketchGroups { value: sg }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ExtrudeGroupSet> for MemoryItem {
|
|
|
|
|
fn from(eg: ExtrudeGroupSet) -> Self {
|
|
|
|
|
match eg {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(eg) => MemoryItem::ExtrudeGroup(eg),
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroups(egs) => MemoryItem::ExtrudeGroups { value: egs },
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<Box<ExtrudeGroup>>> for MemoryItem {
|
|
|
|
|
fn from(eg: Vec<Box<ExtrudeGroup>>) -> Self {
|
|
|
|
|
if eg.len() == 1 {
|
|
|
|
|
MemoryItem::ExtrudeGroup(eg[0].clone())
|
|
|
|
|
} else {
|
|
|
|
|
MemoryItem::ExtrudeGroups { value: eg }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 15:08:54 -08:00
|
|
|
|
/// A geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
|
pub enum Geometry {
|
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Geometry {
|
|
|
|
|
pub fn id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
Geometry::SketchGroup(s) => s.id,
|
|
|
|
|
Geometry::ExtrudeGroup(e) => e.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A set of geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type")]
|
|
|
|
|
pub enum Geometries {
|
|
|
|
|
SketchGroups(Vec<Box<SketchGroup>>),
|
|
|
|
|
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A sketch group or a group of sketch groups.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum SketchGroupSet {
|
|
|
|
|
SketchGroup(Box<SketchGroup>),
|
|
|
|
|
SketchGroups(Vec<Box<SketchGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 23:04:32 -07:00
|
|
|
|
impl From<SketchGroup> for SketchGroupSet {
|
|
|
|
|
fn from(sg: SketchGroup) -> Self {
|
|
|
|
|
SketchGroupSet::SketchGroup(Box::new(sg))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Box<SketchGroup>> for SketchGroupSet {
|
|
|
|
|
fn from(sg: Box<SketchGroup>) -> Self {
|
|
|
|
|
SketchGroupSet::SketchGroup(sg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<SketchGroup>> for SketchGroupSet {
|
|
|
|
|
fn from(sg: Vec<SketchGroup>) -> Self {
|
|
|
|
|
if sg.len() == 1 {
|
|
|
|
|
SketchGroupSet::SketchGroup(Box::new(sg[0].clone()))
|
|
|
|
|
} else {
|
|
|
|
|
SketchGroupSet::SketchGroups(sg.into_iter().map(Box::new).collect())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<Box<SketchGroup>>> for SketchGroupSet {
|
|
|
|
|
fn from(sg: Vec<Box<SketchGroup>>) -> Self {
|
|
|
|
|
if sg.len() == 1 {
|
|
|
|
|
SketchGroupSet::SketchGroup(sg[0].clone())
|
|
|
|
|
} else {
|
|
|
|
|
SketchGroupSet::SketchGroups(sg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<SketchGroupSet> for Vec<Box<SketchGroup>> {
|
|
|
|
|
fn from(sg: SketchGroupSet) -> Self {
|
|
|
|
|
match sg {
|
|
|
|
|
SketchGroupSet::SketchGroup(sg) => vec![sg],
|
|
|
|
|
SketchGroupSet::SketchGroups(sgs) => sgs,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&SketchGroup> for Vec<Box<SketchGroup>> {
|
|
|
|
|
fn from(sg: &SketchGroup) -> Self {
|
|
|
|
|
vec![Box::new(sg.clone())]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Box<SketchGroup>> for Vec<Box<SketchGroup>> {
|
|
|
|
|
fn from(sg: Box<SketchGroup>) -> Self {
|
|
|
|
|
vec![sg]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-23 10:31:20 -07:00
|
|
|
|
/// A extrude group or a group of extrude groups.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum ExtrudeGroupSet {
|
|
|
|
|
ExtrudeGroup(Box<ExtrudeGroup>),
|
|
|
|
|
ExtrudeGroups(Vec<Box<ExtrudeGroup>>),
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-23 23:04:32 -07:00
|
|
|
|
impl From<ExtrudeGroup> for ExtrudeGroupSet {
|
|
|
|
|
fn from(eg: ExtrudeGroup) -> Self {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(Box::new(eg))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Box<ExtrudeGroup>> for ExtrudeGroupSet {
|
|
|
|
|
fn from(eg: Box<ExtrudeGroup>) -> Self {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(eg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<ExtrudeGroup>> for ExtrudeGroupSet {
|
|
|
|
|
fn from(eg: Vec<ExtrudeGroup>) -> Self {
|
|
|
|
|
if eg.len() == 1 {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(Box::new(eg[0].clone()))
|
|
|
|
|
} else {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroups(eg.into_iter().map(Box::new).collect())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Vec<Box<ExtrudeGroup>>> for ExtrudeGroupSet {
|
|
|
|
|
fn from(eg: Vec<Box<ExtrudeGroup>>) -> Self {
|
|
|
|
|
if eg.len() == 1 {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(eg[0].clone())
|
|
|
|
|
} else {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroups(eg)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<ExtrudeGroupSet> for Vec<Box<ExtrudeGroup>> {
|
|
|
|
|
fn from(eg: ExtrudeGroupSet) -> Self {
|
|
|
|
|
match eg {
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroup(eg) => vec![eg],
|
|
|
|
|
ExtrudeGroupSet::ExtrudeGroups(egs) => egs,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<&ExtrudeGroup> for Vec<Box<ExtrudeGroup>> {
|
|
|
|
|
fn from(eg: &ExtrudeGroup) -> Self {
|
|
|
|
|
vec![Box::new(eg.clone())]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Box<ExtrudeGroup>> for Vec<Box<ExtrudeGroup>> {
|
|
|
|
|
fn from(eg: Box<ExtrudeGroup>) -> Self {
|
|
|
|
|
vec![eg]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
/// Data for an imported geometry.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ImportedGeometry {
|
|
|
|
|
/// The ID of the imported geometry.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
/// The original file paths.
|
|
|
|
|
pub value: Vec<String>,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// A plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Plane {
|
|
|
|
|
/// The id of the plane.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
// The code for the plane either a string or custom.
|
|
|
|
|
pub value: PlaneType,
|
|
|
|
|
/// Origin of the plane.
|
|
|
|
|
pub origin: Point3d,
|
|
|
|
|
/// What should the plane’s X axis be?
|
|
|
|
|
pub x_axis: Point3d,
|
|
|
|
|
/// What should the plane’s Y axis be?
|
|
|
|
|
pub y_axis: Point3d,
|
|
|
|
|
/// The z-axis (normal).
|
|
|
|
|
pub z_axis: Point3d,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-15 17:18:32 -07:00
|
|
|
|
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct DefaultPlanes {
|
|
|
|
|
pub xy: uuid::Uuid,
|
|
|
|
|
pub xz: uuid::Uuid,
|
|
|
|
|
pub yz: uuid::Uuid,
|
|
|
|
|
pub neg_xy: uuid::Uuid,
|
|
|
|
|
pub neg_xz: uuid::Uuid,
|
|
|
|
|
pub neg_yz: uuid::Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// A face.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Face {
|
|
|
|
|
/// The id of the face.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
/// The tag of the face.
|
|
|
|
|
pub value: String,
|
|
|
|
|
/// What should the face’s X axis be?
|
|
|
|
|
pub x_axis: Point3d,
|
|
|
|
|
/// What should the face’s Y axis be?
|
|
|
|
|
pub y_axis: Point3d,
|
|
|
|
|
/// The z-axis (normal).
|
|
|
|
|
pub z_axis: Point3d,
|
2024-06-23 19:19:24 -07:00
|
|
|
|
/// The extrude group the face is on.
|
|
|
|
|
pub extrude_group: Box<ExtrudeGroup>,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// Type for a plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
#[display(style = "camelCase")]
|
|
|
|
|
pub enum PlaneType {
|
|
|
|
|
#[serde(rename = "XY", alias = "xy")]
|
|
|
|
|
#[display("XY")]
|
|
|
|
|
XY,
|
|
|
|
|
#[serde(rename = "XZ", alias = "xz")]
|
|
|
|
|
#[display("XZ")]
|
|
|
|
|
XZ,
|
|
|
|
|
#[serde(rename = "YZ", alias = "yz")]
|
|
|
|
|
#[display("YZ")]
|
|
|
|
|
YZ,
|
|
|
|
|
/// A custom plane.
|
|
|
|
|
#[serde(rename = "Custom")]
|
|
|
|
|
#[display("Custom")]
|
|
|
|
|
Custom,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub struct UserVal {
|
2024-01-11 15:31:35 -08:00
|
|
|
|
#[ts(type = "any")]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
pub value: serde_json::Value,
|
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2024-06-24 14:45:07 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub struct TagIdentifier {
|
|
|
|
|
pub value: String,
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub info: Option<TagEngineInfo>,
|
2024-06-24 14:45:07 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
impl Eq for TagIdentifier {}
|
|
|
|
|
|
2024-06-24 14:45:07 -07:00
|
|
|
|
impl std::fmt::Display for TagIdentifier {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", self.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::str::FromStr for TagIdentifier {
|
|
|
|
|
type Err = KclError;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
|
Ok(Self {
|
|
|
|
|
value: s.to_string(),
|
2024-07-27 22:56:46 -07:00
|
|
|
|
info: None,
|
2024-06-24 14:45:07 -07:00
|
|
|
|
meta: Default::default(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Ord for TagIdentifier {
|
|
|
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
|
|
|
self.value.cmp(&other.value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for TagIdentifier {
|
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
|
|
|
Some(self.cmp(other))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl std::hash::Hash for TagIdentifier {
|
|
|
|
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
|
|
|
|
self.value.hash(state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-05 16:53:13 -07:00
|
|
|
|
pub type MemoryFunction =
|
|
|
|
|
fn(
|
|
|
|
|
s: Vec<MemoryItem>,
|
|
|
|
|
memory: ProgramMemory,
|
|
|
|
|
expression: Box<FunctionExpression>,
|
|
|
|
|
metadata: Vec<Metadata>,
|
|
|
|
|
ctx: ExecutorContext,
|
|
|
|
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>> + Send>>;
|
2023-09-20 18:27:08 -07:00
|
|
|
|
|
|
|
|
|
fn force_memory_function<
|
|
|
|
|
F: Fn(
|
|
|
|
|
Vec<MemoryItem>,
|
|
|
|
|
ProgramMemory,
|
|
|
|
|
Box<FunctionExpression>,
|
|
|
|
|
Vec<Metadata>,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
ExecutorContext,
|
2024-07-05 16:53:13 -07:00
|
|
|
|
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Option<ProgramReturn>, KclError>> + Send>>,
|
2023-09-20 18:27:08 -07:00
|
|
|
|
>(
|
|
|
|
|
f: F,
|
|
|
|
|
) -> F {
|
|
|
|
|
f
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
impl From<MemoryItem> for Vec<SourceRange> {
|
|
|
|
|
fn from(item: MemoryItem) -> Self {
|
|
|
|
|
match item {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
MemoryItem::UserVal(u) => u.meta.iter().map(|m| m.source_range).collect(),
|
2024-06-24 14:45:07 -07:00
|
|
|
|
MemoryItem::TagDeclarator(t) => t.into(),
|
|
|
|
|
MemoryItem::TagIdentifier(t) => t.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::SketchGroup(s) => s.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
MemoryItem::SketchGroups { value } => value
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|sg| sg.meta.iter().map(|m| m.source_range))
|
|
|
|
|
.collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::ExtrudeGroup(e) => e.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-11 15:08:54 -08:00
|
|
|
|
MemoryItem::ExtrudeGroups { value } => value
|
|
|
|
|
.iter()
|
|
|
|
|
.flat_map(|eg| eg.meta.iter().map(|m| m.source_range))
|
|
|
|
|
.collect(),
|
2024-02-12 12:18:37 -08:00
|
|
|
|
MemoryItem::ImportedGeometry(i) => i.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
MemoryItem::Function { meta, .. } => meta.iter().map(|m| m.source_range).collect(),
|
2023-10-05 14:27:48 -07:00
|
|
|
|
MemoryItem::Plane(p) => p.meta.iter().map(|m| m.source_range).collect(),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
MemoryItem::Face(f) => f.meta.iter().map(|m| m.source_range).collect(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl MemoryItem {
|
|
|
|
|
pub fn get_json_value(&self) -> Result<serde_json::Value, KclError> {
|
2023-09-12 18:10:27 -07:00
|
|
|
|
if let MemoryItem::UserVal(user_val) = self {
|
|
|
|
|
Ok(user_val.value.clone())
|
2023-08-24 15:34:51 -07:00
|
|
|
|
} else {
|
2023-09-19 16:05:53 -07:00
|
|
|
|
serde_json::to_value(self).map_err(|err| {
|
|
|
|
|
KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Cannot convert memory item to json value: {:?}", err),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
/// Get a JSON value and deserialize it into some concrete type.
|
|
|
|
|
pub fn get_json<T: serde::de::DeserializeOwned>(&self) -> Result<T, KclError> {
|
|
|
|
|
let json = self.get_json_value()?;
|
|
|
|
|
|
|
|
|
|
serde_json::from_value(json).map_err(|e| {
|
|
|
|
|
KclError::Type(KclErrorDetails {
|
|
|
|
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get a JSON value and deserialize it into some concrete type.
|
|
|
|
|
/// If it's a KCL None, return None. Otherwise return Some.
|
|
|
|
|
pub fn get_json_opt<T: serde::de::DeserializeOwned>(&self) -> Result<Option<T>, KclError> {
|
|
|
|
|
let json = self.get_json_value()?;
|
|
|
|
|
if let JValue::Object(ref o) = json {
|
|
|
|
|
if let Some(JValue::String(s)) = o.get("type") {
|
|
|
|
|
if s == "KclNone" {
|
|
|
|
|
return Ok(None);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
serde_json::from_value(json)
|
|
|
|
|
.map_err(|e| {
|
|
|
|
|
KclError::Type(KclErrorDetails {
|
|
|
|
|
message: format!("Failed to deserialize struct from JSON: {}", e),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.map(Some)
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-19 20:30:13 -05:00
|
|
|
|
pub fn as_user_val(&self) -> Option<&UserVal> {
|
2024-06-27 22:20:51 -05:00
|
|
|
|
if let MemoryItem::UserVal(x) = self {
|
|
|
|
|
Some(x)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If this value is of type u32, return it.
|
|
|
|
|
pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
|
|
|
|
|
let err = KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: "Expected an integer >= 0".to_owned(),
|
|
|
|
|
source_ranges,
|
|
|
|
|
});
|
|
|
|
|
self.as_user_val()
|
|
|
|
|
.and_then(|uv| uv.value.as_number())
|
|
|
|
|
.and_then(|n| n.as_u64())
|
|
|
|
|
.and_then(|n| u32::try_from(n).ok())
|
|
|
|
|
.ok_or(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// If this value is of type function, return it.
|
2024-07-19 20:30:13 -05:00
|
|
|
|
pub fn get_function(&self) -> Option<FnAsArg<'_>> {
|
2024-06-27 22:20:51 -05:00
|
|
|
|
let MemoryItem::Function {
|
|
|
|
|
func,
|
|
|
|
|
expression,
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory,
|
2024-06-27 22:20:51 -05:00
|
|
|
|
meta: _,
|
|
|
|
|
} = &self
|
|
|
|
|
else {
|
2024-07-19 20:30:13 -05:00
|
|
|
|
return None;
|
2024-06-27 22:20:51 -05:00
|
|
|
|
};
|
2024-07-19 20:30:13 -05:00
|
|
|
|
let func = func.as_ref()?;
|
|
|
|
|
Some(FnAsArg {
|
2024-06-27 22:20:51 -05:00
|
|
|
|
func,
|
|
|
|
|
expr: expression.to_owned(),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory: memory.to_owned(),
|
2024-06-27 22:20:51 -05:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 17:59:41 -07:00
|
|
|
|
/// Get a tag identifier from a memory item.
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
|
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::TagIdentifier(t) => Ok(*t.clone()),
|
2024-07-27 17:59:41 -07:00
|
|
|
|
MemoryItem::UserVal(_) => {
|
2024-07-05 16:53:13 -07:00
|
|
|
|
if let Some(identifier) = self.get_json_opt::<TagIdentifier>()? {
|
|
|
|
|
Ok(identifier)
|
|
|
|
|
} else {
|
2024-07-27 17:59:41 -07:00
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a tag identifier: {:?}", self),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
}))
|
2024-07-05 16:53:13 -07:00
|
|
|
|
}
|
2024-06-24 14:45:07 -07:00
|
|
|
|
}
|
|
|
|
|
_ => Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a tag identifier: {:?}", self),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 17:59:41 -07:00
|
|
|
|
/// Get a tag declarator from a memory item.
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn get_tag_declarator(&self) -> Result<TagDeclarator, KclError> {
|
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::TagDeclarator(t) => Ok(*t.clone()),
|
|
|
|
|
_ => Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a tag declarator: {:?}", self),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 17:59:41 -07:00
|
|
|
|
/// Get an optional tag from a memory item.
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn get_tag_declarator_opt(&self) -> Result<Option<TagDeclarator>, KclError> {
|
|
|
|
|
match self {
|
|
|
|
|
MemoryItem::TagDeclarator(t) => Ok(Some(*t.clone())),
|
|
|
|
|
_ => Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a tag declarator: {:?}", self),
|
|
|
|
|
source_ranges: self.clone().into(),
|
|
|
|
|
})),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-08 13:44:31 -06:00
|
|
|
|
/// If this memory item is a function, call it with the given arguments, return its val as Ok.
|
|
|
|
|
/// If it's not a function, return Err.
|
2023-09-20 18:27:08 -07:00
|
|
|
|
pub async fn call_fn(
|
2023-08-24 15:34:51 -07:00
|
|
|
|
&self,
|
2023-09-20 18:27:08 -07:00
|
|
|
|
args: Vec<MemoryItem>,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
ctx: ExecutorContext,
|
2024-07-05 16:53:13 -07:00
|
|
|
|
) -> Result<Option<ProgramReturn>, KclError> {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
let MemoryItem::Function {
|
|
|
|
|
func,
|
|
|
|
|
expression,
|
|
|
|
|
memory: closure_memory,
|
|
|
|
|
meta,
|
|
|
|
|
} = &self
|
|
|
|
|
else {
|
2023-11-08 13:44:31 -06:00
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
2023-09-20 18:27:08 -07:00
|
|
|
|
message: "not a in memory function".to_string(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
source_ranges: vec![],
|
2023-11-08 13:44:31 -06:00
|
|
|
|
}));
|
|
|
|
|
};
|
|
|
|
|
let Some(func) = func else {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: format!("Not a function: {:?}", expression),
|
|
|
|
|
source_ranges: vec![],
|
|
|
|
|
}));
|
|
|
|
|
};
|
2024-07-22 19:43:40 -04:00
|
|
|
|
func(
|
|
|
|
|
args,
|
|
|
|
|
closure_memory.as_ref().clone(),
|
|
|
|
|
expression.clone(),
|
|
|
|
|
meta.clone(),
|
|
|
|
|
ctx,
|
|
|
|
|
)
|
|
|
|
|
.await
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
/// Engine information for a tag.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub struct TagEngineInfo {
|
|
|
|
|
/// The id of the tagged object.
|
|
|
|
|
pub id: uuid::Uuid,
|
|
|
|
|
/// The sketch group the tag is on.
|
|
|
|
|
pub sketch_group: uuid::Uuid,
|
|
|
|
|
/// The path the tag is on.
|
2024-07-28 00:30:04 -07:00
|
|
|
|
pub path: Option<BasePath>,
|
2024-07-27 22:56:46 -07:00
|
|
|
|
/// The surface information for the tag.
|
|
|
|
|
pub surface: Option<ExtrudeSurface>,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A sketch group is a collection of paths.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub struct SketchGroup {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the sketch group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The paths in the sketch group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub value: Vec<Path>,
|
2024-02-16 16:42:01 -08:00
|
|
|
|
/// What the sketch is on (can be a plane or a face).
|
|
|
|
|
pub on: SketchSurface,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The starting path.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub start: BasePath,
|
2024-07-05 16:53:13 -07:00
|
|
|
|
/// Tag identifiers that have been declared in this sketch group.
|
|
|
|
|
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
|
|
|
|
|
pub tags: HashMap<String, TagIdentifier>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-16 16:42:01 -08:00
|
|
|
|
/// A sketch group type.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum SketchSurface {
|
|
|
|
|
Plane(Box<Plane>),
|
|
|
|
|
Face(Box<Face>),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl SketchSurface {
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn id(&self) -> uuid::Uuid {
|
2024-02-16 16:42:01 -08:00
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.id,
|
|
|
|
|
SketchSurface::Face(face) => face.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn x_axis(&self) -> Point3d {
|
2024-02-16 16:42:01 -08:00
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.x_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.x_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn y_axis(&self) -> Point3d {
|
2024-02-16 16:42:01 -08:00
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.y_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.y_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn z_axis(&self) -> Point3d {
|
2024-02-16 16:42:01 -08:00
|
|
|
|
match self {
|
|
|
|
|
SketchSurface::Plane(plane) => plane.z_axis.clone(),
|
|
|
|
|
SketchSurface::Face(face) => face.z_axis.clone(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-11 12:59:00 +11:00
|
|
|
|
pub struct GetTangentialInfoFromPathsResult {
|
|
|
|
|
pub center_or_tangent_point: [f64; 2],
|
|
|
|
|
pub is_center: bool,
|
|
|
|
|
pub ccw: bool,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl SketchGroup {
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn add_tag(&mut self, tag: &TagDeclarator, current_path: &Path) {
|
|
|
|
|
let mut tag_identifier: TagIdentifier = tag.into();
|
|
|
|
|
let base = current_path.get_base();
|
|
|
|
|
tag_identifier.info = Some(TagEngineInfo {
|
|
|
|
|
id: base.geo_meta.id,
|
|
|
|
|
sketch_group: self.id,
|
2024-07-28 00:30:04 -07:00
|
|
|
|
path: Some(base.clone()),
|
2024-07-27 22:56:46 -07:00
|
|
|
|
surface: None,
|
|
|
|
|
});
|
2024-06-24 14:45:07 -07:00
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
self.tags.insert(tag.name.to_string(), tag_identifier);
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-05-30 17:48:59 -05:00
|
|
|
|
/// Get the path most recently sketched.
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn latest_path(&self) -> Option<&Path> {
|
2024-05-30 17:48:59 -05:00
|
|
|
|
self.value.last()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The "pen" is an imaginary pen drawing the path.
|
|
|
|
|
/// This gets the current point the pen is hovering over, i.e. the point
|
|
|
|
|
/// where the last path segment ends, and the next path segment will begin.
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn current_pen_position(&self) -> Result<Point2d, KclError> {
|
2024-05-30 17:48:59 -05:00
|
|
|
|
let Some(path) = self.latest_path() else {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return Ok(self.start.to.into());
|
2024-05-30 17:48:59 -05:00
|
|
|
|
};
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
2024-05-30 17:48:59 -05:00
|
|
|
|
let base = path.get_base();
|
|
|
|
|
Ok(base.to.into())
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
2024-02-11 12:59:00 +11:00
|
|
|
|
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
|
2024-05-30 17:48:59 -05:00
|
|
|
|
let Some(path) = self.latest_path() else {
|
2024-02-11 12:59:00 +11:00
|
|
|
|
return GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: self.start.to,
|
|
|
|
|
is_center: false,
|
|
|
|
|
ccw: false,
|
|
|
|
|
};
|
2024-05-30 17:48:59 -05:00
|
|
|
|
};
|
|
|
|
|
match path {
|
|
|
|
|
Path::TangentialArcTo { center, ccw, .. } => GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: *center,
|
|
|
|
|
is_center: true,
|
|
|
|
|
ccw: *ccw,
|
|
|
|
|
},
|
|
|
|
|
_ => {
|
|
|
|
|
let base = path.get_base();
|
|
|
|
|
GetTangentialInfoFromPathsResult {
|
|
|
|
|
center_or_tangent_point: base.from,
|
|
|
|
|
is_center: false,
|
|
|
|
|
ccw: false,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude group is a collection of extrude surfaces.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub struct ExtrudeGroup {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the extrude group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The extrude surfaces.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub value: Vec<ExtrudeSurface>,
|
2024-06-21 23:50:30 -07:00
|
|
|
|
/// The sketch group.
|
|
|
|
|
pub sketch_group: SketchGroup,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The height of the extrude group.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub height: f64,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// The id of the extrusion start cap
|
|
|
|
|
pub start_cap_id: Option<uuid::Uuid>,
|
|
|
|
|
/// The id of the extrusion end cap
|
|
|
|
|
pub end_cap_id: Option<uuid::Uuid>,
|
2024-06-23 19:19:24 -07:00
|
|
|
|
/// Chamfers or fillets on this extrude group.
|
|
|
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
|
|
|
pub fillet_or_chamfers: Vec<FilletOrChamfer>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__meta")]
|
|
|
|
|
pub meta: Vec<Metadata>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ExtrudeGroup {
|
2024-07-27 22:56:46 -07:00
|
|
|
|
pub(crate) fn get_all_fillet_or_chamfer_ids(&self) -> Vec<uuid::Uuid> {
|
2024-06-23 19:19:24 -07:00
|
|
|
|
self.fillet_or_chamfers.iter().map(|foc| foc.id()).collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A fillet or a chamfer.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum FilletOrChamfer {
|
|
|
|
|
/// A fillet.
|
|
|
|
|
Fillet {
|
|
|
|
|
/// The id of the engine command that called this fillet.
|
|
|
|
|
id: uuid::Uuid,
|
|
|
|
|
radius: f64,
|
|
|
|
|
/// The engine id of the edge to fillet.
|
|
|
|
|
edge_id: uuid::Uuid,
|
2024-07-28 00:30:04 -07:00
|
|
|
|
tag: Box<Option<TagDeclarator>>,
|
2024-06-23 19:19:24 -07:00
|
|
|
|
},
|
|
|
|
|
/// A chamfer.
|
|
|
|
|
Chamfer {
|
|
|
|
|
/// The id of the engine command that called this chamfer.
|
|
|
|
|
id: uuid::Uuid,
|
|
|
|
|
length: f64,
|
|
|
|
|
/// The engine id of the edge to chamfer.
|
|
|
|
|
edge_id: uuid::Uuid,
|
2024-07-09 12:24:42 -04:00
|
|
|
|
tag: Box<Option<TagDeclarator>>,
|
2024-06-23 19:19:24 -07:00
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl FilletOrChamfer {
|
|
|
|
|
pub fn id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
FilletOrChamfer::Fillet { id, .. } => *id,
|
|
|
|
|
FilletOrChamfer::Chamfer { id, .. } => *id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn edge_id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
FilletOrChamfer::Fillet { edge_id, .. } => *edge_id,
|
|
|
|
|
FilletOrChamfer::Chamfer { edge_id, .. } => *edge_id,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-06-23 23:04:32 -07:00
|
|
|
|
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn tag(&self) -> Option<TagDeclarator> {
|
2024-06-23 23:04:32 -07:00
|
|
|
|
match self {
|
2024-07-28 00:30:04 -07:00
|
|
|
|
FilletOrChamfer::Fillet { tag, .. } => *tag.clone(),
|
2024-07-09 12:24:42 -04:00
|
|
|
|
FilletOrChamfer::Chamfer { tag, .. } => *tag.clone(),
|
2024-06-23 23:04:32 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub enum BodyType {
|
|
|
|
|
Root,
|
|
|
|
|
Sketch,
|
|
|
|
|
Block,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
#[derive(Debug, Default, Deserialize, Serialize, PartialEq, Copy, Clone, ts_rs::TS, JsonSchema, Hash, Eq)]
|
2024-06-19 17:32:08 -07:00
|
|
|
|
#[cfg_attr(feature = "pyo3", pyo3::pyclass)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2023-09-12 18:10:27 -07:00
|
|
|
|
pub struct SourceRange(#[ts(type = "[number, number]")] pub [usize; 2]);
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
impl SourceRange {
|
|
|
|
|
/// Create a new source range.
|
|
|
|
|
pub fn new(start: usize, end: usize) -> Self {
|
|
|
|
|
Self([start, end])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the start of the range.
|
|
|
|
|
pub fn start(&self) -> usize {
|
|
|
|
|
self.0[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Get the end of the range.
|
|
|
|
|
pub fn end(&self) -> usize {
|
|
|
|
|
self.0[1]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Check if the range contains a position.
|
|
|
|
|
pub fn contains(&self, pos: usize) -> bool {
|
|
|
|
|
pos >= self.start() && pos <= self.end()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn start_to_lsp_position(&self, code: &str) -> LspPosition {
|
|
|
|
|
// Calculate the line and column of the error from the source range.
|
|
|
|
|
// Lines are zero indexed in vscode so we need to subtract 1.
|
2024-06-21 15:06:01 -07:00
|
|
|
|
let mut line = code.get(..self.start()).unwrap_or_default().lines().count();
|
2023-09-05 16:02:27 -07:00
|
|
|
|
if line > 0 {
|
|
|
|
|
line = line.saturating_sub(1);
|
|
|
|
|
}
|
|
|
|
|
let column = code[..self.start()].lines().last().map(|l| l.len()).unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
LspPosition {
|
|
|
|
|
line: line as u32,
|
|
|
|
|
character: column as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn end_to_lsp_position(&self, code: &str) -> LspPosition {
|
2024-06-21 15:06:01 -07:00
|
|
|
|
let lines = code.get(..self.end()).unwrap_or_default().lines();
|
2024-04-15 17:18:32 -07:00
|
|
|
|
if lines.clone().count() == 0 {
|
|
|
|
|
return LspPosition { line: 0, character: 0 };
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-05 16:02:27 -07:00
|
|
|
|
// Calculate the line and column of the error from the source range.
|
|
|
|
|
// Lines are zero indexed in vscode so we need to subtract 1.
|
2024-04-15 17:18:32 -07:00
|
|
|
|
let line = lines.clone().count() - 1;
|
|
|
|
|
let column = lines.last().map(|l| l.len()).unwrap_or_default();
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
|
|
LspPosition {
|
|
|
|
|
line: line as u32,
|
|
|
|
|
character: column as u32,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn to_lsp_range(&self, code: &str) -> LspRange {
|
|
|
|
|
let start = self.start_to_lsp_position(code);
|
|
|
|
|
let end = self.end_to_lsp_position(code);
|
|
|
|
|
LspRange { start, end }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 15:51:26 -06:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
pub struct Point2d {
|
|
|
|
|
pub x: f64,
|
|
|
|
|
pub y: f64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<[f64; 2]> for Point2d {
|
|
|
|
|
fn from(p: [f64; 2]) -> Self {
|
|
|
|
|
Self { x: p[0], y: p[1] }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
|
impl From<&[f64; 2]> for Point2d {
|
|
|
|
|
fn from(p: &[f64; 2]) -> Self {
|
|
|
|
|
Self { x: p[0], y: p[1] }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl From<Point2d> for [f64; 2] {
|
|
|
|
|
fn from(p: Point2d) -> Self {
|
|
|
|
|
[p.x, p.y]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-31 22:19:23 -07:00
|
|
|
|
impl From<Point2d> for kittycad::types::Point2D {
|
|
|
|
|
fn from(p: Point2d) -> Self {
|
|
|
|
|
Self { x: p.x, y: p.y }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-14 15:51:26 -06:00
|
|
|
|
impl Point2d {
|
|
|
|
|
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
|
|
|
|
|
pub fn scale(self, scalar: f64) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
x: self.x * scalar,
|
|
|
|
|
y: self.y * scalar,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-04 16:28:32 -05:00
|
|
|
|
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
pub struct Point3d {
|
|
|
|
|
pub x: f64,
|
|
|
|
|
pub y: f64,
|
|
|
|
|
pub z: f64,
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
impl Point3d {
|
|
|
|
|
pub fn new(x: f64, y: f64, z: f64) -> Self {
|
|
|
|
|
Self { x, y, z }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<Point3d> for kittycad::types::Point3D {
|
|
|
|
|
fn from(p: Point3d) -> Self {
|
|
|
|
|
Self { x: p.x, y: p.y, z: p.z }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2024-06-24 14:45:07 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct Metadata {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The source range.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub source_range: SourceRange,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<SourceRange> for Metadata {
|
|
|
|
|
fn from(source_range: SourceRange) -> Self {
|
|
|
|
|
Self { source_range }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A base path.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct BasePath {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The from point.
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(type = "[number, number]")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub from: [f64; 2],
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The to point.
|
2023-09-12 18:10:27 -07:00
|
|
|
|
#[ts(type = "[number, number]")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub to: [f64; 2],
|
2024-06-24 14:45:07 -07:00
|
|
|
|
/// The tag of the path.
|
|
|
|
|
pub tag: Option<TagDeclarator>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(rename = "__geoMeta")]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Geometry metadata.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct GeoMeta {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The id of the geometry.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub id: uuid::Uuid,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// Metadata.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub metadata: Metadata,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
2024-02-11 12:59:00 +11:00
|
|
|
|
#[serde(tag = "type")]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub enum Path {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path that goes to a point.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
ToPoint {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
2024-02-11 12:59:00 +11:00
|
|
|
|
/// A arc that is tangential to the last path segment that goes to a point
|
|
|
|
|
TangentialArcTo {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
/// the arc's center
|
|
|
|
|
#[ts(type = "[number, number]")]
|
|
|
|
|
center: [f64; 2],
|
|
|
|
|
/// arc's direction
|
|
|
|
|
ccw: bool,
|
|
|
|
|
},
|
2024-02-22 19:07:17 -08:00
|
|
|
|
/// A arc that is tangential to the last path segment
|
|
|
|
|
TangentialArc {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A path that is horizontal.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Horizontal {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The x coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
x: f64,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An angled line to.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
AngledLineTo {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The x coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
x: Option<f64>,
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// The y coordinate.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
y: Option<f64>,
|
|
|
|
|
},
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// A base path.
|
2023-08-24 15:34:51 -07:00
|
|
|
|
Base {
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
base: BasePath,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Path {
|
|
|
|
|
pub fn get_id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => base.geo_meta.id,
|
|
|
|
|
Path::Horizontal { base, .. } => base.geo_meta.id,
|
|
|
|
|
Path::AngledLineTo { base, .. } => base.geo_meta.id,
|
|
|
|
|
Path::Base { base } => base.geo_meta.id,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
Path::TangentialArcTo { base, .. } => base.geo_meta.id,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => base.geo_meta.id,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn get_tag(&self) -> Option<TagDeclarator> {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
match self {
|
2024-06-24 14:45:07 -07:00
|
|
|
|
Path::ToPoint { base } => base.tag.clone(),
|
|
|
|
|
Path::Horizontal { base, .. } => base.tag.clone(),
|
|
|
|
|
Path::AngledLineTo { base, .. } => base.tag.clone(),
|
|
|
|
|
Path::Base { base } => base.tag.clone(),
|
|
|
|
|
Path::TangentialArcTo { base, .. } => base.tag.clone(),
|
|
|
|
|
Path::TangentialArc { base } => base.tag.clone(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn get_base(&self) -> &BasePath {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => base,
|
|
|
|
|
Path::Horizontal { base, .. } => base,
|
|
|
|
|
Path::AngledLineTo { base, .. } => base,
|
|
|
|
|
Path::Base { base } => base,
|
2024-02-11 12:59:00 +11:00
|
|
|
|
Path::TangentialArcTo { base, .. } => base,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => base,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-12 18:08:42 -08:00
|
|
|
|
|
|
|
|
|
pub fn get_base_mut(&mut self) -> Option<&mut BasePath> {
|
|
|
|
|
match self {
|
|
|
|
|
Path::ToPoint { base } => Some(base),
|
|
|
|
|
Path::Horizontal { base, .. } => Some(base),
|
|
|
|
|
Path::AngledLineTo { base, .. } => Some(base),
|
|
|
|
|
Path::Base { base } => Some(base),
|
|
|
|
|
Path::TangentialArcTo { base, .. } => Some(base),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
Path::TangentialArc { base } => Some(base),
|
2024-02-12 18:08:42 -08:00
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude surface.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(tag = "type", rename_all = "camelCase")]
|
|
|
|
|
pub enum ExtrudeSurface {
|
2023-08-25 13:41:04 -07:00
|
|
|
|
/// An extrude plane.
|
2024-02-12 18:08:42 -08:00
|
|
|
|
ExtrudePlane(ExtrudePlane),
|
2024-02-22 19:07:17 -08:00
|
|
|
|
ExtrudeArc(ExtrudeArc),
|
2024-07-28 00:30:04 -07:00
|
|
|
|
Chamfer(ChamferSurface),
|
|
|
|
|
Fillet(FilletSurface),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Chamfer surface.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ChamferSurface {
|
|
|
|
|
/// The id for the chamfer surface.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
|
|
|
|
/// The tag.
|
|
|
|
|
pub tag: Option<TagDeclarator>,
|
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fillet surface.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct FilletSurface {
|
|
|
|
|
/// The id for the fillet surface.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
|
|
|
|
/// The tag.
|
|
|
|
|
pub tag: Option<TagDeclarator>,
|
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// An extruded plane.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ExtrudePlane {
|
|
|
|
|
/// The face id for the extrude plane.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
2024-06-24 14:45:07 -07:00
|
|
|
|
/// The tag.
|
|
|
|
|
pub tag: Option<TagDeclarator>,
|
2024-02-12 18:08:42 -08:00
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-02-22 19:07:17 -08:00
|
|
|
|
/// An extruded arc.
|
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ExtrudeArc {
|
|
|
|
|
/// The face id for the extrude plane.
|
|
|
|
|
pub face_id: uuid::Uuid,
|
2024-06-24 14:45:07 -07:00
|
|
|
|
/// The tag.
|
|
|
|
|
pub tag: Option<TagDeclarator>,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
/// Metadata.
|
|
|
|
|
#[serde(flatten)]
|
|
|
|
|
pub geo_meta: GeoMeta,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
impl ExtrudeSurface {
|
|
|
|
|
pub fn get_id(&self) -> uuid::Uuid {
|
|
|
|
|
match self {
|
2024-02-12 18:08:42 -08:00
|
|
|
|
ExtrudeSurface::ExtrudePlane(ep) => ep.geo_meta.id,
|
2024-02-22 19:07:17 -08:00
|
|
|
|
ExtrudeSurface::ExtrudeArc(ea) => ea.geo_meta.id,
|
2024-07-28 00:30:04 -07:00
|
|
|
|
ExtrudeSurface::Fillet(f) => f.geo_meta.id,
|
|
|
|
|
ExtrudeSurface::Chamfer(c) => c.geo_meta.id,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-24 14:45:07 -07:00
|
|
|
|
pub fn get_tag(&self) -> Option<TagDeclarator> {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
match self {
|
2024-06-24 14:45:07 -07:00
|
|
|
|
ExtrudeSurface::ExtrudePlane(ep) => ep.tag.clone(),
|
|
|
|
|
ExtrudeSurface::ExtrudeArc(ea) => ea.tag.clone(),
|
2024-07-28 00:30:04 -07:00
|
|
|
|
ExtrudeSurface::Fillet(f) => f.tag.clone(),
|
|
|
|
|
ExtrudeSurface::Chamfer(c) => c.tag.clone(),
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-25 13:41:04 -07:00
|
|
|
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[ts(export)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct PipeInfo {
|
2024-03-28 13:11:09 -05:00
|
|
|
|
pub previous_results: Option<MemoryItem>,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PipeInfo {
|
|
|
|
|
pub fn new() -> Self {
|
2024-03-28 13:11:09 -05:00
|
|
|
|
Self { previous_results: None }
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for PipeInfo {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self::new()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
/// The executor context.
|
2024-06-06 16:01:41 -05:00
|
|
|
|
/// Cloning will return another handle to the same engine connection/session,
|
|
|
|
|
/// as this uses `Arc` under the hood.
|
2023-10-05 14:27:48 -07:00
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ExecutorContext {
|
2024-03-13 12:56:46 -07:00
|
|
|
|
pub engine: Arc<Box<dyn EngineManager>>,
|
2024-04-15 17:18:32 -07:00
|
|
|
|
pub fs: Arc<FileManager>,
|
2023-11-08 20:23:59 -06:00
|
|
|
|
pub stdlib: Arc<StdLib>,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
pub settings: ExecutorSettings,
|
2024-03-26 19:32:31 -07:00
|
|
|
|
/// Mock mode is only for the modeling app when they just want to mock engine calls and not
|
|
|
|
|
/// actually make them.
|
|
|
|
|
pub is_mock: bool,
|
2023-10-05 14:27:48 -07:00
|
|
|
|
}
|
|
|
|
|
|
2024-04-25 00:13:09 -07:00
|
|
|
|
/// The executor settings.
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
pub struct ExecutorSettings {
|
|
|
|
|
/// The unit to use in modeling dimensions.
|
2024-06-18 14:38:25 -05:00
|
|
|
|
pub units: UnitLength,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
/// Highlight edges of 3D objects?
|
|
|
|
|
pub highlight_edges: bool,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
/// Whether or not Screen Space Ambient Occlusion (SSAO) is enabled.
|
|
|
|
|
pub enable_ssao: bool,
|
2024-07-05 12:39:58 -07:00
|
|
|
|
// Show grid?
|
|
|
|
|
pub show_grid: bool,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Default for ExecutorSettings {
|
|
|
|
|
fn default() -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: Default::default(),
|
|
|
|
|
highlight_edges: true,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
enable_ssao: false,
|
2024-07-05 12:39:58 -07:00
|
|
|
|
show_grid: false,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::Configuration> for ExecutorSettings {
|
|
|
|
|
fn from(config: crate::settings::types::Configuration) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: config.settings.modeling.base_unit,
|
|
|
|
|
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
2024-07-05 12:39:58 -07:00
|
|
|
|
show_grid: config.settings.modeling.show_scale_grid,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::project::ProjectConfiguration> for ExecutorSettings {
|
|
|
|
|
fn from(config: crate::settings::types::project::ProjectConfiguration) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: config.settings.modeling.base_unit,
|
|
|
|
|
highlight_edges: config.settings.modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: config.settings.modeling.enable_ssao.into(),
|
2024-07-05 12:39:58 -07:00
|
|
|
|
show_grid: config.settings.modeling.show_scale_grid,
|
2024-04-25 02:31:18 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<crate::settings::types::ModelingSettings> for ExecutorSettings {
|
|
|
|
|
fn from(modeling: crate::settings::types::ModelingSettings) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
units: modeling.base_unit,
|
|
|
|
|
highlight_edges: modeling.highlight_edges.into(),
|
|
|
|
|
enable_ssao: modeling.enable_ssao.into(),
|
2024-07-05 12:39:58 -07:00
|
|
|
|
show_grid: modeling.show_scale_grid,
|
2024-04-25 00:13:09 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
impl ExecutorContext {
|
|
|
|
|
/// Create a new default executor context.
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2024-04-25 02:31:18 -07:00
|
|
|
|
pub async fn new(client: &kittycad::Client, settings: ExecutorSettings) -> Result<Self> {
|
|
|
|
|
let ws = client
|
|
|
|
|
.modeling()
|
|
|
|
|
.commands_ws(
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
if settings.enable_ssao {
|
|
|
|
|
Some(kittycad::types::PostEffectType::Ssao)
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
},
|
2024-07-05 12:39:58 -07:00
|
|
|
|
if settings.show_grid { Some(true) } else { None },
|
2024-04-25 02:31:18 -07:00
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
None,
|
|
|
|
|
Some(false),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let engine: Arc<Box<dyn EngineManager>> =
|
|
|
|
|
Arc::new(Box::new(crate::engine::conn::EngineConnection::new(ws).await?));
|
|
|
|
|
|
|
|
|
|
// Set the edge visibility.
|
|
|
|
|
engine
|
2024-06-19 13:57:50 -07:00
|
|
|
|
.batch_modeling_cmd(
|
2024-04-25 02:31:18 -07:00
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
2024-06-19 13:57:50 -07:00
|
|
|
|
&kittycad::types::ModelingCmd::EdgeLinesVisible {
|
2024-04-25 02:31:18 -07:00
|
|
|
|
hidden: !settings.highlight_edges,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2024-02-12 12:18:37 -08:00
|
|
|
|
Ok(Self {
|
2024-04-25 02:31:18 -07:00
|
|
|
|
engine,
|
2024-04-15 17:18:32 -07:00
|
|
|
|
fs: Arc::new(FileManager::new()),
|
2024-02-12 12:18:37 -08:00
|
|
|
|
stdlib: Arc::new(StdLib::new()),
|
2024-04-25 00:13:09 -07:00
|
|
|
|
settings,
|
2024-03-26 19:32:31 -07:00
|
|
|
|
is_mock: false,
|
2024-02-12 12:18:37 -08:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-18 14:38:25 -05:00
|
|
|
|
/// For executing unit tests.
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
|
pub async fn new_for_unit_test(units: UnitLength) -> Result<Self> {
|
|
|
|
|
let user_agent = concat!(env!("CARGO_PKG_NAME"), ".rs/", env!("CARGO_PKG_VERSION"),);
|
|
|
|
|
let http_client = reqwest::Client::builder()
|
|
|
|
|
.user_agent(user_agent)
|
|
|
|
|
// For file conversions we need this to be long.
|
|
|
|
|
.timeout(std::time::Duration::from_secs(600))
|
|
|
|
|
.connect_timeout(std::time::Duration::from_secs(60));
|
|
|
|
|
let ws_client = reqwest::Client::builder()
|
|
|
|
|
.user_agent(user_agent)
|
|
|
|
|
// For file conversions we need this to be long.
|
|
|
|
|
.timeout(std::time::Duration::from_secs(600))
|
|
|
|
|
.connect_timeout(std::time::Duration::from_secs(60))
|
|
|
|
|
.connection_verbose(true)
|
|
|
|
|
.tcp_keepalive(std::time::Duration::from_secs(600))
|
|
|
|
|
.http1_only();
|
|
|
|
|
|
|
|
|
|
let token = std::env::var("KITTYCAD_API_TOKEN").expect("KITTYCAD_API_TOKEN not set");
|
|
|
|
|
|
|
|
|
|
// Create the client.
|
|
|
|
|
let mut client = kittycad::Client::new_from_reqwest(token, http_client, ws_client);
|
|
|
|
|
// Set a local engine address if it's set.
|
|
|
|
|
if let Ok(addr) = std::env::var("LOCAL_ENGINE_ADDR") {
|
|
|
|
|
client.set_base_url(addr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let ctx = ExecutorContext::new(
|
|
|
|
|
&client,
|
|
|
|
|
ExecutorSettings {
|
|
|
|
|
units,
|
|
|
|
|
highlight_edges: true,
|
|
|
|
|
enable_ssao: false,
|
2024-07-05 12:39:58 -07:00
|
|
|
|
show_grid: false,
|
2024-06-18 14:38:25 -05:00
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(ctx)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Clear everything in the scene.
|
|
|
|
|
pub async fn reset_scene(&self) -> Result<()> {
|
|
|
|
|
self.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::SceneClearAll {},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Perform the execution of a program.
|
|
|
|
|
/// You can optionally pass in some initialization memory.
|
|
|
|
|
/// Kurt uses this for partial execution.
|
|
|
|
|
pub async fn run(
|
|
|
|
|
&self,
|
2024-06-26 14:51:47 -07:00
|
|
|
|
program: &crate::ast::types::Program,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
memory: Option<ProgramMemory>,
|
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
// Before we even start executing the program, set the units.
|
|
|
|
|
self.engine
|
2024-06-19 13:57:50 -07:00
|
|
|
|
.batch_modeling_cmd(
|
2024-04-12 21:32:57 -07:00
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
SourceRange::default(),
|
2024-06-19 13:57:50 -07:00
|
|
|
|
&kittycad::types::ModelingCmd::SetSceneUnits {
|
2024-06-19 17:32:08 -07:00
|
|
|
|
unit: self.settings.units.into(),
|
2024-04-12 21:32:57 -07:00
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
let mut memory = if let Some(memory) = memory {
|
|
|
|
|
memory.clone()
|
|
|
|
|
} else {
|
|
|
|
|
Default::default()
|
|
|
|
|
};
|
|
|
|
|
self.inner_execute(program, &mut memory, crate::executor::BodyType::Root)
|
|
|
|
|
.await
|
|
|
|
|
}
|
2024-02-20 17:55:06 -08:00
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Execute an AST's program.
|
|
|
|
|
#[async_recursion]
|
|
|
|
|
pub(crate) async fn inner_execute(
|
|
|
|
|
&self,
|
2024-06-26 14:51:47 -07:00
|
|
|
|
program: &crate::ast::types::Program,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
memory: &mut ProgramMemory,
|
2024-05-22 18:50:54 -05:00
|
|
|
|
body_type: BodyType,
|
2024-04-12 21:32:57 -07:00
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
let pipe_info = PipeInfo::default();
|
|
|
|
|
|
|
|
|
|
// Iterate over the body of the program.
|
|
|
|
|
for statement in &program.body {
|
|
|
|
|
match statement {
|
|
|
|
|
BodyItem::ExpressionStatement(expression_statement) => {
|
|
|
|
|
if let Value::PipeExpression(pipe_expr) = &expression_statement.expression {
|
|
|
|
|
pipe_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
} else if let Value::CallExpression(call_expr) = &expression_statement.expression {
|
2024-07-28 23:49:28 -07:00
|
|
|
|
call_expr.execute(memory, &pipe_info, self).await?;
|
2024-04-12 21:32:57 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
BodyItem::VariableDeclaration(variable_declaration) => {
|
|
|
|
|
for declaration in &variable_declaration.declarations {
|
|
|
|
|
let var_name = declaration.id.name.to_string();
|
|
|
|
|
let source_range: SourceRange = declaration.init.clone().into();
|
|
|
|
|
let metadata = Metadata { source_range };
|
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
let memory_item = self
|
|
|
|
|
.arg_into_mem_item(
|
|
|
|
|
&declaration.init,
|
|
|
|
|
memory,
|
|
|
|
|
&pipe_info,
|
|
|
|
|
&metadata,
|
|
|
|
|
StatementKind::Declaration { name: &var_name },
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
memory.add(&var_name, memory_item, source_range)?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
BodyItem::ReturnStatement(return_statement) => match &return_statement.argument {
|
|
|
|
|
Value::BinaryExpression(bin_expr) => {
|
|
|
|
|
let result = bin_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
Value::UnaryExpression(unary_expr) => {
|
|
|
|
|
let result = unary_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::Identifier(identifier) => {
|
|
|
|
|
let value = memory.get(&identifier.name, identifier.into())?.clone();
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(value));
|
|
|
|
|
}
|
|
|
|
|
Value::Literal(literal) => {
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(literal.into()));
|
|
|
|
|
}
|
2024-06-24 14:45:07 -07:00
|
|
|
|
Value::TagDeclarator(tag) => {
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(tag.into()));
|
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
Value::ArrayExpression(array_expr) => {
|
|
|
|
|
let result = array_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::ObjectExpression(obj_expr) => {
|
|
|
|
|
let result = obj_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::CallExpression(call_expr) => {
|
|
|
|
|
let result = call_expr.execute(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::MemberExpression(member_expr) => {
|
|
|
|
|
let result = member_expr.get_result(memory)?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::PipeExpression(pipe_expr) => {
|
|
|
|
|
let result = pipe_expr.get_result(memory, &pipe_info, self).await?;
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(result));
|
|
|
|
|
}
|
|
|
|
|
Value::PipeSubstitution(_) => {}
|
|
|
|
|
Value::FunctionExpression(_) => {}
|
|
|
|
|
Value::None(none) => {
|
|
|
|
|
memory.return_ = Some(ProgramReturn::Value(MemoryItem::from(none)));
|
|
|
|
|
}
|
|
|
|
|
},
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-22 18:50:54 -05:00
|
|
|
|
if BodyType::Root == body_type {
|
|
|
|
|
// Flush the batch queue.
|
2024-06-22 14:31:37 -07:00
|
|
|
|
self.engine
|
|
|
|
|
.flush_batch(
|
|
|
|
|
// True here tells the engine to flush all the end commands as well like fillets
|
|
|
|
|
// and chamfers where the engine would otherwise eat the ID of the segments.
|
|
|
|
|
true,
|
|
|
|
|
SourceRange([program.end, program.end]),
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
2024-05-22 18:50:54 -05:00
|
|
|
|
}
|
2024-04-12 21:32:57 -07:00
|
|
|
|
|
|
|
|
|
Ok(memory.clone())
|
|
|
|
|
}
|
2024-03-23 15:45:55 -07:00
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
pub async fn arg_into_mem_item<'a>(
|
|
|
|
|
&self,
|
|
|
|
|
init: &Value,
|
|
|
|
|
memory: &mut ProgramMemory,
|
|
|
|
|
pipe_info: &PipeInfo,
|
|
|
|
|
metadata: &Metadata,
|
|
|
|
|
statement_kind: StatementKind<'a>,
|
|
|
|
|
) -> Result<MemoryItem, KclError> {
|
|
|
|
|
let item = match init {
|
|
|
|
|
Value::None(none) => none.into(),
|
|
|
|
|
Value::Literal(literal) => literal.into(),
|
2024-06-24 14:45:07 -07:00
|
|
|
|
Value::TagDeclarator(tag) => tag.execute(memory).await?,
|
2024-05-23 14:50:22 -05:00
|
|
|
|
Value::Identifier(identifier) => {
|
|
|
|
|
let value = memory.get(&identifier.name, identifier.into())?;
|
|
|
|
|
value.clone()
|
|
|
|
|
}
|
|
|
|
|
Value::BinaryExpression(binary_expression) => binary_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
Value::FunctionExpression(function_expression) => {
|
|
|
|
|
let mem_func = force_memory_function(
|
|
|
|
|
|args: Vec<MemoryItem>,
|
|
|
|
|
memory: ProgramMemory,
|
|
|
|
|
function_expression: Box<FunctionExpression>,
|
|
|
|
|
_metadata: Vec<Metadata>,
|
|
|
|
|
ctx: ExecutorContext| {
|
|
|
|
|
Box::pin(async move {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
// Create a new environment to execute the function
|
|
|
|
|
// body in so that local variables shadow variables
|
|
|
|
|
// in the parent scope. The new environment's
|
|
|
|
|
// parent should be the environment of the closure.
|
|
|
|
|
let mut body_memory = memory.clone();
|
|
|
|
|
let closure_env = memory.current_env;
|
|
|
|
|
let body_env = body_memory.new_env_for_call(closure_env);
|
|
|
|
|
body_memory.current_env = body_env;
|
|
|
|
|
let mut fn_memory = assign_args_to_params(&function_expression, args, body_memory)?;
|
2024-05-23 14:50:22 -05:00
|
|
|
|
|
|
|
|
|
let result = ctx
|
2024-06-26 14:51:47 -07:00
|
|
|
|
.inner_execute(&function_expression.body, &mut fn_memory, BodyType::Block)
|
2024-05-23 14:50:22 -05:00
|
|
|
|
.await?;
|
|
|
|
|
|
2024-07-05 16:53:13 -07:00
|
|
|
|
Ok(result.return_)
|
2024-05-23 14:50:22 -05:00
|
|
|
|
})
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-07-22 19:43:40 -04:00
|
|
|
|
// Cloning memory here is crucial for semantics so that we close
|
|
|
|
|
// over variables. Variables defined lexically later shouldn't
|
|
|
|
|
// be available to the function body.
|
2024-05-23 14:50:22 -05:00
|
|
|
|
MemoryItem::Function {
|
|
|
|
|
expression: function_expression.clone(),
|
|
|
|
|
meta: vec![metadata.to_owned()],
|
|
|
|
|
func: Some(mem_func),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory: Box::new(memory.clone()),
|
2024-05-23 14:50:22 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Value::CallExpression(call_expression) => call_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::PipeExpression(pipe_expression) => pipe_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
Value::PipeSubstitution(pipe_substitution) => match statement_kind {
|
|
|
|
|
StatementKind::Declaration { name } => {
|
|
|
|
|
let message = format!(
|
|
|
|
|
"you cannot declare variable {name} as %, because % can only be used in function calls"
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message,
|
|
|
|
|
source_ranges: vec![pipe_substitution.into()],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
StatementKind::Expression => match pipe_info.previous_results.clone() {
|
|
|
|
|
Some(x) => x,
|
|
|
|
|
None => {
|
|
|
|
|
return Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: "cannot use % outside a pipe expression".to_owned(),
|
|
|
|
|
source_ranges: vec![pipe_substitution.into()],
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
Value::ArrayExpression(array_expression) => array_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::ObjectExpression(object_expression) => object_expression.execute(memory, pipe_info, self).await?,
|
|
|
|
|
Value::MemberExpression(member_expression) => member_expression.get_result(memory)?,
|
|
|
|
|
Value::UnaryExpression(unary_expression) => unary_expression.get_result(memory, pipe_info, self).await?,
|
|
|
|
|
};
|
|
|
|
|
Ok(item)
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-12 21:32:57 -07:00
|
|
|
|
/// Update the units for the executor.
|
2024-06-18 14:38:25 -05:00
|
|
|
|
pub fn update_units(&mut self, units: UnitLength) {
|
2024-04-25 00:13:09 -07:00
|
|
|
|
self.settings.units = units;
|
2024-04-12 21:32:57 -07:00
|
|
|
|
}
|
2024-06-06 16:01:41 -05:00
|
|
|
|
|
|
|
|
|
/// Execute the program, then get a PNG screenshot.
|
2024-06-26 14:51:47 -07:00
|
|
|
|
pub async fn execute_and_prepare_snapshot(&self, program: &Program) -> Result<kittycad::types::TakeSnapshot> {
|
2024-06-06 16:01:41 -05:00
|
|
|
|
let _ = self.run(program, None).await?;
|
|
|
|
|
|
|
|
|
|
// Zoom to fit.
|
|
|
|
|
self.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
crate::executor::SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::ZoomToFit {
|
|
|
|
|
object_ids: Default::default(),
|
|
|
|
|
padding: 0.1,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
// Send a snapshot request to the engine.
|
|
|
|
|
let resp = self
|
|
|
|
|
.engine
|
|
|
|
|
.send_modeling_cmd(
|
|
|
|
|
uuid::Uuid::new_v4(),
|
|
|
|
|
crate::executor::SourceRange::default(),
|
|
|
|
|
kittycad::types::ModelingCmd::TakeSnapshot {
|
|
|
|
|
format: kittycad::types::ImageFormat::Png,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let kittycad::types::OkWebSocketResponseData::Modeling {
|
|
|
|
|
modeling_response: kittycad::types::OkModelingCmdResponse::TakeSnapshot { data },
|
|
|
|
|
} = resp
|
|
|
|
|
else {
|
|
|
|
|
anyhow::bail!("Unexpected response from engine: {:?}", resp);
|
|
|
|
|
};
|
|
|
|
|
Ok(data)
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
/// For each argument given,
|
|
|
|
|
/// assign it to a parameter of the function, in the given block of function memory.
|
|
|
|
|
/// Returns Err if too few/too many arguments were given for the function.
|
|
|
|
|
fn assign_args_to_params(
|
|
|
|
|
function_expression: &FunctionExpression,
|
|
|
|
|
args: Vec<MemoryItem>,
|
|
|
|
|
mut fn_memory: ProgramMemory,
|
|
|
|
|
) -> Result<ProgramMemory, KclError> {
|
|
|
|
|
let num_args = function_expression.number_of_args();
|
|
|
|
|
let (min_params, max_params) = num_args.into_inner();
|
|
|
|
|
let n = args.len();
|
|
|
|
|
|
|
|
|
|
// Check if the user supplied too many arguments
|
|
|
|
|
// (we'll check for too few arguments below).
|
|
|
|
|
let err_wrong_number_args = KclError::Semantic(KclErrorDetails {
|
|
|
|
|
message: if min_params == max_params {
|
|
|
|
|
format!("Expected {min_params} arguments, got {n}")
|
|
|
|
|
} else {
|
|
|
|
|
format!("Expected {min_params}-{max_params} arguments, got {n}")
|
|
|
|
|
},
|
|
|
|
|
source_ranges: vec![function_expression.into()],
|
|
|
|
|
});
|
|
|
|
|
if n > max_params {
|
|
|
|
|
return Err(err_wrong_number_args);
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
// Add the arguments to the memory. A new call frame should have already
|
|
|
|
|
// been created.
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
for (index, param) in function_expression.params.iter().enumerate() {
|
|
|
|
|
if let Some(arg) = args.get(index) {
|
|
|
|
|
// Argument was provided.
|
|
|
|
|
fn_memory.add(¶m.identifier.name, arg.clone(), (¶m.identifier).into())?;
|
|
|
|
|
} else {
|
|
|
|
|
// Argument was not provided.
|
|
|
|
|
if param.optional {
|
|
|
|
|
// If the corresponding parameter is optional,
|
|
|
|
|
// then it's fine, the user doesn't need to supply it.
|
|
|
|
|
let none = KclNone {
|
|
|
|
|
start: param.identifier.start,
|
|
|
|
|
end: param.identifier.end,
|
|
|
|
|
};
|
|
|
|
|
fn_memory.add(
|
|
|
|
|
¶m.identifier.name,
|
|
|
|
|
MemoryItem::from(&none),
|
|
|
|
|
(¶m.identifier).into(),
|
|
|
|
|
)?;
|
|
|
|
|
} else {
|
|
|
|
|
// But if the corresponding parameter was required,
|
|
|
|
|
// then the user has called with too few arguments.
|
|
|
|
|
return Err(err_wrong_number_args);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(fn_memory)
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-23 14:50:22 -05:00
|
|
|
|
pub enum StatementKind<'a> {
|
|
|
|
|
Declaration { name: &'a str },
|
|
|
|
|
Expression,
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
2024-03-13 12:56:46 -07:00
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
2023-08-29 14:12:48 -07:00
|
|
|
|
use super::*;
|
2024-02-11 15:08:54 -08:00
|
|
|
|
use crate::ast::types::{Identifier, Parameter};
|
2023-08-29 14:12:48 -07:00
|
|
|
|
|
2023-08-24 15:34:51 -07:00
|
|
|
|
pub async fn parse_execute(code: &str) -> Result<ProgramMemory> {
|
2024-04-15 17:18:32 -07:00
|
|
|
|
let tokens = crate::token::lexer(code)?;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
let parser = crate::parser::Parser::new(tokens);
|
|
|
|
|
let program = parser.ast()?;
|
2024-03-13 12:56:46 -07:00
|
|
|
|
let ctx = ExecutorContext {
|
|
|
|
|
engine: Arc::new(Box::new(crate::engine::conn_mock::EngineConnection::new().await?)),
|
2024-04-15 17:18:32 -07:00
|
|
|
|
fs: Arc::new(crate::fs::FileManager::new()),
|
2024-03-13 12:56:46 -07:00
|
|
|
|
stdlib: Arc::new(crate::std::StdLib::new()),
|
2024-04-25 00:13:09 -07:00
|
|
|
|
settings: Default::default(),
|
2024-05-15 10:17:29 -07:00
|
|
|
|
is_mock: true,
|
2024-03-13 12:56:46 -07:00
|
|
|
|
};
|
2024-06-26 14:51:47 -07:00
|
|
|
|
let memory = ctx.run(&program, None).await?;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
Ok(memory)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_assign_two_variables() {
|
|
|
|
|
let ast = r#"const myVar = 5
|
|
|
|
|
const newVar = myVar + 1"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(5),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("myVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(6.0),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("newVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_angled_line_that_intersects() {
|
|
|
|
|
let ast_fn = |offset: &str| -> String {
|
|
|
|
|
format!(
|
2023-10-05 14:27:48 -07:00
|
|
|
|
r#"const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-07-27 17:59:41 -07:00
|
|
|
|
|> lineTo([2, 2], %, $yo)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> lineTo([3, 1], %)
|
|
|
|
|
|> angledLineThatIntersects({{
|
|
|
|
|
angle: 180,
|
2024-07-27 17:59:41 -07:00
|
|
|
|
intersectTag: yo,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
offset: {},
|
2024-07-27 17:59:41 -07:00
|
|
|
|
}}, %, $yo2)
|
2024-07-27 22:56:46 -07:00
|
|
|
|
const intersect = segEndX(yo2)"#,
|
2023-08-24 15:34:51 -07:00
|
|
|
|
offset
|
|
|
|
|
)
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(&ast_fn("-1")).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0 + 2.0f64.sqrt()),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("intersect", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(&ast_fn("0")).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0000000000000002),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("intersect", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-08-24 15:34:51 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_fn_definitions() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn def = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn ghi = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn jkl = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
2023-09-13 11:42:09 -07:00
|
|
|
|
fn hmm = (x) => {
|
2023-08-24 15:34:51 -07:00
|
|
|
|
return x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const yo = 5 + 6
|
|
|
|
|
|
|
|
|
|
const abc = 3
|
|
|
|
|
const identifierGuy = 5
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([-1.2, 4.83], %)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([2.8, 0], %)
|
|
|
|
|
|> angledLine([100 + 100, 3.01], %)
|
|
|
|
|
|> angledLine([abc, 3.02], %)
|
|
|
|
|
|> angledLine([def(yo), 3.03], %)
|
|
|
|
|
|> angledLine([ghi(2), 3.04], %)
|
|
|
|
|
|> angledLine([jkl(yo) + 2, 3.05], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
const yo2 = hmm([identifierGuy + 5])"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_pipe_substitutions_unary() {
|
|
|
|
|
let ast = r#"const myVar = 3
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-07-27 17:59:41 -07:00
|
|
|
|
|> line([3, 4], %, $seg01)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([
|
2024-07-27 22:56:46 -07:00
|
|
|
|
min(segLen(seg01), myVar),
|
|
|
|
|
-legLen(segLen(seg01), myVar)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
], %)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_pipe_substitutions() {
|
|
|
|
|
let ast = r#"const myVar = 3
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2024-07-27 17:59:41 -07:00
|
|
|
|
|> line([3, 4], %, $seg01)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|> line([
|
2024-07-27 22:56:46 -07:00
|
|
|
|
min(segLen(seg01), myVar),
|
|
|
|
|
legLen(segLen(seg01), myVar)
|
2023-08-24 15:34:51 -07:00
|
|
|
|
], %)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_inline_comment() {
|
|
|
|
|
let ast = r#"const baseThick = 1
|
|
|
|
|
const armAngle = 60
|
|
|
|
|
|
|
|
|
|
const baseThickHalf = baseThick / 2
|
|
|
|
|
const halfArmAngle = armAngle / 2
|
|
|
|
|
|
|
|
|
|
const arrExpShouldNotBeIncluded = [1, 2, 3]
|
|
|
|
|
const objExpShouldNotBeIncluded = { a: 1, b: 2, c: 3 }
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const part001 = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0, 0], %)
|
2023-09-05 16:02:27 -07:00
|
|
|
|
|> yLineTo(1, %)
|
|
|
|
|
|> xLine(3.84, %) // selection-range-7ish-before-this
|
|
|
|
|
|
|
|
|
|
const variableBelowShouldNotBeIncluded = 3
|
2024-03-01 17:16:18 -08:00
|
|
|
|
"#;
|
2023-08-24 15:34:51 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_literal_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = () => {
|
|
|
|
|
return -8
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing()], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_unary_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return -x
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing(8)], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_array_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return [0, -x]
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line(thing(8), %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_call_in_pipe() {
|
|
|
|
|
let ast = r#"const w = 20
|
|
|
|
|
const l = 8
|
|
|
|
|
const h = 10
|
|
|
|
|
|
|
|
|
|
fn other_thing = (y) => {
|
|
|
|
|
return -y
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn thing = (x) => {
|
|
|
|
|
return other_thing(x)
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const firstExtrude = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, thing(8)], %)
|
|
|
|
|
|> close(%)
|
2024-03-01 17:16:18 -08:00
|
|
|
|
|> extrude(h, %)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_with_function_sketch() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (h, l, w) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, -l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-01 17:16:18 -08:00
|
|
|
|
const fnBox = box(3, 6, 10)"#;
|
2023-09-11 15:15:37 -07:00
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_period() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj.start, %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj.l], %)
|
|
|
|
|
|> line([obj.w, 0], %)
|
|
|
|
|
|> line([0, -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj.h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_brace() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj["start"], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj["l"]], %)
|
|
|
|
|
|> line([obj["w"], 0], %)
|
|
|
|
|
|> line([0, -obj["l"]], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj["h"], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_object_with_function_mix_period_brace() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj["start"], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj["l"]], %)
|
|
|
|
|
|> line([obj["w"], 0], %)
|
|
|
|
|
|> line([10 - obj["w"], -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj["h"], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box({start: [0,0], l: 6, w: 10, h: 3})
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
#[ignore] // ignore til we get loops
|
|
|
|
|
async fn test_execute_with_function_sketch_loop_objects() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (obj) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(obj.start, %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, obj.l], %)
|
|
|
|
|
|> line([obj.w, 0], %)
|
|
|
|
|
|> line([0, -obj.l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(obj.h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for var in [{start: [0,0], l: 6, w: 10, h: 3}, {start: [-10,-10], l: 3, w: 5, h: 1.5}] {
|
|
|
|
|
const thisBox = box(var)
|
|
|
|
|
}"#;
|
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
#[ignore] // ignore til we get loops
|
|
|
|
|
async fn test_execute_with_function_sketch_loop_array() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (h, l, w, start) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const myBox = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([0, l], %)
|
|
|
|
|
|> line([w, 0], %)
|
|
|
|
|
|> line([0, -l], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(h, %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for var in [[3, 6, 10, [0,0]], [1.5, 3, 5, [-10,-10]]] {
|
|
|
|
|
const thisBox = box(var[0], var[1], var[2], var[3])
|
|
|
|
|
}"#;
|
|
|
|
|
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_get_member_of_array_with_function() {
|
2023-09-13 11:42:09 -07:00
|
|
|
|
let ast = r#"fn box = (array) => {
|
2023-10-05 14:27:48 -07:00
|
|
|
|
let myBox =startSketchOn('XY')
|
|
|
|
|
|> startProfileAt(array[0], %)
|
2023-09-13 09:23:24 -07:00
|
|
|
|
|> line([0, array[1]], %)
|
2023-09-13 07:23:14 -07:00
|
|
|
|
|> line([array[2], 0], %)
|
|
|
|
|
|> line([0, -array[1]], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(array[3], %)
|
|
|
|
|
|
|
|
|
|
return myBox
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const thisBox = box([[0,0], 6, 10, 3])
|
|
|
|
|
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-22 19:43:40 -04:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_function_cannot_access_future_definitions() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
fn returnX = () => {
|
|
|
|
|
// x shouldn't be defined yet.
|
|
|
|
|
return x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const x = 5
|
|
|
|
|
|
|
|
|
|
const answer = returnX()"#;
|
|
|
|
|
|
|
|
|
|
let result = parse_execute(ast).await;
|
|
|
|
|
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
err,
|
|
|
|
|
KclError::UndefinedValue(KclErrorDetails {
|
|
|
|
|
message: "memory item key `x` is not defined".to_owned(),
|
|
|
|
|
source_ranges: vec![SourceRange([64, 65]), SourceRange([97, 106])],
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_pattern_transform_function_cannot_access_future_definitions() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
fn transform = (replicaId) => {
|
|
|
|
|
// x shouldn't be defined yet.
|
|
|
|
|
let scale = x
|
|
|
|
|
return {
|
|
|
|
|
translate: [0, 0, replicaId * 10],
|
|
|
|
|
scale: [scale, 1, 0],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn layer = () => {
|
|
|
|
|
return startSketchOn("XY")
|
2024-07-27 17:59:41 -07:00
|
|
|
|
|> circle([0, 0], 1, %, $tag1)
|
2024-07-22 19:43:40 -04:00
|
|
|
|
|> extrude(10, %)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const x = 5
|
|
|
|
|
|
|
|
|
|
// The 10 layers are replicas of each other, with a transform applied to each.
|
|
|
|
|
let shape = layer() |> patternTransform(10, transform, %)
|
|
|
|
|
"#;
|
|
|
|
|
|
|
|
|
|
let result = parse_execute(ast).await;
|
|
|
|
|
let err = result.unwrap_err().downcast::<KclError>().unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
err,
|
|
|
|
|
KclError::UndefinedValue(KclErrorDetails {
|
|
|
|
|
message: "memory item key `x` is not defined".to_owned(),
|
|
|
|
|
source_ranges: vec![SourceRange([80, 81])],
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_function_with_parameter_redefined_outside() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
fn myIdentity = (x) => {
|
|
|
|
|
return x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const x = 33
|
|
|
|
|
|
|
|
|
|
const two = myIdentity(2)"#;
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(2),
|
|
|
|
|
memory
|
|
|
|
|
.get("two", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(33),
|
|
|
|
|
memory
|
|
|
|
|
.get("x", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_function_referencing_variable_in_parent_scope() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
const x = 22
|
|
|
|
|
const y = 3
|
|
|
|
|
|
|
|
|
|
fn add = (x) => {
|
|
|
|
|
return x + y
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const answer = add(2)"#;
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(5.0),
|
|
|
|
|
memory
|
|
|
|
|
.get("answer", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(22),
|
|
|
|
|
memory
|
|
|
|
|
.get("x", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_function_redefining_variable_in_parent_scope() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
const x = 1
|
|
|
|
|
|
|
|
|
|
fn foo = () => {
|
|
|
|
|
const x = 2
|
|
|
|
|
return x
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const answer = foo()"#;
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(2),
|
|
|
|
|
memory
|
|
|
|
|
.get("answer", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1),
|
|
|
|
|
memory
|
|
|
|
|
.get("x", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_execute_pattern_transform_function_redefining_variable_in_parent_scope() {
|
|
|
|
|
let ast = r#"
|
|
|
|
|
const scale = 100
|
|
|
|
|
fn transform = (replicaId) => {
|
|
|
|
|
// Redefine same variable as in parent scope.
|
|
|
|
|
const scale = 2
|
|
|
|
|
return {
|
|
|
|
|
translate: [0, 0, replicaId * 10],
|
|
|
|
|
scale: [scale, 1, 0],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn layer = () => {
|
|
|
|
|
return startSketchOn("XY")
|
2024-07-27 17:59:41 -07:00
|
|
|
|
|> circle([0, 0], 1, %, $tag1)
|
2024-07-22 19:43:40 -04:00
|
|
|
|
|> extrude(10, %)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The 10 layers are replicas of each other, with a transform applied to each.
|
|
|
|
|
let shape = layer() |> patternTransform(10, transform, %)"#;
|
|
|
|
|
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
// TODO: Assert that scale 2 was used.
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(100),
|
|
|
|
|
memory
|
|
|
|
|
.get("scale", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 07:23:14 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_with_functions() {
|
|
|
|
|
let ast = r#"const myVar = 2 + min(100, -1 + legLen(5, 3))"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(5.0),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("myVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-09-13 07:23:14 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute() {
|
|
|
|
|
let ast = r#"const myVar = 1 + 2 * (3 - 4) / -5 + 6"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(7.4),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("myVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-09-13 07:23:14 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_start_negative() {
|
|
|
|
|
let ast = r#"const myVar = -5 + 6"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(1.0),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("myVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-09-13 07:23:14 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
2023-09-13 13:10:55 -07:00
|
|
|
|
|
2023-09-19 16:05:53 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_execute_with_pi() {
|
|
|
|
|
let ast = r#"const myVar = pi() * 2"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(std::f64::consts::TAU),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("myVar", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-09-19 16:05:53 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 13:10:55 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_define_decimal_without_leading_zero() {
|
|
|
|
|
let ast = r#"let thing = .4 + 7"#;
|
|
|
|
|
let memory = parse_execute(ast).await.unwrap();
|
|
|
|
|
assert_eq!(
|
|
|
|
|
serde_json::json!(7.4),
|
2024-07-22 19:43:40 -04:00
|
|
|
|
memory
|
|
|
|
|
.get("thing", SourceRange::default())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.get_json_value()
|
|
|
|
|
.unwrap()
|
2023-09-13 13:10:55 -07:00
|
|
|
|
);
|
|
|
|
|
}
|
2023-09-15 13:19:53 -07:00
|
|
|
|
|
2023-09-20 10:51:49 -05:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_zero_param_fn() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
fn thickness = () => { return 0.56 }
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-20 10:51:49 -05:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness()], %)
|
|
|
|
|
|> line([-leg2 + thickness(), 0], %)
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:19:53 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_negative_variable_in_binary_expression() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 1 // inch
|
|
|
|
|
|
|
|
|
|
const p = 150 // lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
|
|
|
|
|
const thickness_squared = distance * p * FOS * 6 / sigmaAllow
|
|
|
|
|
const thickness = 0.56 // inches. App does not support square root function yet
|
|
|
|
|
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 13:19:53 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-leg2 + thickness, 0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_doubly_nested_parens() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 4 // inch
|
|
|
|
|
const p = 150 // Force on shelf - lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
const thickness_squared = (distance * p * FOS * 6 / (sigmaAllow - width))
|
|
|
|
|
const thickness = 0.32 // inches. App does not support square root function yet
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-1 * leg2 + thickness, 0], %)
|
|
|
|
|
|> line([0, -1 * leg1 + thickness], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(width, %)
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_math_nested_parens_one_less() {
|
|
|
|
|
let ast = r#"const sigmaAllow = 35000 // psi
|
|
|
|
|
const width = 4 // inch
|
|
|
|
|
const p = 150 // Force on shelf - lbs
|
|
|
|
|
const distance = 6 // inches
|
|
|
|
|
const FOS = 2
|
|
|
|
|
const leg1 = 5 // inches
|
|
|
|
|
const leg2 = 8 // inches
|
|
|
|
|
const thickness_squared = distance * p * FOS * 6 / (sigmaAllow - width)
|
|
|
|
|
const thickness = 0.32 // inches. App does not support square root function yet
|
2023-10-05 14:27:48 -07:00
|
|
|
|
const bracket = startSketchOn('XY')
|
|
|
|
|
|> startProfileAt([0,0], %)
|
2023-09-15 17:40:57 -07:00
|
|
|
|
|> line([0, leg1], %)
|
|
|
|
|
|> line([leg2, 0], %)
|
|
|
|
|
|> line([0, -thickness], %)
|
|
|
|
|
|> line([-1 * leg2 + thickness, 0], %)
|
|
|
|
|
|> line([0, -1 * leg1 + thickness], %)
|
|
|
|
|
|> close(%)
|
|
|
|
|
|> extrude(width, %)
|
2023-09-15 13:19:53 -07:00
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
|
2024-07-29 00:33:31 -07:00
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
|
|
|
async fn test_fn_as_operand() {
|
|
|
|
|
let ast = r#"fn f = () => { return 1 }
|
|
|
|
|
let x = f()
|
|
|
|
|
let y = x + 1
|
|
|
|
|
let z = f() + 1
|
|
|
|
|
let w = f() + f()
|
|
|
|
|
"#;
|
|
|
|
|
parse_execute(ast).await.unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_assign_args_to_params() {
|
|
|
|
|
// Set up a little framework for this test.
|
|
|
|
|
fn mem(number: usize) -> MemoryItem {
|
|
|
|
|
MemoryItem::UserVal(UserVal {
|
|
|
|
|
value: number.into(),
|
|
|
|
|
meta: Default::default(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
fn ident(s: &'static str) -> Identifier {
|
|
|
|
|
Identifier {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
name: s.to_owned(),
|
2024-07-09 12:24:42 -04:00
|
|
|
|
digest: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn opt_param(s: &'static str) -> Parameter {
|
|
|
|
|
Parameter {
|
|
|
|
|
identifier: ident(s),
|
2024-03-21 17:14:30 -07:00
|
|
|
|
type_: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
optional: true,
|
2024-07-09 12:24:42 -04:00
|
|
|
|
digest: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
fn req_param(s: &'static str) -> Parameter {
|
|
|
|
|
Parameter {
|
|
|
|
|
identifier: ident(s),
|
2024-03-21 17:14:30 -07:00
|
|
|
|
type_: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
optional: false,
|
2024-07-09 12:24:42 -04:00
|
|
|
|
digest: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-11 18:26:09 -08:00
|
|
|
|
fn additional_program_memory(items: &[(String, MemoryItem)]) -> ProgramMemory {
|
|
|
|
|
let mut program_memory = ProgramMemory::new();
|
|
|
|
|
for (name, item) in items {
|
2024-07-22 19:43:40 -04:00
|
|
|
|
program_memory
|
|
|
|
|
.add(name.as_str(), item.clone(), SourceRange::default())
|
|
|
|
|
.unwrap();
|
2024-02-11 18:26:09 -08:00
|
|
|
|
}
|
|
|
|
|
program_memory
|
|
|
|
|
}
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
// Declare the test cases.
|
|
|
|
|
for (test_name, params, args, expected) in [
|
|
|
|
|
("empty", Vec::new(), Vec::new(), Ok(ProgramMemory::new())),
|
|
|
|
|
(
|
|
|
|
|
"all params required, and all given, should be OK",
|
|
|
|
|
vec![req_param("x")],
|
|
|
|
|
vec![mem(1)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[("x".to_owned(), mem(1))])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"all params required, none given, should error",
|
|
|
|
|
vec![req_param("x")],
|
|
|
|
|
vec![],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1 arguments, got 0".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"all params optional, none given, should be OK",
|
|
|
|
|
vec![opt_param("x")],
|
|
|
|
|
vec![],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[(
|
|
|
|
|
"x".to_owned(),
|
|
|
|
|
MemoryItem::from(&KclNone::default()),
|
|
|
|
|
)])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, too few given",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1-2 arguments, got 0".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, minimum given, should be OK",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[
|
|
|
|
|
("x".to_owned(), mem(1)),
|
|
|
|
|
("y".to_owned(), MemoryItem::from(&KclNone::default())),
|
|
|
|
|
])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, maximum given, should be OK",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1), mem(2)],
|
2024-02-11 18:26:09 -08:00
|
|
|
|
Ok(additional_program_memory(&[
|
|
|
|
|
("x".to_owned(), mem(1)),
|
|
|
|
|
("y".to_owned(), mem(2)),
|
|
|
|
|
])),
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
),
|
|
|
|
|
(
|
|
|
|
|
"mixed params, too many given",
|
|
|
|
|
vec![req_param("x"), opt_param("y")],
|
|
|
|
|
vec![mem(1), mem(2), mem(3)],
|
|
|
|
|
Err(KclError::Semantic(KclErrorDetails {
|
|
|
|
|
source_ranges: vec![SourceRange([0, 0])],
|
|
|
|
|
message: "Expected 1-2 arguments, got 3".to_owned(),
|
|
|
|
|
})),
|
|
|
|
|
),
|
|
|
|
|
] {
|
|
|
|
|
// Run each test.
|
|
|
|
|
let func_expr = &FunctionExpression {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
params,
|
|
|
|
|
body: crate::ast::types::Program {
|
|
|
|
|
start: 0,
|
|
|
|
|
end: 0,
|
|
|
|
|
body: Vec::new(),
|
|
|
|
|
non_code_meta: Default::default(),
|
2024-07-09 12:24:42 -04:00
|
|
|
|
digest: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
},
|
2024-03-21 17:14:30 -07:00
|
|
|
|
return_type: None,
|
2024-07-09 12:24:42 -04:00
|
|
|
|
digest: None,
|
Remove just one enum (#1096)
# Problem
This is my proposal for fixing #1107 . I've only done it for one stdlib function, `tangentialArcTo` -- if y'all like it, I'll apply this idea to the rest of the stdlib.
Previously, if users want to put a tag on the arc, the function's parameters change type.
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is object
tangentialArcTo({to: [x, y], tag: "myTag"}, %)
```
# Solution
My proposal in #1006 is that KCL should have optional values. This means we can change the stdlib `tangentialArcTo` function to use them. In this PR, the calls are now like
```
// Tag missing: first param is array
tangentialArcTo([x, y], %)
// Tag present: first param is array still, but we now pass a tag at the end.
tangentialArcTo([x, y], %, "myTag")
```
This adds an "option" type to KCL typesystem, but it's not really revealed to users (no KCL types are revealed to users right now, they write untyped code and only interact with types when they get type errors upon executing programs). Also adds a None type, which is the default case of the Optional enum.
2023-12-18 23:49:32 -06:00
|
|
|
|
};
|
|
|
|
|
let actual = assign_args_to_params(func_expr, args, ProgramMemory::new());
|
|
|
|
|
assert_eq!(
|
|
|
|
|
actual, expected,
|
|
|
|
|
"failed test '{test_name}':\ngot {actual:?}\nbut expected\n{expected:?}"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-11 15:08:54 -08:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_serialize_memory_item() {
|
|
|
|
|
let mem = MemoryItem::ExtrudeGroups {
|
|
|
|
|
value: Default::default(),
|
|
|
|
|
};
|
|
|
|
|
let json = serde_json::to_string(&mem).unwrap();
|
|
|
|
|
assert_eq!(json, r#"{"type":"ExtrudeGroups","value":[]}"#);
|
|
|
|
|
}
|
2023-08-24 15:34:51 -07:00
|
|
|
|
}
|