semantifold 0.11.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +2 -1
  2. package/build/src/backends/c.d.ts +2 -1
  3. package/build/src/backends/c.d.ts.map +1 -1
  4. package/build/src/backends/c.js +26 -5
  5. package/build/src/backends/cpp.js +3 -1
  6. package/build/src/backends/effects.js +3 -2
  7. package/build/src/backends/java.js +33 -14
  8. package/build/src/backends/javascript.js +83 -18
  9. package/build/src/backends/ordered-expressions.d.ts +19 -7
  10. package/build/src/backends/ordered-expressions.d.ts.map +1 -1
  11. package/build/src/backends/ordered-expressions.js +46 -11
  12. package/build/src/backends/php.js +33 -14
  13. package/build/src/backends/ruby.js +37 -12
  14. package/build/src/backends/shared.d.ts.map +1 -1
  15. package/build/src/backends/shared.js +92 -37
  16. package/build/src/backends/typescript.js +83 -18
  17. package/build/src/frontends/c.d.ts +34 -1
  18. package/build/src/frontends/c.d.ts.map +1 -1
  19. package/build/src/frontends/c.js +128 -21
  20. package/build/src/frontends/java.d.ts +8 -0
  21. package/build/src/frontends/java.d.ts.map +1 -1
  22. package/build/src/frontends/java.js +77 -33
  23. package/build/src/frontends/javascript-typescript.d.ts +2 -0
  24. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  25. package/build/src/frontends/javascript-typescript.js +118 -37
  26. package/build/src/frontends/php.d.ts +8 -0
  27. package/build/src/frontends/php.d.ts.map +1 -1
  28. package/build/src/frontends/php.js +68 -29
  29. package/build/src/frontends/ruby.d.ts +3 -0
  30. package/build/src/frontends/ruby.d.ts.map +1 -1
  31. package/build/src/frontends/ruby.js +103 -30
  32. package/build/src/language-registry.d.ts.map +1 -1
  33. package/build/src/language-registry.js +16 -1
  34. package/build/src/semantic/effects.d.ts.map +1 -1
  35. package/build/src/semantic/effects.js +18 -6
  36. package/build/src/semantic/lifetime.js +28 -4
  37. package/build/src/semantic/provenance.d.ts.map +1 -1
  38. package/build/src/semantic/provenance.js +26 -7
  39. package/build/src/semantic/types.d.ts +76 -16
  40. package/build/src/semantic/types.d.ts.map +1 -1
  41. package/build/src/semantic/types.js +23 -7
  42. package/build/src/semantic/validate.d.ts.map +1 -1
  43. package/build/src/semantic/validate.js +106 -35
  44. package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
  45. package/docs/adr-numeric-tower.md +156 -0
  46. package/docs/architecture.md +1 -1
  47. package/docs/c.md +1 -1
  48. package/docs/language-support.md +10 -9
  49. package/docs/testing.md +8 -5
  50. package/package.json +1 -1
  51. package/src/backends/c.js +23 -4
  52. package/src/backends/cpp.js +1 -0
  53. package/src/backends/effects.js +2 -1
  54. package/src/backends/java.js +33 -14
  55. package/src/backends/javascript.js +83 -18
  56. package/src/backends/ordered-expressions.js +46 -10
  57. package/src/backends/php.js +33 -14
  58. package/src/backends/ruby.js +36 -12
  59. package/src/backends/shared.js +89 -42
  60. package/src/backends/typescript.js +83 -18
  61. package/src/frontends/c.js +135 -19
  62. package/src/frontends/java.js +82 -33
  63. package/src/frontends/javascript-typescript.js +130 -38
  64. package/src/frontends/php.js +68 -27
  65. package/src/frontends/ruby.js +107 -28
  66. package/src/language-registry.js +15 -0
  67. package/src/semantic/effects.js +17 -5
  68. package/src/semantic/lifetime.js +29 -3
  69. package/src/semantic/provenance.js +27 -12
  70. package/src/semantic/types.js +24 -6
  71. package/src/semantic/validate.js +105 -36
@@ -391,9 +391,15 @@ function emitStatement(writer, statement, indent, path) {
391
391
  writer.synthetic(");\n", "error construction close", [statement.error], [`${path}/error`])
392
392
  return
393
393
  }
394
+ if (statement.kind == "RethrowStatement") {
395
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
396
+ writer.synthetic(" ", "throw spacing", [statement], [path])
397
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
398
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
399
+ writer.synthetic("\n", "line break", [statement], [path])
400
+ return
401
+ }
394
402
  if (statement.kind == "TryStatement") {
395
- const caught = writer.errorForId(statement.catchType.declarationId)
396
-
397
403
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
398
404
  writer.synthetic(" ", "try spacing", [statement], [path])
399
405
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -401,18 +407,31 @@ function emitStatement(writer, statement, indent, path) {
401
407
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
402
408
  writer.synthetic(indent, "indentation", [statement], [path])
403
409
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
404
- writer.synthetic(" ", "catch spacing", [statement], [path])
405
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
406
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
407
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
408
- writer.synthetic(" ", "catch binding spacing", [statement.catchBinding], [`${path}/catchBinding`])
409
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
410
- writer.synthetic(") ", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
411
- writer.mapped("{", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
412
- writer.synthetic("\n", "line break", [statement], [path])
413
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
414
- writer.synthetic(indent, "indentation", [statement], [path])
415
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
410
+ for (const [index, handler] of statement.handlers.entries()) {
411
+ const handlerPath = `${path}/handlers/${index}`
412
+ const caught = writer.errorForId(handler.catchType.declarationId)
413
+
414
+ writer.synthetic(" ", "catch spacing", [handler], [handlerPath])
415
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
416
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
417
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
418
+ writer.synthetic(" ", "catch binding spacing", [handler.catchBinding], [`${handlerPath}/catchBinding`])
419
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
420
+ writer.synthetic(") ", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
421
+ writer.mapped("{", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
422
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
423
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
424
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
425
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
426
+ }
427
+ if (statement.finallyBody) {
428
+ writer.synthetic(" ", "finally spacing", [statement], [path])
429
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
430
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
431
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
432
+ writer.synthetic(indent, "indentation", [statement], [path])
433
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
434
+ }
416
435
  writer.synthetic("\n", "line break", [statement], [path])
417
436
  return
418
437
  }
@@ -427,10 +427,14 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
427
427
  writer.synthetic(")\n", "error construction close", [statement.error], [`${path}/error`])
428
428
  return
429
429
  }
430
+ if (statement.kind == "RethrowStatement") {
431
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
432
+ writer.synthetic(" ", "throw spacing", [statement], [path])
433
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
434
+ writer.synthetic("\n", "line break", [statement], [path])
435
+ return
436
+ }
430
437
  if (statement.kind == "TryStatement") {
431
- const caught = writer.errorForId(statement.catchType.declarationId)
432
- const targetName = writer.errorNameForId(statement.catchType.declarationId)
433
-
434
438
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
435
439
  writer.synthetic(" ", "try spacing", [statement], [path])
436
440
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -438,21 +442,15 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
438
442
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`, canonicalizeZero)
439
443
  writer.synthetic(indent, "indentation", [statement], [path])
440
444
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
441
- writer.synthetic(" ", "catch spacing", [statement], [path])
442
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
443
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
444
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
445
- writer.synthetic(") {\n", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
446
- writer.synthetic(`${indent} if (!(`, "exact catch guard", [statement], [path])
447
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
448
- writer.synthetic(" instanceof ", "exact catch guard", [statement], [path])
449
- writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
450
- writer.synthetic(`)) {\n${indent} throw `, "unmatched error propagation", [statement], [path])
451
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
452
- writer.synthetic(`\n${indent} }\n`, "unmatched error propagation", [statement], [path])
453
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`, canonicalizeZero)
454
- writer.synthetic(indent, "indentation", [statement], [path])
455
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
445
+ if (statement.handlers.length > 0) emitJavaScriptHandler(writer, statement, 0, indent, path, canonicalizeZero)
446
+ if (statement.finallyBody) {
447
+ writer.synthetic(" ", "finally spacing", [statement], [path])
448
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
449
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
450
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`, canonicalizeZero)
451
+ writer.synthetic(indent, "indentation", [statement], [path])
452
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
453
+ }
456
454
  writer.synthetic("\n", "line break", [statement], [path])
457
455
  return
458
456
  }
@@ -505,6 +503,73 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
505
503
  writer.synthetic("\n", "line break", [statement], [path])
506
504
  }
507
505
 
506
+ /**
507
+ * Emits one ordered JavaScript catch handler with its exact nominal guard.
508
+ *
509
+ * JavaScript has no sibling catch clauses, so handler k > 0 hangs off handler k - 1's
510
+ * unmatched rethrow inside a nested try. Each handler body sits outside its own guard,
511
+ * so a raise from one handler body escapes outward instead of being caught by a later
512
+ * sibling handler, exactly like native ordered multi-catch.
513
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
514
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
515
+ * @param {number} index - Zero-based ordered handler index.
516
+ * @param {string} indent - Leading indentation for this catch clause.
517
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
518
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
519
+ * @returns {void}
520
+ */
521
+ function emitJavaScriptHandler(writer, statement, index, indent, path, canonicalizeZero) {
522
+ const handler = statement.handlers[index]
523
+ const handlerPath = `${path}/handlers/${index}`
524
+ const bodyIndent = `${indent} `
525
+ const caught = writer.errorForId(handler.catchType.declarationId)
526
+ const targetName = writer.errorNameForId(handler.catchType.declarationId)
527
+
528
+ writer.synthetic(" ", "catch spacing", [statement], [path])
529
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
530
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
531
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
532
+ writer.synthetic(") {\n", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
533
+ writer.synthetic(`${bodyIndent}if (!(`, "exact catch guard", [handler], [handlerPath])
534
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
535
+ writer.synthetic(" instanceof ", "exact catch guard", [handler], [handlerPath])
536
+ writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
537
+ writer.synthetic(")) {\n", "exact catch guard", [handler], [handlerPath])
538
+ if (index + 1 < statement.handlers.length) {
539
+ emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero)
540
+ writer.synthetic(`${bodyIndent}} else {\n`, "matched error handling", [handler], [handlerPath])
541
+ emitBlock(writer, handler.catchBody, `${bodyIndent} `, `${handlerPath}/catchBody`, canonicalizeZero)
542
+ writer.synthetic(`${bodyIndent}}\n`, "matched error handling close", [handler], [handlerPath])
543
+ } else {
544
+ writer.synthetic(`${bodyIndent} throw `, "unmatched error propagation", [handler], [handlerPath])
545
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
546
+ writer.synthetic(`\n${bodyIndent}}\n`, "unmatched error propagation", [handler], [handlerPath])
547
+ emitBlock(writer, handler.catchBody, bodyIndent, `${handlerPath}/catchBody`, canonicalizeZero)
548
+ }
549
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
550
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
551
+ }
552
+
553
+ /**
554
+ * Emits the nested re-dispatch of one unmatched caught value to the remaining ordered handlers.
555
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
556
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
557
+ * @param {number} index - Index of the handler whose value is re-dispatched.
558
+ * @param {string} indent - Leading indentation for this dispatch try.
559
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
560
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
561
+ * @returns {void}
562
+ */
563
+ function emitJavaScriptDispatch(writer, statement, index, indent, path, canonicalizeZero) {
564
+ writer.synthetic(`${indent} try {\n`, "ordered handler dispatch", [statement], [path])
565
+ writer.synthetic(`${indent} throw `, "ordered handler dispatch", [statement], [path])
566
+ writer.mapped(statement.handlers[index].catchBinding.name, {
567
+ mappingKind: "exact", node: statement.handlers[index].catchBinding, path: `${path}/handlers/${index}/catchBinding`, role: "name"
568
+ })
569
+ writer.synthetic(`\n${indent} }`, "ordered handler dispatch", [statement], [path])
570
+ emitJavaScriptHandler(writer, statement, index + 1, `${indent} `, path, canonicalizeZero)
571
+ }
572
+
508
573
  /**
509
574
  * Emits one JavaScript local statement and its JSDoc type carrier.
510
575
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
@@ -5,7 +5,8 @@ import {unsupportedCapability} from "../diagnostic.js"
5
5
 
6
6
  /** @typedef {import("../semantic/types.js").Expression} Expression */
7
7
  /** @typedef {import("../semantic/types.js").SemanticTypeName} Scalar */
8
- /** @typedef {{expression: Expression, path: string, type: Scalar, name?: string}} PlannedValue */
8
+ /** @typedef {Scalar | "void"} PlannedType */
9
+ /** @typedef {{expression: Expression, path: string, type: PlannedType, name?: string}} PlannedValue */
9
10
  /** @typedef {{result: PlannedValue, operands: PlannedValue[], rightSteps?: PlannedStep[]}} PlannedStep */
10
11
  /** @typedef {Array<string | number | boolean | Signature>} Signature */
11
12
 
@@ -20,7 +21,7 @@ export class OrderedExpressionPlanner {
20
21
  this.language = language
21
22
  this.functions = new Map(module.functions.map((declaration) => [
22
23
  declaration.name,
23
- scalarName(declaration.returnType, language, declaration.location)
24
+ returnName(declaration.returnType, language, declaration.location)
24
25
  ]))
25
26
  this.nextTemporary = 1
26
27
  }
@@ -29,7 +30,7 @@ export class OrderedExpressionPlanner {
29
30
  * Resolves an expression type from the validated lexical environment.
30
31
  * @param {Expression} expression - Semantic expression.
31
32
  * @param {Map<string, Scalar>} bindings - Lexical scalar types.
32
- * @returns {Scalar} Exact semantic result type.
33
+ * @returns {PlannedType} Exact semantic result type.
33
34
  */
34
35
  type(expression, bindings) {
35
36
  if (expression.kind == "IntegerLiteral") return "integer"
@@ -76,12 +77,31 @@ export class OrderedExpressionPlanner {
76
77
  }
77
78
  operands.push(this.plan(expression.right, `${path}/right`, bindings, steps))
78
79
  }
80
+ if (result.type == "void") throw new Error("Void call reached native value planning.")
79
81
  const planned = {...result, name: this.allocate(expression)}
80
82
 
81
83
  steps.push({operands, result: planned})
82
84
  return planned
83
85
  }
84
86
 
87
+ /**
88
+ * Plans the arguments of one terminal void call without inventing a void temporary.
89
+ * @param {Expression} expression - Terminal expression occurrence.
90
+ * @param {string} path - Occurrence path.
91
+ * @param {Map<string, Scalar>} bindings - Visible scalar bindings.
92
+ * @param {PlannedStep[]} steps - Ordered enclosing prelude.
93
+ * @returns {PlannedStep} Terminal direct-call step.
94
+ */
95
+ terminal(expression, path, bindings, steps) {
96
+ if (expression.kind != "CallExpression" || this.type(expression, bindings) != "void") {
97
+ throw new Error("Non-void expression reached native terminal-call planning.")
98
+ }
99
+ const result = {expression, path, type: /** @type {const} */ ("void")}
100
+ const operands = expression.arguments.map((argument, index) => this.plan(argument, `${path}/arguments/${index}`, bindings, steps))
101
+
102
+ return {operands, result}
103
+ }
104
+
85
105
  /**
86
106
  * Allocates one bounded deterministic occurrence name.
87
107
  * @param {Expression} expression - Related expression.
@@ -108,9 +128,9 @@ export function statementSignature(statement) {
108
128
  statement.kind == "AssignmentStatement" || statement.kind == "ExpressionStatement" || statement.kind == "PrintStatement" ? statement.expression :
109
129
  statement.kind == "ReturnStatement" ? statement.expression : undefined
110
130
 
111
- if (!expression) throw new TypeError("Bare return reached native statement signature planning.")
131
+ if (!expression && statement.kind != "ReturnStatement") throw new TypeError("Bare return reached native statement signature planning.")
112
132
 
113
- return createHash("sha256").update(JSON.stringify([consumer, expressionSignature(expression)])).digest("hex")
133
+ return createHash("sha256").update(JSON.stringify([consumer, expression ? expressionSignature(expression) : null])).digest("hex")
114
134
  }
115
135
 
116
136
  /**
@@ -128,7 +148,7 @@ function expressionSignature(expression) {
128
148
  return [expression.kind, expression.operation, expression.type, expressionSignature(expression.left), expressionSignature(expression.right)]
129
149
  }
130
150
 
131
- /** @typedef {{id: string, steps: PlannedStep[], value: PlannedValue}} StatementPlan */
151
+ /** @typedef {{id: string, steps: PlannedStep[], value?: PlannedValue, terminal?: PlannedStep}} StatementPlan */
132
152
  /**
133
153
  * Plans every occurrence before constructing a writer or exposing either artifact.
134
154
  * @param {import("../semantic/types.js").SemanticModule} module - Validated module.
@@ -159,12 +179,13 @@ export function planNativeModule(module, language = "c") {
159
179
  statement.kind == "ReturnStatement" ? statement.expression : undefined
160
180
  /** @type {PlannedStep[]} */
161
181
  const steps = []
162
-
163
- if (!expression) throw new TypeError("Bare return reached native expression planning.")
164
- const value = planner.plan(expression, `${statementPath}/${field}`, bindings, steps)
182
+ /** @type {StatementPlan} */
183
+ const plan = {id: String(plans.size + 1).padStart(6, "0"), steps}
165
184
 
166
185
  if (plans.size >= 999999) unsupportedCapability(language, "ordered statement limit", statement.location)
167
- plans.set(statementPath, {id: String(plans.size + 1).padStart(6, "0"), steps, value})
186
+ if (statement.kind == "ExpressionStatement") plan.terminal = planner.terminal(statement.expression, `${statementPath}/${field}`, bindings, steps)
187
+ else if (expression) plan.value = planner.plan(expression, `${statementPath}/${field}`, bindings, steps)
188
+ plans.set(statementPath, plan)
168
189
  if (statement.kind == "LocalDeclaration") bindings.set(statement.name, scalarName(statement.type, language, statement.location))
169
190
  if (statement.kind == "IfStatement") {
170
191
  visit(statement.consequent, `${statementPath}/consequent`, bindings)
@@ -193,3 +214,18 @@ function scalarName(type, language, location) {
193
214
 
194
215
  return type.name
195
216
  }
217
+
218
+ /**
219
+ * Narrows a validated function return, admitting the non-value `void` result.
220
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType} type - Semantic type.
221
+ * @param {"c" | "cpp"} language - Diagnostic language.
222
+ * @param {import("../semantic/types.js").SourceLocation} location - Owning location.
223
+ * @returns {PlannedType} Scalar or void name.
224
+ */
225
+ function returnName(type, language, location) {
226
+ if (type.kind != "TypeReference") {
227
+ return unsupportedCapability(language, "collection type reached native scalar planning", location)
228
+ }
229
+
230
+ return type.name
231
+ }
@@ -489,9 +489,15 @@ function emitStatement(writer, statement, indent, path) {
489
489
  writer.synthetic(");\n", "error construction close", [statement.error], [`${path}/error`])
490
490
  return
491
491
  }
492
+ if (statement.kind == "RethrowStatement") {
493
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
494
+ writer.synthetic(" ", "throw spacing", [statement], [path])
495
+ writer.mapped(`$${statement.binding}`, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
496
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
497
+ writer.synthetic("\n", "line break", [statement], [path])
498
+ return
499
+ }
492
500
  if (statement.kind == "TryStatement") {
493
- const caught = writer.errorForId(statement.catchType.declarationId)
494
-
495
501
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
496
502
  writer.synthetic(" ", "try spacing", [statement], [path])
497
503
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -499,18 +505,31 @@ function emitStatement(writer, statement, indent, path) {
499
505
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
500
506
  writer.synthetic(indent, "indentation", [statement], [path])
501
507
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
502
- writer.synthetic(" ", "catch spacing", [statement], [path])
503
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
504
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
505
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
506
- writer.synthetic(" ", "catch binding spacing", [statement.catchBinding], [`${path}/catchBinding`])
507
- writer.mapped(`$${statement.catchBinding.name}`, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
508
- writer.synthetic(") ", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
509
- writer.mapped("{", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
510
- writer.synthetic("\n", "line break", [statement], [path])
511
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
512
- writer.synthetic(indent, "indentation", [statement], [path])
513
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
508
+ for (const [index, handler] of statement.handlers.entries()) {
509
+ const handlerPath = `${path}/handlers/${index}`
510
+ const caught = writer.errorForId(handler.catchType.declarationId)
511
+
512
+ writer.synthetic(" ", "catch spacing", [handler], [handlerPath])
513
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
514
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
515
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
516
+ writer.synthetic(" ", "catch binding spacing", [handler.catchBinding], [`${handlerPath}/catchBinding`])
517
+ writer.mapped(`$${handler.catchBinding.name}`, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
518
+ writer.synthetic(") ", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
519
+ writer.mapped("{", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
520
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
521
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
522
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
523
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
524
+ }
525
+ if (statement.finallyBody) {
526
+ writer.synthetic(" ", "finally spacing", [statement], [path])
527
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
528
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
529
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
530
+ writer.synthetic(indent, "indentation", [statement], [path])
531
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
532
+ }
514
533
  writer.synthetic("\n", "line break", [statement], [path])
515
534
  return
516
535
  }
@@ -364,22 +364,46 @@ function emitStatement(writer, statement, indent, path) {
364
364
  writer.synthetic("\n", "line break", [statement], [path])
365
365
  return
366
366
  }
367
+ if (statement.kind == "RethrowStatement") {
368
+ writer.mapped("raise", {mappingKind: "anchor", node: statement, path})
369
+ writer.synthetic(" ", "raise spacing", [statement], [path])
370
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
371
+ writer.synthetic("\n", "line break", [statement], [path])
372
+ return
373
+ }
367
374
  if (statement.kind == "TryStatement") {
368
- const caught = writer.errorForId(statement.catchType.declarationId)
369
-
370
375
  writer.mapped("begin", {mappingKind: "anchor", node: statement, path})
371
376
  writer.synthetic("\n", "line break", [statement], [path])
372
377
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`)
373
- writer.synthetic(indent, "indentation", [statement], [path])
374
- writer.mapped("rescue", {mappingKind: "anchor", node: statement, path, role: "catch"})
375
- writer.synthetic(" ", "rescue spacing", [statement], [path])
376
- writer.mapped(writer.errorNameForId(statement.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
377
- writer.synthetic(" => ", "catch binding separator", [statement.catchBinding], [`${path}/catchBinding`])
378
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
379
- writer.synthetic("\n", "line break", [statement], [path])
380
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`)
381
- writer.synthetic(indent, "indentation", [statement], [path])
382
- writer.mapped("end", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
378
+ for (const [index, handler] of statement.handlers.entries()) {
379
+ const handlerPath = `${path}/handlers/${index}`
380
+ const caught = writer.errorForId(handler.catchType.declarationId)
381
+
382
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
383
+ writer.mapped("rescue", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
384
+ writer.synthetic(" ", "rescue spacing", [handler], [handlerPath])
385
+ writer.mapped(writer.errorNameForId(handler.catchType.declarationId), {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
386
+ writer.synthetic(" => ", "catch binding separator", [handler.catchBinding], [`${handlerPath}/catchBinding`])
387
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
388
+ writer.synthetic("\n", "line break", [handler], [handlerPath])
389
+ emitBlock(writer, handler.catchBody, `${indent} `, `${handlerPath}/catchBody`)
390
+ }
391
+ if (statement.finallyBody) {
392
+ writer.synthetic(indent, "indentation", [statement], [path])
393
+ writer.mapped("ensure", {mappingKind: "anchor", node: statement, path, role: "ensure"})
394
+ writer.synthetic("\n", "line break", [statement], [path])
395
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`)
396
+ writer.synthetic(indent, "indentation", [statement], [path])
397
+ writer.mapped("end", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
398
+ } else if (statement.handlers.length > 0) {
399
+ const lastIndex = statement.handlers.length - 1
400
+
401
+ writer.synthetic(indent, "indentation", [statement], [path])
402
+ writer.mapped("end", {mappingKind: "anchor", node: statement.handlers[lastIndex].catchBody, path: `${path}/handlers/${lastIndex}/catchBody`})
403
+ } else {
404
+ writer.synthetic(indent, "indentation", [statement], [path])
405
+ writer.mapped("end", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
406
+ }
383
407
  writer.synthetic("\n", "line break", [statement], [path])
384
408
  return
385
409
  }