Skip to main content

kcl_lib/
lib.rs

1//! Rust support for KCL (aka the KittyCAD Language).
2//!
3//! KCL is written in Rust. This crate contains the compiler tooling (e.g. parser, lexer, code generation),
4//! the standard library implementation, generator for the docs, and more.
5#![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/// Internal compiler APIs consumed by the standalone language-server crate.
95#[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
236// Rather than make executor public and make lots of it pub(crate), just re-export into a new module.
237// Ideally we wouldn't export these things at all, they should only be used for testing.
238pub 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        // Re-export trim module items
306        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, Deserialize)]
343pub struct Program {
344    #[serde(flatten)]
345    pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
346    // The ui doesn't need to know about this.
347    // It's purely used for saving the contents of the original file, so we can use it for errors.
348    // Because in the case of the root file, we don't want to read the file from disk again.
349    #[serde(skip)]
350    pub original_file_contents: String,
351}
352
353impl Program {
354    pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationIssue>), KclError> {
355        let module_id = ModuleId::default();
356        let (ast, errs) = parsing::parse_str(input, module_id).0?;
357
358        Ok((
359            ast.map(|ast| Program {
360                ast,
361                original_file_contents: input.to_string(),
362            }),
363            errs,
364        ))
365    }
366
367    pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
368        let module_id = ModuleId::default();
369        let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
370
371        Ok(Program {
372            ast,
373            original_file_contents: input.to_string(),
374        })
375    }
376
377    pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
378        self.ast.compute_digest()
379    }
380
381    /// Get the meta settings for the kcl file from the annotations.
382    pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
383        self.ast.meta_settings()
384    }
385
386    /// Change the meta settings for the kcl file.
387    pub fn change_default_units(
388        &self,
389        length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
390    ) -> Result<Self, KclError> {
391        Ok(Self {
392            ast: self.ast.change_default_units(length_units)?,
393            original_file_contents: self.original_file_contents.clone(),
394        })
395    }
396
397    pub fn change_kcl_version(&self, kcl_version: Option<String>) -> Result<Self, KclError> {
398        Ok(Self {
399            ast: self.ast.change_kcl_version(kcl_version)?,
400            original_file_contents: self.original_file_contents.clone(),
401        })
402    }
403
404    pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
405        Ok(Self {
406            ast: self.ast.change_experimental_features(warning_level)?,
407            original_file_contents: self.original_file_contents.clone(),
408        })
409    }
410
411    pub fn is_empty_or_only_settings(&self) -> bool {
412        self.ast.is_empty_or_only_settings()
413    }
414
415    pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
416        self.ast.lint_all()
417    }
418
419    pub fn lint_all_with_options(&self, options: lint::LintOptions) -> Result<Vec<lint::Discovered>, anyhow::Error> {
420        self.ast.lint_all_with_options(options)
421    }
422
423    pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
424        self.ast.lint(rule)
425    }
426
427    pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
428        let module_infos = indexmap::IndexMap::new();
429        let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
430        NodePath::from_range(&programs, cached_body_items, range)
431    }
432
433    /// Fill node paths and consume the input so that the program without paths
434    /// isn't accidentally used. Filling node paths happens automatically during
435    /// parsing. Calling this is only needed after the caller invalidates the
436    /// node paths such as by mutating an AST or by making a round-trip through
437    /// serialization.
438    pub fn fill_node_paths(mut self) -> Program {
439        parsing::ast::types::fill_node_paths(&mut self.ast);
440        self
441    }
442
443    pub fn recast(&self) -> String {
444        // Use the default options until we integrate into the UI the ability to change them.
445        self.ast.recast_top(&Default::default(), 0)
446    }
447
448    pub fn recast_with_options(&self, options: &FormatOptions) -> String {
449        self.ast.recast_top(options, 0)
450    }
451
452    /// Create an empty program.
453    pub fn empty() -> Self {
454        Self {
455            ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
456            original_file_contents: String::new(),
457        }
458    }
459}
460
461#[inline]
462fn try_f64_to_usize(f: f64) -> Option<usize> {
463    let i = f as usize;
464    if i as f64 == f { Some(i) } else { None }
465}
466
467#[inline]
468fn try_f64_to_u32(f: f64) -> Option<u32> {
469    let i = f as u32;
470    if i as f64 == f { Some(i) } else { None }
471}
472
473#[inline]
474fn try_f64_to_u64(f: f64) -> Option<u64> {
475    let i = f as u64;
476    if i as f64 == f { Some(i) } else { None }
477}
478
479#[inline]
480fn try_f64_to_i64(f: f64) -> Option<i64> {
481    let i = f as i64;
482    if i as f64 == f { Some(i) } else { None }
483}
484
485/// Get the version of the KCL library.
486pub fn version() -> &'static str {
487    env!("CARGO_PKG_VERSION")
488}
489
490#[cfg(test)]
491mod test {
492    use super::*;
493
494    #[test]
495    fn proprietary_file_extensions_use_real_suffixes() {
496        for extension in ["sat", "sab", "catpart", "prt", "ipt", "x_t", "x_b", "sldprt"] {
497            assert!(IMPORT_FILE_EXTENSIONS.iter().any(|candidate| candidate == extension));
498        }
499    }
500
501    #[test]
502    fn convert_int() {
503        assert_eq!(try_f64_to_usize(0.0), Some(0));
504        assert_eq!(try_f64_to_usize(42.0), Some(42));
505        assert_eq!(try_f64_to_usize(0.00000000001), None);
506        assert_eq!(try_f64_to_usize(-1.0), None);
507        assert_eq!(try_f64_to_usize(f64::NAN), None);
508        assert_eq!(try_f64_to_usize(f64::INFINITY), None);
509        assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
510
511        assert_eq!(try_f64_to_u32(0.0), Some(0));
512        assert_eq!(try_f64_to_u32(42.0), Some(42));
513        assert_eq!(try_f64_to_u32(0.00000000001), None);
514        assert_eq!(try_f64_to_u32(-1.0), None);
515        assert_eq!(try_f64_to_u32(f64::NAN), None);
516        assert_eq!(try_f64_to_u32(f64::INFINITY), None);
517        assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
518
519        assert_eq!(try_f64_to_u64(0.0), Some(0));
520        assert_eq!(try_f64_to_u64(42.0), Some(42));
521        assert_eq!(try_f64_to_u64(0.00000000001), None);
522        assert_eq!(try_f64_to_u64(-1.0), None);
523        assert_eq!(try_f64_to_u64(f64::NAN), None);
524        assert_eq!(try_f64_to_u64(f64::INFINITY), None);
525        assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
526
527        assert_eq!(try_f64_to_i64(0.0), Some(0));
528        assert_eq!(try_f64_to_i64(42.0), Some(42));
529        assert_eq!(try_f64_to_i64(0.00000000001), None);
530        assert_eq!(try_f64_to_i64(-1.0), Some(-1));
531        assert_eq!(try_f64_to_i64(f64::NAN), None);
532        assert_eq!(try_f64_to_i64(f64::INFINITY), None);
533        assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
534    }
535}