semantifold 0.1.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 (111) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +71 -0
  3. package/build/index.d.ts +54 -0
  4. package/build/index.d.ts.map +1 -0
  5. package/build/index.js +45 -0
  6. package/build/src/backends/identifiers.d.ts +19 -0
  7. package/build/src/backends/identifiers.d.ts.map +1 -0
  8. package/build/src/backends/identifiers.js +93 -0
  9. package/build/src/backends/index.d.ts +35 -0
  10. package/build/src/backends/index.d.ts.map +1 -0
  11. package/build/src/backends/index.js +149 -0
  12. package/build/src/backends/java.d.ts +8 -0
  13. package/build/src/backends/java.d.ts.map +1 -0
  14. package/build/src/backends/java.js +172 -0
  15. package/build/src/backends/javascript.d.ts +8 -0
  16. package/build/src/backends/javascript.d.ts.map +1 -0
  17. package/build/src/backends/javascript.js +173 -0
  18. package/build/src/backends/php.d.ts +8 -0
  19. package/build/src/backends/php.d.ts.map +1 -0
  20. package/build/src/backends/php.js +175 -0
  21. package/build/src/backends/ruby.d.ts +8 -0
  22. package/build/src/backends/ruby.d.ts.map +1 -0
  23. package/build/src/backends/ruby.js +155 -0
  24. package/build/src/backends/scalars.d.ts +15 -0
  25. package/build/src/backends/scalars.d.ts.map +1 -0
  26. package/build/src/backends/scalars.js +138 -0
  27. package/build/src/backends/shared.d.ts +26 -0
  28. package/build/src/backends/shared.d.ts.map +1 -0
  29. package/build/src/backends/shared.js +424 -0
  30. package/build/src/backends/typescript.d.ts +8 -0
  31. package/build/src/backends/typescript.d.ts.map +1 -0
  32. package/build/src/backends/typescript.js +160 -0
  33. package/build/src/backends/writer.d.ts +87 -0
  34. package/build/src/backends/writer.d.ts.map +1 -0
  35. package/build/src/backends/writer.js +207 -0
  36. package/build/src/diagnostic.d.ts +67 -0
  37. package/build/src/diagnostic.d.ts.map +1 -0
  38. package/build/src/diagnostic.js +96 -0
  39. package/build/src/frontends/index.d.ts +14 -0
  40. package/build/src/frontends/index.d.ts.map +1 -0
  41. package/build/src/frontends/index.js +36 -0
  42. package/build/src/frontends/java.d.ts +12 -0
  43. package/build/src/frontends/java.d.ts.map +1 -0
  44. package/build/src/frontends/java.js +630 -0
  45. package/build/src/frontends/javascript-typescript.d.ts +19 -0
  46. package/build/src/frontends/javascript-typescript.d.ts.map +1 -0
  47. package/build/src/frontends/javascript-typescript.js +633 -0
  48. package/build/src/frontends/php.d.ts +12 -0
  49. package/build/src/frontends/php.d.ts.map +1 -0
  50. package/build/src/frontends/php.js +523 -0
  51. package/build/src/frontends/ruby.d.ts +12 -0
  52. package/build/src/frontends/ruby.d.ts.map +1 -0
  53. package/build/src/frontends/ruby.js +515 -0
  54. package/build/src/frontends/scalars.d.ts +19 -0
  55. package/build/src/frontends/scalars.d.ts.map +1 -0
  56. package/build/src/frontends/scalars.js +71 -0
  57. package/build/src/mapping.d.ts +160 -0
  58. package/build/src/mapping.d.ts.map +1 -0
  59. package/build/src/mapping.js +1120 -0
  60. package/build/src/semantic/location.d.ts +61 -0
  61. package/build/src/semantic/location.d.ts.map +1 -0
  62. package/build/src/semantic/location.js +142 -0
  63. package/build/src/semantic/operators.d.ts +16 -0
  64. package/build/src/semantic/operators.d.ts.map +1 -0
  65. package/build/src/semantic/operators.js +24 -0
  66. package/build/src/semantic/provenance.d.ts +74 -0
  67. package/build/src/semantic/provenance.d.ts.map +1 -0
  68. package/build/src/semantic/provenance.js +577 -0
  69. package/build/src/semantic/scalars.d.ts +19 -0
  70. package/build/src/semantic/scalars.d.ts.map +1 -0
  71. package/build/src/semantic/scalars.js +40 -0
  72. package/build/src/semantic/types.d.ts +1003 -0
  73. package/build/src/semantic/types.d.ts.map +1 -0
  74. package/build/src/semantic/types.js +279 -0
  75. package/build/src/semantic/validate.d.ts +60 -0
  76. package/build/src/semantic/validate.d.ts.map +1 -0
  77. package/build/src/semantic/validate.js +472 -0
  78. package/docs/architecture.md +39 -0
  79. package/docs/coding-standards.md +7 -0
  80. package/docs/goals.md +7 -0
  81. package/docs/language-support.md +69 -0
  82. package/docs/plans/2026-09-02-initial-toolchain.md +54 -0
  83. package/docs/source-maps.md +134 -0
  84. package/docs/testing.md +25 -0
  85. package/index.js +63 -0
  86. package/package.json +69 -0
  87. package/src/backends/identifiers.js +101 -0
  88. package/src/backends/index.js +159 -0
  89. package/src/backends/java.js +182 -0
  90. package/src/backends/javascript.js +184 -0
  91. package/src/backends/php.js +184 -0
  92. package/src/backends/ruby.js +163 -0
  93. package/src/backends/scalars.js +120 -0
  94. package/src/backends/shared.js +456 -0
  95. package/src/backends/typescript.js +168 -0
  96. package/src/backends/writer.js +224 -0
  97. package/src/diagnostic.js +103 -0
  98. package/src/frontends/index.js +36 -0
  99. package/src/frontends/java.js +735 -0
  100. package/src/frontends/javascript-typescript.js +718 -0
  101. package/src/frontends/php.js +593 -0
  102. package/src/frontends/ruby.js +622 -0
  103. package/src/frontends/scalars.js +77 -0
  104. package/src/mapping.js +1260 -0
  105. package/src/semantic/location.js +154 -0
  106. package/src/semantic/operators.js +28 -0
  107. package/src/semantic/provenance.js +629 -0
  108. package/src/semantic/scalars.js +44 -0
  109. package/src/semantic/types.js +319 -0
  110. package/src/semantic/validate.js +529 -0
  111. package/tsconfig.json +16 -0
@@ -0,0 +1,456 @@
1
+ // @ts-check
2
+
3
+ import {unsupportedCapability} from "../diagnostic.js"
4
+ import {validateBackendTypes} from "../semantic/validate.js"
5
+ import {validateTargetBindingIdentifier, validateTargetIdentifier} from "./identifiers.js"
6
+ import {emitStringLiteral} from "./scalars.js"
7
+
8
+ /** @type {Readonly<Record<import("../semantic/types.js").SemanticUnaryOperation, string>>} */
9
+ const unaryOperationSyntax = Object.freeze({BooleanNot: "!", IntegerNegate: "-"})
10
+ /** @type {Readonly<Record<import("../semantic/types.js").SemanticBinaryOperation, Readonly<{default: string, php?: string, strict?: string}>>>} */
11
+ const binaryOperationSyntax = Object.freeze({
12
+ BooleanAnd: Object.freeze({default: "&&"}),
13
+ BooleanEqual: Object.freeze({default: "==", strict: "==="}),
14
+ BooleanNotEqual: Object.freeze({default: "!=", strict: "!=="}),
15
+ BooleanOr: Object.freeze({default: "||"}),
16
+ IntegerAdd: Object.freeze({default: "+"}),
17
+ IntegerEqual: Object.freeze({default: "==", strict: "==="}),
18
+ IntegerGreaterThan: Object.freeze({default: ">"}),
19
+ IntegerGreaterThanOrEqual: Object.freeze({default: ">="}),
20
+ IntegerLessThan: Object.freeze({default: "<"}),
21
+ IntegerLessThanOrEqual: Object.freeze({default: "<="}),
22
+ IntegerMultiply: Object.freeze({default: "*"}),
23
+ IntegerNotEqual: Object.freeze({default: "!=", strict: "!=="}),
24
+ IntegerSubtract: Object.freeze({default: "-"}),
25
+ StringConcat: Object.freeze({default: "+", php: "."}),
26
+ StringEqual: Object.freeze({default: "==", strict: "==="}),
27
+ StringNotEqual: Object.freeze({default: "!=", strict: "!=="})
28
+ })
29
+
30
+ /**
31
+ * Checks the intentionally narrow backend contract.
32
+ * @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
33
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
34
+ * @returns {void}
35
+ */
36
+ export function validateBackendModule(module, language) {
37
+ if (module.kind != "Module") unsupportedCapability(language, module.kind, module.location)
38
+ if (module.functions.length == 0) unsupportedCapability(language, "module without functions", module.location)
39
+ validateBlock(module.entryPoint.body, language, module.entryPoint.location)
40
+
41
+ for (const functionDeclaration of module.functions) {
42
+ validateTargetBindingIdentifier(language, functionDeclaration.name, "function", functionDeclaration.location)
43
+
44
+ if (functionDeclaration.parameters.length != 2) {
45
+ unsupportedCapability(language, "function parameter count other than two", functionDeclaration.location)
46
+ }
47
+ validateBlock(functionDeclaration.body, language, functionDeclaration.location)
48
+
49
+ for (const parameter of functionDeclaration.parameters) {
50
+ validateTargetBindingIdentifier(language, parameter.name, "parameter", parameter.location)
51
+
52
+ }
53
+
54
+ }
55
+ validateScaffoldingNames(module, language)
56
+ validateBackendTypes(module, language)
57
+ }
58
+
59
+ /**
60
+ * Rejects semantic names that would capture syntax owned by one backend emitter.
61
+ * @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
62
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
63
+ * @returns {void}
64
+ */
65
+ function validateScaffoldingNames(module, language) {
66
+ const ownedEntryNames = language == "java" ? new Set(["args", "System"]) :
67
+ ["javascript", "typescript"].includes(language) ? new Set(["console"]) : new Set()
68
+ const ownedPrintReceiverNames = language == "java" ? new Set(["System"]) :
69
+ ["javascript", "typescript"].includes(language) ? new Set(["console"]) : new Set()
70
+ const ownedCallableNames = ["javascript", "typescript"].includes(language) ? new Set(["console"]) :
71
+ language == "ruby" ? new Set(["puts"]) : new Set()
72
+
73
+ for (const statement of allStatements(module.entryPoint.body)) {
74
+ if (statement.kind == "LocalDeclaration" && ownedEntryNames.has(statement.name)) {
75
+ unsupportedCapability(language, `entry local '${statement.name}' captures backend scaffolding`, statement.location)
76
+ }
77
+ }
78
+ for (const declaration of module.functions) {
79
+ if (ownedCallableNames.has(declaration.name)) {
80
+ unsupportedCapability(language, `function '${declaration.name}' captures backend scaffolding`, declaration.location)
81
+ }
82
+ for (const parameter of declaration.parameters) {
83
+ if (ownedPrintReceiverNames.has(parameter.name)) {
84
+ unsupportedCapability(language, `function parameter '${parameter.name}' captures backend scaffolding`, parameter.location)
85
+ }
86
+ }
87
+ for (const statement of allStatements(declaration.body)) {
88
+ if (statement.kind == "LocalDeclaration" && ownedPrintReceiverNames.has(statement.name)) {
89
+ unsupportedCapability(language, `function local '${statement.name}' captures backend scaffolding`, statement.location)
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Returns every statement nested beneath a block in semantic order.
97
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
98
+ * @returns {import("../semantic/types.js").Statement[]} Statements including nested branches.
99
+ */
100
+ function allStatements(block) {
101
+ return block.statements.flatMap((statement) => statement.kind == "IfStatement"
102
+ ? [statement, ...allStatements(statement.consequent), ...(statement.alternate ? allStatements(statement.alternate) : [])]
103
+ : [statement])
104
+ }
105
+
106
+ /**
107
+ * Validates one complete semantic block before emission.
108
+ * @param {unknown} block - Candidate block.
109
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
110
+ * @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Enclosing location.
111
+ * @returns {void}
112
+ */
113
+ function validateBlock(block, language, ownerLocation) {
114
+ if (!block || typeof block != "object" || Array.isArray(block)) {
115
+ return unsupportedCapability(language, "missing or invalid block", ownerLocation)
116
+ }
117
+
118
+ const candidate = /** @type {import("../semantic/types.js").Block} */ (block)
119
+ const location = candidate.location ?? ownerLocation
120
+
121
+ if (candidate.kind != "Block") unsupportedCapability(language, `block ${String(Reflect.get(block, "kind"))}`, location)
122
+ if (!Array.isArray(candidate.statements)) unsupportedCapability(language, "missing or invalid block statements", location)
123
+
124
+ for (const statement of candidate.statements) validateStatement(statement, language, location)
125
+ }
126
+
127
+ /**
128
+ * Validates one supported statement recursively.
129
+ * @param {unknown} statement - Candidate statement.
130
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
131
+ * @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Enclosing body location.
132
+ * @returns {void}
133
+ */
134
+ function validateStatement(statement, language, ownerLocation) {
135
+ if (!statement || typeof statement != "object" || Array.isArray(statement)) {
136
+ return unsupportedCapability(language, "missing or invalid statement", ownerLocation)
137
+ }
138
+
139
+ const kind = Reflect.get(statement, "kind")
140
+ const location = /** @type {import("../semantic/types.js").SourceLocation | undefined} */ (
141
+ Reflect.get(statement, "location") ?? ownerLocation
142
+ )
143
+
144
+ if (typeof kind != "string") return unsupportedCapability(language, "missing or invalid statement", location)
145
+ if (kind == "LocalDeclaration") {
146
+ const declaration = /** @type {import("../semantic/types.js").LocalDeclaration} */ (statement)
147
+
148
+ if (typeof declaration.mutable != "boolean") {
149
+ unsupportedCapability(language, "local declaration with invalid mutability", location)
150
+ }
151
+ validateTargetBindingIdentifier(language, declaration.name, "local", location)
152
+ validateExpression(declaration.initializer, language, location)
153
+ return
154
+ }
155
+ if (kind == "AssignmentStatement") {
156
+ const assignment = /** @type {import("../semantic/types.js").AssignmentStatement} */ (statement)
157
+
158
+ validateAssignmentTarget(assignment.target, language, location)
159
+ validateExpression(assignment.expression, language, location)
160
+ return
161
+ }
162
+ if (kind == "ReturnStatement" || kind == "PrintStatement") {
163
+ validateExpression(Reflect.get(statement, "expression"), language, location)
164
+ return
165
+ }
166
+ if (kind == "IfStatement") {
167
+ const branch = /** @type {import("../semantic/types.js").IfStatement} */ (statement)
168
+
169
+ validateExpression(branch.condition, language, location)
170
+ validateBlock(branch.consequent, language, location)
171
+ if (Object.hasOwn(branch, "alternate")) validateBlock(branch.alternate, language, location)
172
+ return
173
+ }
174
+
175
+ unsupportedCapability(language, `statement ${kind}`, location)
176
+ }
177
+
178
+ /**
179
+ * Validates the simple identifier target introduced by task 002.
180
+ * @param {unknown} target - Candidate assignment target.
181
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
182
+ * @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Assignment location.
183
+ * @returns {void}
184
+ */
185
+ function validateAssignmentTarget(target, language, ownerLocation) {
186
+ if (!target || typeof target != "object" || Array.isArray(target)) {
187
+ return unsupportedCapability(language, "missing or invalid assignment target", ownerLocation)
188
+ }
189
+
190
+ const candidate = /** @type {import("../semantic/types.js").IdentifierExpression} */ (target)
191
+ const location = candidate.location ?? ownerLocation
192
+
193
+ if (candidate.kind != "IdentifierExpression") {
194
+ return unsupportedCapability(language, `assignment target ${String(Reflect.get(target, "kind"))}`, location)
195
+ }
196
+
197
+ validateTargetBindingIdentifier(language, candidate.name, "assignment target", location)
198
+ }
199
+
200
+ /**
201
+ * Checks expression backend capabilities recursively.
202
+ * @param {unknown} expression - Candidate semantic expression.
203
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
204
+ * @param {import("../semantic/types.js").SourceLocation | undefined} ownerLocation - Nearest owning node location.
205
+ * @param {boolean} [allowJavaNegatedMinimumOperand] - Whether Java may use 2147483648 only beneath integer negation.
206
+ * @returns {void}
207
+ */
208
+ function validateExpression(expression, language, ownerLocation, allowJavaNegatedMinimumOperand = false) {
209
+ if (!expression || typeof expression != "object" || Array.isArray(expression)) {
210
+ return unsupportedCapability(language, "missing or invalid expression", ownerLocation)
211
+ }
212
+
213
+ const candidate = /** @type {import("../semantic/types.js").Expression} */ (expression)
214
+ const location = candidate.location ?? ownerLocation
215
+
216
+ if (typeof candidate.kind != "string") {
217
+ return unsupportedCapability(language, "missing or invalid expression", location)
218
+ }
219
+ if (candidate.kind == "IdentifierExpression") {
220
+ validateTargetIdentifier(language, candidate.name, "reference", location)
221
+ return
222
+ }
223
+ if (candidate.kind == "IntegerLiteral") {
224
+ if (!Number.isSafeInteger(candidate.value)) {
225
+ unsupportedCapability(language, "non-safe integer literal", location)
226
+ }
227
+ const validNegatedMinimumOperand = allowJavaNegatedMinimumOperand && candidate.value == 2147483648
228
+
229
+ if (language == "java" && !validNegatedMinimumOperand && (candidate.value < -2147483648 || candidate.value > 2147483647)) {
230
+ unsupportedCapability(language, "integer literal outside signed 32-bit int range", location)
231
+ }
232
+ return
233
+ }
234
+ if (candidate.kind == "BooleanLiteral" || candidate.kind == "StringLiteral") return
235
+ if (candidate.kind == "CallExpression") {
236
+ validateTargetIdentifier(language, candidate.callee, "callee", location)
237
+ if (!Array.isArray(candidate.arguments)) {
238
+ unsupportedCapability(language, "missing or invalid call arguments", location)
239
+ }
240
+ if (candidate.arguments.length != 2) {
241
+ unsupportedCapability(language, "call argument count other than two", location)
242
+ }
243
+ for (const argument of candidate.arguments) validateExpression(argument, language, location)
244
+ return
245
+ }
246
+ if (candidate.kind == "UnaryExpression") {
247
+ if (!Object.hasOwn(unaryOperationSyntax, String(candidate.operation))) {
248
+ unsupportedCapability(language, `unary operation ${String(candidate.operation)}`, location)
249
+ }
250
+ validateExpression(candidate.operand, language, location, candidate.operation == "IntegerNegate")
251
+ validateKnownJavaInteger(candidate, language, location)
252
+ return
253
+ }
254
+ if (candidate.kind == "BinaryExpression") {
255
+ if (!Object.hasOwn(binaryOperationSyntax, String(candidate.operation))) {
256
+ unsupportedCapability(language, `binary operation ${String(candidate.operation)}`, location)
257
+ }
258
+ validateExpression(candidate.left, language, location)
259
+ validateExpression(candidate.right, language, location)
260
+ validateKnownJavaInteger(candidate, language, location)
261
+ return
262
+ }
263
+
264
+ unsupportedCapability(language, String(Reflect.get(expression, "kind")), location)
265
+ }
266
+
267
+ /**
268
+ * Rejects compile-time-known Java integer operation results outside primitive int.
269
+ * @param {import("../semantic/types.js").UnaryExpression | import("../semantic/types.js").BinaryExpression} expression - Validated operation shape.
270
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Backend language.
271
+ * @param {import("../semantic/types.js").SourceLocation | undefined} location - Operation location.
272
+ * @returns {void}
273
+ */
274
+ function validateKnownJavaInteger(expression, language, location) {
275
+ if (language != "java") return
276
+
277
+ const value = knownIntegerValue(expression)
278
+
279
+ if (value !== undefined && (value < -2147483648n || value > 2147483647n)) {
280
+ unsupportedCapability(language, "compile-time-known integer operation outside signed 32-bit int range", location)
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Evaluates only literal integer-operation trees for bounds validation, never IR folding.
286
+ * @param {import("../semantic/types.js").Expression} expression - Semantic expression.
287
+ * @returns {bigint | undefined} Known mathematical integer.
288
+ */
289
+ function knownIntegerValue(expression) {
290
+ if (expression.kind == "IntegerLiteral" && Number.isSafeInteger(expression.value)) return BigInt(expression.value)
291
+ if (expression.kind == "UnaryExpression" && expression.operation == "IntegerNegate") {
292
+ const operand = knownIntegerValue(expression.operand)
293
+
294
+ return operand === undefined ? undefined : -operand
295
+ }
296
+ if (expression.kind != "BinaryExpression") return undefined
297
+
298
+ const left = knownIntegerValue(expression.left)
299
+ const right = knownIntegerValue(expression.right)
300
+
301
+ if (left === undefined || right === undefined) return undefined
302
+ if (expression.operation == "IntegerAdd") return left + right
303
+ if (expression.operation == "IntegerSubtract") return left - right
304
+ if (expression.operation == "IntegerMultiply") return left * right
305
+
306
+ return undefined
307
+ }
308
+
309
+ /**
310
+ * Emits a supported expression.
311
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
312
+ * @param {import("../semantic/types.js").Expression} expression - Semantic expression.
313
+ * @param {string} path - Exact JSON Pointer for this expression occurrence.
314
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Target language.
315
+ * @param {(name: string) => string} emitIdentifier - Identifier formatter.
316
+ * @returns {void}
317
+ */
318
+ export function emitExpression(writer, expression, path, language, emitIdentifier) {
319
+ if (expression.kind == "IdentifierExpression") {
320
+ writer.mapped(emitIdentifier(expression.name), {mappingKind: "exact", node: expression, path, role: "name"})
321
+ return
322
+ }
323
+ if (expression.kind == "IntegerLiteral") {
324
+ writer.mapped(String(expression.value), {mappingKind: "exact", node: expression, path, role: "literal"})
325
+ return
326
+ }
327
+ if (expression.kind == "BooleanLiteral") {
328
+ writer.mapped(expression.value ? "true" : "false", {mappingKind: "exact", node: expression, path, role: "literal"})
329
+ return
330
+ }
331
+ if (expression.kind == "StringLiteral") {
332
+ writer.mapped(emitStringLiteral(language, expression.value), {mappingKind: "exact", node: expression, path, role: "literal"})
333
+ return
334
+ }
335
+ if (expression.kind == "CallExpression") {
336
+ writer.mapped(expression.callee, {mappingKind: "exact", node: expression, path, role: "callee"})
337
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
338
+ expression.arguments.forEach((argument, index) => {
339
+ if (index > 0) writer.synthetic(", ", "argument separator", [expression], [path])
340
+ emitExpression(writer, argument, `${path}/arguments/${index}`, language, emitIdentifier)
341
+ })
342
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
343
+ return
344
+ }
345
+
346
+ if (expression.kind == "UnaryExpression") {
347
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
348
+ writer.mapped(unaryOperationSyntax[expression.operation], {
349
+ mappingKind: "exact", node: expression, path, role: "operator"
350
+ })
351
+ emitExpression(writer, expression.operand, `${path}/operand`, language, emitIdentifier)
352
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
353
+ return
354
+ }
355
+
356
+ if (language == "java" && (expression.operation == "StringEqual" || expression.operation == "StringNotEqual")) {
357
+ const compositeOperator = expression.operation == "StringNotEqual" && writer.hasRange(expression, path, "equalityOperator")
358
+
359
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
360
+ if (expression.operation == "StringNotEqual") {
361
+ writer.mapped("!", {mappingKind: "exact", node: expression, path, role: "operator"})
362
+ }
363
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
364
+ emitExpression(writer, expression.left, `${path}/left`, language, emitIdentifier)
365
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
366
+ writer.mapped(".equals", {
367
+ mappingKind: "exact", node: expression, path, role: compositeOperator ? "equalityOperator" : "operator"
368
+ })
369
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
370
+ emitExpression(writer, expression.right, `${path}/right`, language, emitIdentifier)
371
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
372
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
373
+ return
374
+ }
375
+
376
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
377
+ emitExpression(writer, expression.left, `${path}/left`, language, emitIdentifier)
378
+ writer.synthetic(" ", "operator spacing", [expression], [path])
379
+ const spelling = binaryOperationSpelling(expression.operation, language)
380
+
381
+ if (expression.operation == "StringNotEqual" && writer.hasRange(expression, path, "equalityOperator")) {
382
+ writer.mapped(spelling.slice(0, 1), {mappingKind: "exact", node: expression, path, role: "operator"})
383
+ writer.mapped(spelling.slice(1), {mappingKind: "exact", node: expression, path, role: "equalityOperator"})
384
+ } else writer.mapped(spelling, {mappingKind: "exact", node: expression, path, role: "operator"})
385
+ writer.synthetic(" ", "operator spacing", [expression], [path])
386
+ emitExpression(writer, expression.right, `${path}/right`, language, emitIdentifier)
387
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
388
+ }
389
+
390
+ /**
391
+ * Reports whether JavaScript-family execution can create IEEE-754 signed zero.
392
+ * Semantifold integers have one mathematical zero, so those targets canonicalize
393
+ * their observable scalar output without changing the operation tree.
394
+ * @param {import("../semantic/types.js").SemanticModule} module - Validated semantic module.
395
+ * @returns {boolean} Whether canonical output rendering is required.
396
+ */
397
+ export function requiresCanonicalZeroRendering(module) {
398
+ return module.functions.some((declaration) => blockContainsSignProducingOperation(declaration.body)) ||
399
+ blockContainsSignProducingOperation(module.entryPoint.body)
400
+ }
401
+
402
+ /**
403
+ * Checks one block for sign-producing integer operations.
404
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
405
+ * @returns {boolean} Whether a nested expression can produce signed zero in JavaScript.
406
+ */
407
+ function blockContainsSignProducingOperation(block) {
408
+ return block.statements.some((statement) => {
409
+ if (statement.kind == "IfStatement") {
410
+ return expressionContainsSignProducingOperation(statement.condition) ||
411
+ blockContainsSignProducingOperation(statement.consequent) ||
412
+ Boolean(statement.alternate && blockContainsSignProducingOperation(statement.alternate))
413
+ }
414
+ if (statement.kind == "LocalDeclaration") return expressionContainsSignProducingOperation(statement.initializer)
415
+ if (statement.kind == "AssignmentStatement" || statement.kind == "ReturnStatement" || statement.kind == "PrintStatement") {
416
+ return expressionContainsSignProducingOperation(statement.expression)
417
+ }
418
+
419
+ return false
420
+ })
421
+ }
422
+
423
+ /**
424
+ * Checks one expression tree for sign-producing integer operations.
425
+ * @param {import("../semantic/types.js").Expression} expression - Semantic expression.
426
+ * @returns {boolean} Whether this tree contains integer negation or multiplication.
427
+ */
428
+ function expressionContainsSignProducingOperation(expression) {
429
+ if (expression.kind == "UnaryExpression") {
430
+ return expression.operation == "IntegerNegate" || expressionContainsSignProducingOperation(expression.operand)
431
+ }
432
+ if (expression.kind == "BinaryExpression") {
433
+ return expression.operation == "IntegerMultiply" || expressionContainsSignProducingOperation(expression.left) ||
434
+ expressionContainsSignProducingOperation(expression.right)
435
+ }
436
+ if (expression.kind == "CallExpression") {
437
+ return expression.arguments.some((argument) => expressionContainsSignProducingOperation(argument))
438
+ }
439
+
440
+ return false
441
+ }
442
+
443
+ /**
444
+ * Maps one already validated semantic operation to target syntax.
445
+ * @param {import("../semantic/types.js").SemanticBinaryOperation} operation - Closed semantic operation.
446
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Target language.
447
+ * @returns {string} Target operator spelling.
448
+ */
449
+ function binaryOperationSpelling(operation, language) {
450
+ const syntax = binaryOperationSyntax[operation]
451
+
452
+ if (language == "php" && syntax.php) return syntax.php
453
+ if ((language == "php" || language == "javascript" || language == "typescript") && syntax.strict) return syntax.strict
454
+
455
+ return syntax.default
456
+ }
@@ -0,0 +1,168 @@
1
+ // @ts-check
2
+
3
+ import {emitExpression, requiresCanonicalZeroRendering} from "./shared.js"
4
+ import {emitScalarType} from "./scalars.js"
5
+
6
+ /**
7
+ * Emits an independently executable TypeScript program through the source-aware writer.
8
+ * @param {import("../semantic/types.js").SemanticModule} module - Semantic module.
9
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
10
+ * @returns {void}
11
+ */
12
+ export function generateTypeScript(module, writer) {
13
+ const canonicalizeZero = requiresCanonicalZeroRendering(module)
14
+
15
+ module.functions.forEach((declaration, functionIndex) => {
16
+ if (functionIndex > 0) writer.synthetic("\n\n", "declaration separator", [declaration])
17
+
18
+ writer.mapped("function", {mappingKind: "anchor", node: declaration})
19
+ writer.synthetic(" ", "function spacing", [declaration])
20
+ writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, role: "name"})
21
+ writer.mapped("(", {mappingKind: "anchor", node: declaration})
22
+ declaration.parameters.forEach((parameter, index) => {
23
+ const parameterPath = `/functions/${functionIndex}/parameters/${index}`
24
+
25
+ if (index > 0) writer.synthetic(", ", "parameter separator", [declaration])
26
+ writer.mapped(parameter.name, {mappingKind: "exact", node: parameter, path: parameterPath, role: "name"})
27
+ writer.synthetic(": ", "type separator", [parameter], [parameterPath])
28
+ writer.mapped(emitScalarType("typescript", parameter.type), {
29
+ mappingKind: "exact",
30
+ node: parameter.type,
31
+ path: `${parameterPath}/type`,
32
+ role: "type"
33
+ })
34
+ })
35
+ writer.mapped(")", {mappingKind: "anchor", node: declaration})
36
+ writer.synthetic(": ", "return type separator", [declaration])
37
+ writer.mapped(emitScalarType("typescript", declaration.returnType), {
38
+ mappingKind: "exact",
39
+ node: declaration.returnType,
40
+ path: `/functions/${functionIndex}/returnType`,
41
+ role: "type"
42
+ })
43
+ writer.synthetic(" ", "function spacing", [declaration])
44
+ writer.mapped("{", {mappingKind: "anchor", node: declaration})
45
+ writer.synthetic("\n", "line break", [declaration])
46
+
47
+ emitBlock(writer, declaration.body, " ", `/functions/${functionIndex}/body`, canonicalizeZero)
48
+ writer.mapped("}", {mappingKind: "anchor", node: declaration})
49
+ })
50
+
51
+ writer.synthetic("\n\n", "entry-point separator", [module.entryPoint])
52
+ emitBlock(writer, module.entryPoint.body, "", "/entryPoint/body", canonicalizeZero)
53
+ }
54
+
55
+ /**
56
+ * Emits one ordered TypeScript block body.
57
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
58
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
59
+ * @param {string} indent - Indentation.
60
+ * @param {string} path - Exact block path.
61
+ * @param {boolean} canonicalizeZero - Whether printed integer zero must be canonicalized.
62
+ * @returns {void}
63
+ */
64
+ function emitBlock(writer, block, indent, path, canonicalizeZero) {
65
+ block.statements.forEach((statement, index) => emitStatement(writer, statement, indent, `${path}/statements/${index}`, canonicalizeZero))
66
+ }
67
+
68
+ /**
69
+ * Emits one TypeScript statement.
70
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
71
+ * @param {import("../semantic/types.js").Statement} statement - Semantic statement.
72
+ * @param {string} indent - Indentation.
73
+ * @param {string} path - Exact statement path.
74
+ * @param {boolean} canonicalizeZero - Whether printed integer zero must be canonicalized.
75
+ * @returns {void}
76
+ */
77
+ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
78
+ if (statement.kind == "LocalDeclaration" || statement.kind == "AssignmentStatement") {
79
+ emitLocal(writer, statement, indent, path)
80
+ writer.synthetic("\n", "line break", [statement], [path])
81
+ return
82
+ }
83
+ writer.synthetic(indent, "indentation", [statement], [path])
84
+ if (statement.kind == "ReturnStatement") {
85
+ writer.mapped("return", {mappingKind: "anchor", node: statement, path})
86
+ writer.synthetic(" ", "return spacing", [statement], [path])
87
+ emitExpression(writer, statement.expression, `${path}/expression`, "typescript", identity)
88
+ writer.synthetic("\n", "line break", [statement], [path])
89
+ return
90
+ }
91
+ if (statement.kind == "PrintStatement") {
92
+ writer.mapped("console.log", {mappingKind: "anchor", node: statement, path})
93
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
94
+ if (canonicalizeZero) writer.synthetic("(", "canonical integer output", [statement], [path])
95
+ emitExpression(writer, statement.expression, `${path}/expression`, "typescript", identity)
96
+ if (canonicalizeZero) writer.synthetic(").toString()", "canonical integer output", [statement], [path])
97
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
98
+ writer.synthetic("\n", "line break", [statement], [path])
99
+ return
100
+ }
101
+ writer.mapped("if", {mappingKind: "anchor", node: statement, path})
102
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
103
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
104
+ emitExpression(writer, statement.condition, `${path}/condition`, "typescript", identity)
105
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
106
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
107
+ writer.mapped("{", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
108
+ writer.synthetic("\n", "line break", [statement], [path])
109
+ emitBlock(writer, statement.consequent, `${indent} `, `${path}/consequent`, canonicalizeZero)
110
+ writer.synthetic(indent, "indentation", [statement], [path])
111
+ writer.mapped("}", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
112
+ if (statement.alternate) {
113
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
114
+ writer.mapped("else", {mappingKind: "anchor", node: statement, path})
115
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
116
+ writer.mapped("{", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
117
+ writer.synthetic("\n", "line break", [statement], [path])
118
+ emitBlock(writer, statement.alternate, `${indent} `, `${path}/alternate`, canonicalizeZero)
119
+ writer.synthetic(indent, "indentation", [statement], [path])
120
+ writer.mapped("}", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
121
+ }
122
+ writer.synthetic("\n", "line break", [statement], [path])
123
+ }
124
+
125
+ /**
126
+ * Emits one TypeScript local statement.
127
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
128
+ * @param {import("../semantic/types.js").LocalStatement} statement - Local statement.
129
+ * @param {string} indent - Leading indentation.
130
+ * @param {string} statementPath - Exact JSON Pointer for this statement occurrence.
131
+ * @returns {void}
132
+ */
133
+ function emitLocal(writer, statement, indent, statementPath) {
134
+ writer.synthetic(indent, "indentation", [statement], [statementPath])
135
+
136
+ if (statement.kind == "AssignmentStatement") {
137
+ writer.mapped(statement.target.name, {mappingKind: "exact", node: statement.target, path: `${statementPath}/target`, role: "name"})
138
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
139
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
140
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
141
+ emitExpression(writer, statement.expression, `${statementPath}/expression`, "typescript", identity)
142
+ return
143
+ }
144
+
145
+ writer.mapped(statement.mutable ? "let" : "const", {mappingKind: "anchor", node: statement, path: statementPath})
146
+ writer.synthetic(" ", "declaration spacing", [statement], [statementPath])
147
+ writer.mapped(statement.name, {mappingKind: "exact", node: statement, path: statementPath, role: "name"})
148
+ writer.synthetic(": ", "type separator", [statement], [statementPath])
149
+ writer.mapped(emitScalarType("typescript", statement.type), {
150
+ mappingKind: "exact",
151
+ node: statement.type,
152
+ path: `${statementPath}/type`,
153
+ role: "type"
154
+ })
155
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
156
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
157
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
158
+ emitExpression(writer, statement.initializer, `${statementPath}/initializer`, "typescript", identity)
159
+ }
160
+
161
+ /**
162
+ * Returns an unchanged TypeScript identifier.
163
+ * @param {string} name - Identifier.
164
+ * @returns {string} Identifier.
165
+ */
166
+ function identity(name) {
167
+ return name
168
+ }