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