semantifold 0.1.1 → 0.2.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 (90) hide show
  1. package/README.md +13 -6
  2. package/build/index.d.ts +32 -3
  3. package/build/index.d.ts.map +1 -1
  4. package/build/index.js +99 -4
  5. package/build/src/acceptance.d.ts +22 -0
  6. package/build/src/acceptance.d.ts.map +1 -0
  7. package/build/src/acceptance.js +339 -0
  8. package/build/src/array.d.ts +7 -0
  9. package/build/src/array.d.ts.map +1 -0
  10. package/build/src/array.js +18 -0
  11. package/build/src/artifact-path.d.ts +13 -0
  12. package/build/src/artifact-path.d.ts.map +1 -0
  13. package/build/src/artifact-path.js +30 -0
  14. package/build/src/artifacts.d.ts +7 -0
  15. package/build/src/artifacts.d.ts.map +1 -0
  16. package/build/src/artifacts.js +389 -0
  17. package/build/src/backends/identifiers.d.ts.map +1 -1
  18. package/build/src/backends/identifiers.js +10 -2
  19. package/build/src/backends/index.d.ts.map +1 -1
  20. package/build/src/backends/index.js +19 -37
  21. package/build/src/backends/python.d.ts +8 -0
  22. package/build/src/backends/python.d.ts.map +1 -0
  23. package/build/src/backends/python.js +162 -0
  24. package/build/src/backends/scalars.d.ts.map +1 -1
  25. package/build/src/backends/scalars.js +2 -1
  26. package/build/src/backends/shared.d.ts.map +1 -1
  27. package/build/src/backends/shared.js +9 -6
  28. package/build/src/binary-mapping.d.ts +32 -0
  29. package/build/src/binary-mapping.d.ts.map +1 -0
  30. package/build/src/binary-mapping.js +285 -0
  31. package/build/src/diagnostic.d.ts +36 -4
  32. package/build/src/diagnostic.d.ts.map +1 -1
  33. package/build/src/diagnostic.js +42 -3
  34. package/build/src/frontends/index.d.ts.map +1 -1
  35. package/build/src/frontends/index.js +5 -23
  36. package/build/src/frontends/python.d.ts +14 -0
  37. package/build/src/frontends/python.d.ts.map +1 -0
  38. package/build/src/frontends/python.js +615 -0
  39. package/build/src/frontends/scalars.d.ts.map +1 -1
  40. package/build/src/frontends/scalars.js +6 -1
  41. package/build/src/language-registry.d.ts +113 -0
  42. package/build/src/language-registry.d.ts.map +1 -0
  43. package/build/src/language-registry.js +372 -0
  44. package/build/src/mapping.js +3 -2
  45. package/build/src/semantic/location.d.ts +7 -0
  46. package/build/src/semantic/location.d.ts.map +1 -1
  47. package/build/src/semantic/location.js +10 -1
  48. package/build/src/semantic/provenance.js +3 -3
  49. package/build/src/semantic/types.d.ts +399 -2
  50. package/build/src/semantic/types.d.ts.map +1 -1
  51. package/build/src/semantic/types.js +110 -2
  52. package/build/src/subprocess.d.ts +29 -0
  53. package/build/src/subprocess.d.ts.map +1 -0
  54. package/build/src/subprocess.js +207 -0
  55. package/build/src/timeout.d.ts +8 -0
  56. package/build/src/timeout.d.ts.map +1 -0
  57. package/build/src/timeout.js +11 -0
  58. package/build/src/toolchains.d.ts +83 -0
  59. package/build/src/toolchains.d.ts.map +1 -0
  60. package/build/src/toolchains.js +413 -0
  61. package/docs/architecture.md +7 -3
  62. package/docs/language-support.md +25 -4
  63. package/docs/parser-qualification.md +70 -0
  64. package/docs/source-maps.md +31 -4
  65. package/docs/testing.md +21 -2
  66. package/index.js +109 -4
  67. package/package.json +4 -2
  68. package/src/acceptance.js +359 -0
  69. package/src/array.js +19 -0
  70. package/src/artifact-path.js +34 -0
  71. package/src/artifacts.js +420 -0
  72. package/src/backends/identifiers.js +9 -1
  73. package/src/backends/index.js +16 -32
  74. package/src/backends/python.js +175 -0
  75. package/src/backends/scalars.js +1 -0
  76. package/src/backends/shared.js +8 -5
  77. package/src/binary-mapping.js +305 -0
  78. package/src/diagnostic.js +34 -2
  79. package/src/frontends/index.js +5 -20
  80. package/src/frontends/python.js +689 -0
  81. package/src/frontends/scalars.js +5 -0
  82. package/src/language-registry.js +403 -0
  83. package/src/mapping.js +2 -1
  84. package/src/semantic/location.js +10 -0
  85. package/src/semantic/provenance.js +2 -2
  86. package/src/semantic/types.js +124 -1
  87. package/src/subprocess.js +203 -0
  88. package/src/timeout.js +12 -0
  89. package/src/toolchains.js +444 -0
  90. package/tsconfig.json +1 -0
@@ -31,6 +31,11 @@ const sourceScalarTypes = Object.freeze({
31
31
  ["bool", "boolean"],
32
32
  ["string", "string"]
33
33
  ]),
34
+ python: sourceTypeMap([
35
+ ["int", "integer"],
36
+ ["bool", "boolean"],
37
+ ["str", "string"]
38
+ ]),
34
39
  ruby: sourceTypeMap([
35
40
  ["[Integer]", "integer"],
36
41
  ["[bool]", "boolean"],
@@ -0,0 +1,403 @@
1
+ // @ts-check
2
+
3
+ import {isDenseArray} from "./array.js"
4
+ import {SemantifoldDiagnostic, unsupportedRole} from "./diagnostic.js"
5
+ import {generateJava} from "./backends/java.js"
6
+ import {generateJavaScript} from "./backends/javascript.js"
7
+ import {generatePhp} from "./backends/php.js"
8
+ import {generatePython} from "./backends/python.js"
9
+ import {generateRuby} from "./backends/ruby.js"
10
+ import {generateTypeScript} from "./backends/typescript.js"
11
+ import {parseJava} from "./frontends/java.js"
12
+ import {parseJavaScriptTypeScript} from "./frontends/javascript-typescript.js"
13
+ import {parsePhp} from "./frontends/php.js"
14
+ import {parsePython} from "./frontends/python.js"
15
+ import {parseRuby} from "./frontends/ruby.js"
16
+
17
+ const registryRoles = Object.freeze(["frontend", "textBackend", "binaryBackend", "applicationBackend", "interoperability"])
18
+ const acceptanceStageOrder = ["parse", "generate", "compile", "link", "validate", "instantiate", "execute"]
19
+ const acceptanceStages = new Map(acceptanceStageOrder.map((stage, index) => [stage, index]))
20
+ const registryKeys = new Set([
21
+ "acceptance", "applicationBackend", "artifactMultiplicity", "binaryBackend", "defaultFilename", "frontend", "id",
22
+ "interoperability", "mapping", "mediaType", "roundTrip", "textBackend"
23
+ ])
24
+
25
+ /** @typedef {"frontend" | "textBackend" | "binaryBackend" | "applicationBackend" | "interoperability"} RegistryRole */
26
+ /** @typedef {(input: {filename: string, source: string}) => import("./semantic/types.js").SemanticModule} Frontend */
27
+ /** @typedef {(...values: unknown[]) => unknown} RegistryImplementation */
28
+
29
+ /**
30
+ * @typedef LanguageRegistryRecord
31
+ * @property {string} id - Stable identity.
32
+ * @property {{stages: import("./semantic/types.js").AcceptanceStage[], toolchains: string[]}} acceptance - Acceptance declaration.
33
+ * @property {"single" | "multiple"} artifactMultiplicity - Artifact multiplicity.
34
+ * @property {import("./semantic/types.js").LanguageMappingCapabilities} mapping - Mapping capabilities.
35
+ * @property {boolean} roundTrip - Round-trip declaration.
36
+ * @property {string} [defaultFilename] - Default backend filename.
37
+ * @property {string} [mediaType] - Default backend media type.
38
+ * @property {RegistryImplementation} [frontend] - Frontend adapter.
39
+ * @property {RegistryImplementation} [textBackend] - Text backend.
40
+ * @property {RegistryImplementation} [binaryBackend] - Binary backend.
41
+ * @property {RegistryImplementation} [applicationBackend] - Application-artifact backend.
42
+ * @property {RegistryImplementation} [interoperability] - Interoperability bridge.
43
+ */
44
+
45
+ /**
46
+ * @typedef LanguageRegistry
47
+ * @property {readonly import("./semantic/types.js").LanguageCapabilities[]} descriptors - Public descriptors.
48
+ * @property {readonly string[]} ids - Registered IDs in stable order.
49
+ * @property {(id: string) => Readonly<LanguageRegistryRecord>} record - Exact record lookup.
50
+ * @property {(id: string, role: RegistryRole, location?: import("./semantic/types.js").SourceLocation) => RegistryImplementation} resolve - Role lookup.
51
+ */
52
+
53
+ /**
54
+ * Constructs an immutable role registry from ordered records.
55
+ * @param {unknown} candidateRecords - Ordered language records.
56
+ * @returns {Readonly<LanguageRegistry>} Registry.
57
+ */
58
+ export function createLanguageRegistry(candidateRecords) {
59
+ if (!isDenseArray(candidateRecords)) invalidRegistry("Registry records must be an ordered dense array.")
60
+
61
+ /** @type {Map<string, Readonly<LanguageRegistryRecord>>} */
62
+ const recordsById = new Map()
63
+ /** @type {import("./semantic/types.js").LanguageCapabilities[]} */
64
+ const descriptors = []
65
+
66
+ for (let recordIndex = 0; recordIndex < candidateRecords.length; recordIndex += 1) {
67
+ const candidate = candidateRecords[recordIndex]
68
+
69
+ if (!isPlainObject(candidate)) invalidRegistry("Every registry record must be a plain object.")
70
+ const id = candidate.id
71
+
72
+ if (typeof id != "string" || id.length == 0 || !/^[a-z][a-z0-9-]*$/u.test(id)) {
73
+ invalidRegistry("Every registry record requires a stable lowercase ID.", typeof id == "string" ? id : undefined)
74
+ }
75
+ if (recordsById.has(id)) invalidRegistry(`Duplicate registry ID '${id}'.`, id)
76
+ if (Object.keys(candidate).some((key) => !registryKeys.has(key))) invalidRegistry(`Registry record '${id}' has unknown fields.`, id)
77
+
78
+ for (const role of registryRoles) {
79
+ if (candidate[role] !== undefined && typeof candidate[role] != "function") {
80
+ invalidRegistry(`Registry role '${role}' for '${id}' must be a function when present.`, id)
81
+ }
82
+ }
83
+ const artifactMultiplicity = candidate.artifactMultiplicity
84
+
85
+ if (artifactMultiplicity != "single" && artifactMultiplicity != "multiple") {
86
+ invalidRegistry(`Registry record '${id}' has an invalid artifact multiplicity.`, id)
87
+ }
88
+ if (typeof candidate.roundTrip != "boolean") invalidRegistry(`Registry record '${id}' requires a Boolean round-trip declaration.`, id)
89
+ const mappingCandidate = candidate.mapping
90
+
91
+ if (!isPlainObject(mappingCandidate)) invalidRegistry(`Registry record '${id}' has an invalid mapping declaration.`, id)
92
+ const mappingSnapshot = {
93
+ binaryRanges: mappingCandidate.binaryRanges,
94
+ richText: mappingCandidate.richText,
95
+ sourceMapV3: mappingCandidate.sourceMapV3
96
+ }
97
+
98
+ if (typeof mappingSnapshot.richText != "boolean" || typeof mappingSnapshot.sourceMapV3 != "boolean" ||
99
+ typeof mappingSnapshot.binaryRanges != "boolean") {
100
+ invalidRegistry(`Registry record '${id}' has an invalid mapping declaration.`, id)
101
+ }
102
+ const mapping = deepFreeze(/** @type {import("./semantic/types.js").LanguageMappingCapabilities} */ (mappingSnapshot))
103
+ const acceptanceCandidate = candidate.acceptance
104
+
105
+ if (!isPlainObject(acceptanceCandidate) || !isDenseArray(acceptanceCandidate.stages) ||
106
+ !isDenseArray(acceptanceCandidate.toolchains)) {
107
+ invalidRegistry(`Registry record '${id}' has an invalid acceptance declaration.`, id)
108
+ }
109
+ /** @type {unknown[]} */
110
+ const stageSnapshot = []
111
+ /** @type {unknown[]} */
112
+ const toolchainSnapshot = []
113
+
114
+ for (let index = 0; index < acceptanceCandidate.stages.length; index += 1) {
115
+ stageSnapshot.push(acceptanceCandidate.stages[index])
116
+ }
117
+ for (let index = 0; index < acceptanceCandidate.toolchains.length; index += 1) {
118
+ toolchainSnapshot.push(acceptanceCandidate.toolchains[index])
119
+ }
120
+ if (!stageSnapshot.every((stage) => typeof stage == "string" && acceptanceStages.has(stage)) ||
121
+ !toolchainSnapshot.every((toolchain) => typeof toolchain == "string" && toolchain.length > 0)) {
122
+ invalidRegistry(`Registry record '${id}' has an invalid acceptance declaration.`, id)
123
+ }
124
+ const stages = /** @type {import("./semantic/types.js").AcceptanceStage[]} */ (stageSnapshot)
125
+ const toolchains = /** @type {string[]} */ (toolchainSnapshot)
126
+ let previousStage = -1
127
+ const declaredStages = new Set()
128
+
129
+ for (const stage of stages) {
130
+ const stageIndex = /** @type {number} */ (acceptanceStages.get(/** @type {string} */ (stage)))
131
+
132
+ if (declaredStages.has(stage) || stageIndex < previousStage) invalidRegistry(`Registry record '${id}' has unordered or duplicate acceptance stages.`, id)
133
+ declaredStages.add(stage)
134
+ previousStage = stageIndex
135
+ }
136
+ if (new Set(toolchains).size != toolchains.length) {
137
+ invalidRegistry(`Registry record '${id}' has duplicate acceptance toolchains.`, id)
138
+ }
139
+
140
+ const hasFrontend = typeof candidate.frontend == "function"
141
+ const hasTextBackend = typeof candidate.textBackend == "function"
142
+ const hasBinaryBackend = typeof candidate.binaryBackend == "function"
143
+ const hasApplicationBackend = typeof candidate.applicationBackend == "function"
144
+ const hasBackend = hasTextBackend || hasBinaryBackend || hasApplicationBackend
145
+
146
+ if ((mapping.richText || mapping.sourceMapV3) && !hasTextBackend ||
147
+ mapping.sourceMapV3 && !mapping.richText ||
148
+ mapping.binaryRanges && !hasBinaryBackend && !hasApplicationBackend) {
149
+ invalidRegistry(`Registry record '${id}' declares mapping support without its required backend role.`, id)
150
+ }
151
+ if (candidate.roundTrip && (!hasFrontend || !hasBackend)) {
152
+ invalidRegistry(`Registry record '${id}' declares round-trip support without frontend and backend roles.`, id)
153
+ }
154
+ if (stages.includes("parse") && !hasFrontend || stages.includes("generate") && !hasBackend) {
155
+ invalidRegistry(`Registry record '${id}' declares an acceptance stage without its required role.`, id)
156
+ }
157
+
158
+ if ((hasBackend || candidate.defaultFilename !== undefined) &&
159
+ (typeof candidate.defaultFilename != "string" || candidate.defaultFilename.length == 0)) {
160
+ invalidRegistry(`Registry backend '${id}' requires a default filename.`, id)
161
+ }
162
+ if ((hasBackend || candidate.mediaType !== undefined) &&
163
+ (typeof candidate.mediaType != "string" || candidate.mediaType.length == 0)) {
164
+ invalidRegistry(`Registry backend '${id}' requires a media type.`, id)
165
+ }
166
+
167
+ const acceptance = deepFreeze(/** @type {import("./semantic/types.js").LanguageAcceptanceCapabilities} */ ({
168
+ stages,
169
+ toolchains
170
+ }))
171
+ const record = deepFreeze(/** @type {LanguageRegistryRecord} */ ({
172
+ acceptance,
173
+ artifactMultiplicity,
174
+ id,
175
+ mapping,
176
+ roundTrip: candidate.roundTrip,
177
+ ...(typeof candidate.defaultFilename == "string" ? {defaultFilename: candidate.defaultFilename} : {}),
178
+ ...(typeof candidate.mediaType == "string" ? {mediaType: candidate.mediaType} : {}),
179
+ ...(typeof candidate.frontend == "function" ? {frontend: candidate.frontend} : {}),
180
+ ...(typeof candidate.textBackend == "function" ? {textBackend: candidate.textBackend} : {}),
181
+ ...(typeof candidate.binaryBackend == "function" ? {binaryBackend: candidate.binaryBackend} : {}),
182
+ ...(typeof candidate.applicationBackend == "function" ? {applicationBackend: candidate.applicationBackend} : {}),
183
+ ...(typeof candidate.interoperability == "function" ? {interoperability: candidate.interoperability} : {})
184
+ }))
185
+ const descriptor = deepFreeze(/** @type {import("./semantic/types.js").LanguageCapabilities} */ ({
186
+ acceptance,
187
+ artifactMultiplicity,
188
+ id,
189
+ mapping,
190
+ roles: {
191
+ applicationBackend: typeof candidate.applicationBackend == "function",
192
+ binaryBackend: typeof candidate.binaryBackend == "function",
193
+ frontend: typeof candidate.frontend == "function",
194
+ interoperability: typeof candidate.interoperability == "function",
195
+ textBackend: typeof candidate.textBackend == "function"
196
+ },
197
+ roundTrip: candidate.roundTrip
198
+ }))
199
+
200
+ recordsById.set(id, record)
201
+ descriptors.push(descriptor)
202
+ }
203
+
204
+ const frozenDescriptors = deepFreeze(descriptors)
205
+ const ids = Object.freeze(frozenDescriptors.map(({id}) => id))
206
+
207
+ return Object.freeze({
208
+ descriptors: frozenDescriptors,
209
+ ids,
210
+ record(id) {
211
+ const record = recordsById.get(id)
212
+
213
+ if (!record) unsupportedLanguage(id)
214
+
215
+ return record
216
+ },
217
+ resolve(id, role, location) {
218
+ if (!registryRoles.includes(role)) invalidRegistry(`Unknown registry role '${role}'.`, id)
219
+ const record = recordsById.get(id)
220
+
221
+ if (!record) unsupportedLanguage(id, location, role)
222
+ const implementation = record[role]
223
+
224
+ if (typeof implementation != "function") unsupportedRole(id, publicRoleName(role), location)
225
+
226
+ return implementation
227
+ }
228
+ })
229
+ }
230
+
231
+ /**
232
+ * PHP registry frontend wrapper.
233
+ * @type {Frontend}
234
+ */
235
+ const phpFrontend = ({filename, source}) => parsePhp({filename, source})
236
+
237
+ /**
238
+ * Ruby registry frontend wrapper.
239
+ * @type {Frontend}
240
+ */
241
+ const rubyFrontend = ({filename, source}) => parseRuby({filename, source})
242
+
243
+ /**
244
+ * JavaScript registry frontend wrapper.
245
+ * @type {Frontend}
246
+ */
247
+ const javaScriptFrontend = ({filename, source}) => parseJavaScriptTypeScript({filename, language: "javascript", source})
248
+
249
+ /**
250
+ * TypeScript registry frontend wrapper.
251
+ * @type {Frontend}
252
+ */
253
+ const typeScriptFrontend = ({filename, source}) => parseJavaScriptTypeScript({filename, language: "typescript", source})
254
+
255
+ /**
256
+ * Java registry frontend wrapper.
257
+ * @type {Frontend}
258
+ */
259
+ const javaFrontend = ({filename, source}) => parseJava({filename, source})
260
+
261
+ /**
262
+ * Python registry frontend wrapper.
263
+ * @type {Frontend}
264
+ */
265
+ const pythonFrontend = ({filename, source}) => parsePython({filename, source})
266
+
267
+ const records = [
268
+ language({
269
+ acceptance: {stages: ["parse", "generate", "execute"], toolchains: ["php"]},
270
+ defaultFilename: "program.php",
271
+ frontend: phpFrontend,
272
+ id: "php",
273
+ mediaType: "application/x-httpd-php",
274
+ textBackend: generatePhp
275
+ }),
276
+ language({
277
+ acceptance: {stages: ["parse", "generate", "execute"], toolchains: ["ruby"]},
278
+ defaultFilename: "program.rb",
279
+ frontend: rubyFrontend,
280
+ id: "ruby",
281
+ mediaType: "text/x-ruby",
282
+ textBackend: generateRuby
283
+ }),
284
+ language({
285
+ acceptance: {stages: ["parse", "generate", "execute"], toolchains: ["node"]},
286
+ defaultFilename: "program.js",
287
+ frontend: javaScriptFrontend,
288
+ id: "javascript",
289
+ mediaType: "text/javascript",
290
+ textBackend: generateJavaScript
291
+ }),
292
+ language({
293
+ acceptance: {stages: ["parse", "generate", "compile", "execute"], toolchains: ["tsc", "node"]},
294
+ defaultFilename: "program.ts",
295
+ frontend: typeScriptFrontend,
296
+ id: "typescript",
297
+ mediaType: "text/typescript",
298
+ textBackend: generateTypeScript
299
+ }),
300
+ language({
301
+ acceptance: {stages: ["parse", "generate", "compile", "execute"], toolchains: ["javac", "java"]},
302
+ defaultFilename: "Main.java",
303
+ frontend: javaFrontend,
304
+ id: "java",
305
+ mediaType: "text/x-java-source",
306
+ textBackend: generateJava
307
+ }),
308
+ language({
309
+ acceptance: {stages: ["parse", "generate", "compile", "execute"], toolchains: ["python"]},
310
+ defaultFilename: "program.py",
311
+ frontend: pythonFrontend,
312
+ id: "python",
313
+ mediaType: "text/x-python",
314
+ textBackend: generatePython
315
+ })
316
+ ]
317
+
318
+ export const languageRegistry = createLanguageRegistry(records)
319
+ export const languageCapabilities = languageRegistry.descriptors
320
+ /** @type {readonly import("./semantic/types.js").SemanticLanguage[]} */
321
+ export const supportedLanguages = Object.freeze(languageCapabilities
322
+ .filter(({roles}) => roles.frontend && roles.textBackend)
323
+ .map(({id}) => /** @type {import("./semantic/types.js").SemanticLanguage} */ (id)))
324
+
325
+ /**
326
+ * Supplies common original-five capability declarations.
327
+ * @param {Record<string, unknown>} values - Language-specific record fields.
328
+ * @returns {Record<string, unknown>} Complete record.
329
+ */
330
+ function language(values) {
331
+ return {
332
+ artifactMultiplicity: "single",
333
+ mapping: {binaryRanges: false, richText: true, sourceMapV3: true},
334
+ roundTrip: true,
335
+ ...values
336
+ }
337
+ }
338
+
339
+ /**
340
+ * Formats an internal registry key for a public role diagnostic.
341
+ * @param {string} role - Internal role key.
342
+ * @returns {string} Public role name.
343
+ */
344
+ function publicRoleName(role) {
345
+ if (role == "textBackend") return "text backend"
346
+ if (role == "binaryBackend") return "binary backend"
347
+ if (role == "applicationBackend") return "application-artifact backend"
348
+
349
+ return role
350
+ }
351
+
352
+ /**
353
+ * Throws an unknown-language diagnostic.
354
+ * @param {string} id - Unknown identity.
355
+ * @param {import("./semantic/types.js").SourceLocation | undefined} [location] - Related location.
356
+ * @param {RegistryRole | undefined} [role] - Requested role.
357
+ * @returns {never} Always throws.
358
+ */
359
+ function unsupportedLanguage(id, location, role) {
360
+ const message = role == "frontend" ? "No frontend adapter is registered for this language." :
361
+ role == "textBackend" ? "No source backend is registered for this language." :
362
+ "No language or target is registered for this ID."
363
+
364
+ throw new SemantifoldDiagnostic({
365
+ code: "UNSUPPORTED_LANGUAGE",
366
+ language: id,
367
+ location,
368
+ message
369
+ })
370
+ }
371
+
372
+ /**
373
+ * Throws a normalized registry-shape diagnostic.
374
+ * @param {string} message - Failure detail.
375
+ * @param {string | undefined} [id] - Candidate registry ID.
376
+ * @returns {never} Always throws.
377
+ */
378
+ function invalidRegistry(message, id = "registry") {
379
+ throw new SemantifoldDiagnostic({code: "INVALID_REGISTRY", language: id, message})
380
+ }
381
+
382
+ /**
383
+ * Checks for an ordinary string-keyed object.
384
+ * @param {unknown} value - Candidate value.
385
+ * @returns {value is Record<string, unknown>} Whether the value is a plain object.
386
+ */
387
+ function isPlainObject(value) {
388
+ return typeof value == "object" && value != null && !Array.isArray(value) && Object.getPrototypeOf(value) == Object.prototype
389
+ }
390
+
391
+ /**
392
+ * Deeply freezes registry records and public descriptors.
393
+ * @template T
394
+ * @param {T} value - Registry-owned value.
395
+ * @returns {T} Frozen value.
396
+ */
397
+ function deepFreeze(value) {
398
+ if (!value || typeof value != "object" || Object.isFrozen(value)) return value
399
+
400
+ for (const nested of Object.values(value)) deepFreeze(nested)
401
+
402
+ return Object.freeze(value)
403
+ }
package/src/mapping.js CHANGED
@@ -1104,7 +1104,8 @@ function validPoint(value) {
1104
1104
  * @returns {value is import("./semantic/types.js").SemanticLanguage} Whether supported.
1105
1105
  */
1106
1106
  function isLanguage(value) {
1107
- return value == "php" || value == "ruby" || value == "javascript" || value == "typescript" || value == "java"
1107
+ return value == "php" || value == "ruby" || value == "javascript" || value == "typescript" || value == "java" ||
1108
+ value == "python"
1108
1109
  }
1109
1110
 
1110
1111
  /**
@@ -143,6 +143,16 @@ export function locationFromOffsets(filename, source, startOffset, endOffset) {
143
143
  }
144
144
  }
145
145
 
146
+ /**
147
+ * Checks the one-based lexicographic order of a source range's endpoints.
148
+ * @param {import("./types.js").SourcePoint} start - Inclusive range start.
149
+ * @param {import("./types.js").SourcePoint} end - Exclusive range end.
150
+ * @returns {boolean} Whether end is at or after start.
151
+ */
152
+ export function isSourceRangeOrdered(start, end) {
153
+ return end.line > start.line || end.line == start.line && end.column >= start.column
154
+ }
155
+
146
156
  /**
147
157
  * Builds a location spanning a complete source file.
148
158
  * @param {string} filename - Originating filename.
@@ -185,7 +185,7 @@ export function createGenerationIndex(module, providedSources = []) {
185
185
  */
186
186
  function addSource(filename, content, language) {
187
187
  if (typeof filename != "string" || content !== null && typeof content != "string" ||
188
- language !== null && !["php", "ruby", "javascript", "typescript", "java"].includes(language)) {
188
+ language !== null && !["php", "ruby", "javascript", "typescript", "java", "python"].includes(language)) {
189
189
  throw new TypeError("Malformed semantic source registry entry.")
190
190
  }
191
191
 
@@ -242,7 +242,7 @@ function usableSourceProvenance(provenance) {
242
242
  if (!source || typeof source.id != "string" || sourceIds.has(source.id) || typeof source.filename != "string" ||
243
243
  source.filename.length == 0 ||
244
244
  source.content !== null && typeof source.content != "string" || source.language !== null &&
245
- !["php", "ruby", "javascript", "typescript", "java"].includes(source.language)) return undefined
245
+ !["php", "ruby", "javascript", "typescript", "java", "python"].includes(source.language)) return undefined
246
246
  sourceIds.add(source.id)
247
247
  }
248
248
 
@@ -1,11 +1,45 @@
1
1
  // @ts-check
2
2
 
3
- /** @typedef {"php" | "ruby" | "javascript" | "typescript" | "java"} SemanticLanguage */
3
+ /** @typedef {"php" | "ruby" | "javascript" | "typescript" | "java" | "python"} SemanticLanguage */
4
4
  /** @typedef {"integer" | "boolean" | "string"} SemanticTypeName */
5
5
  /** @typedef {"IntegerNegate" | "BooleanNot"} SemanticUnaryOperation */
6
6
  /** @typedef {"IntegerAdd" | "IntegerSubtract" | "IntegerMultiply" | "BooleanAnd" | "BooleanOr" | "IntegerEqual" | "IntegerNotEqual" | "BooleanEqual" | "BooleanNotEqual" | "StringEqual" | "StringNotEqual" | "IntegerLessThan" | "IntegerLessThanOrEqual" | "IntegerGreaterThan" | "IntegerGreaterThanOrEqual" | "StringConcat"} SemanticBinaryOperation */
7
7
  /** @typedef {"function" | "parameter" | "local"} SemanticSymbolKind */
8
8
  /** @typedef {"declaration" | "read" | "write" | "call"} SemanticSymbolRole */
9
+ /** @typedef {"parse" | "generate" | "compile" | "link" | "validate" | "instantiate" | "execute"} AcceptanceStage */
10
+ /** @typedef {"entry" | "source" | "manifest" | "support" | "mapping" | "resource" | "loader"} GeneratedArtifactRole */
11
+
12
+ /**
13
+ * @typedef LanguageRoleCapabilities
14
+ * @property {boolean} frontend - Parser adapter availability.
15
+ * @property {boolean} textBackend - Single- or multi-file text backend availability.
16
+ * @property {boolean} binaryBackend - Binary backend availability.
17
+ * @property {boolean} applicationBackend - Application-artifact backend availability.
18
+ * @property {boolean} interoperability - Interoperability bridge availability.
19
+ */
20
+
21
+ /**
22
+ * @typedef LanguageMappingCapabilities
23
+ * @property {boolean} richText - Authoritative rich text mappings.
24
+ * @property {boolean} sourceMapV3 - Source Map v3 projection.
25
+ * @property {boolean} binaryRanges - Byte-coordinate binary/resource mappings.
26
+ */
27
+
28
+ /**
29
+ * @typedef LanguageAcceptanceCapabilities
30
+ * @property {readonly AcceptanceStage[]} stages - Applicable ordered acceptance stages.
31
+ * @property {readonly string[]} toolchains - Required canonical toolchain IDs.
32
+ */
33
+
34
+ /**
35
+ * @typedef LanguageCapabilities
36
+ * @property {string} id - Stable registry identity.
37
+ * @property {Readonly<LanguageRoleCapabilities>} roles - Independently registered roles.
38
+ * @property {"single" | "multiple"} artifactMultiplicity - Target artifact multiplicity.
39
+ * @property {boolean} roundTrip - Whether frontend/backend round-trip acceptance is declared.
40
+ * @property {Readonly<LanguageMappingCapabilities>} mapping - Mapping forms.
41
+ * @property {Readonly<LanguageAcceptanceCapabilities>} acceptance - Stages and toolchains.
42
+ */
9
43
 
10
44
  /**
11
45
  * @typedef SourcePoint
@@ -60,6 +94,24 @@
60
94
 
61
95
  /** @typedef {SourceOrigin | DerivedOrigin | SyntheticOrigin} SemanticOrigin */
62
96
 
97
+ /**
98
+ * @typedef GeneratedByteRange
99
+ * @property {{start: number, end: number}} generated - Half-open generated byte offsets.
100
+ * @property {SemanticOrigin} origin - Semantic or explicit synthetic provenance.
101
+ * @property {string} [nodeId] - Originating semantic node identity.
102
+ * @property {string} [symbolId] - Originating semantic symbol identity.
103
+ * @property {string} [role] - Semantic or binary-region role.
104
+ */
105
+
106
+ /**
107
+ * @typedef SemantifoldByteMapping
108
+ * @property {"SemantifoldByteMapping"} schema - Schema discriminator.
109
+ * @property {1} version - Schema version.
110
+ * @property {"bytes"} coordinateSystem - Generated offsets are bytes, never text columns.
111
+ * @property {{path: string, byteLength: number}} generated - Generated artifact identity.
112
+ * @property {readonly GeneratedByteRange[]} ranges - Ordered non-overlapping ranges.
113
+ */
114
+
63
115
  /**
64
116
  * @typedef SemanticNodeSourceProvenance
65
117
  * @property {"SemantifoldNodeProvenance"} schema - Association discriminator.
@@ -147,6 +199,77 @@
147
199
  * @property {string} sourceMapFilename - Default external Source Map filename.
148
200
  */
149
201
 
202
+ /**
203
+ * @typedef TextArtifactProvenance
204
+ * @property {"text"} kind - Rich text provenance discriminator.
205
+ * @property {SemantifoldMapping} mapping - Authoritative range mapping.
206
+ * @property {import("@jridgewell/gen-mapping").EncodedSourceMap} sourceMap - Interoperable projection.
207
+ * @property {string} [sourceMapFilename] - Logical Source Map sidecar path.
208
+ */
209
+
210
+ /**
211
+ * @typedef ByteArtifactProvenance
212
+ * @property {"bytes"} kind - Byte provenance discriminator.
213
+ * @property {SemantifoldByteMapping} mapping - Authoritative byte ranges.
214
+ */
215
+
216
+ /**
217
+ * @typedef ArtifactSyntheticProvenance
218
+ * @property {"synthetic"} kind - Synthetic artifact discriminator.
219
+ * @property {string} reason - Explicit target-scaffolding reason.
220
+ * @property {readonly RelatedOrigin[]} relatedOrigins - Related semantic source ranges.
221
+ */
222
+
223
+ /** @typedef {TextArtifactProvenance | ByteArtifactProvenance | ArtifactSyntheticProvenance} ArtifactProvenance */
224
+
225
+ /**
226
+ * @typedef GeneratedSetArtifact
227
+ * @property {string} path - Unique safe relative POSIX path.
228
+ * @property {"text" | "binary"} contentKind - Content representation.
229
+ * @property {string | Uint8Array} content - Text or a detached copy of the exact generated bytes on every binary read.
230
+ * @property {string} mediaType - Artifact media type.
231
+ * @property {GeneratedArtifactRole} role - Artifact role; exactly one set artifact is entry.
232
+ * @property {"generated"} ownership - Task-015 ownership contract.
233
+ * @property {ArtifactProvenance} provenance - Text, byte-range, or synthetic provenance.
234
+ */
235
+
236
+ /**
237
+ * @typedef GeneratedArtifactSet
238
+ * @property {"GeneratedArtifactSet"} schema - Schema discriminator.
239
+ * @property {1} version - Schema version.
240
+ * @property {string} target - Stable language or target ID.
241
+ * @property {string} entry - Path of the sole declared entry artifact.
242
+ * @property {readonly GeneratedSetArtifact[]} artifacts - Deterministic artifact order.
243
+ */
244
+
245
+ /**
246
+ * @typedef DiscoveredToolchain
247
+ * @property {string} id - Toolchain ID.
248
+ * @property {string} command - Documented canonical command.
249
+ * @property {string} executable - Exact absolute executable path.
250
+ * @property {"override" | "canonical"} source - Resolution route.
251
+ * @property {string} version - First captured version line.
252
+ * @property {string} versionOutput - Complete captured version output.
253
+ * @property {readonly string[]} versionArguments - Exact version argument array.
254
+ */
255
+
256
+ /**
257
+ * @typedef AcceptanceStageResult
258
+ * @property {AcceptanceStage} stage - Completed stage.
259
+ * @property {string} executable - Exact executable used.
260
+ * @property {readonly string[]} arguments - Exact argument array used.
261
+ * @property {string} version - Discovered tool version.
262
+ * @property {string} stdout - Captured standard output.
263
+ * @property {string} stderr - Captured standard error.
264
+ */
265
+
266
+ /**
267
+ * @typedef AcceptanceResult
268
+ * @property {string} target - Target identity.
269
+ * @property {string} directory - Removed isolated project directory identity.
270
+ * @property {readonly AcceptanceStageResult[]} stages - Ordered completed stages.
271
+ */
272
+
150
273
  /**
151
274
  * @typedef TypeReference
152
275
  * @property {"TypeReference"} kind - Node discriminator.