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, Symmetric, 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_kcl_version(&self, kcl_version: Option<String>) -> Result<Self, KclError> {
320 Ok(Self {
321 ast: self.ast.change_kcl_version(kcl_version)?,
322 original_file_contents: self.original_file_contents.clone(),
323 })
324 }
325
326 pub fn change_experimental_features(&self, warning_level: Option<WarningLevel>) -> Result<Self, KclError> {
327 Ok(Self {
328 ast: self.ast.change_experimental_features(warning_level)?,
329 original_file_contents: self.original_file_contents.clone(),
330 })
331 }
332
333 pub fn is_empty_or_only_settings(&self) -> bool {
334 self.ast.is_empty_or_only_settings()
335 }
336
337 pub fn lint_all(&self) -> Result<Vec<lint::Discovered>, anyhow::Error> {
338 self.ast.lint_all()
339 }
340
341 pub fn lint<'a>(&'a self, rule: impl lint::Rule<'a>) -> Result<Vec<lint::Discovered>, anyhow::Error> {
342 self.ast.lint(rule)
343 }
344
345 #[cfg(feature = "artifact-graph")]
346 pub fn node_path_from_range(&self, cached_body_items: usize, range: SourceRange) -> Option<NodePath> {
347 let module_infos = indexmap::IndexMap::new();
348 let programs = crate::execution::ProgramLookup::new(self.ast.clone(), module_infos);
349 NodePath::from_range(&programs, cached_body_items, range)
350 }
351
352 #[cfg(feature = "artifact-graph")]
358 pub fn fill_node_paths(mut self) -> Program {
359 parsing::ast::types::fill_node_paths(&mut self.ast);
360 self
361 }
362
363 pub fn recast(&self) -> String {
364 self.ast.recast_top(&Default::default(), 0)
366 }
367
368 pub fn recast_with_options(&self, options: &FormatOptions) -> String {
369 self.ast.recast_top(options, 0)
370 }
371
372 pub fn empty() -> Self {
374 Self {
375 ast: parsing::ast::types::Node::no_src(parsing::ast::types::Program::default()),
376 original_file_contents: String::new(),
377 }
378 }
379}
380
381#[inline]
382fn try_f64_to_usize(f: f64) -> Option<usize> {
383 let i = f as usize;
384 if i as f64 == f { Some(i) } else { None }
385}
386
387#[inline]
388fn try_f64_to_u32(f: f64) -> Option<u32> {
389 let i = f as u32;
390 if i as f64 == f { Some(i) } else { None }
391}
392
393#[inline]
394fn try_f64_to_u64(f: f64) -> Option<u64> {
395 let i = f as u64;
396 if i as f64 == f { Some(i) } else { None }
397}
398
399#[inline]
400fn try_f64_to_i64(f: f64) -> Option<i64> {
401 let i = f as i64;
402 if i as f64 == f { Some(i) } else { None }
403}
404
405pub fn version() -> &'static str {
407 env!("CARGO_PKG_VERSION")
408}
409
410#[cfg(test)]
411mod test {
412 use super::*;
413
414 #[test]
415 fn convert_int() {
416 assert_eq!(try_f64_to_usize(0.0), Some(0));
417 assert_eq!(try_f64_to_usize(42.0), Some(42));
418 assert_eq!(try_f64_to_usize(0.00000000001), None);
419 assert_eq!(try_f64_to_usize(-1.0), None);
420 assert_eq!(try_f64_to_usize(f64::NAN), None);
421 assert_eq!(try_f64_to_usize(f64::INFINITY), None);
422 assert_eq!(try_f64_to_usize((0.1 + 0.2) * 10.0), None);
423
424 assert_eq!(try_f64_to_u32(0.0), Some(0));
425 assert_eq!(try_f64_to_u32(42.0), Some(42));
426 assert_eq!(try_f64_to_u32(0.00000000001), None);
427 assert_eq!(try_f64_to_u32(-1.0), None);
428 assert_eq!(try_f64_to_u32(f64::NAN), None);
429 assert_eq!(try_f64_to_u32(f64::INFINITY), None);
430 assert_eq!(try_f64_to_u32((0.1 + 0.2) * 10.0), None);
431
432 assert_eq!(try_f64_to_u64(0.0), Some(0));
433 assert_eq!(try_f64_to_u64(42.0), Some(42));
434 assert_eq!(try_f64_to_u64(0.00000000001), None);
435 assert_eq!(try_f64_to_u64(-1.0), None);
436 assert_eq!(try_f64_to_u64(f64::NAN), None);
437 assert_eq!(try_f64_to_u64(f64::INFINITY), None);
438 assert_eq!(try_f64_to_u64((0.1 + 0.2) * 10.0), None);
439
440 assert_eq!(try_f64_to_i64(0.0), Some(0));
441 assert_eq!(try_f64_to_i64(42.0), Some(42));
442 assert_eq!(try_f64_to_i64(0.00000000001), None);
443 assert_eq!(try_f64_to_i64(-1.0), Some(-1));
444 assert_eq!(try_f64_to_i64(f64::NAN), None);
445 assert_eq!(try_f64_to_i64(f64::INFINITY), None);
446 assert_eq!(try_f64_to_i64((0.1 + 0.2) * 10.0), None);
447 }
448}