1#![recursion_limit = "1024"]
6#![allow(clippy::boxed_local)]
7
8#[allow(unused_macros)]
9macro_rules! println {
10 ($($rest:tt)*) => {
11 #[cfg(all(feature = "disable-println", not(test)))]
12 {
13 let _ = format!($($rest)*);
14 }
15 #[cfg(any(not(feature = "disable-println"), test))]
16 std::println!($($rest)*)
17 }
18}
19
20#[allow(unused_macros)]
21macro_rules! eprintln {
22 ($($rest:tt)*) => {
23 #[cfg(all(feature = "disable-println", not(test)))]
24 {
25 let _ = format!($($rest)*);
26 }
27 #[cfg(any(not(feature = "disable-println"), test))]
28 std::eprintln!($($rest)*)
29 }
30}
31
32#[allow(unused_macros)]
33macro_rules! print {
34 ($($rest:tt)*) => {
35 #[cfg(all(feature = "disable-println", not(test)))]
36 {
37 let _ = format!($($rest)*);
38 }
39 #[cfg(any(not(feature = "disable-println"), test))]
40 std::print!($($rest)*)
41 }
42}
43
44#[allow(unused_macros)]
45macro_rules! eprint {
46 ($($rest:tt)*) => {
47 #[cfg(all(feature = "disable-println", not(test)))]
48 {
49 let _ = format!($($rest)*);
50 }
51 #[cfg(any(not(feature = "disable-println"), test))]
52 std::eprint!($($rest)*)
53 }
54}
55#[cfg(feature = "dhat-heap")]
56#[global_allocator]
57static ALLOC: dhat::Alloc = dhat::Alloc;
58
59pub mod collections;
60mod docs;
61mod engine;
62mod errors;
63mod execution;
64mod fmt;
65mod frontend;
66mod fs;
67pub(crate) mod id;
68pub mod lint;
69mod log;
70mod lsp;
71mod modules;
72mod parsing;
73mod project;
74mod runtime_flags;
75mod settings;
76#[cfg(test)]
77mod simulation_tests;
78pub mod std;
79#[cfg(not(target_arch = "wasm32"))]
80pub mod test_server;
81mod thread;
82#[doc(hidden)]
83pub mod tooling;
84pub mod unit_conversion;
85mod unparser;
86mod util;
87#[cfg(test)]
88mod variant_name;
89pub mod walk;
90#[cfg(target_arch = "wasm32")]
91mod wasm;
92
93pub use engine::AsyncTasks;
94pub use engine::EngineBatchContext;
95pub use engine::EngineStats;
96pub use errors::BacktraceItem;
97pub use errors::CompilationIssue;
98pub use errors::CompilationIssueReport;
99pub use errors::ConnectionError;
100pub use errors::ExecError;
101pub use errors::IsRetryable;
102pub use errors::KclError;
103pub use errors::KclErrorWithOutputs;
104pub use errors::Report;
105pub use errors::ReportWithOutputs;
106pub use errors::render_compilation_issue_miette;
107pub use execution::ConstraintKind;
108pub use execution::EdgeRefactorMeta;
109pub use execution::ExecOutcome;
110pub use execution::ExecState;
111pub use execution::ExecutionCallbacks;
112pub use execution::ExecutorContext;
113pub use execution::ExecutorSettings;
114pub use execution::KclValueView;
115pub use execution::KclVersion;
116pub use execution::LegacyAngleRefactorMeta;
117pub use execution::MetaSettings;
118pub use execution::MockConfig;
119pub use execution::OperationCallbackArgs;
120pub use execution::Point2d;
121pub use execution::RefactorMetadata;
122pub use execution::SegmentDragAnchor;
123pub use execution::SketchConstraintReport;
124pub use execution::SketchConstraintStatus;
125pub use execution::bust_cache;
126pub use execution::clear_mem_cache;
127pub use execution::pre_execute_transpile;
128pub use execution::transpile_all_old_sketches_to_new;
129pub use execution::transpile_old_sketch_to_new;
130pub use execution::transpile_old_sketch_to_new_ast;
131pub use execution::transpile_old_sketch_to_new_with_execution;
132pub use execution::typed_path::TypedPath;
133pub use fs::FileSystem;
134pub use fs::FileSystemHandle;
135pub use fs::in_memory::InMemoryFiles;
136pub use fs::new_file_system_handle;
137pub use kcl_error;
138pub use kcl_error::SourceRange;
139pub use lsp::ToLspRange;
140pub use lsp::copilot::Backend as CopilotLspBackend;
141pub use lsp::kcl::Backend as KclLspBackend;
142pub use lsp::kcl::Server as KclLspServerSubCommand;
143pub use modules::ModuleId;
144pub use parsing::ast::types::FormatOptions;
145pub use parsing::ast::types::NodePath;
146pub use parsing::ast::types::NodePathExt;
147pub use parsing::ast::types::Program as AstProgram;
148pub use parsing::ast::types::Step as NodePathStep;
149pub use project::ProjectManager;
150pub use runtime_flags::KclRuntimeFlags;
151pub use runtime_flags::RuntimeFlag;
152pub use runtime_flags::kcl_runtime_flags;
153pub use runtime_flags::set_kcl_runtime_flags;
154pub use settings::types::Configuration;
155pub use settings::types::project::ProjectConfiguration;
156#[cfg(not(target_arch = "wasm32"))]
157pub use unparser::recast_dir;
158#[cfg(not(target_arch = "wasm32"))]
159pub use unparser::walk_dir;
160
161pub mod engine_connection {
162 pub use crate::engine::engine_manager::EngineManager;
163 pub use crate::engine::engine_manager::EngineTransport;
164 pub use crate::engine::engine_manager::ResponseInformation;
165 pub use crate::engine::engine_manager::SocketHealth;
166 pub use crate::engine::engine_manager::TransportCloseError;
167}
168
169pub mod exec {
172 pub use kcl_api::NumericType;
173 pub use kcl_api::UnitAngle;
174 pub use kcl_api::UnitLength;
175 pub use kcl_api::UnitType;
176
177 pub use crate::execution::ArtifactCommand;
178 pub use crate::execution::DefaultPlanes;
179 pub use crate::execution::IdGenerator;
180 pub use crate::execution::KclObjectKind;
181 pub use crate::execution::KclValue;
182 pub use crate::execution::KclValueView;
183 pub use crate::execution::Operation;
184 pub use crate::execution::PlaneKind;
185 pub use crate::execution::Sketch;
186 pub use crate::execution::annotations::WarningLevel;
187 pub use crate::util::RetryConfig;
188 pub use crate::util::execute_with_retries;
189}
190
191#[cfg(target_arch = "wasm32")]
192pub mod wasm_engine {
193 pub use crate::engine::conn_wasm::EngineCommandManager;
194 pub use crate::engine::conn_wasm::EngineConnection;
195 pub use crate::engine::conn_wasm::ResponseContext;
196 pub use crate::fs::wasm::FileManager;
197 pub use crate::fs::wasm::FileSystemManager;
198}
199
200pub mod std_utils {
201 pub use crate::std::utils::TangentialArcInfoInput;
202 pub use crate::std::utils::get_tangential_arc_to_info;
203 pub use crate::std::utils::is_points_ccw_wasm;
204 pub use crate::std::utils::untyped_point_to_unit;
205}
206
207pub mod pretty {
208 pub use crate::fmt::format_number_literal;
209 pub use crate::fmt::format_number_value;
210 pub use crate::fmt::human_display_number;
211 pub use crate::parsing::token::NumericSuffix;
212}
213
214pub mod front {
215 pub use crate::frontend::MAX_SKETCH_CHECKPOINTS;
216 pub(crate) use crate::frontend::modify::find_defined_names;
217 pub(crate) use crate::frontend::modify::next_free_name_using_max;
218 pub use crate::frontend::sketch::ExecResult;
219 pub use crate::frontend::{
220 EditAngleConstraintOptions,
221 EditDistanceConstraintLabelPositionOptions,
222 EditSegmentsOptions,
223 FrontendState,
224 SetProgramOutcome,
225 api::{
226 Cap, CapKind, EditSketchOutcome, Error, Expr, Face, File, FileId, LifecycleApi, NewSketchOutcome, Number,
227 Object, ObjectId, ObjectKind, Plane, ProjectId, RestoreSketchCheckpointOutcome, Result, SceneGraph,
228 SceneGraphDelta, Settings, SketchCheckpointId, SketchMutationOutcome, SourceDelta, SourceRef, Version,
229 Wall,
230 },
231 sketch::{
232 Angle, Arc, ArcCtor, ArcDirection, Circle, CircleCtor, Coincident, Constraint, ConstraintLabelPositionEdit,
233 ControlPointSpline, ControlPointSplineCtor, Distance, EqualRadius, ExistingSegmentCtor, Fixed, FixedPoint,
234 Freedom, Horizontal, Line, LineCtor, LinesEqualLength, Midpoint, NewSegmentInfo, Parallel, Perpendicular,
235 Point, Point2d, PointCtor, Segment, SegmentCtor, Sketch, SketchApi, SketchCtor, StartOrEnd, Symmetric,
236 Tangent, Vertical,
237 },
238 trim::{
240 ArcPoint, AttachToEndpoint, CoincidentData, ConstraintToMigrate, Coords2d, EndpointChanged, LineEndpoint,
241 TrimDirection, TrimItem, TrimOperation, TrimTermination, TrimTerminations, execute_trim_loop_with_context,
242 get_next_trim_spawn, get_position_coords_for_line, get_position_coords_from_arc,
243 get_trim_spawn_terminations, is_point_on_line_segment, line_segment_intersection,
244 perpendicular_distance_to_segment, project_point_onto_arc, project_point_onto_segment,
245 },
246 };
247}
248
249#[cfg(feature = "cli")]
250use clap::ValueEnum;
251use serde::Deserialize;
252use serde::Serialize;
253
254use crate::exec::WarningLevel;
255#[allow(unused_imports)]
256use crate::log::log;
257#[allow(unused_imports)]
258use crate::log::logln;
259
260lazy_static::lazy_static! {
261
262 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
263 let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
264 #[cfg(feature = "cli")]
265 let named_extensions = kittycad::types::FileImportFormat::value_variants()
266 .iter()
267 .map(|x| format!("{x}"))
268 .collect::<Vec<String>>();
269 #[cfg(not(feature = "cli"))]
270 let named_extensions = vec![]; import_file_extensions.extend_from_slice(&named_extensions);
273 import_file_extensions
274 };
275
276 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
277 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
278 relevant_extensions.push("kcl".to_string());
279 relevant_extensions.push("md".to_string());
280 relevant_extensions
281 };
282}
283
284#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
285pub struct Program {
286 #[serde(flatten)]
287 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
288 #[serde(skip)]
292 pub original_file_contents: String,
293}
294
295#[cfg(any(test, feature = "lsp-test-util"))]
296pub use lsp::test_util::copilot_lsp_server;
297#[cfg(any(test, feature = "lsp-test-util"))]
298pub use lsp::test_util::kcl_lsp_server;
299
300impl Program {
301 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationIssue>), KclError> {
302 let module_id = ModuleId::default();
303 let (ast, errs) = parsing::parse_str(input, module_id).0?;
304
305 Ok((
306 ast.map(|ast| Program {
307 ast,
308 original_file_contents: input.to_string(),
309 }),
310 errs,
311 ))
312 }
313
314 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
315 let module_id = ModuleId::default();
316 let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
317
318 Ok(Program {
319 ast,
320 original_file_contents: input.to_string(),
321 })
322 }
323
324 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
325 self.ast.compute_digest()
326 }
327
328 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
330 self.ast.meta_settings()
331 }
332
333 pub fn change_default_units(
335 &self,
336 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
337 ) -> Result<Self, KclError> {
338 Ok(Self {
339 ast: self.ast.change_default_units(length_units)?,
340 original_file_contents: self.original_file_contents.clone(),
341 })
342 }
343
344 pub fn change_kcl_version(&self, kcl_version: Option<String>) -> Result<Self, KclError> {
345 Ok(Self {
346 ast: self.ast.change_kcl_version(kcl_version)?,
347 original_file_contents: self.original_file_contents.clone(),
348 })
349 }
350
351 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
352 Ok(Self {
353 ast: self.ast.change_experimental_features(warning_level)?,
354 original_file_contents: self.original_file_contents.clone(),
355 })
356 }
357
358 pub fn is_empty_or_only_settings(&self) -> bool {
359 self.ast.is_empty_or_only_settings()
360 }
361
362 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
363 self.ast.lint_all()
364 }
365
366 pub fn lint_all_with_options(&self, options: lint::LintOptions) -> Result<Vec<lint::Discovered>, anyhow::Error> {
367 self.ast.lint_all_with_options(options)
368 }
369
370 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
371 self.ast.lint(rule)
372 }
373
374 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
375 let module_infos = indexmap::IndexMap::new();
376 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
377 NodePath::from_range(&programs, cached_body_items, range)
378 }
379
380 pub fn fill_node_paths(mut self) -> Program {
386 parsing::ast::types::fill_node_paths(&mut self.ast);
387 self
388 }
389
390 pub fn recast(&self) -> String {
391 self.ast.recast_top(&Default::default(), 0)
393 }
394
395 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
396 self.ast.recast_top(options, 0)
397 }
398
399 pub fn empty() -> Self {
401 Self {
402 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
403 original_file_contents: String::new(),
404 }
405 }
406}
407
408#[inline]
409fn try_f64_to_usize(f: f64) -> Option<usize> {
410 let i = f as usize;
411 if i as f64 == f { Some(i) } else { None }
412}
413
414#[inline]
415fn try_f64_to_u32(f: f64) -> Option<u32> {
416 let i = f as u32;
417 if i as f64 == f { Some(i) } else { None }
418}
419
420#[inline]
421fn try_f64_to_u64(f: f64) -> Option<u64> {
422 let i = f as u64;
423 if i as f64 == f { Some(i) } else { None }
424}
425
426#[inline]
427fn try_f64_to_i64(f: f64) -> Option<i64> {
428 let i = f as i64;
429 if i as f64 == f { Some(i) } else { None }
430}
431
432pub fn version() -> &'static str {
434 env!("CARGO_PKG_VERSION")
435}
436
437#[cfg(test)]
438mod test {
439 use super::*;
440
441 #[test]
442 fn convert_int() {
443 assert_eq!(try_f64_to_usize(0.0), Some(0));
444 assert_eq!(try_f64_to_usize(42.0), Some(42));
445 assert_eq!(try_f64_to_usize(0.00000000001), None);
446 assert_eq!(try_f64_to_usize(-1.0), None);
447 assert_eq!(try_f64_to_usize(f64::NAN), None);
448 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
449 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
450
451 assert_eq!(try_f64_to_u32(0.0), Some(0));
452 assert_eq!(try_f64_to_u32(42.0), Some(42));
453 assert_eq!(try_f64_to_u32(0.00000000001), None);
454 assert_eq!(try_f64_to_u32(-1.0), None);
455 assert_eq!(try_f64_to_u32(f64::NAN), None);
456 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
457 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
458
459 assert_eq!(try_f64_to_u64(0.0), Some(0));
460 assert_eq!(try_f64_to_u64(42.0), Some(42));
461 assert_eq!(try_f64_to_u64(0.00000000001), None);
462 assert_eq!(try_f64_to_u64(-1.0), None);
463 assert_eq!(try_f64_to_u64(f64::NAN), None);
464 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
465 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
466
467 assert_eq!(try_f64_to_i64(0.0), Some(0));
468 assert_eq!(try_f64_to_i64(42.0), Some(42));
469 assert_eq!(try_f64_to_i64(0.00000000001), None);
470 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
471 assert_eq!(try_f64_to_i64(f64::NAN), None);
472 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
473 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
474 }
475}