( body: Expression, elements: ReadonlyArray<Expression>, ce: ComputeEngine )
| 314 | // domain up front. |
| 315 | collection: comprehensionCollectionHandlers(), |
| 316 | }, |
| 317 | |
| 318 | // `Break`/`Continue` are inert: they have no `evaluate` handler, so they |
| 319 | // evaluate to themselves (with their operands evaluated) and are |
| 320 | // intercepted structurally by `Loop`/`Block`. They are NOT `lazy`: the |
| 321 | // optional `Break` value must be evaluated in the loop context so a value |
| 322 | // referencing the loop variable is concrete (mirrors `Return`). |
| 323 | Break: { |
| 324 | description: |
| 325 | 'Exit the enclosing loop immediately, optionally with a value ' + |
| 326 | '(`Break(v)`) that becomes the loop value.', |
| 327 | signature: '(value:any?) -> nothing', |
| 328 | }, |
| 329 | |
| 330 | Continue: { |
| 331 | description: 'Skip to the next iteration of the enclosing loop.', |
| 332 | signature: '() -> nothing', |
| 333 | }, |
| 334 | |
| 335 | When: { |
| 336 | description: |
| 337 | 'Conditional/restriction value. `When(e, cond)` evaluates to:\n' + |
| 338 | ' - `e` when `cond` evaluates to `True`\n' + |
| 339 | ' - `Undefined` when `cond` evaluates to `False` (the "masking rule"; consumers like 2D plotters skip masked points)\n' + |
| 340 | ' - `When(e, cond_simplified)` when `cond` is indeterminate (holds)\n' + |
| 341 | 'Stacked restrictions canonicalize: `When(When(e, c1), c2)` → `When(e, And(c1, c2))`.\n' + |
| 342 | 'Compiles to ternary `(cond) ? (e) : NaN` in JS and GLSL.', |
| 343 | lazy: true, |
| 344 | signature: '(expression, boolean) -> any', |
| 345 | typeHandlerKind: 'types', |
| 346 | type: ([expr, cond]) => { |
| 347 | // A list/vector-of-booleans condition broadcasts: the result is a |
| 348 | // list whose element type is `expr`'s type (see the broadcast branch |
| 349 | // in `evaluate`). Lazy operators bypass the generic list-broadcast |
| 350 | // typing wrapper, so lift the type here explicitly — but only when the |
| 351 | // condition's *declared* type is a list/vector of booleans. A scalar |
| 352 | // or unknown boolean condition keeps `expr`'s type. |
| 353 | // |
| 354 | // A type read must never crash, whatever the application looks |
| 355 | // like, so a malformed arity-0 `When` answers `unknown` here. (An |
| 356 | // earlier version of this handler dereferenced `expr.type` |
| 357 | // unconditionally and threw on that input.) |
| 358 | if (expr === undefined) return 'unknown'; |
| 359 | if ( |
| 360 | cond !== undefined && |
| 361 | isSubtype(cond.type, parseType('list<boolean>')!) |
| 362 | ) |
| 363 | return `list<${typeToString(expr.type)}>`; |
| 364 | return expr.type; |
| 365 | }, |
| 366 | canonical: (args, { engine: ce }) => { |
| 367 | if (args.length !== 2) return null; |
| 368 | const [expr, cond] = args; |
| 369 | // Canonicalize stacked restrictions: |
| 370 | // When(When(e, c1), c2) → When(e, And(c1, c2)) |
| 371 | if (isFunction(expr, 'When')) { |
| 372 | const inner = expr.op1.canonical; |
| 373 | const innerCond = expr.op2.canonical; |
no test coverage detected