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