[Constraint 2.0 / C] Parametric expression grammar (+, -, *, /, min, max) #11

Closed
opened 2026-08-03 16:30:11 +00:00 by Mark · 0 comments
Owner

Parametric expression grammar — plate_length = wall_thickness * 2 + clearance

Why: today every numeric value in an annotation is parsed by parseFloat() in constraints.ts:159. So // @sf:place gap=${wall_thickness}+0.5 is silently truncated. Engineering constraints need real expressions: variables, arithmetic, min/max.

Spec: introduce a tiny expression layer. Grammar (EBNF):

Expr   := Term (('+' | '-') Term)*
Term   := Factor (('*' | '/') Factor)*
Factor := Number | ParamName | '(' Expr ')' | 'min(' Expr ',' Expr ')' | 'max(' Expr ',' Expr ')'
  • Number = -?\d+(\.\d+)? (same as today)
  • ParamName = [a-zA-Z_][\w]* — resolves through project-wide shared-param table (sub-issue #D) then file-local param annotations.

Implementation:

  • New src/lib/scad/expr.ts (~120 LOC): tokenizer + recursive-descent parser + evaluator with a Scope { params: Record<string, number> } interface.
  • constraints.ts:159 num() replaced with exprEval() on the parsed AST (cached on the constraint object).
  • fmt() already rounds to 3 decimals — extend to format expressions as flat numeric results in serializeConstraint() (the constraint surface stays numeric; only the solver evaluates expressions).

Design choice: AST or string round-trip?

  • Keep the .scad export concrete (no plate_length + 0.5 in real OpenSCAD, since OpenSCAD's parameter substitution doesn't see those). The expressions live in annotations only; the exported .scad always contains solved numbers. This matches the current behavior of constraintCodegen.ts:23 ("Linked values across modules reference the variables declared here") — just with a smarter source.
  • Inside the workbench, recompute on every shared-param change so the 3D viewport updates live.

Files to change:

  • src/lib/scad/expr.ts — new
  • src/lib/scad/constraints.ts — wire num() to exprEval()
  • src/lib/scad/solver.ts — pass Scope through during resolve
  • src/lib/scad/constraintCodegen.ts — already solved-numbers-on-export, no change needed
  • src/components/workbench/ConstraintPanel.tsx — when editing gap/distance/spacing, show formula input + live preview

Acceptance:

  • // @sf:place gap=0.5+wall_thickness*2 solves to wall_thickness * 2.5
  • Solver re-runs when wall_thickness changes; 3D viewport updates
  • Lint clean, build clean

Out of scope: unit conversions, conditional expressions, array formulas.

Labels: enhancement


Part of epic #8.

## Parametric expression grammar — `plate_length = wall_thickness * 2 + clearance` **Why:** today every numeric value in an annotation is parsed by `parseFloat()` in `constraints.ts:159`. So `// @sf:place gap=${wall_thickness}+0.5` is silently truncated. Engineering constraints need real expressions: variables, arithmetic, min/max. **Spec:** introduce a tiny expression layer. Grammar (EBNF): ``` Expr := Term (('+' | '-') Term)* Term := Factor (('*' | '/') Factor)* Factor := Number | ParamName | '(' Expr ')' | 'min(' Expr ',' Expr ')' | 'max(' Expr ',' Expr ')' ``` - `Number` = `-?\d+(\.\d+)?` (same as today) - `ParamName` = `[a-zA-Z_][\w]*` — resolves through project-wide shared-param table (sub-issue #D) then file-local `param` annotations. **Implementation:** - New `src/lib/scad/expr.ts` (~120 LOC): tokenizer + recursive-descent parser + evaluator with a `Scope { params: Record<string, number> }` interface. - `constraints.ts:159` `num()` replaced with `exprEval()` on the parsed AST (cached on the constraint object). - `fmt()` already rounds to 3 decimals — extend to format expressions as flat numeric results in `serializeConstraint()` (the constraint surface stays numeric; only the solver evaluates expressions). **Design choice: AST or string round-trip?** - Keep the `.scad` export concrete (no `plate_length + 0.5` in real OpenSCAD, since OpenSCAD's parameter substitution doesn't see those). The expressions live in **annotations only**; the exported `.scad` always contains solved numbers. This matches the current behavior of `constraintCodegen.ts:23` ("Linked values across modules reference the variables declared here") — just with a smarter source. - Inside the workbench, recompute on every shared-param change so the 3D viewport updates live. **Files to change:** - `src/lib/scad/expr.ts` — new - `src/lib/scad/constraints.ts` — wire `num()` to `exprEval()` - `src/lib/scad/solver.ts` — pass `Scope` through during resolve - `src/lib/scad/constraintCodegen.ts` — already solved-numbers-on-export, no change needed - `src/components/workbench/ConstraintPanel.tsx` — when editing gap/distance/spacing, show formula input + live preview **Acceptance:** - `// @sf:place gap=0.5+wall_thickness*2` solves to `wall_thickness * 2.5` - Solver re-runs when `wall_thickness` changes; 3D viewport updates - Lint clean, build clean **Out of scope:** unit conversions, conditional expressions, array formulas. **Labels:** enhancement --- Part of epic #8.
Mark closed this issue 2026-08-03 16:30:11 +00:00
Sign in to join this conversation.
No description provided.