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,224 @@
1
+ // @ts-check
2
+
3
+ import {createGenerationIndex} from "../semantic/provenance.js"
4
+
5
+ /** Source-aware deterministic generated-output builder. */
6
+ export class SourceWriter {
7
+ /**
8
+ * Creates a source-aware writer.
9
+ * @param {object} options - Writer options.
10
+ * @param {string} options.filename - Output filename.
11
+ * @param {import("../semantic/types.js").SemanticLanguage} options.language - Output language.
12
+ * @param {import("../semantic/types.js").SemanticModule} options.module - Semantic module.
13
+ * @param {{filename: string, content: string, language?: import("../semantic/types.js").SemanticLanguage}[]} [options.sources] - Caller-provided sources.
14
+ */
15
+ constructor({filename, language, module, sources}) {
16
+ const index = createGenerationIndex(module, sources)
17
+
18
+ this.filename = filename
19
+ this.language = language
20
+ this.index = index
21
+ /** @type {string[]} */
22
+ this.parts = []
23
+ /** @type {import("../semantic/types.js").SemantifoldMappingSpan[]} */
24
+ this.spans = []
25
+ this.offset = 0
26
+ this.line = 1
27
+ this.column = 1
28
+ }
29
+
30
+ /**
31
+ * Returns generated content so far.
32
+ * @returns {string} Generated content.
33
+ */
34
+ get code() {
35
+ return this.parts.join("")
36
+ }
37
+
38
+ /**
39
+ * Returns the current JSON Pointer for one unambiguous semantic occurrence.
40
+ * @param {import("../semantic/types.js").SemanticNode} node - Semantic node occurrence.
41
+ * @returns {string} Current occurrence path.
42
+ */
43
+ occurrencePath(node) {
44
+ return this.index.recordFor(node).path
45
+ }
46
+
47
+ /**
48
+ * Reports whether an exact parser-owned role range is available.
49
+ * @param {import("../semantic/types.js").SemanticNode} node - Semantic node occurrence.
50
+ * @param {string} path - JSON Pointer for a shared node occurrence.
51
+ * @param {string} role - Semantic token role.
52
+ * @returns {boolean} Whether the role has an exact source range.
53
+ */
54
+ hasRange(node, path, role) {
55
+ return this.index.recordFor(node, path).ranges[role] !== undefined
56
+ }
57
+
58
+ /**
59
+ * Writes text mapped to a semantic node.
60
+ * @param {string} text - Generated text.
61
+ * @param {object} annotation - Mapping annotation.
62
+ * @param {import("../semantic/types.js").SemanticNode} annotation.node - Semantic node.
63
+ * @param {"exact" | "anchor"} annotation.mappingKind - Mapping precision.
64
+ * @param {string} [annotation.role] - Semantic token role.
65
+ * @param {string} [annotation.name] - Source Map name.
66
+ * @param {string} [annotation.path] - JSON Pointer for a shared node occurrence.
67
+ * @returns {void}
68
+ */
69
+ mapped(text, {mappingKind, name, node, path, role}) {
70
+ if (text.length == 0) return
71
+
72
+ const record = this.index.recordFor(node, path)
73
+ const roleLocation = role ? record.ranges[role] : undefined
74
+ const origin = roleLocation ? this.#sourceOrigin(roleLocation, record.origin) : record.origin
75
+ const precision = mappingKind == "exact" && role && !roleLocation ? "anchor" : mappingKind
76
+ const symbol = record.symbolId
77
+ ? this.index.provenance.symbols.find((candidate) => candidate.id == record.symbolId)
78
+ : undefined
79
+
80
+ this.#append(text, {
81
+ mappingKind: precision,
82
+ name: name ?? symbol?.name,
83
+ nodeId: record.id,
84
+ origin,
85
+ role,
86
+ symbolId: record.symbolId
87
+ })
88
+ }
89
+
90
+ /**
91
+ * Writes source-free scaffolding with optional semantic context.
92
+ * @param {string} text - Generated text.
93
+ * @param {string} reason - Stable reason.
94
+ * @param {import("../semantic/types.js").SemanticNode[]} [relatedNodes] - Related nodes.
95
+ * @param {(string | undefined)[]} [relatedPaths] - Occurrence paths for shared related nodes.
96
+ * @returns {void}
97
+ */
98
+ synthetic(text, reason, relatedNodes = [], relatedPaths = []) {
99
+ if (text.length == 0) return
100
+
101
+ /** @type {Set<string>} */
102
+ const seen = new Set()
103
+ const relatedOrigins = relatedNodes.flatMap((node, index) => {
104
+ const record = this.index.recordFor(node, relatedPaths[index])
105
+
106
+ return relatedOriginsFor(record.origin).flatMap((related) => {
107
+ const contextual = {
108
+ ...related,
109
+ nodeId: related.nodeId ?? record.id,
110
+ role: related.role ?? "context"
111
+ }
112
+ const key = relatedOriginKey(contextual)
113
+
114
+ if (seen.has(key)) return []
115
+ seen.add(key)
116
+
117
+ return [contextual]
118
+ })
119
+ })
120
+
121
+ this.#append(text, {mappingKind: "synthetic", origin: {kind: "synthetic", reason, relatedOrigins}})
122
+ }
123
+
124
+ /**
125
+ * Returns the authoritative rich map for the current content.
126
+ * @returns {import("../semantic/types.js").SemantifoldMapping} Mapping.
127
+ */
128
+ finish() {
129
+ return {
130
+ coordinateSystem: "utf16",
131
+ generated: {content: this.code, filename: this.filename, language: this.language},
132
+ nodes: [...this.index.provenance.nodes],
133
+ schema: "SemantifoldMapping",
134
+ sources: [...this.index.provenance.sources],
135
+ spans: [...this.spans],
136
+ symbols: [...this.index.provenance.symbols],
137
+ version: 1
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Appends one mapped span.
143
+ * @param {string} text - Generated text.
144
+ * @param {Omit<import("../semantic/types.js").SemantifoldMappingSpan, "generated">} annotation - Span annotation.
145
+ * @returns {void}
146
+ */
147
+ #append(text, annotation) {
148
+ if (text.includes("\r")) throw new TypeError("Generated output must use deterministic LF line endings.")
149
+
150
+ const start = {column: this.column, line: this.line, offset: this.offset}
151
+
152
+ this.parts.push(text)
153
+ for (let index = 0; index < text.length; index++) {
154
+ this.offset++
155
+ if (text[index] == "\n") {
156
+ this.line++
157
+ this.column = 1
158
+ } else this.column++
159
+ }
160
+ const compact = /** @type {Omit<import("../semantic/types.js").SemantifoldMappingSpan, "generated">} */ (
161
+ Object.fromEntries(Object.entries(annotation).filter(([, value]) => value !== undefined))
162
+ )
163
+
164
+ this.spans.push({
165
+ ...compact,
166
+ generated: {
167
+ end: {column: this.column, line: this.line, offset: this.offset},
168
+ filename: this.filename,
169
+ start
170
+ }
171
+ })
172
+ }
173
+
174
+ /**
175
+ * Creates a direct role origin using canonical source identities.
176
+ * @param {import("../semantic/types.js").SourceLocation} location - Exact role range.
177
+ * @param {import("../semantic/types.js").SemanticOrigin} fallback - Owning origin.
178
+ * @returns {import("../semantic/types.js").SemanticOrigin} Role origin.
179
+ */
180
+ #sourceOrigin(location, fallback) {
181
+ /** @type {string | undefined} */
182
+ let fallbackSourceId
183
+
184
+ if (fallback.kind == "source" && fallback.location.filename == location.filename) fallbackSourceId = fallback.sourceId
185
+ else if (fallback.kind == "derived") {
186
+ fallbackSourceId = fallback.origins.find((origin) => origin.location.filename == location.filename)?.sourceId
187
+ } else if (fallback.kind == "synthetic") {
188
+ fallbackSourceId = fallback.relatedOrigins.find((origin) => origin.location.filename == location.filename)?.sourceId
189
+ }
190
+ const source = this.index.provenance.sources.find((candidate) => candidate.id == fallbackSourceId) ??
191
+ this.index.provenance.sources.find((candidate) => candidate.filename == location.filename)
192
+
193
+ return source ? {kind: "source", location, sourceId: source.id} : fallback
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Expands every closed provenance range in semantic order.
199
+ * @param {import("../semantic/types.js").SemanticOrigin} origin - Closed provenance.
200
+ * @returns {import("../semantic/types.js").RelatedOrigin[]} Related ranges.
201
+ */
202
+ function relatedOriginsFor(origin) {
203
+ if (origin.kind == "source") return [{location: origin.location, sourceId: origin.sourceId}]
204
+ if (origin.kind == "derived") return origin.origins
205
+
206
+ return origin.relatedOrigins
207
+ }
208
+
209
+ /**
210
+ * Builds a deterministic identity for one contextual related origin.
211
+ * @param {import("../semantic/types.js").RelatedOrigin} origin - Related origin.
212
+ * @returns {string} Stable identity.
213
+ */
214
+ function relatedOriginKey(origin) {
215
+ return JSON.stringify([
216
+ origin.sourceId,
217
+ origin.location.filename,
218
+ origin.location.start.offset,
219
+ origin.location.end.offset,
220
+ origin.nodeId ?? null,
221
+ origin.symbolId ?? null,
222
+ origin.role ?? null
223
+ ])
224
+ }
@@ -0,0 +1,103 @@
1
+ // @ts-check
2
+
3
+ /** A clear parser, semantic, or backend capability diagnostic. */
4
+ export class SemantifoldDiagnostic extends Error {
5
+ /**
6
+ * Creates a diagnostic.
7
+ * @param {object} options - Diagnostic fields.
8
+ * @param {string} options.code - Stable diagnostic code.
9
+ * @param {string} options.message - Human-readable message.
10
+ * @param {import("./semantic/types.js").SemanticLanguage} options.language - Related language.
11
+ * @param {import("./semantic/types.js").SourceLocation | undefined} [options.location] - Related location.
12
+ * @param {import("./semantic/types.js").SourceLocation | undefined} [options.generatedLocation] - Pre-remapping generated location.
13
+ * @param {Error | undefined} [options.cause] - Preserved parser error.
14
+ */
15
+ constructor({cause, code, generatedLocation, language, location, message}) {
16
+ const locationText = location ? ` at ${location.filename}:${location.start.line}:${location.start.column}` : ""
17
+
18
+ super(`[${code}] ${language}${locationText}: ${message}`, cause ? {cause} : undefined)
19
+ this.name = "SemantifoldDiagnostic"
20
+ this.code = code
21
+ this.detail = message
22
+ this.generatedLocation = generatedLocation
23
+ this.language = language
24
+ this.location = location
25
+ }
26
+ }
27
+
28
+ /**
29
+ * Throws a normalized parser diagnostic while preserving the parser error.
30
+ * @param {import("./semantic/types.js").SemanticLanguage} language - Frontend language.
31
+ * @param {unknown} error - Opaque parser error, narrowed immediately.
32
+ * @returns {never} Always throws.
33
+ */
34
+ export function parseFailure(language, error) {
35
+ const cause = error instanceof Error ? error : new Error(String(error))
36
+
37
+ throw new SemantifoldDiagnostic({
38
+ cause,
39
+ code: "PARSE_ERROR",
40
+ language,
41
+ message: cause.message
42
+ })
43
+ }
44
+
45
+ /**
46
+ * Throws an unsupported-syntax diagnostic.
47
+ * @param {import("./semantic/types.js").SemanticLanguage} language - Frontend language.
48
+ * @param {string} syntax - Parser syntax kind.
49
+ * @param {import("./semantic/types.js").SourceLocation | undefined} location - Source location.
50
+ * @returns {never} Always throws.
51
+ */
52
+ export function unsupportedSyntax(language, syntax, location) {
53
+ throw new SemantifoldDiagnostic({
54
+ code: "UNSUPPORTED_SYNTAX",
55
+ language,
56
+ location,
57
+ message: `Syntax '${syntax}' is outside the implemented semantic subset.`
58
+ })
59
+ }
60
+
61
+ /**
62
+ * Throws a missing-type diagnostic.
63
+ * @param {import("./semantic/types.js").SemanticLanguage} language - Frontend language.
64
+ * @param {string} subject - Untyped subject.
65
+ * @param {import("./semantic/types.js").SourceLocation | undefined} location - Source location.
66
+ * @returns {never} Always throws.
67
+ */
68
+ export function missingType(language, subject, location) {
69
+ throw new SemantifoldDiagnostic({
70
+ code: "MISSING_TYPE",
71
+ language,
72
+ location,
73
+ message: `${subject} requires an explicit supported scalar type: integer, boolean, or string.`
74
+ })
75
+ }
76
+
77
+ /**
78
+ * Throws a language-neutral semantic diagnostic discovered after adaptation.
79
+ * @param {import("./semantic/types.js").SemanticLanguage} language - Source language.
80
+ * @param {string} code - Stable semantic diagnostic code.
81
+ * @param {string} detail - Human-readable semantic failure.
82
+ * @param {import("./semantic/types.js").SourceLocation | undefined} location - Source location.
83
+ * @returns {never} Always throws.
84
+ */
85
+ export function semanticFailure(language, code, detail, location) {
86
+ throw new SemantifoldDiagnostic({code, language, location, message: detail})
87
+ }
88
+
89
+ /**
90
+ * Throws an unsupported backend-capability diagnostic.
91
+ * @param {import("./semantic/types.js").SemanticLanguage} language - Backend language.
92
+ * @param {string} capability - Unsupported semantic capability.
93
+ * @param {import("./semantic/types.js").SourceLocation | undefined} location - Semantic source location.
94
+ * @returns {never} Always throws.
95
+ */
96
+ export function unsupportedCapability(language, capability, location) {
97
+ throw new SemantifoldDiagnostic({
98
+ code: "UNSUPPORTED_CAPABILITY",
99
+ language,
100
+ location,
101
+ message: `Backend cannot emit semantic capability '${capability}'.`
102
+ })
103
+ }
@@ -0,0 +1,36 @@
1
+ // @ts-check
2
+
3
+ import {SemantifoldDiagnostic} from "../diagnostic.js"
4
+ import {annotateParsedModule} from "../semantic/provenance.js"
5
+ import {validateParsedModule} from "../semantic/validate.js"
6
+ import {parseJava} from "./java.js"
7
+ import {parseJavaScriptTypeScript} from "./javascript-typescript.js"
8
+ import {parsePhp} from "./php.js"
9
+ import {parseRuby} from "./ruby.js"
10
+
11
+ /**
12
+ * Parses supported source into a shared semantic module.
13
+ * @param {object} input - Parse request.
14
+ * @param {string} input.filename - Source filename used in diagnostics.
15
+ * @param {import("../semantic/types.js").SemanticLanguage} input.language - Source language.
16
+ * @param {string} input.source - Source text.
17
+ * @returns {import("../semantic/types.js").SemanticModule} Semantic module.
18
+ */
19
+ export function parseSource({filename, language, source}) {
20
+ let module
21
+
22
+ if (language == "php") module = validateParsedModule(parsePhp({filename, source}), language)
23
+ else if (language == "ruby") module = validateParsedModule(parseRuby({filename, source}), language)
24
+ if (language == "javascript" || language == "typescript") {
25
+ module = validateParsedModule(parseJavaScriptTypeScript({filename, language, source}), language)
26
+ }
27
+ else if (language == "java") module = validateParsedModule(parseJava({filename, source}), language)
28
+
29
+ if (module) return annotateParsedModule(module, {filename, language, source})
30
+
31
+ throw new SemantifoldDiagnostic({
32
+ code: "UNSUPPORTED_LANGUAGE",
33
+ language,
34
+ message: "No frontend adapter is registered for this language."
35
+ })
36
+ }