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.
- package/LICENSE +15 -0
- package/README.md +71 -0
- package/build/index.d.ts +54 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +45 -0
- package/build/src/backends/identifiers.d.ts +19 -0
- package/build/src/backends/identifiers.d.ts.map +1 -0
- package/build/src/backends/identifiers.js +93 -0
- package/build/src/backends/index.d.ts +35 -0
- package/build/src/backends/index.d.ts.map +1 -0
- package/build/src/backends/index.js +149 -0
- package/build/src/backends/java.d.ts +8 -0
- package/build/src/backends/java.d.ts.map +1 -0
- package/build/src/backends/java.js +172 -0
- package/build/src/backends/javascript.d.ts +8 -0
- package/build/src/backends/javascript.d.ts.map +1 -0
- package/build/src/backends/javascript.js +173 -0
- package/build/src/backends/php.d.ts +8 -0
- package/build/src/backends/php.d.ts.map +1 -0
- package/build/src/backends/php.js +175 -0
- package/build/src/backends/ruby.d.ts +8 -0
- package/build/src/backends/ruby.d.ts.map +1 -0
- package/build/src/backends/ruby.js +155 -0
- package/build/src/backends/scalars.d.ts +15 -0
- package/build/src/backends/scalars.d.ts.map +1 -0
- package/build/src/backends/scalars.js +138 -0
- package/build/src/backends/shared.d.ts +26 -0
- package/build/src/backends/shared.d.ts.map +1 -0
- package/build/src/backends/shared.js +424 -0
- package/build/src/backends/typescript.d.ts +8 -0
- package/build/src/backends/typescript.d.ts.map +1 -0
- package/build/src/backends/typescript.js +160 -0
- package/build/src/backends/writer.d.ts +87 -0
- package/build/src/backends/writer.d.ts.map +1 -0
- package/build/src/backends/writer.js +207 -0
- package/build/src/diagnostic.d.ts +67 -0
- package/build/src/diagnostic.d.ts.map +1 -0
- package/build/src/diagnostic.js +96 -0
- package/build/src/frontends/index.d.ts +14 -0
- package/build/src/frontends/index.d.ts.map +1 -0
- package/build/src/frontends/index.js +36 -0
- package/build/src/frontends/java.d.ts +12 -0
- package/build/src/frontends/java.d.ts.map +1 -0
- package/build/src/frontends/java.js +630 -0
- package/build/src/frontends/javascript-typescript.d.ts +19 -0
- package/build/src/frontends/javascript-typescript.d.ts.map +1 -0
- package/build/src/frontends/javascript-typescript.js +633 -0
- package/build/src/frontends/php.d.ts +12 -0
- package/build/src/frontends/php.d.ts.map +1 -0
- package/build/src/frontends/php.js +523 -0
- package/build/src/frontends/ruby.d.ts +12 -0
- package/build/src/frontends/ruby.d.ts.map +1 -0
- package/build/src/frontends/ruby.js +515 -0
- package/build/src/frontends/scalars.d.ts +19 -0
- package/build/src/frontends/scalars.d.ts.map +1 -0
- package/build/src/frontends/scalars.js +71 -0
- package/build/src/mapping.d.ts +160 -0
- package/build/src/mapping.d.ts.map +1 -0
- package/build/src/mapping.js +1120 -0
- package/build/src/semantic/location.d.ts +61 -0
- package/build/src/semantic/location.d.ts.map +1 -0
- package/build/src/semantic/location.js +142 -0
- package/build/src/semantic/operators.d.ts +16 -0
- package/build/src/semantic/operators.d.ts.map +1 -0
- package/build/src/semantic/operators.js +24 -0
- package/build/src/semantic/provenance.d.ts +74 -0
- package/build/src/semantic/provenance.d.ts.map +1 -0
- package/build/src/semantic/provenance.js +577 -0
- package/build/src/semantic/scalars.d.ts +19 -0
- package/build/src/semantic/scalars.d.ts.map +1 -0
- package/build/src/semantic/scalars.js +40 -0
- package/build/src/semantic/types.d.ts +1003 -0
- package/build/src/semantic/types.d.ts.map +1 -0
- package/build/src/semantic/types.js +279 -0
- package/build/src/semantic/validate.d.ts +60 -0
- package/build/src/semantic/validate.d.ts.map +1 -0
- package/build/src/semantic/validate.js +472 -0
- package/docs/architecture.md +39 -0
- package/docs/coding-standards.md +7 -0
- package/docs/goals.md +7 -0
- package/docs/language-support.md +69 -0
- package/docs/plans/2026-09-02-initial-toolchain.md +54 -0
- package/docs/source-maps.md +134 -0
- package/docs/testing.md +25 -0
- package/index.js +63 -0
- package/package.json +69 -0
- package/src/backends/identifiers.js +101 -0
- package/src/backends/index.js +159 -0
- package/src/backends/java.js +182 -0
- package/src/backends/javascript.js +184 -0
- package/src/backends/php.js +184 -0
- package/src/backends/ruby.js +163 -0
- package/src/backends/scalars.js +120 -0
- package/src/backends/shared.js +456 -0
- package/src/backends/typescript.js +168 -0
- package/src/backends/writer.js +224 -0
- package/src/diagnostic.js +103 -0
- package/src/frontends/index.js +36 -0
- package/src/frontends/java.js +735 -0
- package/src/frontends/javascript-typescript.js +718 -0
- package/src/frontends/php.js +593 -0
- package/src/frontends/ruby.js +622 -0
- package/src/frontends/scalars.js +77 -0
- package/src/mapping.js +1260 -0
- package/src/semantic/location.js +154 -0
- package/src/semantic/operators.js +28 -0
- package/src/semantic/provenance.js +629 -0
- package/src/semantic/scalars.js +44 -0
- package/src/semantic/types.js +319 -0
- package/src/semantic/validate.js +529 -0
- 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
|
+
}
|