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