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;
68#[cfg(feature = "artifact-graph")]
69pub(crate) mod id;
70pub mod lint;
71mod log;
72mod lsp;
73mod modules;
74mod parsing;
75mod project;
76mod settings;
77#[cfg(test)]
78mod simulation_tests;
79pub mod std;
80#[cfg(not(target_arch = "wasm32"))]
81pub mod test_server;
82mod thread;
83mod unparser;
84mod util;
85#[cfg(test)]
86mod variant_name;
87pub mod walk;
88#[cfg(target_arch = "wasm32")]
89mod wasm;
90
91pub use coredump::CoreDump;
92pub use engine::AsyncTasks;
93pub use engine::EngineManager;
94pub use engine::EngineStats;
95pub use errors::BacktraceItem;
96pub use errors::CompilationError;
97pub use errors::ConnectionError;
98pub use errors::ExecError;
99pub use errors::KclError;
100pub use errors::KclErrorWithOutputs;
101pub use errors::Report;
102pub use errors::ReportWithOutputs;
103pub use execution::ExecOutcome;
104pub use execution::ExecState;
105pub use execution::ExecutorContext;
106pub use execution::ExecutorSettings;
107pub use execution::MetaSettings;
108pub use execution::MockConfig;
109pub use execution::Point2d;
110pub use execution::bust_cache;
111pub use execution::clear_mem_cache;
112pub use execution::pre_execute_transpile;
113pub use execution::transpile_all_old_sketches_to_new;
114pub use execution::transpile_old_sketch_to_new;
115pub use execution::transpile_old_sketch_to_new_ast;
116pub use execution::transpile_old_sketch_to_new_with_execution;
117pub use execution::typed_path::TypedPath;
118pub use kcl_error::SourceRange;
119pub use lsp::ToLspRange;
120pub use lsp::copilot::Backend as CopilotLspBackend;
121pub use lsp::kcl::Backend as KclLspBackend;
122pub use lsp::kcl::Server as KclLspServerSubCommand;
123pub use modules::ModuleId;
124pub use parsing::ast::types::FormatOptions;
125pub use parsing::ast::types::NodePath;
126pub use parsing::ast::types::Step as NodePathStep;
127pub use project::ProjectManager;
128pub use settings::types::Configuration;
129pub use settings::types::project::ProjectConfiguration;
130#[cfg(not(target_arch = "wasm32"))]
131pub use unparser::recast_dir;
132#[cfg(not(target_arch = "wasm32"))]
133pub use unparser::walk_dir;
134
135pub mod exec {
138 #[cfg(feature = "artifact-graph")]
139 pub use crate::execution::ArtifactCommand;
140 pub use crate::execution::DefaultPlanes;
141 pub use crate::execution::IdGenerator;
142 pub use crate::execution::KclValue;
143 #[cfg(feature = "artifact-graph")]
144 pub use crate::execution::Operation;
145 pub use crate::execution::PlaneKind;
146 pub use crate::execution::Sketch;
147 pub use crate::execution::annotations::WarningLevel;
148 pub use crate::execution::types::NumericType;
149 pub use crate::execution::types::UnitType;
150 pub use crate::util::RetryConfig;
151 pub use crate::util::execute_with_retries;
152}
153
154#[cfg(target_arch = "wasm32")]
155pub mod wasm_engine {
156 pub use crate::coredump::wasm::CoreDumpManager;
157 pub use crate::coredump::wasm::CoreDumper;
158 pub use crate::engine::conn_wasm::EngineCommandManager;
159 pub use crate::engine::conn_wasm::EngineConnection;
160 pub use crate::engine::conn_wasm::ResponseContext;
161 pub use crate::fs::wasm::FileManager;
162 pub use crate::fs::wasm::FileSystemManager;
163}
164
165pub mod mock_engine {
166 pub use crate::engine::conn_mock::EngineConnection;
167}
168
169#[cfg(not(target_arch = "wasm32"))]
170pub mod native_engine {
171 pub use crate::engine::conn::EngineConnection;
172}
173
174pub mod std_utils {
175 pub use crate::std::utils::TangentialArcInfoInput;
176 pub use crate::std::utils::get_tangential_arc_to_info;
177 pub use crate::std::utils::is_points_ccw_wasm;
178 pub use crate::std::utils::untyped_point_to_unit;
179}
180
181pub mod pretty {
182 pub use crate::fmt::format_number_literal;
183 pub use crate::fmt::format_number_value;
184 pub use crate::fmt::human_display_number;
185 pub use crate::parsing::token::NumericSuffix;
186}
187
188pub mod front {
189 pub(crate) use crate::frontend::modify::find_defined_names;
190 pub(crate) use crate::frontend::modify::next_free_name_using_max;
191 pub use crate::frontend::{
193 FrontendState, SetProgramOutcome,
194 api::{
195 Error, Expr, Face, File, FileId, LifecycleApi, Number, Object, ObjectId, ObjectKind, Plane, ProjectId,
196 Result, SceneGraph, SceneGraphDelta, Settings, SourceDelta, SourceRef, Version,
197 },
198 sketch::{
199 Angle, Arc, ArcCtor, Circle, CircleCtor, Coincident, Constraint, Distance, ExistingSegmentCtor, Freedom,
200 Horizontal, Line, LineCtor, LinesEqualLength, NewSegmentInfo, Parallel, Perpendicular, Point, Point2d,
201 PointCtor, Segment, SegmentCtor, Sketch, SketchApi, SketchCtor, StartOrEnd, Tangent, 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}