Merge branch 'main' into achalmers/grid-scale
This commit is contained in:
@ -2,48 +2,11 @@ use std::{collections::HashMap, fs, path::Path};
|
||||
|
||||
use anyhow::Result;
|
||||
use base64::Engine;
|
||||
use convert_case::Casing;
|
||||
use indexmap::IndexMap;
|
||||
use itertools::Itertools;
|
||||
use serde_json::json;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
use super::kcl_doc::{ConstData, DocData, ExampleProperties, FnData, ModData, TyData};
|
||||
use crate::{
|
||||
docs::{StdLibFn, DECLARED_TYPES},
|
||||
std::StdLib,
|
||||
ExecutorContext,
|
||||
};
|
||||
|
||||
// Types with special handling.
|
||||
const SPECIAL_TYPES: [&str; 4] = ["TagDeclarator", "TagIdentifier", "Start", "End"];
|
||||
|
||||
const TYPE_REWRITES: [(&str, &str); 11] = [
|
||||
("TagNode", "TagDeclarator"),
|
||||
("SketchData", "Plane | Solid"),
|
||||
("SketchOrSurface", "Sketch | Plane | Face"),
|
||||
("SketchSurface", "Plane | Face"),
|
||||
("SolidOrImportedGeometry", "[Solid] | ImportedGeometry"),
|
||||
(
|
||||
"SolidOrSketchOrImportedGeometry",
|
||||
"[Solid] | [Sketch] | ImportedGeometry",
|
||||
),
|
||||
("KclValue", "any"),
|
||||
("[KclValue]", "[any]"),
|
||||
("FaceTag", "TagIdentifier | Start | End"),
|
||||
("GeometryWithImportedGeometry", "Solid | Sketch | ImportedGeometry"),
|
||||
("SweepPath", "Sketch | Helix"),
|
||||
];
|
||||
|
||||
fn rename_type(input: &str) -> &str {
|
||||
for (i, o) in TYPE_REWRITES {
|
||||
if input == i {
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
input
|
||||
}
|
||||
use crate::ExecutorContext;
|
||||
|
||||
fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
let mut hbs = handlebars::Handlebars::new();
|
||||
@ -104,7 +67,7 @@ fn init_handlebars() -> Result<handlebars::Handlebars<'static>> {
|
||||
Ok(hbs)
|
||||
}
|
||||
|
||||
fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &ModData) -> Result<()> {
|
||||
fn generate_index(kcl_lib: &ModData) -> Result<()> {
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
let mut functions = HashMap::new();
|
||||
@ -115,31 +78,6 @@ fn generate_index(combined: &IndexMap<String, Box<dyn StdLibFn>>, kcl_lib: &ModD
|
||||
let mut types = HashMap::new();
|
||||
types.insert("Primitive types".to_owned(), Vec::new());
|
||||
|
||||
for key in combined.keys() {
|
||||
let internal_fn = combined
|
||||
.get(key)
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to get internal function: {}", key))?;
|
||||
|
||||
if internal_fn.unpublished() || internal_fn.deprecated() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tags = internal_fn.tags();
|
||||
let module = tags.first().map(|s| format!("std::{s}")).unwrap_or("std".to_owned());
|
||||
|
||||
functions
|
||||
.entry(module.to_owned())
|
||||
.or_default()
|
||||
.push((internal_fn.name(), format!("/docs/kcl-std/{}", internal_fn.name())));
|
||||
}
|
||||
|
||||
for name in SPECIAL_TYPES {
|
||||
types
|
||||
.get_mut("Primitive types")
|
||||
.unwrap()
|
||||
.push((name.to_owned(), format!("/docs/kcl-lang/types#{name}")));
|
||||
}
|
||||
|
||||
for d in kcl_lib.all_docs() {
|
||||
if d.hide() {
|
||||
continue;
|
||||
@ -257,8 +195,8 @@ fn generate_example(index: usize, src: &str, props: &ExampleProperties, file_nam
|
||||
}))
|
||||
}
|
||||
|
||||
fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String) -> Result<()> {
|
||||
if ty.properties.doc_hidden || !DECLARED_TYPES.contains(&&*ty.name) {
|
||||
fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String, kcl_std: &ModData) -> Result<()> {
|
||||
if ty.properties.doc_hidden {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -282,18 +220,14 @@ fn generate_type_from_kcl(ty: &TyData, file_name: String, example_name: String)
|
||||
});
|
||||
|
||||
let output = hbs.render("kclType", &data)?;
|
||||
let output = cleanup_types(&output);
|
||||
let output = cleanup_types(&output, kcl_std);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<String, Box<dyn StdLibFn>>) -> Result<()> {
|
||||
fn list_items(
|
||||
m: &ModData,
|
||||
namespace: &str,
|
||||
combined: &IndexMap<String, Box<dyn StdLibFn>>,
|
||||
) -> Vec<gltf_json::Value> {
|
||||
fn generate_mod_from_kcl(m: &ModData, file_name: String) -> Result<()> {
|
||||
fn list_items(m: &ModData, namespace: &str) -> Vec<gltf_json::Value> {
|
||||
let mut items: Vec<_> = m
|
||||
.children
|
||||
.iter()
|
||||
@ -301,25 +235,6 @@ fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<Str
|
||||
.map(|(_, v)| (v.preferred_name().to_owned(), v.file_name()))
|
||||
.collect();
|
||||
|
||||
if namespace == "I:" {
|
||||
// Add in functions declared in Rust
|
||||
items.extend(
|
||||
combined
|
||||
.values()
|
||||
.filter(|f| {
|
||||
if f.unpublished() || f.deprecated() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let tags = f.tags();
|
||||
let module = tags.first().map(|s| format!("std::{s}")).unwrap_or("std".to_owned());
|
||||
|
||||
module == m.qual_name
|
||||
})
|
||||
.map(|f| (f.name(), f.name())),
|
||||
)
|
||||
}
|
||||
|
||||
items.sort();
|
||||
items
|
||||
.into_iter()
|
||||
@ -333,9 +248,9 @@ fn generate_mod_from_kcl(m: &ModData, file_name: String, combined: &IndexMap<Str
|
||||
}
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
let functions = list_items(m, "I:", combined);
|
||||
let modules = list_items(m, "M:", combined);
|
||||
let types = list_items(m, "T:", combined);
|
||||
let functions = list_items(m, "I:");
|
||||
let modules = list_items(m, "M:");
|
||||
let types = list_items(m, "T:");
|
||||
|
||||
let data = json!({
|
||||
"name": m.name,
|
||||
@ -391,7 +306,7 @@ fn generate_function_from_kcl(
|
||||
json!({
|
||||
"name": arg.name,
|
||||
"type_": arg.ty,
|
||||
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| super::docs_for_type(t, kcl_std))).unwrap_or_default(),
|
||||
"description": docs.or_else(|| arg.ty.as_ref().and_then(|t| docs_for_type(t, kcl_std))).unwrap_or_default(),
|
||||
"required": arg.kind.required(),
|
||||
})
|
||||
}).collect::<Vec<_>>();
|
||||
@ -408,18 +323,30 @@ fn generate_function_from_kcl(
|
||||
"return_value": function.return_type.as_ref().map(|t| {
|
||||
json!({
|
||||
"type_": t,
|
||||
"description": super::docs_for_type(t, kcl_std).unwrap_or_default(),
|
||||
"description": docs_for_type(t, kcl_std).unwrap_or_default(),
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
let output = hbs.render("function", &data)?;
|
||||
let output = &cleanup_types(&output);
|
||||
let output = &cleanup_types(&output, kcl_std);
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", file_name), output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn docs_for_type(ty: &str, kcl_std: &ModData) -> Option<String> {
|
||||
let key = if ty.starts_with("number") { "number" } else { ty };
|
||||
|
||||
if !key.contains('|') && !key.contains('[') {
|
||||
if let Some(data) = kcl_std.find_by_name(key) {
|
||||
return data.summary().cloned();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: String) -> Result<()> {
|
||||
if cnst.properties.doc_hidden {
|
||||
return Ok(());
|
||||
@ -450,83 +377,7 @@ fn generate_const_from_kcl(cnst: &ConstData, file_name: String, example_name: St
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn generate_function(internal_fn: Box<dyn StdLibFn>, kcl_std: &ModData) -> Result<()> {
|
||||
let hbs = init_handlebars()?;
|
||||
|
||||
if internal_fn.unpublished() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let fn_name = internal_fn.name();
|
||||
let snake_case_name = clean_function_name(&fn_name);
|
||||
|
||||
let examples: Vec<serde_json::Value> = internal_fn
|
||||
.examples()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, (example, norun))| {
|
||||
let image_base64 = if !norun {
|
||||
let image_path = format!(
|
||||
"{}/tests/outputs/serial_test_example_{}{}.png",
|
||||
env!("CARGO_MANIFEST_DIR"),
|
||||
snake_case_name,
|
||||
index
|
||||
);
|
||||
let image_data =
|
||||
std::fs::read(&image_path).unwrap_or_else(|_| panic!("Failed to read image file: {}", image_path));
|
||||
base64::engine::general_purpose::STANDARD.encode(&image_data)
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
|
||||
json!({
|
||||
"content": example,
|
||||
"image_base64": image_base64,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
let tags = internal_fn.tags();
|
||||
let module = tags
|
||||
.first()
|
||||
.map(|s| &**s)
|
||||
.map(|m| format!("std::{m}"))
|
||||
.unwrap_or("std".to_owned());
|
||||
|
||||
let data = json!({
|
||||
"name": fn_name,
|
||||
"module": module,
|
||||
"summary": internal_fn.summary(),
|
||||
"description": internal_fn.description(),
|
||||
"deprecated": internal_fn.deprecated(),
|
||||
"fn_signature": internal_fn.fn_signature(true),
|
||||
"examples": examples,
|
||||
"args": internal_fn.args(false).iter().map(|arg| {
|
||||
json!({
|
||||
"name": arg.name,
|
||||
"type_": rename_type(&arg.type_),
|
||||
"description": arg.description(Some(kcl_std)),
|
||||
"required": arg.required,
|
||||
})
|
||||
}).collect::<Vec<_>>(),
|
||||
"return_value": internal_fn.return_value(false).map(|ret| {
|
||||
json!({
|
||||
"type_": rename_type(&ret.type_),
|
||||
"description": ret.description(Some(kcl_std)),
|
||||
})
|
||||
}),
|
||||
});
|
||||
|
||||
let mut output = hbs.render("function", &data)?;
|
||||
// Fix the links to the types.
|
||||
output = cleanup_types(&output);
|
||||
|
||||
expectorate::assert_contents(format!("../../docs/kcl-std/{}.md", fn_name), &output);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_types(input: &str) -> String {
|
||||
fn cleanup_types(input: &str, kcl_std: &ModData) -> String {
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||
enum State {
|
||||
Text,
|
||||
@ -550,7 +401,7 @@ fn cleanup_types(input: &str) -> String {
|
||||
if code_type.starts_with(' ') {
|
||||
code.push(' ');
|
||||
}
|
||||
code.push_str(&cleanup_type_string(code_type.trim(), false));
|
||||
code.push_str(&cleanup_type_string(code_type.trim(), false, kcl_std));
|
||||
if code_type.ends_with(' ') {
|
||||
code.push(' ');
|
||||
}
|
||||
@ -586,7 +437,7 @@ fn cleanup_types(input: &str) -> String {
|
||||
}
|
||||
ticks = 0;
|
||||
} else if state == State::Text && ticks == 2 && !code.is_empty() {
|
||||
output.push_str(&cleanup_type_string(&code, true));
|
||||
output.push_str(&cleanup_type_string(&code, true, kcl_std));
|
||||
code = String::new();
|
||||
ticks = 0;
|
||||
} else if state == State::CodeBlock {
|
||||
@ -631,14 +482,12 @@ fn cleanup_types(input: &str) -> String {
|
||||
output
|
||||
}
|
||||
|
||||
fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
fn cleanup_type_string(input: &str, fmt_for_text: bool, kcl_std: &ModData) -> String {
|
||||
assert!(
|
||||
!(input.starts_with('[') && input.ends_with(']') && input.contains('|')),
|
||||
"Arrays of unions are not supported"
|
||||
);
|
||||
|
||||
let input = rename_type(input);
|
||||
|
||||
let tys: Vec<_> = input
|
||||
.split('|')
|
||||
.map(|ty| {
|
||||
@ -676,9 +525,7 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-number)")
|
||||
} else if fmt_for_text && ty.starts_with("fn") {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-fn)")
|
||||
} else if fmt_for_text && SPECIAL_TYPES.contains(&ty) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-lang/types#{ty})")
|
||||
} else if fmt_for_text && DECLARED_TYPES.contains(&ty) {
|
||||
} else if fmt_for_text && matches!(kcl_std.find_by_name(ty), Some(DocData::Ty(_))) {
|
||||
format!("[{prefix}{ty}{suffix}](/docs/kcl-std/types/std-types-{ty})")
|
||||
} else {
|
||||
format!("{prefix}{ty}{suffix}")
|
||||
@ -689,73 +536,22 @@ fn cleanup_type_string(input: &str, fmt_for_text: bool) -> String {
|
||||
tys.join(if fmt_for_text { " or " } else { " | " })
|
||||
}
|
||||
|
||||
fn clean_function_name(name: &str) -> String {
|
||||
// Convert from camel case to snake case.
|
||||
let mut fn_name = name.to_case(convert_case::Case::Snake);
|
||||
// Clean the fn name.
|
||||
if fn_name.starts_with("last_seg_") {
|
||||
fn_name = fn_name.replace("last_seg_", "last_segment_");
|
||||
} else if fn_name.contains("_2_d") {
|
||||
fn_name = fn_name.replace("_2_d", "_2d");
|
||||
} else if fn_name.contains("_3_d") {
|
||||
fn_name = fn_name.replace("_3_d", "_3d");
|
||||
} else if fn_name == "seg_ang" {
|
||||
fn_name = "segment_angle".to_string();
|
||||
} else if fn_name == "seg_len" {
|
||||
fn_name = "segment_length".to_string();
|
||||
} else if fn_name.starts_with("seg_") {
|
||||
fn_name = fn_name.replace("seg_", "segment_");
|
||||
}
|
||||
|
||||
fn_name
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_markdown_docs() {
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
|
||||
// Generate the index which is the table of contents.
|
||||
generate_index(&combined, &kcl_std).unwrap();
|
||||
|
||||
for key in combined.keys().sorted() {
|
||||
let internal_fn = combined.get(key).unwrap();
|
||||
generate_function(internal_fn.clone(), &kcl_std).unwrap();
|
||||
}
|
||||
generate_index(&kcl_std).unwrap();
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
match d {
|
||||
DocData::Fn(f) => generate_function_from_kcl(f, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Const(c) => generate_const_from_kcl(c, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name()).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name(), &combined).unwrap(),
|
||||
DocData::Ty(t) => generate_type_from_kcl(t, d.file_name(), d.example_name(), &kcl_std).unwrap(),
|
||||
DocData::Mod(m) => generate_mod_from_kcl(m, d.file_name()).unwrap(),
|
||||
}
|
||||
}
|
||||
generate_mod_from_kcl(&kcl_std, "modules/std".to_owned(), &combined).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_json_schema() {
|
||||
// If this test fails and you've modified the AST or something else which affects the json repr
|
||||
// of stdlib functions, you should rerun the test with `EXPECTORATE=overwrite` to create new
|
||||
// test data, then check `/docs/kcl-std/std.json` to ensure the changes are expected.
|
||||
// Alternatively, run `just redo-kcl-stdlib-docs` (make sure to have just installed).
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
let json_data: Vec<_> = combined
|
||||
.keys()
|
||||
.sorted()
|
||||
.map(|key| {
|
||||
let internal_fn = combined.get(key).unwrap();
|
||||
internal_fn.to_json().unwrap()
|
||||
})
|
||||
.collect();
|
||||
expectorate::assert_contents(
|
||||
"../../docs/kcl-std/std.json",
|
||||
&serde_json::to_string_pretty(&json_data).unwrap(),
|
||||
);
|
||||
generate_mod_from_kcl(&kcl_std, "modules/std".to_owned()).unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
|
@ -302,6 +302,7 @@ impl DocData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(super) fn summary(&self) -> Option<&String> {
|
||||
match self {
|
||||
DocData::Fn(f) => f.summary.as_ref(),
|
||||
@ -462,6 +463,7 @@ impl ModData {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn find_by_name(&self, name: &str) -> Option<&DocData> {
|
||||
if let Some(result) = self
|
||||
.children
|
||||
@ -812,6 +814,7 @@ impl ArgData {
|
||||
return Some((index + n - 1, snippet));
|
||||
}
|
||||
match self.ty.as_deref() {
|
||||
Some("Sketch") if self.kind == ArgKind::Special => None,
|
||||
Some(s) if s.starts_with("number") => Some((index, format!(r#"{label}${{{}:10}}"#, index))),
|
||||
Some("Point2d") => Some((index + 1, format!(r#"{label}[${{{}:0}}, ${{{}:0}}]"#, index, index + 1))),
|
||||
Some("Point3d") => Some((
|
||||
@ -827,7 +830,7 @@ impl ArgData {
|
||||
Some("Sketch") | Some("Sketch | Helix") => Some((index, format!(r#"{label}${{{index}:sketch000}}"#))),
|
||||
Some("Edge") => Some((index, format!(r#"{label}${{{index}:tag_or_edge_fn}}"#))),
|
||||
Some("[Edge; 1+]") => Some((index, format!(r#"{label}[${{{index}:tag_or_edge_fn}}]"#))),
|
||||
Some("Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
Some("Plane") | Some("Solid | Plane") => Some((index, format!(r#"{label}${{{}:XY}}"#, index))),
|
||||
Some("[tag; 2]") => Some((
|
||||
index + 1,
|
||||
format!(r#"{label}[${{{}:tag}}, ${{{}:tag}}]"#, index, index + 1),
|
||||
@ -989,7 +992,7 @@ trait ApplyMeta {
|
||||
}
|
||||
|
||||
let mut summary = None;
|
||||
let mut description = None;
|
||||
let mut description: Option<String> = None;
|
||||
let mut example: Option<(String, ExampleProperties)> = None;
|
||||
let mut examples = Vec::new();
|
||||
for l in comments.iter().filter(|l| l.starts_with("///")).map(|l| {
|
||||
@ -999,22 +1002,6 @@ trait ApplyMeta {
|
||||
&l[3..]
|
||||
}
|
||||
}) {
|
||||
if description.is_none() && summary.is_none() {
|
||||
summary = Some(l.to_owned());
|
||||
continue;
|
||||
}
|
||||
if description.is_none() {
|
||||
if l.is_empty() {
|
||||
description = Some(String::new());
|
||||
} else {
|
||||
description = summary;
|
||||
summary = None;
|
||||
let d = description.as_mut().unwrap();
|
||||
d.push('\n');
|
||||
d.push_str(l);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
#[allow(clippy::manual_strip)]
|
||||
if l.starts_with("```") {
|
||||
if let Some((e, p)) = example {
|
||||
@ -1050,12 +1037,36 @@ trait ApplyMeta {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// An empty line outside of an example. This either starts the description (with or
|
||||
// without a summary) or adds a blank line to the description.
|
||||
if l.is_empty() {
|
||||
match &mut description {
|
||||
Some(d) => {
|
||||
d.push('\n');
|
||||
}
|
||||
None => description = Some(String::new()),
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Our first line, start the summary.
|
||||
if description.is_none() && summary.is_none() {
|
||||
summary = Some(l.to_owned());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Append the line to either the description or summary.
|
||||
match &mut description {
|
||||
Some(d) => {
|
||||
d.push_str(l);
|
||||
d.push('\n');
|
||||
}
|
||||
None => unreachable!(),
|
||||
None => {
|
||||
let s = summary.as_mut().unwrap();
|
||||
s.push(' ');
|
||||
s.push_str(l);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(example.is_none());
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -67,6 +67,7 @@ pub struct TcpRead {
|
||||
|
||||
/// Occurs when client couldn't read from the WebSocket to the engine.
|
||||
// #[derive(Debug)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum WebSocketReadError {
|
||||
/// Could not read a message due to WebSocket errors.
|
||||
Read(tokio_tungstenite::tungstenite::Error),
|
||||
@ -206,7 +207,7 @@ impl EngineConnection {
|
||||
async fn inner_send_to_engine(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
|
||||
let msg = serde_json::to_string(&request).map_err(|e| anyhow!("could not serialize json: {e}"))?;
|
||||
tcp_write
|
||||
.send(WsMsg::Text(msg))
|
||||
.send(WsMsg::Text(msg.into()))
|
||||
.await
|
||||
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
|
||||
Ok(())
|
||||
@ -216,19 +217,17 @@ impl EngineConnection {
|
||||
async fn inner_send_to_engine_binary(request: WebSocketRequest, tcp_write: &mut WebSocketTcpWrite) -> Result<()> {
|
||||
let msg = bson::to_vec(&request).map_err(|e| anyhow!("could not serialize bson: {e}"))?;
|
||||
tcp_write
|
||||
.send(WsMsg::Binary(msg))
|
||||
.send(WsMsg::Binary(msg.into()))
|
||||
.await
|
||||
.map_err(|e| anyhow!("could not send json over websocket: {e}"))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn new(ws: reqwest::Upgraded) -> Result<EngineConnection> {
|
||||
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig {
|
||||
let wsconfig = tokio_tungstenite::tungstenite::protocol::WebSocketConfig::default()
|
||||
// 4294967296 bytes, which is around 4.2 GB.
|
||||
max_message_size: Some(usize::MAX),
|
||||
max_frame_size: Some(usize::MAX),
|
||||
..Default::default()
|
||||
};
|
||||
.max_message_size(Some(usize::MAX))
|
||||
.max_frame_size(Some(usize::MAX));
|
||||
|
||||
let ws_stream = tokio_tungstenite::WebSocketStream::from_raw_socket(
|
||||
ws,
|
||||
|
@ -80,12 +80,12 @@ impl ResponseContext {
|
||||
}
|
||||
|
||||
// Add a response to the context.
|
||||
pub async fn send_response(&self, data: js_sys::Uint8Array) -> Result<(), JsValue> {
|
||||
pub async fn send_response(&self, data: js_sys::Uint8Array) {
|
||||
let ws_result: WebSocketResponse = match bson::from_slice(&data.to_vec()) {
|
||||
Ok(res) => res,
|
||||
Err(_) => {
|
||||
// We don't care about the error if we can't parse it.
|
||||
return Ok(());
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
@ -96,13 +96,11 @@ impl ResponseContext {
|
||||
|
||||
let Some(id) = id else {
|
||||
// We only care if we have an id.
|
||||
return Ok(());
|
||||
return;
|
||||
};
|
||||
|
||||
// Add this response to our responses.
|
||||
self.add(id, ws_result.clone()).await;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,10 @@ pub enum KclError {
|
||||
#[error("value already defined: {details:?}")]
|
||||
ValueAlreadyDefined { details: KclErrorDetails },
|
||||
#[error("undefined value: {details:?}")]
|
||||
UndefinedValue { details: KclErrorDetails },
|
||||
UndefinedValue {
|
||||
details: KclErrorDetails,
|
||||
name: Option<String>,
|
||||
},
|
||||
#[error("invalid expression: {details:?}")]
|
||||
InvalidExpression { details: KclErrorDetails },
|
||||
#[error("engine: {details:?}")]
|
||||
@ -451,8 +454,8 @@ impl KclError {
|
||||
KclError::Lexical { details }
|
||||
}
|
||||
|
||||
pub fn new_undefined_value(details: KclErrorDetails) -> KclError {
|
||||
KclError::UndefinedValue { details }
|
||||
pub fn new_undefined_value(details: KclErrorDetails, name: Option<String>) -> KclError {
|
||||
KclError::UndefinedValue { details, name }
|
||||
}
|
||||
|
||||
pub fn new_type(details: KclErrorDetails) -> KclError {
|
||||
@ -491,7 +494,7 @@ impl KclError {
|
||||
KclError::Io { details: e } => e.source_ranges.clone(),
|
||||
KclError::Unexpected { details: e } => e.source_ranges.clone(),
|
||||
KclError::ValueAlreadyDefined { details: e } => e.source_ranges.clone(),
|
||||
KclError::UndefinedValue { details: e } => e.source_ranges.clone(),
|
||||
KclError::UndefinedValue { details: e, .. } => e.source_ranges.clone(),
|
||||
KclError::InvalidExpression { details: e } => e.source_ranges.clone(),
|
||||
KclError::Engine { details: e } => e.source_ranges.clone(),
|
||||
KclError::Internal { details: e } => e.source_ranges.clone(),
|
||||
@ -509,7 +512,7 @@ impl KclError {
|
||||
KclError::Io { details: e } => &e.message,
|
||||
KclError::Unexpected { details: e } => &e.message,
|
||||
KclError::ValueAlreadyDefined { details: e } => &e.message,
|
||||
KclError::UndefinedValue { details: e } => &e.message,
|
||||
KclError::UndefinedValue { details: e, .. } => &e.message,
|
||||
KclError::InvalidExpression { details: e } => &e.message,
|
||||
KclError::Engine { details: e } => &e.message,
|
||||
KclError::Internal { details: e } => &e.message,
|
||||
@ -526,7 +529,7 @@ impl KclError {
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => e.backtrace.clone(),
|
||||
@ -544,7 +547,7 @@ impl KclError {
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => {
|
||||
@ -562,30 +565,6 @@ impl KclError {
|
||||
new
|
||||
}
|
||||
|
||||
pub(crate) fn set_last_backtrace_fn_name(&self, last_fn_name: Option<String>) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
KclError::Lexical { details: e }
|
||||
| KclError::Syntax { details: e }
|
||||
| KclError::Semantic { details: e }
|
||||
| KclError::ImportCycle { details: e }
|
||||
| KclError::Type { details: e }
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => {
|
||||
if let Some(item) = e.backtrace.last_mut() {
|
||||
item.fn_name = last_fn_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new
|
||||
}
|
||||
|
||||
pub(crate) fn add_unwind_location(&self, last_fn_name: Option<String>, source_range: SourceRange) -> Self {
|
||||
let mut new = self.clone();
|
||||
match &mut new {
|
||||
@ -597,7 +576,7 @@ impl KclError {
|
||||
| KclError::Io { details: e }
|
||||
| KclError::Unexpected { details: e }
|
||||
| KclError::ValueAlreadyDefined { details: e }
|
||||
| KclError::UndefinedValue { details: e }
|
||||
| KclError::UndefinedValue { details: e, .. }
|
||||
| KclError::InvalidExpression { details: e }
|
||||
| KclError::Engine { details: e }
|
||||
| KclError::Internal { details: e } => {
|
||||
|
@ -676,6 +676,7 @@ impl EdgeCut {
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ArtifactGraph {
|
||||
map: IndexMap<ArtifactId, Artifact>,
|
||||
pub(super) item_count: usize,
|
||||
}
|
||||
|
||||
impl ArtifactGraph {
|
||||
@ -711,10 +712,10 @@ pub(super) fn build_artifact_graph(
|
||||
artifact_commands: &[ArtifactCommand],
|
||||
responses: &IndexMap<Uuid, WebSocketResponse>,
|
||||
ast: &Node<Program>,
|
||||
cached_body_items: usize,
|
||||
exec_artifacts: &mut IndexMap<ArtifactId, Artifact>,
|
||||
initial_graph: ArtifactGraph,
|
||||
) -> Result<ArtifactGraph, KclError> {
|
||||
let item_count = initial_graph.item_count;
|
||||
let mut map = initial_graph.into_map();
|
||||
|
||||
let mut path_to_plane_id_map = FnvHashMap::default();
|
||||
@ -725,7 +726,7 @@ pub(super) fn build_artifact_graph(
|
||||
for exec_artifact in exec_artifacts.values_mut() {
|
||||
// Note: We only have access to the new AST. So if these artifacts
|
||||
// somehow came from cached AST, this won't fill in anything.
|
||||
fill_in_node_paths(exec_artifact, ast, cached_body_items);
|
||||
fill_in_node_paths(exec_artifact, ast, item_count);
|
||||
}
|
||||
|
||||
for artifact_command in artifact_commands {
|
||||
@ -752,7 +753,7 @@ pub(super) fn build_artifact_graph(
|
||||
&flattened_responses,
|
||||
&path_to_plane_id_map,
|
||||
ast,
|
||||
cached_body_items,
|
||||
item_count,
|
||||
exec_artifacts,
|
||||
)?;
|
||||
for artifact in artifact_updates {
|
||||
@ -765,7 +766,10 @@ pub(super) fn build_artifact_graph(
|
||||
merge_artifact_into_map(&mut map, exec_artifact.clone());
|
||||
}
|
||||
|
||||
Ok(ArtifactGraph { map })
|
||||
Ok(ArtifactGraph {
|
||||
map,
|
||||
item_count: item_count + ast.body.len(),
|
||||
})
|
||||
}
|
||||
|
||||
/// These may have been created with placeholder `CodeRef`s because we didn't
|
||||
|
@ -6,25 +6,31 @@ use itertools::{EitherOrBoth, Itertools};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
execution::{annotations, memory::Stack, state::ModuleInfoMap, EnvironmentRef, ExecState, ExecutorSettings},
|
||||
execution::{
|
||||
annotations,
|
||||
memory::Stack,
|
||||
state::{self as exec_state, ModuleInfoMap},
|
||||
EnvironmentRef, ExecutorSettings,
|
||||
},
|
||||
parsing::ast::types::{Annotation, Node, Program},
|
||||
walk::Node as WalkNode,
|
||||
ExecOutcome, ExecutorContext,
|
||||
};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// A static mutable lock for updating the last successful execution state for the cache.
|
||||
static ref OLD_AST: Arc<RwLock<Option<OldAstState>>> = Default::default();
|
||||
static ref OLD_AST: Arc<RwLock<Option<GlobalState>>> = Default::default();
|
||||
// The last successful run's memory. Not cleared after an unssuccessful run.
|
||||
static ref PREV_MEMORY: Arc<RwLock<Option<(Stack, ModuleInfoMap)>>> = Default::default();
|
||||
}
|
||||
|
||||
/// Read the old ast memory from the lock.
|
||||
pub(crate) async fn read_old_ast() -> Option<OldAstState> {
|
||||
pub(super) async fn read_old_ast() -> Option<GlobalState> {
|
||||
let old_ast = OLD_AST.read().await;
|
||||
old_ast.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_ast(old_state: OldAstState) {
|
||||
pub(super) async fn write_old_ast(old_state: GlobalState) {
|
||||
let mut old_ast = OLD_AST.write().await;
|
||||
*old_ast = Some(old_state);
|
||||
}
|
||||
@ -34,7 +40,7 @@ pub(crate) async fn read_old_memory() -> Option<(Stack, ModuleInfoMap)> {
|
||||
old_mem.clone()
|
||||
}
|
||||
|
||||
pub(super) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
|
||||
pub(crate) async fn write_old_memory(mem: (Stack, ModuleInfoMap)) {
|
||||
let mut old_mem = PREV_MEMORY.write().await;
|
||||
*old_mem = Some(mem);
|
||||
}
|
||||
@ -56,16 +62,73 @@ pub struct CacheInformation<'a> {
|
||||
pub settings: &'a ExecutorSettings,
|
||||
}
|
||||
|
||||
/// The old ast and program memory.
|
||||
/// The cached state of the whole program.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OldAstState {
|
||||
/// The ast.
|
||||
pub ast: Node<Program>,
|
||||
pub(super) struct GlobalState {
|
||||
pub(super) main: ModuleState,
|
||||
/// The exec state.
|
||||
pub exec_state: ExecState,
|
||||
pub(super) exec_state: exec_state::GlobalState,
|
||||
/// The last settings used for execution.
|
||||
pub settings: crate::execution::ExecutorSettings,
|
||||
pub result_env: EnvironmentRef,
|
||||
pub(super) settings: ExecutorSettings,
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
pub fn new(
|
||||
state: exec_state::ExecState,
|
||||
settings: ExecutorSettings,
|
||||
ast: Node<Program>,
|
||||
result_env: EnvironmentRef,
|
||||
) -> Self {
|
||||
Self {
|
||||
main: ModuleState {
|
||||
ast,
|
||||
exec_state: state.mod_local,
|
||||
result_env,
|
||||
},
|
||||
exec_state: state.global,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_settings(mut self, settings: ExecutorSettings) -> GlobalState {
|
||||
self.settings = settings;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn reconstitute_exec_state(&self) -> exec_state::ExecState {
|
||||
exec_state::ExecState {
|
||||
global: self.exec_state.clone(),
|
||||
mod_local: self.main.exec_state.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn into_exec_outcome(self, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self.main.exec_state.variables(self.main.result_env),
|
||||
filenames: self.exec_state.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.exec_state.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.exec_state.artifacts.commands,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.exec_state.artifacts.graph,
|
||||
errors: self.exec_state.errors,
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-module cached state
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
/// The AST of the module.
|
||||
pub(super) ast: Node<Program>,
|
||||
/// The ExecState of the module.
|
||||
pub(super) exec_state: exec_state::ModuleState,
|
||||
/// The memory env for the module.
|
||||
pub(super) result_env: EnvironmentRef,
|
||||
}
|
||||
|
||||
/// The result of a cache check.
|
||||
@ -79,9 +142,6 @@ pub(super) enum CacheResult {
|
||||
reapply_settings: bool,
|
||||
/// The program that needs to be executed.
|
||||
program: Node<Program>,
|
||||
/// The number of body items that were cached and omitted from the
|
||||
/// program that needs to be executed. Used to compute [`crate::NodePath`].
|
||||
cached_body_items: usize,
|
||||
},
|
||||
/// Check only the imports, and not the main program.
|
||||
/// Before sending this we already checked the main program and it is the same.
|
||||
@ -146,7 +206,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
|
||||
// We know they have the same imports because the ast is the same.
|
||||
// If we have no imports, we can skip this.
|
||||
if !old.ast.has_import_statements() {
|
||||
println!("No imports, no need to check.");
|
||||
return CacheResult::NoAction(reapply_settings);
|
||||
}
|
||||
|
||||
@ -194,7 +253,6 @@ pub(super) async fn get_changed_program(old: CacheInformation<'_>, new: CacheInf
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new.ast.clone(),
|
||||
cached_body_items: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -223,7 +281,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: true,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: 0,
|
||||
};
|
||||
}
|
||||
|
||||
@ -244,7 +301,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: true,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Greater => {
|
||||
@ -261,7 +317,6 @@ fn generate_changed_program(old_ast: Node<Program>, mut new_ast: Node<Program>,
|
||||
clear_scene: false,
|
||||
reapply_settings,
|
||||
program: new_ast,
|
||||
cached_body_items: old_ast.body.len(),
|
||||
}
|
||||
}
|
||||
std::cmp::Ordering::Equal => {
|
||||
@ -600,7 +655,6 @@ startSketchOn(XY)
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new_program.ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -639,7 +693,6 @@ startSketchOn(XY)
|
||||
clear_scene: true,
|
||||
reapply_settings: true,
|
||||
program: new_program.ast,
|
||||
cached_body_items: 0,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
use indexmap::IndexMap;
|
||||
use schemars::JsonSchema;
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{types::NumericType, ArtifactId, KclValue};
|
||||
use crate::{ModuleId, SourceRange};
|
||||
use crate::{ModuleId, NodePath, SourceRange};
|
||||
|
||||
/// A CAD modeling operation for display in the feature tree, AKA operations
|
||||
/// timeline.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Operation {
|
||||
@ -18,6 +17,8 @@ pub enum Operation {
|
||||
unlabeled_arg: Option<OpArg>,
|
||||
/// The labeled keyword arguments to the function.
|
||||
labeled_args: IndexMap<String, OpArg>,
|
||||
/// The node path of the operation in the source code.
|
||||
node_path: NodePath,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
/// True if the operation resulted in an error.
|
||||
@ -28,6 +29,8 @@ pub enum Operation {
|
||||
GroupBegin {
|
||||
/// The details of the group.
|
||||
group: Group,
|
||||
/// The node path of the operation in the source code.
|
||||
node_path: NodePath,
|
||||
/// The source range of the operation in the source code.
|
||||
source_range: SourceRange,
|
||||
},
|
||||
@ -64,7 +67,7 @@ impl Operation {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
@ -95,7 +98,7 @@ pub enum Group {
|
||||
}
|
||||
|
||||
/// An argument to a CAD modeling operation.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpArg {
|
||||
@ -119,7 +122,7 @@ fn is_false(b: &bool) -> bool {
|
||||
|
||||
/// A KCL value used in Operations. `ArtifactId`s are used to refer to the
|
||||
/// actual scene objects. Any data not needed in the UI may be omitted.
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum OpKclValue {
|
||||
@ -177,21 +180,21 @@ pub enum OpKclValue {
|
||||
|
||||
pub type OpKclObjectFields = IndexMap<String, OpKclValue>;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpSketch {
|
||||
artifact_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpSolid {
|
||||
artifact_id: ArtifactId,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
|
||||
#[ts(export_to = "Operation.ts")]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct OpHelix {
|
||||
|
@ -19,8 +19,8 @@ use crate::{
|
||||
parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, Expr, IfExpression, ImportPath, ImportSelector, ItemVisibility, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program,
|
||||
TagDeclarator, Type, UnaryExpression, UnaryOperator,
|
||||
LiteralValue, MemberExpression, Name, Node, NodeRef, ObjectExpression, PipeExpression, Program, TagDeclarator,
|
||||
Type, UnaryExpression, UnaryOperator,
|
||||
},
|
||||
source_range::SourceRange,
|
||||
std::args::TyF64,
|
||||
@ -164,10 +164,13 @@ impl ExecutorContext {
|
||||
let mut mod_value = mem.get_from(&mod_name, env_ref, import_item.into(), 0).cloned();
|
||||
|
||||
if value.is_err() && ty.is_err() && mod_value.is_err() {
|
||||
return Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("{} is not defined in module", import_item.name.name),
|
||||
vec![SourceRange::from(&import_item.name)],
|
||||
)));
|
||||
return Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("{} is not defined in module", import_item.name.name),
|
||||
vec![SourceRange::from(&import_item.name)],
|
||||
),
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
// Check that the item is allowed to be imported (in at least one namespace).
|
||||
@ -301,7 +304,12 @@ impl ExecutorContext {
|
||||
|
||||
let annotations = &variable_declaration.outer_attrs;
|
||||
|
||||
let value = self
|
||||
// During the evaluation of the variable's RHS, set context that this is all happening inside a variable
|
||||
// declaration, for the given name. This helps improve user-facing error messages.
|
||||
let lhs = variable_declaration.inner.name().to_owned();
|
||||
let prev_being_declared = exec_state.mod_local.being_declared.take();
|
||||
exec_state.mod_local.being_declared = Some(lhs);
|
||||
let rhs_result = self
|
||||
.execute_expr(
|
||||
&variable_declaration.declaration.init,
|
||||
exec_state,
|
||||
@ -309,10 +317,14 @@ impl ExecutorContext {
|
||||
annotations,
|
||||
StatementKind::Declaration { name: &var_name },
|
||||
)
|
||||
.await?;
|
||||
.await;
|
||||
// Declaration over, so unset this context.
|
||||
exec_state.mod_local.being_declared = prev_being_declared;
|
||||
let rhs = rhs_result?;
|
||||
|
||||
exec_state
|
||||
.mut_stack()
|
||||
.add(var_name.clone(), value.clone(), source_range)?;
|
||||
.add(var_name.clone(), rhs.clone(), source_range)?;
|
||||
|
||||
// Track exports.
|
||||
if let ItemVisibility::Export = variable_declaration.visibility {
|
||||
@ -326,7 +338,7 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
// Variable declaration can be the return value of a module.
|
||||
last_expr = matches!(body_type, BodyType::Root).then_some(value);
|
||||
last_expr = matches!(body_type, BodyType::Root).then_some(rhs);
|
||||
}
|
||||
BodyItem::TypeDeclaration(ty) => {
|
||||
let metadata = Metadata::from(&**ty);
|
||||
@ -635,7 +647,12 @@ impl ExecutorContext {
|
||||
Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state),
|
||||
Expr::TagDeclarator(tag) => tag.execute(exec_state).await?,
|
||||
Expr::Name(name) => {
|
||||
let value = name.get_result(exec_state, self).await?.clone();
|
||||
let being_declared = exec_state.mod_local.being_declared.clone();
|
||||
let value = name
|
||||
.get_result(exec_state, self)
|
||||
.await
|
||||
.map_err(|e| var_in_own_ref_err(e, &being_declared))?
|
||||
.clone();
|
||||
if let KclValue::Module { value: module_id, meta } = value {
|
||||
self.exec_module_for_result(
|
||||
module_id,
|
||||
@ -722,7 +739,7 @@ impl ExecutorContext {
|
||||
Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
|
||||
Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
|
||||
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
||||
Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
|
||||
Expr::LabelledExpression(expr) => {
|
||||
@ -741,6 +758,24 @@ impl ExecutorContext {
|
||||
}
|
||||
}
|
||||
|
||||
/// If the error is about an undefined name, and that name matches the name being defined,
|
||||
/// make the error message more specific.
|
||||
fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
|
||||
let KclError::UndefinedValue { name, mut details } = e else {
|
||||
return e;
|
||||
};
|
||||
// TODO after June 26th: replace this with a let-chain,
|
||||
// which will be available in Rust 1.88
|
||||
// https://rust-lang.github.io/rfcs/2497-if-let-chains.html
|
||||
match (&being_declared, &name) {
|
||||
(Some(name0), Some(name1)) if name0 == name1 => {
|
||||
details.message = format!("You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
KclError::UndefinedValue { details, name }
|
||||
}
|
||||
|
||||
impl Node<AscribedExpression> {
|
||||
#[async_recursion]
|
||||
pub async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
@ -790,7 +825,7 @@ impl BinaryPart {
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
|
||||
BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
|
||||
BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
|
||||
BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
|
||||
}
|
||||
@ -802,6 +837,17 @@ impl Node<Name> {
|
||||
&self,
|
||||
exec_state: &'a mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<&'a KclValue, KclError> {
|
||||
let being_declared = exec_state.mod_local.being_declared.clone();
|
||||
self.get_result_inner(exec_state, ctx)
|
||||
.await
|
||||
.map_err(|e| var_in_own_ref_err(e, &being_declared))
|
||||
}
|
||||
|
||||
async fn get_result_inner<'a>(
|
||||
&self,
|
||||
exec_state: &'a mut ExecState,
|
||||
ctx: &ExecutorContext,
|
||||
) -> Result<&'a KclValue, KclError> {
|
||||
if self.abs_path {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
@ -896,16 +942,14 @@ impl Node<Name> {
|
||||
}
|
||||
|
||||
impl Node<MemberExpression> {
|
||||
fn get_result(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
|
||||
async fn get_result(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
let property = Property::try_from(self.computed, self.property.clone(), exec_state, self.into())?;
|
||||
let object = match &self.object {
|
||||
// TODO: Don't use recursion here, use a loop.
|
||||
MemberObject::MemberExpression(member_expr) => member_expr.get_result(exec_state)?,
|
||||
MemberObject::Identifier(identifier) => {
|
||||
let value = exec_state.stack().get(&identifier.name, identifier.into())?;
|
||||
value.clone()
|
||||
}
|
||||
let meta = Metadata {
|
||||
source_range: SourceRange::from(self),
|
||||
};
|
||||
let object = ctx
|
||||
.execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
|
||||
.await?;
|
||||
|
||||
// Check the property and object match -- e.g. ints for arrays, strs for objects.
|
||||
match (object, property, self.computed) {
|
||||
@ -913,10 +957,13 @@ impl Node<MemberExpression> {
|
||||
if let Some(value) = map.get(&property) {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("Property '{property}' not found in object"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("Property '{property}' not found in object"),
|
||||
vec![self.clone().into()],
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
(KclValue::Object { .. }, Property::String(property), true) => {
|
||||
@ -938,10 +985,13 @@ impl Node<MemberExpression> {
|
||||
if let Some(value) = value_of_arr {
|
||||
Ok(value.to_owned())
|
||||
} else {
|
||||
Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("The array doesn't have any item at index {index}"),
|
||||
vec![self.clone().into()],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("The array doesn't have any item at index {index}"),
|
||||
vec![self.clone().into()],
|
||||
),
|
||||
None,
|
||||
))
|
||||
}
|
||||
}
|
||||
// Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
|
||||
@ -1311,7 +1361,7 @@ pub(crate) async fn execute_pipe_body(
|
||||
// Now that we've evaluated the first child expression in the pipeline, following child expressions
|
||||
// should use the previous child expression for %.
|
||||
// This means there's no more need for the previous pipe_value from the parent AST node above this one.
|
||||
let previous_pipe_value = std::mem::replace(&mut exec_state.mod_local.pipe_value, Some(output));
|
||||
let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
|
||||
// Evaluate remaining elements.
|
||||
let result = inner_execute_pipe_body(exec_state, body, ctx).await;
|
||||
// Restore the previous pipe value.
|
||||
@ -1861,7 +1911,6 @@ d = b + c
|
||||
project_directory: Some(crate::TypedPath(tmpdir.path().into())),
|
||||
..Default::default()
|
||||
},
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
let mut exec_state = ExecState::new(&exec_ctxt);
|
||||
|
@ -2,7 +2,6 @@ use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cad_op::{Group, OpArg, OpKclValue, Operation},
|
||||
@ -15,7 +14,7 @@ use crate::{
|
||||
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
|
||||
source_range::SourceRange,
|
||||
std::StdFn,
|
||||
CompilationError,
|
||||
CompilationError, NodePath,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -184,40 +183,6 @@ impl<'a> From<&'a FunctionSource> for FunctionDefinition<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&dyn StdLibFn> for FunctionDefinition<'static> {
|
||||
fn from(value: &dyn StdLibFn) -> Self {
|
||||
let mut input_arg = None;
|
||||
let mut named_args = IndexMap::new();
|
||||
for a in value.args(false) {
|
||||
if !a.label_required {
|
||||
input_arg = Some((a.name.clone(), None));
|
||||
continue;
|
||||
}
|
||||
|
||||
named_args.insert(
|
||||
a.name.clone(),
|
||||
(
|
||||
if a.required {
|
||||
None
|
||||
} else {
|
||||
Some(DefaultParamVal::none())
|
||||
},
|
||||
None,
|
||||
),
|
||||
);
|
||||
}
|
||||
FunctionDefinition {
|
||||
input_arg,
|
||||
named_args,
|
||||
return_type: None,
|
||||
deprecated: value.deprecated(),
|
||||
include_in_feature_tree: value.feature_tree_operation(),
|
||||
is_std: true,
|
||||
body: FunctionBody::Rust(value.std_lib_fn()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<CallExpressionKw> {
|
||||
#[async_recursion]
|
||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
@ -274,59 +239,44 @@ impl Node<CallExpressionKw> {
|
||||
exec_state.pipe_value().map(|v| Arg::new(v.clone(), callsite)),
|
||||
);
|
||||
|
||||
match ctx.stdlib.get_rust_function(fn_name) {
|
||||
Some(func) => {
|
||||
let def: FunctionDefinition = (&*func).into();
|
||||
// All std lib functions return a value, so the unwrap is safe.
|
||||
def.call_kw(Some(func.name()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map(Option::unwrap)
|
||||
.map_err(|e| {
|
||||
// This is used for the backtrace display. We don't add
|
||||
// another location the way we do for user-defined
|
||||
// functions because the error uses the Args, which
|
||||
// already points here.
|
||||
e.set_last_backtrace_fn_name(Some(func.name()))
|
||||
})
|
||||
}
|
||||
None => {
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||
// Clone the function so that we can use a mutable reference to
|
||||
// exec_state.
|
||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||
|
||||
let Some(fn_src) = func.as_function() else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"cannot call this because it isn't a function".to_string(),
|
||||
vec![callsite],
|
||||
)));
|
||||
};
|
||||
let Some(fn_src) = func.as_function() else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"cannot call this because it isn't a function".to_string(),
|
||||
vec![callsite],
|
||||
)));
|
||||
};
|
||||
|
||||
let return_value = fn_src
|
||||
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
//
|
||||
// TODO: Use the name that the function was defined
|
||||
// with, not the identifier it was used with.
|
||||
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
|
||||
})?;
|
||||
let return_value = fn_src
|
||||
.call_kw(Some(fn_name.to_string()), exec_state, ctx, args, callsite)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
// Add the call expression to the source ranges.
|
||||
//
|
||||
// TODO: Use the name that the function was defined
|
||||
// with, not the identifier it was used with.
|
||||
e.add_unwind_location(Some(fn_name.name.name.clone()), callsite)
|
||||
})?;
|
||||
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
||||
// We want to send the source range of the original function.
|
||||
if let KclValue::Function { meta, .. } = func {
|
||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||
};
|
||||
KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
))
|
||||
})?;
|
||||
let result = return_value.ok_or_else(move || {
|
||||
let mut source_ranges: Vec<SourceRange> = vec![callsite];
|
||||
// We want to send the source range of the original function.
|
||||
if let KclValue::Function { meta, .. } = func {
|
||||
source_ranges = meta.iter().map(|m| m.source_range).collect();
|
||||
};
|
||||
KclError::new_undefined_value(
|
||||
KclErrorDetails::new(
|
||||
format!("Result of user-defined function {} is undefined", fn_name),
|
||||
source_ranges,
|
||||
),
|
||||
None,
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
@ -372,6 +322,7 @@ impl FunctionDefinition<'_> {
|
||||
.unlabeled_kw_arg_unconverted()
|
||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.value), arg.source_range)),
|
||||
labeled_args: op_labeled_args,
|
||||
node_path: NodePath::placeholder(),
|
||||
source_range: callsite,
|
||||
is_error: false,
|
||||
})
|
||||
@ -387,6 +338,7 @@ impl FunctionDefinition<'_> {
|
||||
.map(|arg| OpArg::new(OpKclValue::from(&arg.1.value), arg.1.source_range)),
|
||||
labeled_args: op_labeled_args,
|
||||
},
|
||||
node_path: NodePath::placeholder(),
|
||||
source_range: callsite,
|
||||
});
|
||||
|
||||
@ -600,30 +552,33 @@ fn type_check_params_kw(
|
||||
|
||||
for (label, arg) in &mut args.labeled {
|
||||
match fn_def.named_args.get(label) {
|
||||
Some((_, ty)) => {
|
||||
if let Some(ty) = ty {
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"{label} requires a value with type `{}`, but found {}",
|
||||
ty,
|
||||
arg.value.human_friendly_type(),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||
}
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
message,
|
||||
vec![arg.source_range],
|
||||
))
|
||||
})?;
|
||||
Some((def, ty)) => {
|
||||
// For optional args, passing None should be the same as not passing an arg.
|
||||
if !(def.is_some() && matches!(arg.value, KclValue::KclNone { .. })) {
|
||||
if let Some(ty) = ty {
|
||||
arg.value = arg
|
||||
.value
|
||||
.coerce(
|
||||
&RuntimeType::from_parsed(ty.clone(), exec_state, arg.source_range).map_err(|e| KclError::new_semantic(e.into()))?,
|
||||
true,
|
||||
exec_state,
|
||||
)
|
||||
.map_err(|e| {
|
||||
let mut message = format!(
|
||||
"{label} requires a value with type `{}`, but found {}",
|
||||
ty,
|
||||
arg.value.human_friendly_type(),
|
||||
);
|
||||
if let Some(ty) = e.explicit_coercion {
|
||||
// TODO if we have access to the AST for the argument we could choose which example to suggest.
|
||||
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
|
||||
}
|
||||
KclError::new_semantic(KclErrorDetails::new(
|
||||
message,
|
||||
vec![arg.source_range],
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
@ -703,7 +658,7 @@ fn type_check_params_kw(
|
||||
exec_state.err(CompilationError::err(
|
||||
arg.source_range,
|
||||
format!(
|
||||
"{} expects an unlabeled first parameter (`@{name}`), but it is labelled in the call",
|
||||
"{} expects an unlabeled first argument (`@{name}`), but it is labelled in the call",
|
||||
fn_name
|
||||
.map(|n| format!("The function `{}`", n))
|
||||
.unwrap_or_else(|| "This function".to_owned()),
|
||||
@ -937,7 +892,6 @@ mod test {
|
||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::Mock,
|
||||
};
|
||||
|
@ -24,6 +24,7 @@ type Point3D = kcmc::shared::Point3d<f64>;
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum Geometry {
|
||||
Sketch(Sketch),
|
||||
Solid(Solid),
|
||||
@ -52,6 +53,7 @@ impl Geometry {
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum GeometryWithImportedGeometry {
|
||||
Sketch(Sketch),
|
||||
Solid(Solid),
|
||||
|
@ -31,7 +31,7 @@ pub(crate) type Universe = HashMap<String, DependencyInfo>;
|
||||
/// run concurrently. Each "stage" is blocking in this model, which will
|
||||
/// change in the future. Don't use this function widely, yet.
|
||||
#[allow(clippy::iter_over_hash_type)]
|
||||
pub fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
||||
pub(crate) fn import_graph(progs: &Universe, ctx: &ExecutorContext) -> Result<Vec<Vec<String>>, KclError> {
|
||||
let mut graph = Graph::new();
|
||||
|
||||
for (name, (_, _, path, repr)) in progs.iter() {
|
||||
@ -120,7 +120,7 @@ fn topsort(all_modules: &[&str], graph: Graph) -> Result<Vec<Vec<String>>, KclEr
|
||||
|
||||
type ImportDependencies = Vec<(String, AstNode<ImportStatement>, ModulePath)>;
|
||||
|
||||
pub(crate) fn import_dependencies(
|
||||
fn import_dependencies(
|
||||
path: &ModulePath,
|
||||
repr: &ModuleRepr,
|
||||
ctx: &ExecutorContext,
|
@ -367,10 +367,10 @@ impl ProgramMemory {
|
||||
|
||||
let name = var.trim_start_matches(TYPE_PREFIX).trim_start_matches(MODULE_PREFIX);
|
||||
|
||||
Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("`{name}` is not defined"),
|
||||
vec![source_range],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(format!("`{name}` is not defined"), vec![source_range]),
|
||||
Some(name.to_owned()),
|
||||
))
|
||||
}
|
||||
|
||||
/// Iterate over all key/value pairs in the specified environment which satisfy the provided
|
||||
@ -488,10 +488,10 @@ impl ProgramMemory {
|
||||
};
|
||||
}
|
||||
|
||||
Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
format!("`{}` is not defined", var),
|
||||
vec![],
|
||||
)))
|
||||
Err(KclError::new_undefined_value(
|
||||
KclErrorDetails::new(format!("`{}` is not defined", var), vec![]),
|
||||
Some(var.to_owned()),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use artifact::{Artifact, ArtifactCommand, ArtifactGraph, CodeRef, StartSketchOnFace, StartSketchOnPlane};
|
||||
use cache::OldAstState;
|
||||
use cache::GlobalState;
|
||||
pub use cache::{bust_cache, clear_mem_cache};
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub use cad_op::{Group, Operation};
|
||||
@ -27,13 +27,12 @@ use serde::{Deserialize, Serialize};
|
||||
pub use state::{ExecState, MetaSettings};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
use crate::execution::artifact::build_artifact_graph;
|
||||
use crate::{
|
||||
engine::EngineManager,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
execution::{
|
||||
cache::{CacheInformation, CacheResult},
|
||||
import_graph::{Universe, UniverseMap},
|
||||
typed_path::TypedPath,
|
||||
types::{UnitAngle, UnitLen},
|
||||
},
|
||||
@ -41,8 +40,6 @@ use crate::{
|
||||
modules::{ModuleId, ModulePath, ModuleRepr},
|
||||
parsing::ast::types::{Expr, ImportPath, NodeRef},
|
||||
source_range::SourceRange,
|
||||
std::StdLib,
|
||||
walk::{Universe, UniverseMap},
|
||||
CompilationError, ExecError, KclErrorWithOutputs,
|
||||
};
|
||||
|
||||
@ -56,6 +53,7 @@ pub mod fn_call;
|
||||
mod geometry;
|
||||
mod id_generator;
|
||||
mod import;
|
||||
mod import_graph;
|
||||
pub(crate) mod kcl_value;
|
||||
mod memory;
|
||||
mod state;
|
||||
@ -273,7 +271,6 @@ pub enum ContextType {
|
||||
pub struct ExecutorContext {
|
||||
pub engine: Arc<Box<dyn EngineManager>>,
|
||||
pub fs: Arc<FileManager>,
|
||||
pub stdlib: Arc<StdLib>,
|
||||
pub settings: ExecutorSettings,
|
||||
pub context_type: ContextType,
|
||||
}
|
||||
@ -412,7 +409,6 @@ impl ExecutorContext {
|
||||
Ok(Self {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
})
|
||||
@ -423,7 +419,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Live,
|
||||
}
|
||||
@ -436,7 +431,6 @@ impl ExecutorContext {
|
||||
crate::engine::conn_mock::EngineConnection::new().await.unwrap(),
|
||||
)),
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings: settings.unwrap_or_default(),
|
||||
context_type: ContextType::Mock,
|
||||
}
|
||||
@ -447,7 +441,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs,
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings,
|
||||
context_type: ContextType::Mock,
|
||||
}
|
||||
@ -458,7 +451,6 @@ impl ExecutorContext {
|
||||
ExecutorContext {
|
||||
engine,
|
||||
fs: Arc::new(FileManager::new()),
|
||||
stdlib: Arc::new(StdLib::new()),
|
||||
settings: Default::default(),
|
||||
context_type: ContextType::MockCustomForwarded,
|
||||
}
|
||||
@ -575,7 +567,7 @@ impl ExecutorContext {
|
||||
// part of the scene).
|
||||
exec_state.mut_stack().push_new_env_for_scope();
|
||||
|
||||
let result = self.inner_run(&program, 0, &mut exec_state, true).await?;
|
||||
let result = self.inner_run(&program, &mut exec_state, true).await?;
|
||||
|
||||
// Restore any temporary variables, then save any newly created variables back to
|
||||
// memory in case another run wants to use them. Note this is just saved to the preserved
|
||||
@ -583,7 +575,7 @@ impl ExecutorContext {
|
||||
|
||||
let mut mem = exec_state.stack().clone();
|
||||
let module_infos = exec_state.global.module_infos.clone();
|
||||
let outcome = exec_state.to_mock_exec_outcome(result.0).await;
|
||||
let outcome = exec_state.to_mock_exec_outcome(result.0, self).await;
|
||||
|
||||
mem.squash_env(result.0);
|
||||
cache::write_old_memory((mem, module_infos)).await;
|
||||
@ -600,169 +592,179 @@ impl ExecutorContext {
|
||||
.map(|s| s.default_length_units)
|
||||
.map(kcmc::units::UnitLength::from);
|
||||
|
||||
let (program, mut exec_state, preserve_mem, cached_body_items, imports_info) = if let Some(OldAstState {
|
||||
ast: old_ast,
|
||||
exec_state: mut old_state,
|
||||
settings: old_settings,
|
||||
result_env,
|
||||
}) =
|
||||
cache::read_old_ast().await
|
||||
{
|
||||
let old = CacheInformation {
|
||||
ast: &old_ast,
|
||||
settings: &old_settings,
|
||||
};
|
||||
let new = CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &self.settings,
|
||||
};
|
||||
|
||||
// Get the program that actually changed from the old and new information.
|
||||
let (clear_scene, program, body_items, import_check_info) = match cache::get_changed_program(old, new).await
|
||||
{
|
||||
CacheResult::ReExecute {
|
||||
clear_scene,
|
||||
reapply_settings,
|
||||
program: changed_program,
|
||||
cached_body_items,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator(), grid_scale)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, cached_body_items, None)
|
||||
} else {
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
cached_body_items,
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::CheckImportsOnly {
|
||||
reapply_settings,
|
||||
ast: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator(), grid_scale)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, old_ast.body.len(), None)
|
||||
} else {
|
||||
// We need to check our imports to see if they changed.
|
||||
let mut new_exec_state = ExecState::new(self);
|
||||
let (new_universe, new_universe_map) = self.get_universe(&program, &mut new_exec_state).await?;
|
||||
let mut clear_scene = false;
|
||||
|
||||
let mut keys = new_universe.keys().clone().collect::<Vec<_>>();
|
||||
keys.sort();
|
||||
for key in keys {
|
||||
let (_, id, _, _) = &new_universe[key];
|
||||
if let (Some(source0), Some(source1)) =
|
||||
(old_state.get_source(*id), new_exec_state.get_source(*id))
|
||||
{
|
||||
if source0.source != source1.source {
|
||||
clear_scene = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !clear_scene {
|
||||
// Return early we don't need to clear the scene.
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
old_ast.body.len(),
|
||||
// We only care about this if we are clearing the scene.
|
||||
if clear_scene {
|
||||
Some((new_universe, new_universe_map, new_exec_state))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::NoAction(true) => {
|
||||
if self
|
||||
.engine
|
||||
.reapply_settings(&self.settings, Default::default(), old_state.id_generator(), grid_scale)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// We need to update the old ast state with the new settings!!
|
||||
cache::write_old_ast(OldAstState {
|
||||
ast: old_ast,
|
||||
exec_state: old_state.clone(),
|
||||
settings: self.settings.clone(),
|
||||
result_env,
|
||||
})
|
||||
.await;
|
||||
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
(true, program, old_ast.body.len(), None)
|
||||
}
|
||||
CacheResult::NoAction(false) => {
|
||||
let outcome = old_state.to_exec_outcome(result_env).await;
|
||||
return Ok(outcome);
|
||||
}
|
||||
};
|
||||
|
||||
let (exec_state, preserve_mem, universe_info) =
|
||||
if let Some((new_universe, new_universe_map, mut new_exec_state)) = import_check_info {
|
||||
// Clear the scene if the imports changed.
|
||||
self.send_clear_scene(&mut new_exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
(new_exec_state, false, Some((new_universe, new_universe_map)))
|
||||
} else if clear_scene {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
let mut exec_state = old_state;
|
||||
exec_state.reset(self);
|
||||
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
(exec_state, false, None)
|
||||
} else {
|
||||
old_state.mut_stack().restore_env(result_env);
|
||||
|
||||
(old_state, true, None)
|
||||
let (program, exec_state, result) = match cache::read_old_ast().await {
|
||||
Some(mut cached_state) => {
|
||||
let old = CacheInformation {
|
||||
ast: &cached_state.main.ast,
|
||||
settings: &cached_state.settings,
|
||||
};
|
||||
let new = CacheInformation {
|
||||
ast: &program.ast,
|
||||
settings: &self.settings,
|
||||
};
|
||||
|
||||
(program, exec_state, preserve_mem, body_items, universe_info)
|
||||
} else {
|
||||
let mut exec_state = ExecState::new(self);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
(program, exec_state, false, 0, None)
|
||||
};
|
||||
// Get the program that actually changed from the old and new information.
|
||||
let (clear_scene, program, import_check_info) = match cache::get_changed_program(old, new).await {
|
||||
CacheResult::ReExecute {
|
||||
clear_scene,
|
||||
reapply_settings,
|
||||
program: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, None)
|
||||
} else {
|
||||
(
|
||||
clear_scene,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
None,
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::CheckImportsOnly {
|
||||
reapply_settings,
|
||||
ast: changed_program,
|
||||
} => {
|
||||
if reapply_settings
|
||||
&& self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
(true, program, None)
|
||||
} else {
|
||||
// We need to check our imports to see if they changed.
|
||||
let mut new_exec_state = ExecState::new(self);
|
||||
let (new_universe, new_universe_map) =
|
||||
self.get_universe(&program, &mut new_exec_state).await?;
|
||||
|
||||
let result = self
|
||||
.run_concurrent(&program, cached_body_items, &mut exec_state, imports_info, preserve_mem)
|
||||
.await;
|
||||
let clear_scene = new_universe.keys().any(|key| {
|
||||
let id = new_universe[key].1;
|
||||
match (
|
||||
cached_state.exec_state.get_source(id),
|
||||
new_exec_state.global.get_source(id),
|
||||
) {
|
||||
(Some(s0), Some(s1)) => s0.source != s1.source,
|
||||
_ => false,
|
||||
}
|
||||
});
|
||||
|
||||
if !clear_scene {
|
||||
// Return early we don't need to clear the scene.
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
|
||||
(
|
||||
true,
|
||||
crate::Program {
|
||||
ast: changed_program,
|
||||
original_file_contents: program.original_file_contents,
|
||||
},
|
||||
Some((new_universe, new_universe_map, new_exec_state)),
|
||||
)
|
||||
}
|
||||
}
|
||||
CacheResult::NoAction(true) => {
|
||||
if self
|
||||
.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
Default::default(),
|
||||
&mut cached_state.main.exec_state.id_generator,
|
||||
grid_scale,
|
||||
)
|
||||
.await
|
||||
.is_ok()
|
||||
{
|
||||
// We need to update the old ast state with the new settings!!
|
||||
cache::write_old_ast(GlobalState::with_settings(
|
||||
cached_state.clone(),
|
||||
self.settings.clone(),
|
||||
))
|
||||
.await;
|
||||
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
(true, program, None)
|
||||
}
|
||||
CacheResult::NoAction(false) => {
|
||||
return Ok(cached_state.into_exec_outcome(self).await);
|
||||
}
|
||||
};
|
||||
|
||||
let (exec_state, result) = match import_check_info {
|
||||
Some((new_universe, new_universe_map, mut new_exec_state)) => {
|
||||
// Clear the scene if the imports changed.
|
||||
self.send_clear_scene(&mut new_exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self
|
||||
.run_concurrent(
|
||||
&program,
|
||||
&mut new_exec_state,
|
||||
Some((new_universe, new_universe_map)),
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
|
||||
(new_exec_state, result)
|
||||
}
|
||||
None if clear_scene => {
|
||||
// Pop the execution state, since we are starting fresh.
|
||||
let mut exec_state = cached_state.reconstitute_exec_state();
|
||||
exec_state.reset(self);
|
||||
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
|
||||
|
||||
(exec_state, result)
|
||||
}
|
||||
None => {
|
||||
let mut exec_state = cached_state.reconstitute_exec_state();
|
||||
exec_state.mut_stack().restore_env(cached_state.main.result_env);
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, true).await;
|
||||
|
||||
(exec_state, result)
|
||||
}
|
||||
};
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
None => {
|
||||
let mut exec_state = ExecState::new(self);
|
||||
self.send_clear_scene(&mut exec_state, Default::default())
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
let result = self.run_concurrent(&program, &mut exec_state, None, false).await;
|
||||
|
||||
(program, exec_state, result)
|
||||
}
|
||||
};
|
||||
|
||||
if result.is_err() {
|
||||
cache::bust_cache().await;
|
||||
@ -772,15 +774,15 @@ impl ExecutorContext {
|
||||
let result = result?;
|
||||
|
||||
// Save this as the last successful execution to the cache.
|
||||
cache::write_old_ast(OldAstState {
|
||||
ast: program.ast,
|
||||
exec_state: exec_state.clone(),
|
||||
settings: self.settings.clone(),
|
||||
result_env: result.0,
|
||||
})
|
||||
cache::write_old_ast(GlobalState::new(
|
||||
exec_state.clone(),
|
||||
self.settings.clone(),
|
||||
program.ast,
|
||||
result.0,
|
||||
))
|
||||
.await;
|
||||
|
||||
let outcome = exec_state.to_exec_outcome(result.0).await;
|
||||
let outcome = exec_state.to_exec_outcome(result.0, self).await;
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
@ -795,11 +797,11 @@ impl ExecutorContext {
|
||||
program: &crate::Program,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
self.run_concurrent(program, 0, exec_state, None, false).await
|
||||
self.run_concurrent(program, exec_state, None, false).await
|
||||
}
|
||||
|
||||
/// Perform the execution of a program using a concurrent
|
||||
/// execution model. This has the same signature as [Self::run].
|
||||
/// execution model.
|
||||
///
|
||||
/// You can optionally pass in some initialization memory for partial
|
||||
/// execution.
|
||||
@ -808,13 +810,12 @@ impl ExecutorContext {
|
||||
pub async fn run_concurrent(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
universe_info: Option<(Universe, UniverseMap)>,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
// Reuse our cached universe if we have one.
|
||||
#[allow(unused_variables)]
|
||||
|
||||
let (universe, universe_map) = if let Some((universe, universe_map)) = universe_info {
|
||||
(universe, universe_map)
|
||||
} else {
|
||||
@ -828,29 +829,8 @@ impl ExecutorContext {
|
||||
.await
|
||||
.map_err(KclErrorWithOutputs::no_outputs)?;
|
||||
|
||||
for modules in crate::walk::import_graph(&universe, self)
|
||||
.map_err(|err| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
err,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes.clone(),
|
||||
)
|
||||
})?
|
||||
for modules in import_graph::import_graph(&universe, self)
|
||||
.map_err(|err| exec_state.error_with_outputs(err, default_planes.clone()))?
|
||||
.into_iter()
|
||||
{
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -872,32 +852,14 @@ impl ExecutorContext {
|
||||
let module_path = module_path.clone();
|
||||
let source_range = SourceRange::from(import_stmt);
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
match &module_path {
|
||||
ModulePath::Main => {
|
||||
// This should never happen.
|
||||
}
|
||||
ModulePath::Local { value, .. } => {
|
||||
// We only want to display the top-level module imports in
|
||||
// the Feature Tree, not transitive imports.
|
||||
if universe_map.contains_key(value) {
|
||||
exec_state.global.operations.push(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
},
|
||||
source_range,
|
||||
});
|
||||
// Due to concurrent execution, we cannot easily
|
||||
// group operations by module. So we leave the
|
||||
// group empty and close it immediately.
|
||||
exec_state.global.operations.push(Operation::GroupEnd);
|
||||
}
|
||||
}
|
||||
ModulePath::Std { .. } => {
|
||||
// We don't want to display stdlib in the Feature Tree.
|
||||
}
|
||||
}
|
||||
self.add_import_module_ops(
|
||||
exec_state,
|
||||
program,
|
||||
module_id,
|
||||
&module_path,
|
||||
source_range,
|
||||
&universe_map,
|
||||
);
|
||||
|
||||
let repr = repr.clone();
|
||||
let exec_state = exec_state.clone();
|
||||
@ -936,7 +898,6 @@ impl ExecutorContext {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
//set.spawn(async move {
|
||||
let mut exec_state = exec_state;
|
||||
let exec_ctxt = exec_ctxt;
|
||||
|
||||
@ -1006,33 +967,13 @@ impl ExecutorContext {
|
||||
exec_state.global.module_infos[&module_id].restore_repr(repr);
|
||||
}
|
||||
Err(e) => {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
return Err(KclErrorWithOutputs::new(
|
||||
e,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
));
|
||||
return Err(exec_state.error_with_outputs(e, default_planes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.inner_run(program, cached_body_items, exec_state, preserve_mem)
|
||||
.await
|
||||
self.inner_run(program, exec_state, preserve_mem).await
|
||||
}
|
||||
|
||||
/// Get the universe & universe map of the program.
|
||||
@ -1048,7 +989,7 @@ impl ExecutorContext {
|
||||
|
||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||
|
||||
let root_imports = crate::walk::import_universe(
|
||||
let root_imports = import_graph::import_universe(
|
||||
self,
|
||||
&ModulePath::Main,
|
||||
&ModuleRepr::Kcl(program.ast.clone(), None),
|
||||
@ -1056,51 +997,89 @@ impl ExecutorContext {
|
||||
exec_state,
|
||||
)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
println!("Error: {err:?}");
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
err,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
)
|
||||
})?;
|
||||
.map_err(|err| exec_state.error_with_outputs(err, default_planes))?;
|
||||
|
||||
Ok((universe, root_imports))
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
exec_state: &mut ExecState,
|
||||
program: &crate::Program,
|
||||
module_id: ModuleId,
|
||||
module_path: &ModulePath,
|
||||
source_range: SourceRange,
|
||||
universe_map: &UniverseMap,
|
||||
) {
|
||||
match module_path {
|
||||
ModulePath::Main => {
|
||||
// This should never happen.
|
||||
}
|
||||
ModulePath::Local { value, .. } => {
|
||||
// We only want to display the top-level module imports in
|
||||
// the Feature Tree, not transitive imports.
|
||||
if universe_map.contains_key(value) {
|
||||
use crate::NodePath;
|
||||
|
||||
let node_path = if source_range.is_top_level_module() {
|
||||
let cached_body_items = exec_state.global.artifacts.cached_body_items();
|
||||
NodePath::from_range(&program.ast, cached_body_items, source_range).unwrap_or_default()
|
||||
} else {
|
||||
// The frontend doesn't care about paths in
|
||||
// files other than the top-level module.
|
||||
NodePath::placeholder()
|
||||
};
|
||||
|
||||
exec_state.push_op(Operation::GroupBegin {
|
||||
group: Group::ModuleInstance {
|
||||
name: value.file_name().unwrap_or_default(),
|
||||
module_id,
|
||||
},
|
||||
node_path,
|
||||
source_range,
|
||||
});
|
||||
// Due to concurrent execution, we cannot easily
|
||||
// group operations by module. So we leave the
|
||||
// group empty and close it immediately.
|
||||
exec_state.push_op(Operation::GroupEnd);
|
||||
}
|
||||
}
|
||||
ModulePath::Std { .. } => {
|
||||
// We don't want to display stdlib in the Feature Tree.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
fn add_import_module_ops(
|
||||
&self,
|
||||
_exec_state: &mut ExecState,
|
||||
_program: &crate::Program,
|
||||
_module_id: ModuleId,
|
||||
_module_path: &ModulePath,
|
||||
_source_range: SourceRange,
|
||||
_universe_map: &UniverseMap,
|
||||
) {
|
||||
}
|
||||
|
||||
/// Perform the execution of a program. Accept all possible parameters and
|
||||
/// output everything.
|
||||
async fn inner_run(
|
||||
&self,
|
||||
program: &crate::Program,
|
||||
cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<(EnvironmentRef, Option<ModelingSessionData>), KclErrorWithOutputs> {
|
||||
let _stats = crate::log::LogPerfStats::new("Interpretation");
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
let grid_scale = program
|
||||
.meta_settings()
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|s| s.default_length_units)
|
||||
.map(kcmc::units::UnitLength::from);
|
||||
|
||||
// Re-apply the settings, in case the cache was busted.
|
||||
self.engine
|
||||
.reapply_settings(
|
||||
&self.settings,
|
||||
@ -1113,7 +1092,7 @@ impl ExecutorContext {
|
||||
|
||||
let default_planes = self.engine.get_default_planes().read().await.clone();
|
||||
let result = self
|
||||
.execute_and_build_graph(&program.ast, cached_body_items, exec_state, preserve_mem)
|
||||
.execute_and_build_graph(&program.ast, exec_state, preserve_mem)
|
||||
.await;
|
||||
|
||||
crate::log::log(format!(
|
||||
@ -1122,28 +1101,7 @@ impl ExecutorContext {
|
||||
));
|
||||
crate::log::log(format!("Engine stats: {:?}", self.engine.stats()));
|
||||
|
||||
let env_ref = result.map_err(|e| {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = exec_state
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
e,
|
||||
exec_state.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
exec_state.global.artifact_graph.clone(),
|
||||
module_id_to_module_path,
|
||||
exec_state.global.id_to_source.clone(),
|
||||
default_planes.clone(),
|
||||
)
|
||||
})?;
|
||||
let env_ref = result.map_err(|e| exec_state.error_with_outputs(e, default_planes))?;
|
||||
|
||||
if !self.is_mock() {
|
||||
let mut mem = exec_state.stack().deep_clone();
|
||||
@ -1160,13 +1118,17 @@ impl ExecutorContext {
|
||||
async fn execute_and_build_graph(
|
||||
&self,
|
||||
program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
#[cfg_attr(not(feature = "artifact-graph"), expect(unused))] cached_body_items: usize,
|
||||
exec_state: &mut ExecState,
|
||||
preserve_mem: bool,
|
||||
) -> Result<EnvironmentRef, KclError> {
|
||||
// Don't early return! We need to build other outputs regardless of
|
||||
// whether execution failed.
|
||||
|
||||
// Because of execution caching, we may start with operations from a
|
||||
// previous run.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
let start_op = exec_state.global.artifacts.operations.len();
|
||||
|
||||
self.eval_prelude(exec_state, SourceRange::from(program).start_as_range())
|
||||
.await?;
|
||||
|
||||
@ -1180,6 +1142,29 @@ impl ExecutorContext {
|
||||
)
|
||||
.await;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
// Fill in NodePath for operations.
|
||||
let cached_body_items = exec_state.global.artifacts.cached_body_items();
|
||||
for op in exec_state.global.artifacts.operations.iter_mut().skip(start_op) {
|
||||
match op {
|
||||
Operation::StdLibCall {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
}
|
||||
| Operation::GroupBegin {
|
||||
node_path,
|
||||
source_range,
|
||||
..
|
||||
} => {
|
||||
node_path.fill_placeholder(program, cached_body_items, *source_range);
|
||||
}
|
||||
Operation::GroupEnd => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all the async commands completed.
|
||||
self.engine.ensure_async_commands_completed().await?;
|
||||
|
||||
@ -1187,40 +1172,9 @@ impl ExecutorContext {
|
||||
// and should be dropped.
|
||||
self.engine.clear_queues().await;
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
{
|
||||
let new_commands = self.engine.take_artifact_commands().await;
|
||||
let new_responses = self.engine.take_responses().await;
|
||||
let initial_graph = exec_state.global.artifact_graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
let graph_result = build_artifact_graph(
|
||||
&new_commands,
|
||||
&new_responses,
|
||||
program,
|
||||
cached_body_items,
|
||||
&mut exec_state.global.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
exec_state.global.artifact_commands.extend(new_commands);
|
||||
exec_state.global.artifact_responses.extend(new_responses);
|
||||
|
||||
match graph_result {
|
||||
Ok(artifact_graph) => {
|
||||
exec_state.global.artifact_graph = artifact_graph;
|
||||
exec_result.map(|(_, env_ref, _)| env_ref)
|
||||
}
|
||||
Err(err) => {
|
||||
// Prefer the exec error.
|
||||
exec_result.and(Err(err))
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
{
|
||||
exec_result.map(|(_, env_ref, _)| env_ref)
|
||||
match exec_state.build_artifact_graph(&self.engine, program).await {
|
||||
Ok(_) => exec_result.map(|(_, env_ref, _)| env_ref),
|
||||
Err(err) => exec_result.and(Err(err)),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1407,7 +1361,6 @@ pub(crate) async fn parse_execute_with_project_dir(
|
||||
})?,
|
||||
)),
|
||||
fs: Arc::new(crate::fs::FileManager::new()),
|
||||
stdlib: Arc::new(crate::std::StdLib::new()),
|
||||
settings: ExecutorSettings {
|
||||
project_directory,
|
||||
..Default::default()
|
||||
@ -2089,7 +2042,7 @@ notPipeSub = 1 |> identity(!%))";
|
||||
// a runtime error instead.
|
||||
parse_execute(code11).await.unwrap_err(),
|
||||
KclError::new_syntax(KclErrorDetails::new(
|
||||
"There was an unexpected !. Try removing it.".to_owned(),
|
||||
"There was an unexpected `!`. Try removing it.".to_owned(),
|
||||
vec![SourceRange::new(56, 57, ModuleId::default())],
|
||||
))
|
||||
);
|
||||
@ -2223,7 +2176,7 @@ w = f() + f()
|
||||
}
|
||||
|
||||
// Get the id_generator from the first execution.
|
||||
let id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
let id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
|
||||
|
||||
let code = r#"sketch001 = startSketchOn(XZ)
|
||||
|> startProfile(at = [62.74, 206.13])
|
||||
@ -2244,7 +2197,7 @@ w = f() + f()
|
||||
// Execute the program.
|
||||
ctx.run_with_caching(program).await.unwrap();
|
||||
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().exec_state.mod_local.id_generator;
|
||||
let new_id_generator = cache::read_old_ast().await.unwrap().main.exec_state.id_generator;
|
||||
|
||||
assert_eq!(id_generator, new_id_generator);
|
||||
}
|
||||
|
@ -12,19 +12,19 @@ use uuid::Uuid;
|
||||
use crate::execution::{Artifact, ArtifactCommand, ArtifactGraph, ArtifactId};
|
||||
use crate::{
|
||||
errors::{KclError, KclErrorDetails, Severity},
|
||||
exec::DefaultPlanes,
|
||||
execution::{
|
||||
annotations,
|
||||
cad_op::Operation,
|
||||
id_generator::IdGenerator,
|
||||
memory::{ProgramMemory, Stack},
|
||||
types,
|
||||
types::NumericType,
|
||||
types::{self, NumericType},
|
||||
EnvironmentRef, ExecOutcome, ExecutorSettings, KclValue, UnitAngle, UnitLen,
|
||||
},
|
||||
modules::{ModuleId, ModuleInfo, ModuleLoader, ModulePath, ModuleRepr, ModuleSource},
|
||||
parsing::ast::types::Annotation,
|
||||
parsing::ast::types::{Annotation, NodeRef},
|
||||
source_range::SourceRange,
|
||||
CompilationError,
|
||||
CompilationError, EngineManager, ExecutorContext, KclErrorWithOutputs,
|
||||
};
|
||||
|
||||
/// State for executing a program.
|
||||
@ -32,7 +32,6 @@ use crate::{
|
||||
pub struct ExecState {
|
||||
pub(super) global: GlobalState,
|
||||
pub(super) mod_local: ModuleState,
|
||||
pub(super) exec_context: Option<super::ExecutorContext>,
|
||||
}
|
||||
|
||||
pub type ModuleInfoMap = IndexMap<ModuleId, ModuleInfo>;
|
||||
@ -45,33 +44,39 @@ pub(super) struct GlobalState {
|
||||
pub id_to_source: IndexMap<ModuleId, ModuleSource>,
|
||||
/// Map from module ID to module info.
|
||||
pub module_infos: ModuleInfoMap,
|
||||
/// Output map of UUIDs to artifacts.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub artifact_graph: ArtifactGraph,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub operations: Vec<Operation>,
|
||||
/// Module loader.
|
||||
pub mod_loader: ModuleLoader,
|
||||
/// Errors and warnings.
|
||||
pub errors: Vec<CompilationError>,
|
||||
#[allow(dead_code)]
|
||||
pub artifacts: ArtifactState,
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {
|
||||
/// Output map of UUIDs to artifacts.
|
||||
pub artifacts: IndexMap<ArtifactId, Artifact>,
|
||||
/// Output commands to allow building the artifact graph by the caller.
|
||||
/// These are accumulated in the [`ExecutorContext`] but moved here for
|
||||
/// convenience of the execution cache.
|
||||
pub commands: Vec<ArtifactCommand>,
|
||||
/// Responses from the engine for `artifact_commands`. We need to cache
|
||||
/// this so that we can build the artifact graph. These are accumulated in
|
||||
/// the [`ExecutorContext`] but moved here for convenience of the execution
|
||||
/// cache.
|
||||
pub responses: IndexMap<Uuid, WebSocketResponse>,
|
||||
/// Output artifact graph.
|
||||
pub graph: ArtifactGraph,
|
||||
/// Operations that have been performed in execution order, for display in
|
||||
/// the Feature Tree.
|
||||
pub operations: Vec<Operation>,
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(super) struct ArtifactState {}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct ModuleState {
|
||||
/// The id generator for this module.
|
||||
@ -80,6 +85,11 @@ pub(super) struct ModuleState {
|
||||
/// The current value of the pipe operator returned from the previous
|
||||
/// expression. If we're not currently in a pipeline, this will be None.
|
||||
pub pipe_value: Option<KclValue>,
|
||||
/// The closest variable declaration being executed in any parent node in the AST.
|
||||
/// This is used to provide better error messages, e.g. noticing when the user is trying
|
||||
/// to use the variable `length` inside the RHS of its own definition, like `length = tan(length)`.
|
||||
/// TODO: Make this a reference.
|
||||
pub being_declared: Option<String>,
|
||||
/// Identifiers that have been exported from the current module.
|
||||
pub module_exports: Vec<String>,
|
||||
/// Settings specified from annotations.
|
||||
@ -93,7 +103,6 @@ impl ExecState {
|
||||
ExecState {
|
||||
global: GlobalState::new(&exec_context.settings),
|
||||
mod_local: ModuleState::new(ModulePath::Main, ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,7 +112,6 @@ impl ExecState {
|
||||
*self = ExecState {
|
||||
global,
|
||||
mod_local: ModuleState::new(self.mod_local.path.clone(), ProgramMemory::new(), Default::default()),
|
||||
exec_context: Some(exec_context.clone()),
|
||||
};
|
||||
}
|
||||
|
||||
@ -125,45 +133,26 @@ impl ExecState {
|
||||
/// Convert to execution outcome when running in WebAssembly. We want to
|
||||
/// reduce the amount of data that crosses the WASM boundary as much as
|
||||
/// possible.
|
||||
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
pub async fn to_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
filenames: self.global.filenames(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: self.global.operations,
|
||||
operations: self.global.artifacts.operations,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: self.global.artifact_commands,
|
||||
artifact_commands: self.global.artifacts.commands,
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: self.global.artifact_graph,
|
||||
artifact_graph: self.global.artifacts.graph,
|
||||
errors: self.global.errors,
|
||||
filenames: self
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef) -> ExecOutcome {
|
||||
// Fields are opt-in so that we don't accidentally leak private internal
|
||||
// state when we add more to ExecState.
|
||||
pub async fn to_mock_exec_outcome(self, main_ref: EnvironmentRef, ctx: &ExecutorContext) -> ExecOutcome {
|
||||
ExecOutcome {
|
||||
variables: self
|
||||
.stack()
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect(),
|
||||
variables: self.mod_local.variables(main_ref),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
@ -172,11 +161,7 @@ impl ExecState {
|
||||
artifact_graph: Default::default(),
|
||||
errors: self.global.errors,
|
||||
filenames: Default::default(),
|
||||
default_planes: if let Some(ctx) = &self.exec_context {
|
||||
ctx.engine.get_default_planes().read().await.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
default_planes: ctx.engine.get_default_planes().read().await.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -199,12 +184,12 @@ impl ExecState {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn add_artifact(&mut self, artifact: Artifact) {
|
||||
let id = artifact.id();
|
||||
self.global.artifacts.insert(id, artifact);
|
||||
self.global.artifacts.artifacts.insert(id, artifact);
|
||||
}
|
||||
|
||||
pub(crate) fn push_op(&mut self, op: Operation) {
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.operations.push(op);
|
||||
self.global.artifacts.operations.push(op);
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
drop(op);
|
||||
}
|
||||
@ -246,10 +231,6 @@ impl ExecState {
|
||||
self.global.id_to_source.insert(id, source.clone());
|
||||
}
|
||||
|
||||
pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
|
||||
self.global.id_to_source.get(&id)
|
||||
}
|
||||
|
||||
pub(super) fn add_module(&mut self, id: ModuleId, path: ModulePath, repr: ModuleRepr) {
|
||||
debug_assert!(self.global.path_to_source_id.contains_key(&path));
|
||||
let module_info = ModuleInfo { id, repr, path };
|
||||
@ -295,6 +276,71 @@ impl ExecState {
|
||||
pub(crate) fn pipe_value(&self) -> Option<&KclValue> {
|
||||
self.mod_local.pipe_value.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn error_with_outputs(
|
||||
&self,
|
||||
error: KclError,
|
||||
default_planes: Option<DefaultPlanes>,
|
||||
) -> KclErrorWithOutputs {
|
||||
let module_id_to_module_path: IndexMap<ModuleId, ModulePath> = self
|
||||
.global
|
||||
.path_to_source_id
|
||||
.iter()
|
||||
.map(|(k, v)| ((*v), k.clone()))
|
||||
.collect();
|
||||
|
||||
KclErrorWithOutputs::new(
|
||||
error,
|
||||
self.errors().to_vec(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.operations.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.commands.clone(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
self.global.artifacts.graph.clone(),
|
||||
module_id_to_module_path,
|
||||
self.global.id_to_source.clone(),
|
||||
default_planes,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) async fn build_artifact_graph(
|
||||
&mut self,
|
||||
engine: &Arc<Box<dyn EngineManager>>,
|
||||
program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
) -> Result<(), KclError> {
|
||||
let new_commands = engine.take_artifact_commands().await;
|
||||
let new_responses = engine.take_responses().await;
|
||||
let initial_graph = self.global.artifacts.graph.clone();
|
||||
|
||||
// Build the artifact graph.
|
||||
let graph_result = crate::execution::artifact::build_artifact_graph(
|
||||
&new_commands,
|
||||
&new_responses,
|
||||
program,
|
||||
&mut self.global.artifacts.artifacts,
|
||||
initial_graph,
|
||||
);
|
||||
// Move the artifact commands and responses into ExecState to
|
||||
// simplify cache management and error creation.
|
||||
self.global.artifacts.commands.extend(new_commands);
|
||||
self.global.artifacts.responses.extend(new_responses);
|
||||
|
||||
let artifact_graph = graph_result?;
|
||||
self.global.artifacts.graph = artifact_graph;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "artifact-graph"))]
|
||||
pub(crate) async fn build_artifact_graph(
|
||||
&mut self,
|
||||
_engine: &Arc<Box<dyn EngineManager>>,
|
||||
_program: NodeRef<'_, crate::parsing::ast::types::Program>,
|
||||
) -> Result<(), KclError> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalState {
|
||||
@ -302,16 +348,7 @@ impl GlobalState {
|
||||
let mut global = GlobalState {
|
||||
path_to_source_id: Default::default(),
|
||||
module_infos: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifacts: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_commands: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_responses: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
artifact_graph: Default::default(),
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
operations: Default::default(),
|
||||
mod_loader: Default::default(),
|
||||
errors: Default::default(),
|
||||
id_to_source: Default::default(),
|
||||
@ -334,6 +371,21 @@ impl GlobalState {
|
||||
.insert(ModulePath::Local { value: root_path }, root_id);
|
||||
global
|
||||
}
|
||||
|
||||
pub(super) fn filenames(&self) -> IndexMap<ModuleId, ModulePath> {
|
||||
self.path_to_source_id.iter().map(|(k, v)| ((*v), k.clone())).collect()
|
||||
}
|
||||
|
||||
pub(super) fn get_source(&self, id: ModuleId) -> Option<&ModuleSource> {
|
||||
self.id_to_source.get(&id)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
impl ArtifactState {
|
||||
pub fn cached_body_items(&self) -> usize {
|
||||
self.graph.item_count
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleState {
|
||||
@ -342,6 +394,7 @@ impl ModuleState {
|
||||
id_generator: IdGenerator::new(module_id),
|
||||
stack: memory.new_stack(),
|
||||
pipe_value: Default::default(),
|
||||
being_declared: Default::default(),
|
||||
module_exports: Default::default(),
|
||||
explicit_length_units: false,
|
||||
path,
|
||||
@ -352,6 +405,13 @@ impl ModuleState {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn variables(&self, main_ref: EnvironmentRef) -> IndexMap<String, KclValue> {
|
||||
self.stack
|
||||
.find_all_in_env(main_ref)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
|
||||
|
@ -220,6 +220,7 @@ impl schemars::JsonSchema for TypedPath {
|
||||
///
|
||||
/// * Does **not** touch `..` or symlinks – call `canonicalize()` if you need that.
|
||||
/// * Returns an owned `PathBuf` only when normalisation was required.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn normalise_import<S: AsRef<str>>(raw: S) -> std::path::PathBuf {
|
||||
let s = raw.as_ref();
|
||||
// On Unix we need to swap `\` → `/`. On Windows we leave it alone.
|
||||
|
@ -187,7 +187,7 @@ impl RuntimeType {
|
||||
};
|
||||
RuntimeType::Primitive(PrimitiveType::Number(ty))
|
||||
}
|
||||
AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
|
||||
AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
|
||||
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
|
||||
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
|
||||
|
@ -247,17 +247,6 @@ impl ObjectProperty {
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
fn get_hover_value_for_position(&self, pos: usize, code: &str, opts: &HoverOpts) -> Option<Hover> {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => {
|
||||
member_expression.get_hover_value_for_position(pos, code, opts)
|
||||
}
|
||||
MemberObject::Identifier(_identifier) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberExpression {
|
||||
fn get_hover_value_for_position(&self, pos: usize, code: &str, opts: &HoverOpts) -> Option<Hover> {
|
||||
let object_source_range: SourceRange = self.object.clone().into();
|
||||
|
@ -175,11 +175,10 @@ impl Backend {
|
||||
zoo_client: kittycad::Client,
|
||||
can_send_telemetry: bool,
|
||||
) -> Result<Self, String> {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
|
||||
let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_args = get_arg_maps_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
|
||||
let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
|
||||
let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
|
||||
|
||||
Ok(Self {
|
||||
client,
|
||||
@ -1189,7 +1188,6 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
|
||||
async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
|
||||
// ADAM: This is the entrypoint.
|
||||
let mut completions = vec![CompletionItem {
|
||||
label: PIPE_OPERATOR.to_string(),
|
||||
label_details: None,
|
||||
@ -1635,16 +1633,8 @@ impl LanguageServer for Backend {
|
||||
}
|
||||
|
||||
/// Get completions from our stdlib.
|
||||
pub fn get_completions_from_stdlib(
|
||||
stdlib: &crate::std::StdLib,
|
||||
kcl_std: &ModData,
|
||||
) -> Result<HashMap<String, CompletionItem>> {
|
||||
pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
|
||||
let mut completions = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
|
||||
}
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
if let Some(ci) = d.to_completion_item() {
|
||||
@ -1661,13 +1651,8 @@ pub fn get_completions_from_stdlib(
|
||||
}
|
||||
|
||||
/// Get signatures from our stdlib.
|
||||
pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
|
||||
pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
|
||||
let mut signatures = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
|
||||
}
|
||||
|
||||
for d in kcl_std.all_docs() {
|
||||
if let Some(sig) = d.to_signature_help() {
|
||||
@ -1679,44 +1664,32 @@ pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData
|
||||
}
|
||||
|
||||
/// Get signatures from our stdlib.
|
||||
pub fn get_arg_maps_from_stdlib(
|
||||
stdlib: &crate::std::StdLib,
|
||||
kcl_std: &ModData,
|
||||
) -> HashMap<String, HashMap<String, String>> {
|
||||
pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, String>> {
|
||||
let mut result = HashMap::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
for internal_fn in combined.values() {
|
||||
let arg_map: HashMap<String, String> = internal_fn
|
||||
.args(false)
|
||||
.into_iter()
|
||||
for d in kcl_std.all_docs() {
|
||||
let crate::docs::kcl_doc::DocData::Fn(f) = d else {
|
||||
continue;
|
||||
};
|
||||
let arg_map: HashMap<String, String> = f
|
||||
.args
|
||||
.iter()
|
||||
.map(|data| {
|
||||
let mut tip = "```\n".to_owned();
|
||||
tip.push_str(&data.name.clone());
|
||||
if !data.required {
|
||||
tip.push('?');
|
||||
}
|
||||
if !data.type_.is_empty() {
|
||||
tip.push_str(": ");
|
||||
tip.push_str(&data.type_);
|
||||
}
|
||||
tip.push_str(&data.to_string());
|
||||
tip.push_str("\n```");
|
||||
if !data.description.is_empty() {
|
||||
if let Some(docs) = &data.docs {
|
||||
tip.push_str("\n\n");
|
||||
tip.push_str(&data.description);
|
||||
tip.push_str(docs);
|
||||
}
|
||||
(data.name, tip)
|
||||
(data.name.clone(), tip)
|
||||
})
|
||||
.collect();
|
||||
if !arg_map.is_empty() {
|
||||
result.insert(internal_fn.name(), arg_map);
|
||||
result.insert(f.name.clone(), arg_map);
|
||||
}
|
||||
}
|
||||
|
||||
for _d in kcl_std.all_docs() {
|
||||
// TODO add KCL std fns
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,10 @@ use tower_lsp::LanguageServer;
|
||||
|
||||
// Create a fake kcl lsp server for testing.
|
||||
pub async fn kcl_lsp_server(execute: bool) -> Result<crate::lsp::kcl::Backend> {
|
||||
let stdlib = crate::std::StdLib::new();
|
||||
let kcl_std = crate::docs::kcl_doc::walk_prelude();
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&stdlib, &kcl_std)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_args = crate::lsp::kcl::get_arg_maps_from_stdlib(&stdlib, &kcl_std);
|
||||
let stdlib_completions = crate::lsp::kcl::get_completions_from_stdlib(&kcl_std)?;
|
||||
let stdlib_signatures = crate::lsp::kcl::get_signatures_from_stdlib(&kcl_std);
|
||||
let stdlib_args = crate::lsp::kcl::get_arg_maps_from_stdlib(&kcl_std);
|
||||
|
||||
let zoo_client = crate::engine::new_zoo_client(None, None)?;
|
||||
|
||||
|
@ -928,7 +928,7 @@ startSketchOn(XY)
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("startSketchOn"));
|
||||
assert!(value.contains(": SketchSurface"));
|
||||
assert!(value.contains(": Plane | Face"));
|
||||
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
@ -1113,13 +1113,7 @@ async fn test_kcl_lsp_signature_help() {
|
||||
"Expected one signature, got {:?}",
|
||||
signature_help.signatures
|
||||
);
|
||||
assert_eq!(
|
||||
signature_help.signatures[0].label,
|
||||
r#"startSketchOn(
|
||||
@planeOrSolid: SketchData,
|
||||
face?: FaceTag,
|
||||
): SketchSurface"#
|
||||
);
|
||||
assert!(signature_help.signatures[0].label.starts_with("startSketchOn"));
|
||||
} else {
|
||||
panic!("Expected signature help");
|
||||
}
|
||||
@ -3884,7 +3878,7 @@ startSketchOn(XY)
|
||||
match hover.unwrap().contents {
|
||||
tower_lsp::lsp_types::HoverContents::Markup(tower_lsp::lsp_types::MarkupContent { value, .. }) => {
|
||||
assert!(value.contains("startSketchOn"));
|
||||
assert!(value.contains(": SketchSurface"));
|
||||
assert!(value.contains(": Plane | Face"));
|
||||
assert!(value.contains("Start a new 2-dimensional sketch on a specific"));
|
||||
}
|
||||
_ => unreachable!(),
|
||||
|
@ -4,7 +4,7 @@ use crate::parsing::ast::types::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryPart, BodyItem,
|
||||
CallExpressionKw, DefaultParamVal, ElseIf, Expr, ExpressionStatement, FunctionExpression, FunctionType, Identifier,
|
||||
IfExpression, ImportItem, ImportSelector, ImportStatement, ItemVisibility, KclNone, LabelledExpression, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name, ObjectExpression, ObjectProperty, Parameter,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, Name, ObjectExpression, ObjectProperty, Parameter,
|
||||
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, TagDeclarator, Type, TypeDeclaration,
|
||||
UnaryExpression, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
};
|
||||
@ -167,15 +167,6 @@ impl BinaryPart {
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
MemberObject::MemberExpression(me) => me.compute_digest(),
|
||||
MemberObject::Identifier(id) => id.compute_digest(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiteralIdentifier {
|
||||
pub fn compute_digest(&mut self) -> Digest {
|
||||
match self {
|
||||
@ -228,7 +219,7 @@ impl PrimitiveType {
|
||||
let mut hasher = Sha256::new();
|
||||
match self {
|
||||
PrimitiveType::Any => hasher.update(b"any"),
|
||||
PrimitiveType::Named(id) => hasher.update(id.compute_digest()),
|
||||
PrimitiveType::Named { id } => hasher.update(id.compute_digest()),
|
||||
PrimitiveType::String => hasher.update(b"string"),
|
||||
PrimitiveType::Number(suffix) => hasher.update(suffix.digestable_id()),
|
||||
PrimitiveType::Boolean => hasher.update(b"bool"),
|
||||
|
@ -2,7 +2,7 @@ pub(crate) mod digest;
|
||||
pub mod types;
|
||||
|
||||
use crate::{
|
||||
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier, MemberObject},
|
||||
parsing::ast::types::{BinaryPart, BodyItem, Expr, LiteralIdentifier},
|
||||
ModuleId,
|
||||
};
|
||||
|
||||
@ -57,15 +57,6 @@ impl BinaryPart {
|
||||
}
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => member_expression.module_id,
|
||||
MemberObject::Identifier(identifier) => identifier.module_id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiteralIdentifier {
|
||||
pub fn module_id(&self) -> ModuleId {
|
||||
match self {
|
||||
|
@ -25,7 +25,6 @@ pub use crate::parsing::ast::types::{
|
||||
none::KclNone,
|
||||
};
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{
|
||||
annotations,
|
||||
@ -454,7 +453,7 @@ impl Node<Program> {
|
||||
alpha: c.a,
|
||||
},
|
||||
};
|
||||
if colors.borrow().iter().any(|c| *c == color) {
|
||||
if colors.borrow().contains(&color) {
|
||||
return;
|
||||
}
|
||||
colors.borrow_mut().push(color);
|
||||
@ -529,7 +528,7 @@ impl Node<Program> {
|
||||
let new_color = csscolorparser::Color::new(color.red, color.green, color.blue, color.alpha);
|
||||
Ok(Some(ColorPresentation {
|
||||
// The label will be what they replace the color with.
|
||||
label: new_color.to_hex_string(),
|
||||
label: new_color.to_css_hex(),
|
||||
text_edit: None,
|
||||
additional_text_edits: None,
|
||||
}))
|
||||
@ -1973,31 +1972,6 @@ impl CallExpressionKw {
|
||||
}
|
||||
}
|
||||
|
||||
/// A function declaration.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Function {
|
||||
/// A stdlib function written in Rust (aka core lib).
|
||||
StdLib {
|
||||
/// The function.
|
||||
func: Box<dyn StdLibFn>,
|
||||
},
|
||||
/// A function that is defined in memory.
|
||||
#[default]
|
||||
InMemory,
|
||||
}
|
||||
|
||||
impl PartialEq for Function {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Function::StdLib { func: func1 }, Function::StdLib { func: func2 }) => func1.name() == func2.name(),
|
||||
(Function::InMemory, Function::InMemory) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, FromStr, Display)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@ -2782,47 +2756,6 @@ impl ObjectProperty {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum MemberObject {
|
||||
MemberExpression(BoxNode<MemberExpression>),
|
||||
Identifier(BoxNode<Identifier>),
|
||||
}
|
||||
|
||||
impl MemberObject {
|
||||
pub fn start(&self) -> usize {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => member_expression.start,
|
||||
MemberObject::Identifier(identifier) => identifier.start,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn end(&self) -> usize {
|
||||
match self {
|
||||
MemberObject::MemberExpression(member_expression) => member_expression.end,
|
||||
MemberObject::Identifier(identifier) => identifier.end,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn contains_range(&self, range: &SourceRange) -> bool {
|
||||
let sr = SourceRange::from(self);
|
||||
sr.contains_range(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MemberObject> for SourceRange {
|
||||
fn from(obj: MemberObject) -> Self {
|
||||
Self::new(obj.start(), obj.end(), obj.module_id())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MemberObject> for SourceRange {
|
||||
fn from(obj: &MemberObject) -> Self {
|
||||
Self::new(obj.start(), obj.end(), obj.module_id())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
@ -2868,7 +2801,7 @@ impl From<&LiteralIdentifier> for SourceRange {
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub struct MemberExpression {
|
||||
pub object: MemberObject,
|
||||
pub object: Expr,
|
||||
pub property: LiteralIdentifier,
|
||||
pub computed: bool,
|
||||
|
||||
@ -2890,12 +2823,7 @@ impl Node<MemberExpression> {
|
||||
impl MemberExpression {
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
match &mut self.object {
|
||||
MemberObject::MemberExpression(ref mut member_expression) => {
|
||||
member_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
MemberObject::Identifier(ref mut identifier) => identifier.rename(old_name, new_name),
|
||||
}
|
||||
self.object.rename_identifiers(old_name, new_name);
|
||||
|
||||
match &mut self.property {
|
||||
LiteralIdentifier::Identifier(ref mut identifier) => identifier.rename(old_name, new_name),
|
||||
@ -3230,7 +3158,7 @@ pub enum PrimitiveType {
|
||||
/// `fn`, type of functions.
|
||||
Function(FunctionType),
|
||||
/// An identifier used as a type (not really a primitive type, but whatever).
|
||||
Named(Node<Identifier>),
|
||||
Named { id: Node<Identifier> },
|
||||
}
|
||||
|
||||
impl PrimitiveType {
|
||||
@ -3286,7 +3214,7 @@ impl fmt::Display for PrimitiveType {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
PrimitiveType::Named(n) => write!(f, "{}", n.name),
|
||||
PrimitiveType::Named { id: n } => write!(f, "{}", n.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use serde::Serialize;
|
||||
|
||||
use super::{BodyItem, Expr, MemberObject, Node, Program};
|
||||
use super::{BodyItem, Expr, Node, Program};
|
||||
use crate::SourceRange;
|
||||
|
||||
/// A traversal path through the AST to a node.
|
||||
@ -60,6 +60,20 @@ pub enum Step {
|
||||
}
|
||||
|
||||
impl NodePath {
|
||||
/// Placeholder for when the AST isn't available to create a real path. It
|
||||
/// will be filled in later.
|
||||
pub(crate) fn placeholder() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "artifact-graph")]
|
||||
pub(crate) fn fill_placeholder(&mut self, program: &Node<Program>, cached_body_items: usize, range: SourceRange) {
|
||||
if !self.is_empty() {
|
||||
return;
|
||||
}
|
||||
*self = Self::from_range(program, cached_body_items, range).unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Given a program and a [`SourceRange`], return the path to the node that
|
||||
/// contains the range.
|
||||
pub(crate) fn from_range(program: &Node<Program>, cached_body_items: usize, range: SourceRange) -> Option<Self> {
|
||||
@ -248,7 +262,7 @@ impl NodePath {
|
||||
Expr::MemberExpression(node) => {
|
||||
if node.object.contains_range(&range) {
|
||||
path.push(Step::MemberExpressionObject);
|
||||
return Self::from_member_expr_object(&node.object, range, path);
|
||||
return NodePath::from_expr(&node.object, range, path);
|
||||
}
|
||||
if node.property.contains_range(&range) {
|
||||
path.push(Step::MemberExpressionProperty);
|
||||
@ -313,18 +327,6 @@ impl NodePath {
|
||||
Some(path)
|
||||
}
|
||||
|
||||
fn from_member_expr_object(mut expr: &MemberObject, range: SourceRange, mut path: NodePath) -> Option<NodePath> {
|
||||
while let MemberObject::MemberExpression(node) = expr {
|
||||
if !node.object.contains_range(&range) {
|
||||
break;
|
||||
}
|
||||
path.push(Step::MemberExpressionObject);
|
||||
expr = &node.object;
|
||||
}
|
||||
|
||||
Some(path)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.steps.is_empty()
|
||||
}
|
||||
@ -402,4 +404,19 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_node_path_from_range_import() {
|
||||
let code = r#"import "cube.step" as cube
|
||||
import "cylinder.kcl" as cylinder
|
||||
"#;
|
||||
let program = crate::Program::parse_no_errs(code).unwrap();
|
||||
// The entire cylinder import statement.
|
||||
assert_eq!(
|
||||
NodePath::from_range(&program.ast, 0, range(27, 60)).unwrap(),
|
||||
NodePath {
|
||||
steps: vec![Step::ProgramBodyItem { index: 1 }],
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,8 @@ use crate::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem,
|
||||
BoxNode, CallExpressionKw, CommentStyle, DefaultParamVal, ElseIf, Expr, ExpressionStatement,
|
||||
FunctionExpression, FunctionType, Identifier, IfExpression, ImportItem, ImportSelector, ImportStatement,
|
||||
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, Name,
|
||||
Node, NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
|
||||
ItemVisibility, LabeledArg, Literal, LiteralIdentifier, LiteralValue, MemberExpression, Name, Node,
|
||||
NodeList, NonCodeMeta, NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter,
|
||||
PipeExpression, PipeSubstitution, PrimitiveType, Program, ReturnStatement, Shebang, TagDeclarator, Type,
|
||||
TypeDeclaration, UnaryExpression, UnaryOperator, VariableDeclaration, VariableDeclarator, VariableKind,
|
||||
},
|
||||
@ -1326,10 +1326,7 @@ fn member_expression_subscript(i: &mut TokenSlice) -> ModalResult<(LiteralIdenti
|
||||
|
||||
/// Get a property of an object, or an index of an array, or a member of a collection.
|
||||
/// Can be arbitrarily nested, e.g. `people[i]['adam'].age`.
|
||||
fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>> {
|
||||
// This is an identifier, followed by a sequence of members (aka properties)
|
||||
// First, the identifier.
|
||||
let id = nameable_identifier.context(expected("the identifier of the object whose property you're trying to access, e.g. in 'shape.size.width', 'shape' is the identifier")).parse_next(i)?;
|
||||
fn build_member_expression(object: Expr, i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>> {
|
||||
// Now a sequence of members.
|
||||
let member = alt((member_expression_dot, member_expression_subscript)).context(expected("a member/property, e.g. size.x and size['height'] and size[0] are all different ways to access a member/property of 'size'"));
|
||||
let mut members: Vec<_> = repeat(1.., member)
|
||||
@ -1340,11 +1337,11 @@ fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>>
|
||||
// It's safe to call remove(0), because the vec is created from repeat(1..),
|
||||
// which is guaranteed to have >=1 elements.
|
||||
let (property, end, computed) = members.remove(0);
|
||||
let start = id.start;
|
||||
let module_id = id.module_id;
|
||||
let start = object.start();
|
||||
let module_id = object.module_id();
|
||||
let initial_member_expression = Node::new(
|
||||
MemberExpression {
|
||||
object: MemberObject::Identifier(Box::new(id)),
|
||||
object,
|
||||
computed,
|
||||
property,
|
||||
digest: None,
|
||||
@ -1362,7 +1359,7 @@ fn member_expression(i: &mut TokenSlice) -> ModalResult<Node<MemberExpression>>
|
||||
.fold(initial_member_expression, |accumulated, (property, end, computed)| {
|
||||
Node::new(
|
||||
MemberExpression {
|
||||
object: MemberObject::MemberExpression(Box::new(accumulated)),
|
||||
object: Expr::MemberExpression(Box::new(accumulated)),
|
||||
computed,
|
||||
property,
|
||||
digest: None,
|
||||
@ -2085,8 +2082,7 @@ fn unnecessarily_bracketed(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
}
|
||||
|
||||
fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
alt((
|
||||
member_expression.map(Box::new).map(Expr::MemberExpression),
|
||||
let parsed_expr = alt((
|
||||
bool_value.map(Box::new).map(Expr::Literal),
|
||||
tag.map(Box::new).map(Expr::TagDeclarator),
|
||||
literal.map(Expr::Literal),
|
||||
@ -2100,14 +2096,19 @@ fn expr_allowed_in_pipe_expr(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
unnecessarily_bracketed,
|
||||
))
|
||||
.context(expected("a KCL expression (but not a pipe expression)"))
|
||||
.parse_next(i)
|
||||
.parse_next(i)?;
|
||||
|
||||
let maybe_member = build_member_expression(parsed_expr.clone(), i);
|
||||
if let Ok(mem) = maybe_member {
|
||||
return Ok(Expr::MemberExpression(Box::new(mem)));
|
||||
}
|
||||
Ok(parsed_expr)
|
||||
}
|
||||
|
||||
fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
let mut expr = alt((
|
||||
unary_expression.map(Box::new).map(Expr::UnaryExpression),
|
||||
bool_value.map(Box::new).map(Expr::Literal),
|
||||
member_expression.map(Box::new).map(Expr::MemberExpression),
|
||||
literal.map(Expr::Literal),
|
||||
fn_call_kw.map(Box::new).map(Expr::CallExpressionKw),
|
||||
name.map(Box::new).map(Expr::Name),
|
||||
@ -2118,6 +2119,10 @@ fn possible_operands(i: &mut TokenSlice) -> ModalResult<Expr> {
|
||||
"a KCL value which can be used as an argument/operand to an operator",
|
||||
))
|
||||
.parse_next(i)?;
|
||||
let maybe_member = build_member_expression(expr.clone(), i);
|
||||
if let Ok(mem) = maybe_member {
|
||||
expr = Expr::MemberExpression(Box::new(mem));
|
||||
}
|
||||
|
||||
let ty = opt((colon, opt(whitespace), type_)).parse_next(i)?;
|
||||
if let Some((_, _, ty)) = ty {
|
||||
@ -2480,32 +2485,13 @@ impl TryFrom<Token> for Node<TagDeclarator> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Node<TagDeclarator> {
|
||||
fn into_valid_binding_name(self) -> Result<Self, CompilationError> {
|
||||
// Make sure they are not assigning a variable to a stdlib function.
|
||||
if crate::std::name_in_stdlib(&self.name) {
|
||||
return Err(CompilationError::fatal(
|
||||
SourceRange::from(&self),
|
||||
format!("Cannot assign a tag to a reserved keyword: {}", self.name),
|
||||
));
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a Kcl tag that starts with a `$`.
|
||||
fn tag(i: &mut TokenSlice) -> ModalResult<Node<TagDeclarator>> {
|
||||
dollar.parse_next(i)?;
|
||||
let tag_declarator = any
|
||||
.try_map(Node::<TagDeclarator>::try_from)
|
||||
any.try_map(Node::<TagDeclarator>::try_from)
|
||||
.context(expected("a tag, e.g. '$seg01' or '$line01'"))
|
||||
.parse_next(i)
|
||||
.map_err(|e: ErrMode<ContextError>| e.cut())?;
|
||||
// Now that we've parsed a tag declarator, verify that it's not a stdlib
|
||||
// name. If it is, stop backtracking.
|
||||
tag_declarator
|
||||
.into_valid_binding_name()
|
||||
.map_err(|e| ErrMode::Cut(ContextError::from(e)))
|
||||
.map_err(|e: ErrMode<ContextError>| e.cut())
|
||||
}
|
||||
|
||||
/// Helper function. Matches any number of whitespace tokens and ignores them.
|
||||
@ -2938,7 +2924,7 @@ fn primitive_type(i: &mut TokenSlice) -> ModalResult<Node<PrimitiveType>> {
|
||||
(identifier, opt(delimited(open_paren, uom_for_type, close_paren))).map(|(ident, suffix)| {
|
||||
let mut result = Node::new(PrimitiveType::Boolean, ident.start, ident.end, ident.module_id);
|
||||
result.inner =
|
||||
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named(ident));
|
||||
PrimitiveType::primitive_from_str(&ident.name, suffix).unwrap_or(PrimitiveType::Named { id: ident });
|
||||
result
|
||||
}),
|
||||
))
|
||||
@ -3277,7 +3263,7 @@ fn fn_call_kw(i: &mut TokenSlice) -> ModalResult<Node<CallExpressionKw>> {
|
||||
return Err(ErrMode::Cut(
|
||||
CompilationError::fatal(
|
||||
SourceRange::from(&tok),
|
||||
format!("There was an unexpected {}. Try removing it.", tok.value),
|
||||
format!("There was an unexpected `{}`. Try removing it.", tok.value),
|
||||
)
|
||||
.into(),
|
||||
));
|
||||
@ -4446,7 +4432,7 @@ z(-[["#,
|
||||
assert_err(
|
||||
r#"z
|
||||
(--#"#,
|
||||
"There was an unexpected -. Try removing it.",
|
||||
"There was an unexpected `-`. Try removing it.",
|
||||
[3, 4],
|
||||
);
|
||||
}
|
||||
@ -4898,19 +4884,6 @@ let myBox = box(p=[0,0], h=-3, l=-16, w=-10)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_tag_named_std_lib() {
|
||||
let some_program_string = r#"startSketchOn(XY)
|
||||
|> startProfile(at = [0, 0])
|
||||
|> line(%, end = [5, 5], tag = $xLine)
|
||||
"#;
|
||||
assert_err(
|
||||
some_program_string,
|
||||
"Cannot assign a tag to a reserved keyword: xLine",
|
||||
[86, 92],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_empty_tag_brace() {
|
||||
let some_program_string = r#"startSketchOn(XY)
|
||||
@ -5029,6 +5002,17 @@ type foo = fn(fn, f: fn(number(_))): [fn([any]): string]
|
||||
assert_no_err(some_program_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_fn_call_then_field() {
|
||||
let some_program_string = "myFunction().field";
|
||||
let module_id = ModuleId::default();
|
||||
let tokens = crate::parsing::token::lex(some_program_string, module_id).unwrap(); // Updated import path
|
||||
let actual = expression.parse(tokens.as_slice()).unwrap();
|
||||
let Expr::MemberExpression(_expr) = actual else {
|
||||
panic!("expected member expression")
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_array_missing_closing_bracket() {
|
||||
let some_program_string = r#"
|
||||
@ -5217,7 +5201,7 @@ bar = 1
|
||||
.cause
|
||||
.as_ref()
|
||||
.expect("Found an error, but there was no cause. Add a cause.");
|
||||
assert_eq!(cause.message, "There was an unexpected }. Try removing it.",);
|
||||
assert_eq!(cause.message, "There was an unexpected `}`. Try removing it.",);
|
||||
assert_eq!(cause.source_range.start(), expected_src_start);
|
||||
}
|
||||
|
||||
|
@ -117,12 +117,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 45,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 40,
|
||||
"end": 43,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 40,
|
||||
"end": 43,
|
||||
"name": "obj",
|
||||
"start": 40,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 40,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 44,
|
||||
|
@ -117,12 +117,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 49,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 41,
|
||||
"end": 44,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 41,
|
||||
"end": 44,
|
||||
"name": "obj",
|
||||
"start": 41,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 41,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 45,
|
||||
|
@ -104,12 +104,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 44,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"start": 36,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 36,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 40,
|
||||
|
@ -120,12 +120,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 49,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 41,
|
||||
"end": 44,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 41,
|
||||
"end": 44,
|
||||
"name": "obj",
|
||||
"start": 41,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 41,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 45,
|
||||
|
@ -107,12 +107,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 45,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 37,
|
||||
"end": 40,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 37,
|
||||
"end": 40,
|
||||
"name": "obj",
|
||||
"start": 37,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 37,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 41,
|
||||
|
@ -107,12 +107,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 44,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"start": 36,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 36,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 40,
|
||||
|
@ -37,12 +37,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 18,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 13,
|
||||
"end": 16,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 13,
|
||||
"end": 16,
|
||||
"name": "obj",
|
||||
"start": 13,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 13,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 17,
|
||||
|
@ -24,12 +24,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 19,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 11,
|
||||
"end": 14,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 11,
|
||||
"end": 14,
|
||||
"name": "obj",
|
||||
"start": 11,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 11,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 15,
|
||||
|
@ -101,12 +101,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 44,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"name": {
|
||||
"commentStart": 36,
|
||||
"end": 39,
|
||||
"name": "obj",
|
||||
"start": 36,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 36,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 40,
|
||||
|
@ -25,12 +25,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 16,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 7,
|
||||
"end": 9,
|
||||
"name": "yo",
|
||||
"name": {
|
||||
"commentStart": 7,
|
||||
"end": 9,
|
||||
"name": "yo",
|
||||
"start": 7,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 7,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 10,
|
||||
|
@ -21,12 +21,20 @@ expression: actual
|
||||
"computed": true,
|
||||
"end": 11,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"name": {
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"start": 6,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 6,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 9,
|
||||
|
@ -33,12 +33,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 13,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 7,
|
||||
"end": 9,
|
||||
"name": "yo",
|
||||
"name": {
|
||||
"commentStart": 7,
|
||||
"end": 9,
|
||||
"name": "yo",
|
||||
"start": 7,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 7,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 10,
|
||||
|
@ -21,12 +21,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 11,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"name": {
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"start": 6,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 6,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 9,
|
||||
|
@ -21,12 +21,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 16,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"name": {
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"start": 6,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 6,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 9,
|
||||
|
@ -21,12 +21,20 @@ expression: actual
|
||||
"computed": false,
|
||||
"end": 13,
|
||||
"object": {
|
||||
"abs_path": false,
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"name": {
|
||||
"commentStart": 6,
|
||||
"end": 8,
|
||||
"name": "b1",
|
||||
"start": 6,
|
||||
"type": "Identifier"
|
||||
},
|
||||
"path": [],
|
||||
"start": 6,
|
||||
"type": "Identifier",
|
||||
"type": "Identifier"
|
||||
"type": "Name",
|
||||
"type": "Name"
|
||||
},
|
||||
"property": {
|
||||
"commentStart": 9,
|
||||
|
@ -163,7 +163,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
// Run the program.
|
||||
let exec_res = crate::test_server::execute_and_snapshot_ast(ast, Some(test.entry_point.clone()), export_step).await;
|
||||
match exec_res {
|
||||
Ok((exec_state, env_ref, png, step)) => {
|
||||
Ok((exec_state, ctx, env_ref, png, step)) => {
|
||||
let fail_path = test.output_dir.join("execution_error.snap");
|
||||
if std::fs::exists(&fail_path).unwrap() {
|
||||
panic!("This test case is expected to fail, but it passed. If this is intended, and the test should actually be passing now, please delete kcl-lib/{}", fail_path.to_string_lossy())
|
||||
@ -181,7 +181,7 @@ async fn execute_test(test: &Test, render_to_png: bool, export_step: bool) {
|
||||
panic!("Step data was empty");
|
||||
}
|
||||
}
|
||||
let outcome = exec_state.to_exec_outcome(env_ref).await;
|
||||
let outcome = exec_state.to_exec_outcome(env_ref, &ctx).await;
|
||||
|
||||
let mem_result = catch_unwind(AssertUnwindSafe(|| {
|
||||
assert_snapshot(test, "Variables in memory after executing", || {
|
||||
@ -3483,3 +3483,66 @@ mod spheres {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod var_ref_in_own_def {
|
||||
const TEST_NAME: &str = "var_ref_in_own_def";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod ascription_unknown_type {
|
||||
const TEST_NAME: &str = "ascription_unknown_type";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
mod var_ref_in_own_def_decl {
|
||||
const TEST_NAME: &str = "var_ref_in_own_def_decl";
|
||||
|
||||
/// Test parsing KCL.
|
||||
#[test]
|
||||
fn parse() {
|
||||
super::parse(TEST_NAME)
|
||||
}
|
||||
|
||||
/// Test that parsing and unparsing KCL produces the original KCL input.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn unparse() {
|
||||
super::unparse(TEST_NAME).await
|
||||
}
|
||||
|
||||
/// Test that KCL is executed correctly.
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn kcl_test_execute() {
|
||||
super::execute(TEST_NAME, true).await
|
||||
}
|
||||
}
|
||||
|
@ -94,6 +94,11 @@ impl SourceRange {
|
||||
ModuleId::from_usize(self.0[2])
|
||||
}
|
||||
|
||||
/// True if this source range is from the top-level module.
|
||||
pub fn is_top_level_module(&self) -> bool {
|
||||
self.module_id().is_top_level()
|
||||
}
|
||||
|
||||
/// Check if the range contains a position.
|
||||
pub fn contains(&self, pos: usize) -> bool {
|
||||
pos >= self.start() && pos <= self.end()
|
||||
|
@ -143,9 +143,14 @@ impl Args {
|
||||
where
|
||||
T: for<'a> FromKclValue<'a>,
|
||||
{
|
||||
if self.kw_args.labeled.get(label).is_none() {
|
||||
return Ok(None);
|
||||
};
|
||||
match self.kw_args.labeled.get(label) {
|
||||
None => return Ok(None),
|
||||
Some(a) => {
|
||||
if let KclValue::KclNone { .. } = &a.value {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.get_kw_arg_typed(label, ty, exec_state).map(Some)
|
||||
}
|
||||
@ -174,7 +179,7 @@ impl Args {
|
||||
{
|
||||
let Some(arg) = self.kw_args.labeled.get(label) else {
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
format!("This function requires a keyword argument '{label}'"),
|
||||
format!("This function requires a keyword argument `{label}`"),
|
||||
vec![self.source_range],
|
||||
)));
|
||||
};
|
||||
@ -186,7 +191,7 @@ impl Args {
|
||||
.map(|t| t.to_string())
|
||||
.unwrap_or_else(|| arg.value.human_friendly_type().to_owned());
|
||||
let msg_base = format!(
|
||||
"This function expected the input argument to be {} but it's actually of type {actual_type_name}",
|
||||
"This function expected its `{label}` argument to be {} but it's actually of type {actual_type_name}",
|
||||
ty.human_friendly_type(),
|
||||
);
|
||||
let suggestion = match (ty, actual_type) {
|
||||
|
@ -23,7 +23,7 @@ pub async fn union(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"At least two solids are required for a union operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
@ -88,7 +88,7 @@ pub async fn intersect(exec_state: &mut ExecState, args: Args) -> Result<KclValu
|
||||
let tolerance: Option<TyF64> = args.get_kw_arg_opt_typed("tolerance", &RuntimeType::length(), exec_state)?;
|
||||
|
||||
if solids.len() < 2 {
|
||||
return Err(KclError::new_undefined_value(KclErrorDetails::new(
|
||||
return Err(KclError::new_semantic(KclErrorDetails::new(
|
||||
"At least two solids are required for an intersect operation.".to_string(),
|
||||
vec![args.source_range],
|
||||
)));
|
||||
|
@ -28,14 +28,10 @@ pub mod utils;
|
||||
|
||||
use anyhow::Result;
|
||||
pub use args::Args;
|
||||
use indexmap::IndexMap;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::KclError,
|
||||
execution::{types::PrimitiveType, ExecState, KclValue},
|
||||
parsing::ast::types::Name,
|
||||
};
|
||||
|
||||
pub type StdFn = fn(
|
||||
@ -43,46 +39,6 @@ pub type StdFn = fn(
|
||||
Args,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<KclValue, KclError>> + Send + '_>>;
|
||||
|
||||
lazy_static! {
|
||||
static ref CORE_FNS: Vec<Box<dyn StdLibFn>> = vec![
|
||||
Box::new(crate::std::segment::SegEnd),
|
||||
Box::new(crate::std::segment::SegEndX),
|
||||
Box::new(crate::std::segment::SegEndY),
|
||||
Box::new(crate::std::segment::SegStart),
|
||||
Box::new(crate::std::segment::SegStartX),
|
||||
Box::new(crate::std::segment::SegStartY),
|
||||
Box::new(crate::std::segment::LastSegX),
|
||||
Box::new(crate::std::segment::LastSegY),
|
||||
Box::new(crate::std::segment::SegLen),
|
||||
Box::new(crate::std::segment::SegAng),
|
||||
Box::new(crate::std::segment::TangentToEnd),
|
||||
Box::new(crate::std::sketch::InvoluteCircular),
|
||||
Box::new(crate::std::sketch::Line),
|
||||
Box::new(crate::std::sketch::XLine),
|
||||
Box::new(crate::std::sketch::YLine),
|
||||
Box::new(crate::std::sketch::AngledLine),
|
||||
Box::new(crate::std::sketch::AngledLineThatIntersects),
|
||||
Box::new(crate::std::sketch::StartSketchOn),
|
||||
Box::new(crate::std::sketch::StartProfile),
|
||||
Box::new(crate::std::sketch::ProfileStartX),
|
||||
Box::new(crate::std::sketch::ProfileStartY),
|
||||
Box::new(crate::std::sketch::ProfileStart),
|
||||
Box::new(crate::std::sketch::Close),
|
||||
Box::new(crate::std::sketch::Arc),
|
||||
Box::new(crate::std::sketch::TangentialArc),
|
||||
Box::new(crate::std::sketch::BezierCurve),
|
||||
Box::new(crate::std::sketch::Subtract2D),
|
||||
];
|
||||
}
|
||||
|
||||
pub fn name_in_stdlib(name: &str) -> bool {
|
||||
CORE_FNS.iter().any(|f| f.name() == name)
|
||||
}
|
||||
|
||||
pub fn get_stdlib_fn(name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
CORE_FNS.iter().find(|f| f.name() == name).cloned()
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct StdFnProps {
|
||||
pub name: String,
|
||||
@ -206,7 +162,7 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
),
|
||||
("transform", "mirror2d") => (
|
||||
|e, a| Box::pin(crate::std::mirror::mirror_2d(e, a)),
|
||||
StdFnProps::default("std::transform::mirror2d"),
|
||||
StdFnProps::default("std::transform::mirror2d").include_in_feature_tree(),
|
||||
),
|
||||
("transform", "translate") => (
|
||||
|e, a| Box::pin(crate::std::transform::translate(e, a)),
|
||||
@ -352,6 +308,114 @@ pub(crate) fn std_fn(path: &str, fn_name: &str) -> (crate::std::StdFn, StdFnProp
|
||||
|e, a| Box::pin(crate::std::patterns::pattern_circular_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::patternCircular2d"),
|
||||
),
|
||||
("sketch", "segEnd") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end(e, a)),
|
||||
StdFnProps::default("std::sketch::segEnd"),
|
||||
),
|
||||
("sketch", "segEndX") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end_x(e, a)),
|
||||
StdFnProps::default("std::sketch::segEndX"),
|
||||
),
|
||||
("sketch", "segEndY") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_end_y(e, a)),
|
||||
StdFnProps::default("std::sketch::segEndY"),
|
||||
),
|
||||
("sketch", "segStart") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start(e, a)),
|
||||
StdFnProps::default("std::sketch::segStart"),
|
||||
),
|
||||
("sketch", "segStartX") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start_x(e, a)),
|
||||
StdFnProps::default("std::sketch::segStartX"),
|
||||
),
|
||||
("sketch", "segStartY") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_start_y(e, a)),
|
||||
StdFnProps::default("std::sketch::segStartY"),
|
||||
),
|
||||
("sketch", "lastSegX") => (
|
||||
|e, a| Box::pin(crate::std::segment::last_segment_x(e, a)),
|
||||
StdFnProps::default("std::sketch::lastSegX"),
|
||||
),
|
||||
("sketch", "lastSegY") => (
|
||||
|e, a| Box::pin(crate::std::segment::last_segment_y(e, a)),
|
||||
StdFnProps::default("std::sketch::lastSegY"),
|
||||
),
|
||||
("sketch", "segLen") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_length(e, a)),
|
||||
StdFnProps::default("std::sketch::segLen"),
|
||||
),
|
||||
("sketch", "segAng") => (
|
||||
|e, a| Box::pin(crate::std::segment::segment_angle(e, a)),
|
||||
StdFnProps::default("std::sketch::segAng"),
|
||||
),
|
||||
("sketch", "tangentToEnd") => (
|
||||
|e, a| Box::pin(crate::std::segment::tangent_to_end(e, a)),
|
||||
StdFnProps::default("std::sketch::tangentToEnd"),
|
||||
),
|
||||
("sketch", "profileStart") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStart"),
|
||||
),
|
||||
("sketch", "profileStartX") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start_x(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStartX"),
|
||||
),
|
||||
("sketch", "profileStartY") => (
|
||||
|e, a| Box::pin(crate::std::sketch::profile_start_y(e, a)),
|
||||
StdFnProps::default("std::sketch::profileStartY"),
|
||||
),
|
||||
("sketch", "startSketchOn") => (
|
||||
|e, a| Box::pin(crate::std::sketch::start_sketch_on(e, a)),
|
||||
StdFnProps::default("std::sketch::startSketchOn").include_in_feature_tree(),
|
||||
),
|
||||
("sketch", "startProfile") => (
|
||||
|e, a| Box::pin(crate::std::sketch::start_profile(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "involuteCircular") => (
|
||||
|e, a| Box::pin(crate::std::sketch::involute_circular(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "line") => (
|
||||
|e, a| Box::pin(crate::std::sketch::line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "xLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::x_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "yLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::y_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "angledLine") => (
|
||||
|e, a| Box::pin(crate::std::sketch::angled_line(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "angledLineThatIntersects") => (
|
||||
|e, a| Box::pin(crate::std::sketch::angled_line_that_intersects(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "close") => (
|
||||
|e, a| Box::pin(crate::std::sketch::close(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "arc") => (
|
||||
|e, a| Box::pin(crate::std::sketch::arc(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "tangentialArc") => (
|
||||
|e, a| Box::pin(crate::std::sketch::tangential_arc(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "bezierCurve") => (
|
||||
|e, a| Box::pin(crate::std::sketch::bezier_curve(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile"),
|
||||
),
|
||||
("sketch", "subtract2d") => (
|
||||
|e, a| Box::pin(crate::std::sketch::subtract_2d(e, a)),
|
||||
StdFnProps::default("std::sketch::startProfile").include_in_feature_tree(),
|
||||
),
|
||||
("appearance", "hexString") => (
|
||||
|e, a| Box::pin(crate::std::appearance::hex_string(e, a)),
|
||||
StdFnProps::default("std::appearance::hexString"),
|
||||
@ -376,56 +440,5 @@ pub(crate) fn std_ty(path: &str, fn_name: &str) -> (PrimitiveType, StdFnProps) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct StdLib {
|
||||
pub fns: IndexMap<String, Box<dyn StdLibFn>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for StdLib {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("StdLib").field("fns.len()", &self.fns.len()).finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl StdLib {
|
||||
pub fn new() -> Self {
|
||||
let fns = CORE_FNS
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|internal_fn| (internal_fn.name(), internal_fn))
|
||||
.collect();
|
||||
|
||||
Self { fns }
|
||||
}
|
||||
|
||||
// Get the combined hashmaps.
|
||||
pub fn combined(&self) -> IndexMap<String, Box<dyn StdLibFn>> {
|
||||
self.fns.clone()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Box<dyn StdLibFn>> {
|
||||
self.fns.get(name).cloned()
|
||||
}
|
||||
|
||||
pub fn get_rust_function(&self, name: &Name) -> Option<Box<dyn StdLibFn>> {
|
||||
if let Some(name) = name.local_ident() {
|
||||
if let Some(f) = self.get(name.inner) {
|
||||
return Some(f);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn contains_key(&self, key: &str) -> bool {
|
||||
self.fns.contains_key(key)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for StdLib {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// The default tolerance for modeling commands in [`kittycad_modeling_cmds::length_unit::LengthUnit`].
|
||||
const DEFAULT_TOLERANCE: f64 = 0.0000001;
|
||||
|
@ -1,7 +1,6 @@
|
||||
//! Functions related to line segments.
|
||||
|
||||
use anyhow::Result;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kittycad_modeling_cmds::shared::Angle;
|
||||
|
||||
use super::utils::untype_point;
|
||||
@ -22,39 +21,6 @@ pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// w = 15
|
||||
/// cube = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [w, 0], tag = $line1)
|
||||
/// |> line(end = [0, w], tag = $line2)
|
||||
/// |> line(end = [-w, 0], tag = $line3)
|
||||
/// |> line(end = [0, -w], tag = $line4)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// fn cylinder(radius, tag) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> circle(radius = radius, center = segEnd(tag) )
|
||||
/// |> extrude(length = radius)
|
||||
/// }
|
||||
///
|
||||
/// cylinder(radius = 1, tag = line1)
|
||||
/// cylinder(radius = 2, tag = line2)
|
||||
/// cylinder(radius = 3, tag = line3)
|
||||
/// cylinder(radius = 4, tag = line4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEnd",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -78,27 +44,6 @@ pub async fn segment_end_x(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0], tag = $thing)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [segEndX(thing), 0])
|
||||
/// |> line(end = [-20, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEndX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -119,28 +64,6 @@ pub async fn segment_end_y(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the ending point of the provided line segment along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, 3], tag = $thing)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> line(end = [0, segEndY(thing)])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segEndY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -161,39 +84,6 @@ pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// w = 15
|
||||
/// cube = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [w, 0], tag = $line1)
|
||||
/// |> line(end = [0, w], tag = $line2)
|
||||
/// |> line(end = [-w, 0], tag = $line3)
|
||||
/// |> line(end = [0, -w], tag = $line4)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// fn cylinder(radius, tag) {
|
||||
/// return startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> circle( radius = radius, center = segStart(tag) )
|
||||
/// |> extrude(length = radius)
|
||||
/// }
|
||||
///
|
||||
/// cylinder(radius = 1, tag = line1)
|
||||
/// cylinder(radius = 2, tag = line2)
|
||||
/// cylinder(radius = 3, tag = line3)
|
||||
/// cylinder(radius = 4, tag = line4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStart",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<[TyF64; 2], KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -217,27 +107,6 @@ pub async fn segment_start_x(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0], tag = $thing)
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [20 - segStartX(thing), 0])
|
||||
/// |> line(end = [-20, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStartX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start_x(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -258,28 +127,6 @@ pub async fn segment_start_y(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the starting point of the provided line segment along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> line(end = [0, 3], tag = $thing)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> line(end = [0, 20-segStartY(thing)])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segStartY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_start_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -300,28 +147,6 @@ pub async fn last_segment_x(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Extract the 'x' axis value of the last line segment in the provided 2-d
|
||||
/// sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [20, 5])
|
||||
/// |> line(end = [lastSegX(%), 0])
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "lastSegX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch whose line segment is being queried"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_last_segment_x(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
let last_line = sketch
|
||||
.paths
|
||||
@ -346,28 +171,6 @@ pub async fn last_segment_y(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Extract the 'y' axis value of the last line segment in the provided 2-d
|
||||
/// sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [20, 5])
|
||||
/// |> line(end = [0, lastSegY(%)])
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "lastSegY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch whose line segment is being queried"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_last_segment_y(sketch: Sketch, args: Args) -> Result<TyF64, KclError> {
|
||||
let last_line = sketch
|
||||
.paths
|
||||
@ -390,33 +193,6 @@ pub async fn segment_length(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(result))
|
||||
}
|
||||
|
||||
/// Compute the length of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// tag = $thing,
|
||||
/// )
|
||||
/// |> tangentialArc(angle = -120, radius = 5)
|
||||
/// |> angledLine(
|
||||
/// angle = -60,
|
||||
/// length = segLen(thing),
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segLen",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_length(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<TyF64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -437,29 +213,6 @@ pub async fn segment_angle(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
}
|
||||
|
||||
/// Compute the angle (in degrees) of the provided line segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [5, 10], tag = $seg01)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(angle = segAng(seg01), length = 10)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> angledLine(angle = segAng(seg01), length = -15)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 4)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "segAng",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
fn inner_segment_angle(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
@ -482,85 +235,6 @@ pub async fn tangent_to_end(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, NumericType::degrees())))
|
||||
}
|
||||
|
||||
/// Returns the angle coming out of the end of the segment in degrees.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Horizontal pill.
|
||||
/// pillSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [20, 0])
|
||||
/// |> tangentialArc(end = [0, 10], tag = $arc1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(arc1),
|
||||
/// length = 20,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [0, -10])
|
||||
/// |> close()
|
||||
///
|
||||
/// pillExtrude = extrude(pillSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Vertical pill. Use absolute coordinate for arc.
|
||||
/// pillSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 20])
|
||||
/// |> tangentialArc(endAbsolute = [10, 20], tag = $arc1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(arc1),
|
||||
/// length = 20,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// pillExtrude = extrude(pillSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// rectangleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0], tag = $seg1)
|
||||
/// |> angledLine(
|
||||
/// angle = tangentToEnd(seg1),
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-20, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// rectangleExtrude = extrude(rectangleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// bottom = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> arc(
|
||||
/// endAbsolute = [10, 10],
|
||||
/// interiorAbsolute = [5, 1],
|
||||
/// tag = $arc1,
|
||||
/// )
|
||||
/// |> angledLine(angle = tangentToEnd(arc1), length = 20)
|
||||
/// |> close()
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// circSketch = startSketchOn(XY)
|
||||
/// |> circle( center= [0, 0], radius= 3 , tag= $circ)
|
||||
///
|
||||
/// triangleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [-5, 0])
|
||||
/// |> angledLine(angle = tangentToEnd(circ), length = 10)
|
||||
/// |> line(end = [-15, 0])
|
||||
/// |> close()
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "tangentToEnd",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
tag = { docs = "The line segment being queried by its tag"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_tangent_to_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args) -> Result<f64, KclError> {
|
||||
let line = args.get_tag_engine_info(exec_state, tag)?;
|
||||
let path = line.path.clone().ok_or_else(|| {
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
use anyhow::Result;
|
||||
use indexmap::IndexMap;
|
||||
use kcl_derive_docs::stdlib;
|
||||
use kcmc::shared::Point2d as KPoint2d; // Point2d is already defined in this pkg, to impl ts_rs traits.
|
||||
use kcmc::shared::Point3d as KPoint3d; // Point3d is already defined in this pkg, to impl ts_rs traits.
|
||||
use kcmc::{each_cmd as mcmd, length_unit::LengthUnit, shared::Angle, websocket::ModelingCmdReq, ModelingCmd};
|
||||
@ -100,8 +99,7 @@ pub enum StartOrEnd {
|
||||
pub const NEW_TAG_KW: &str = "tag";
|
||||
|
||||
pub async fn involute_circular(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch =
|
||||
args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::Primitive(PrimitiveType::Sketch), exec_state)?;
|
||||
let sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
|
||||
let start_radius: TyF64 = args.get_kw_arg_typed("startRadius", &RuntimeType::length(), exec_state)?;
|
||||
let end_radius: TyF64 = args.get_kw_arg_typed("endRadius", &RuntimeType::length(), exec_state)?;
|
||||
@ -122,29 +120,6 @@ fn involute_curve(radius: f64, angle: f64) -> (f64, f64) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Extend the current sketch with a new involute circular curve.
|
||||
///
|
||||
/// ```no_run
|
||||
/// a = 10
|
||||
/// b = 14
|
||||
/// startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60)
|
||||
/// |> involuteCircular(startRadius = a, endRadius = b, angle = 60, reverse = true)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "involuteCircular",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
start_radius = { docs = "The involute is described between two circles, start_radius is the radius of the inner circle."},
|
||||
end_radius = { docs = "The involute is described between two circles, end_radius is the radius of the outer circle."},
|
||||
angle = { docs = "The angle to rotate the involute by. A value of zero will produce a curve with a tangent along the x-axis at the start point of the curve."},
|
||||
reverse = { docs = "If reverse is true, the segment will start from the end of the involute, otherwise it will start from that start. Defaults to false."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_involute_circular(
|
||||
sketch: Sketch,
|
||||
@ -228,41 +203,6 @@ pub async fn line(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kc
|
||||
})
|
||||
}
|
||||
|
||||
/// Extend the current sketch with a new straight line.
|
||||
///
|
||||
/// ```no_run
|
||||
/// triangle = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// // The END argument means it ends at exactly [10, 0].
|
||||
/// // This is an absolute measurement, it is NOT relative to
|
||||
/// // the start of the sketch.
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> line(endAbsolute = [0, 10])
|
||||
/// |> line(endAbsolute = [-10, 0], tag = $thirdLineOfTriangle)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
///
|
||||
/// box = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [10, 10])
|
||||
/// // The 'to' argument means move the pen this much.
|
||||
/// // So, [10, 0] is a relative distance away from the current point.
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0], tag = $thirdLineOfBox)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "line",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
end_absolute = { docs = "Which absolute point should this line go to? Incompatible with `end`."},
|
||||
end = { docs = "How far away (along the X and Y axes) should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_line(
|
||||
sketch: Sketch,
|
||||
end_absolute: Option<[TyF64; 2]>,
|
||||
@ -401,39 +341,6 @@ pub async fn x_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line relative to the current origin to a specified distance away
|
||||
/// from the current position along the 'x' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> xLine(length = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 80,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> xLine(length = 10)
|
||||
/// |> angledLine(
|
||||
/// angle = 120,
|
||||
/// length = 30,
|
||||
/// )
|
||||
/// |> xLine(length = -15)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "xLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
length = { docs = "How far away along the X axis should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
end_absolute = { docs = "Which absolute X value should this line go to? Incompatible with `length`."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_x_line(
|
||||
sketch: Sketch,
|
||||
length: Option<TyF64>,
|
||||
@ -471,34 +378,6 @@ pub async fn y_line(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line relative to the current origin to a specified distance away
|
||||
/// from the current position along the 'y' axis.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> yLine(length = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 30,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> yLine(length = -5)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "yLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
length = { docs = "How far away along the Y axis should this line go? Incompatible with `endAbsolute`.", include_in_snippet = true},
|
||||
end_absolute = { docs = "Which absolute Y value should this line go to? Incompatible with `length`."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_y_line(
|
||||
sketch: Sketch,
|
||||
length: Option<TyF64>,
|
||||
@ -553,38 +432,6 @@ pub async fn angled_line(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a line segment relative to the current origin using the polar
|
||||
/// measure of some angle and distance.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> yLine(endAbsolute = 15)
|
||||
/// |> angledLine(
|
||||
/// angle = 30,
|
||||
/// length = 15,
|
||||
/// )
|
||||
/// |> line(end = [8, -10])
|
||||
/// |> yLine(endAbsolute = 0)
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "angledLine",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
angle = { docs = "Which angle should the line be drawn at?" },
|
||||
length = { docs = "Draw the line this distance along the given angle. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
length_x = { docs = "Draw the line this distance along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
length_y = { docs = "Draw the line this distance along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
end_absolute_x = { docs = "Draw the line along the given angle until it reaches this point along the X axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
end_absolute_y = { docs = "Draw the line along the given angle until it reaches this point along the Y axis. Only one of `length`, `lengthX`, `lengthY`, `endAbsoluteX`, `endAbsoluteY` can be given."},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_angled_line(
|
||||
sketch: Sketch,
|
||||
@ -850,37 +697,6 @@ pub async fn angled_line_that_intersects(exec_state: &mut ExecState, args: Args)
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw an angled line from the current origin, constructing a line segment
|
||||
/// such that the newly created line intersects the desired target line
|
||||
/// segment.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(endAbsolute = [5, 10])
|
||||
/// |> line(endAbsolute = [-10, 10], tag = $lineToIntersect)
|
||||
/// |> line(endAbsolute = [0, 20])
|
||||
/// |> angledLineThatIntersects(
|
||||
/// angle = 80,
|
||||
/// intersectTag = lineToIntersect,
|
||||
/// offset = 10,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "angledLineThatIntersects",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
angle = { docs = "Which angle should the line be drawn at?" },
|
||||
intersect_tag = { docs = "The tag of the line to intersect with" },
|
||||
offset = { docs = "The offset from the intersecting line. Defaults to 0." },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub async fn inner_angled_line_that_intersects(
|
||||
sketch: Sketch,
|
||||
angle: TyF64,
|
||||
@ -971,190 +787,6 @@ pub async fn start_sketch_on(exec_state: &mut ExecState, args: Args) -> Result<K
|
||||
}
|
||||
}
|
||||
|
||||
/// Start a new 2-dimensional sketch on a specific plane or face.
|
||||
///
|
||||
/// ### Sketch on Face Behavior
|
||||
///
|
||||
/// There are some important behaviors to understand when sketching on a face:
|
||||
///
|
||||
/// The resulting sketch will _include_ the face and thus Solid
|
||||
/// that was sketched on. So say you were to export the resulting Sketch / Solid
|
||||
/// from a sketch on a face, you would get both the artifact of the sketch
|
||||
/// on the face and the parent face / Solid itself.
|
||||
///
|
||||
/// This is important to understand because if you were to then sketch on the
|
||||
/// resulting Solid, it would again include the face and parent Solid that was
|
||||
/// sketched on. This could go on indefinitely.
|
||||
///
|
||||
/// The point is if you want to export the result of a sketch on a face, you
|
||||
/// only need to export the final Solid that was created from the sketch on the
|
||||
/// face, since it will include all the parent faces and Solids.
|
||||
///
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = END)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = END)
|
||||
/// |> startProfile(at = [2, 2])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [-6, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sketch on the end of an extruded face by tagging the end face.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5, tagEnd = $end01)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = end01)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5, tagEnd = $end02)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = end02)
|
||||
/// |> startProfile(at = [2, 2])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [-6, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10], tag = $sketchingFace)
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = sketchingFace)
|
||||
/// |> startProfile(at = [1, 1])
|
||||
/// |> line(end = [8, 0])
|
||||
/// |> line(end = [0, 8])
|
||||
/// |> line(end = [-8, 0])
|
||||
/// |> close(tag = $sketchingFace002)
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 10)
|
||||
///
|
||||
/// exampleSketch003 = startSketchOn(example002, face = sketchingFace002)
|
||||
/// |> startProfile(at = [-8, 12])
|
||||
/// |> line(end = [0, 6])
|
||||
/// |> line(end = [6, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> close()
|
||||
///
|
||||
/// example003 = extrude(exampleSketch003, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = END)
|
||||
/// |> startProfile(at = [4.5, -5])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Sketch on the end of a revolved face by tagging the end face.
|
||||
///
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [4, 12])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [4, -6])
|
||||
/// |> line(end = [0, -6])
|
||||
/// |> line(end = [-3.75, -4.5])
|
||||
/// |> line(end = [0, -5.5])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = revolve(exampleSketch, axis = Y, angle = 180, tagEnd = $end01)
|
||||
///
|
||||
/// exampleSketch002 = startSketchOn(example, face = end01)
|
||||
/// |> startProfile(at = [4.5, -5])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
///
|
||||
/// example002 = extrude(exampleSketch002, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// a1 = startSketchOn({
|
||||
/// origin = { x = 0, y = 0, z = 0 },
|
||||
/// xAxis = { x = 1, y = 0, z = 0 },
|
||||
/// yAxis = { x = 0, y = 1, z = 0 },
|
||||
/// zAxis = { x = 0, y = 0, z = 1 }
|
||||
/// })
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [100.0, 0])
|
||||
/// |> yLine(length = -100.0)
|
||||
/// |> xLine(length = -100.0)
|
||||
/// |> yLine(length = 100.0)
|
||||
/// |> close()
|
||||
/// |> extrude(length = 3.14)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "startSketchOn",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
plane_or_solid = { docs = "The plane or solid to sketch on"},
|
||||
face = { docs = "Identify a face of a solid if a solid is specified as the input argument (`plane_or_solid`)"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_start_sketch_on(
|
||||
plane_or_solid: SketchData,
|
||||
face: Option<FaceTag>,
|
||||
@ -1280,50 +912,6 @@ pub async fn start_profile(exec_state: &mut ExecState, args: Args) -> Result<Kcl
|
||||
})
|
||||
}
|
||||
|
||||
/// Start a new profile at a given point.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [10, 10])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [-10, 23])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 5)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "startProfile",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch_surface = { docs = "What to start the profile on" },
|
||||
at = { docs = "Where to start the profile. An absolute point.", snippet_value_array = ["0", "0"] },
|
||||
tag = { docs = "Tag this first starting point" },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) async fn inner_start_profile(
|
||||
sketch_surface: SketchSurface,
|
||||
at: [TyF64; 2],
|
||||
@ -1440,91 +1028,36 @@ pub(crate) async fn inner_start_profile(
|
||||
|
||||
/// Returns the X component of the sketch profile start point.
|
||||
pub async fn profile_start_x(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let x = inner_profile_start_x(sketch)?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty)))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin's 'x'
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = -26.6, length = 50)
|
||||
/// |> angledLine(angle = 90, length = 50)
|
||||
/// |> angledLine(angle = 30, endAbsoluteX = profileStartX(%))
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartX",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_x(profile: Sketch) -> Result<f64, KclError> {
|
||||
Ok(profile.start.to[0])
|
||||
}
|
||||
|
||||
/// Returns the Y component of the sketch profile start point.
|
||||
pub async fn profile_start_y(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let x = inner_profile_start_y(sketch)?;
|
||||
Ok(args.make_user_val_from_f64_with_type(TyF64::new(x, ty)))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin's 'y'
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = -60, length = 14 )
|
||||
/// |> angledLine(angle = 30, endAbsoluteY = profileStartY(%))
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStartY",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start_y(profile: Sketch) -> Result<f64, KclError> {
|
||||
Ok(profile.start.to[1])
|
||||
}
|
||||
|
||||
/// Returns the sketch profile start point.
|
||||
pub async fn profile_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("sketch", &RuntimeType::sketch(), exec_state)?;
|
||||
let sketch: Sketch = args.get_unlabeled_kw_arg_typed("profile", &RuntimeType::sketch(), exec_state)?;
|
||||
let ty = sketch.units.into();
|
||||
let point = inner_profile_start(sketch)?;
|
||||
Ok(KclValue::from_point2d(point, ty, args.into()))
|
||||
}
|
||||
|
||||
/// Extract the provided 2-dimensional sketch's profile's origin
|
||||
/// value.
|
||||
///
|
||||
/// ```no_run
|
||||
/// sketch001 = startSketchOn(XY)
|
||||
/// |> startProfile(at = [5, 2])
|
||||
/// |> angledLine(angle = 120, length = 50 , tag = $seg01)
|
||||
/// |> angledLine(angle = segAng(seg01) + 120, length = 50 )
|
||||
/// |> line(end = profileStart(%))
|
||||
/// |> close()
|
||||
/// |> extrude(length = 20)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "profileStart",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
profile = {docs = "Profile whose start is being used"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) fn inner_profile_start(profile: Sketch) -> Result<[f64; 2], KclError> {
|
||||
Ok(profile.start.to)
|
||||
}
|
||||
@ -1540,41 +1073,6 @@ pub async fn close(exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
|
||||
})
|
||||
}
|
||||
|
||||
/// Construct a line segment from the current origin back to the profile's
|
||||
/// origin, ensuring the resulting 2-dimensional sketch is not open-ended.
|
||||
///
|
||||
/// If you want to perform some 3-dimensional operation on a sketch, like
|
||||
/// extrude or sweep, you must `close` it first. `close` must be called even
|
||||
/// if the end point of the last segment is coincident with the sketch
|
||||
/// starting point.
|
||||
///
|
||||
/// ```no_run
|
||||
/// startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 10])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "close",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "The sketch you want to close"},
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
pub(crate) async fn inner_close(
|
||||
sketch: Sketch,
|
||||
tag: Option<TagNode>,
|
||||
@ -1644,54 +1142,6 @@ pub async fn arc(exec_state: &mut ExecState, args: Args) -> Result<KclValue, Kcl
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a curved line segment along an imaginary circle.
|
||||
///
|
||||
/// The arc is constructed such that the current position of the sketch is
|
||||
/// placed along an imaginary circle of the specified radius, at angleStart
|
||||
/// degrees. The resulting arc is the segment of the imaginary circle from
|
||||
/// that origin point to angleEnd, radius away from the center of the imaginary
|
||||
/// circle.
|
||||
///
|
||||
/// Unless this makes a lot of sense and feels like what you're looking
|
||||
/// for to construct your shape, you're likely looking for tangentialArc.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [10, 0])
|
||||
/// |> arc(
|
||||
/// angleStart = 0,
|
||||
/// angleEnd = 280,
|
||||
/// radius = 16
|
||||
/// )
|
||||
/// |> close()
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> arc(
|
||||
/// endAbsolute = [10,0],
|
||||
/// interiorAbsolute = [5,5]
|
||||
/// )
|
||||
/// |> close()
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "arc",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?" },
|
||||
angle_start = { docs = "Where along the circle should this arc start?", include_in_snippet = true },
|
||||
angle_end = { docs = "Where along the circle should this arc end?", include_in_snippet = true },
|
||||
radius = { docs = "How large should the circle be? Incompatible with `diameter`." },
|
||||
diameter = { docs = "How large should the circle be? Incompatible with `radius`.", include_in_snippet = true },
|
||||
interior_absolute = { docs = "Any point between the arc's start and end? Requires `endAbsolute`. Incompatible with `angleStart` or `angleEnd`" },
|
||||
end_absolute = { docs = "Where should this arc end? Requires `interiorAbsolute`. Incompatible with `angleStart` or `angleEnd`" },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) async fn inner_arc(
|
||||
sketch: Sketch,
|
||||
@ -1880,74 +1330,6 @@ pub async fn tangential_arc(exec_state: &mut ExecState, args: Args) -> Result<Kc
|
||||
})
|
||||
}
|
||||
|
||||
/// Starting at the current sketch's origin, draw a curved line segment along
|
||||
/// some part of an imaginary circle until it reaches the desired (x, y)
|
||||
/// coordinates.
|
||||
///
|
||||
/// When using radius and angle, draw a curved line segment along part of an
|
||||
/// imaginary circle. The arc is constructed such that the last line segment is
|
||||
/// placed tangent to the imaginary circle of the specified radius. The
|
||||
/// resulting arc is the segment of the imaginary circle from that tangent point
|
||||
/// for 'angle' degrees along the imaginary circle.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 45,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(end = [0, -10])
|
||||
/// |> line(end = [-10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(endAbsolute = [15, 15])
|
||||
/// |> line(end = [10, -15])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> angledLine(
|
||||
/// angle = 60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> tangentialArc(radius = 10, angle = -120)
|
||||
/// |> angledLine(
|
||||
/// angle = -60,
|
||||
/// length = 10,
|
||||
/// )
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "tangentialArc",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
end_absolute = { docs = "Which absolute point should this arc go to? Incompatible with `end`, `radius`, and `offset`."},
|
||||
end = { docs = "How far away (along the X and Y axes) should this arc go? Incompatible with `endAbsolute`, `radius`, and `offset`.", include_in_snippet = true },
|
||||
radius = { docs = "Radius of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `diameter`."},
|
||||
diameter = { docs = "Diameter of the imaginary circle. `angle` must be given. Incompatible with `end` and `endAbsolute` and `radius`."},
|
||||
angle = { docs = "Offset of the arc in degrees. `radius` must be given. Incompatible with `end` and `endAbsolute`."},
|
||||
tag = { docs = "Create a new tag which refers to this arc"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_tangential_arc(
|
||||
sketch: Sketch,
|
||||
@ -2206,48 +1588,6 @@ pub async fn bezier_curve(exec_state: &mut ExecState, args: Args) -> Result<KclV
|
||||
})
|
||||
}
|
||||
|
||||
/// Draw a smooth, continuous, curved line segment from the current origin to
|
||||
/// the desired (x, y), using a number of control points to shape the curve's
|
||||
/// shape.
|
||||
///
|
||||
/// ```no_run
|
||||
/// // Example using relative control points.
|
||||
/// exampleSketch = startSketchOn(XZ)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 10])
|
||||
/// |> bezierCurve(
|
||||
/// control1 = [5, 0],
|
||||
/// control2 = [5, 10],
|
||||
/// end = [10, 10],
|
||||
/// )
|
||||
/// |> line(endAbsolute = [10, 0])
|
||||
/// |> close()
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 10)
|
||||
/// ```
|
||||
/// ```no_run
|
||||
/// // Example using absolute control points.
|
||||
/// startSketchOn(XY)
|
||||
/// |> startProfile(at = [300, 300])
|
||||
/// |> bezierCurve(control1Absolute = [600, 300], control2Absolute = [-300, -100], endAbsolute = [600, 600])
|
||||
/// |> close()
|
||||
/// |> extrude(length = 10)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "bezierCurve",
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?"},
|
||||
control1 = { docs = "First control point for the cubic" },
|
||||
control2 = { docs = "Second control point for the cubic" },
|
||||
end = { docs = "How far away (along the X and Y axes) should this line go?" },
|
||||
control1_absolute = { docs = "First control point for the cubic. Absolute point." },
|
||||
control2_absolute = { docs = "Second control point for the cubic. Absolute point." },
|
||||
end_absolute = { docs = "Coordinate on the plane at which this line should end." },
|
||||
tag = { docs = "Create a new tag which refers to this line"},
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn inner_bezier_curve(
|
||||
sketch: Sketch,
|
||||
@ -2364,47 +1704,6 @@ pub async fn subtract_2d(exec_state: &mut ExecState, args: Args) -> Result<KclVa
|
||||
})
|
||||
}
|
||||
|
||||
/// Use a 2-dimensional sketch to cut a hole in another 2-dimensional sketch.
|
||||
///
|
||||
/// ```no_run
|
||||
/// exampleSketch = startSketchOn(XY)
|
||||
/// |> startProfile(at = [0, 0])
|
||||
/// |> line(end = [0, 5])
|
||||
/// |> line(end = [5, 0])
|
||||
/// |> line(end = [0, -5])
|
||||
/// |> close()
|
||||
/// |> subtract2d(tool =circle( center = [1, 1], radius = .25 ))
|
||||
/// |> subtract2d(tool =circle( center = [1, 4], radius = .25 ))
|
||||
///
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
///
|
||||
/// ```no_run
|
||||
/// fn squareHoleSketch() {
|
||||
/// squareSketch = startSketchOn(-XZ)
|
||||
/// |> startProfile(at = [-1, -1])
|
||||
/// |> line(end = [2, 0])
|
||||
/// |> line(end = [0, 2])
|
||||
/// |> line(end = [-2, 0])
|
||||
/// |> close()
|
||||
/// return squareSketch
|
||||
/// }
|
||||
///
|
||||
/// exampleSketch = startSketchOn(-XZ)
|
||||
/// |> circle( center = [0, 0], radius = 3 )
|
||||
/// |> subtract2d(tool = squareHoleSketch())
|
||||
/// example = extrude(exampleSketch, length = 1)
|
||||
/// ```
|
||||
#[stdlib {
|
||||
name = "subtract2d",
|
||||
feature_tree_operation = true,
|
||||
unlabeled_first = true,
|
||||
args = {
|
||||
sketch = { docs = "Which sketch should this path be added to?" },
|
||||
tool = { docs = "The shape(s) which should be cut out of the sketch." },
|
||||
},
|
||||
tags = ["sketch"]
|
||||
}]
|
||||
async fn inner_subtract_2d(
|
||||
sketch: Sketch,
|
||||
tool: Vec<Sketch>,
|
||||
|
@ -21,6 +21,7 @@ use crate::{
|
||||
#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
|
||||
#[ts(export)]
|
||||
#[serde(untagged)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum SweepPath {
|
||||
Sketch(Sketch),
|
||||
Helix(Box<Helix>),
|
||||
|
@ -36,7 +36,16 @@ pub async fn execute_and_snapshot_ast(
|
||||
ast: Program,
|
||||
current_file: Option<PathBuf>,
|
||||
with_export_step: bool,
|
||||
) -> Result<(ExecState, EnvironmentRef, image::DynamicImage, Option<Vec<u8>>), ExecErrorWithState> {
|
||||
) -> Result<
|
||||
(
|
||||
ExecState,
|
||||
ExecutorContext,
|
||||
EnvironmentRef,
|
||||
image::DynamicImage,
|
||||
Option<Vec<u8>>,
|
||||
),
|
||||
ExecErrorWithState,
|
||||
> {
|
||||
let ctx = new_context(true, current_file).await?;
|
||||
let (exec_state, env, img) = match do_execute_and_snapshot(&ctx, ast).await {
|
||||
Ok((exec_state, env_ref, img)) => (exec_state, env_ref, img),
|
||||
@ -64,7 +73,7 @@ pub async fn execute_and_snapshot_ast(
|
||||
step = files.into_iter().next().map(|f| f.contents);
|
||||
}
|
||||
ctx.close().await;
|
||||
Ok((exec_state, env, img, step))
|
||||
Ok((exec_state, ctx, env, img, step))
|
||||
}
|
||||
|
||||
pub async fn execute_and_snapshot_no_auth(
|
||||
|
@ -5,8 +5,8 @@ use crate::parsing::{
|
||||
Annotation, ArrayExpression, ArrayRangeExpression, AscribedExpression, BinaryExpression, BinaryOperator,
|
||||
BinaryPart, BodyItem, CallExpressionKw, CommentStyle, DefaultParamVal, Expr, FormatOptions, FunctionExpression,
|
||||
IfExpression, ImportSelector, ImportStatement, ItemVisibility, LabeledArg, Literal, LiteralIdentifier,
|
||||
LiteralValue, MemberExpression, MemberObject, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter,
|
||||
PipeExpression, Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
LiteralValue, MemberExpression, Node, NonCodeNode, NonCodeValue, ObjectExpression, Parameter, PipeExpression,
|
||||
Program, TagDeclarator, TypeDeclaration, UnaryExpression, VariableDeclaration, VariableKind,
|
||||
},
|
||||
deprecation, DeprecationKind, PIPE_OPERATOR,
|
||||
};
|
||||
@ -278,11 +278,11 @@ impl Expr {
|
||||
ctxt = ExprContext::Other;
|
||||
}
|
||||
match &self {
|
||||
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options),
|
||||
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::ArrayRangeExpression(range_exp) => range_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::ObjectExpression(ref obj_exp) => obj_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::MemberExpression(mem_exp) => mem_exp.recast(),
|
||||
Expr::MemberExpression(mem_exp) => mem_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::Literal(literal) => literal.recast(),
|
||||
Expr::FunctionExpression(func_exp) => {
|
||||
let mut result = if is_decl { String::new() } else { "fn".to_owned() };
|
||||
@ -299,7 +299,7 @@ impl Expr {
|
||||
}
|
||||
Expr::TagDeclarator(tag) => tag.recast(),
|
||||
Expr::PipeExpression(pipe_exp) => pipe_exp.recast(options, indentation_level),
|
||||
Expr::UnaryExpression(unary_exp) => unary_exp.recast(options),
|
||||
Expr::UnaryExpression(unary_exp) => unary_exp.recast(options, indentation_level, ctxt),
|
||||
Expr::IfExpression(e) => e.recast(options, indentation_level, ctxt),
|
||||
Expr::PipeSubstitution(_) => crate::parsing::PIPE_SUBSTITUTION_OPERATOR.to_string(),
|
||||
Expr::LabelledExpression(e) => {
|
||||
@ -332,7 +332,7 @@ impl AscribedExpression {
|
||||
}
|
||||
|
||||
impl BinaryPart {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize) -> String {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
match &self {
|
||||
BinaryPart::Literal(literal) => literal.recast(),
|
||||
BinaryPart::Name(name) => {
|
||||
@ -342,12 +342,16 @@ impl BinaryPart {
|
||||
None => result,
|
||||
}
|
||||
}
|
||||
BinaryPart::BinaryExpression(binary_expression) => binary_expression.recast(options),
|
||||
BinaryPart::BinaryExpression(binary_expression) => {
|
||||
binary_expression.recast(options, indentation_level, ctxt)
|
||||
}
|
||||
BinaryPart::CallExpressionKw(call_expression) => {
|
||||
call_expression.recast(options, indentation_level, ExprContext::Other)
|
||||
}
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options),
|
||||
BinaryPart::MemberExpression(member_expression) => member_expression.recast(),
|
||||
BinaryPart::UnaryExpression(unary_expression) => unary_expression.recast(options, indentation_level, ctxt),
|
||||
BinaryPart::MemberExpression(member_expression) => {
|
||||
member_expression.recast(options, indentation_level, ctxt)
|
||||
}
|
||||
BinaryPart::IfExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
|
||||
BinaryPart::AscribedExpression(e) => e.recast(options, indentation_level, ExprContext::Other),
|
||||
}
|
||||
@ -670,7 +674,7 @@ impl ObjectExpression {
|
||||
}
|
||||
|
||||
impl MemberExpression {
|
||||
fn recast(&self) -> String {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
let key_str = match &self.property {
|
||||
LiteralIdentifier::Identifier(identifier) => {
|
||||
if self.computed {
|
||||
@ -682,15 +686,12 @@ impl MemberExpression {
|
||||
LiteralIdentifier::Literal(lit) => format!("[{}]", &(*lit.raw)),
|
||||
};
|
||||
|
||||
match &self.object {
|
||||
MemberObject::MemberExpression(member_exp) => member_exp.recast() + key_str.as_str(),
|
||||
MemberObject::Identifier(identifier) => identifier.name.to_string() + key_str.as_str(),
|
||||
}
|
||||
self.object.recast(options, indentation_level, ctxt) + key_str.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl BinaryExpression {
|
||||
fn recast(&self, options: &FormatOptions) -> String {
|
||||
fn recast(&self, options: &FormatOptions, _indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
let maybe_wrap_it = |a: String, doit: bool| -> String {
|
||||
if doit {
|
||||
format!("({})", a)
|
||||
@ -715,15 +716,15 @@ impl BinaryExpression {
|
||||
|
||||
format!(
|
||||
"{} {} {}",
|
||||
maybe_wrap_it(self.left.recast(options, 0), should_wrap_left),
|
||||
maybe_wrap_it(self.left.recast(options, 0, ctxt), should_wrap_left),
|
||||
self.operator,
|
||||
maybe_wrap_it(self.right.recast(options, 0), should_wrap_right)
|
||||
maybe_wrap_it(self.right.recast(options, 0, ctxt), should_wrap_right)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl UnaryExpression {
|
||||
fn recast(&self, options: &FormatOptions) -> String {
|
||||
fn recast(&self, options: &FormatOptions, _indentation_level: usize, ctxt: ExprContext) -> String {
|
||||
match self.argument {
|
||||
BinaryPart::Literal(_)
|
||||
| BinaryPart::Name(_)
|
||||
@ -731,10 +732,10 @@ impl UnaryExpression {
|
||||
| BinaryPart::IfExpression(_)
|
||||
| BinaryPart::AscribedExpression(_)
|
||||
| BinaryPart::CallExpressionKw(_) => {
|
||||
format!("{}{}", &self.operator, self.argument.recast(options, 0))
|
||||
format!("{}{}", &self.operator, self.argument.recast(options, 0, ctxt))
|
||||
}
|
||||
BinaryPart::BinaryExpression(_) | BinaryPart::UnaryExpression(_) => {
|
||||
format!("{}({})", &self.operator, self.argument.recast(options, 0))
|
||||
format!("{}({})", &self.operator, self.argument.recast(options, 0, ctxt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,15 +226,6 @@ impl<'tree> From<&'tree types::BinaryPart> for Node<'tree> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::MemberObject> for Node<'tree> {
|
||||
fn from(node: &'tree types::MemberObject) -> Self {
|
||||
match node {
|
||||
types::MemberObject::MemberExpression(me) => me.as_ref().into(),
|
||||
types::MemberObject::Identifier(id) => id.as_ref().into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tree> From<&'tree types::LiteralIdentifier> for Node<'tree> {
|
||||
fn from(node: &'tree types::LiteralIdentifier) -> Self {
|
||||
match node {
|
||||
|
@ -3,10 +3,7 @@
|
||||
mod ast_node;
|
||||
mod ast_visitor;
|
||||
mod ast_walk;
|
||||
mod import_graph;
|
||||
|
||||
pub use ast_node::Node;
|
||||
pub use ast_visitor::Visitable;
|
||||
pub use ast_walk::walk;
|
||||
pub use import_graph::import_graph;
|
||||
pub(crate) use import_graph::{import_universe, Universe, UniverseMap};
|
||||
pub(crate) use ast_node::Node;
|
||||
pub(crate) use ast_visitor::Visitable;
|
||||
pub(crate) use ast_walk::walk;
|
||||
|
Reference in New Issue
Block a user