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;
68mod import_format;
69pub mod lint;
70mod log;
71mod lsp_types;
72mod modules;
73mod parsing;
74mod project;
75mod runtime_flags;
76mod settings;
77#[cfg(test)]
78mod simulation_tests;
79pub mod std;
80#[cfg(not(target_arch = "wasm32"))]
81pub mod test_server;
82mod thread;
83#[doc(hidden)]
84pub mod tooling;
85pub mod unit_conversion;
86mod unparser;
87mod util;
88#[cfg(test)]
89mod variant_name;
90pub mod walk;
91#[cfg(target_arch = "wasm32")]
92mod wasm;
93
94#[doc(hidden)]
96pub mod lsp_support {
97 pub mod docs {
98 pub mod kcl_doc {
99 pub use crate::docs::kcl_doc::ArgData;
100 pub use crate::docs::kcl_doc::DocData;
101 pub use crate::docs::kcl_doc::ModData;
102 pub use crate::docs::kcl_doc::walk_stdlib;
103 }
104 }
105
106 pub mod engine {
107 #[cfg(not(target_arch = "wasm32"))]
108 pub use crate::engine::new_zoo_client;
109 }
110
111 pub mod errors {
112 pub use crate::errors::Severity;
113 pub use crate::errors::Suggestion;
114 }
115
116 pub mod execution {
117 pub use crate::execution::ExecutorContext;
118 pub use crate::execution::MockConfig;
119
120 pub mod cache {
121 pub use crate::execution::cache::read_old_memory_var;
122 }
123
124 pub mod typed_path {
125 pub use crate::execution::typed_path::TypedPath;
126 }
127 }
128
129 pub mod fs {
130 pub use crate::fs::FileManager;
131 pub use crate::fs::FileSystemHandle;
132 pub use crate::fs::new_file_system_handle;
133
134 #[cfg(all(target_arch = "wasm32", not(test)))]
135 pub mod wasm {
136 pub use crate::fs::wasm::FileSystemManager;
137 }
138 }
139
140 pub mod parsing {
141 pub use crate::parsing::PIPE_OPERATOR;
142 pub use crate::parsing::parse_str;
143 pub use crate::parsing::parse_tokens;
144
145 pub mod ast {
146 pub mod types {
147 pub use crate::parsing::ast::types::*;
148 }
149 }
150
151 pub mod token {
152 pub use crate::parsing::token::LexerMode;
153 pub use crate::parsing::token::RESERVED_WORDS;
154 pub use crate::parsing::token::TokenStream;
155 pub use crate::parsing::token::lex;
156
157 pub mod adapter {
158 pub use crate::parsing::token::adapter::lex_with_diagnostics;
159 }
160 }
161 }
162}
163
164pub use engine::AsyncTasks;
165pub use engine::EngineBatchContext;
166pub use engine::EngineStats;
167pub use errors::BacktraceItem;
168pub use errors::BacktraceItemKind;
169pub use errors::CompilationIssue;
170pub use errors::CompilationIssueReport;
171pub use errors::ConnectionError;
172pub use errors::ExecError;
173pub use errors::IsRetryable;
174pub use errors::KclError;
175pub use errors::KclErrorWithOutputs;
176pub use errors::Report;
177pub use errors::ReportWithOutputs;
178pub use errors::render_compilation_issue_miette;
179pub use execution::ConstraintKind;
180pub use execution::EdgeRefactorMeta;
181pub use execution::EnvironmentRef;
182pub use execution::ExecOutcome;
183pub use execution::ExecState;
184pub use execution::ExecutionCallbacks;
185pub use execution::ExecutorContext;
186pub use execution::ExecutorSettings;
187pub use execution::KclValueView;
188pub use execution::KclVersion;
189pub use execution::LegacyAngleRefactorMeta;
190pub use execution::MetaSettings;
191pub use execution::MockConfig;
192pub use execution::OperationCallbackArgs;
193pub use execution::Point2d;
194pub use execution::RefactorMetadata;
195pub use execution::SegmentDragAnchor;
196pub use execution::SketchConstraintReport;
197pub use execution::SketchConstraintStatus;
198pub use execution::bust_cache;
199pub use execution::clear_mem_cache;
200pub use execution::typed_path::TypedPath;
201pub use fs::FileSystem;
202pub use fs::FileSystemHandle;
203pub use fs::in_memory::InMemoryFiles;
204pub use fs::new_file_system_handle;
205pub use kcl_error;
206pub use kcl_error::SourceRange;
207pub use lsp_types::IntoDiagnostic;
208pub use lsp_types::LspSuggestion;
209pub use lsp_types::ToLspRange;
210pub use modules::ModuleId;
211pub use parsing::ast::types::FormatOptions;
212pub use parsing::ast::types::NodePath;
213pub use parsing::ast::types::NodePathExt;
214pub use parsing::ast::types::Program as AstProgram;
215pub use parsing::ast::types::Step as NodePathStep;
216pub use project::ProjectManager;
217pub use runtime_flags::KclRuntimeFlags;
218pub use runtime_flags::RuntimeFlag;
219pub use runtime_flags::kcl_runtime_flags;
220pub use runtime_flags::set_kcl_runtime_flags;
221pub use settings::types::Configuration;
222pub use settings::types::project::ProjectConfiguration;
223#[cfg(not(target_arch = "wasm32"))]
224pub use unparser::recast_dir;
225#[cfg(not(target_arch = "wasm32"))]
226pub use unparser::walk_dir;
227
228pub mod engine_connection {
229 pub use crate::engine::engine_manager::EngineManager;
230 pub use crate::engine::engine_manager::EngineTransport;
231 pub use crate::engine::engine_manager::ResponseInformation;
232 pub use crate::engine::engine_manager::SocketHealth;
233 pub use crate::engine::engine_manager::TransportCloseError;
234}
235
236pub mod exec {
239 pub use kcl_api::NumericType;
240 pub use kcl_api::UnitAngle;
241 pub use kcl_api::UnitLength;
242 pub use kcl_api::UnitType;
243
244 pub use crate::execution::ArtifactCommand;
245 pub use crate::execution::DefaultPlanes;
246 pub use crate::execution::IdGenerator;
247 pub use crate::execution::KclObjectKind;
248 pub use crate::execution::KclValue;
249 pub use crate::execution::KclValueView;
250 pub use crate::execution::Operation;
251 pub use crate::execution::PlaneKind;
252 pub use crate::execution::Sketch;
253 pub use crate::execution::annotations::WarningLevel;
254 pub use crate::util::RetryConfig;
255 pub use crate::util::execute_with_retries;
256}
257
258#[cfg(target_arch = "wasm32")]
259pub mod wasm_engine {
260 pub use crate::engine::conn_wasm::EngineCommandManager;
261 pub use crate::engine::conn_wasm::EngineConnection;
262 pub use crate::engine::conn_wasm::ResponseContext;
263 pub use crate::fs::wasm::FileManager;
264 pub use crate::fs::wasm::FileSystemManager;
265}
266
267pub mod std_utils {
268 pub use crate::std::utils::TangentialArcInfoInput;
269 pub use crate::std::utils::get_tangential_arc_to_info;
270 pub use crate::std::utils::is_points_ccw_wasm;
271 pub use crate::std::utils::untyped_point_to_unit;
272}
273
274pub mod pretty {
275 pub use crate::fmt::format_number_literal;
276 pub use crate::fmt::format_number_value;
277 pub use crate::fmt::human_display_number;
278 pub use crate::parsing::token::NumericSuffix;
279}
280
281pub mod front {
282 pub use crate::frontend::MAX_SKETCH_CHECKPOINTS;
283 pub(crate) use crate::frontend::modify::find_defined_names;
284 pub(crate) use crate::frontend::modify::next_free_name_using_max;
285 pub use crate::frontend::sketch::ExecResult;
286 pub use crate::frontend::{
287 EditConstraintOptions,
288 EditDistanceConstraintLabelPositionOptions,
289 EditSegmentsOptions,
290 FrontendState,
291 SetProgramOutcome,
292 api::{
293 Cap, CapKind, EditSketchOutcome, Error, Expr, Face, File, FileId, LifecycleApi, NewSketchOutcome, Number,
294 Object, ObjectId, ObjectKind, Plane, ProjectId, RestoreSketchCheckpointOutcome, Result, SceneGraph,
295 SceneGraphDelta, Settings, SketchCheckpointId, SketchMutationOutcome, SourceDelta, SourceRef, Version,
296 Wall,
297 },
298 sketch::{
299 Angle, Arc, ArcCtor, ArcDirection, Circle, CircleCtor, Coincident, Constraint, ConstraintLabelPositionEdit,
300 ControlPointSpline, ControlPointSplineCtor, Distance, EqualRadius, ExistingSegmentCtor, Fixed, FixedPoint,
301 Freedom, Horizontal, Line, LineCtor, LinesEqualLength, Midpoint, NewSegmentInfo, Parallel, Perpendicular,
302 Point, Point2d, PointCtor, Segment, SegmentCtor, Sketch, SketchApi, SketchCtor, StartOrEnd, Symmetric,
303 Tangent, Vertical,
304 },
305 trim::{
307 ArcPoint, AttachToEndpoint, CoincidentData, ConstraintToMigrate, Coords2d, EndpointChanged, LineEndpoint,
308 TrimDirection, TrimItem, TrimOperation, TrimTermination, TrimTerminations, execute_trim_loop_with_context,
309 get_next_trim_spawn, get_position_coords_for_line, get_position_coords_from_arc, is_point_on_line_segment,
310 line_segment_intersection, perpendicular_distance_to_segment, project_point_onto_arc,
311 project_point_onto_segment,
312 },
313 };
314}
315
316use serde::Deserialize;
317use serde::Serialize;
318
319use crate::exec::WarningLevel;
320#[allow(unused_imports)]
321use crate::log::log;
322#[allow(unused_imports)]
323use crate::log::logln;
324
325lazy_static::lazy_static! {
326
327 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
328 import_format::IMPORT_FILE_EXTENSION_FORMATS
329 .iter()
330 .map(|(extension, _)| (*extension).to_owned())
331 .collect()
332 };
333
334 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
335 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
336 relevant_extensions.push("kcl".to_string());
337 relevant_extensions.push("md".to_string());
338 relevant_extensions
339 };
340}
341
342#[derive(Clone, Debug, PartialEq, Serialize)]
343pub struct Program {
344 pub kcl_version: KclVersion,
345 #[serde(flatten)]
346 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
347 #[serde(skip)]
351 pub original_file_contents: String,
352}
353
354impl<'de> Deserialize<'de> for Program {
355 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
356 where
357 D: serde::Deserializer<'de>,
358 {
359 #[derive(Deserialize)]
360 struct ProgramHelper {
361 #[serde(flatten)]
362 ast: parsing::ast::types::Node<parsing::ast::types::Program>,
363 }
364
365 let program = ProgramHelper::deserialize(deserializer)?;
366 let kcl_version = execution::computed_kcl_version(&program.ast);
370
371 Ok(Self {
372 kcl_version,
373 ast: program.ast,
374 original_file_contents: String::new(),
375 })
376 }
377}
378
379impl Program {
380 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationIssue>), KclError> {
381 let module_id = ModuleId::default();
382 let (program, errs) = parsing::parse_str(input, module_id).0?;
383
384 Ok((
385 program.map(|program| Program {
386 kcl_version: program.kcl_version,
387 ast: program.ast,
388 original_file_contents: input.to_string(),
389 }),
390 errs,
391 ))
392 }
393
394 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
395 let module_id = ModuleId::default();
396 let program = parsing::parse_str(input, module_id).parse_errs_as_err()?;
397
398 Ok(Program {
399 kcl_version: program.kcl_version,
400 ast: program.ast,
401 original_file_contents: input.to_string(),
402 })
403 }
404
405 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
406 self.ast.compute_digest()
407 }
408
409 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
411 self.ast.meta_settings()
412 }
413
414 pub fn language_version(&self) -> Result<KclVersion, KclError> {
417 Ok(self.meta_settings()?.unwrap_or_default().kcl_version)
418 }
419
420 pub fn change_default_units(
422 &self,
423 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
424 ) -> Result<Self, KclError> {
425 Ok(Self {
426 kcl_version: self.kcl_version,
427 ast: self.ast.change_default_units(length_units)?,
428 original_file_contents: self.original_file_contents.clone(),
429 })
430 }
431
432 pub fn change_kcl_version(&self, kcl_version: Option<KclVersion>) -> Result<Self, KclError> {
433 Ok(Self {
434 kcl_version: kcl_version.unwrap_or_default(),
435 ast: self.ast.change_kcl_version(kcl_version)?,
436 original_file_contents: self.original_file_contents.clone(),
437 })
438 }
439
440 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
441 Ok(Self {
442 kcl_version: self.kcl_version,
443 ast: self.ast.change_experimental_features(warning_level)?,
444 original_file_contents: self.original_file_contents.clone(),
445 })
446 }
447
448 pub fn is_empty_or_only_settings(&self) -> bool {
449 self.ast.is_empty_or_only_settings()
450 }
451
452 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
453 self.ast.lint_all()
454 }
455
456 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
457 self.ast.lint(rule)
458 }
459
460 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
461 let module_infos = indexmap::IndexMap::new();
462 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
463 NodePath::from_range(&programs, cached_body_items, range)
464 }
465
466 pub fn fill_node_paths(mut self) -> Program {
472 parsing::ast::types::fill_node_paths(&mut self.ast);
473 self
474 }
475
476 pub fn recast(&self) -> String {
477 self.ast.recast_top(&Default::default(), 0)
479 }
480
481 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
482 self.ast.recast_top(options, 0)
483 }
484
485 pub fn empty() -> Self {
487 Self {
488 kcl_version: KclVersion::default(),
489 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
490 original_file_contents: String::new(),
491 }
492 }
493}
494
495#[inline]
496fn try_f64_to_usize(f: f64) -> Option<usize> {
497 let i = f as usize;
498 if i as f64 == f { Some(i) } else { None }
499}
500
501#[inline]
502fn try_f64_to_u32(f: f64) -> Option<u32> {
503 let i = f as u32;
504 if i as f64 == f { Some(i) } else { None }
505}
506
507#[inline]
508fn try_f64_to_u64(f: f64) -> Option<u64> {
509 let i = f as u64;
510 if i as f64 == f { Some(i) } else { None }
511}
512
513#[inline]
514fn try_f64_to_i64(f: f64) -> Option<i64> {
515 let i = f as i64;
516 if i as f64 == f { Some(i) } else { None }
517}
518
519pub fn version() -> &'static str {
521 env!("CARGO_PKG_VERSION")
522}
523
524#[cfg(test)]
525mod test {
526 use super::*;
527
528 #[test]
529 fn program_deserializes_ast_without_kcl_version() {
530 for code in [
531 "",
532 "x = 1\n",
533 "@settings(defaultLengthUnit = mm)\nx = 1\n",
534 "@settings(kclVersion = 1.0)\nx = 1\n",
535 "@settings(kclVersion = 2.0)\nx = 1\n",
536 "@settings(kclVersion = 3.0)\nx = 1\n",
537 "@settings(kclVersion = \"3.0\")\nx = 1\n",
538 "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
539 "@settings(kclVersion = \"3-preview\")\nx = 1\n",
540 "@settings(kclVersion = 2.0)\n@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
541 "@settings(kclVersion = \"3.0-preview\")\n@settings(kclVersion = 2.0)\nx = 1\n",
542 "@settings(kclVersion = 2.0)\n@settings(defaultLengthUnit = in)\nx = 1\n",
543 "@settings(kclVersion = 99.0)\nx = 1\n",
544 ] {
545 let parsed = Program::parse_no_errs(code).unwrap();
546 let json = serde_json::to_value(&parsed.ast).unwrap();
548 let deserialized: Program = serde_json::from_str(&json.to_string()).unwrap();
549
550 assert_eq!(deserialized.kcl_version, parsed.kcl_version, "{code}");
551 assert_eq!(deserialized.recast(), parsed.recast(), "{code}");
552 }
553 }
554
555 #[test]
556 fn program_deserialization_recomputes_kcl_version() {
557 for (code, supplied_version, expected_version) in [
558 ("x = 1\n", KclVersion::V2, KclVersion::V1),
559 (
560 "@settings(kclVersion = 2.0)\nx = 1\n",
561 KclVersion::V3Preview,
562 KclVersion::V2,
563 ),
564 (
565 "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
566 KclVersion::V2,
567 KclVersion::V3Preview,
568 ),
569 (
570 "@settings(kclVersion = 3.0)\nx = 1\n",
571 KclVersion::V3Preview,
572 KclVersion::V3,
573 ),
574 (
575 "@settings(kclVersion = \"3.0-preview\")\nx = 1\n",
576 KclVersion::V3,
577 KclVersion::V3Preview,
578 ),
579 ] {
580 let mut program = Program::parse_no_errs(code).unwrap();
581 program.kcl_version = supplied_version;
582
583 let json = serde_json::to_value(&program).unwrap();
584 let deserialized: Program = serde_json::from_str(&json.to_string()).unwrap();
585
586 assert_eq!(deserialized.kcl_version, expected_version, "{code}");
587 assert_eq!(deserialized.recast(), program.recast(), "{code}");
588 assert!(deserialized.original_file_contents.is_empty());
589 }
590 }
591
592 #[test]
593 fn entry_point_language_version() {
594 for (code, expected) in [
595 ("", KclVersion::V1),
596 ("@settings(defaultLengthUnit = mm)", KclVersion::V1),
597 ("@settings(kclVersion = 2.0)", KclVersion::V2),
598 ("@settings(kclVersion = 3.0)", KclVersion::V3),
599 ("@settings(kclVersion = \"3.0\")", KclVersion::V3),
600 ("@settings(kclVersion = \"3.0-preview\")", KclVersion::V3Preview),
601 ] {
602 assert_eq!(
603 Program::parse_no_errs(code).unwrap().language_version().unwrap(),
604 expected
605 );
606 }
607 let invalid = Program::parse_no_errs("@settings(kclVersion = 99.0)").unwrap();
608 invalid.language_version().unwrap_err();
609 }
610
611 #[test]
612 fn proprietary_file_extensions_use_real_suffixes() {
613 for extension in ["sat", "sab", "catpart", "prt", "ipt", "x_t", "x_b", "sldprt"] {
614 assert!(IMPORT_FILE_EXTENSIONS.iter().any(|candidate| candidate == extension));
615 }
616 }
617
618 #[test]
619 fn convert_int() {
620 assert_eq!(try_f64_to_usize(0.0), Some(0));
621 assert_eq!(try_f64_to_usize(42.0), Some(42));
622 assert_eq!(try_f64_to_usize(0.00000000001), None);
623 assert_eq!(try_f64_to_usize(-1.0), None);
624 assert_eq!(try_f64_to_usize(f64::NAN), None);
625 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
626 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
627
628 assert_eq!(try_f64_to_u32(0.0), Some(0));
629 assert_eq!(try_f64_to_u32(42.0), Some(42));
630 assert_eq!(try_f64_to_u32(0.00000000001), None);
631 assert_eq!(try_f64_to_u32(-1.0), None);
632 assert_eq!(try_f64_to_u32(f64::NAN), None);
633 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
634 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
635
636 assert_eq!(try_f64_to_u64(0.0), Some(0));
637 assert_eq!(try_f64_to_u64(42.0), Some(42));
638 assert_eq!(try_f64_to_u64(0.00000000001), None);
639 assert_eq!(try_f64_to_u64(-1.0), None);
640 assert_eq!(try_f64_to_u64(f64::NAN), None);
641 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
642 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
643
644 assert_eq!(try_f64_to_i64(0.0), Some(0));
645 assert_eq!(try_f64_to_i64(42.0), Some(42));
646 assert_eq!(try_f64_to_i64(0.00000000001), None);
647 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
648 assert_eq!(try_f64_to_i64(f64::NAN), None);
649 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
650 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
651 }
652}