1use std::collections::HashMap;
2
3use async_recursion::async_recursion;
4use ezpz::Constraint;
5use ezpz::NonLinearSystemError;
6use indexmap::IndexMap;
7use kcl_api::Group;
8use kcl_api::NumericType;
9use kcl_api::Operation;
10use kcl_api::UnitAngle;
11
12use crate::CompilationIssue;
13use crate::NodePath;
14use crate::NodePathExt;
15use crate::SourceRange;
16use crate::errors::KclError;
17use crate::errors::KclErrorDetails;
18use crate::exec::Sketch;
19use crate::execution::AbstractSegment;
20use crate::execution::Artifact;
21use crate::execution::ArtifactId;
22use crate::execution::BodyType;
23use crate::execution::ConstraintKind;
24use crate::execution::ControlFlowKind;
25use crate::execution::EarlyReturn;
26use crate::execution::EnvironmentRef;
27use crate::execution::ExecState;
28use crate::execution::ExecutorContext;
29use crate::execution::KclValue;
30use crate::execution::KclValueControlFlow;
31use crate::execution::Metadata;
32use crate::execution::ModelingCmdMeta;
33use crate::execution::ModuleArtifactState;
34use crate::execution::PreserveMem;
35use crate::execution::SKETCH_BLOCK_PARAM_ON;
36use crate::execution::SKETCH_OBJECT_META;
37use crate::execution::SKETCH_OBJECT_META_SKETCH;
38use crate::execution::Segment;
39use crate::execution::SegmentKind;
40use crate::execution::SegmentRepr;
41use crate::execution::SketchConstraintKind;
42use crate::execution::SketchSurface;
43use crate::execution::StatementKind;
44use crate::execution::TagIdentifier;
45use crate::execution::UnsolvedExpr;
46use crate::execution::UnsolvedSegment;
47use crate::execution::UnsolvedSegmentKind;
48use crate::execution::annotations;
49use crate::execution::annotations::FnAttrs;
50use crate::execution::cad_op::op_from_kcl_value;
51use crate::execution::control_continue;
52use crate::execution::early_return;
53use crate::execution::fn_call::Arg;
54use crate::execution::fn_call::Args;
55use crate::execution::fn_call::unexpected_kw_arg_message;
56use crate::execution::kcl_value::FunctionSource;
57use crate::execution::kcl_value::KclFunctionSourceParams;
58use crate::execution::kcl_value::KclObjectKind;
59use crate::execution::kcl_value::TypeDef;
60use crate::execution::memory::SKETCH_PREFIX;
61use crate::execution::memory::{self};
62use crate::execution::sketch_constraint_status_for_sketch;
63use crate::execution::sketch_solve::FreedomAnalysis;
64use crate::execution::sketch_solve::Solved;
65use crate::execution::sketch_solve::create_segment_scene_objects;
66use crate::execution::sketch_solve::normalize_to_solver_angle_unit;
67use crate::execution::sketch_solve::normalize_to_solver_distance_unit;
68use crate::execution::sketch_solve::solver_numeric_type;
69use crate::execution::sketch_solve::substitute_sketch_var_in_segment;
70use crate::execution::sketch_solve::substitute_sketch_vars;
71use crate::execution::state::ModuleState;
72use crate::execution::state::SketchBlockState;
73use crate::execution::types::NumericTypeExt;
74use crate::execution::types::PrimitiveType;
75use crate::execution::types::RuntimeType;
76use crate::front::LineCtor;
77use crate::front::Object;
78use crate::front::ObjectId;
79use crate::front::ObjectKind;
80use crate::front::PointCtor;
81use crate::modules::ModuleExecutionOutcome;
82use crate::modules::ModuleId;
83use crate::modules::ModulePath;
84use crate::modules::ModuleRepr;
85use crate::parsing::ast::types::Annotation;
86use crate::parsing::ast::types::ArrayExpression;
87use crate::parsing::ast::types::ArrayRangeExpression;
88use crate::parsing::ast::types::AscribedExpression;
89use crate::parsing::ast::types::BinaryExpression;
90use crate::parsing::ast::types::BinaryOperator;
91use crate::parsing::ast::types::BinaryPart;
92use crate::parsing::ast::types::BodyItem;
93use crate::parsing::ast::types::CodeBlock;
94use crate::parsing::ast::types::Expr;
95use crate::parsing::ast::types::IfExpression;
96use crate::parsing::ast::types::ImportPath;
97use crate::parsing::ast::types::ImportSelector;
98use crate::parsing::ast::types::ItemVisibility;
99use crate::parsing::ast::types::MemberExpression;
100use crate::parsing::ast::types::Name;
101use crate::parsing::ast::types::Node;
102use crate::parsing::ast::types::ObjectExpression;
103use crate::parsing::ast::types::PipeExpression;
104use crate::parsing::ast::types::Program;
105use crate::parsing::ast::types::SketchBlock;
106use crate::parsing::ast::types::SketchVar;
107use crate::parsing::ast::types::TagDeclarator;
108use crate::parsing::ast::types::Type;
109use crate::parsing::ast::types::UnaryExpression;
110use crate::parsing::ast::types::UnaryOperator;
111use crate::std::StdFnProps;
112use crate::std::args::FromKclValue;
113use crate::std::args::TyF64;
114use crate::std::shapes::SketchOrSurface;
115use crate::std::sketch::ensure_sketch_plane_in_engine;
116use crate::std::solver::SOLVER_CONVERGENCE_TOLERANCE;
117use crate::std::solver::create_segments_in_engine;
118
119fn internal_err(message: impl Into<String>, range: impl Into<SourceRange>) -> KclError {
120 KclError::new_internal(KclErrorDetails::new(message.into(), vec![range.into()]))
121}
122
123fn datum_point_from_constrainable(
124 point: &crate::execution::ConstrainablePoint2d,
125 range: SourceRange,
126) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
127 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
128 point.vars.x.to_constraint_id(range)?,
129 point.vars.y.to_constraint_id(range)?,
130 ))
131}
132
133fn push_fixed_origin_point(
134 sketch_block_state: &mut SketchBlockState,
135 sketch_var_ty: NumericType,
136 range: SourceRange,
137) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
138 let origin_x_id = sketch_block_state.next_sketch_var_id();
139 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
140 value: Box::new(crate::execution::SketchVar {
141 id: origin_x_id,
142 initial_value: 0.0,
143 ty: sketch_var_ty,
144 node_path: None,
146 meta: vec![],
147 }),
148 });
149 let origin_y_id = sketch_block_state.next_sketch_var_id();
150 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
151 value: Box::new(crate::execution::SketchVar {
152 id: origin_y_id,
153 initial_value: 0.0,
154 ty: sketch_var_ty,
155 node_path: None,
157 meta: vec![],
158 }),
159 });
160
161 sketch_block_state
162 .solver_constraints
163 .push(Constraint::Fixed(origin_x_id.to_constraint_id(range)?, 0.0));
164 sketch_block_state
165 .solver_constraints
166 .push(Constraint::Fixed(origin_y_id.to_constraint_id(range)?, 0.0));
167
168 Ok(ezpz::datatypes::inputs::DatumPoint::new_xy(
169 origin_x_id.to_constraint_id(range)?,
170 origin_y_id.to_constraint_id(range)?,
171 ))
172}
173
174fn datum_point_from_constrainable_or_origin(
175 sketch_block_state: &mut SketchBlockState,
176 sketch_var_ty: NumericType,
177 point: &crate::execution::ConstrainablePoint2dOrOrigin,
178 range: SourceRange,
179) -> Result<ezpz::datatypes::inputs::DatumPoint, KclError> {
180 match point {
181 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => datum_point_from_constrainable(point, range),
182 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
183 push_fixed_origin_point(sketch_block_state, sketch_var_ty, range)
184 }
185 }
186}
187
188fn datum_line_from_constrainable(
189 line: &crate::execution::ConstrainableLine2d,
190 range: SourceRange,
191) -> Result<ezpz::datatypes::inputs::DatumLineSegment, KclError> {
192 Ok(ezpz::datatypes::inputs::DatumLineSegment::new(
193 ezpz::datatypes::inputs::DatumPoint::new_xy(
194 line.vars[0].x.to_constraint_id(range)?,
195 line.vars[0].y.to_constraint_id(range)?,
196 ),
197 ezpz::datatypes::inputs::DatumPoint::new_xy(
198 line.vars[1].x.to_constraint_id(range)?,
199 line.vars[1].y.to_constraint_id(range)?,
200 ),
201 ))
202}
203
204fn sketch_var_initial_value(
205 sketch_vars: &[KclValue],
206 id: crate::execution::SketchVarId,
207 exec_state: &mut ExecState,
208 range: SourceRange,
209 description: &str,
210) -> Result<f64, KclError> {
211 sketch_vars
212 .get(id.0)
213 .and_then(KclValue::as_sketch_var)
214 .map(|sketch_var| {
215 sketch_var
216 .initial_value_to_solver_units(exec_state, range, description)
217 .map(|value| value.n)
218 })
219 .transpose()?
220 .ok_or_else(|| internal_err(format!("Missing sketch variable initial value for id {}", id.0), range))
221}
222
223fn constrainable_point_initial_position(
224 sketch_vars: &[KclValue],
225 point: &crate::execution::ConstrainablePoint2d,
226 exec_state: &mut ExecState,
227 range: SourceRange,
228 description: &str,
229) -> Result<[f64; 2], KclError> {
230 Ok([
231 sketch_var_initial_value(sketch_vars, point.vars.x, exec_state, range, description)?,
232 sketch_var_initial_value(sketch_vars, point.vars.y, exec_state, range, description)?,
233 ])
234}
235
236fn constrainable_point_or_origin_initial_position(
237 sketch_vars: &[KclValue],
238 point: &crate::execution::ConstrainablePoint2dOrOrigin,
239 exec_state: &mut ExecState,
240 range: SourceRange,
241 description: &str,
242) -> Result<[f64; 2], KclError> {
243 match point {
244 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
245 constrainable_point_initial_position(sketch_vars, point, exec_state, range, description)
246 }
247 crate::execution::ConstrainablePoint2dOrOrigin::Origin => Ok([0.0, 0.0]),
248 }
249}
250
251fn constrainable_line_initial_positions(
257 sketch_vars: &[KclValue],
258 line: &crate::execution::ConstrainableLine2d,
259 exec_state: &mut ExecState,
260 range: SourceRange,
261 description: &str,
262) -> Result<([f64; 2], [f64; 2]), KclError> {
263 let start = crate::execution::ConstrainablePoint2d {
264 vars: line.vars[0].clone(),
265 object_id: line.object_id,
266 };
267 let end = crate::execution::ConstrainablePoint2d {
268 vars: line.vars[1].clone(),
269 object_id: line.object_id,
270 };
271 Ok((
272 constrainable_point_initial_position(sketch_vars, &start, exec_state, range, description)?,
273 constrainable_point_initial_position(sketch_vars, &end, exec_state, range, description)?,
274 ))
275}
276
277fn projected_point_on_line_initial_position(
278 sketch_vars: &[KclValue],
279 point: &crate::execution::ConstrainablePoint2dOrOrigin,
280 line: &crate::execution::ConstrainableLine2d,
281 exec_state: &mut ExecState,
282 range: SourceRange,
283) -> Result<[f64; 2], KclError> {
284 let point = constrainable_point_or_origin_initial_position(
285 sketch_vars,
286 point,
287 exec_state,
288 range,
289 "point-line distance initial point",
290 )?;
291 let (line_start, line_end) =
292 constrainable_line_initial_positions(sketch_vars, line, exec_state, range, "point-line distance initial line")?;
293 let dx = line_end[0] - line_start[0];
294 let dy = line_end[1] - line_start[1];
295 let len_sq = dx * dx + dy * dy;
296 if len_sq == 0.0 {
297 return Err(KclError::new_semantic(KclErrorDetails::new(
298 "distance() line input must have non-zero length".to_owned(),
299 vec![range],
300 )));
301 }
302
303 let t = ((point[0] - line_start[0]) * dx + (point[1] - line_start[1]) * dy) / len_sq;
306 Ok([line_start[0] + t * dx, line_start[1] + t * dy])
307}
308
309fn constrainable_points_initial_distance(
310 sketch_vars: &[KclValue],
311 point0: &crate::execution::ConstrainablePoint2d,
312 point1: &crate::execution::ConstrainablePoint2d,
313 exec_state: &mut ExecState,
314 range: SourceRange,
315 description: &str,
316) -> Result<f64, KclError> {
317 let p0 = constrainable_point_initial_position(sketch_vars, point0, exec_state, range, description)?;
318 let p1 = constrainable_point_initial_position(sketch_vars, point1, exec_state, range, description)?;
319 Ok(libm::hypot(p0[0] - p1[0], p0[1] - p1[1]))
320}
321
322#[derive(Clone, Copy)]
326struct CircularDistanceDatums {
327 center: ezpz::datatypes::inputs::DatumPoint,
328 start: ezpz::datatypes::inputs::DatumPoint,
329 end: Option<ezpz::datatypes::inputs::DatumPoint>,
330 radius_initial_value: f64,
331}
332
333fn circular_distance_datums(
334 sketch_vars: &[KclValue],
335 center: &crate::execution::ConstrainablePoint2d,
336 start: &crate::execution::ConstrainablePoint2d,
337 end: Option<&crate::execution::ConstrainablePoint2d>,
338 exec_state: &mut ExecState,
339 range: SourceRange,
340) -> Result<CircularDistanceDatums, KclError> {
341 Ok(CircularDistanceDatums {
342 center: datum_point_from_constrainable(center, range)?,
343 start: datum_point_from_constrainable(start, range)?,
344 end: end.map(|end| datum_point_from_constrainable(end, range)).transpose()?,
345 radius_initial_value: constrainable_points_initial_distance(
346 sketch_vars,
347 center,
348 start,
349 exec_state,
350 range,
351 "circular distance radius initial value",
352 )?,
353 })
354}
355
356fn circular_circular_support_initial_position(
357 sketch_vars: &[KclValue],
358 center0: &crate::execution::ConstrainablePoint2d,
359 center1: &crate::execution::ConstrainablePoint2d,
360 radius0: f64,
361 distance_value: f64,
362 exec_state: &mut ExecState,
363 range: SourceRange,
364) -> Result<[f64; 2], KclError> {
365 let center0_initial =
366 constrainable_point_initial_position(sketch_vars, center0, exec_state, range, "circular distance center")?;
367 let center1_initial =
368 constrainable_point_initial_position(sketch_vars, center1, exec_state, range, "circular distance center")?;
369 let dx = center1_initial[0] - center0_initial[0];
370 let dy = center1_initial[1] - center0_initial[1];
371 let center_distance = libm::hypot(dx, dy);
372 let support_distance = radius0 + distance_value / 2.0;
377
378 if center_distance <= f64::EPSILON {
379 return Ok([center0_initial[0] + support_distance, center0_initial[1]]);
382 }
383
384 Ok([
385 center0_initial[0] + dx / center_distance * support_distance,
386 center0_initial[1] + dy / center_distance * support_distance,
387 ])
388}
389
390fn push_circular_radius_constraints(
391 sketch_block_state: &mut SketchBlockState,
392 sketch_var_ty: NumericType,
393 circular: CircularDistanceDatums,
394 range: SourceRange,
395) -> Result<ezpz::datatypes::inputs::DatumCircle, KclError> {
396 let circular_radius_id = sketch_block_state.next_sketch_var_id();
400 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
401 value: Box::new(crate::execution::SketchVar {
402 id: circular_radius_id,
403 initial_value: circular.radius_initial_value,
404 ty: sketch_var_ty,
405 node_path: None,
407 meta: vec![],
408 }),
409 });
410 let circular_radius = ezpz::datatypes::inputs::DatumDistance::new(circular_radius_id.to_constraint_id(range)?);
411
412 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
413 circular.start,
414 circular.center,
415 circular_radius,
416 ));
417 if let Some(end) = circular.end {
418 sketch_block_state
419 .solver_constraints
420 .push(Constraint::DistanceVar(end, circular.center, circular_radius));
421 }
422
423 Ok(ezpz::datatypes::inputs::DatumCircle {
424 center: circular.center,
425 radius: circular_radius,
426 })
427}
428
429fn push_circular_distance_constraints(
430 sketch_block_state: &mut SketchBlockState,
431 sketch_var_ty: NumericType,
432 target_point: ezpz::datatypes::inputs::DatumPoint,
433 circular: CircularDistanceDatums,
434 distance_value: f64,
435 range: SourceRange,
436) -> Result<(), KclError> {
437 let circular_target = push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular, range)?;
438
439 let target_distance_id = sketch_block_state.next_sketch_var_id();
442 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
443 value: Box::new(crate::execution::SketchVar {
444 id: target_distance_id,
445 initial_value: distance_value,
446 ty: sketch_var_ty,
447 node_path: None,
449 meta: vec![],
450 }),
451 });
452 let target_distance = ezpz::datatypes::inputs::DatumDistance::new(target_distance_id.to_constraint_id(range)?);
453
454 sketch_block_state
455 .solver_constraints
456 .push(Constraint::Fixed(target_distance.id, distance_value));
457
458 let target_circle = ezpz::datatypes::inputs::DatumCircle {
459 center: target_point,
460 radius: target_distance,
461 };
462 sketch_block_state
463 .solver_constraints
464 .push(Constraint::CircleTangentToCircle(
465 target_circle,
466 circular_target,
467 ezpz::CircleSide::Exterior,
468 ));
469
470 Ok(())
471}
472
473fn sketch_on_cache_name(sketch_id: ObjectId) -> String {
474 format!("{SKETCH_PREFIX}{}_on", sketch_id.0)
475}
476
477fn default_plane_name_from_expr(expr: &Expr) -> Option<crate::engine::PlaneName> {
478 fn parse_name(name: &str, negative: bool) -> Option<crate::engine::PlaneName> {
479 use crate::engine::PlaneName;
480
481 match (name, negative) {
482 ("XY", false) => Some(PlaneName::Xy),
483 ("XY", true) => Some(PlaneName::NegXy),
484 ("XZ", false) => Some(PlaneName::Xz),
485 ("XZ", true) => Some(PlaneName::NegXz),
486 ("YZ", false) => Some(PlaneName::Yz),
487 ("YZ", true) => Some(PlaneName::NegYz),
488 _ => None,
489 }
490 }
491
492 match expr {
493 Expr::Name(name) => {
494 if !name.path.is_empty() {
495 return None;
496 }
497 parse_name(&name.name.name, false)
498 }
499 Expr::UnaryExpression(unary) => {
500 if unary.operator != UnaryOperator::Neg {
501 return None;
502 }
503 let crate::parsing::ast::types::BinaryPart::Name(name) = &unary.argument else {
504 return None;
505 };
506 if !name.path.is_empty() {
507 return None;
508 }
509 parse_name(&name.name.name, true)
510 }
511 _ => None,
512 }
513}
514
515fn sketch_on_frontend_plane(
516 arguments: &[crate::parsing::ast::types::LabeledArg],
517 on_object_id: crate::front::ObjectId,
518) -> crate::front::Plane {
519 for arg in arguments {
520 let Some(label) = &arg.label else {
521 continue;
522 };
523 if label.name != SKETCH_BLOCK_PARAM_ON {
524 continue;
525 }
526 if let Some(name) = default_plane_name_from_expr(&arg.arg) {
527 return crate::front::Plane::Default(name);
528 }
529 break;
530 }
531
532 crate::front::Plane::Object(on_object_id)
533}
534
535impl<'a> StatementKind<'a> {
536 fn expect_name(&self) -> &'a str {
537 match self {
538 StatementKind::Declaration { name } => name,
539 StatementKind::Expression => unreachable!(),
540 }
541 }
542}
543
544impl ExecutorContext {
545 async fn handle_annotations(
547 &self,
548 annotations: impl Iterator<Item = &Node<Annotation>>,
549 body_type: BodyType,
550 exec_state: &mut ExecState,
551 ) -> Result<bool, KclError> {
552 let mut no_prelude = false;
553 for annotation in annotations {
554 if annotation.name() == Some(annotations::SETTINGS) {
555 if matches!(body_type, BodyType::Root) {
556 let (updated_len, updated_angle) =
557 exec_state.mod_local.settings.update_from_annotation(annotation)?;
558 if updated_len {
559 exec_state.mod_local.explicit_length_units = true;
560 }
561 if updated_angle {
562 exec_state.warn(
563 CompilationIssue::err(
564 annotation.as_source_range(),
565 "Prefer to use explicit units for angles",
566 ),
567 annotations::WARN_ANGLE_UNITS,
568 );
569 }
570 } else {
571 exec_state.err(CompilationIssue::err(
572 annotation.as_source_range(),
573 "Settings can only be modified at the top level scope of a file",
574 ));
575 }
576 } else if annotation.name() == Some(annotations::NO_PRELUDE) {
577 if matches!(body_type, BodyType::Root) {
578 no_prelude = true;
579 } else {
580 exec_state.err(CompilationIssue::err(
581 annotation.as_source_range(),
582 "The standard library can only be skipped at the top level scope of a file",
583 ));
584 }
585 } else if annotation.name() == Some(annotations::WARNINGS) {
586 if matches!(body_type, BodyType::Root) {
588 let props = annotations::expect_properties(annotations::WARNINGS, annotation)?;
589 for p in props {
590 match &*p.inner.key.name {
591 annotations::WARN_ALLOW => {
592 let allowed = annotations::many_of(
593 &p.inner.value,
594 &annotations::WARN_VALUES,
595 annotation.as_source_range(),
596 )?;
597 exec_state.mod_local.allowed_warnings = allowed;
598 }
599 annotations::WARN_DENY => {
600 let denied = annotations::many_of(
601 &p.inner.value,
602 &annotations::WARN_VALUES,
603 annotation.as_source_range(),
604 )?;
605 exec_state.mod_local.denied_warnings = denied;
606 }
607 name => {
608 return Err(KclError::new_semantic(KclErrorDetails::new(
609 format!(
610 "Unexpected warnings key: `{name}`; expected one of `{}`, `{}`",
611 annotations::WARN_ALLOW,
612 annotations::WARN_DENY,
613 ),
614 vec![annotation.as_source_range()],
615 )));
616 }
617 }
618 }
619 } else {
620 exec_state.err(CompilationIssue::err(
621 annotation.as_source_range(),
622 "Warnings can only be customized at the top level scope of a file",
623 ));
624 }
625 } else {
626 exec_state.warn(
627 CompilationIssue::err(annotation.as_source_range(), "Unknown annotation"),
628 annotations::WARN_UNKNOWN_ATTR,
629 );
630 }
631 }
632 Ok(no_prelude)
633 }
634
635 pub(super) async fn exec_module_body(
636 &self,
637 program: &Node<Program>,
638 exec_state: &mut ExecState,
639 preserve_mem: PreserveMem,
640 module_id: ModuleId,
641 path: &ModulePath,
642 ) -> Result<ModuleExecutionOutcome, (KclError, Option<EnvironmentRef>, Option<ModuleArtifactState>)> {
643 crate::log::log(format!("enter module {path} {}", exec_state.stack()));
644
645 let mut local_state = ModuleState::new(
654 path.clone(),
655 exec_state.stack().memory.clone(),
656 Some(module_id),
657 exec_state.mod_local.sketch_mode,
658 exec_state.mod_local.freedom_analysis,
659 );
660 match preserve_mem {
661 PreserveMem::Always => {
662 exec_state
663 .mod_local
664 .artifacts
665 .restore_scene_objects(&exec_state.global.root_module_artifacts.scene_objects);
666 }
667 PreserveMem::Normal => {
668 local_state
669 .artifacts
670 .restore_scene_objects(&exec_state.mod_local.artifacts.scene_objects);
671 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
672 }
673 }
674
675 let no_prelude = self
676 .handle_annotations(program.inner_attrs.iter(), crate::execution::BodyType::Root, exec_state)
677 .await
678 .map_err(|err| (err, None, None))?;
679
680 if preserve_mem.normal() {
681 exec_state
682 .mut_stack()
683 .push_new_root_env(!no_prelude)
684 .map_err(|err| (err, None, None))?;
685 }
686
687 let result = self
688 .exec_block(program, exec_state, crate::execution::BodyType::Root)
689 .await;
690
691 let env_ref = match preserve_mem {
692 PreserveMem::Always => exec_state.mut_stack().pop_and_preserve_env(),
693 PreserveMem::Normal => exec_state.mut_stack().pop_env(),
694 }
695 .map_err(|err| (err, None, None))?;
696 let module_artifacts = match preserve_mem {
697 PreserveMem::Always => std::mem::take(&mut exec_state.mod_local.artifacts),
698 PreserveMem::Normal => {
699 std::mem::swap(&mut exec_state.mod_local, &mut local_state);
700 local_state.artifacts
701 }
702 };
703
704 crate::log::log(format!("leave {path}"));
705
706 result
707 .map_err(|err| (err, Some(env_ref), Some(module_artifacts.clone())))
708 .map(|last_expr| ModuleExecutionOutcome {
709 last_expr: last_expr.map(|value_cf| value_cf.into_value()),
710 environment: env_ref,
711 exports: local_state.module_exports,
712 artifacts: module_artifacts,
713 })
714 }
715
716 #[async_recursion]
718 pub(super) async fn exec_block<'a, B>(
719 &'a self,
720 block: &'a B,
721 exec_state: &mut ExecState,
722 body_type: BodyType,
723 ) -> Result<Option<KclValueControlFlow>, KclError>
724 where
725 B: CodeBlock + Sync,
726 {
727 let mut last_expr = None;
728 for statement in block.body() {
730 match statement {
731 BodyItem::ImportStatement(import_stmt) => {
732 if exec_state.sketch_mode() {
733 continue;
734 }
735 if !matches!(body_type, BodyType::Root) {
736 return Err(KclError::new_semantic(KclErrorDetails::new(
737 "Imports are only supported at the top-level of a file.".to_owned(),
738 vec![import_stmt.into()],
739 )));
740 }
741
742 let source_range = SourceRange::from(import_stmt);
743 let attrs = &import_stmt.outer_attrs;
744 let module_path = ModulePath::from_import_path(
745 &import_stmt.path,
746 &self.settings.project_directory,
747 &exec_state.mod_local.path,
748 )?;
749 let module_id = self
750 .open_module(&import_stmt.path, attrs, &module_path, exec_state, source_range)
751 .await?;
752
753 if let ModulePath::Local { value, .. } = &module_path {
754 let name = import_stmt
755 .module_name()
756 .unwrap_or_else(|| value.file_name().unwrap_or_default());
757 exec_state.push_op(Operation::ModuleInstance {
758 name,
759 module_id,
760 glob: matches!(import_stmt.selector, ImportSelector::Glob(_)),
761 node_path: NodePath::placeholder(),
762 source_range,
763 });
764 }
765
766 match &import_stmt.selector {
767 ImportSelector::List { items } => {
768 let (env_ref, module_exports) =
769 self.exec_module_for_items(module_id, exec_state, source_range).await?;
770 for import_item in items {
771 let mem = &exec_state.stack().memory;
773 let mut value =
774 mem.get_from_owned(&import_item.name.name, env_ref, import_item.into(), 0);
775 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.name.name);
776 let mut ty = mem.get_from_owned(&ty_name, env_ref, import_item.into(), 0);
777 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.name.name);
778 let mut mod_value = mem.get_from_owned(&mod_name, env_ref, import_item.into(), 0);
779
780 if value.is_err() && ty.is_err() && mod_value.is_err() {
781 return Err(KclError::new_undefined_value(
782 KclErrorDetails::new(
783 format!("{} is not defined in module", import_item.name.name),
784 vec![SourceRange::from(&import_item.name)],
785 ),
786 None,
787 ));
788 }
789
790 if value.is_ok() && !module_exports.contains(&import_item.name.name) {
792 value = Err(KclError::new_semantic(KclErrorDetails::new(
793 format!(
794 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
795 import_item.name.name
796 ),
797 vec![SourceRange::from(&import_item.name)],
798 )));
799 }
800
801 if ty.is_ok() && !module_exports.contains(&ty_name) {
802 ty = Err(KclError::new_semantic(KclErrorDetails::new(
803 format!(
804 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
805 import_item.name.name
806 ),
807 vec![SourceRange::from(&import_item.name)],
808 )));
809 }
810
811 if mod_value.is_ok() && !module_exports.contains(&mod_name) {
812 mod_value = Err(KclError::new_semantic(KclErrorDetails::new(
813 format!(
814 "Cannot import \"{}\" from module because it is not exported. Add \"export\" before the definition to export it.",
815 import_item.name.name
816 ),
817 vec![SourceRange::from(&import_item.name)],
818 )));
819 }
820
821 if value.is_err() && ty.is_err() && mod_value.is_err() {
822 return value.map(|v| Some(v.continue_()));
823 }
824
825 if let Ok(value) = value {
827 exec_state.mut_stack().add(
828 import_item.identifier().to_owned(),
829 value,
830 SourceRange::from(&import_item.name),
831 )?;
832
833 if let ItemVisibility::Export = import_stmt.visibility {
834 exec_state
835 .mod_local
836 .module_exports
837 .push(import_item.identifier().to_owned());
838 }
839 }
840
841 if let Ok(ty) = ty {
842 let ty_name = format!("{}{}", memory::TYPE_PREFIX, import_item.identifier());
843 exec_state.mut_stack().add(
844 ty_name.clone(),
845 ty,
846 SourceRange::from(&import_item.name),
847 )?;
848
849 if let ItemVisibility::Export = import_stmt.visibility {
850 exec_state.mod_local.module_exports.push(ty_name);
851 }
852 }
853
854 if let Ok(mod_value) = mod_value {
855 let mod_name = format!("{}{}", memory::MODULE_PREFIX, import_item.identifier());
856 exec_state.mut_stack().add(
857 mod_name.clone(),
858 mod_value,
859 SourceRange::from(&import_item.name),
860 )?;
861
862 if let ItemVisibility::Export = import_stmt.visibility {
863 exec_state.mod_local.module_exports.push(mod_name);
864 }
865 }
866 }
867 }
868 ImportSelector::Glob(_) => {
869 let (env_ref, module_exports) =
870 self.exec_module_for_items(module_id, exec_state, source_range).await?;
871 for name in module_exports.iter() {
872 let item = exec_state
873 .stack()
874 .memory
875 .get_from_owned(name, env_ref, source_range, 0)
876 .map_err(|_err| {
877 internal_err(
878 format!("{name} is not defined in module (but was exported?)"),
879 source_range,
880 )
881 })?;
882 exec_state.mut_stack().add(name.to_owned(), item, source_range)?;
883
884 if let ItemVisibility::Export = import_stmt.visibility {
885 exec_state.mod_local.module_exports.push(name.clone());
886 }
887 }
888 }
889 ImportSelector::None { .. } => {
890 let name = import_stmt.module_name().unwrap();
891 let item = KclValue::Module {
892 value: module_id,
893 meta: vec![source_range.into()],
894 };
895 exec_state.mut_stack().add(
896 format!("{}{}", memory::MODULE_PREFIX, name),
897 item,
898 source_range,
899 )?;
900 }
901 }
902 last_expr = None;
903 }
904 BodyItem::ExpressionStatement(expression_statement) => {
905 if exec_state.sketch_mode() && sketch_mode_should_skip(&expression_statement.expression) {
906 continue;
907 }
908
909 let metadata = Metadata::from(expression_statement);
910 let value = self
911 .execute_expr(
912 &expression_statement.expression,
913 exec_state,
914 &metadata,
915 &[],
916 StatementKind::Expression,
917 )
918 .await?;
919
920 let is_return = value.is_some_return();
921 last_expr = Some(value);
922
923 if is_return {
924 break;
925 }
926 }
927 BodyItem::VariableDeclaration(variable_declaration) => {
928 if exec_state.sketch_mode() && sketch_mode_should_skip(&variable_declaration.declaration.init) {
929 continue;
930 }
931
932 let var_name = variable_declaration.declaration.id.name.to_string();
933 let source_range = SourceRange::from(&variable_declaration.declaration.init);
934 let metadata = Metadata { source_range };
935
936 let annotations = &variable_declaration.outer_attrs;
937
938 let lhs = variable_declaration.inner.name().to_owned();
941 let prev_being_declared = exec_state.mod_local.being_declared.take();
942 exec_state.mod_local.being_declared = Some(lhs);
943 let rhs_result = self
944 .execute_expr(
945 &variable_declaration.declaration.init,
946 exec_state,
947 &metadata,
948 annotations,
949 StatementKind::Declaration { name: &var_name },
950 )
951 .await;
952 exec_state.mod_local.being_declared = prev_being_declared;
954 let rhs = rhs_result?;
955
956 if rhs.is_some_return() {
957 last_expr = Some(rhs);
958 break;
959 }
960 let mut rhs = rhs.into_value();
961
962 if let KclValue::Segment { value } = &mut rhs
966 && let SegmentRepr::Unsolved { segment } = &mut value.repr
967 {
968 segment.tag = Some(TagIdentifier {
969 value: variable_declaration.declaration.id.name.clone(),
970 info: Default::default(),
971 meta: vec![SourceRange::from(&variable_declaration.declaration.id).into()],
972 });
973 }
974 let rhs = rhs; let should_bind_name =
977 if let Some(fn_name) = variable_declaration.declaration.init.fn_declaring_name() {
978 var_name != fn_name
982 } else {
983 true
986 };
987 if should_bind_name {
988 exec_state
989 .mut_stack()
990 .add(var_name.clone(), rhs.clone(), source_range)?;
991 }
992
993 if let Some(sketch_block_state) = exec_state.mod_local.sketch_block.as_mut()
994 && let KclValue::Segment { value } = &rhs
995 {
996 let segment_object_id = match &value.repr {
999 SegmentRepr::Unsolved { segment } => segment.object_id,
1000 SegmentRepr::Solved { segment } => segment.object_id,
1001 };
1002 sketch_block_state
1003 .segment_tags
1004 .entry(segment_object_id)
1005 .or_insert_with(|| {
1006 let id_node = &variable_declaration.declaration.id;
1007 Node::new(
1008 TagDeclarator {
1009 name: id_node.name.clone(),
1010 digest: None,
1011 },
1012 id_node.start,
1013 id_node.end,
1014 id_node.module_id,
1015 )
1016 });
1017 }
1018
1019 let should_show_in_feature_tree =
1023 !exec_state.mod_local.inside_stdlib && rhs.show_variable_in_feature_tree();
1024 if should_show_in_feature_tree {
1025 exec_state.push_op(Operation::VariableDeclaration {
1026 name: var_name.clone(),
1027 value: op_from_kcl_value(&rhs),
1028 visibility: variable_declaration.visibility,
1029 node_path: NodePath::placeholder(),
1030 source_range,
1031 });
1032 }
1033
1034 if let ItemVisibility::Export = variable_declaration.visibility {
1036 if matches!(body_type, BodyType::Root) {
1037 exec_state.mod_local.module_exports.push(var_name);
1038 } else {
1039 exec_state.err(CompilationIssue::err(
1040 variable_declaration.as_source_range(),
1041 "Exports are only supported at the top-level of a file. Remove `export` or move it to the top-level.",
1042 ));
1043 }
1044 }
1045 last_expr = matches!(body_type, BodyType::Root).then_some(rhs.continue_());
1047 }
1048 BodyItem::TypeDeclaration(ty) => {
1049 if exec_state.sketch_mode() {
1050 continue;
1051 }
1052
1053 let metadata = Metadata::from(&**ty);
1054 let attrs = annotations::get_fn_attrs(&ty.outer_attrs, metadata.source_range)?.unwrap_or_default();
1055 match attrs.impl_ {
1056 annotations::Impl::Rust
1057 | annotations::Impl::RustConstrainable
1058 | annotations::Impl::RustConstraint => {
1059 let std_path = match &exec_state.mod_local.path {
1060 ModulePath::Std { value } => value,
1061 ModulePath::Local { .. } | ModulePath::Main => {
1062 return Err(KclError::new_semantic(KclErrorDetails::new(
1063 "User-defined types are not yet supported.".to_owned(),
1064 vec![metadata.source_range],
1065 )));
1066 }
1067 };
1068 let (t, props) = crate::std::std_ty(std_path, &ty.name.name);
1069 let value = KclValue::Type {
1070 value: TypeDef::RustRepr(t, props),
1071 meta: vec![metadata],
1072 experimental: attrs.experimental,
1073 };
1074 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1075 exec_state
1076 .mut_stack()
1077 .add(name_in_mem.clone(), value, metadata.source_range)
1078 .map_err(|_| {
1079 KclError::new_semantic(KclErrorDetails::new(
1080 format!("Redefinition of type {}.", ty.name.name),
1081 vec![metadata.source_range],
1082 ))
1083 })?;
1084
1085 if let ItemVisibility::Export = ty.visibility {
1086 exec_state.mod_local.module_exports.push(name_in_mem);
1087 }
1088 }
1089 annotations::Impl::Primitive => {}
1091 annotations::Impl::Kcl | annotations::Impl::KclConstrainable => match &ty.alias {
1092 Some(alias) => {
1093 let value = KclValue::Type {
1094 value: TypeDef::Alias(
1095 RuntimeType::from_parsed(
1096 alias.inner.clone(),
1097 exec_state,
1098 metadata.source_range,
1099 attrs.impl_ == annotations::Impl::KclConstrainable,
1100 false,
1101 )
1102 .map_err(|e| KclError::new_semantic(e.into()))?,
1103 ),
1104 meta: vec![metadata],
1105 experimental: attrs.experimental,
1106 };
1107 let name_in_mem = format!("{}{}", memory::TYPE_PREFIX, ty.name.name);
1108 exec_state
1109 .mut_stack()
1110 .add(name_in_mem.clone(), value, metadata.source_range)
1111 .map_err(|_| {
1112 KclError::new_semantic(KclErrorDetails::new(
1113 format!("Redefinition of type {}.", ty.name.name),
1114 vec![metadata.source_range],
1115 ))
1116 })?;
1117
1118 if let ItemVisibility::Export = ty.visibility {
1119 exec_state.mod_local.module_exports.push(name_in_mem);
1120 }
1121 }
1122 None => {
1123 return Err(KclError::new_semantic(KclErrorDetails::new(
1124 "User-defined types are not yet supported.".to_owned(),
1125 vec![metadata.source_range],
1126 )));
1127 }
1128 },
1129 }
1130
1131 last_expr = None;
1132 }
1133 BodyItem::ReturnStatement(return_statement) => {
1134 if exec_state.sketch_mode() && sketch_mode_should_skip(&return_statement.argument) {
1135 continue;
1136 }
1137
1138 let metadata = Metadata::from(return_statement);
1139
1140 if matches!(body_type, BodyType::Root) {
1141 return Err(KclError::new_semantic(KclErrorDetails::new(
1142 "Cannot return from outside a function.".to_owned(),
1143 vec![metadata.source_range],
1144 )));
1145 }
1146
1147 let value_cf = self
1148 .execute_expr(
1149 &return_statement.argument,
1150 exec_state,
1151 &metadata,
1152 &[],
1153 StatementKind::Expression,
1154 )
1155 .await?;
1156 if value_cf.is_some_return() {
1157 last_expr = Some(value_cf);
1158 break;
1159 }
1160 let value = value_cf.into_value();
1161 exec_state
1162 .mut_stack()
1163 .add(memory::RETURN_NAME.to_owned(), value, metadata.source_range)
1164 .map_err(|_| {
1165 KclError::new_semantic(KclErrorDetails::new(
1166 "Multiple returns from a single function.".to_owned(),
1167 vec![metadata.source_range],
1168 ))
1169 })?;
1170 last_expr = None;
1171 }
1172 }
1173 }
1174
1175 if matches!(body_type, BodyType::Root) {
1176 exec_state
1178 .flush_batch(
1179 ModelingCmdMeta::new(exec_state, self, block.to_source_range()),
1180 true,
1183 )
1184 .await?;
1185 }
1186
1187 Ok(last_expr)
1188 }
1189
1190 pub async fn open_module(
1191 &self,
1192 path: &ImportPath,
1193 attrs: &[Node<Annotation>],
1194 resolved_path: &ModulePath,
1195 exec_state: &mut ExecState,
1196 source_range: SourceRange,
1197 ) -> Result<ModuleId, KclError> {
1198 match path {
1199 ImportPath::Kcl { .. } => {
1200 exec_state.global.mod_loader.cycle_check(resolved_path, source_range)?;
1201
1202 if let Some(id) = exec_state.id_for_module(resolved_path) {
1203 return Ok(id);
1204 }
1205
1206 let id = exec_state.next_module_id();
1207 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1209 let source = resolved_path.source(&self.fs, source_range).await?;
1210 exec_state.add_id_to_source(id, source.clone());
1211 let parsed = crate::parsing::parse_str(&source.source, id).parse_errs_as_err()?;
1213 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1214
1215 Ok(id)
1216 }
1217 ImportPath::Foreign { .. } => {
1218 if let Some(id) = exec_state.id_for_module(resolved_path) {
1219 return Ok(id);
1220 }
1221
1222 let id = exec_state.next_module_id();
1223 let path = resolved_path.expect_path();
1224 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1226 let format = super::import::format_from_annotations(attrs, path, source_range)?;
1227 let geom = super::import::import_foreign(path, format, exec_state, self, source_range).await?;
1228 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Foreign(geom, None));
1229 Ok(id)
1230 }
1231 ImportPath::Std { .. } => {
1232 if resolved_path.is_solver_module() && exec_state.mod_local.sketch_block.is_none() {
1233 return Err(KclError::new_semantic(KclErrorDetails::new(
1234 format!("The `{resolved_path}` module is only available inside sketch blocks."),
1235 vec![source_range],
1236 )));
1237 }
1238
1239 if let Some(id) = exec_state.id_for_module(resolved_path) {
1240 return Ok(id);
1241 }
1242
1243 let id = exec_state.next_module_id();
1244 exec_state.add_path_to_source_id(resolved_path.clone(), id);
1246 let source = resolved_path.source(&self.fs, source_range).await?;
1247 exec_state.add_id_to_source(id, source.clone());
1248 let parsed = crate::parsing::parse_str(&source.source, id)
1249 .parse_errs_as_err()
1250 .unwrap();
1251 exec_state.add_module(id, resolved_path.clone(), ModuleRepr::Kcl(parsed, None));
1252 Ok(id)
1253 }
1254 }
1255 }
1256
1257 pub(super) async fn exec_module_for_items(
1258 &self,
1259 module_id: ModuleId,
1260 exec_state: &mut ExecState,
1261 source_range: SourceRange,
1262 ) -> Result<(EnvironmentRef, Vec<String>), KclError> {
1263 let path = exec_state.global.module_infos[&module_id].path.clone();
1264 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1265 let result = match &mut repr {
1268 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1269 ModuleRepr::Kcl(_, Some(outcome)) => Ok((outcome.environment, outcome.exports.clone())),
1270 ModuleRepr::Kcl(program, cache) => self
1271 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1272 .await
1273 .map(|outcome| {
1274 *cache = Some(outcome.clone());
1275 (outcome.environment, outcome.exports)
1276 }),
1277 ModuleRepr::Foreign(geom, _) => Err(KclError::new_semantic(KclErrorDetails::new(
1278 "Cannot import items from foreign modules".to_owned(),
1279 vec![geom.source_range],
1280 ))),
1281 ModuleRepr::Dummy => unreachable!("Looking up {}, but it is still being interpreted", path),
1282 };
1283
1284 exec_state.global.module_infos[&module_id].restore_repr(repr);
1285 result
1286 }
1287
1288 async fn exec_module_for_result(
1289 &self,
1290 module_id: ModuleId,
1291 exec_state: &mut ExecState,
1292 source_range: SourceRange,
1293 ) -> Result<Option<KclValue>, KclError> {
1294 let path = exec_state.global.module_infos[&module_id].path.clone();
1295 let mut repr = exec_state.global.module_infos[&module_id].take_repr();
1296 let result = match &mut repr {
1299 ModuleRepr::Root => Err(exec_state.circular_import_error(&path, source_range)),
1300 ModuleRepr::Kcl(_, Some(outcome)) => Ok(outcome.last_expr.clone()),
1301 ModuleRepr::Kcl(program, cached_items) => {
1302 let result = self
1303 .exec_module_from_ast(program, module_id, &path, exec_state, source_range, PreserveMem::Normal)
1304 .await;
1305 match result {
1306 Ok(outcome) => {
1307 let value = outcome.last_expr.clone();
1308 *cached_items = Some(outcome);
1309 Ok(value)
1310 }
1311 Err(e) => Err(e),
1312 }
1313 }
1314 ModuleRepr::Foreign(_, Some((imported, _))) => Ok(imported.clone()),
1315 ModuleRepr::Foreign(geom, cached) => {
1316 let result = super::import::send_to_engine(geom.clone(), exec_state, self)
1317 .await
1318 .map(|geom| Some(KclValue::ImportedGeometry(geom)));
1319
1320 match result {
1321 Ok(val) => {
1322 *cached = Some((val.clone(), exec_state.mod_local.artifacts.clone()));
1323 Ok(val)
1324 }
1325 Err(e) => Err(e),
1326 }
1327 }
1328 ModuleRepr::Dummy => unreachable!(),
1329 };
1330
1331 exec_state.global.module_infos[&module_id].restore_repr(repr);
1332
1333 result
1334 }
1335
1336 pub async fn exec_module_from_ast(
1337 &self,
1338 program: &Node<Program>,
1339 module_id: ModuleId,
1340 path: &ModulePath,
1341 exec_state: &mut ExecState,
1342 source_range: SourceRange,
1343 preserve_mem: PreserveMem,
1344 ) -> Result<ModuleExecutionOutcome, KclError> {
1345 exec_state.global.mod_loader.enter_module(path);
1346 let result = self
1347 .exec_module_body(program, exec_state, preserve_mem, module_id, path)
1348 .await;
1349 exec_state.global.mod_loader.leave_module(path, source_range)?;
1350
1351 result.map_err(|(err, _, _)| {
1354 match err {
1355 KclError::ImportCycle { .. } => {
1356 err.override_source_ranges(vec![source_range])
1358 }
1359 KclError::EngineHangup { .. } | KclError::EngineInternal { .. } => {
1360 err.override_source_ranges(vec![source_range])
1363 }
1364 _ => {
1365 KclError::new_semantic(KclErrorDetails::new(
1367 format!(
1368 "Error loading imported file ({path}). Open it to view more details.\n {}",
1369 err.message()
1370 ),
1371 vec![source_range],
1372 ))
1373 }
1374 }
1375 })
1376 }
1377
1378 #[async_recursion]
1379 pub(crate) async fn execute_expr<'a: 'async_recursion>(
1380 &self,
1381 init: &Expr,
1382 exec_state: &mut ExecState,
1383 metadata: &Metadata,
1384 annotations: &[Node<Annotation>],
1385 statement_kind: StatementKind<'a>,
1386 ) -> Result<KclValueControlFlow, KclError> {
1387 let item = match init {
1388 Expr::None(none) => KclValue::from(none).continue_(),
1389 Expr::Literal(literal) => KclValue::from_literal((**literal).clone(), exec_state).continue_(),
1390 Expr::TagDeclarator(tag) => tag.execute(exec_state).await?.continue_(),
1391 Expr::Name(name) => {
1392 let being_declared = exec_state.mod_local.being_declared.clone();
1393 let value = name
1394 .get_result(exec_state, self)
1395 .await
1396 .map_err(|e| var_in_own_ref_err(e, &being_declared))?
1397 .clone();
1398 if let KclValue::Module { value: module_id, meta } = value {
1399 self.exec_module_for_result(
1400 module_id,
1401 exec_state,
1402 metadata.source_range
1403 ).await?.map(|v| v.continue_())
1404 .unwrap_or_else(|| {
1405 exec_state.warn(CompilationIssue::err(
1406 metadata.source_range,
1407 "Imported module has no return value. The last statement of the module must be an expression, usually the Solid.",
1408 ),
1409 annotations::WARN_MOD_RETURN_VALUE);
1410
1411 let mut new_meta = vec![metadata.to_owned()];
1412 new_meta.extend(meta);
1413 KclValue::KclNone {
1414 value: Default::default(),
1415 meta: new_meta,
1416 }.continue_()
1417 })
1418 } else {
1419 value.continue_()
1420 }
1421 }
1422 Expr::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, self).await?,
1423 Expr::FunctionExpression(function_expression) => {
1424 let attrs = annotations::get_fn_attrs(annotations, metadata.source_range)?;
1425 let experimental = attrs
1426 .as_ref()
1427 .map(|a| a.experimental)
1428 .unwrap_or_else(|| FnAttrs::default().experimental);
1430
1431 let include_in_feature_tree = attrs
1433 .as_ref()
1434 .map(|a| a.include_in_feature_tree)
1435 .unwrap_or_else(|| FnAttrs::default().include_in_feature_tree);
1437 let (mut closure, placeholder_env_ref) = if let Some(attrs) = attrs
1438 && (attrs.impl_ == annotations::Impl::Rust
1439 || attrs.impl_ == annotations::Impl::RustConstrainable
1440 || attrs.impl_ == annotations::Impl::RustConstraint)
1441 {
1442 if let ModulePath::Std { value: std_path } = &exec_state.mod_local.path {
1443 let (func, props) = crate::std::std_fn(std_path, statement_kind.expect_name());
1444 (
1445 KclValue::Function {
1446 value: Box::new(FunctionSource::rust(func, function_expression.clone(), props, attrs)),
1447 meta: vec![metadata.to_owned()],
1448 },
1449 None,
1450 )
1451 } else {
1452 return Err(KclError::new_semantic(KclErrorDetails::new(
1453 "Rust implementation of functions is restricted to the standard library".to_owned(),
1454 vec![metadata.source_range],
1455 )));
1456 }
1457 } else {
1458 let std_props = function_expression
1459 .name_str()
1460 .and_then(|name| exec_state.mod_local.path.build_std_fully_qualified_name(name))
1461 .map(|name| StdFnProps::default(&name));
1462 let (env_ref, placeholder_env_ref) = if function_expression.name.is_some() {
1466 let dummy = EnvironmentRef::dummy();
1469 (dummy, Some(dummy))
1470 } else {
1471 (exec_state.mut_stack().snapshot()?, None)
1472 };
1473 (
1474 KclValue::Function {
1475 value: Box::new(FunctionSource::kcl(
1476 function_expression.clone(),
1477 env_ref,
1478 KclFunctionSourceParams {
1479 std_props,
1480 experimental,
1481 include_in_feature_tree,
1482 },
1483 )),
1484 meta: vec![metadata.to_owned()],
1485 },
1486 placeholder_env_ref,
1487 )
1488 };
1489
1490 if let Some(fn_name) = &function_expression.name {
1493 if let Some(placeholder_env_ref) = placeholder_env_ref {
1497 closure = exec_state.mut_stack().add_recursive_closure(
1498 fn_name.name.to_owned(),
1499 closure,
1500 placeholder_env_ref,
1501 metadata.source_range,
1502 )?;
1503 } else {
1504 exec_state
1506 .mut_stack()
1507 .add(fn_name.name.clone(), closure.clone(), metadata.source_range)?;
1508 }
1509 }
1510
1511 closure.continue_()
1512 }
1513 Expr::CallExpressionKw(call_expression) => call_expression.execute(exec_state, self).await?,
1514 Expr::PipeExpression(pipe_expression) => pipe_expression.get_result(exec_state, self).await?,
1515 Expr::PipeSubstitution(pipe_substitution) => match statement_kind {
1516 StatementKind::Declaration { name } => {
1517 let message = format!(
1518 "you cannot declare variable {name} as %, because % can only be used in function calls"
1519 );
1520
1521 return Err(KclError::new_semantic(KclErrorDetails::new(
1522 message,
1523 vec![pipe_substitution.into()],
1524 )));
1525 }
1526 StatementKind::Expression => match exec_state.mod_local.pipe_value.clone() {
1527 Some(x) => x.continue_(),
1528 None => {
1529 return Err(KclError::new_semantic(KclErrorDetails::new(
1530 "cannot use % outside a pipe expression".to_owned(),
1531 vec![pipe_substitution.into()],
1532 )));
1533 }
1534 },
1535 },
1536 Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
1537 Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
1538 Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
1539 Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state, self).await?,
1540 Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
1541 Expr::IfExpression(expr) => expr.get_result(exec_state, self).await?,
1542 Expr::LabelledExpression(expr) => {
1543 let value_cf = self
1544 .execute_expr(&expr.expr, exec_state, metadata, &[], statement_kind)
1545 .await?;
1546 let value = control_continue!(value_cf);
1547 exec_state
1548 .mut_stack()
1549 .add(expr.label.name.clone(), value.clone(), init.into())?;
1550 value.continue_()
1552 }
1553 Expr::AscribedExpression(expr) => expr.get_result(exec_state, self).await?,
1554 Expr::SketchBlock(expr) => expr.get_result(exec_state, self).await?,
1555 Expr::SketchVar(expr) => expr.get_result(exec_state, self).await?.continue_(),
1556 };
1557 Ok(item)
1558 }
1559}
1560
1561fn sketch_mode_should_skip(expr: &Expr) -> bool {
1564 match expr {
1565 Expr::SketchBlock(sketch_block) => !sketch_block.is_being_edited,
1566 _ => true,
1567 }
1568}
1569
1570fn var_in_own_ref_err(e: KclError, being_declared: &Option<String>) -> KclError {
1573 let KclError::UndefinedValue { name, mut details } = e else {
1574 return e;
1575 };
1576 if let (Some(name0), Some(name1)) = (&being_declared, &name)
1580 && name0 == name1
1581 {
1582 details.message = format!(
1583 "You can't use `{name0}` because you're currently trying to define it. Use a different variable here instead."
1584 );
1585 }
1586 KclError::UndefinedValue { details, name }
1587}
1588
1589impl Node<AscribedExpression> {
1590 #[async_recursion]
1591 pub(super) async fn get_result(
1592 &self,
1593 exec_state: &mut ExecState,
1594 ctx: &ExecutorContext,
1595 ) -> Result<KclValueControlFlow, KclError> {
1596 let metadata = Metadata {
1597 source_range: SourceRange::from(self),
1598 };
1599 let result = ctx
1600 .execute_expr(&self.expr, exec_state, &metadata, &[], StatementKind::Expression)
1601 .await?;
1602 let result = control_continue!(result);
1603 apply_ascription(&result, &self.ty, exec_state, self.into()).map(KclValue::continue_)
1604 }
1605}
1606
1607impl Node<SketchBlock> {
1608 pub(super) async fn get_result(
1609 &self,
1610 exec_state: &mut ExecState,
1611 ctx: &ExecutorContext,
1612 ) -> Result<KclValueControlFlow, KclError> {
1613 if exec_state.mod_local.sketch_block.is_some() {
1614 return Err(KclError::new_semantic(KclErrorDetails::new(
1616 "Cannot execute a sketch block from within another sketch block".to_owned(),
1617 vec![SourceRange::from(self)],
1618 )));
1619 }
1620
1621 let range = SourceRange::from(self);
1622
1623 let (sketch_id, sketch_surface) = match self.exec_arguments(exec_state, ctx).await {
1625 Ok(x) => x,
1626 Err(cf_error) => match cf_error {
1627 EarlyReturn::Value(cf_value) => return Ok(cf_value),
1629 EarlyReturn::Error(err) => return Err(err),
1630 },
1631 };
1632 let on_object_id = if let Some(object_id) = sketch_surface.object_id() {
1633 object_id
1634 } else {
1635 let message = "The `on` argument should have an object after ensure_sketch_plane_in_engine".to_owned();
1636 debug_assert!(false, "{message}");
1637 return Err(internal_err(message, range));
1638 };
1639 let sketch_ctor_on = sketch_on_frontend_plane(&self.arguments, on_object_id);
1640 let sketch_block_artifact_id = {
1641 use crate::execution::CodeRef;
1642 use crate::execution::SketchBlock;
1643 use crate::front::Plane;
1644 use crate::front::SourceRef;
1645
1646 let on_object = exec_state.mod_local.artifacts.scene_object_by_id(on_object_id);
1647
1648 let plane_artifact_id = on_object.map(|object| object.artifact_id);
1650 let plane_info = match &sketch_surface {
1651 SketchSurface::Plane(plane) => Some(plane.info.clone()),
1652 SketchSurface::Face(_) => None,
1653 };
1654
1655 let standard_plane = match &sketch_ctor_on {
1656 Plane::Default(plane) => Some(*plane),
1657 Plane::Object(_) => None,
1658 };
1659
1660 let artifact_id = ArtifactId::from(exec_state.next_uuid());
1661 let sketch_scene_object = Object {
1663 id: sketch_id,
1664 kind: ObjectKind::Sketch(crate::frontend::sketch::Sketch {
1665 args: crate::front::SketchCtor { on: sketch_ctor_on },
1666 plane: on_object_id,
1667 segments: Default::default(),
1668 constraints: Default::default(),
1669 }),
1670 label: Default::default(),
1671 comments: Default::default(),
1672 artifact_id,
1673 source: SourceRef::new(self.into(), self.node_path.clone()),
1674 };
1675 exec_state.set_scene_object(sketch_scene_object);
1676
1677 exec_state.add_artifact(Artifact::SketchBlock(SketchBlock {
1679 id: artifact_id,
1680 standard_plane,
1681 plane_id: plane_artifact_id,
1682 plane_info,
1683 path_id: None,
1687 code_ref: CodeRef::placeholder(range),
1688 sketch_id,
1689 }));
1690
1691 exec_state.push_op(Operation::GroupBegin {
1692 group: Group::SketchBlock { sketch_id },
1693 node_path: NodePath::placeholder(),
1694 source_range: range,
1695 });
1696 artifact_id
1697 };
1698
1699 let (return_result, variables, sketch_block_state) = {
1700 self.prep_mem(exec_state.mut_stack().snapshot()?, exec_state)?;
1702
1703 let initial_sketch_block_state = {
1705 SketchBlockState {
1706 sketch_id: Some(sketch_id),
1707 ..Default::default()
1708 }
1709 };
1710
1711 let original_value = exec_state.mod_local.sketch_block.replace(initial_sketch_block_state);
1712
1713 let original_sketch_mode = std::mem::replace(&mut exec_state.mod_local.sketch_mode, false);
1716
1717 let (result, block_variables) = match self.load_sketch2_into_current_scope(exec_state, ctx, range).await {
1722 Ok(()) => {
1723 let parent = exec_state.mut_stack().snapshot()?;
1724 exec_state.mut_stack().push_new_env_for_call(parent)?;
1725 let result = ctx.exec_block(&self.body, exec_state, BodyType::Block).await;
1726 let (result, block_variables) = match exec_state.stack().find_all_in_current_env() {
1727 Ok(block_variables) => (result, block_variables.into_iter().collect::<IndexMap<_, _>>()),
1728 Err(err) => (Err(err), IndexMap::new()),
1729 };
1730 let result = match exec_state.mut_stack().pop_env() {
1731 Ok(_) => result,
1732 Err(err) => Err(err),
1733 };
1734 (result, block_variables)
1735 }
1736 Err(err) => (Err(err), IndexMap::new()),
1737 };
1738
1739 exec_state.mod_local.sketch_mode = original_sketch_mode;
1740
1741 let sketch_block_state = std::mem::replace(&mut exec_state.mod_local.sketch_block, original_value);
1742
1743 let result = match exec_state.mut_stack().pop_env() {
1745 Ok(_) => result,
1746 Err(err) => Err(err),
1747 };
1748
1749 (result, block_variables, sketch_block_state)
1750 };
1751
1752 let return_control_flow = return_result?;
1754 if let Some(control_flow) = return_control_flow
1759 && control_flow.is_some_return()
1760 {
1761 exec_state.push_op(Operation::GroupEnd);
1764 return Ok(control_flow);
1765 }
1766 let Some(sketch_block_state) = sketch_block_state else {
1767 debug_assert!(false, "Sketch block state should still be set to Some from just above");
1768 return Err(internal_err(
1769 "Sketch block state should still be set to Some from just above",
1770 self,
1771 ));
1772 };
1773 let mut sketch_block_state = sketch_block_state;
1774
1775 let constraints = sketch_block_state
1777 .solver_constraints
1778 .iter()
1779 .cloned()
1780 .map(ezpz::ConstraintRequest::highest_priority)
1781 .chain(
1782 sketch_block_state
1784 .solver_optional_constraints
1785 .iter()
1786 .cloned()
1787 .map(|c| ezpz::ConstraintRequest::new(c, 1)),
1788 )
1789 .collect::<Vec<_>>();
1790 let initial_guesses = sketch_block_state
1791 .sketch_vars
1792 .iter()
1793 .map(|v| {
1794 let Some(sketch_var) = v.as_sketch_var() else {
1795 return Err(internal_err("Expected sketch variable", self));
1796 };
1797 let constraint_id = sketch_var.id.to_constraint_id(range)?;
1798 let number_value = KclValue::Number {
1800 value: sketch_var.initial_value,
1801 ty: sketch_var.ty,
1802 meta: sketch_var.meta.clone(),
1803 };
1804 let initial_guess_value = normalize_to_solver_distance_unit(
1805 &number_value,
1806 v.into(),
1807 exec_state,
1808 "sketch variable initial value",
1809 )?;
1810 let initial_guess = if let Some(n) = initial_guess_value.as_ty_f64() {
1811 n.n
1812 } else {
1813 let message = format!(
1814 "Expected number after coercion, but found {}",
1815 initial_guess_value.human_friendly_type()
1816 );
1817 debug_assert!(false, "{}", &message);
1818 return Err(internal_err(message, self));
1819 };
1820 Ok((constraint_id, initial_guess))
1821 })
1822 .collect::<Result<Vec<_>, KclError>>()?;
1823 let config = ezpz::Config::default()
1825 .with_max_iterations(50)
1826 .with_convergence_tolerance(SOLVER_CONVERGENCE_TOLERANCE);
1827 let solve_result = if exec_state.mod_local.freedom_analysis {
1828 ezpz::solve_analysis(&constraints, initial_guesses.clone(), config).map(|outcome| {
1829 let freedom_analysis = FreedomAnalysis::from_ezpz_analysis(outcome.analysis, constraints.len());
1830 (outcome.outcome, Some(freedom_analysis))
1831 })
1832 } else {
1833 ezpz::solve(&constraints, initial_guesses.clone(), config).map(|outcome| (outcome, None))
1834 };
1835 let num_required_constraints = sketch_block_state.solver_constraints.len();
1837 let all_constraints: Vec<ezpz::Constraint> = sketch_block_state
1838 .solver_constraints
1839 .iter()
1840 .cloned()
1841 .chain(sketch_block_state.solver_optional_constraints.iter().cloned())
1842 .collect();
1843
1844 let (solve_outcome, solve_analysis) = match solve_result {
1845 Ok((solved, freedom)) => {
1846 if solved
1847 .final_values()
1848 .iter()
1849 .any(|number| number.is_infinite() || number.is_nan())
1850 {
1851 return Err(KclError::new_internal(KclErrorDetails::new(
1852 "KCL's 2D constraint solver returned an invalid number".to_owned(),
1853 vec![SourceRange::from(self)],
1854 )));
1855 }
1856 let outcome = Solved::from_ezpz_outcome(solved, &all_constraints, num_required_constraints);
1857 if !outcome.converged {
1858 exec_state.warn(
1859 CompilationIssue::err(range, "Constraint solver failed to find a solution".to_owned()),
1860 annotations::WARN_SOLVER,
1861 );
1862 }
1863 (outcome, freedom)
1864 }
1865 Err(failure) => {
1866 match &failure.error {
1867 NonLinearSystemError::FaerMatrix { .. }
1868 | NonLinearSystemError::Faer { .. }
1869 | NonLinearSystemError::FaerSolve { .. }
1870 | NonLinearSystemError::FaerSvd(..) => {
1871 exec_state.warn(
1874 CompilationIssue::err(range, "Internal error in constraint solver".to_owned()),
1875 annotations::WARN_SOLVER,
1876 );
1877 let final_values = initial_guesses.iter().map(|(_, v)| *v).collect::<Vec<_>>();
1878 (
1879 Solved {
1880 final_values,
1881 iterations: Default::default(),
1882 warnings: failure.warnings,
1883 priority_solved: Default::default(),
1884 variables_in_conflicts: Default::default(),
1885 converged: false,
1886 },
1887 None,
1888 )
1889 }
1890 NonLinearSystemError::EmptySystemNotAllowed
1891 | NonLinearSystemError::WrongNumberGuesses { .. }
1892 | NonLinearSystemError::MissingGuess { .. }
1893 | NonLinearSystemError::NotFound(..) => {
1894 #[cfg(target_arch = "wasm32")]
1897 web_sys::console::error_1(
1898 &format!("Internal error from constraint solver: {}", failure.error).into(),
1899 );
1900 return Err(internal_err(
1901 format!("Internal error from constraint solver: {}", failure.error),
1902 self,
1903 ));
1904 }
1905 _ => {
1906 return Err(internal_err(
1908 format!("Error from constraint solver: {}", failure.error),
1909 self,
1910 ));
1911 }
1912 }
1913 }
1914 };
1915 for warning in &solve_outcome.warnings {
1917 let message = if let Some(index) = warning.about_constraint.as_ref() {
1918 format!("{}; constraint index {}", warning.content, index)
1919 } else {
1920 format!("{}", warning.content)
1921 };
1922 exec_state.warn(CompilationIssue::err(range, message), annotations::WARN_SOLVER);
1923 }
1924 let sketch_engine_id = exec_state.next_uuid();
1926 let solution_ty = solver_numeric_type(exec_state);
1927 let mut solved_segments = Vec::with_capacity(sketch_block_state.needed_by_engine.len());
1928 for unsolved_segment in &sketch_block_state.needed_by_engine {
1929 solved_segments.push(substitute_sketch_var_in_segment(
1930 unsolved_segment.clone(),
1931 &sketch_surface,
1932 sketch_engine_id,
1933 None,
1934 &solve_outcome,
1935 solver_numeric_type(exec_state),
1936 solve_analysis.as_ref(),
1937 )?);
1938 }
1939 exec_state.mod_local.artifacts.var_solutions =
1945 sketch_block_state.var_solutions(&solve_outcome, solution_ty, SourceRange::from(self))?;
1946
1947 let scene_objects = create_segment_scene_objects(&solved_segments, range, exec_state)?;
1949
1950 let sketch = create_segments_in_engine(
1952 &sketch_surface,
1953 sketch_engine_id,
1954 &mut solved_segments,
1955 &sketch_block_state.segment_tags,
1956 ctx,
1957 exec_state,
1958 range,
1959 )
1960 .await?;
1961
1962 if let Some(sketch_artifact_id) = sketch.as_ref().map(|s| s.artifact_id) {
1964 if let Some(Artifact::SketchBlock(sketch_block_artifact)) =
1965 exec_state.artifact_mut(sketch_block_artifact_id)
1966 {
1967 sketch_block_artifact.path_id = Some(sketch_artifact_id);
1968 } else {
1969 let message = "Sketch block artifact not found, so path couldn't be linked to it".to_owned();
1970 debug_assert!(false, "{message}");
1971 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
1972 }
1973 }
1974
1975 let variables = substitute_sketch_vars(
1980 variables,
1981 &sketch_surface,
1982 sketch_engine_id,
1983 sketch.as_ref(),
1984 &solve_outcome,
1985 solution_ty,
1986 solve_analysis.as_ref(),
1987 )?;
1988
1989 let mut segment_object_ids = Vec::with_capacity(scene_objects.len());
1990 for scene_object in scene_objects {
1991 segment_object_ids.push(scene_object.id);
1992 exec_state.set_scene_object(scene_object);
1994 }
1995 let Some(sketch_object) = exec_state.mod_local.artifacts.scene_object_by_id_mut(sketch_id) else {
1997 let message = format!("Sketch object not found after it was just created; id={:?}", sketch_id);
1998 debug_assert!(false, "{}", &message);
1999 return Err(internal_err(message, range));
2000 };
2001 let ObjectKind::Sketch(front_sketch) = &mut sketch_object.kind else {
2002 let message = format!(
2003 "Expected Sketch object after it was just created to be a sketch kind; id={:?}, actual={:?}",
2004 sketch_id, sketch_object
2005 );
2006 debug_assert!(
2007 false,
2008 "{}; scene_objects={:#?}",
2009 message, exec_state.mod_local.artifacts.scene_objects
2010 );
2011 return Err(internal_err(message, range));
2012 };
2013 front_sketch.segments.extend(segment_object_ids);
2014 front_sketch
2016 .constraints
2017 .extend(std::mem::take(&mut sketch_block_state.sketch_constraints));
2018
2019 exec_state.push_op(Operation::GroupEnd);
2021
2022 if exec_state.mod_local.freedom_analysis {
2026 let status = {
2027 let scene_objects = &exec_state.mod_local.artifacts.scene_objects;
2028 scene_objects
2029 .get(sketch_id.0)
2030 .and_then(|obj| sketch_constraint_status_for_sketch(scene_objects, obj))
2031 };
2032 if let Some(status) = status
2033 && status.status == ConstraintKind::OverConstrained
2034 {
2035 let description = if status.conflict_count == 1 {
2036 "segment has"
2037 } else {
2038 "segments have"
2039 };
2040 let message = format!(
2041 "Sketch is over-constrained: {} {description} conflicting constraints",
2042 status.conflict_count,
2043 );
2044 exec_state.warn(
2045 CompilationIssue::err(range, message),
2046 annotations::WARN_OVER_CONSTRAINED_SKETCH,
2047 );
2048 }
2049 }
2050
2051 let properties = self.sketch_properties(sketch, variables);
2052 let metadata = Metadata {
2053 source_range: SourceRange::from(self),
2054 };
2055 let return_value = KclValue::Object {
2056 value: properties,
2057 constrainable: Default::default(),
2058 object_kind: KclObjectKind::Default,
2059 meta: vec![metadata],
2060 };
2061 Ok(if self.is_being_edited {
2062 return_value.exit()
2065 } else {
2066 return_value.continue_()
2067 })
2068 }
2069
2070 async fn exec_arguments(
2080 &self,
2081 exec_state: &mut ExecState,
2082 ctx: &ExecutorContext,
2083 ) -> Result<(ObjectId, SketchSurface), EarlyReturn> {
2084 let range = SourceRange::from(self);
2085
2086 if !exec_state.sketch_mode() {
2087 let mut labeled = IndexMap::new();
2093 for labeled_arg in &self.arguments {
2094 let source_range = SourceRange::from(labeled_arg.arg.clone());
2095 let metadata = Metadata { source_range };
2096 let value_cf = ctx
2097 .execute_expr(&labeled_arg.arg, exec_state, &metadata, &[], StatementKind::Expression)
2098 .await?;
2099 let value = early_return!(value_cf);
2100 let arg = Arg::new(value, source_range);
2101 match &labeled_arg.label {
2102 Some(label) => {
2103 labeled.insert(label.name.clone(), arg);
2104 }
2105 None => {
2106 let name = labeled_arg.arg.ident_name();
2107 if let Some(name) = name {
2108 labeled.insert(name.to_owned(), arg);
2109 } else {
2110 return Err(KclError::new_semantic(KclErrorDetails::new(
2111 "Arguments to sketch blocks must be either labeled or simple identifiers".to_owned(),
2112 vec![SourceRange::from(&labeled_arg.arg)],
2113 ))
2114 .into());
2115 }
2116 }
2117 }
2118 }
2119 let mut args = Args::new_no_args(
2120 range,
2121 self.node_path.clone(),
2122 ctx.clone(),
2123 Some(SketchBlock::CALLEE_NAME.to_owned()),
2124 );
2125 args.labeled = labeled;
2126
2127 self.check_for_unexpected_arguments(&args, exec_state)?;
2135
2136 let arg_on_value: KclValue =
2137 args.get_kw_arg(SKETCH_BLOCK_PARAM_ON, &RuntimeType::sketch_or_surface(), exec_state)?;
2138
2139 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2140 let message =
2141 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2142 debug_assert!(false, "{message}");
2143 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2144 };
2145 let mut sketch_surface = arg_on.into_sketch_surface();
2146
2147 match &mut sketch_surface {
2150 SketchSurface::Plane(plane) => {
2151 ensure_sketch_plane_in_engine(plane, exec_state, ctx, range, self.node_path.clone()).await?;
2153 }
2154 SketchSurface::Face(_) => {
2155 }
2157 }
2158
2159 let sketch_id = exec_state.next_object_id();
2165 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2166 let on_cache_name = sketch_on_cache_name(sketch_id);
2167 exec_state.mut_stack().add(on_cache_name, arg_on_value, range)?;
2169
2170 Ok((sketch_id, sketch_surface))
2171 } else {
2172 let sketch_id = exec_state.next_object_id();
2179 exec_state.add_placeholder_scene_object(sketch_id, range, self.node_path.clone());
2180 let on_cache_name = sketch_on_cache_name(sketch_id);
2181 let arg_on_value = exec_state.stack().get_owned(&on_cache_name, range)?;
2182
2183 let Some(arg_on) = SketchOrSurface::from_kcl_val(&arg_on_value) else {
2184 let message =
2185 "The `on` argument to a sketch block must be convertible to a sketch or surface.".to_owned();
2186 debug_assert!(false, "{message}");
2187 return Err(KclError::new_semantic(KclErrorDetails::new(message, vec![range])).into());
2188 };
2189 let mut sketch_surface = arg_on.into_sketch_surface();
2190
2191 if sketch_surface.object_id().is_none() {
2194 let Some(last_object) = exec_state.mod_local.artifacts.scene_objects.last() else {
2197 return Err(internal_err(
2198 "In sketch mode, the `on` plane argument must refer to an existing plane object.",
2199 range,
2200 )
2201 .into());
2202 };
2203 sketch_surface.set_object_id(last_object.id);
2204 }
2205
2206 Ok((sketch_id, sketch_surface))
2207 }
2208 }
2209
2210 fn check_for_unexpected_arguments(&self, args: &Args, exec_state: &mut ExecState) -> Result<(), KclError> {
2213 if !args.unlabeled.is_empty() {
2214 let message = "Sketch block doesn't support unlabeled arguments; argument shorthand should have already been desugared";
2215 debug_assert!(false, "{message}");
2216 return Err(KclError::new_internal(KclErrorDetails::new(
2217 message.to_owned(),
2218 vec![args.source_range],
2219 )));
2220 }
2221 for (label, arg) in &args.labeled {
2222 if label == SKETCH_BLOCK_PARAM_ON {
2223 continue;
2224 }
2225 exec_state.err(CompilationIssue::err(
2226 arg.source_range,
2227 unexpected_kw_arg_message(label, Some(SketchBlock::CALLEE_NAME)),
2228 ));
2229 }
2230 Ok(())
2231 }
2232
2233 async fn load_sketch2_into_current_scope(
2234 &self,
2235 exec_state: &mut ExecState,
2236 ctx: &ExecutorContext,
2237 source_range: SourceRange,
2238 ) -> Result<(), KclError> {
2239 let path = vec!["std".to_owned(), "solver".to_owned()];
2240 let resolved_path = ModulePath::from_std_import_path(&path)?;
2241 let module_id = ctx
2242 .open_module(&ImportPath::Std { path }, &[], &resolved_path, exec_state, source_range)
2243 .await?;
2244 let (env_ref, exports) = ctx.exec_module_for_items(module_id, exec_state, source_range).await?;
2245
2246 for name in exports {
2247 let value = exec_state
2248 .stack()
2249 .memory
2250 .get_from_owned(&name, env_ref, source_range, 0)?;
2251 exec_state.mut_stack().add(name, value, source_range)?;
2252 }
2253 Ok(())
2254 }
2255
2256 pub(crate) fn sketch_properties(
2260 &self,
2261 sketch: Option<Sketch>,
2262 variables: HashMap<String, KclValue>,
2263 ) -> HashMap<String, KclValue> {
2264 let Some(sketch) = sketch else {
2265 return variables;
2268 };
2269
2270 let mut properties = variables;
2271
2272 let sketch_value = KclValue::Sketch {
2273 value: Box::new(sketch),
2274 };
2275 let mut meta_map = HashMap::with_capacity(1);
2276 meta_map.insert(SKETCH_OBJECT_META_SKETCH.to_owned(), sketch_value);
2277 let meta_value = KclValue::Object {
2278 value: meta_map,
2279 constrainable: false,
2280 object_kind: KclObjectKind::Default,
2281 meta: vec![Metadata {
2282 source_range: SourceRange::from(self),
2283 }],
2284 };
2285
2286 properties.insert(SKETCH_OBJECT_META.to_owned(), meta_value);
2287
2288 properties
2289 }
2290}
2291
2292impl SketchBlock {
2293 fn prep_mem(&self, parent: EnvironmentRef, exec_state: &mut ExecState) -> Result<(), KclError> {
2294 exec_state.mut_stack().push_new_env_for_call(parent)
2295 }
2296}
2297
2298impl Node<SketchVar> {
2299 pub async fn get_result(&self, exec_state: &mut ExecState, _ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2300 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
2301 return Err(KclError::new_semantic(KclErrorDetails::new(
2302 "Cannot use a sketch variable outside of a sketch block".to_owned(),
2303 vec![SourceRange::from(self)],
2304 )));
2305 };
2306 let id = sketch_block_state.next_sketch_var_id();
2307 let sketch_var = if let Some(initial) = &self.initial {
2308 KclValue::from_sketch_var_literal(initial, id, self.node_path.clone(), exec_state)
2309 } else {
2310 let metadata = Metadata {
2311 source_range: SourceRange::from(self),
2312 };
2313
2314 KclValue::SketchVar {
2315 value: Box::new(super::SketchVar {
2316 id,
2317 initial_value: 0.0,
2318 ty: NumericType::default(),
2319 node_path: self.node_path.clone(),
2320 meta: vec![metadata],
2321 }),
2322 }
2323 };
2324
2325 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
2326 return Err(KclError::new_semantic(KclErrorDetails::new(
2327 "Cannot use a sketch variable outside of a sketch block".to_owned(),
2328 vec![SourceRange::from(self)],
2329 )));
2330 };
2331 sketch_block_state.sketch_vars.push(sketch_var.clone());
2332
2333 Ok(sketch_var)
2334 }
2335}
2336
2337fn apply_ascription(
2338 value: &KclValue,
2339 ty: &Node<Type>,
2340 exec_state: &mut ExecState,
2341 source_range: SourceRange,
2342) -> Result<KclValue, KclError> {
2343 let ty = RuntimeType::from_parsed(ty.inner.clone(), exec_state, value.into(), false, false)
2344 .map_err(|e| KclError::new_semantic(e.into()))?;
2345
2346 if matches!(&ty, &RuntimeType::Primitive(PrimitiveType::Number(..))) {
2347 exec_state.clear_units_warnings(&source_range);
2348 }
2349
2350 value.coerce(&ty, false, exec_state).map_err(|_| {
2351 let suggestion = if ty == RuntimeType::length() {
2352 ", you might try coercing to a fully specified numeric type such as `mm`"
2353 } else if ty == RuntimeType::angle() {
2354 ", you might try coercing to a fully specified numeric type such as `deg`"
2355 } else {
2356 ""
2357 };
2358 let ty_str = if let Some(ty) = value.principal_type() {
2359 format!("(with type `{ty}`) ")
2360 } else {
2361 String::new()
2362 };
2363 KclError::new_semantic(KclErrorDetails::new(
2364 format!(
2365 "could not coerce {} {ty_str}to type `{ty}`{suggestion}",
2366 value.human_friendly_type()
2367 ),
2368 vec![source_range],
2369 ))
2370 })
2371}
2372
2373impl BinaryPart {
2374 #[async_recursion]
2375 pub(super) async fn get_result(
2376 &self,
2377 exec_state: &mut ExecState,
2378 ctx: &ExecutorContext,
2379 ) -> Result<KclValueControlFlow, KclError> {
2380 match self {
2381 BinaryPart::Literal(literal) => Ok(KclValue::from_literal((**literal).clone(), exec_state).continue_()),
2382 BinaryPart::Name(name) => name.get_result(exec_state, ctx).await.map(KclValue::continue_),
2383 BinaryPart::BinaryExpression(binary_expression) => binary_expression.get_result(exec_state, ctx).await,
2384 BinaryPart::CallExpressionKw(call_expression) => call_expression.execute(exec_state, ctx).await,
2385 BinaryPart::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, ctx).await,
2386 BinaryPart::MemberExpression(member_expression) => member_expression.get_result(exec_state, ctx).await,
2387 BinaryPart::ArrayExpression(e) => e.execute(exec_state, ctx).await,
2388 BinaryPart::ArrayRangeExpression(e) => e.execute(exec_state, ctx).await,
2389 BinaryPart::ObjectExpression(e) => e.execute(exec_state, ctx).await,
2390 BinaryPart::IfExpression(e) => e.get_result(exec_state, ctx).await,
2391 BinaryPart::AscribedExpression(e) => e.get_result(exec_state, ctx).await,
2392 BinaryPart::SketchVar(e) => e.get_result(exec_state, ctx).await.map(KclValue::continue_),
2393 }
2394 }
2395}
2396
2397impl Node<Name> {
2398 pub(super) async fn get_result(
2399 &self,
2400 exec_state: &mut ExecState,
2401 ctx: &ExecutorContext,
2402 ) -> Result<KclValue, KclError> {
2403 let being_declared = exec_state.mod_local.being_declared.clone();
2404 self.get_result_inner(exec_state, ctx)
2405 .await
2406 .map_err(|e| var_in_own_ref_err(e, &being_declared))
2407 }
2408
2409 async fn get_result_inner(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
2410 if self.abs_path {
2411 return Err(KclError::new_semantic(KclErrorDetails::new(
2412 "Absolute paths (names beginning with `::` are not yet supported)".to_owned(),
2413 self.as_source_ranges(),
2414 )));
2415 }
2416
2417 let mod_name = format!("{}{}", memory::MODULE_PREFIX, self.name.name);
2418
2419 if self.path.is_empty() {
2420 if let Ok(item_value) = exec_state.stack().get(&self.name.name, self.into()) {
2421 return Ok(item_value);
2422 }
2423 return exec_state.stack().get(&mod_name, self.into());
2424 }
2425
2426 let mut mem_spec: Option<(EnvironmentRef, Vec<String>)> = None;
2427 for p in &self.path {
2428 let value = match mem_spec {
2429 Some((env, exports)) => {
2430 if !exports.contains(&p.name) {
2431 return Err(KclError::new_semantic(KclErrorDetails::new(
2432 format!("Item {} not found in module's exported items", p.name),
2433 p.as_source_ranges(),
2434 )));
2435 }
2436
2437 exec_state
2438 .stack()
2439 .memory
2440 .get_from_owned(&p.name, env, p.as_source_range(), 0)?
2441 }
2442 None => exec_state
2443 .stack()
2444 .get(&format!("{}{}", memory::MODULE_PREFIX, p.name), self.into())?,
2445 };
2446
2447 let module_id = match value {
2448 KclValue::Module { value, .. } => value,
2449 value => {
2450 return Err(KclError::new_semantic(KclErrorDetails::new(
2451 format!(
2452 "Identifier in path must refer to a module, found {}",
2453 value.human_friendly_type()
2454 ),
2455 p.as_source_ranges(),
2456 )));
2457 }
2458 };
2459
2460 mem_spec = Some(
2461 ctx.exec_module_for_items(module_id, exec_state, p.as_source_range())
2462 .await?,
2463 );
2464 }
2465
2466 let (env, exports) = mem_spec.unwrap();
2467
2468 let item_exported = exports.contains(&self.name.name);
2469 let item_value = exec_state
2470 .stack()
2471 .memory
2472 .get_from_owned(&self.name.name, env, self.name.as_source_range(), 0);
2473
2474 if item_exported && item_value.is_ok() {
2476 return item_value;
2477 }
2478
2479 let mod_exported = exports.contains(&mod_name);
2480 let mod_value = exec_state
2481 .stack()
2482 .memory
2483 .get_from_owned(&mod_name, env, self.name.as_source_range(), 0);
2484
2485 if mod_exported && mod_value.is_ok() {
2487 return mod_value;
2488 }
2489
2490 if item_value.is_err() && mod_value.is_err() {
2492 return item_value;
2493 }
2494
2495 debug_assert!((item_value.is_ok() && !item_exported) || (mod_value.is_ok() && !mod_exported));
2497 Err(KclError::new_semantic(KclErrorDetails::new(
2498 format!("Item {} not found in module's exported items", self.name.name),
2499 self.name.as_source_ranges(),
2500 )))
2501 }
2502}
2503
2504impl Node<MemberExpression> {
2505 async fn get_result(
2506 &self,
2507 exec_state: &mut ExecState,
2508 ctx: &ExecutorContext,
2509 ) -> Result<KclValueControlFlow, KclError> {
2510 let meta = Metadata {
2511 source_range: SourceRange::from(self),
2512 };
2513 let property = Property::try_from(
2516 self.computed,
2517 self.property.clone(),
2518 exec_state,
2519 self.into(),
2520 ctx,
2521 &meta,
2522 &[],
2523 StatementKind::Expression,
2524 )
2525 .await?;
2526 let object_cf = ctx
2527 .execute_expr(&self.object, exec_state, &meta, &[], StatementKind::Expression)
2528 .await?;
2529 let object = control_continue!(object_cf);
2530
2531 match (object, property, self.computed) {
2533 (KclValue::Segment { value: segment }, Property::String(property), false) => match property.as_str() {
2534 "at" => match &segment.repr {
2535 SegmentRepr::Unsolved { segment } => {
2536 match &segment.kind {
2537 UnsolvedSegmentKind::Point { position, .. } => {
2538 Ok(KclValue::HomArray {
2540 value: vec![
2541 KclValue::from_unsolved_expr(position[0].clone(), segment.meta.clone()),
2542 KclValue::from_unsolved_expr(position[1].clone(), segment.meta.clone()),
2543 ],
2544 ty: RuntimeType::any(),
2545 }
2546 .continue_())
2547 }
2548 _ => Err(KclError::new_undefined_value(
2549 KclErrorDetails::new(
2550 format!("Property '{property}' not found in segment"),
2551 vec![self.clone().into()],
2552 ),
2553 None,
2554 )),
2555 }
2556 }
2557 SegmentRepr::Solved { segment } => {
2558 match &segment.kind {
2559 SegmentKind::Point { position, .. } => {
2560 Ok(KclValue::array_from_point2d(
2562 [position[0].n, position[1].n],
2563 position[0].ty,
2564 segment.meta.clone(),
2565 )
2566 .continue_())
2567 }
2568 _ => Err(KclError::new_undefined_value(
2569 KclErrorDetails::new(
2570 format!("Property '{property}' not found in segment"),
2571 vec![self.clone().into()],
2572 ),
2573 None,
2574 )),
2575 }
2576 }
2577 },
2578 "start" => match &segment.repr {
2579 SegmentRepr::Unsolved { segment } => match &segment.kind {
2580 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2581 KclErrorDetails::new(
2582 format!("Property '{property}' not found in point segment"),
2583 vec![self.clone().into()],
2584 ),
2585 None,
2586 )),
2587 UnsolvedSegmentKind::Line {
2588 start,
2589 ctor,
2590 start_object_id,
2591 ..
2592 } => Ok(KclValue::Segment {
2593 value: Box::new(AbstractSegment {
2594 repr: SegmentRepr::Unsolved {
2595 segment: Box::new(UnsolvedSegment {
2596 id: segment.id,
2597 object_id: *start_object_id,
2598 kind: UnsolvedSegmentKind::Point {
2599 position: start.clone(),
2600 ctor: Box::new(PointCtor {
2601 position: ctor.start.clone(),
2602 }),
2603 },
2604 tag: segment.tag.clone(),
2605 node_path: segment.node_path.clone(),
2606 meta: segment.meta.clone(),
2607 }),
2608 },
2609 meta: segment.meta.clone(),
2610 }),
2611 }
2612 .continue_()),
2613 UnsolvedSegmentKind::Arc {
2614 start,
2615 ctor,
2616 start_object_id,
2617 ..
2618 } => Ok(KclValue::Segment {
2619 value: Box::new(AbstractSegment {
2620 repr: SegmentRepr::Unsolved {
2621 segment: Box::new(UnsolvedSegment {
2622 id: segment.id,
2623 object_id: *start_object_id,
2624 kind: UnsolvedSegmentKind::Point {
2625 position: start.clone(),
2626 ctor: Box::new(PointCtor {
2627 position: ctor.start.clone(),
2628 }),
2629 },
2630 tag: segment.tag.clone(),
2631 node_path: segment.node_path.clone(),
2632 meta: segment.meta.clone(),
2633 }),
2634 },
2635 meta: segment.meta.clone(),
2636 }),
2637 }
2638 .continue_()),
2639 UnsolvedSegmentKind::Circle {
2640 start,
2641 ctor,
2642 start_object_id,
2643 ..
2644 } => Ok(KclValue::Segment {
2645 value: Box::new(AbstractSegment {
2646 repr: SegmentRepr::Unsolved {
2647 segment: Box::new(UnsolvedSegment {
2648 id: segment.id,
2649 object_id: *start_object_id,
2650 kind: UnsolvedSegmentKind::Point {
2651 position: start.clone(),
2652 ctor: Box::new(PointCtor {
2653 position: ctor.start.clone(),
2654 }),
2655 },
2656 tag: segment.tag.clone(),
2657 node_path: segment.node_path.clone(),
2658 meta: segment.meta.clone(),
2659 }),
2660 },
2661 meta: segment.meta.clone(),
2662 }),
2663 }
2664 .continue_()),
2665 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2666 KclErrorDetails::new(
2667 format!("Property '{property}' not found in segment"),
2668 vec![self.clone().into()],
2669 ),
2670 None,
2671 )),
2672 },
2673 SegmentRepr::Solved { segment } => match &segment.kind {
2674 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2675 KclErrorDetails::new(
2676 format!("Property '{property}' not found in point segment"),
2677 vec![self.clone().into()],
2678 ),
2679 None,
2680 )),
2681 SegmentKind::Line {
2682 start,
2683 ctor,
2684 start_object_id,
2685 start_freedom,
2686 ..
2687 } => Ok(KclValue::Segment {
2688 value: Box::new(AbstractSegment {
2689 repr: SegmentRepr::Solved {
2690 segment: Box::new(Segment {
2691 id: segment.id,
2692 object_id: *start_object_id,
2693 kind: SegmentKind::Point {
2694 position: start.clone(),
2695 ctor: Box::new(PointCtor {
2696 position: ctor.start.clone(),
2697 }),
2698 freedom: *start_freedom,
2699 },
2700 surface: segment.surface.clone(),
2701 sketch_id: segment.sketch_id,
2702 sketch: segment.sketch.clone(),
2703 tag: segment.tag.clone(),
2704 node_path: segment.node_path.clone(),
2705 meta: segment.meta.clone(),
2706 }),
2707 },
2708 meta: segment.meta.clone(),
2709 }),
2710 }
2711 .continue_()),
2712 SegmentKind::Arc {
2713 start,
2714 ctor,
2715 start_object_id,
2716 start_freedom,
2717 ..
2718 } => Ok(KclValue::Segment {
2719 value: Box::new(AbstractSegment {
2720 repr: SegmentRepr::Solved {
2721 segment: Box::new(Segment {
2722 id: segment.id,
2723 object_id: *start_object_id,
2724 kind: SegmentKind::Point {
2725 position: start.clone(),
2726 ctor: Box::new(PointCtor {
2727 position: ctor.start.clone(),
2728 }),
2729 freedom: *start_freedom,
2730 },
2731 surface: segment.surface.clone(),
2732 sketch_id: segment.sketch_id,
2733 sketch: segment.sketch.clone(),
2734 tag: segment.tag.clone(),
2735 node_path: segment.node_path.clone(),
2736 meta: segment.meta.clone(),
2737 }),
2738 },
2739 meta: segment.meta.clone(),
2740 }),
2741 }
2742 .continue_()),
2743 SegmentKind::Circle {
2744 start,
2745 ctor,
2746 start_object_id,
2747 start_freedom,
2748 ..
2749 } => Ok(KclValue::Segment {
2750 value: Box::new(AbstractSegment {
2751 repr: SegmentRepr::Solved {
2752 segment: Box::new(Segment {
2753 id: segment.id,
2754 object_id: *start_object_id,
2755 kind: SegmentKind::Point {
2756 position: start.clone(),
2757 ctor: Box::new(PointCtor {
2758 position: ctor.start.clone(),
2759 }),
2760 freedom: *start_freedom,
2761 },
2762 surface: segment.surface.clone(),
2763 sketch_id: segment.sketch_id,
2764 sketch: segment.sketch.clone(),
2765 tag: segment.tag.clone(),
2766 node_path: segment.node_path.clone(),
2767 meta: segment.meta.clone(),
2768 }),
2769 },
2770 meta: segment.meta.clone(),
2771 }),
2772 }
2773 .continue_()),
2774 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2775 KclErrorDetails::new(
2776 format!("Property '{property}' not found in segment"),
2777 vec![self.clone().into()],
2778 ),
2779 None,
2780 )),
2781 },
2782 },
2783 "end" => match &segment.repr {
2784 SegmentRepr::Unsolved { segment } => match &segment.kind {
2785 UnsolvedSegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2786 KclErrorDetails::new(
2787 format!("Property '{property}' not found in point segment"),
2788 vec![self.clone().into()],
2789 ),
2790 None,
2791 )),
2792 UnsolvedSegmentKind::Line {
2793 end,
2794 ctor,
2795 end_object_id,
2796 ..
2797 } => Ok(KclValue::Segment {
2798 value: Box::new(AbstractSegment {
2799 repr: SegmentRepr::Unsolved {
2800 segment: Box::new(UnsolvedSegment {
2801 id: segment.id,
2802 object_id: *end_object_id,
2803 kind: UnsolvedSegmentKind::Point {
2804 position: end.clone(),
2805 ctor: Box::new(PointCtor {
2806 position: ctor.end.clone(),
2807 }),
2808 },
2809 tag: segment.tag.clone(),
2810 node_path: segment.node_path.clone(),
2811 meta: segment.meta.clone(),
2812 }),
2813 },
2814 meta: segment.meta.clone(),
2815 }),
2816 }
2817 .continue_()),
2818 UnsolvedSegmentKind::Arc {
2819 end,
2820 ctor,
2821 end_object_id,
2822 ..
2823 } => Ok(KclValue::Segment {
2824 value: Box::new(AbstractSegment {
2825 repr: SegmentRepr::Unsolved {
2826 segment: Box::new(UnsolvedSegment {
2827 id: segment.id,
2828 object_id: *end_object_id,
2829 kind: UnsolvedSegmentKind::Point {
2830 position: end.clone(),
2831 ctor: Box::new(PointCtor {
2832 position: ctor.end.clone(),
2833 }),
2834 },
2835 tag: segment.tag.clone(),
2836 node_path: segment.node_path.clone(),
2837 meta: segment.meta.clone(),
2838 }),
2839 },
2840 meta: segment.meta.clone(),
2841 }),
2842 }
2843 .continue_()),
2844 UnsolvedSegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2845 KclErrorDetails::new(
2846 format!("Property '{property}' not found in segment"),
2847 vec![self.into()],
2848 ),
2849 None,
2850 )),
2851 UnsolvedSegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2852 KclErrorDetails::new(
2853 format!("Property '{property}' not found in segment"),
2854 vec![self.clone().into()],
2855 ),
2856 None,
2857 )),
2858 },
2859 SegmentRepr::Solved { segment } => match &segment.kind {
2860 SegmentKind::Point { .. } => Err(KclError::new_undefined_value(
2861 KclErrorDetails::new(
2862 format!("Property '{property}' not found in point segment"),
2863 vec![self.clone().into()],
2864 ),
2865 None,
2866 )),
2867 SegmentKind::Line {
2868 end,
2869 ctor,
2870 end_object_id,
2871 end_freedom,
2872 ..
2873 } => Ok(KclValue::Segment {
2874 value: Box::new(AbstractSegment {
2875 repr: SegmentRepr::Solved {
2876 segment: Box::new(Segment {
2877 id: segment.id,
2878 object_id: *end_object_id,
2879 kind: SegmentKind::Point {
2880 position: end.clone(),
2881 ctor: Box::new(PointCtor {
2882 position: ctor.end.clone(),
2883 }),
2884 freedom: *end_freedom,
2885 },
2886 surface: segment.surface.clone(),
2887 sketch_id: segment.sketch_id,
2888 sketch: segment.sketch.clone(),
2889 tag: segment.tag.clone(),
2890 node_path: segment.node_path.clone(),
2891 meta: segment.meta.clone(),
2892 }),
2893 },
2894 meta: segment.meta.clone(),
2895 }),
2896 }
2897 .continue_()),
2898 SegmentKind::Arc {
2899 end,
2900 ctor,
2901 end_object_id,
2902 end_freedom,
2903 ..
2904 } => Ok(KclValue::Segment {
2905 value: Box::new(AbstractSegment {
2906 repr: SegmentRepr::Solved {
2907 segment: Box::new(Segment {
2908 id: segment.id,
2909 object_id: *end_object_id,
2910 kind: SegmentKind::Point {
2911 position: end.clone(),
2912 ctor: Box::new(PointCtor {
2913 position: ctor.end.clone(),
2914 }),
2915 freedom: *end_freedom,
2916 },
2917 surface: segment.surface.clone(),
2918 sketch_id: segment.sketch_id,
2919 sketch: segment.sketch.clone(),
2920 tag: segment.tag.clone(),
2921 node_path: segment.node_path.clone(),
2922 meta: segment.meta.clone(),
2923 }),
2924 },
2925 meta: segment.meta.clone(),
2926 }),
2927 }
2928 .continue_()),
2929 SegmentKind::Circle { .. } => Err(KclError::new_undefined_value(
2930 KclErrorDetails::new(
2931 format!("Property '{property}' not found in segment"),
2932 vec![self.into()],
2933 ),
2934 None,
2935 )),
2936 SegmentKind::ControlPointSpline { .. } => Err(KclError::new_undefined_value(
2937 KclErrorDetails::new(
2938 format!("Property '{property}' not found in segment"),
2939 vec![self.clone().into()],
2940 ),
2941 None,
2942 )),
2943 },
2944 },
2945 "center" => match &segment.repr {
2946 SegmentRepr::Unsolved { segment } => match &segment.kind {
2947 UnsolvedSegmentKind::Arc {
2948 center,
2949 ctor,
2950 center_object_id,
2951 ..
2952 } => Ok(KclValue::Segment {
2953 value: Box::new(AbstractSegment {
2954 repr: SegmentRepr::Unsolved {
2955 segment: Box::new(UnsolvedSegment {
2956 id: segment.id,
2957 object_id: *center_object_id,
2958 kind: UnsolvedSegmentKind::Point {
2959 position: center.clone(),
2960 ctor: Box::new(PointCtor {
2961 position: ctor.center.clone(),
2962 }),
2963 },
2964 tag: segment.tag.clone(),
2965 node_path: segment.node_path.clone(),
2966 meta: segment.meta.clone(),
2967 }),
2968 },
2969 meta: segment.meta.clone(),
2970 }),
2971 }
2972 .continue_()),
2973 UnsolvedSegmentKind::Circle {
2974 center,
2975 ctor,
2976 center_object_id,
2977 ..
2978 } => Ok(KclValue::Segment {
2979 value: Box::new(AbstractSegment {
2980 repr: SegmentRepr::Unsolved {
2981 segment: Box::new(UnsolvedSegment {
2982 id: segment.id,
2983 object_id: *center_object_id,
2984 kind: UnsolvedSegmentKind::Point {
2985 position: center.clone(),
2986 ctor: Box::new(PointCtor {
2987 position: ctor.center.clone(),
2988 }),
2989 },
2990 tag: segment.tag.clone(),
2991 node_path: segment.node_path.clone(),
2992 meta: segment.meta.clone(),
2993 }),
2994 },
2995 meta: segment.meta.clone(),
2996 }),
2997 }
2998 .continue_()),
2999 _ => Err(KclError::new_undefined_value(
3000 KclErrorDetails::new(
3001 format!("Property '{property}' not found in segment"),
3002 vec![self.clone().into()],
3003 ),
3004 None,
3005 )),
3006 },
3007 SegmentRepr::Solved { segment } => match &segment.kind {
3008 SegmentKind::Arc {
3009 center,
3010 ctor,
3011 center_object_id,
3012 center_freedom,
3013 ..
3014 } => Ok(KclValue::Segment {
3015 value: Box::new(AbstractSegment {
3016 repr: SegmentRepr::Solved {
3017 segment: Box::new(Segment {
3018 id: segment.id,
3019 object_id: *center_object_id,
3020 kind: SegmentKind::Point {
3021 position: center.clone(),
3022 ctor: Box::new(PointCtor {
3023 position: ctor.center.clone(),
3024 }),
3025 freedom: *center_freedom,
3026 },
3027 surface: segment.surface.clone(),
3028 sketch_id: segment.sketch_id,
3029 sketch: segment.sketch.clone(),
3030 tag: segment.tag.clone(),
3031 node_path: segment.node_path.clone(),
3032 meta: segment.meta.clone(),
3033 }),
3034 },
3035 meta: segment.meta.clone(),
3036 }),
3037 }
3038 .continue_()),
3039 SegmentKind::Circle {
3040 center,
3041 ctor,
3042 center_object_id,
3043 center_freedom,
3044 ..
3045 } => Ok(KclValue::Segment {
3046 value: Box::new(AbstractSegment {
3047 repr: SegmentRepr::Solved {
3048 segment: Box::new(Segment {
3049 id: segment.id,
3050 object_id: *center_object_id,
3051 kind: SegmentKind::Point {
3052 position: center.clone(),
3053 ctor: Box::new(PointCtor {
3054 position: ctor.center.clone(),
3055 }),
3056 freedom: *center_freedom,
3057 },
3058 surface: segment.surface.clone(),
3059 sketch_id: segment.sketch_id,
3060 sketch: segment.sketch.clone(),
3061 tag: segment.tag.clone(),
3062 node_path: segment.node_path.clone(),
3063 meta: segment.meta.clone(),
3064 }),
3065 },
3066 meta: segment.meta.clone(),
3067 }),
3068 }
3069 .continue_()),
3070 _ => Err(KclError::new_undefined_value(
3071 KclErrorDetails::new(
3072 format!("Property '{property}' not found in segment"),
3073 vec![self.clone().into()],
3074 ),
3075 None,
3076 )),
3077 },
3078 },
3079 "controls" => match &segment.repr {
3080 SegmentRepr::Unsolved { segment } => match &segment.kind {
3081 UnsolvedSegmentKind::ControlPointSpline {
3082 controls,
3083 ctor,
3084 control_object_ids,
3085 ..
3086 } => Ok(KclValue::HomArray {
3087 value: controls
3088 .iter()
3089 .zip(control_object_ids.iter())
3090 .zip(ctor.points.iter())
3091 .map(|((position, object_id), ctor_point)| KclValue::Segment {
3092 value: Box::new(AbstractSegment {
3093 repr: SegmentRepr::Unsolved {
3094 segment: Box::new(UnsolvedSegment {
3095 id: segment.id,
3096 object_id: *object_id,
3097 kind: UnsolvedSegmentKind::Point {
3098 position: position.clone(),
3099 ctor: Box::new(PointCtor {
3100 position: ctor_point.clone(),
3101 }),
3102 },
3103 tag: segment.tag.clone(),
3104 node_path: segment.node_path.clone(),
3105 meta: segment.meta.clone(),
3106 }),
3107 },
3108 meta: segment.meta.clone(),
3109 }),
3110 })
3111 .collect(),
3112 ty: RuntimeType::segment(),
3113 }
3114 .continue_()),
3115 _ => Err(KclError::new_undefined_value(
3116 KclErrorDetails::new(
3117 format!("Property '{property}' not found in segment"),
3118 vec![self.clone().into()],
3119 ),
3120 None,
3121 )),
3122 },
3123 SegmentRepr::Solved { segment } => match &segment.kind {
3124 SegmentKind::ControlPointSpline {
3125 controls,
3126 ctor,
3127 control_object_ids,
3128 control_freedoms,
3129 ..
3130 } => Ok(KclValue::HomArray {
3131 value: controls
3132 .iter()
3133 .zip(control_object_ids.iter())
3134 .zip(control_freedoms.iter())
3135 .zip(ctor.points.iter())
3136 .map(|(((position, object_id), freedom), ctor_point)| KclValue::Segment {
3137 value: Box::new(AbstractSegment {
3138 repr: SegmentRepr::Solved {
3139 segment: Box::new(Segment {
3140 id: segment.id,
3141 object_id: *object_id,
3142 kind: SegmentKind::Point {
3143 position: position.clone(),
3144 ctor: Box::new(PointCtor {
3145 position: ctor_point.clone(),
3146 }),
3147 freedom: *freedom,
3148 },
3149 surface: segment.surface.clone(),
3150 sketch_id: segment.sketch_id,
3151 sketch: segment.sketch.clone(),
3152 tag: segment.tag.clone(),
3153 node_path: segment.node_path.clone(),
3154 meta: segment.meta.clone(),
3155 }),
3156 },
3157 meta: segment.meta.clone(),
3158 }),
3159 })
3160 .collect(),
3161 ty: RuntimeType::segment(),
3162 }
3163 .continue_()),
3164 _ => Err(KclError::new_undefined_value(
3165 KclErrorDetails::new(
3166 format!("Property '{property}' not found in segment"),
3167 vec![self.clone().into()],
3168 ),
3169 None,
3170 )),
3171 },
3172 },
3173 "edges" => match &segment.repr {
3174 SegmentRepr::Unsolved { segment } => match &segment.kind {
3175 UnsolvedSegmentKind::ControlPointSpline {
3176 controls,
3177 ctor,
3178 control_object_ids,
3179 control_polygon_edge_object_ids,
3180 construction,
3181 ..
3182 } => Ok(KclValue::HomArray {
3183 value: control_polygon_edge_object_ids
3184 .iter()
3185 .enumerate()
3186 .map(|(index, object_id)| KclValue::Segment {
3187 value: Box::new(AbstractSegment {
3188 repr: SegmentRepr::Unsolved {
3189 segment: Box::new(UnsolvedSegment {
3190 id: segment.id,
3191 object_id: *object_id,
3192 kind: UnsolvedSegmentKind::Line {
3193 start: controls[index].clone(),
3194 end: controls[index + 1].clone(),
3195 ctor: Box::new(LineCtor {
3196 start: ctor.points[index].clone(),
3197 end: ctor.points[index + 1].clone(),
3198 construction: Some(*construction),
3199 }),
3200 start_object_id: control_object_ids[index],
3201 end_object_id: control_object_ids[index + 1],
3202 construction: *construction,
3203 },
3204 tag: segment.tag.clone(),
3205 node_path: segment.node_path.clone(),
3206 meta: segment.meta.clone(),
3207 }),
3208 },
3209 meta: segment.meta.clone(),
3210 }),
3211 })
3212 .collect(),
3213 ty: RuntimeType::segment(),
3214 }
3215 .continue_()),
3216 _ => Err(KclError::new_undefined_value(
3217 KclErrorDetails::new(
3218 format!("Property '{property}' not found in segment"),
3219 vec![self.clone().into()],
3220 ),
3221 None,
3222 )),
3223 },
3224 SegmentRepr::Solved { segment } => match &segment.kind {
3225 SegmentKind::ControlPointSpline {
3226 controls,
3227 ctor,
3228 control_object_ids,
3229 control_polygon_edge_object_ids,
3230 control_freedoms,
3231 construction,
3232 ..
3233 } => Ok(KclValue::HomArray {
3234 value: control_polygon_edge_object_ids
3235 .iter()
3236 .enumerate()
3237 .map(|(index, object_id)| KclValue::Segment {
3238 value: Box::new(AbstractSegment {
3239 repr: SegmentRepr::Solved {
3240 segment: Box::new(Segment {
3241 id: segment.id,
3242 object_id: *object_id,
3243 kind: SegmentKind::Line {
3244 start: controls[index].clone(),
3245 end: controls[index + 1].clone(),
3246 ctor: Box::new(LineCtor {
3247 start: ctor.points[index].clone(),
3248 end: ctor.points[index + 1].clone(),
3249 construction: Some(*construction),
3250 }),
3251 start_object_id: control_object_ids[index],
3252 end_object_id: control_object_ids[index + 1],
3253 start_freedom: control_freedoms[index],
3254 end_freedom: control_freedoms[index + 1],
3255 construction: *construction,
3256 },
3257 surface: segment.surface.clone(),
3258 sketch_id: segment.sketch_id,
3259 sketch: segment.sketch.clone(),
3260 tag: segment.tag.clone(),
3261 node_path: segment.node_path.clone(),
3262 meta: segment.meta.clone(),
3263 }),
3264 },
3265 meta: segment.meta.clone(),
3266 }),
3267 })
3268 .collect(),
3269 ty: RuntimeType::segment(),
3270 }
3271 .continue_()),
3272 _ => Err(KclError::new_undefined_value(
3273 KclErrorDetails::new(
3274 format!("Property '{property}' not found in segment"),
3275 vec![self.clone().into()],
3276 ),
3277 None,
3278 )),
3279 },
3280 },
3281 other => Err(KclError::new_undefined_value(
3282 KclErrorDetails::new(
3283 format!("Property '{other}' not found in segment"),
3284 vec![self.clone().into()],
3285 ),
3286 None,
3287 )),
3288 },
3289 (KclValue::Plane { value: plane }, Property::String(property), false) => match property.as_str() {
3290 "zAxis" => {
3291 let (p, u) = plane.info.z_axis.as_3_dims();
3292 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
3293 }
3294 "yAxis" => {
3295 let (p, u) = plane.info.y_axis.as_3_dims();
3296 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
3297 }
3298 "xAxis" => {
3299 let (p, u) = plane.info.x_axis.as_3_dims();
3300 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
3301 }
3302 "origin" => {
3303 let (p, u) = plane.info.origin.as_3_dims();
3304 Ok(KclValue::array_from_point3d(p, NumericType::optional_length(u), vec![meta]).continue_())
3305 }
3306 other => Err(KclError::new_undefined_value(
3307 KclErrorDetails::new(
3308 format!("Property '{other}' not found in plane"),
3309 vec![self.clone().into()],
3310 ),
3311 None,
3312 )),
3313 },
3314 (
3315 KclValue::Object {
3316 value: map,
3317 object_kind,
3318 ..
3319 },
3320 Property::String(property),
3321 false,
3322 ) => {
3323 if let Some(value) = map.get(&property) {
3324 if object_kind
3325 .deprecated_solid_tag_names()
3326 .iter()
3327 .any(|tag_name| tag_name == &property)
3328 {
3329 exec_state.warn(
3330 CompilationIssue::err(
3331 SourceRange::from(self),
3332 format!(
3333 "Accessing solid-created face `{property}` through sketch tags is deprecated. Use the body's faces instead, e.g. `body.faces.{property}`."
3334 ),
3335 ),
3336 annotations::WARN_DEPRECATED,
3337 );
3338 }
3339 Ok(value.to_owned().continue_())
3340 } else {
3341 Err(KclError::new_undefined_value(
3342 KclErrorDetails::new(
3343 format!("Property '{property}' not found in object"),
3344 vec![self.clone().into()],
3345 ),
3346 None,
3347 ))
3348 }
3349 }
3350 (KclValue::Object { .. }, Property::String(property), true) => {
3351 Err(KclError::new_semantic(KclErrorDetails::new(
3352 format!("Cannot index object with string; use dot notation instead, e.g. `obj.{property}`"),
3353 vec![self.clone().into()],
3354 )))
3355 }
3356 (KclValue::Object { value: map, .. }, p @ Property::UInt(i), _) => {
3357 if i == 0
3358 && let Some(value) = map.get("x")
3359 {
3360 return Ok(value.to_owned().continue_());
3361 }
3362 if i == 1
3363 && let Some(value) = map.get("y")
3364 {
3365 return Ok(value.to_owned().continue_());
3366 }
3367 if i == 2
3368 && let Some(value) = map.get("z")
3369 {
3370 return Ok(value.to_owned().continue_());
3371 }
3372 let t = p.type_name();
3373 let article = article_for(t);
3374 Err(KclError::new_semantic(KclErrorDetails::new(
3375 format!("Only strings can be used as the property of an object, but you're using {article} {t}",),
3376 vec![self.clone().into()],
3377 )))
3378 }
3379 (KclValue::HomArray { value: arr, .. }, Property::UInt(index), _) => {
3380 let value_of_arr = arr.get(index);
3381 let oob_error = KclError::new_undefined_value(
3383 KclErrorDetails::new(
3384 format!("The array doesn't have any item at index {index}"),
3385 vec![self.clone().into()],
3386 ),
3387 None,
3388 );
3389 if let Some(value) = value_of_arr {
3390 Ok(value.to_owned().continue_())
3392 } else if ctx.no_engine_commands().await && !exec_state.is_sketch_mode_execution() {
3393 let value = arr.first();
3403 value.map(|value| value.to_owned().continue_()).ok_or(oob_error)
3404 } else {
3405 Err(oob_error)
3406 }
3407 }
3408 (obj, Property::UInt(0), _) => Ok(obj.continue_()),
3411 (KclValue::HomArray { .. }, p, _) => {
3412 let t = p.type_name();
3413 let article = article_for(t);
3414 Err(KclError::new_semantic(KclErrorDetails::new(
3415 format!("Only integers >= 0 can be used as the index of an array, but you're using {article} {t}",),
3416 vec![self.clone().into()],
3417 )))
3418 }
3419 (KclValue::Solid { value }, Property::String(prop), false) if prop == "sketch" => {
3420 let Some(sketch) = value.sketch() else {
3421 return Err(KclError::new_semantic(KclErrorDetails::new(
3422 "This solid was created without a sketch, so `solid.sketch` is unavailable.".to_owned(),
3423 vec![self.clone().into()],
3424 )));
3425 };
3426 Ok(KclValue::Sketch {
3427 value: Box::new(sketch.clone()),
3428 }
3429 .continue_())
3430 }
3431 (KclValue::Solid { value: solid }, Property::String(prop), false) if prop == "faces" => {
3432 Ok(KclValue::Object {
3433 meta: vec![Metadata {
3434 source_range: SourceRange::from(self.clone()),
3435 }],
3436 value: solid
3437 .faces
3438 .iter()
3439 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
3440 .collect(),
3441 constrainable: false,
3442 object_kind: KclObjectKind::Default,
3443 }
3444 .continue_())
3445 }
3446 (geometry @ KclValue::Solid { .. }, Property::String(prop), false) if prop == "tags" => {
3447 Err(KclError::new_semantic(KclErrorDetails::new(
3449 format!(
3450 "Property `{prop}` not found on {}. You can get a solid's faces through `exampleSolid.faces`, or its sketch tags through `exampleSolid.sketch.tags`.",
3451 geometry.human_friendly_type()
3452 ),
3453 vec![self.clone().into()],
3454 )))
3455 }
3456 (KclValue::Sketch { value: sk }, Property::String(prop), false) if prop == "tags" => Ok(KclValue::Object {
3457 meta: vec![Metadata {
3458 source_range: SourceRange::from(self.clone()),
3459 }],
3460 value: sk
3461 .tags
3462 .iter()
3463 .map(|(k, tag)| (k.to_owned(), KclValue::TagIdentifier(Box::new(tag.to_owned()))))
3464 .collect(),
3465 constrainable: false,
3466 object_kind: KclObjectKind::SketchTags {
3467 deprecated_solid_tag_names: sk
3468 .tags
3469 .iter()
3470 .filter(|(_, tag)| tag.is_body_created_tag())
3471 .map(|(name, _)| name.to_owned())
3472 .collect(),
3473 },
3474 }
3475 .continue_()),
3476 (geometry @ (KclValue::Sketch { .. } | KclValue::Solid { .. }), Property::String(property), false) => {
3477 Err(KclError::new_semantic(KclErrorDetails::new(
3478 format!("Property `{property}` not found on {}", geometry.human_friendly_type()),
3479 vec![self.clone().into()],
3480 )))
3481 }
3482 (being_indexed, _, false) => Err(KclError::new_semantic(KclErrorDetails::new(
3483 format!(
3484 "Only objects can have members accessed with dot notation, but you're trying to access {}",
3485 being_indexed.human_friendly_type()
3486 ),
3487 vec![self.clone().into()],
3488 ))),
3489 (being_indexed, _, true) => Err(KclError::new_semantic(KclErrorDetails::new(
3490 format!(
3491 "Only arrays can be indexed, but you're trying to index {}",
3492 being_indexed.human_friendly_type()
3493 ),
3494 vec![self.clone().into()],
3495 ))),
3496 }
3497 }
3498}
3499
3500impl Node<BinaryExpression> {
3501 pub(super) async fn get_result(
3502 &self,
3503 exec_state: &mut ExecState,
3504 ctx: &ExecutorContext,
3505 ) -> Result<KclValueControlFlow, KclError> {
3506 enum State {
3507 EvaluateLeft(Node<BinaryExpression>),
3508 FromLeft {
3509 node: Node<BinaryExpression>,
3510 },
3511 EvaluateRight {
3512 node: Node<BinaryExpression>,
3513 left: KclValue,
3514 },
3515 FromRight {
3516 node: Node<BinaryExpression>,
3517 left: KclValue,
3518 },
3519 }
3520
3521 let mut stack = vec![State::EvaluateLeft(self.clone())];
3522 let mut last_result: Option<KclValue> = None;
3523
3524 while let Some(state) = stack.pop() {
3525 match state {
3526 State::EvaluateLeft(node) => {
3527 let left_part = node.left.clone();
3528 match left_part {
3529 BinaryPart::BinaryExpression(child) => {
3530 stack.push(State::FromLeft { node });
3531 stack.push(State::EvaluateLeft(*child));
3532 }
3533 part => {
3534 let left_value = part.get_result(exec_state, ctx).await?;
3535 let left_value = control_continue!(left_value);
3536 stack.push(State::EvaluateRight { node, left: left_value });
3537 }
3538 }
3539 }
3540 State::FromLeft { node } => {
3541 let Some(left_value) = last_result.take() else {
3542 return Err(Self::missing_result_error(&node));
3543 };
3544 stack.push(State::EvaluateRight { node, left: left_value });
3545 }
3546 State::EvaluateRight { node, left } => {
3547 let right_part = node.right.clone();
3548 match right_part {
3549 BinaryPart::BinaryExpression(child) => {
3550 stack.push(State::FromRight { node, left });
3551 stack.push(State::EvaluateLeft(*child));
3552 }
3553 part => {
3554 let right_value = part.get_result(exec_state, ctx).await?;
3555 let right_value = control_continue!(right_value);
3556 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3557 last_result = Some(result);
3558 }
3559 }
3560 }
3561 State::FromRight { node, left } => {
3562 let Some(right_value) = last_result.take() else {
3563 return Err(Self::missing_result_error(&node));
3564 };
3565 let result = node.apply_operator(exec_state, ctx, left, right_value).await?;
3566 last_result = Some(result);
3567 }
3568 }
3569 }
3570
3571 last_result
3572 .map(KclValue::continue_)
3573 .ok_or_else(|| Self::missing_result_error(self))
3574 }
3575
3576 async fn apply_operator(
3577 &self,
3578 exec_state: &mut ExecState,
3579 ctx: &ExecutorContext,
3580 left_value: KclValue,
3581 right_value: KclValue,
3582 ) -> Result<KclValue, KclError> {
3583 let mut meta = left_value.metadata();
3584 meta.extend(right_value.metadata());
3585
3586 if self.operator == BinaryOperator::Add
3588 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
3589 (&left_value, &right_value)
3590 {
3591 return Ok(KclValue::String {
3592 value: format!("{left}{right}"),
3593 meta,
3594 });
3595 }
3596
3597 if self.operator == BinaryOperator::Add || self.operator == BinaryOperator::Or {
3599 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3600 let args = Args::new_no_args(
3601 self.into(),
3602 self.node_path.clone(),
3603 ctx.clone(),
3604 Some("union".to_owned()),
3605 );
3606 let result = crate::std::csg::inner_union(
3607 vec![*left.clone(), *right.clone()],
3608 Default::default(),
3609 crate::std::csg::CsgAlgorithm::Latest,
3610 exec_state,
3611 args,
3612 )
3613 .await?;
3614 return Ok(result.into());
3615 }
3616 } else if self.operator == BinaryOperator::Sub {
3617 if let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value) {
3619 let args = Args::new_no_args(
3620 self.into(),
3621 self.node_path.clone(),
3622 ctx.clone(),
3623 Some("subtract".to_owned()),
3624 );
3625 let result = crate::std::csg::inner_subtract(
3626 vec![*left.clone()],
3627 vec![*right.clone()],
3628 Default::default(),
3629 crate::std::csg::CsgAlgorithm::Latest,
3630 exec_state,
3631 args,
3632 )
3633 .await?;
3634 return Ok(result.into());
3635 }
3636 } else if self.operator == BinaryOperator::And
3637 && let (KclValue::Solid { value: left }, KclValue::Solid { value: right }) = (&left_value, &right_value)
3638 {
3639 let args = Args::new_no_args(
3641 self.into(),
3642 self.node_path.clone(),
3643 ctx.clone(),
3644 Some("intersect".to_owned()),
3645 );
3646 let result = crate::std::csg::inner_intersect(
3647 vec![*left.clone(), *right.clone()],
3648 Default::default(),
3649 crate::std::csg::CsgAlgorithm::Latest,
3650 exec_state,
3651 args,
3652 )
3653 .await?;
3654 return Ok(result.into());
3655 }
3656
3657 if self.operator == BinaryOperator::Or || self.operator == BinaryOperator::And {
3659 let KclValue::Bool { value: left_value, .. } = left_value else {
3660 return Err(KclError::new_semantic(KclErrorDetails::new(
3661 format!(
3662 "Cannot apply logical operator to non-boolean value: {}",
3663 left_value.human_friendly_type()
3664 ),
3665 vec![self.left.clone().into()],
3666 )));
3667 };
3668 let KclValue::Bool { value: right_value, .. } = right_value else {
3669 return Err(KclError::new_semantic(KclErrorDetails::new(
3670 format!(
3671 "Cannot apply logical operator to non-boolean value: {}",
3672 right_value.human_friendly_type()
3673 ),
3674 vec![self.right.clone().into()],
3675 )));
3676 };
3677 let raw_value = match self.operator {
3678 BinaryOperator::Or => left_value || right_value,
3679 BinaryOperator::And => left_value && right_value,
3680 _ => unreachable!(),
3681 };
3682 return Ok(KclValue::Bool { value: raw_value, meta });
3683 }
3684
3685 if self.operator == BinaryOperator::Eq && exec_state.mod_local.sketch_block.is_some() {
3687 match (&left_value, &right_value) {
3688 (KclValue::SketchVar { value: left_value, .. }, KclValue::SketchVar { value: right_value, .. })
3690 if left_value.id == right_value.id =>
3691 {
3692 return Ok(KclValue::none());
3693 }
3694 (KclValue::SketchVar { value: var0 }, KclValue::SketchVar { value: var1, .. }) => {
3696 let constraint = Constraint::ScalarEqual(
3697 var0.id.to_constraint_id(self.as_source_range())?,
3698 var1.id.to_constraint_id(self.as_source_range())?,
3699 );
3700 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3701 let message = "Being inside a sketch block should have already been checked above".to_owned();
3702 debug_assert!(false, "{}", &message);
3703 return Err(internal_err(message, self));
3704 };
3705 sketch_block_state.solver_constraints.push(constraint);
3706 return Ok(KclValue::none());
3707 }
3708 (KclValue::SketchVar { value: var, .. }, input_number @ KclValue::Number { .. })
3710 | (input_number @ KclValue::Number { .. }, KclValue::SketchVar { value: var, .. }) => {
3711 let number_value = normalize_to_solver_distance_unit(
3712 input_number,
3713 input_number.into(),
3714 exec_state,
3715 "fixed constraint value",
3716 )?;
3717 let Some(n) = number_value.as_ty_f64() else {
3718 let message = format!(
3719 "Expected number after coercion, but found {}",
3720 number_value.human_friendly_type()
3721 );
3722 debug_assert!(false, "{}", &message);
3723 return Err(internal_err(message, self));
3724 };
3725 let constraint = Constraint::Fixed(var.id.to_constraint_id(self.as_source_range())?, n.n);
3726 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3727 let message = "Being inside a sketch block should have already been checked above".to_owned();
3728 debug_assert!(false, "{}", &message);
3729 return Err(internal_err(message, self));
3730 };
3731 sketch_block_state.solver_constraints.push(constraint);
3732 exec_state.warn_experimental("scalar fixed constraint", self.as_source_range());
3733 return Ok(KclValue::none());
3734 }
3735 (KclValue::SketchConstraint { value: constraint }, input_number @ KclValue::Number { .. })
3737 | (input_number @ KclValue::Number { .. }, KclValue::SketchConstraint { value: constraint }) => {
3738 let number_value = match constraint.kind {
3739 SketchConstraintKind::Angle { .. } => normalize_to_solver_angle_unit(
3741 input_number,
3742 input_number.into(),
3743 exec_state,
3744 "fixed constraint value",
3745 )?,
3746 SketchConstraintKind::Distance { .. }
3748 | SketchConstraintKind::PointLineDistance { .. }
3749 | SketchConstraintKind::LineLineDistance { .. }
3750 | SketchConstraintKind::PointCircularDistance { .. }
3751 | SketchConstraintKind::LineCircularDistance { .. }
3752 | SketchConstraintKind::CircularCircularDistance { .. }
3753 | SketchConstraintKind::Radius { .. }
3754 | SketchConstraintKind::Diameter { .. }
3755 | SketchConstraintKind::HorizontalDistance { .. }
3756 | SketchConstraintKind::VerticalDistance { .. } => normalize_to_solver_distance_unit(
3757 input_number,
3758 input_number.into(),
3759 exec_state,
3760 "fixed constraint value",
3761 )?,
3762 };
3763 let Some(n) = number_value.as_ty_f64() else {
3764 let message = format!(
3765 "Expected number after coercion, but found {}",
3766 number_value.human_friendly_type()
3767 );
3768 debug_assert!(false, "{}", &message);
3769 return Err(internal_err(message, self));
3770 };
3771 let number_binary_part = if matches!(&left_value, KclValue::SketchConstraint { .. }) {
3773 &self.right
3774 } else {
3775 &self.left
3776 };
3777 let source = {
3778 use crate::unparser::ExprContext;
3779 let mut buf = String::new();
3780 number_binary_part.recast(&mut buf, &Default::default(), 0, ExprContext::Other);
3781 crate::frontend::sketch::ConstraintSource {
3782 expr: buf,
3783 is_literal: matches!(number_binary_part, BinaryPart::Literal(_)),
3784 }
3785 };
3786
3787 match &constraint.kind {
3788 SketchConstraintKind::Angle { line0, line1 } => {
3789 let range = self.as_source_range();
3790 let ax = line0.vars[0].x.to_constraint_id(range)?;
3793 let ay = line0.vars[0].y.to_constraint_id(range)?;
3794 let bx = line0.vars[1].x.to_constraint_id(range)?;
3795 let by = line0.vars[1].y.to_constraint_id(range)?;
3796 let cx = line1.vars[0].x.to_constraint_id(range)?;
3797 let cy = line1.vars[0].y.to_constraint_id(range)?;
3798 let dx = line1.vars[1].x.to_constraint_id(range)?;
3799 let dy = line1.vars[1].y.to_constraint_id(range)?;
3800 let solver_line0 = ezpz::datatypes::inputs::DatumLineSegment::new(
3801 ezpz::datatypes::inputs::DatumPoint::new_xy(ax, ay),
3802 ezpz::datatypes::inputs::DatumPoint::new_xy(bx, by),
3803 );
3804 let solver_line1 = ezpz::datatypes::inputs::DatumLineSegment::new(
3805 ezpz::datatypes::inputs::DatumPoint::new_xy(cx, cy),
3806 ezpz::datatypes::inputs::DatumPoint::new_xy(dx, dy),
3807 );
3808 let desired_angle = match n.ty {
3809 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Degrees))
3810 | NumericType::Default {
3811 len: _,
3812 angle: UnitAngle::Degrees,
3813 } => ezpz::datatypes::Angle::from_degrees(n.n),
3814 NumericType::Known(crate::exec::UnitType::Angle(crate::exec::UnitAngle::Radians))
3815 | NumericType::Default {
3816 len: _,
3817 angle: UnitAngle::Radians,
3818 } => ezpz::datatypes::Angle::from_radians(n.n),
3819 NumericType::Known(crate::exec::UnitType::Count)
3820 | NumericType::Known(crate::exec::UnitType::GenericLength)
3821 | NumericType::Known(crate::exec::UnitType::GenericAngle)
3822 | NumericType::Known(crate::exec::UnitType::Length(_))
3823 | NumericType::Unknown
3824 | NumericType::Any => {
3825 let message = format!("Expected angle but found {:?}", n);
3826 debug_assert!(false, "{}", &message);
3827 return Err(internal_err(message, self));
3828 }
3829 };
3830 let solver_constraint = Constraint::LinesAtAngle(
3831 solver_line0,
3832 solver_line1,
3833 ezpz::datatypes::AngleKind::Other(desired_angle),
3834 );
3835 let constraint_id = exec_state.next_object_id();
3836 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3837 let message =
3838 "Being inside a sketch block should have already been checked above".to_owned();
3839 debug_assert!(false, "{}", &message);
3840 return Err(internal_err(message, self));
3841 };
3842 sketch_block_state.solver_constraints.push(solver_constraint);
3843 use crate::execution::Artifact;
3844 use crate::execution::CodeRef;
3845 use crate::execution::SketchBlockConstraint;
3846 use crate::execution::SketchBlockConstraintType;
3847 use crate::front::Angle;
3848 use crate::front::SourceRef;
3849
3850 let Some(sketch_id) = sketch_block_state.sketch_id else {
3851 let message = "Sketch id missing for constraint artifact".to_owned();
3852 debug_assert!(false, "{}", &message);
3853 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3854 };
3855 let sketch_constraint = crate::front::Constraint::Angle(Angle {
3856 lines: vec![line0.object_id, line1.object_id],
3857 angle: n.try_into().map_err(|_| {
3858 internal_err("Failed to convert angle units numeric suffix:", range)
3859 })?,
3860 source,
3861 });
3862 sketch_block_state.sketch_constraints.push(constraint_id);
3863 let artifact_id = exec_state.next_artifact_id();
3864 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
3865 id: artifact_id,
3866 sketch_id,
3867 constraint_id,
3868 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
3869 code_ref: CodeRef::placeholder(range),
3870 }));
3871 exec_state.add_scene_object(
3872 Object {
3873 id: constraint_id,
3874 kind: ObjectKind::Constraint {
3875 constraint: sketch_constraint,
3876 },
3877 label: Default::default(),
3878 comments: Default::default(),
3879 artifact_id,
3880 source: SourceRef::new(range, self.node_path.clone()),
3881 },
3882 range,
3883 );
3884 }
3885 SketchConstraintKind::Distance { points, label_position } => {
3886 let range = self.as_source_range();
3887 let p0 = &points[0];
3888 let p1 = &points[1];
3889 let sketch_var_ty = solver_numeric_type(exec_state);
3890 let constraint_id = exec_state.next_object_id();
3891 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
3892 let message =
3893 "Being inside a sketch block should have already been checked above".to_owned();
3894 debug_assert!(false, "{}", &message);
3895 return Err(internal_err(message, self));
3896 };
3897 match (p0, p1) {
3898 (
3899 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
3900 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
3901 ) => {
3902 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3903 p0.vars.x.to_constraint_id(range)?,
3904 p0.vars.y.to_constraint_id(range)?,
3905 );
3906 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
3907 p1.vars.x.to_constraint_id(range)?,
3908 p1.vars.y.to_constraint_id(range)?,
3909 );
3910 sketch_block_state
3911 .solver_constraints
3912 .push(Constraint::Distance(solver_pt0, solver_pt1, n.n));
3913 }
3914 (
3915 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3916 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3917 )
3918 | (
3919 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3920 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
3921 ) => {
3922 let origin_x_id = sketch_block_state.next_sketch_var_id();
3923 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3924 value: Box::new(crate::execution::SketchVar {
3925 id: origin_x_id,
3926 initial_value: 0.0,
3927 ty: sketch_var_ty,
3928 node_path: None,
3930 meta: vec![],
3931 }),
3932 });
3933 let origin_y_id = sketch_block_state.next_sketch_var_id();
3934 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
3935 value: Box::new(crate::execution::SketchVar {
3936 id: origin_y_id,
3937 initial_value: 0.0,
3938 ty: sketch_var_ty,
3939 node_path: None,
3941 meta: vec![],
3942 }),
3943 });
3944 let origin_x = origin_x_id.to_constraint_id(range)?;
3945 let origin_y = origin_y_id.to_constraint_id(range)?;
3946 sketch_block_state
3947 .solver_constraints
3948 .push(Constraint::Fixed(origin_x, 0.0));
3949 sketch_block_state
3950 .solver_constraints
3951 .push(Constraint::Fixed(origin_y, 0.0));
3952 let solver_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
3953 point.vars.x.to_constraint_id(range)?,
3954 point.vars.y.to_constraint_id(range)?,
3955 );
3956 let origin_point = ezpz::datatypes::inputs::DatumPoint::new_xy(origin_x, origin_y);
3957 sketch_block_state.solver_constraints.push(Constraint::Distance(
3958 solver_point,
3959 origin_point,
3960 n.n,
3961 ));
3962 }
3963 (
3964 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3965 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
3966 ) => {
3967 return Err(internal_err(
3968 "distance() cannot constrain ORIGIN against ORIGIN".to_owned(),
3969 range,
3970 ));
3971 }
3972 }
3973 use crate::execution::Artifact;
3974 use crate::execution::CodeRef;
3975 use crate::execution::SketchBlockConstraint;
3976 use crate::execution::SketchBlockConstraintType;
3977 use crate::front::Distance;
3978 use crate::front::SourceRef;
3979 use crate::frontend::sketch::ConstraintSegment;
3980
3981 let Some(sketch_id) = sketch_block_state.sketch_id else {
3982 let message = "Sketch id missing for constraint artifact".to_owned();
3983 debug_assert!(false, "{}", &message);
3984 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
3985 };
3986 let sketch_constraint = crate::front::Constraint::Distance(Distance {
3987 points: vec![
3988 match p0 {
3989 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3990 ConstraintSegment::from(point.object_id)
3991 }
3992 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
3993 ConstraintSegment::ORIGIN
3994 }
3995 },
3996 match p1 {
3997 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
3998 ConstraintSegment::from(point.object_id)
3999 }
4000 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
4001 ConstraintSegment::ORIGIN
4002 }
4003 },
4004 ],
4005 distance: n.try_into().map_err(|_| {
4006 internal_err("Failed to convert distance units numeric suffix:", range)
4007 })?,
4008 label_position: label_position.clone(),
4009 source,
4010 });
4011 sketch_block_state.sketch_constraints.push(constraint_id);
4012 let artifact_id = exec_state.next_artifact_id();
4013 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4014 id: artifact_id,
4015 sketch_id,
4016 constraint_id,
4017 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4018 code_ref: CodeRef::placeholder(range),
4019 }));
4020 exec_state.add_scene_object(
4021 Object {
4022 id: constraint_id,
4023 kind: ObjectKind::Constraint {
4024 constraint: sketch_constraint,
4025 },
4026 label: Default::default(),
4027 comments: Default::default(),
4028 artifact_id,
4029 source: SourceRef::new(range, self.node_path.clone()),
4030 },
4031 range,
4032 );
4033 }
4034 SketchConstraintKind::PointLineDistance {
4035 point,
4036 line,
4037 input_object_ids,
4038 label_position,
4039 } => {
4040 let range = self.as_source_range();
4041 let sketch_var_ty = solver_numeric_type(exec_state);
4042 let sketch_vars = exec_state
4043 .mod_local
4044 .sketch_block
4045 .as_ref()
4046 .ok_or_else(|| {
4047 internal_err(
4048 "Being inside a sketch block should have already been checked above",
4049 self,
4050 )
4051 })?
4052 .sketch_vars
4053 .clone();
4054 let support_initial =
4055 projected_point_on_line_initial_position(&sketch_vars, point, line, exec_state, range)?;
4056 let solver_line = datum_line_from_constrainable(line, range)?;
4057
4058 let constraint_id = exec_state.next_object_id();
4059 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4060 let message =
4061 "Being inside a sketch block should have already been checked above".to_owned();
4062 debug_assert!(false, "{}", &message);
4063 return Err(internal_err(message, self));
4064 };
4065
4066 let solver_point = datum_point_from_constrainable_or_origin(
4072 sketch_block_state,
4073 sketch_var_ty,
4074 point,
4075 range,
4076 )?;
4077 let support_x_id = sketch_block_state.next_sketch_var_id();
4078 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4079 value: Box::new(crate::execution::SketchVar {
4080 id: support_x_id,
4081 initial_value: support_initial[0],
4082 ty: sketch_var_ty,
4083 node_path: None,
4085 meta: vec![],
4086 }),
4087 });
4088 let support_y_id = sketch_block_state.next_sketch_var_id();
4089 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4090 value: Box::new(crate::execution::SketchVar {
4091 id: support_y_id,
4092 initial_value: support_initial[1],
4093 ty: sketch_var_ty,
4094 node_path: None,
4096 meta: vec![],
4097 }),
4098 });
4099 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4100 support_x_id.to_constraint_id(range)?,
4101 support_y_id.to_constraint_id(range)?,
4102 );
4103 let support_line =
4104 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
4105
4106 sketch_block_state
4107 .solver_constraints
4108 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
4109 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4110 support_line,
4111 solver_line,
4112 ezpz::datatypes::AngleKind::Perpendicular,
4113 ));
4114 sketch_block_state.solver_constraints.push(Constraint::Distance(
4115 solver_point,
4116 support_point,
4117 n.n,
4118 ));
4119
4120 use crate::execution::Artifact;
4121 use crate::execution::CodeRef;
4122 use crate::execution::SketchBlockConstraint;
4123 use crate::execution::SketchBlockConstraintType;
4124 use crate::front::Distance;
4125 use crate::front::SourceRef;
4126 use crate::frontend::sketch::ConstraintSegment;
4127
4128 let Some(sketch_id) = sketch_block_state.sketch_id else {
4129 let message = "Sketch id missing for constraint artifact".to_owned();
4130 debug_assert!(false, "{}", &message);
4131 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4132 };
4133 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4134 points: input_object_ids
4135 .iter()
4136 .copied()
4137 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
4138 .collect(),
4139 distance: n.try_into().map_err(|_| {
4140 internal_err("Failed to convert distance units numeric suffix:", range)
4141 })?,
4142 label_position: label_position.clone(),
4143 source,
4144 });
4145 sketch_block_state.sketch_constraints.push(constraint_id);
4146 let artifact_id = exec_state.next_artifact_id();
4147 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4148 id: artifact_id,
4149 sketch_id,
4150 constraint_id,
4151 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4152 code_ref: CodeRef::placeholder(range),
4153 }));
4154 exec_state.add_scene_object(
4155 Object {
4156 id: constraint_id,
4157 kind: ObjectKind::Constraint {
4158 constraint: sketch_constraint,
4159 },
4160 label: Default::default(),
4161 comments: Default::default(),
4162 artifact_id,
4163 source: SourceRef::new(range, self.node_path.clone()),
4164 },
4165 range,
4166 );
4167 }
4168 SketchConstraintKind::LineLineDistance {
4169 line0,
4170 line1,
4171 input_object_ids,
4172 label_position,
4173 } => {
4174 let range = self.as_source_range();
4175 let reference_point = crate::execution::ConstrainablePoint2d {
4176 vars: line0.vars[0].clone(),
4177 object_id: line0.object_id,
4178 };
4179 let sketch_var_ty = solver_numeric_type(exec_state);
4180 let sketch_vars = exec_state
4181 .mod_local
4182 .sketch_block
4183 .as_ref()
4184 .ok_or_else(|| {
4185 internal_err(
4186 "Being inside a sketch block should have already been checked above",
4187 self,
4188 )
4189 })?
4190 .sketch_vars
4191 .clone();
4192 let support_initial = projected_point_on_line_initial_position(
4193 &sketch_vars,
4194 &crate::execution::ConstrainablePoint2dOrOrigin::Point(reference_point.clone()),
4195 line1,
4196 exec_state,
4197 range,
4198 )?;
4199 let solver_point = datum_point_from_constrainable(&reference_point, range)?;
4200 let solver_line0 = datum_line_from_constrainable(line0, range)?;
4201 let solver_line1 = datum_line_from_constrainable(line1, range)?;
4202
4203 let constraint_id = exec_state.next_object_id();
4204 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4205 let message =
4206 "Being inside a sketch block should have already been checked above".to_owned();
4207 debug_assert!(false, "{}", &message);
4208 return Err(internal_err(message, self));
4209 };
4210
4211 let support_x_id = sketch_block_state.next_sketch_var_id();
4217 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4218 value: Box::new(crate::execution::SketchVar {
4219 id: support_x_id,
4220 initial_value: support_initial[0],
4221 ty: sketch_var_ty,
4222 node_path: None,
4224 meta: vec![],
4225 }),
4226 });
4227 let support_y_id = sketch_block_state.next_sketch_var_id();
4228 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4229 value: Box::new(crate::execution::SketchVar {
4230 id: support_y_id,
4231 initial_value: support_initial[1],
4232 ty: sketch_var_ty,
4233 node_path: None,
4235 meta: vec![],
4236 }),
4237 });
4238 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4239 support_x_id.to_constraint_id(range)?,
4240 support_y_id.to_constraint_id(range)?,
4241 );
4242 let support_line =
4243 ezpz::datatypes::inputs::DatumLineSegment::new(solver_point, support_point);
4244
4245 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4246 solver_line0,
4247 solver_line1,
4248 ezpz::datatypes::AngleKind::Parallel,
4249 ));
4250 sketch_block_state
4251 .solver_constraints
4252 .push(Constraint::PointLineDistance(support_point, solver_line1, 0.0));
4253 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4254 support_line,
4255 solver_line1,
4256 ezpz::datatypes::AngleKind::Perpendicular,
4257 ));
4258 sketch_block_state.solver_constraints.push(Constraint::Distance(
4259 solver_point,
4260 support_point,
4261 n.n,
4262 ));
4263
4264 use crate::execution::Artifact;
4265 use crate::execution::CodeRef;
4266 use crate::execution::SketchBlockConstraint;
4267 use crate::execution::SketchBlockConstraintType;
4268 use crate::front::Distance;
4269 use crate::front::SourceRef;
4270 use crate::frontend::sketch::ConstraintSegment;
4271
4272 let Some(sketch_id) = sketch_block_state.sketch_id else {
4273 let message = "Sketch id missing for constraint artifact".to_owned();
4274 debug_assert!(false, "{}", &message);
4275 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4276 };
4277 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4278 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4279 distance: n.try_into().map_err(|_| {
4280 internal_err("Failed to convert distance units numeric suffix:", range)
4281 })?,
4282 label_position: label_position.clone(),
4283 source,
4284 });
4285 sketch_block_state.sketch_constraints.push(constraint_id);
4286 let artifact_id = exec_state.next_artifact_id();
4287 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4288 id: artifact_id,
4289 sketch_id,
4290 constraint_id,
4291 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4292 code_ref: CodeRef::placeholder(range),
4293 }));
4294 exec_state.add_scene_object(
4295 Object {
4296 id: constraint_id,
4297 kind: ObjectKind::Constraint {
4298 constraint: sketch_constraint,
4299 },
4300 label: Default::default(),
4301 comments: Default::default(),
4302 artifact_id,
4303 source: SourceRef::new(range, self.node_path.clone()),
4304 },
4305 range,
4306 );
4307 }
4308 SketchConstraintKind::PointCircularDistance {
4309 point,
4310 center,
4311 start,
4312 end,
4313 input_object_ids,
4314 label_position,
4315 } => {
4316 let range = self.as_source_range();
4317 let sketch_var_ty = solver_numeric_type(exec_state);
4318 let sketch_vars = exec_state
4319 .mod_local
4320 .sketch_block
4321 .as_ref()
4322 .ok_or_else(|| {
4323 internal_err(
4324 "Being inside a sketch block should have already been checked above",
4325 self,
4326 )
4327 })?
4328 .sketch_vars
4329 .clone();
4330 let circular =
4331 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
4332
4333 let constraint_id = exec_state.next_object_id();
4334 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4335 let message =
4336 "Being inside a sketch block should have already been checked above".to_owned();
4337 debug_assert!(false, "{}", &message);
4338 return Err(internal_err(message, self));
4339 };
4340
4341 let target_point = datum_point_from_constrainable_or_origin(
4346 sketch_block_state,
4347 sketch_var_ty,
4348 point,
4349 range,
4350 )?;
4351 push_circular_distance_constraints(
4352 sketch_block_state,
4353 sketch_var_ty,
4354 target_point,
4355 circular,
4356 n.n,
4357 range,
4358 )?;
4359
4360 use crate::execution::Artifact;
4361 use crate::execution::CodeRef;
4362 use crate::execution::SketchBlockConstraint;
4363 use crate::execution::SketchBlockConstraintType;
4364 use crate::front::Distance;
4365 use crate::front::SourceRef;
4366 use crate::frontend::sketch::ConstraintSegment;
4367
4368 let Some(sketch_id) = sketch_block_state.sketch_id else {
4369 let message = "Sketch id missing for constraint artifact".to_owned();
4370 debug_assert!(false, "{}", &message);
4371 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4372 };
4373 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4374 points: input_object_ids
4375 .iter()
4376 .copied()
4377 .map(|id| id.map_or(ConstraintSegment::ORIGIN, ConstraintSegment::from))
4378 .collect(),
4379 distance: n.try_into().map_err(|_| {
4380 internal_err("Failed to convert distance units numeric suffix:", range)
4381 })?,
4382 label_position: label_position.clone(),
4383 source,
4384 });
4385 sketch_block_state.sketch_constraints.push(constraint_id);
4386 let artifact_id = exec_state.next_artifact_id();
4387 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4388 id: artifact_id,
4389 sketch_id,
4390 constraint_id,
4391 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4392 code_ref: CodeRef::placeholder(range),
4393 }));
4394 exec_state.add_scene_object(
4395 Object {
4396 id: constraint_id,
4397 kind: ObjectKind::Constraint {
4398 constraint: sketch_constraint,
4399 },
4400 label: Default::default(),
4401 comments: Default::default(),
4402 artifact_id,
4403 source: SourceRef::new(range, self.node_path.clone()),
4404 },
4405 range,
4406 );
4407 }
4408 SketchConstraintKind::LineCircularDistance {
4409 line,
4410 center,
4411 start,
4412 end,
4413 input_object_ids,
4414 label_position,
4415 } => {
4416 let range = self.as_source_range();
4417 let sketch_var_ty = solver_numeric_type(exec_state);
4418 let sketch_vars = exec_state
4419 .mod_local
4420 .sketch_block
4421 .as_ref()
4422 .ok_or_else(|| {
4423 internal_err(
4424 "Being inside a sketch block should have already been checked above",
4425 self,
4426 )
4427 })?
4428 .sketch_vars
4429 .clone();
4430 let support_initial = projected_point_on_line_initial_position(
4431 &sketch_vars,
4432 &crate::execution::ConstrainablePoint2dOrOrigin::Point(center.clone()),
4433 line,
4434 exec_state,
4435 range,
4436 )?;
4437 let solver_line = datum_line_from_constrainable(line, range)?;
4438 let circular =
4439 circular_distance_datums(&sketch_vars, center, start, end.as_ref(), exec_state, range)?;
4440
4441 let constraint_id = exec_state.next_object_id();
4442 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4443 let message =
4444 "Being inside a sketch block should have already been checked above".to_owned();
4445 debug_assert!(false, "{}", &message);
4446 return Err(internal_err(message, self));
4447 };
4448
4449 let support_x_id = sketch_block_state.next_sketch_var_id();
4455 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4456 value: Box::new(crate::execution::SketchVar {
4457 id: support_x_id,
4458 initial_value: support_initial[0],
4459 ty: sketch_var_ty,
4460 node_path: None,
4462 meta: vec![],
4463 }),
4464 });
4465 let support_y_id = sketch_block_state.next_sketch_var_id();
4466 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4467 value: Box::new(crate::execution::SketchVar {
4468 id: support_y_id,
4469 initial_value: support_initial[1],
4470 ty: sketch_var_ty,
4471 node_path: None,
4473 meta: vec![],
4474 }),
4475 });
4476 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4477 support_x_id.to_constraint_id(range)?,
4478 support_y_id.to_constraint_id(range)?,
4479 );
4480 let support_line =
4481 ezpz::datatypes::inputs::DatumLineSegment::new(circular.center, support_point);
4482
4483 sketch_block_state
4484 .solver_constraints
4485 .push(Constraint::PointLineDistance(support_point, solver_line, 0.0));
4486 sketch_block_state.solver_constraints.push(Constraint::LinesAtAngle(
4487 support_line,
4488 solver_line,
4489 ezpz::datatypes::AngleKind::Perpendicular,
4490 ));
4491 push_circular_distance_constraints(
4492 sketch_block_state,
4493 sketch_var_ty,
4494 support_point,
4495 circular,
4496 n.n,
4497 range,
4498 )?;
4499
4500 use crate::execution::Artifact;
4501 use crate::execution::CodeRef;
4502 use crate::execution::SketchBlockConstraint;
4503 use crate::execution::SketchBlockConstraintType;
4504 use crate::front::Distance;
4505 use crate::front::SourceRef;
4506 use crate::frontend::sketch::ConstraintSegment;
4507
4508 let Some(sketch_id) = sketch_block_state.sketch_id else {
4509 let message = "Sketch id missing for constraint artifact".to_owned();
4510 debug_assert!(false, "{}", &message);
4511 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4512 };
4513 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4514 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4515 distance: n.try_into().map_err(|_| {
4516 internal_err("Failed to convert distance units numeric suffix:", range)
4517 })?,
4518 label_position: label_position.clone(),
4519 source,
4520 });
4521 sketch_block_state.sketch_constraints.push(constraint_id);
4522 let artifact_id = exec_state.next_artifact_id();
4523 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4524 id: artifact_id,
4525 sketch_id,
4526 constraint_id,
4527 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4528 code_ref: CodeRef::placeholder(range),
4529 }));
4530 exec_state.add_scene_object(
4531 Object {
4532 id: constraint_id,
4533 kind: ObjectKind::Constraint {
4534 constraint: sketch_constraint,
4535 },
4536 label: Default::default(),
4537 comments: Default::default(),
4538 artifact_id,
4539 source: SourceRef::new(range, self.node_path.clone()),
4540 },
4541 range,
4542 );
4543 }
4544 SketchConstraintKind::CircularCircularDistance {
4545 center0,
4546 start0,
4547 end0,
4548 center1,
4549 start1,
4550 end1,
4551 input_object_ids,
4552 label_position,
4553 } => {
4554 let range = self.as_source_range();
4555 let sketch_var_ty = solver_numeric_type(exec_state);
4556 let sketch_vars = exec_state
4557 .mod_local
4558 .sketch_block
4559 .as_ref()
4560 .ok_or_else(|| {
4561 internal_err(
4562 "Being inside a sketch block should have already been checked above",
4563 self,
4564 )
4565 })?
4566 .sketch_vars
4567 .clone();
4568 let circular0 = circular_distance_datums(
4569 &sketch_vars,
4570 center0,
4571 start0,
4572 end0.as_ref(),
4573 exec_state,
4574 range,
4575 )?;
4576 let circular1 = circular_distance_datums(
4577 &sketch_vars,
4578 center1,
4579 start1,
4580 end1.as_ref(),
4581 exec_state,
4582 range,
4583 )?;
4584 let support_initial = circular_circular_support_initial_position(
4585 &sketch_vars,
4586 center0,
4587 center1,
4588 circular0.radius_initial_value,
4589 n.n,
4590 exec_state,
4591 range,
4592 )?;
4593
4594 let constraint_id = exec_state.next_object_id();
4595 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4596 let message =
4597 "Being inside a sketch block should have already been checked above".to_owned();
4598 debug_assert!(false, "{}", &message);
4599 return Err(internal_err(message, self));
4600 };
4601
4602 let circular_target0 =
4608 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular0, range)?;
4609 let circular_target1 =
4610 push_circular_radius_constraints(sketch_block_state, sketch_var_ty, circular1, range)?;
4611
4612 let support_x_id = sketch_block_state.next_sketch_var_id();
4613 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4614 value: Box::new(crate::execution::SketchVar {
4615 id: support_x_id,
4616 initial_value: support_initial[0],
4617 ty: sketch_var_ty,
4618 node_path: None,
4620 meta: vec![],
4621 }),
4622 });
4623 let support_y_id = sketch_block_state.next_sketch_var_id();
4624 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4625 value: Box::new(crate::execution::SketchVar {
4626 id: support_y_id,
4627 initial_value: support_initial[1],
4628 ty: sketch_var_ty,
4629 node_path: None,
4631 meta: vec![],
4632 }),
4633 });
4634 let support_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4635 support_x_id.to_constraint_id(range)?,
4636 support_y_id.to_constraint_id(range)?,
4637 );
4638
4639 let support_radius_id = sketch_block_state.next_sketch_var_id();
4640 let support_radius_value = n.n / 2.0;
4641 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4642 value: Box::new(crate::execution::SketchVar {
4643 id: support_radius_id,
4644 initial_value: support_radius_value,
4645 ty: sketch_var_ty,
4646 node_path: None,
4648 meta: vec![],
4649 }),
4650 });
4651 let support_radius =
4652 ezpz::datatypes::inputs::DatumDistance::new(support_radius_id.to_constraint_id(range)?);
4653 let support_circle = ezpz::datatypes::inputs::DatumCircle {
4654 center: support_point,
4655 radius: support_radius,
4656 };
4657 let center_line = ezpz::datatypes::inputs::DatumLineSegment::new(
4658 circular_target0.center,
4659 circular_target1.center,
4660 );
4661
4662 sketch_block_state
4663 .solver_constraints
4664 .push(Constraint::Fixed(support_radius.id, support_radius_value));
4665 sketch_block_state
4666 .solver_constraints
4667 .push(Constraint::PointLineDistance(support_point, center_line, 0.0));
4668 sketch_block_state
4669 .solver_constraints
4670 .push(Constraint::CircleTangentToCircle(
4671 circular_target0,
4672 support_circle,
4673 ezpz::CircleSide::Exterior,
4674 ));
4675 sketch_block_state
4676 .solver_constraints
4677 .push(Constraint::CircleTangentToCircle(
4678 support_circle,
4679 circular_target1,
4680 ezpz::CircleSide::Exterior,
4681 ));
4682
4683 use crate::execution::Artifact;
4684 use crate::execution::CodeRef;
4685 use crate::execution::SketchBlockConstraint;
4686 use crate::execution::SketchBlockConstraintType;
4687 use crate::front::Distance;
4688 use crate::front::SourceRef;
4689 use crate::frontend::sketch::ConstraintSegment;
4690
4691 let Some(sketch_id) = sketch_block_state.sketch_id else {
4692 let message = "Sketch id missing for constraint artifact".to_owned();
4693 debug_assert!(false, "{}", &message);
4694 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4695 };
4696 let sketch_constraint = crate::front::Constraint::Distance(Distance {
4697 points: input_object_ids.iter().copied().map(ConstraintSegment::from).collect(),
4698 distance: n.try_into().map_err(|_| {
4699 internal_err("Failed to convert distance units numeric suffix:", range)
4700 })?,
4701 label_position: label_position.clone(),
4702 source,
4703 });
4704 sketch_block_state.sketch_constraints.push(constraint_id);
4705 let artifact_id = exec_state.next_artifact_id();
4706 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4707 id: artifact_id,
4708 sketch_id,
4709 constraint_id,
4710 constraint_type: SketchBlockConstraintType::from(&sketch_constraint),
4711 code_ref: CodeRef::placeholder(range),
4712 }));
4713 exec_state.add_scene_object(
4714 Object {
4715 id: constraint_id,
4716 kind: ObjectKind::Constraint {
4717 constraint: sketch_constraint,
4718 },
4719 label: Default::default(),
4720 comments: Default::default(),
4721 artifact_id,
4722 source: SourceRef::new(range, self.node_path.clone()),
4723 },
4724 range,
4725 );
4726 }
4727 SketchConstraintKind::Radius { .. } | SketchConstraintKind::Diameter { .. } => {
4728 #[derive(Clone, Copy)]
4729 enum CircularSegmentConstraintTarget {
4730 Arc {
4731 object_id: ObjectId,
4732 end: [crate::execution::SketchVarId; 2],
4733 },
4734 Circle {
4735 object_id: ObjectId,
4736 },
4737 }
4738
4739 fn sketch_var_initial_value(
4740 sketch_vars: &[KclValue],
4741 id: crate::execution::SketchVarId,
4742 exec_state: &mut ExecState,
4743 range: SourceRange,
4744 ) -> Result<f64, KclError> {
4745 sketch_vars
4746 .get(id.0)
4747 .and_then(KclValue::as_sketch_var)
4748 .map(|sketch_var| {
4749 sketch_var
4750 .initial_value_to_solver_units(
4751 exec_state,
4752 range,
4753 "circle radius initial value",
4754 )
4755 .map(|value| value.n)
4756 })
4757 .transpose()?
4758 .ok_or_else(|| {
4759 internal_err(
4760 format!("Missing sketch variable initial value for id {}", id.0),
4761 range,
4762 )
4763 })
4764 }
4765
4766 let (points, label_position) = match &constraint.kind {
4767 SketchConstraintKind::Radius { points, label_position } => {
4768 (points, label_position.clone())
4769 }
4770 SketchConstraintKind::Diameter { points, label_position } => {
4771 (points, label_position.clone())
4772 }
4773 _ => unreachable!(),
4774 };
4775 let range = self.as_source_range();
4776 let center = &points[0];
4777 let start = &points[1];
4778 let Some(sketch_block_state) = &exec_state.mod_local.sketch_block else {
4779 return Err(internal_err(
4780 "Being inside a sketch block should have already been checked above",
4781 self,
4782 ));
4783 };
4784 let (constraint_name, is_diameter) = match &constraint.kind {
4785 SketchConstraintKind::Radius { .. } => ("radius", false),
4786 SketchConstraintKind::Diameter { .. } => ("diameter", true),
4787 _ => unreachable!(),
4788 };
4789 let sketch_vars = sketch_block_state.sketch_vars.clone();
4790 let target_segment = sketch_block_state
4791 .needed_by_engine
4792 .iter()
4793 .find_map(|seg| match &seg.kind {
4794 UnsolvedSegmentKind::Arc {
4795 center_object_id,
4796 start_object_id,
4797 end,
4798 ..
4799 } if *center_object_id == center.object_id
4800 && *start_object_id == start.object_id =>
4801 {
4802 let (end_x_var, end_y_var) = match (&end[0], &end[1]) {
4803 (UnsolvedExpr::Unknown(end_x), UnsolvedExpr::Unknown(end_y)) => {
4804 (*end_x, *end_y)
4805 }
4806 _ => return None,
4807 };
4808 Some(CircularSegmentConstraintTarget::Arc {
4809 object_id: seg.object_id,
4810 end: [end_x_var, end_y_var],
4811 })
4812 }
4813 UnsolvedSegmentKind::Circle {
4814 center_object_id,
4815 start_object_id,
4816 ..
4817 } if *center_object_id == center.object_id
4818 && *start_object_id == start.object_id =>
4819 {
4820 Some(CircularSegmentConstraintTarget::Circle {
4821 object_id: seg.object_id,
4822 })
4823 }
4824 _ => None,
4825 })
4826 .ok_or_else(|| {
4827 internal_err(
4828 format!("Could not find circular segment for {} constraint", constraint_name),
4829 range,
4830 )
4831 })?;
4832 let radius_value = if is_diameter { n.n / 2.0 } else { n.n };
4833 let center_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4834 center.vars.x.to_constraint_id(range)?,
4835 center.vars.y.to_constraint_id(range)?,
4836 );
4837 let start_point = ezpz::datatypes::inputs::DatumPoint::new_xy(
4838 start.vars.x.to_constraint_id(range)?,
4839 start.vars.y.to_constraint_id(range)?,
4840 );
4841 let solver_constraint = match target_segment {
4842 CircularSegmentConstraintTarget::Arc { end, .. } => {
4843 let solver_arc = ezpz::datatypes::inputs::DatumCircularArc {
4844 center: center_point,
4845 start: start_point,
4846 end: ezpz::datatypes::inputs::DatumPoint::new_xy(
4847 end[0].to_constraint_id(range)?,
4848 end[1].to_constraint_id(range)?,
4849 ),
4850 };
4851 Constraint::ArcRadius(solver_arc, radius_value)
4852 }
4853 CircularSegmentConstraintTarget::Circle { .. } => {
4854 let sketch_var_ty = solver_numeric_type(exec_state);
4855 let start_x =
4856 sketch_var_initial_value(&sketch_vars, start.vars.x, exec_state, range)?;
4857 let start_y =
4858 sketch_var_initial_value(&sketch_vars, start.vars.y, exec_state, range)?;
4859 let center_x =
4860 sketch_var_initial_value(&sketch_vars, center.vars.x, exec_state, range)?;
4861 let center_y =
4862 sketch_var_initial_value(&sketch_vars, center.vars.y, exec_state, range)?;
4863
4864 let radius_initial_value = libm::hypot(start_x - center_x, start_y - center_y);
4866
4867 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4868 let message =
4869 "Being inside a sketch block should have already been checked above"
4870 .to_owned();
4871 debug_assert!(false, "{}", &message);
4872 return Err(internal_err(message, self));
4873 };
4874 let radius_id = sketch_block_state.next_sketch_var_id();
4875 sketch_block_state.sketch_vars.push(KclValue::SketchVar {
4876 value: Box::new(crate::execution::SketchVar {
4877 id: radius_id,
4878 initial_value: radius_initial_value,
4879 ty: sketch_var_ty,
4880 node_path: None,
4882 meta: vec![],
4883 }),
4884 });
4885 let radius =
4886 ezpz::datatypes::inputs::DatumDistance::new(radius_id.to_constraint_id(range)?);
4887 let solver_circle = ezpz::datatypes::inputs::DatumCircle {
4888 center: center_point,
4889 radius,
4890 };
4891 sketch_block_state.solver_constraints.push(Constraint::DistanceVar(
4892 start_point,
4893 center_point,
4894 radius,
4895 ));
4896 Constraint::CircleRadius(solver_circle, radius_value)
4897 }
4898 };
4899
4900 let constraint_id = exec_state.next_object_id();
4901 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4902 let message =
4903 "Being inside a sketch block should have already been checked above".to_owned();
4904 debug_assert!(false, "{}", &message);
4905 return Err(internal_err(message, self));
4906 };
4907 sketch_block_state.solver_constraints.push(solver_constraint);
4908 use crate::execution::Artifact;
4909 use crate::execution::CodeRef;
4910 use crate::execution::SketchBlockConstraint;
4911 use crate::execution::SketchBlockConstraintType;
4912 use crate::front::SourceRef;
4913 let segment_object_id = match target_segment {
4914 CircularSegmentConstraintTarget::Arc { object_id, .. }
4915 | CircularSegmentConstraintTarget::Circle { object_id } => object_id,
4916 };
4917
4918 let constraint = if is_diameter {
4919 use crate::frontend::sketch::Diameter;
4920 crate::front::Constraint::Diameter(Diameter {
4921 arc: segment_object_id,
4922 diameter: n.try_into().map_err(|_| {
4923 internal_err("Failed to convert diameter units numeric suffix:", range)
4924 })?,
4925 label_position,
4926 source,
4927 })
4928 } else {
4929 use crate::frontend::sketch::Radius;
4930 crate::front::Constraint::Radius(Radius {
4931 arc: segment_object_id,
4932 radius: n.try_into().map_err(|_| {
4933 internal_err("Failed to convert radius units numeric suffix:", range)
4934 })?,
4935 label_position,
4936 source,
4937 })
4938 };
4939 sketch_block_state.sketch_constraints.push(constraint_id);
4940 let Some(sketch_id) = sketch_block_state.sketch_id else {
4941 let message = "Sketch id missing for constraint artifact".to_owned();
4942 debug_assert!(false, "{}", &message);
4943 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
4944 };
4945 let artifact_id = exec_state.next_artifact_id();
4946 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
4947 id: artifact_id,
4948 sketch_id,
4949 constraint_id,
4950 constraint_type: SketchBlockConstraintType::from(&constraint),
4951 code_ref: CodeRef::placeholder(range),
4952 }));
4953 exec_state.add_scene_object(
4954 Object {
4955 id: constraint_id,
4956 kind: ObjectKind::Constraint { constraint },
4957 label: Default::default(),
4958 comments: Default::default(),
4959 artifact_id,
4960 source: SourceRef::new(range, self.node_path.clone()),
4961 },
4962 range,
4963 );
4964 }
4965 SketchConstraintKind::HorizontalDistance { points, label_position } => {
4966 let range = self.as_source_range();
4967 let p0 = &points[0];
4968 let p1 = &points[1];
4969 let constraint_id = exec_state.next_object_id();
4970 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
4971 let message =
4972 "Being inside a sketch block should have already been checked above".to_owned();
4973 debug_assert!(false, "{}", &message);
4974 return Err(internal_err(message, self));
4975 };
4976 match (p0, p1) {
4977 (
4978 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
4979 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
4980 ) => {
4981 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4982 p0.vars.x.to_constraint_id(range)?,
4983 p0.vars.y.to_constraint_id(range)?,
4984 );
4985 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
4986 p1.vars.x.to_constraint_id(range)?,
4987 p1.vars.y.to_constraint_id(range)?,
4988 );
4989 sketch_block_state
4990 .solver_constraints
4991 .push(ezpz::Constraint::HorizontalDistance(solver_pt1, solver_pt0, n.n));
4992 }
4993 (
4994 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
4995 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
4996 ) => {
4997 sketch_block_state
4999 .solver_constraints
5000 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, -n.n));
5001 }
5002 (
5003 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5004 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5005 ) => {
5006 sketch_block_state
5008 .solver_constraints
5009 .push(ezpz::Constraint::Fixed(point.vars.x.to_constraint_id(range)?, n.n));
5010 }
5011 (
5012 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5013 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5014 ) => {
5015 return Err(internal_err(
5016 "horizontalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
5017 range,
5018 ));
5019 }
5020 }
5021 use crate::execution::Artifact;
5022 use crate::execution::CodeRef;
5023 use crate::execution::SketchBlockConstraint;
5024 use crate::execution::SketchBlockConstraintType;
5025 use crate::front::Distance;
5026 use crate::front::SourceRef;
5027 use crate::frontend::sketch::ConstraintSegment;
5028
5029 let constraint = crate::front::Constraint::HorizontalDistance(Distance {
5030 points: vec![
5031 match p0 {
5032 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5033 ConstraintSegment::from(point.object_id)
5034 }
5035 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5036 ConstraintSegment::ORIGIN
5037 }
5038 },
5039 match p1 {
5040 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5041 ConstraintSegment::from(point.object_id)
5042 }
5043 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5044 ConstraintSegment::ORIGIN
5045 }
5046 },
5047 ],
5048 distance: n.try_into().map_err(|_| {
5049 internal_err("Failed to convert distance units numeric suffix:", range)
5050 })?,
5051 label_position: label_position.clone(),
5052 source,
5053 });
5054 sketch_block_state.sketch_constraints.push(constraint_id);
5055 let Some(sketch_id) = sketch_block_state.sketch_id else {
5056 let message = "Sketch id missing for constraint artifact".to_owned();
5057 debug_assert!(false, "{}", &message);
5058 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5059 };
5060 let artifact_id = exec_state.next_artifact_id();
5061 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5062 id: artifact_id,
5063 sketch_id,
5064 constraint_id,
5065 constraint_type: SketchBlockConstraintType::from(&constraint),
5066 code_ref: CodeRef::placeholder(range),
5067 }));
5068 exec_state.add_scene_object(
5069 Object {
5070 id: constraint_id,
5071 kind: ObjectKind::Constraint { constraint },
5072 label: Default::default(),
5073 comments: Default::default(),
5074 artifact_id,
5075 source: SourceRef::new(range, self.node_path.clone()),
5076 },
5077 range,
5078 );
5079 }
5080 SketchConstraintKind::VerticalDistance { points, label_position } => {
5081 let range = self.as_source_range();
5082 let p0 = &points[0];
5083 let p1 = &points[1];
5084 let constraint_id = exec_state.next_object_id();
5085 let Some(sketch_block_state) = &mut exec_state.mod_local.sketch_block else {
5086 let message =
5087 "Being inside a sketch block should have already been checked above".to_owned();
5088 debug_assert!(false, "{}", &message);
5089 return Err(internal_err(message, self));
5090 };
5091 match (p0, p1) {
5092 (
5093 crate::execution::ConstrainablePoint2dOrOrigin::Point(p0),
5094 crate::execution::ConstrainablePoint2dOrOrigin::Point(p1),
5095 ) => {
5096 let solver_pt0 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5097 p0.vars.x.to_constraint_id(range)?,
5098 p0.vars.y.to_constraint_id(range)?,
5099 );
5100 let solver_pt1 = ezpz::datatypes::inputs::DatumPoint::new_xy(
5101 p1.vars.x.to_constraint_id(range)?,
5102 p1.vars.y.to_constraint_id(range)?,
5103 );
5104 sketch_block_state
5105 .solver_constraints
5106 .push(ezpz::Constraint::VerticalDistance(solver_pt1, solver_pt0, n.n));
5107 }
5108 (
5109 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5110 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5111 ) => {
5112 sketch_block_state
5113 .solver_constraints
5114 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, -n.n));
5115 }
5116 (
5117 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5118 crate::execution::ConstrainablePoint2dOrOrigin::Point(point),
5119 ) => {
5120 sketch_block_state
5121 .solver_constraints
5122 .push(ezpz::Constraint::Fixed(point.vars.y.to_constraint_id(range)?, n.n));
5123 }
5124 (
5125 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5126 crate::execution::ConstrainablePoint2dOrOrigin::Origin,
5127 ) => {
5128 return Err(internal_err(
5129 "verticalDistance() cannot constrain ORIGIN against ORIGIN".to_owned(),
5130 range,
5131 ));
5132 }
5133 }
5134 use crate::execution::Artifact;
5135 use crate::execution::CodeRef;
5136 use crate::execution::SketchBlockConstraint;
5137 use crate::execution::SketchBlockConstraintType;
5138 use crate::front::Distance;
5139 use crate::front::SourceRef;
5140 use crate::frontend::sketch::ConstraintSegment;
5141
5142 let constraint = crate::front::Constraint::VerticalDistance(Distance {
5143 points: vec![
5144 match p0 {
5145 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5146 ConstraintSegment::from(point.object_id)
5147 }
5148 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5149 ConstraintSegment::ORIGIN
5150 }
5151 },
5152 match p1 {
5153 crate::execution::ConstrainablePoint2dOrOrigin::Point(point) => {
5154 ConstraintSegment::from(point.object_id)
5155 }
5156 crate::execution::ConstrainablePoint2dOrOrigin::Origin => {
5157 ConstraintSegment::ORIGIN
5158 }
5159 },
5160 ],
5161 distance: n.try_into().map_err(|_| {
5162 internal_err("Failed to convert distance units numeric suffix:", range)
5163 })?,
5164 label_position: label_position.clone(),
5165 source,
5166 });
5167 sketch_block_state.sketch_constraints.push(constraint_id);
5168 let Some(sketch_id) = sketch_block_state.sketch_id else {
5169 let message = "Sketch id missing for constraint artifact".to_owned();
5170 debug_assert!(false, "{}", &message);
5171 return Err(KclError::new_internal(KclErrorDetails::new(message, vec![range])));
5172 };
5173 let artifact_id = exec_state.next_artifact_id();
5174 exec_state.add_artifact(Artifact::SketchBlockConstraint(SketchBlockConstraint {
5175 id: artifact_id,
5176 sketch_id,
5177 constraint_id,
5178 constraint_type: SketchBlockConstraintType::from(&constraint),
5179 code_ref: CodeRef::placeholder(range),
5180 }));
5181 exec_state.add_scene_object(
5182 Object {
5183 id: constraint_id,
5184 kind: ObjectKind::Constraint { constraint },
5185 label: Default::default(),
5186 comments: Default::default(),
5187 artifact_id,
5188 source: SourceRef::new(range, self.node_path.clone()),
5189 },
5190 range,
5191 );
5192 }
5193 }
5194 return Ok(KclValue::none());
5195 }
5196 _ => {
5197 return Err(KclError::new_semantic(KclErrorDetails::new(
5198 format!(
5199 "Cannot create an equivalence constraint between values of these types: {} and {}",
5200 left_value.human_friendly_type(),
5201 right_value.human_friendly_type()
5202 ),
5203 vec![self.into()],
5204 )));
5205 }
5206 }
5207 }
5208
5209 if matches!(self.operator, BinaryOperator::Eq | BinaryOperator::Neq)
5212 && let (KclValue::String { value: left, .. }, KclValue::String { value: right, .. }) =
5213 (&left_value, &right_value)
5214 {
5215 let is_equal = left == right;
5216 let value = if self.operator == BinaryOperator::Eq {
5217 is_equal
5218 } else {
5219 !is_equal
5220 };
5221 return Ok(KclValue::Bool { value, meta });
5222 }
5223
5224 let left = number_as_f64(&left_value, self.left.clone().into())?;
5225 let right = number_as_f64(&right_value, self.right.clone().into())?;
5226
5227 let value = match self.operator {
5228 BinaryOperator::Add => {
5229 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
5230 self.warn_on_unknown(&ty, "Adding", exec_state);
5231 KclValue::Number { value: l + r, meta, ty }
5232 }
5233 BinaryOperator::Sub => {
5234 let (l, r, ty) = NumericType::combine_eq_coerce(left, right, None);
5235 self.warn_on_unknown(&ty, "Subtracting", exec_state);
5236 KclValue::Number { value: l - r, meta, ty }
5237 }
5238 BinaryOperator::Mul => {
5239 let (l, r, ty) = NumericType::combine_mul(left, right);
5240 self.warn_on_unknown(&ty, "Multiplying", exec_state);
5241 KclValue::Number { value: l * r, meta, ty }
5242 }
5243 BinaryOperator::Div => {
5244 let (l, r, ty) = NumericType::combine_div(left, right);
5245 self.warn_on_unknown(&ty, "Dividing", exec_state);
5246 KclValue::Number { value: l / r, meta, ty }
5247 }
5248 BinaryOperator::Mod => {
5249 let (l, r, ty) = NumericType::combine_mod(left, right);
5250 self.warn_on_unknown(&ty, "Modulo of", exec_state);
5251 KclValue::Number { value: l % r, meta, ty }
5252 }
5253 BinaryOperator::Pow => KclValue::Number {
5254 value: libm::pow(left.n, right.n),
5255 meta,
5256 ty: exec_state.current_default_units(),
5257 },
5258 BinaryOperator::Neq => {
5259 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5260 self.warn_on_unknown(&ty, "Comparing", exec_state);
5261 KclValue::Bool { value: l != r, meta }
5262 }
5263 BinaryOperator::Gt => {
5264 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5265 self.warn_on_unknown(&ty, "Comparing", exec_state);
5266 KclValue::Bool { value: l > r, meta }
5267 }
5268 BinaryOperator::Gte => {
5269 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5270 self.warn_on_unknown(&ty, "Comparing", exec_state);
5271 KclValue::Bool { value: l >= r, meta }
5272 }
5273 BinaryOperator::Lt => {
5274 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5275 self.warn_on_unknown(&ty, "Comparing", exec_state);
5276 KclValue::Bool { value: l < r, meta }
5277 }
5278 BinaryOperator::Lte => {
5279 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5280 self.warn_on_unknown(&ty, "Comparing", exec_state);
5281 KclValue::Bool { value: l <= r, meta }
5282 }
5283 BinaryOperator::Eq => {
5284 let (l, r, ty) = NumericType::combine_eq(left, right, exec_state, self.as_source_range());
5285 self.warn_on_unknown(&ty, "Comparing", exec_state);
5286 KclValue::Bool { value: l == r, meta }
5287 }
5288 BinaryOperator::And | BinaryOperator::Or => unreachable!(),
5289 };
5290
5291 Ok(value)
5292 }
5293
5294 fn missing_result_error(node: &Node<BinaryExpression>) -> KclError {
5295 internal_err("missing result while evaluating binary expression", node)
5296 }
5297
5298 fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
5299 if ty == &NumericType::Unknown {
5300 let sr = self.as_source_range();
5301 exec_state.clear_units_warnings(&sr);
5302 let mut err = CompilationIssue::err(
5303 sr,
5304 format!(
5305 "{verb} numbers which have unknown or incompatible units.\nYou can probably fix this error by specifying the units using type ascription, e.g., `len: number(mm)` or `(a * b): number(deg)`."
5306 ),
5307 );
5308 err.tag = crate::errors::Tag::UnknownNumericUnits;
5309 exec_state.warn(err, annotations::WARN_UNKNOWN_UNITS);
5310 }
5311 }
5312}
5313
5314impl Node<UnaryExpression> {
5315 pub(super) async fn get_result(
5316 &self,
5317 exec_state: &mut ExecState,
5318 ctx: &ExecutorContext,
5319 ) -> Result<KclValueControlFlow, KclError> {
5320 match self.operator {
5321 UnaryOperator::Not => {
5322 let value = self.argument.get_result(exec_state, ctx).await?;
5323 let value = control_continue!(value);
5324 let KclValue::Bool {
5325 value: bool_value,
5326 meta: _,
5327 } = value
5328 else {
5329 return Err(KclError::new_semantic(KclErrorDetails::new(
5330 format!(
5331 "Cannot apply unary operator ! to non-boolean value: {}",
5332 value.human_friendly_type()
5333 ),
5334 vec![self.into()],
5335 )));
5336 };
5337 let meta = vec![Metadata {
5338 source_range: self.into(),
5339 }];
5340 let negated = KclValue::Bool {
5341 value: !bool_value,
5342 meta,
5343 };
5344
5345 Ok(negated.continue_())
5346 }
5347 UnaryOperator::Neg => {
5348 let value = self.argument.get_result(exec_state, ctx).await?;
5349 let value = control_continue!(value);
5350 let err = || {
5351 KclError::new_semantic(KclErrorDetails::new(
5352 format!(
5353 "You can only negate numbers, planes, or lines, but this is a {}",
5354 value.human_friendly_type()
5355 ),
5356 vec![self.into()],
5357 ))
5358 };
5359 match &value {
5360 KclValue::Number { value, ty, .. } => {
5361 let meta = vec![Metadata {
5362 source_range: self.into(),
5363 }];
5364 Ok(KclValue::Number {
5365 value: -value,
5366 meta,
5367 ty: *ty,
5368 }
5369 .continue_())
5370 }
5371 KclValue::Plane { value } => {
5372 let mut plane = value.clone();
5373 if plane.info.x_axis.x != 0.0 {
5374 plane.info.x_axis.x *= -1.0;
5375 }
5376 if plane.info.x_axis.y != 0.0 {
5377 plane.info.x_axis.y *= -1.0;
5378 }
5379 if plane.info.x_axis.z != 0.0 {
5380 plane.info.x_axis.z *= -1.0;
5381 }
5382 plane.info.z_axis = plane.info.x_axis.axes_cross_product(&plane.info.y_axis);
5383 plane.info.z_axis.canonicalize_signed_zero();
5384
5385 plane.id = exec_state.next_uuid();
5386 plane.object_id = None;
5387 Ok(KclValue::Plane { value: plane }.continue_())
5388 }
5389 KclValue::Object {
5390 value: values, meta, ..
5391 } => {
5392 let Some(direction) = values.get("direction") else {
5394 return Err(err());
5395 };
5396
5397 let direction = match direction {
5398 KclValue::Tuple { value: values, meta } => {
5399 let values = values
5400 .iter()
5401 .map(|v| match v {
5402 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
5403 value: *value * -1.0,
5404 ty: *ty,
5405 meta: meta.clone(),
5406 }),
5407 _ => Err(err()),
5408 })
5409 .collect::<Result<Vec<_>, _>>()?;
5410
5411 KclValue::Tuple {
5412 value: values,
5413 meta: meta.clone(),
5414 }
5415 }
5416 KclValue::HomArray {
5417 value: values,
5418 ty: ty @ RuntimeType::Primitive(PrimitiveType::Number(_)),
5419 } => {
5420 let values = values
5421 .iter()
5422 .map(|v| match v {
5423 KclValue::Number { value, ty, meta } => Ok(KclValue::Number {
5424 value: *value * -1.0,
5425 ty: *ty,
5426 meta: meta.clone(),
5427 }),
5428 _ => Err(err()),
5429 })
5430 .collect::<Result<Vec<_>, _>>()?;
5431
5432 KclValue::HomArray {
5433 value: values,
5434 ty: ty.clone(),
5435 }
5436 }
5437 _ => return Err(err()),
5438 };
5439
5440 let mut value = values.clone();
5441 value.insert("direction".to_owned(), direction);
5442 Ok(KclValue::Object {
5443 value,
5444 meta: meta.clone(),
5445 constrainable: false,
5446 object_kind: KclObjectKind::Default,
5447 }
5448 .continue_())
5449 }
5450 _ => Err(err()),
5451 }
5452 }
5453 UnaryOperator::Plus => {
5454 let operand = self.argument.get_result(exec_state, ctx).await?;
5455 let operand = control_continue!(operand);
5456 match operand {
5457 KclValue::Number { .. } | KclValue::Plane { .. } => Ok(operand.continue_()),
5458 _ => Err(KclError::new_semantic(KclErrorDetails::new(
5459 format!(
5460 "You can only apply unary + to numbers or planes, but this is a {}",
5461 operand.human_friendly_type()
5462 ),
5463 vec![self.into()],
5464 ))),
5465 }
5466 }
5467 }
5468 }
5469}
5470
5471pub(crate) async fn execute_pipe_body(
5472 exec_state: &mut ExecState,
5473 body: &[Expr],
5474 source_range: SourceRange,
5475 ctx: &ExecutorContext,
5476) -> Result<KclValueControlFlow, KclError> {
5477 let Some((first, body)) = body.split_first() else {
5478 return Err(KclError::new_semantic(KclErrorDetails::new(
5479 "Pipe expressions cannot be empty".to_owned(),
5480 vec![source_range],
5481 )));
5482 };
5483 let meta = Metadata {
5488 source_range: SourceRange::from(first),
5489 };
5490 let output = ctx
5491 .execute_expr(first, exec_state, &meta, &[], StatementKind::Expression)
5492 .await?;
5493 let output = control_continue!(output);
5494
5495 let previous_pipe_value = exec_state.mod_local.pipe_value.replace(output);
5499 let result = inner_execute_pipe_body(exec_state, body, ctx).await;
5501 exec_state.mod_local.pipe_value = previous_pipe_value;
5503
5504 result
5505}
5506
5507#[async_recursion]
5510async fn inner_execute_pipe_body(
5511 exec_state: &mut ExecState,
5512 body: &[Expr],
5513 ctx: &ExecutorContext,
5514) -> Result<KclValueControlFlow, KclError> {
5515 for expression in body {
5516 if let Expr::TagDeclarator(_) = expression {
5517 return Err(KclError::new_semantic(KclErrorDetails::new(
5518 format!("This cannot be in a PipeExpression: {expression:?}"),
5519 vec![expression.into()],
5520 )));
5521 }
5522 let metadata = Metadata {
5523 source_range: SourceRange::from(expression),
5524 };
5525 let output = ctx
5526 .execute_expr(expression, exec_state, &metadata, &[], StatementKind::Expression)
5527 .await?;
5528 let output = control_continue!(output);
5529 exec_state.mod_local.pipe_value = Some(output);
5530 }
5531 let final_output = exec_state.mod_local.pipe_value.take().unwrap();
5533 Ok(final_output.continue_())
5534}
5535
5536impl Node<TagDeclarator> {
5537 pub async fn execute(&self, exec_state: &mut ExecState) -> Result<KclValue, KclError> {
5538 let memory_item = KclValue::TagIdentifier(Box::new(TagIdentifier {
5539 value: self.name.clone(),
5540 info: Vec::new(),
5541 meta: vec![Metadata {
5542 source_range: self.into(),
5543 }],
5544 }));
5545
5546 exec_state
5547 .mut_stack()
5548 .add(self.name.clone(), memory_item, self.into())?;
5549
5550 Ok(self.into())
5551 }
5552}
5553
5554impl Node<ArrayExpression> {
5555 #[async_recursion]
5556 pub(super) async fn execute(
5557 &self,
5558 exec_state: &mut ExecState,
5559 ctx: &ExecutorContext,
5560 ) -> Result<KclValueControlFlow, KclError> {
5561 let mut results = Vec::with_capacity(self.elements.len());
5562
5563 for element in &self.elements {
5564 let metadata = Metadata::from(element);
5565 let value = ctx
5568 .execute_expr(element, exec_state, &metadata, &[], StatementKind::Expression)
5569 .await?;
5570 let value = control_continue!(value);
5571
5572 results.push(value);
5573 }
5574
5575 Ok(KclValue::HomArray {
5576 value: results,
5577 ty: RuntimeType::Primitive(PrimitiveType::Any),
5578 }
5579 .continue_())
5580 }
5581}
5582
5583impl Node<ArrayRangeExpression> {
5584 #[async_recursion]
5585 pub(super) async fn execute(
5586 &self,
5587 exec_state: &mut ExecState,
5588 ctx: &ExecutorContext,
5589 ) -> Result<KclValueControlFlow, KclError> {
5590 let metadata = Metadata::from(&self.start_element);
5591 let start_val = ctx
5592 .execute_expr(
5593 &self.start_element,
5594 exec_state,
5595 &metadata,
5596 &[],
5597 StatementKind::Expression,
5598 )
5599 .await?;
5600 let start_val = control_continue!(start_val);
5601 let start = start_val
5602 .as_ty_f64()
5603 .ok_or(KclError::new_semantic(KclErrorDetails::new(
5604 format!(
5605 "Expected number for range start but found {}",
5606 start_val.human_friendly_type()
5607 ),
5608 vec![self.into()],
5609 )))?;
5610 let metadata = Metadata::from(&self.end_element);
5611 let end_val = ctx
5612 .execute_expr(&self.end_element, exec_state, &metadata, &[], StatementKind::Expression)
5613 .await?;
5614 let end_val = control_continue!(end_val);
5615 let end = end_val.as_ty_f64().ok_or(KclError::new_semantic(KclErrorDetails::new(
5616 format!(
5617 "Expected number for range end but found {}",
5618 end_val.human_friendly_type()
5619 ),
5620 vec![self.into()],
5621 )))?;
5622
5623 let (start, end, ty) = NumericType::combine_range(start, end, exec_state, self.as_source_range())?;
5624 let Some(start) = crate::try_f64_to_i64(start) else {
5625 return Err(KclError::new_semantic(KclErrorDetails::new(
5626 format!("Range start must be an integer, but found {start}"),
5627 vec![self.into()],
5628 )));
5629 };
5630 let Some(end) = crate::try_f64_to_i64(end) else {
5631 return Err(KclError::new_semantic(KclErrorDetails::new(
5632 format!("Range end must be an integer, but found {end}"),
5633 vec![self.into()],
5634 )));
5635 };
5636
5637 if end < start {
5638 return Err(KclError::new_semantic(KclErrorDetails::new(
5639 format!("Range start is greater than range end: {start} .. {end}"),
5640 vec![self.into()],
5641 )));
5642 }
5643
5644 let range: Vec<_> = if self.end_inclusive {
5645 (start..=end).collect()
5646 } else {
5647 (start..end).collect()
5648 };
5649
5650 let meta = vec![Metadata {
5651 source_range: self.into(),
5652 }];
5653
5654 Ok(KclValue::HomArray {
5655 value: range
5656 .into_iter()
5657 .map(|num| KclValue::Number {
5658 value: num as f64,
5659 ty,
5660 meta: meta.clone(),
5661 })
5662 .collect(),
5663 ty: RuntimeType::Primitive(PrimitiveType::Number(ty)),
5664 }
5665 .continue_())
5666 }
5667}
5668
5669impl Node<ObjectExpression> {
5670 #[async_recursion]
5671 pub(super) async fn execute(
5672 &self,
5673 exec_state: &mut ExecState,
5674 ctx: &ExecutorContext,
5675 ) -> Result<KclValueControlFlow, KclError> {
5676 let mut object = HashMap::with_capacity(self.properties.len());
5677 for property in &self.properties {
5678 let metadata = Metadata::from(&property.value);
5679 let result = ctx
5680 .execute_expr(&property.value, exec_state, &metadata, &[], StatementKind::Expression)
5681 .await?;
5682 let result = control_continue!(result);
5683 object.insert(property.key.name.clone(), result);
5684 }
5685
5686 Ok(KclValue::Object {
5687 value: object,
5688 meta: vec![Metadata {
5689 source_range: self.into(),
5690 }],
5691 constrainable: false,
5692 object_kind: KclObjectKind::Default,
5693 }
5694 .continue_())
5695 }
5696}
5697
5698fn article_for<S: AsRef<str>>(s: S) -> &'static str {
5699 if s.as_ref().starts_with(['a', 'e', 'i', 'o', 'u', '[']) {
5701 "an"
5702 } else {
5703 "a"
5704 }
5705}
5706
5707fn number_as_f64(v: &KclValue, source_range: SourceRange) -> Result<TyF64, KclError> {
5708 v.as_ty_f64().ok_or_else(|| {
5709 let actual_type = v.human_friendly_type();
5710 KclError::new_semantic(KclErrorDetails::new(
5711 format!("Expected a number, but found {actual_type}",),
5712 vec![source_range],
5713 ))
5714 })
5715}
5716
5717impl Node<IfExpression> {
5718 #[async_recursion]
5719 pub(super) async fn get_result(
5720 &self,
5721 exec_state: &mut ExecState,
5722 ctx: &ExecutorContext,
5723 ) -> Result<KclValueControlFlow, KclError> {
5724 let cond_value = ctx
5726 .execute_expr(
5727 &self.cond,
5728 exec_state,
5729 &Metadata::from(self),
5730 &[],
5731 StatementKind::Expression,
5732 )
5733 .await?;
5734 let cond_value = control_continue!(cond_value);
5735 if cond_value.get_bool()? {
5736 let block_result = ctx.exec_block(&*self.then_val, exec_state, BodyType::Block).await?;
5737 return Ok(block_result.unwrap());
5741 }
5742
5743 for else_if in &self.else_ifs {
5745 let cond_value = ctx
5746 .execute_expr(
5747 &else_if.cond,
5748 exec_state,
5749 &Metadata::from(self),
5750 &[],
5751 StatementKind::Expression,
5752 )
5753 .await?;
5754 let cond_value = control_continue!(cond_value);
5755 if cond_value.get_bool()? {
5756 let block_result = ctx.exec_block(&*else_if.then_val, exec_state, BodyType::Block).await?;
5757 return Ok(block_result.unwrap());
5761 }
5762 }
5763
5764 ctx.exec_block(&*self.final_else, exec_state, BodyType::Block)
5766 .await
5767 .map(|expr| expr.unwrap())
5768 }
5769}
5770
5771#[derive(Debug)]
5772enum Property {
5773 UInt(usize),
5774 String(String),
5775}
5776
5777impl Property {
5778 #[allow(clippy::too_many_arguments)]
5779 async fn try_from<'a>(
5780 computed: bool,
5781 value: Expr,
5782 exec_state: &mut ExecState,
5783 sr: SourceRange,
5784 ctx: &ExecutorContext,
5785 metadata: &Metadata,
5786 annotations: &[Node<Annotation>],
5787 statement_kind: StatementKind<'a>,
5788 ) -> Result<Self, KclError> {
5789 let property_sr = vec![sr];
5790 if !computed {
5791 let Expr::Name(identifier) = value else {
5792 return Err(KclError::new_semantic(KclErrorDetails::new(
5794 "Object expressions like `obj.property` must use simple identifier names, not complex expressions"
5795 .to_owned(),
5796 property_sr,
5797 )));
5798 };
5799 return Ok(Property::String(identifier.to_string()));
5800 }
5801
5802 let prop_value = ctx
5803 .execute_expr(&value, exec_state, metadata, annotations, statement_kind)
5804 .await?;
5805 let prop_value = match prop_value.control {
5806 ControlFlowKind::Continue => prop_value.into_value(),
5807 ControlFlowKind::Exit => {
5808 let message = "Early return inside array brackets is currently not supported".to_owned();
5809 debug_assert!(false, "{}", &message);
5810 return Err(internal_err(message, sr));
5811 }
5812 };
5813 match prop_value {
5814 KclValue::Number { value, ty, meta: _ } => {
5815 if !matches!(
5816 ty,
5817 NumericType::Unknown
5818 | NumericType::Default { .. }
5819 | NumericType::Known(crate::exec::UnitType::Count)
5820 ) {
5821 return Err(KclError::new_semantic(KclErrorDetails::new(
5822 format!(
5823 "{value} is not a valid index, indices must be non-dimensional numbers. If you're sure this is correct, you can add `: number(Count)` to tell KCL this number is an index"
5824 ),
5825 property_sr,
5826 )));
5827 }
5828 if let Some(x) = crate::try_f64_to_usize(value) {
5829 Ok(Property::UInt(x))
5830 } else {
5831 Err(KclError::new_semantic(KclErrorDetails::new(
5832 format!("{value} is not a valid index, indices must be whole numbers >= 0"),
5833 property_sr,
5834 )))
5835 }
5836 }
5837 _ => Err(KclError::new_semantic(KclErrorDetails::new(
5838 "Only numbers (>= 0) can be indexes".to_owned(),
5839 vec![sr],
5840 ))),
5841 }
5842 }
5843}
5844
5845impl Property {
5846 fn type_name(&self) -> &'static str {
5847 match self {
5848 Property::UInt(_) => "number",
5849 Property::String(_) => "string",
5850 }
5851 }
5852}
5853
5854impl Node<PipeExpression> {
5855 #[async_recursion]
5856 pub(super) async fn get_result(
5857 &self,
5858 exec_state: &mut ExecState,
5859 ctx: &ExecutorContext,
5860 ) -> Result<KclValueControlFlow, KclError> {
5861 execute_pipe_body(exec_state, &self.body, self.into(), ctx).await
5862 }
5863}
5864
5865#[cfg(test)]
5866mod test {
5867 use std::sync::Arc;
5868
5869 use kcl_api::UnitLength;
5870 use tokio::io::AsyncWriteExt;
5871
5872 use super::*;
5873 use crate::ExecutorSettings;
5874 use crate::engine::engine_manager;
5875 use crate::errors::Severity;
5876 use crate::exec::UnitType;
5877 use crate::execution::ContextType;
5878 use crate::execution::parse_execute;
5879
5880 #[tokio::test(flavor = "multi_thread")]
5881 async fn ascription() {
5882 let program = r#"
5883a = 42: number
5884b = a: number
5885p = {
5886 origin = { x = 0, y = 0, z = 0 },
5887 xAxis = { x = 1, y = 0, z = 0 },
5888 yAxis = { x = 0, y = 1, z = 0 },
5889 zAxis = { x = 0, y = 0, z = 1 }
5890}: Plane
5891arr1 = [42]: [number(cm)]
5892"#;
5893
5894 let result = parse_execute(program).await.unwrap();
5895 let mem = result.exec_state.stack();
5896 assert!(matches!(
5897 mem.memory
5898 .get_from_owned("p", result.mem_env, SourceRange::default(), 0)
5899 .unwrap(),
5900 KclValue::Plane { .. }
5901 ));
5902 let arr1 = mem
5903 .memory
5904 .get_from_owned("arr1", result.mem_env, SourceRange::default(), 0)
5905 .unwrap();
5906 if let KclValue::HomArray { value, ty } = arr1 {
5907 assert_eq!(value.len(), 1, "Expected Vec with specific length: found {value:?}");
5908 assert_eq!(ty, RuntimeType::known_length(UnitLength::Centimeters));
5909 if let KclValue::Number { value, ty, .. } = &value[0] {
5911 assert_eq!(*value, 42.0);
5913 assert_eq!(*ty, NumericType::Known(UnitType::Length(UnitLength::Centimeters)));
5914 } else {
5915 panic!("Expected a number; found {:?}", value[0]);
5916 }
5917 } else {
5918 panic!("Expected HomArray; found {arr1:?}");
5919 }
5920
5921 let program = r#"
5922a = 42: string
5923"#;
5924 let result = parse_execute(program).await;
5925 let err = result.unwrap_err();
5926 assert!(
5927 err.to_string()
5928 .contains("could not coerce a number (with type `number`) to type `string`"),
5929 "Expected error but found {err:?}"
5930 );
5931
5932 let program = r#"
5933a = 42: Plane
5934"#;
5935 let result = parse_execute(program).await;
5936 let err = result.unwrap_err();
5937 assert!(
5938 err.to_string()
5939 .contains("could not coerce a number (with type `number`) to type `Plane`"),
5940 "Expected error but found {err:?}"
5941 );
5942
5943 let program = r#"
5944arr = [0]: [string]
5945"#;
5946 let result = parse_execute(program).await;
5947 let err = result.unwrap_err();
5948 assert!(
5949 err.to_string().contains(
5950 "could not coerce an array of `number` with 1 value (with type `[any; 1]`) to type `[string]`"
5951 ),
5952 "Expected error but found {err:?}"
5953 );
5954
5955 let program = r#"
5956mixedArr = [0, "a"]: [number(mm)]
5957"#;
5958 let result = parse_execute(program).await;
5959 let err = result.unwrap_err();
5960 assert!(
5961 err.to_string().contains(
5962 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5963 ),
5964 "Expected error but found {err:?}"
5965 );
5966
5967 let program = r#"
5968mixedArr = [0, "a"]: [mm]
5969"#;
5970 let result = parse_execute(program).await;
5971 let err = result.unwrap_err();
5972 assert!(
5973 err.to_string().contains(
5974 "could not coerce an array of `number`, `string` (with type `[any; 2]`) to type `[number(mm)]`"
5975 ),
5976 "Expected error but found {err:?}"
5977 );
5978 }
5979
5980 #[tokio::test(flavor = "multi_thread")]
5981 async fn neg_plane() {
5982 let program = r#"
5983p = {
5984 origin = { x = 0, y = 0, z = 0 },
5985 xAxis = { x = 1, y = 0, z = 0 },
5986 yAxis = { x = 0, y = 1, z = 0 },
5987}: Plane
5988p2 = -p
5989"#;
5990
5991 let result = parse_execute(program).await.unwrap();
5992 let mem = result.exec_state.stack();
5993 match mem
5994 .memory
5995 .get_from_owned("p2", result.mem_env, SourceRange::default(), 0)
5996 .unwrap()
5997 {
5998 KclValue::Plane { value } => {
5999 assert_eq!(value.info.x_axis.x, -1.0);
6000 assert_eq!(value.info.x_axis.y, 0.0);
6001 assert_eq!(value.info.x_axis.z, 0.0);
6002 }
6003 _ => unreachable!(),
6004 }
6005 }
6006
6007 #[tokio::test(flavor = "multi_thread")]
6008 async fn multiple_returns() {
6009 let program = r#"fn foo() {
6010 return 0
6011 return 42
6012}
6013
6014a = foo()
6015"#;
6016
6017 let result = parse_execute(program).await;
6018 assert!(result.unwrap_err().to_string().contains("return"));
6019 }
6020
6021 #[tokio::test(flavor = "multi_thread")]
6022 async fn load_all_modules() {
6023 let program_a_kcl = r#"
6025export a = 1
6026"#;
6027 let program_b_kcl = r#"
6029import a from 'a.kcl'
6030
6031export b = a + 1
6032"#;
6033 let program_c_kcl = r#"
6035import a from 'a.kcl'
6036
6037export c = a + 2
6038"#;
6039
6040 let main_kcl = r#"
6042import b from 'b.kcl'
6043import c from 'c.kcl'
6044
6045d = b + c
6046"#;
6047
6048 let main = crate::parsing::parse_str(main_kcl, ModuleId::default())
6049 .parse_errs_as_err()
6050 .unwrap();
6051
6052 let tmpdir = tempfile::TempDir::with_prefix("zma_kcl_load_all_modules").unwrap();
6053
6054 tokio::fs::File::create(tmpdir.path().join("main.kcl"))
6055 .await
6056 .unwrap()
6057 .write_all(main_kcl.as_bytes())
6058 .await
6059 .unwrap();
6060
6061 tokio::fs::File::create(tmpdir.path().join("a.kcl"))
6062 .await
6063 .unwrap()
6064 .write_all(program_a_kcl.as_bytes())
6065 .await
6066 .unwrap();
6067
6068 tokio::fs::File::create(tmpdir.path().join("b.kcl"))
6069 .await
6070 .unwrap()
6071 .write_all(program_b_kcl.as_bytes())
6072 .await
6073 .unwrap();
6074
6075 tokio::fs::File::create(tmpdir.path().join("c.kcl"))
6076 .await
6077 .unwrap()
6078 .write_all(program_c_kcl.as_bytes())
6079 .await
6080 .unwrap();
6081
6082 let exec_ctxt = ExecutorContext {
6083 engine: Arc::new(engine_manager::EngineManager::new_mock()),
6084 engine_batch: crate::engine::EngineBatchContext::default(),
6085 fs: crate::fs::new_file_system_handle(crate::fs::FileManager::new()),
6086 settings: ExecutorSettings {
6087 project_directory: Some(crate::TypedPath(tmpdir.path().into())),
6088 ..Default::default()
6089 },
6090 context_type: ContextType::Mock,
6091 execution_callbacks: Default::default(),
6092 };
6093 let mut exec_state = ExecState::new(&exec_ctxt);
6094
6095 exec_ctxt
6096 .run(
6097 &crate::Program {
6098 ast: main.clone(),
6099 original_file_contents: "".to_owned(),
6100 },
6101 &mut exec_state,
6102 )
6103 .await
6104 .unwrap();
6105 }
6106
6107 #[tokio::test(flavor = "multi_thread")]
6108 async fn user_coercion() {
6109 let program = r#"fn foo(x: Axis2d) {
6110 return 0
6111}
6112
6113foo(x = { direction = [0, 0], origin = [0, 0]})
6114"#;
6115
6116 parse_execute(program).await.unwrap();
6117
6118 let program = r#"fn foo(x: Axis3d) {
6119 return 0
6120}
6121
6122foo(x = { direction = [0, 0], origin = [0, 0]})
6123"#;
6124
6125 parse_execute(program).await.unwrap_err();
6126 }
6127
6128 #[tokio::test(flavor = "multi_thread")]
6129 async fn coerce_return() {
6130 let program = r#"fn foo(): number(mm) {
6131 return 42
6132}
6133
6134a = foo()
6135"#;
6136
6137 parse_execute(program).await.unwrap();
6138
6139 let program = r#"fn foo(): mm {
6140 return 42
6141}
6142
6143a = foo()
6144"#;
6145
6146 parse_execute(program).await.unwrap();
6147
6148 let program = r#"fn foo(): number(mm) {
6149 return { bar: 42 }
6150}
6151
6152a = foo()
6153"#;
6154
6155 parse_execute(program).await.unwrap_err();
6156
6157 let program = r#"fn foo(): mm {
6158 return { bar: 42 }
6159}
6160
6161a = foo()
6162"#;
6163
6164 parse_execute(program).await.unwrap_err();
6165 }
6166
6167 #[tokio::test(flavor = "multi_thread")]
6168 async fn test_sensible_error_when_missing_equals_in_kwarg() {
6169 for (i, call) in ["f(x=1,3,0)", "f(x=1,3,z)", "f(x=1,0,z=1)", "f(x=1, 3 + 4, z)"]
6170 .into_iter()
6171 .enumerate()
6172 {
6173 let program = format!(
6174 "fn foo() {{ return 0 }}
6175z = 0
6176fn f(x, y, z) {{ return 0 }}
6177{call}"
6178 );
6179 let err = parse_execute(&program).await.unwrap_err();
6180 let msg = err.message();
6181 assert!(
6182 msg.contains("This argument needs a label, but it doesn't have one"),
6183 "failed test {i}: {msg}"
6184 );
6185 assert!(msg.contains("`y`"), "failed test {i}, missing `y`: {msg}");
6186 if i == 0 {
6187 assert!(msg.contains("`z`"), "failed test {i}, missing `z`: {msg}");
6188 }
6189 }
6190 }
6191
6192 #[tokio::test(flavor = "multi_thread")]
6193 async fn default_param_for_unlabeled() {
6194 let ast = r#"fn myExtrude(@sk, length) {
6197 return extrude(sk, length)
6198}
6199sketch001 = startSketchOn(XY)
6200 |> circle(center = [0, 0], radius = 93.75)
6201 |> myExtrude(length = 40)
6202"#;
6203
6204 parse_execute(ast).await.unwrap();
6205 }
6206
6207 #[tokio::test(flavor = "multi_thread")]
6208 async fn dont_use_unlabelled_as_input() {
6209 let ast = r#"length = 10
6211startSketchOn(XY)
6212 |> circle(center = [0, 0], radius = 93.75)
6213 |> extrude(length)
6214"#;
6215
6216 parse_execute(ast).await.unwrap();
6217 }
6218
6219 #[tokio::test(flavor = "multi_thread")]
6220 async fn ascription_in_binop() {
6221 let ast = r#"foo = tan(0): number(rad) - 4deg"#;
6222 parse_execute(ast).await.unwrap();
6223
6224 let ast = r#"foo = tan(0): rad - 4deg"#;
6225 parse_execute(ast).await.unwrap();
6226 }
6227
6228 #[tokio::test(flavor = "multi_thread")]
6229 async fn neg_sqrt() {
6230 let ast = r#"bad = sqrt(-2)"#;
6231
6232 let e = parse_execute(ast).await.unwrap_err();
6233 assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
6235 }
6236
6237 #[tokio::test(flavor = "multi_thread")]
6238 async fn non_array_fns() {
6239 let ast = r#"push(1, item = 2)
6240pop(1)
6241map(1, f = fn(@x) { return x + 1 })
6242reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
6243
6244 parse_execute(ast).await.unwrap();
6245 }
6246
6247 #[tokio::test(flavor = "multi_thread")]
6248 async fn non_array_indexing() {
6249 let good = r#"a = 42
6250good = a[0]
6251"#;
6252 let result = parse_execute(good).await.unwrap();
6253 let mem = result.exec_state.stack();
6254 let num = mem
6255 .memory
6256 .get_from_owned("good", result.mem_env, SourceRange::default(), 0)
6257 .unwrap()
6258 .as_ty_f64()
6259 .unwrap();
6260 assert_eq!(num.n, 42.0);
6261
6262 let bad = r#"a = 42
6263bad = a[1]
6264"#;
6265
6266 parse_execute(bad).await.unwrap_err();
6267 }
6268
6269 #[tokio::test(flavor = "multi_thread")]
6270 async fn coerce_unknown_to_length() {
6271 let ast = r#"x = 2mm * 2mm
6272y = x: number(Length)"#;
6273 let e = parse_execute(ast).await.unwrap_err();
6274 assert!(
6275 e.message().contains("could not coerce"),
6276 "Error message: '{}'",
6277 e.message()
6278 );
6279
6280 let ast = r#"x = 2mm
6281y = x: number(Length)"#;
6282 let result = parse_execute(ast).await.unwrap();
6283 let mem = result.exec_state.stack();
6284 let num = mem
6285 .memory
6286 .get_from_owned("y", result.mem_env, SourceRange::default(), 0)
6287 .unwrap()
6288 .as_ty_f64()
6289 .unwrap();
6290 assert_eq!(num.n, 2.0);
6291 assert_eq!(num.ty, NumericType::mm());
6292 }
6293
6294 #[tokio::test(flavor = "multi_thread")]
6295 async fn one_warning_unknown() {
6296 let ast = r#"
6297// Should warn once
6298a = PI * 2
6299// Should warn once
6300b = (PI * 2) / 3
6301// Should not warn
6302c = ((PI * 2) / 3): number(deg)
6303"#;
6304
6305 let result = parse_execute(ast).await.unwrap();
6306 assert_eq!(result.exec_state.issues().len(), 2);
6307 }
6308
6309 #[tokio::test(flavor = "multi_thread")]
6310 async fn non_count_indexing() {
6311 let ast = r#"x = [0, 0]
6312y = x[1mm]
6313"#;
6314 parse_execute(ast).await.unwrap_err();
6315
6316 let ast = r#"x = [0, 0]
6317y = 1deg
6318z = x[y]
6319"#;
6320 parse_execute(ast).await.unwrap_err();
6321
6322 let ast = r#"x = [0, 0]
6323y = x[0mm + 1]
6324"#;
6325 parse_execute(ast).await.unwrap_err();
6326 }
6327
6328 #[tokio::test(flavor = "multi_thread")]
6329 async fn getting_property_of_plane() {
6330 let ast = std::fs::read_to_string("tests/inputs/planestuff.kcl").unwrap();
6331 parse_execute(&ast).await.unwrap();
6332 }
6333
6334 #[tokio::test(flavor = "multi_thread")]
6335 async fn no_artifacts_from_within_hole_call() {
6336 let ast = std::fs::read_to_string("tests/inputs/sample_hole.kcl").unwrap();
6341 let out = parse_execute(&ast).await.unwrap();
6342
6343 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6345
6346 let expected = 5;
6350 assert_eq!(
6351 actual_operations.len(),
6352 expected,
6353 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6354 actual_operations.len(),
6355 );
6356 }
6357
6358 #[tokio::test(flavor = "multi_thread")]
6359 async fn feature_tree_annotation_on_user_defined_kcl() {
6360 let ast = std::fs::read_to_string("tests/inputs/feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
6363 let out = parse_execute(&ast).await.unwrap();
6364
6365 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6367
6368 let expected = 0;
6369 assert_eq!(
6370 actual_operations.len(),
6371 expected,
6372 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6373 actual_operations.len(),
6374 );
6375 }
6376
6377 #[tokio::test(flavor = "multi_thread")]
6378 async fn no_feature_tree_annotation_on_user_defined_kcl() {
6379 let ast = std::fs::read_to_string("tests/inputs/no_feature_tree_annotation_on_user_defined_kcl.kcl").unwrap();
6382 let out = parse_execute(&ast).await.unwrap();
6383
6384 let actual_operations = out.exec_state.global.root_module_artifacts.operations;
6386
6387 let expected = 2;
6388 assert_eq!(
6389 actual_operations.len(),
6390 expected,
6391 "expected {expected} operations, received {}:\n{actual_operations:#?}",
6392 actual_operations.len(),
6393 );
6394 assert!(matches!(actual_operations[0], Operation::GroupBegin { .. }));
6395 assert!(matches!(actual_operations[1], Operation::GroupEnd));
6396 }
6397
6398 #[tokio::test(flavor = "multi_thread")]
6399 async fn custom_warning() {
6400 let warn = r#"
6401a = PI * 2
6402"#;
6403 let result = parse_execute(warn).await.unwrap();
6404 assert_eq!(result.exec_state.issues().len(), 1);
6405 assert_eq!(result.exec_state.issues()[0].severity, Severity::Warning);
6406
6407 let allow = r#"
6408@warnings(allow = unknownUnits)
6409a = PI * 2
6410"#;
6411 let result = parse_execute(allow).await.unwrap();
6412 assert_eq!(result.exec_state.issues().len(), 0);
6413
6414 let deny = r#"
6415@warnings(deny = [unknownUnits])
6416a = PI * 2
6417"#;
6418 let result = parse_execute(deny).await.unwrap();
6419 assert_eq!(result.exec_state.issues().len(), 1);
6420 assert_eq!(result.exec_state.issues()[0].severity, Severity::Error);
6421 }
6422
6423 #[tokio::test(flavor = "multi_thread")]
6424 async fn sketch_block_unqualified_functions_use_sketch2() {
6425 let ast = r#"
6426s = sketch(on = XY) {
6427 line1 = line(start = [var 0mm, var 0mm], end = [var 1mm, var 0mm])
6428 line2 = line(start = [var 1mm, var 0mm], end = [var 1mm, var 1mm])
6429 coincident([line1.end, line2.start])
6430}
6431"#;
6432 let result = parse_execute(ast).await.unwrap();
6433 let mem = result.exec_state.stack();
6434 let sketch_value = mem
6435 .memory
6436 .get_from_owned("s", result.mem_env, SourceRange::default(), 0)
6437 .unwrap();
6438
6439 let KclValue::Object { value, .. } = sketch_value else {
6440 panic!("Expected sketch block to return an object, got {sketch_value:?}");
6441 };
6442
6443 assert!(value.contains_key("line1"));
6444 assert!(value.contains_key("line2"));
6445 assert!(!value.contains_key("line"));
6448 assert!(!value.contains_key("coincident"));
6449 }
6450
6451 #[tokio::test(flavor = "multi_thread")]
6452 async fn solver_module_is_not_available_outside_sketch_blocks() {
6453 let err = parse_execute("a = solver::ORIGIN").await.unwrap_err();
6454 assert!(err.message().contains("solver"), "Error message: '{}'", err.message());
6455
6456 let err = parse_execute(
6457 r#"@settings(experimentalFeatures = allow)
6458
6459import "std::solver""#,
6460 )
6461 .await
6462 .unwrap_err();
6463 assert!(
6464 err.message().contains("only available inside sketch blocks"),
6465 "Error message: '{}'",
6466 err.message()
6467 );
6468 }
6469
6470 #[tokio::test(flavor = "multi_thread")]
6471 async fn cannot_solid_extrude_an_open_profile() {
6472 let code = std::fs::read_to_string("tests/inputs/cannot_solid_extrude_an_open_profile.kcl").unwrap();
6475 let program = crate::Program::parse_no_errs(&code).expect("should parse");
6476 let exec_ctxt = ExecutorContext::new_mock(None).await;
6477 let mut exec_state = ExecState::new(&exec_ctxt);
6478
6479 let err = exec_ctxt.run(&program, &mut exec_state).await.unwrap_err().error;
6480 assert!(matches!(err, KclError::Semantic { .. }));
6481 exec_ctxt.close().await;
6482 }
6483}