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