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