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
@@ -27,7 +27,7 @@ const binaryOperationSyntax = Object.freeze({
27
27
  StringEqual: Object.freeze({default: "==", strict: "==="}),
28
28
  StringNotEqual: Object.freeze({default: "!=", strict: "!=="})
29
29
  })
30
- const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "dart", "zig"])
30
+ const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig"])
31
31
  const task006Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
32
32
  const task007Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
33
33
  const task008Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
@@ -627,7 +627,8 @@ function rejectBlockConditionLoop(block, language, seen = new Set()) {
627
627
  rejectBlockConditionLoop(Reflect.get(statement, "body"), language, seen)
628
628
  } else if (kind == "TryStatement") {
629
629
  rejectBlockConditionLoop(Reflect.get(statement, "body"), language, seen)
630
- rejectBlockConditionLoop(Reflect.get(statement, "catchBody"), language, seen)
630
+ for (const handler of Reflect.get(statement, "handlers") ?? []) rejectBlockConditionLoop(Reflect.get(handler, "catchBody"), language, seen)
631
+ rejectBlockConditionLoop(Reflect.get(statement, "finallyBody"), language, seen)
631
632
  }
632
633
  }
633
634
  }
@@ -658,7 +659,8 @@ function rejectBlockIteration(block, language, seen = new Set()) {
658
659
  rejectBlockIteration(Reflect.get(statement, "alternate"), language, seen)
659
660
  } else if (kind == "TryStatement") {
660
661
  rejectBlockIteration(Reflect.get(statement, "body"), language, seen)
661
- rejectBlockIteration(Reflect.get(statement, "catchBody"), language, seen)
662
+ for (const handler of Reflect.get(statement, "handlers") ?? []) rejectBlockIteration(Reflect.get(handler, "catchBody"), language, seen)
663
+ rejectBlockIteration(Reflect.get(statement, "finallyBody"), language, seen)
662
664
  }
663
665
  }
664
666
  }
@@ -713,7 +715,8 @@ function rejectBlockOptionalTypes(block, language) {
713
715
  if (statement.kind == "WhileStatement") rejectBlockOptionalTypes(statement.body, language)
714
716
  if (statement.kind == "TryStatement") {
715
717
  rejectBlockOptionalTypes(statement.body, language)
716
- rejectBlockOptionalTypes(statement.catchBody, language)
718
+ for (const handler of statement.handlers ?? []) rejectBlockOptionalTypes(handler.catchBody, language)
719
+ if (statement.finallyBody) rejectBlockOptionalTypes(statement.finallyBody, language)
717
720
  }
718
721
  }
719
722
  }
@@ -788,7 +791,8 @@ function rejectBlockCollectionTypes(block, language) {
788
791
  if (statement.kind == "WhileStatement") rejectBlockCollectionTypes(statement.body, language)
789
792
  if (statement.kind == "TryStatement") {
790
793
  rejectBlockCollectionTypes(statement.body, language)
791
- rejectBlockCollectionTypes(statement.catchBody, language)
794
+ for (const handler of statement.handlers ?? []) rejectBlockCollectionTypes(handler.catchBody, language)
795
+ if (statement.finallyBody) rejectBlockCollectionTypes(statement.finallyBody, language)
792
796
  }
793
797
  }
794
798
  }
@@ -856,9 +860,13 @@ function validateScaffoldingNames(module, language) {
856
860
  }
857
861
  }
858
862
  }
859
- if (statement.kind == "TryStatement" && ownedPrintReceiverNames.has(statement.catchBinding.name)) {
860
- unsupportedCapability(language, `${owner} catch binding '${statement.catchBinding.name}' captures backend scaffolding`,
861
- statement.catchBinding.location)
863
+ if (statement.kind == "TryStatement") {
864
+ for (const handler of statement.handlers ?? []) {
865
+ if (ownedPrintReceiverNames.has(handler.catchBinding.name)) {
866
+ unsupportedCapability(language, `${owner} catch binding '${handler.catchBinding.name}' captures backend scaffolding`,
867
+ handler.catchBinding.location)
868
+ }
869
+ }
862
870
  }
863
871
  }
864
872
  }
@@ -878,9 +886,13 @@ function validateScaffoldingNames(module, language) {
878
886
  }
879
887
  }
880
888
  }
881
- if (statement.kind == "TryStatement" && ownedEntryNames.has(statement.catchBinding.name)) {
882
- unsupportedCapability(language, `entry catch binding '${statement.catchBinding.name}' captures backend scaffolding`,
883
- statement.catchBinding.location)
889
+ if (statement.kind == "TryStatement") {
890
+ for (const handler of statement.handlers ?? []) {
891
+ if (ownedEntryNames.has(handler.catchBinding.name)) {
892
+ unsupportedCapability(language, `entry catch binding '${handler.catchBinding.name}' captures backend scaffolding`,
893
+ handler.catchBinding.location)
894
+ }
895
+ }
884
896
  }
885
897
  }
886
898
  for (const declaration of module.functions) {
@@ -949,12 +961,13 @@ function validateRecordConstructorNames(module, language) {
949
961
  }
950
962
  }
951
963
  }
952
- if (statement.kind == "TryStatement" && recordNames.has(statement.catchBinding.name)) {
953
- unsupportedCapability(
954
- language,
955
- `${owner} catch binding '${statement.catchBinding.name}' captures nominal constructor`,
956
- bindingNameLocation(statement.catchBinding)
957
- )
964
+ if (statement.kind == "TryStatement") {
965
+ const captured = (statement.handlers ?? []).find((handler) => recordNames.has(handler.catchBinding.name))
966
+
967
+ if (captured) {
968
+ unsupportedCapability(language, `${owner} catch binding '${captured.catchBinding.name}' captures nominal constructor`,
969
+ bindingNameLocation(captured.catchBinding))
970
+ }
958
971
  }
959
972
  }
960
973
  }
@@ -1052,12 +1065,15 @@ function validateJavaUtilFactoryNames(module) {
1052
1065
  }
1053
1066
  if (statement.kind == "TryStatement") {
1054
1067
  validateBlockNames(statement.body, capture, owner)
1055
- const catchCapture = statement.catchBinding.name == "java" ? {
1056
- detail: `${owner} catch binding 'java' captures java.util factory syntax`,
1057
- location: statement.catchBinding.location
1058
- } : capture
1068
+ for (const handler of statement.handlers ?? []) {
1069
+ const catchCapture = handler.catchBinding.name == "java" ? {
1070
+ detail: `${owner} catch binding 'java' captures java.util factory syntax`,
1071
+ location: handler.catchBinding.location
1072
+ } : capture
1059
1073
 
1060
- validateBlockNames(statement.catchBody, catchCapture, owner)
1074
+ validateBlockNames(handler.catchBody, catchCapture, owner)
1075
+ }
1076
+ if (statement.finallyBody) validateBlockNames(statement.finallyBody, capture, owner)
1061
1077
  }
1062
1078
  }
1063
1079
  }
@@ -1220,7 +1236,11 @@ function allStatements(block) {
1220
1236
  return [statement, ...allStatements(statement.body)]
1221
1237
  }
1222
1238
  if (statement.kind == "WhileStatement") return [statement, ...allStatements(statement.body)]
1223
- if (statement.kind == "TryStatement") return [statement, ...allStatements(statement.body), ...allStatements(statement.catchBody)]
1239
+ if (statement.kind == "TryStatement") {
1240
+ return [statement, ...allStatements(statement.body),
1241
+ ...(statement.handlers ?? []).flatMap((handler) => allStatements(handler.catchBody)),
1242
+ ...(statement.finallyBody ? allStatements(statement.finallyBody) : [])]
1243
+ }
1224
1244
 
1225
1245
  return [statement]
1226
1246
  })
@@ -1274,7 +1294,8 @@ function collectLoopOwners(module, language) {
1274
1294
  visit(Reflect.get(statement, "alternate"), owner, path)
1275
1295
  } else if (kind == "TryStatement") {
1276
1296
  visit(Reflect.get(statement, "body"), owner, path)
1277
- visit(Reflect.get(statement, "catchBody"), owner, path)
1297
+ for (const handler of Reflect.get(statement, "handlers") ?? []) visit(Reflect.get(handler, "catchBody"), owner, path)
1298
+ visit(Reflect.get(statement, "finallyBody"), owner, path)
1278
1299
  }
1279
1300
  }
1280
1301
  }
@@ -1536,30 +1557,55 @@ function validateStatement(statement, language, ownerLocation, loopContext, acti
1536
1557
  validateExpression(raised.message, language, raised.location, false, new Set(), externalDeclarationIds)
1537
1558
  return
1538
1559
  }
1560
+ if (kind == "RethrowStatement") {
1561
+ const rethrow = /** @type {import("../semantic/types.js").RethrowStatement} */ (statement)
1562
+ const rethrowLocation = requireSemanticLocation(ownLocation, language, "RethrowStatement location", ownerLocation)
1563
+
1564
+ if (Object.keys(rethrow).filter((key) => key != "sourceProvenance").sort().join(",") != "binding,bindingNode,kind,location" ||
1565
+ typeof rethrow.binding != "string" || rethrow.binding.length == 0 ||
1566
+ !rethrow.bindingNode || rethrow.bindingNode.kind != "CatchBinding" || rethrow.bindingNode.name != rethrow.binding) {
1567
+ unsupportedCapability(language, "malformed RethrowStatement", rethrowLocation)
1568
+ }
1569
+ validateTargetBindingIdentifier(language, rethrow.binding, "catch binding", rethrowLocation)
1570
+ return
1571
+ }
1539
1572
  if (kind == "TryStatement") {
1540
- const handler = /** @type {import("../semantic/types.js").TryStatement} */ (statement)
1573
+ const tryStatement = /** @type {import("../semantic/types.js").TryStatement} */ (statement)
1541
1574
  const tryLocation = requireSemanticLocation(ownLocation, language, "TryStatement location", ownerLocation)
1542
- const fields = Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",")
1575
+ const fields = Object.keys(tryStatement).filter((key) => key != "sourceProvenance").sort().join(",")
1543
1576
 
1544
- if (fields != "body,catchBinding,catchBody,catchType,kind,location") {
1577
+ if (fields != "body,handlers,kind,location" && fields != "body,finallyBody,handlers,kind,location") {
1545
1578
  unsupportedCapability(language, "malformed TryStatement", tryLocation)
1546
1579
  }
1547
- validateErrorTypeShape(handler.catchType, language, tryLocation)
1548
- const binding = handler.catchBinding
1549
- const bindingFields = binding && typeof binding == "object" && !Array.isArray(binding)
1550
- ? Object.keys(binding).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1551
- const bindingLocation = requireSemanticLocation(binding?.location, language, "catch binding location", tryLocation)
1552
-
1553
- if (!binding || binding.kind != "CatchBinding" || bindingFields != "kind,location,mutable,name,type" || binding.mutable !== false) {
1554
- unsupportedCapability(language, "malformed catch binding", bindingLocation)
1580
+ if (!Array.isArray(tryStatement.handlers) || (tryStatement.handlers.length == 0 && !tryStatement.finallyBody)) {
1581
+ unsupportedCapability(language, "TryStatement without an ordered handler list", tryLocation)
1555
1582
  }
1556
- validateTargetBindingIdentifier(language, binding.name, "catch binding", bindingLocation)
1557
- validateErrorTypeShape(binding.type, language, bindingLocation)
1558
- if (binding.type.declarationId != handler.catchType.declarationId) {
1559
- unsupportedCapability(language, "catch binding type mismatch", bindingLocation)
1583
+ validateBlock(tryStatement.body, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1584
+ for (const handler of tryStatement.handlers) {
1585
+ const handlerLocation = requireSemanticLocation(handler?.location, language, "catch handler location", tryLocation)
1586
+ const handlerFields = handler && typeof handler == "object" && !Array.isArray(handler)
1587
+ ? Object.keys(handler).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1588
+
1589
+ if (!handler || handler.kind != "CatchHandler" || handlerFields != "catchBinding,catchBody,catchType,kind,location") {
1590
+ unsupportedCapability(language, "malformed catch handler", handlerLocation)
1591
+ }
1592
+ validateErrorTypeShape(handler.catchType, language, handlerLocation)
1593
+ const binding = handler.catchBinding
1594
+ const bindingFields = binding && typeof binding == "object" && !Array.isArray(binding)
1595
+ ? Object.keys(binding).filter((key) => key != "sourceProvenance").sort().join(",") : ""
1596
+ const bindingLocation = requireSemanticLocation(binding?.location, language, "catch binding location", handlerLocation)
1597
+
1598
+ if (!binding || binding.kind != "CatchBinding" || bindingFields != "kind,location,mutable,name,type" || binding.mutable !== false) {
1599
+ unsupportedCapability(language, "malformed catch binding", bindingLocation)
1600
+ }
1601
+ validateTargetBindingIdentifier(language, binding.name, "catch binding", bindingLocation)
1602
+ validateErrorTypeShape(binding.type, language, bindingLocation)
1603
+ if (binding.type.declarationId != handler.catchType.declarationId) {
1604
+ unsupportedCapability(language, "catch binding type mismatch", bindingLocation)
1605
+ }
1606
+ validateBlock(handler.catchBody, language, handlerLocation, loopContext, activePath, externalDeclarationIds)
1560
1607
  }
1561
- validateBlock(handler.body, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1562
- validateBlock(handler.catchBody, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1608
+ if (tryStatement.finallyBody) validateBlock(tryStatement.finallyBody, language, tryLocation, loopContext, activePath, externalDeclarationIds)
1563
1609
  return
1564
1610
  }
1565
1611
 
@@ -2664,7 +2710,8 @@ function blockContainsSignProducingOperation(block) {
2664
2710
  return expressionContainsSignProducingOperation(statement.condition) || blockContainsSignProducingOperation(statement.body)
2665
2711
  }
2666
2712
  if (statement.kind == "TryStatement") {
2667
- return blockContainsSignProducingOperation(statement.body) || blockContainsSignProducingOperation(statement.catchBody)
2713
+ return blockContainsSignProducingOperation(statement.body) || (statement.handlers ?? []).some((handler) => blockContainsSignProducingOperation(handler.catchBody)) ||
2714
+ (statement.finallyBody ? blockContainsSignProducingOperation(statement.finallyBody) : false)
2668
2715
  }
2669
2716
  if (statement.kind == "RaiseStatement") return expressionContainsSignProducingOperation(statement.error.message)
2670
2717
  if (statement.kind == "LocalDeclaration") return expressionContainsSignProducingOperation(statement.initializer)
@@ -376,10 +376,14 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
376
376
  writer.synthetic(")\n", "error construction close", [statement.error], [`${path}/error`])
377
377
  return
378
378
  }
379
+ if (statement.kind == "RethrowStatement") {
380
+ writer.mapped("throw", {mappingKind: "anchor", node: statement, path})
381
+ writer.synthetic(" ", "throw spacing", [statement], [path])
382
+ writer.mapped(statement.binding, {mappingKind: "exact", node: statement.bindingNode, path: `${path}/bindingNode`, role: "name"})
383
+ writer.synthetic("\n", "line break", [statement], [path])
384
+ return
385
+ }
379
386
  if (statement.kind == "TryStatement") {
380
- const caught = writer.errorForId(statement.catchType.declarationId)
381
- const targetName = writer.errorNameForId(statement.catchType.declarationId)
382
-
383
387
  writer.mapped("try", {mappingKind: "anchor", node: statement, path})
384
388
  writer.synthetic(" ", "try spacing", [statement], [path])
385
389
  writer.mapped("{", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
@@ -387,21 +391,15 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
387
391
  emitBlock(writer, statement.body, `${indent} `, `${path}/body`, canonicalizeZero)
388
392
  writer.synthetic(indent, "indentation", [statement], [path])
389
393
  writer.mapped("}", {mappingKind: "anchor", node: statement.body, path: `${path}/body`})
390
- writer.synthetic(" ", "catch spacing", [statement], [path])
391
- writer.mapped("catch", {mappingKind: "anchor", node: statement, path, role: "catch"})
392
- writer.synthetic(" (", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
393
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
394
- writer.synthetic(") {\n", "catch binding scaffolding", [statement.catchBinding], [`${path}/catchBinding`])
395
- writer.synthetic(`${indent} if (!(`, "exact catch guard", [statement], [path])
396
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
397
- writer.synthetic(" instanceof ", "exact catch guard", [statement], [path])
398
- writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: statement.catchType, path: `${path}/catchType`, role: "type"})
399
- writer.synthetic(`)) {\n${indent} throw `, "unmatched error propagation", [statement], [path])
400
- writer.mapped(statement.catchBinding.name, {mappingKind: "exact", node: statement.catchBinding, path: `${path}/catchBinding`, role: "name"})
401
- writer.synthetic(`\n${indent} }\n`, "unmatched error propagation", [statement], [path])
402
- emitBlock(writer, statement.catchBody, `${indent} `, `${path}/catchBody`, canonicalizeZero)
403
- writer.synthetic(indent, "indentation", [statement], [path])
404
- writer.mapped("}", {mappingKind: "anchor", node: statement.catchBody, path: `${path}/catchBody`})
394
+ if (statement.handlers.length > 0) emitTypeScriptHandler(writer, statement, 0, indent, path, canonicalizeZero)
395
+ if (statement.finallyBody) {
396
+ writer.synthetic(" ", "finally spacing", [statement], [path])
397
+ writer.mapped("finally", {mappingKind: "anchor", node: statement, path, role: "finally"})
398
+ writer.synthetic(" {\n", "finally scaffolding", [statement], [path])
399
+ emitBlock(writer, statement.finallyBody, `${indent} `, `${path}/finallyBody`, canonicalizeZero)
400
+ writer.synthetic(indent, "indentation", [statement], [path])
401
+ writer.mapped("}", {mappingKind: "anchor", node: statement.finallyBody, path: `${path}/finallyBody`})
402
+ }
405
403
  writer.synthetic("\n", "line break", [statement], [path])
406
404
  return
407
405
  }
@@ -453,6 +451,73 @@ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
453
451
  writer.synthetic("\n", "line break", [statement], [path])
454
452
  }
455
453
 
454
+ /**
455
+ * Emits one ordered TypeScript catch handler with its exact nominal guard.
456
+ *
457
+ * TypeScript has no sibling catch clauses, so handler k > 0 hangs off handler k - 1's
458
+ * unmatched rethrow inside a nested try. Each handler body sits outside its own guard,
459
+ * so a raise from one handler body escapes outward instead of being caught by a later
460
+ * sibling handler, exactly like native ordered multi-catch.
461
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
462
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
463
+ * @param {number} index - Zero-based ordered handler index.
464
+ * @param {string} indent - Leading indentation for this catch clause.
465
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
466
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
467
+ * @returns {void}
468
+ */
469
+ function emitTypeScriptHandler(writer, statement, index, indent, path, canonicalizeZero) {
470
+ const handler = statement.handlers[index]
471
+ const handlerPath = `${path}/handlers/${index}`
472
+ const bodyIndent = `${indent} `
473
+ const caught = writer.errorForId(handler.catchType.declarationId)
474
+ const targetName = writer.errorNameForId(handler.catchType.declarationId)
475
+
476
+ writer.synthetic(" ", "catch spacing", [statement], [path])
477
+ writer.mapped("catch", {mappingKind: "anchor", node: handler, path: handlerPath, role: "catch"})
478
+ writer.synthetic(" (", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
479
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
480
+ writer.synthetic(") {\n", "catch binding scaffolding", [handler.catchBinding], [`${handlerPath}/catchBinding`])
481
+ writer.synthetic(`${bodyIndent}if (!(`, "exact catch guard", [handler], [handlerPath])
482
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
483
+ writer.synthetic(" instanceof ", "exact catch guard", [handler], [handlerPath])
484
+ writer.mapped(targetName, {mappingKind: "exact", name: caught.name, node: handler.catchType, path: `${handlerPath}/catchType`, role: "type"})
485
+ writer.synthetic(")) {\n", "exact catch guard", [handler], [handlerPath])
486
+ if (index + 1 < statement.handlers.length) {
487
+ emitTypeScriptDispatch(writer, statement, index, indent, path, canonicalizeZero)
488
+ writer.synthetic(`${bodyIndent}} else {\n`, "matched error handling", [handler], [handlerPath])
489
+ emitBlock(writer, handler.catchBody, `${bodyIndent} `, `${handlerPath}/catchBody`, canonicalizeZero)
490
+ writer.synthetic(`${bodyIndent}}\n`, "matched error handling close", [handler], [handlerPath])
491
+ } else {
492
+ writer.synthetic(`${bodyIndent} throw `, "unmatched error propagation", [handler], [handlerPath])
493
+ writer.mapped(handler.catchBinding.name, {mappingKind: "exact", node: handler.catchBinding, path: `${handlerPath}/catchBinding`, role: "name"})
494
+ writer.synthetic(`\n${bodyIndent}}\n`, "unmatched error propagation", [handler], [handlerPath])
495
+ emitBlock(writer, handler.catchBody, bodyIndent, `${handlerPath}/catchBody`, canonicalizeZero)
496
+ }
497
+ writer.synthetic(indent, "indentation", [handler], [handlerPath])
498
+ writer.mapped("}", {mappingKind: "anchor", node: handler.catchBody, path: `${handlerPath}/catchBody`})
499
+ }
500
+
501
+ /**
502
+ * Emits the nested re-dispatch of one unmatched caught value to the remaining ordered handlers.
503
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
504
+ * @param {import("../semantic/types.js").TryStatement} statement - Semantic try statement.
505
+ * @param {number} index - Index of the handler whose value is re-dispatched.
506
+ * @param {string} indent - Leading indentation for this dispatch try.
507
+ * @param {string} path - Exact JSON Pointer for the owning try statement.
508
+ * @param {boolean} canonicalizeZero - Whether scalar output canonicalizes zero.
509
+ * @returns {void}
510
+ */
511
+ function emitTypeScriptDispatch(writer, statement, index, indent, path, canonicalizeZero) {
512
+ writer.synthetic(`${indent} try {\n`, "ordered handler dispatch", [statement], [path])
513
+ writer.synthetic(`${indent} throw `, "ordered handler dispatch", [statement], [path])
514
+ writer.mapped(statement.handlers[index].catchBinding.name, {
515
+ mappingKind: "exact", node: statement.handlers[index].catchBinding, path: `${path}/handlers/${index}/catchBinding`, role: "name"
516
+ })
517
+ writer.synthetic(`\n${indent} }`, "ordered handler dispatch", [statement], [path])
518
+ emitTypeScriptHandler(writer, statement, index + 1, `${indent} `, path, canonicalizeZero)
519
+ }
520
+
456
521
  /**
457
522
  * Emits one TypeScript local statement.
458
523
  * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
@@ -12,7 +12,7 @@ import {withAdaptedOperation} from "../semantic/operators.js"
12
12
  import {withParserRanges} from "../semantic/provenance.js"
13
13
  import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
14
14
  import {validateParsedModule} from "../semantic/validate.js"
15
- import {sourceScalarType} from "./scalars.js"
15
+ import {sourceScalarType, requireSourceReturnType} from "./scalars.js"
16
16
 
17
17
  /** @typedef {import("./c-parser.js").CstNode} CstNode */
18
18
  /** @typedef {import("../semantic/types.js").Expression} Expression */
@@ -29,6 +29,15 @@ const helpers = new Map([
29
29
  ["semantifold_string_equal", "StringEqual"], ["semantifold_string_not_equal", "StringNotEqual"]
30
30
  ])
31
31
 
32
+ /**
33
+ * Reads the exact scalar-or-void type name of a C declaration annotation.
34
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType | import("../semantic/types.js").SemanticValueType} type - Converted C type.
35
+ * @returns {string} Type name.
36
+ */
37
+ function returnTypeName(type) {
38
+ return type.kind == "TypeReference" ? type.name : type.kind
39
+ }
40
+
32
41
  /** Strict field-and-token consumer for the private frozen C CST. */
33
42
  export class CReader {
34
43
  /**
@@ -48,7 +57,11 @@ export class CReader {
48
57
  /** @type {Map<Statement, {node: CstNode, type: string}>} */
49
58
  this.printTypes = new Map()
50
59
  /** @type {Set<string>} */
51
- this.declaredFunctions = new Set()
60
+ this.declaredFunctionNames = new Set()
61
+ /** @type {{name: string, node: CstNode, parameterTypes: string[], returnType: string}[]} */
62
+ this.prototypes = []
63
+ /** @type {boolean} */
64
+ this.voidFunction = false
52
65
  }
53
66
 
54
67
  /**
@@ -303,10 +316,9 @@ export class CReader {
303
316
  if (call.arguments.length != 2) this.fail(node, "scalar helper arity")
304
317
  return this.binary(node, intent, this.expression(call.arguments[0], true), this.expression(call.arguments[1], true), call.name)
305
318
  }
306
- if (call.arguments.length != 2) this.fail(node, "semantic call arity")
307
319
  const callee = this.identifier(call.name)
308
320
 
309
- if (!this.generated && !this.declaredFunctions.has(callee)) this.fail(call.name, "implicit function declaration")
321
+ if (!this.generated && !this.declaredFunctionNames.has(callee)) this.fail(call.name, "implicit function declaration")
310
322
  return withParserRanges({kind: "CallExpression", location, callee,
311
323
  arguments: call.arguments.map((argument) => this.expression(argument, true))}, {callee: this.location(call.name)})
312
324
  }
@@ -369,6 +381,11 @@ export class CReader {
369
381
  if (node.type == "return_statement") {
370
382
  const parts = this.parts(node)
371
383
 
384
+ if (parts.length == 2) {
385
+ this.shape(node, ["return", ";"])
386
+ if (!this.voidFunction) this.fail(node, "bare return requires a void function")
387
+ return {kind: "ReturnStatement", location}
388
+ }
372
389
  this.shape(node, ["return", parts[1], ";"])
373
390
  return {kind: "ReturnStatement", location, expression: this.expression(parts[1])}
374
391
  }
@@ -403,13 +420,18 @@ export class CReader {
403
420
  }
404
421
  const call = this.call(expression)
405
422
 
406
- if (!/^semantifold_print_(?:integer|boolean|string)$/u.test(this.text(call.name)) || call.arguments.length != 1) this.fail(node, "print scaffold")
407
- const value = this.expression(call.arguments[0])
423
+ if (/^semantifold_print_(?:integer|boolean|string)$/u.test(this.text(call.name))) {
424
+ if (call.arguments.length != 1) this.fail(node, "print scaffold")
425
+ const value = this.expression(call.arguments[0])
408
426
 
409
- const statement = {kind: /** @type {const} */ ("PrintStatement"), location, expression: value}
427
+ const statement = {kind: /** @type {const} */ ("PrintStatement"), location, expression: value}
410
428
 
411
- this.printTypes.set(statement, {node: call.name, type: this.text(call.name).slice("semantifold_print_".length)})
412
- return statement
429
+ this.printTypes.set(statement, {node: call.name, type: this.text(call.name).slice("semantifold_print_".length)})
430
+ return statement
431
+ }
432
+ return withParserRanges({kind: /** @type {const} */ ("ExpressionStatement"), location,
433
+ expression: /** @type {import("../semantic/types.js").CallExpression} */ (this.expression(expression))},
434
+ {operator: this.location(this.parts(node)[1])})
413
435
  }
414
436
  return this.fail(node)
415
437
  }
@@ -445,20 +467,98 @@ export class CReader {
445
467
  const parameters = this.field(declarator, "parameters")
446
468
 
447
469
  this.shape(declarator, [name, parameters])
448
- const parts = this.parts(parameters)
470
+ const converted = this.parameterList(parameters)
471
+ const returnType = requireSourceReturnType("c", this.text(type), "function return", this.location(node), this.location(type))
472
+ const outerVoid = this.voidFunction
473
+
474
+ this.voidFunction = returnType.kind == "TypeReference" && returnType.name == "void"
475
+ const convertedBody = this.block(body)
476
+
477
+ this.voidFunction = outerVoid
478
+ return withParserRanges({kind: "FunctionDeclaration", location: this.location(node), name: this.identifier(name), parameters: converted,
479
+ returnType, body: convertedBody}, {name: this.location(name)})
480
+ }
449
481
 
450
- this.shape(parameters, ["(", "parameter_declaration", ",", "parameter_declaration", ")"])
451
- const converted = [parts[1], parts[3]].map((parameter) => {
482
+ /**
483
+ * Validates one exact required positional parameter list and converts it in source order.
484
+ * @param {CstNode} parameters - Parameter list node.
485
+ * @returns {import("../semantic/types.js").Parameter[]} Converted parameters.
486
+ */
487
+ parameterList(parameters) {
488
+ const parts = this.parts(parameters)
489
+ const declarations = parts.slice(1, -1).filter((_part, index) => index % 2 == 0)
490
+ const voidOnly = declarations.length == 1 && parts.length == 3 && declarations[0].type == "parameter_declaration" &&
491
+ !declarations[0].children.some(({field}) => field == "declarator")
492
+
493
+ if (voidOnly) {
494
+ this.shape(parameters, ["(", "parameter_declaration", ")"])
495
+ if (this.text(declarations[0]) != "void") this.fail(declarations[0], "empty parameter list requires explicit void")
496
+ return []
497
+ }
498
+ if (declarations.length == 0) this.fail(parameters, "empty parameter list requires explicit void")
499
+ this.shape(parameters, ["(", ...declarations.flatMap((parameter, index) => index == 0 ? [parameter] : [",", parameter]), ")"])
500
+ return declarations.map((parameter) => {
452
501
  const typeNode = this.field(parameter, "type")
453
502
  const nameNode = this.field(parameter, "declarator")
454
503
 
455
504
  this.shape(parameter, [typeNode, nameNode])
456
505
  return withParserRanges({kind: /** @type {const} */ ("Parameter"), location: this.location(parameter), name: this.identifier(nameNode), type: this.type(typeNode)}, {name: this.location(nameNode)})
457
506
  })
507
+ }
458
508
 
459
- this.declaredFunctions.add(this.identifier(name))
460
- return withParserRanges({kind: "FunctionDeclaration", location: this.location(node), name: this.identifier(name), parameters: converted,
461
- returnType: this.type(type), body: this.block(body)}, {name: this.location(name)})
509
+ /**
510
+ * Recognizes a file-local function prototype declaration without descending further.
511
+ * @param {CstNode} node - Candidate translation-unit member.
512
+ * @returns {boolean} Whether this is a function prototype.
513
+ */
514
+ isPrototype(node) {
515
+ if (node.type != "declaration") return false
516
+ const declarator = node.children.find(({field}) => field == "declarator")?.node
517
+
518
+ return declarator?.type == "function_declarator"
519
+ }
520
+
521
+ /**
522
+ * Records one exact static prototype signature before any body conversion.
523
+ * @param {CstNode} node - Prototype declaration.
524
+ * @returns {void}
525
+ */
526
+ recordPrototype(node) {
527
+ const type = this.field(node, "type")
528
+ const declarator = this.field(node, "declarator")
529
+
530
+ this.shape(node, ["storage_class_specifier", type, declarator, ";"])
531
+ if (this.text(this.parts(node)[0]) != "static" || declarator.type != "function_declarator") this.fail(node, "file-local prototype required")
532
+ const name = this.field(declarator, "declarator")
533
+ const parameters = this.field(declarator, "parameters")
534
+
535
+ this.shape(declarator, [name, parameters])
536
+ const converted = this.parameterList(parameters)
537
+ const nameText = this.identifier(name)
538
+
539
+ this.declaredFunctionNames.add(nameText)
540
+ this.prototypes.push({name: nameText, node,
541
+ parameterTypes: converted.map((parameter) => /** @type {import("../semantic/types.js").TypeReference} */ (parameter.type).name),
542
+ returnType: /** @type {import("../semantic/types.js").TypeReference} */ (requireSourceReturnType("c", this.text(type), "prototype return", this.location(node), this.location(type))).name})
543
+ }
544
+
545
+ /**
546
+ * Confirms every recorded prototype agrees with its unique function definition.
547
+ * @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Converted definitions.
548
+ * @returns {void}
549
+ */
550
+ validatePrototypes(functions) {
551
+ const definitions = new Map(functions.map((declaration) => [declaration.name, declaration]))
552
+
553
+ for (const prototype of this.prototypes) {
554
+ const definition = definitions.get(prototype.name)
555
+
556
+ if (!definition) this.fail(prototype.node, "prototype without definition")
557
+ if (returnTypeName(definition.returnType) != prototype.returnType || definition.parameters.length != prototype.parameterTypes.length ||
558
+ definition.parameters.some((parameter, index) => returnTypeName(parameter.type) != prototype.parameterTypes[index])) {
559
+ this.fail(prototype.node, "prototype conflicts with function definition")
560
+ }
561
+ }
462
562
  }
463
563
 
464
564
  /**
@@ -511,17 +611,33 @@ export class CReader {
511
611
  if (!this.generated) this.rejectScaffoldComments(root)
512
612
  if (root.type != "translation_unit") this.fail(root, "translation unit required")
513
613
  const parts = this.parts(root)
514
- const definitions = parts.slice(1).filter((node) => !this.generated || node.type != "declaration")
614
+ const members = parts.slice(1).filter((node) => !this.generated || node.type == "function_definition" || this.isPrototype(node))
515
615
 
516
- if (parts.length < 3 || parts[0].type != "preproc_include" || definitions.some((node) => node.type != "function_definition")) this.fail(root, "translation unit members")
616
+ if (parts.length < 3 || parts[0].type != "preproc_include" ||
617
+ members.some((node) => node.type != "function_definition" && !this.isPrototype(node))) this.fail(root, "translation unit members")
517
618
  const include = parts[0]
518
619
  const header = this.field(include, "path")
519
620
 
520
621
  this.shape(include, ["#include", header])
521
622
  if (header.type != "string_literal" || this.text(header) != '"semantifold_runtime.h"') this.fail(include, "support header include")
623
+ const definitions = members.filter((node) => node.type == "function_definition")
624
+
522
625
  if (definitions.length < 2) this.fail(root, "semantic function and main required")
523
- const module = {kind: /** @type {const} */ ("Module"), location: this.location(root), functions: definitions.slice(0, -1).map((node) => this.function(node)),
524
- entryPoint: this.main(definitions[definitions.length - 1])}
626
+ const mainNode = definitions[definitions.length - 1]
627
+ const functions = []
628
+
629
+ for (const node of members) {
630
+ if (node === mainNode) continue
631
+ if (node.type != "function_definition") {
632
+ this.recordPrototype(node)
633
+ continue
634
+ }
635
+ this.declaredFunctionNames.add(this.identifier(this.field(this.field(node, "declarator"), "declarator")))
636
+ functions.push(this.function(node))
637
+ }
638
+ this.validatePrototypes(functions)
639
+ const module = {kind: /** @type {const} */ ("Module"), location: this.location(root), functions,
640
+ entryPoint: this.main(mainNode)}
525
641
 
526
642
  if (this.generated) validateNativeGraph(module)
527
643
  const validated = validateParsedModule(module, "c")