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 coredump;
61mod docs;
62mod engine;
63mod errors;
64mod execution;
65mod fmt;
66mod frontend;
67mod fs;
68pub(crate) mod id;
69pub mod lint;
70mod log;
71mod lsp;
72mod modules;
73mod parsing;
74mod project;
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;
84mod unparser;
85mod util;
86#[cfg(test)]
87mod variant_name;
88pub mod walk;
89#[cfg(target_arch = "wasm32")]
90mod wasm;
91
92pub use coredump::CoreDump;
93pub use engine::AsyncTasks;
94pub use engine::EngineManager;
95pub use engine::EngineStats;
96pub use errors::BacktraceItem;
97pub use errors::CompilationIssue;
98pub use errors::ConnectionError;
99pub use errors::ExecError;
100pub use errors::KclError;
101pub use errors::KclErrorWithOutputs;
102pub use errors::Report;
103pub use errors::ReportWithOutputs;
104pub use execution::ConstraintKind;
105pub use execution::ExecOutcome;
106pub use execution::ExecState;
107pub use execution::ExecutorContext;
108pub use execution::ExecutorSettings;
109pub use execution::MetaSettings;
110pub use execution::MockConfig;
111pub use execution::Point2d;
112pub use execution::SketchConstraintReport;
113pub use execution::SketchConstraintStatus;
114pub use execution::bust_cache;
115pub use execution::clear_mem_cache;
116pub use execution::pre_execute_transpile;
117pub use execution::transpile_all_old_sketches_to_new;
118pub use execution::transpile_old_sketch_to_new;
119pub use execution::transpile_old_sketch_to_new_ast;
120pub use execution::transpile_old_sketch_to_new_with_execution;
121pub use execution::typed_path::TypedPath;
122pub use kcl_error::SourceRange;
123pub use lsp::ToLspRange;
124pub use lsp::copilot::Backend as CopilotLspBackend;
125pub use lsp::kcl::Backend as KclLspBackend;
126pub use lsp::kcl::Server as KclLspServerSubCommand;
127pub use modules::ModuleId;
128pub use parsing::ast::types::FormatOptions;
129pub use parsing::ast::types::NodePath;
130pub use parsing::ast::types::Step as NodePathStep;
131pub use project::ProjectManager;
132pub use settings::types::Configuration;
133pub use settings::types::project::ProjectConfiguration;
134#[cfg(not(target_arch = "wasm32"))]
135pub use unparser::recast_dir;
136#[cfg(not(target_arch = "wasm32"))]
137pub use unparser::walk_dir;
138
139pub mod exec {
142 #[cfg(feature = "artifact-graph")]
143 pub use crate::execution::ArtifactCommand;
144 pub use crate::execution::DefaultPlanes;
145 pub use crate::execution::IdGenerator;
146 pub use crate::execution::KclValue;
147 #[cfg(feature = "artifact-graph")]
148 pub use crate::execution::Operation;
149 pub use crate::execution::PlaneKind;
150 pub use crate::execution::Sketch;
151 pub use crate::execution::annotations::WarningLevel;
152 pub use crate::execution::types::NumericType;
153 pub use crate::execution::types::UnitType;
154 pub use crate::util::RetryConfig;
155 pub use crate::util::execute_with_retries;
156}
157
158#[cfg(target_arch = "wasm32")]
159pub mod wasm_engine {
160 pub use crate::coredump::wasm::CoreDumpManager;
161 pub use crate::coredump::wasm::CoreDumper;
162 pub use crate::engine::conn_wasm::EngineCommandManager;
163 pub use crate::engine::conn_wasm::EngineConnection;
164 pub use crate::engine::conn_wasm::ResponseContext;
165 pub use crate::fs::wasm::FileManager;
166 pub use crate::fs::wasm::FileSystemManager;
167}
168
169pub mod mock_engine {
170 pub use crate::engine::conn_mock::EngineConnection;
171}
172
173#[cfg(not(target_arch = "wasm32"))]
174pub mod native_engine {
175 pub use crate::engine::conn::EngineConnection;
176}
177
178pub mod std_utils {
179 pub use crate::std::utils::TangentialArcInfoInput;
180 pub use crate::std::utils::get_tangential_arc_to_info;
181 pub use crate::std::utils::is_points_ccw_wasm;
182 pub use crate::std::utils::untyped_point_to_unit;
183}
184
185pub mod pretty {
186 pub use crate::fmt::format_number_literal;
187 pub use crate::fmt::format_number_value;
188 pub use crate::fmt::human_display_number;
189 pub use crate::parsing::token::NumericSuffix;
190}
191
192pub mod front {
193 pub use crate::frontend::MAX_SKETCH_CHECKPOINTS;
194 pub(crate) use crate::frontend::modify::find_defined_names;
195 pub(crate) use crate::frontend::modify::next_free_name_using_max;
196 pub use crate::frontend::sketch::ExecResult;
197 pub use crate::frontend::{
198 FrontendState,
199 SetProgramOutcome,
200 api::{
201 Cap, CapKind, EditSketchOutcome, Error, Expr, Face, File, FileId, LifecycleApi, NewSketchOutcome, Number,
202 Object, ObjectId, ObjectKind, Plane, ProjectId, RestoreSketchCheckpointOutcome, Result, SceneGraph,
203 SceneGraphDelta, Settings, SketchCheckpointId, SketchMutationOutcome, SourceDelta, SourceRef, Version,
204 Wall,
205 },
206 sketch::{
207 Angle, Arc, ArcCtor, Circle, CircleCtor, Coincident, Constraint, Distance, EqualRadius,
208 ExistingSegmentCtor, Fixed, FixedPoint, Freedom, Horizontal, Line, LineCtor, LinesEqualLength, Midpoint,
209 NewSegmentInfo, Parallel, Perpendicular, Point, Point2d, PointCtor, Segment, SegmentCtor, Sketch,
210 SketchApi, SketchCtor, StartOrEnd, Tangent, Vertical,
211 },
212 trim::{
214 ArcPoint, AttachToEndpoint, CoincidentData, ConstraintToMigrate, Coords2d, EndpointChanged, LineEndpoint,
215 TrimDirection, TrimItem, TrimOperation, TrimTermination, TrimTerminations, arc_arc_intersection,
216 execute_trim_loop_with_context, get_next_trim_spawn, get_position_coords_for_line,
217 get_position_coords_from_arc, get_trim_spawn_terminations, is_point_on_arc, is_point_on_line_segment,
218 line_arc_intersection, line_segment_intersection, perpendicular_distance_to_segment,
219 project_point_onto_arc, project_point_onto_segment,
220 },
221 };
222}
223
224#[cfg(feature = "cli")]
225use clap::ValueEnum;
226use serde::Deserialize;
227use serde::Serialize;
228
229use crate::exec::WarningLevel;
230#[allow(unused_imports)]
231use crate::log::log;
232#[allow(unused_imports)]
233use crate::log::logln;
234
235lazy_static::lazy_static! {
236
237 pub static ref IMPORT_FILE_EXTENSIONS: Vec<String> = {
238 let mut import_file_extensions = vec!["stp".to_string(), "glb".to_string(), "fbxb".to_string()];
239 #[cfg(feature = "cli")]
240 let named_extensions = kittycad::types::FileImportFormat::value_variants()
241 .iter()
242 .map(|x| format!("{x}"))
243 .collect::<Vec<String>>();
244 #[cfg(not(feature = "cli"))]
245 let named_extensions = vec![]; import_file_extensions.extend_from_slice(&named_extensions);
248 import_file_extensions
249 };
250
251 pub static ref RELEVANT_FILE_EXTENSIONS: Vec<String> = {
252 let mut relevant_extensions = IMPORT_FILE_EXTENSIONS.clone();
253 relevant_extensions.push("kcl".to_string());
254 relevant_extensions.push("md".to_string());
255 relevant_extensions
256 };
257}
258
259#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
260pub struct Program {
261 #[serde(flatten)]
262 pub ast: parsing::ast::types::Node<parsing::ast::types::Program>,
263 #[serde(skip)]
267 pub original_file_contents: String,
268}
269
270#[cfg(any(test, feature = "lsp-test-util"))]
271pub use lsp::test_util::copilot_lsp_server;
272#[cfg(any(test, feature = "lsp-test-util"))]
273pub use lsp::test_util::kcl_lsp_server;
274
275impl Program {
276 pub fn parse(input: &str) -> Result<(Option<Program>, Vec<CompilationIssue>), KclError> {
277 let module_id = ModuleId::default();
278 let (ast, errs) = parsing::parse_str(input, module_id).0?;
279
280 Ok((
281 ast.map(|ast| Program {
282 ast,
283 original_file_contents: input.to_string(),
284 }),
285 errs,
286 ))
287 }
288
289 pub fn parse_no_errs(input: &str) -> Result<Program, KclError> {
290 let module_id = ModuleId::default();
291 let ast = parsing::parse_str(input, module_id).parse_errs_as_err()?;
292
293 Ok(Program {
294 ast,
295 original_file_contents: input.to_string(),
296 })
297 }
298
299 pub fn compute_digest(&mut self) -> parsing::ast::digest::Digest {
300 self.ast.compute_digest()
301 }
302
303 pub fn meta_settings(&self) -> Result<Option<crate::MetaSettings>, KclError> {
305 self.ast.meta_settings()
306 }
307
308 pub fn change_default_units(
310 &self,
311 length_units: Option<kittycad_modeling_cmds::units::UnitLength>,
312 ) -> Result<Self, KclError> {
313 Ok(Self {
314 ast: self.ast.change_default_units(length_units)?,
315 original_file_contents: self.original_file_contents.clone(),
316 })
317 }
318
319 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
320 Ok(Self {
321 ast: self.ast.change_experimental_features(warning_level)?,
322 original_file_contents: self.original_file_contents.clone(),
323 })
324 }
325
326 pub fn is_empty_or_only_settings(&self) -> bool {
327 self.ast.is_empty_or_only_settings()
328 }
329
330 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
331 self.ast.lint_all()
332 }
333
334 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
335 self.ast.lint(rule)
336 }
337
338 #[cfg(feature = "artifact-graph")]
339 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
340 let module_infos = indexmap::IndexMap::new();
341 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
342 NodePath::from_range(&programs, cached_body_items, range)
343 }
344
345 #[cfg(feature = "artifact-graph")]
351 pub fn fill_node_paths(mut self) -> Program {
352 parsing::ast::types::fill_node_paths(&mut self.ast);
353 self
354 }
355
356 pub fn recast(&self) -> String {
357 self.ast.recast_top(&Default::default(), 0)
359 }
360
361 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
362 self.ast.recast_top(options, 0)
363 }
364
365 pub fn empty() -> Self {
367 Self {
368 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
369 original_file_contents: String::new(),
370 }
371 }
372}
373
374#[inline]
375fn try_f64_to_usize(f: f64) -> Option<usize> {
376 let i = f as usize;
377 if i as f64 == f { Some(i) } else { None }
378}
379
380#[inline]
381fn try_f64_to_u32(f: f64) -> Option<u32> {
382 let i = f as u32;
383 if i as f64 == f { Some(i) } else { None }
384}
385
386#[inline]
387fn try_f64_to_u64(f: f64) -> Option<u64> {
388 let i = f as u64;
389 if i as f64 == f { Some(i) } else { None }
390}
391
392#[inline]
393fn try_f64_to_i64(f: f64) -> Option<i64> {
394 let i = f as i64;
395 if i as f64 == f { Some(i) } else { None }
396}
397
398pub fn version() -> &'static str {
400 env!("CARGO_PKG_VERSION")
401}
402
403#[cfg(test)]
404mod test {
405 use super::*;
406
407 #[test]
408 fn convert_int() {
409 assert_eq!(try_f64_to_usize(0.0), Some(0));
410 assert_eq!(try_f64_to_usize(42.0), Some(42));
411 assert_eq!(try_f64_to_usize(0.00000000001), None);
412 assert_eq!(try_f64_to_usize(-1.0), None);
413 assert_eq!(try_f64_to_usize(f64::NAN), None);
414 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
415 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
416
417 assert_eq!(try_f64_to_u32(0.0), Some(0));
418 assert_eq!(try_f64_to_u32(42.0), Some(42));
419 assert_eq!(try_f64_to_u32(0.00000000001), None);
420 assert_eq!(try_f64_to_u32(-1.0), None);
421 assert_eq!(try_f64_to_u32(f64::NAN), None);
422 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
423 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
424
425 assert_eq!(try_f64_to_u64(0.0), Some(0));
426 assert_eq!(try_f64_to_u64(42.0), Some(42));
427 assert_eq!(try_f64_to_u64(0.00000000001), None);
428 assert_eq!(try_f64_to_u64(-1.0), None);
429 assert_eq!(try_f64_to_u64(f64::NAN), None);
430 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
431 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
432
433 assert_eq!(try_f64_to_i64(0.0), Some(0));
434 assert_eq!(try_f64_to_i64(42.0), Some(42));
435 assert_eq!(try_f64_to_i64(0.00000000001), None);
436 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
437 assert_eq!(try_f64_to_i64(f64::NAN), None);
438 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
439 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
440 }
441}