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.
- package/README.md +2 -1
- package/build/src/backends/c.d.ts +2 -1
- package/build/src/backends/c.d.ts.map +1 -1
- package/build/src/backends/c.js +26 -5
- package/build/src/backends/cpp.js +3 -1
- package/build/src/backends/effects.js +3 -2
- package/build/src/backends/java.js +33 -14
- package/build/src/backends/javascript.js +83 -18
- package/build/src/backends/ordered-expressions.d.ts +19 -7
- package/build/src/backends/ordered-expressions.d.ts.map +1 -1
- package/build/src/backends/ordered-expressions.js +46 -11
- package/build/src/backends/php.js +33 -14
- package/build/src/backends/ruby.js +37 -12
- package/build/src/backends/shared.d.ts.map +1 -1
- package/build/src/backends/shared.js +92 -37
- package/build/src/backends/typescript.js +83 -18
- package/build/src/frontends/c.d.ts +34 -1
- package/build/src/frontends/c.d.ts.map +1 -1
- package/build/src/frontends/c.js +128 -21
- package/build/src/frontends/java.d.ts +8 -0
- package/build/src/frontends/java.d.ts.map +1 -1
- package/build/src/frontends/java.js +77 -33
- package/build/src/frontends/javascript-typescript.d.ts +2 -0
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
- package/build/src/frontends/javascript-typescript.js +118 -37
- package/build/src/frontends/php.d.ts +8 -0
- package/build/src/frontends/php.d.ts.map +1 -1
- package/build/src/frontends/php.js +68 -29
- package/build/src/frontends/ruby.d.ts +3 -0
- package/build/src/frontends/ruby.d.ts.map +1 -1
- package/build/src/frontends/ruby.js +103 -30
- package/build/src/language-registry.d.ts.map +1 -1
- package/build/src/language-registry.js +16 -1
- package/build/src/semantic/effects.d.ts.map +1 -1
- package/build/src/semantic/effects.js +18 -6
- package/build/src/semantic/lifetime.js +28 -4
- package/build/src/semantic/provenance.d.ts.map +1 -1
- package/build/src/semantic/provenance.js +26 -7
- package/build/src/semantic/types.d.ts +76 -16
- package/build/src/semantic/types.d.ts.map +1 -1
- package/build/src/semantic/types.js +23 -7
- package/build/src/semantic/validate.d.ts.map +1 -1
- package/build/src/semantic/validate.js +106 -35
- package/docs/adr/20261026-typed-error-handling-extension.md +55 -0
- package/docs/adr-numeric-tower.md +156 -0
- package/docs/architecture.md +1 -1
- package/docs/c.md +1 -1
- package/docs/language-support.md +10 -9
- package/docs/testing.md +8 -5
- package/package.json +1 -1
- package/src/backends/c.js +23 -4
- package/src/backends/cpp.js +1 -0
- package/src/backends/effects.js +2 -1
- package/src/backends/java.js +33 -14
- package/src/backends/javascript.js +83 -18
- package/src/backends/ordered-expressions.js +46 -10
- package/src/backends/php.js +33 -14
- package/src/backends/ruby.js +36 -12
- package/src/backends/shared.js +89 -42
- package/src/backends/typescript.js +83 -18
- package/src/frontends/c.js +135 -19
- package/src/frontends/java.js +82 -33
- package/src/frontends/javascript-typescript.js +130 -38
- package/src/frontends/php.js +68 -27
- package/src/frontends/ruby.js +107 -28
- package/src/language-registry.js +15 -0
- package/src/semantic/effects.js +17 -5
- package/src/semantic/lifetime.js +29 -3
- package/src/semantic/provenance.js +27 -12
- package/src/semantic/types.js +24 -6
- package/src/semantic/validate.js +105 -36
package/src/backends/shared.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
860
|
-
|
|
861
|
-
|
|
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"
|
|
882
|
-
|
|
883
|
-
|
|
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"
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
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
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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
|
-
|
|
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")
|
|
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
|
-
|
|
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
|
|
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(
|
|
1575
|
+
const fields = Object.keys(tryStatement).filter((key) => key != "sourceProvenance").sort().join(",")
|
|
1543
1576
|
|
|
1544
|
-
if (fields != "body,
|
|
1577
|
+
if (fields != "body,handlers,kind,location" && fields != "body,finallyBody,handlers,kind,location") {
|
|
1545
1578
|
unsupportedCapability(language, "malformed TryStatement", tryLocation)
|
|
1546
1579
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
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
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
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(
|
|
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) ||
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
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.
|
package/src/frontends/c.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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 (
|
|
407
|
-
|
|
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
|
-
|
|
427
|
+
const statement = {kind: /** @type {const} */ ("PrintStatement"), location, expression: value}
|
|
410
428
|
|
|
411
|
-
|
|
412
|
-
|
|
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
|
|
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
|
-
|
|
451
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
|
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" ||
|
|
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
|
|
524
|
-
|
|
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")
|