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,101 @@
1
+ // @ts-check
2
+
3
+ import {unsupportedCapability} from "../diagnostic.js"
4
+
5
+ /** @type {Record<import("../semantic/types.js").SemanticLanguage, RegExp>} */
6
+ const identifierPatterns = {
7
+ java: /^[A-Za-z_$][A-Za-z0-9_$]*$/u,
8
+ javascript: /^[A-Za-z_$][A-Za-z0-9_$]*$/u,
9
+ php: /^[A-Za-z_][A-Za-z0-9_]*$/u,
10
+ ruby: /^[a-z_][A-Za-z0-9_]*$/u,
11
+ typescript: /^[A-Za-z_$][A-Za-z0-9_$]*$/u
12
+ }
13
+
14
+ /** @type {Record<import("../semantic/types.js").SemanticLanguage, Set<string>>} */
15
+ const reservedWords = {
16
+ java: new Set([
17
+ "_", "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue",
18
+ "default", "do", "double", "else", "enum", "exports", "extends", "false", "final", "finally", "float", "for",
19
+ "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "module", "native", "new",
20
+ "non-sealed", "null", "open", "opens", "package", "permits", "private", "protected", "provides", "public",
21
+ "record", "requires", "return", "sealed", "short", "static", "strictfp", "super", "switch", "synchronized",
22
+ "this", "throw", "throws", "to", "transient", "transitive", "true", "try", "uses", "var", "void", "volatile",
23
+ "when", "while", "with", "yield"
24
+ ]),
25
+ javascript: new Set([
26
+ "await", "break", "case", "catch", "class", "const", "continue", "debugger", "default", "delete", "do", "else",
27
+ "enum", "export", "extends", "false", "finally", "for", "function", "if", "implements", "import", "in",
28
+ "instanceof", "interface", "let", "new", "null", "package", "private", "protected", "public", "return", "static",
29
+ "super", "switch", "this", "throw", "true", "try", "typeof", "var", "void", "while", "with", "yield"
30
+ ]),
31
+ php: new Set([
32
+ "__halt_compiler", "abstract", "and", "array", "as", "break", "callable", "case", "catch", "class", "clone",
33
+ "const", "continue", "declare", "default", "die", "do", "echo", "else", "elseif", "empty", "enddeclare",
34
+ "endfor", "endforeach", "endif", "endswitch", "endwhile", "enum", "eval", "exit", "extends", "false", "final",
35
+ "finally", "fn", "for", "foreach", "from", "function", "global", "goto", "if", "implements", "include",
36
+ "include_once", "instanceof", "insteadof", "interface", "isset", "iterable", "list", "match", "mixed", "namespace",
37
+ "never", "new", "null", "object", "or", "parent", "print", "private", "protected", "public", "readonly", "require",
38
+ "require_once", "return", "self", "static", "switch", "throw", "trait", "true", "try", "unset", "use", "var",
39
+ "void", "while", "xor", "yield"
40
+ ]),
41
+ ruby: new Set([
42
+ "BEGIN", "END", "__ENCODING__", "__END__", "__FILE__", "__LINE__", "alias", "and", "begin", "break", "case",
43
+ "class", "def", "defined?", "do", "else", "elsif", "end", "ensure", "false", "for", "if", "in", "module",
44
+ "next", "nil", "not", "or", "redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef",
45
+ "unless", "until", "when", "while", "yield"
46
+ ]),
47
+ typescript: new Set([
48
+ "abstract", "any", "as", "asserts", "async", "await", "boolean", "break", "case", "catch", "class", "const",
49
+ "constructor", "continue", "debugger", "declare", "default", "delete", "do", "else", "enum", "export", "extends",
50
+ "false", "finally", "for", "from", "function", "get", "if", "implements", "import", "in", "infer", "instanceof",
51
+ "interface", "is", "keyof", "let", "module", "namespace", "never", "new", "null", "number", "object", "of",
52
+ "override", "package", "private", "protected", "public", "readonly", "require", "return", "satisfies", "set",
53
+ "static", "string", "super", "switch", "symbol", "this", "throw", "true", "try", "type", "typeof", "undefined",
54
+ "unique", "unknown", "using", "var", "void", "while", "with", "yield"
55
+ ])
56
+ }
57
+
58
+ const phpInvalidParameterBindings = new Set([
59
+ "GLOBALS", "_COOKIE", "_ENV", "_FILES", "_GET", "_POST", "_REQUEST", "_SERVER", "_SESSION", "this"
60
+ ])
61
+ const phpInvalidAssignedBindings = new Set(["GLOBALS", "this"])
62
+
63
+ /**
64
+ * Validates an identifier against the target backend's deliberately narrow lexical contract.
65
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Target language.
66
+ * @param {unknown} name - Candidate semantic identifier.
67
+ * @param {string} role - Identifier role for diagnostics.
68
+ * @param {import("../semantic/types.js").SourceLocation | undefined} location - Originating location.
69
+ * @returns {void}
70
+ */
71
+ export function validateTargetIdentifier(language, name, role, location) {
72
+ if (typeof name != "string") unsupportedCapability(language, `${role} identifier`, location)
73
+
74
+ const reservedName = language == "php" ? name.toLowerCase() : name
75
+
76
+ if (!identifierPatterns[language].test(name) || reservedWords[language].has(reservedName)) {
77
+ unsupportedCapability(language, `${role} identifier '${name}'`, location)
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Validates a binding identifier against target restrictions beyond general identifier syntax.
83
+ * @param {import("../semantic/types.js").SemanticLanguage} language - Target language.
84
+ * @param {unknown} name - Candidate semantic identifier.
85
+ * @param {string} role - Binding role for diagnostics.
86
+ * @param {import("../semantic/types.js").SourceLocation | undefined} location - Originating location.
87
+ * @returns {void}
88
+ */
89
+ export function validateTargetBindingIdentifier(language, name, role, location) {
90
+ validateTargetIdentifier(language, name, role, location)
91
+
92
+ const invalidTypeScriptBinding = language == "typescript" && (name == "arguments" || name == "eval")
93
+ const invalidPhpVariable = language == "php" && typeof name == "string" &&
94
+ ((role == "parameter" && phpInvalidParameterBindings.has(name)) ||
95
+ ((role == "local" || role == "assignment target") && phpInvalidAssignedBindings.has(name)))
96
+ const invalidRubyBinding = language == "ruby" && typeof name == "string" && /^_[1-9]$/u.test(name)
97
+
98
+ if (invalidTypeScriptBinding || invalidPhpVariable || invalidRubyBinding) {
99
+ unsupportedCapability(language, `${role} identifier '${name}'`, location)
100
+ }
101
+ }
@@ -0,0 +1,159 @@
1
+ // @ts-check
2
+
3
+ import {SemantifoldDiagnostic, unsupportedCapability} from "../diagnostic.js"
4
+ import {generateJava} from "./java.js"
5
+ import {generateJavaScript} from "./javascript.js"
6
+ import {generatePhp} from "./php.js"
7
+ import {generateRuby} from "./ruby.js"
8
+ import {validateBackendModule} from "./shared.js"
9
+ import {generateTypeScript} from "./typescript.js"
10
+ import {SourceWriter} from "./writer.js"
11
+ import {finalizeMapping, toSourceMapV3} from "../mapping.js"
12
+
13
+ /** @type {Readonly<Record<import("../semantic/types.js").SemanticLanguage, string>>} */
14
+ const defaultFilenames = Object.freeze({
15
+ java: "Main.java",
16
+ javascript: "program.js",
17
+ php: "program.php",
18
+ ruby: "program.rb",
19
+ typescript: "program.ts"
20
+ })
21
+ const lineTerminatorPattern = /[\n\r\u2028\u2029]/u
22
+ const syntheticArtifactBase = Object.freeze(["__semantifold_artifacts__"])
23
+
24
+ /**
25
+ * Generates target source from a semantic module.
26
+ * @param {object} input - Generation request.
27
+ * @param {import("../semantic/types.js").SemanticLanguage} input.language - Target language.
28
+ * @param {import("../semantic/types.js").SemanticModule} input.module - Semantic module.
29
+ * @returns {string} Generated source.
30
+ */
31
+ export function generateSource({language, module}) {
32
+ return generateArtifactSource({language, module}).code
33
+ }
34
+
35
+ /**
36
+ * Generates source together with authoritative rich and Source Map v3 mappings.
37
+ * @param {object} input - Generation request.
38
+ * @param {import("../semantic/types.js").SemanticLanguage} input.language - Target language.
39
+ * @param {import("../semantic/types.js").SemanticModule} input.module - Semantic module.
40
+ * @param {string} [input.filename] - Output filename.
41
+ * @param {"none" | "external" | "inline"} [input.mapDirective] - JavaScript-family source map directive.
42
+ * @param {string} [input.sourceMapFilename] - External source map filename.
43
+ * @param {{filename: string, content: string, language?: import("../semantic/types.js").SemanticLanguage}[]} [input.sources] - Source content for legacy or assembled modules.
44
+ * @returns {import("../semantic/types.js").GeneratedArtifact} Generated artifact.
45
+ */
46
+ export function generateArtifactSource({language, filename = defaultFilenames[language], mapDirective = "none", module, sourceMapFilename = `${filename}.map`, sources}) {
47
+ validateBackendModule(module, language)
48
+
49
+ if (typeof filename != "string" || filename.length == 0 || lineTerminatorPattern.test(filename)) {
50
+ throw new TypeError("Generated filename must be a non-empty single-line string.")
51
+ }
52
+ if (typeof sourceMapFilename != "string" || sourceMapFilename.length == 0 || lineTerminatorPattern.test(sourceMapFilename)) {
53
+ throw new TypeError("Source map filename must be a non-empty single-line string.")
54
+ }
55
+ if (language == "java" && filename.split(/[\\/]/u).at(-1) != "Main.java") {
56
+ unsupportedCapability(language, "artifact filename basename other than Main.java", module.location)
57
+ }
58
+ if (!["none", "external", "inline"].includes(mapDirective)) throw new TypeError(`Unsupported map directive: ${mapDirective}`)
59
+ if (mapDirective != "none" && language != "javascript" && language != "typescript") {
60
+ throw new TypeError("Source map directives are supported only for JavaScript and TypeScript outputs.")
61
+ }
62
+
63
+ const writer = new SourceWriter({filename, language, module, sources})
64
+
65
+ if (language == "php") generatePhp(module, writer)
66
+ else if (language == "ruby") generateRuby(module, writer)
67
+ else if (language == "javascript") generateJavaScript(module, writer)
68
+ else if (language == "typescript") generateTypeScript(module, writer)
69
+ else if (language == "java") generateJava(module, writer)
70
+ else {
71
+ throw new SemantifoldDiagnostic({
72
+ code: "UNSUPPORTED_LANGUAGE",
73
+ language,
74
+ location: module.location,
75
+ message: "No source backend is registered for this language."
76
+ })
77
+ }
78
+
79
+ const sourceMap = toSourceMapV3(writer.finish())
80
+
81
+ if (mapDirective == "external") {
82
+ const sourceMapUrl = encodeArtifactPath(relativeArtifactPath(filename, sourceMapFilename))
83
+
84
+ writer.synthetic(`//# sourceMappingURL=${sourceMapUrl}\n`, "external source map directive", [module])
85
+ } else if (mapDirective == "inline") {
86
+ const encoded = base64Utf8(JSON.stringify(sourceMap))
87
+
88
+ writer.synthetic(`//# sourceMappingURL=data:application/json;charset=utf-8;base64,${encoded}\n`, "inline source map directive", [module])
89
+ }
90
+
91
+ const mapping = finalizeMapping(writer.finish())
92
+
93
+ return {
94
+ code: mapping.generated.content,
95
+ filename,
96
+ language,
97
+ mapping,
98
+ sourceMap,
99
+ sourceMapFilename
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Resolves one logical artifact filename relative to another artifact's directory.
105
+ * @param {string} generatedFilename - Generated code artifact filename.
106
+ * @param {string} relatedFilename - Related artifact filename.
107
+ * @returns {string} Portable forward-slash relative path.
108
+ */
109
+ function relativeArtifactPath(generatedFilename, relatedFilename) {
110
+ const generatedDirectory = resolvedArtifactPathParts(generatedFilename)
111
+ const related = resolvedArtifactPathParts(relatedFilename)
112
+
113
+ generatedDirectory.pop()
114
+ let common = 0
115
+
116
+ while (common < generatedDirectory.length && common < related.length && generatedDirectory[common] == related[common]) common++
117
+
118
+ return [...generatedDirectory.slice(common).map(() => ".."), ...related.slice(common)].join("/") || "."
119
+ }
120
+
121
+ /**
122
+ * Resolves separators and dot segments against the shared synthetic artifact base.
123
+ * @param {string} filename - Logical artifact filename.
124
+ * @returns {string[]} Resolved path parts.
125
+ */
126
+ function resolvedArtifactPathParts(filename) {
127
+ const parts = [...syntheticArtifactBase]
128
+
129
+ for (const part of filename.replaceAll("\\", "/").split("/")) {
130
+ if (part == "" || part == ".") continue
131
+ if (part == "..") {
132
+ if (parts.length > syntheticArtifactBase.length) parts.pop()
133
+ } else parts.push(part)
134
+ }
135
+
136
+ return parts
137
+ }
138
+
139
+ /**
140
+ * Encodes literal filesystem path segments without encoding structural slashes.
141
+ * @param {string} path - Relative artifact path.
142
+ * @returns {string} URL-safe relative artifact path.
143
+ */
144
+ function encodeArtifactPath(path) {
145
+ return path.split("/").map((part) => encodeURIComponent(part)).join("/")
146
+ }
147
+
148
+ /**
149
+ * Encodes Unicode JSON without relying on a Node-only artifact contract.
150
+ * @param {string} value - Unicode string.
151
+ * @returns {string} Base64 UTF-8.
152
+ */
153
+ function base64Utf8(value) {
154
+ let binary = ""
155
+
156
+ for (const byte of new TextEncoder().encode(value)) binary += String.fromCharCode(byte)
157
+
158
+ return btoa(binary)
159
+ }
@@ -0,0 +1,182 @@
1
+ // @ts-check
2
+
3
+ import {emitExpression} from "./shared.js"
4
+ import {emitScalarType} from "./scalars.js"
5
+
6
+ /**
7
+ * Emits an independently executable Java `Main` 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 generateJava(module, writer) {
13
+ writer.synthetic("public final class Main {\n", "Java class scaffolding", [module])
14
+
15
+ module.functions.forEach((declaration, functionIndex) => {
16
+ if (functionIndex > 0) writer.synthetic("\n\n", "declaration separator", [declaration])
17
+
18
+ writer.synthetic(" ", "indentation", [declaration])
19
+ writer.mapped("private static", {mappingKind: "anchor", node: declaration})
20
+ writer.synthetic(" ", "method spacing", [declaration])
21
+ writer.mapped(emitScalarType("java", declaration.returnType), {
22
+ mappingKind: "exact",
23
+ node: declaration.returnType,
24
+ path: `/functions/${functionIndex}/returnType`,
25
+ role: "type"
26
+ })
27
+ writer.synthetic(" ", "method spacing", [declaration])
28
+ writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, role: "name"})
29
+ writer.mapped("(", {mappingKind: "anchor", node: declaration})
30
+ declaration.parameters.forEach((parameter, index) => {
31
+ const parameterPath = `/functions/${functionIndex}/parameters/${index}`
32
+
33
+ if (index > 0) writer.synthetic(", ", "parameter separator", [declaration])
34
+ writer.mapped(emitScalarType("java", parameter.type), {
35
+ mappingKind: "exact",
36
+ node: parameter.type,
37
+ path: `${parameterPath}/type`,
38
+ role: "type"
39
+ })
40
+ writer.synthetic(" ", "parameter spacing", [parameter], [parameterPath])
41
+ writer.mapped(parameter.name, {mappingKind: "exact", node: parameter, path: parameterPath, role: "name"})
42
+ })
43
+ writer.mapped(")", {mappingKind: "anchor", node: declaration})
44
+ writer.synthetic(" ", "method spacing", [declaration])
45
+ writer.mapped("{", {mappingKind: "anchor", node: declaration})
46
+ writer.synthetic("\n", "line break", [declaration])
47
+
48
+ emitBlock(writer, declaration.body, " ", `/functions/${functionIndex}/body`)
49
+ writer.synthetic(" ", "indentation", [declaration])
50
+ writer.mapped("}", {mappingKind: "anchor", node: declaration})
51
+ })
52
+
53
+ writer.synthetic("\n\n", "entry-point separator", [module.entryPoint])
54
+ writer.synthetic(" ", "indentation", [module.entryPoint])
55
+ writer.mapped("public static void main", {mappingKind: "anchor", node: module.entryPoint})
56
+ writer.mapped("(", {mappingKind: "anchor", node: module.entryPoint})
57
+ writer.synthetic("String[] args", "Java entry-point signature", [module.entryPoint])
58
+ writer.mapped(")", {mappingKind: "anchor", node: module.entryPoint})
59
+ writer.synthetic(" ", "method spacing", [module.entryPoint])
60
+ writer.mapped("{", {mappingKind: "anchor", node: module.entryPoint})
61
+ writer.synthetic("\n", "line break", [module.entryPoint])
62
+
63
+ emitBlock(writer, module.entryPoint.body, " ", "/entryPoint/body")
64
+ writer.synthetic(" ", "indentation", [module.entryPoint])
65
+ writer.mapped("}", {mappingKind: "anchor", node: module.entryPoint})
66
+ writer.synthetic("\n}\n", "Java class scaffolding", [module])
67
+ }
68
+
69
+ /**
70
+ * Emits one ordered Java block body.
71
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
72
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
73
+ * @param {string} indent - Indentation.
74
+ * @param {string} path - Exact block path.
75
+ * @returns {void}
76
+ */
77
+ function emitBlock(writer, block, indent, path) {
78
+ block.statements.forEach((statement, index) => emitStatement(writer, statement, indent, `${path}/statements/${index}`))
79
+ }
80
+
81
+ /**
82
+ * Emits one Java statement.
83
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
84
+ * @param {import("../semantic/types.js").Statement} statement - Semantic statement.
85
+ * @param {string} indent - Indentation.
86
+ * @param {string} path - Exact statement path.
87
+ * @returns {void}
88
+ */
89
+ function emitStatement(writer, statement, indent, path) {
90
+ if (statement.kind == "LocalDeclaration" || statement.kind == "AssignmentStatement") return emitLocal(writer, statement, indent, path)
91
+ writer.synthetic(indent, "indentation", [statement], [path])
92
+ if (statement.kind == "ReturnStatement") {
93
+ writer.mapped("return", {mappingKind: "anchor", node: statement, path})
94
+ writer.synthetic(" ", "return spacing", [statement], [path])
95
+ emitExpression(writer, statement.expression, `${path}/expression`, "java", identity)
96
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
97
+ writer.synthetic("\n", "line break", [statement], [path])
98
+ return
99
+ }
100
+ if (statement.kind == "PrintStatement") {
101
+ writer.mapped("System.out.println", {mappingKind: "anchor", node: statement, path})
102
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
103
+ emitExpression(writer, statement.expression, `${path}/expression`, "java", identity)
104
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
105
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path})
106
+ writer.synthetic("\n", "line break", [statement], [path])
107
+ return
108
+ }
109
+ writer.mapped("if", {mappingKind: "anchor", node: statement, path})
110
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
111
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
112
+ emitExpression(writer, statement.condition, `${path}/condition`, "java", identity)
113
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
114
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
115
+ writer.mapped("{", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
116
+ writer.synthetic("\n", "line break", [statement], [path])
117
+ emitBlock(writer, statement.consequent, `${indent} `, `${path}/consequent`)
118
+ writer.synthetic(indent, "indentation", [statement], [path])
119
+ writer.mapped("}", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
120
+ if (statement.alternate) {
121
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
122
+ writer.mapped("else", {mappingKind: "anchor", node: statement, path})
123
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
124
+ writer.mapped("{", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
125
+ writer.synthetic("\n", "line break", [statement], [path])
126
+ emitBlock(writer, statement.alternate, `${indent} `, `${path}/alternate`)
127
+ writer.synthetic(indent, "indentation", [statement], [path])
128
+ writer.mapped("}", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
129
+ }
130
+ writer.synthetic("\n", "line break", [statement], [path])
131
+ }
132
+
133
+ /**
134
+ * Emits one Java local statement.
135
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
136
+ * @param {import("../semantic/types.js").LocalStatement} statement - Local statement.
137
+ * @param {string} indent - Leading indentation.
138
+ * @param {string} statementPath - Exact JSON Pointer for this statement occurrence.
139
+ * @returns {void}
140
+ */
141
+ function emitLocal(writer, statement, indent, statementPath) {
142
+ writer.synthetic(indent, "indentation", [statement], [statementPath])
143
+
144
+ if (statement.kind == "AssignmentStatement") {
145
+ writer.mapped(statement.target.name, {mappingKind: "exact", node: statement.target, path: `${statementPath}/target`, role: "name"})
146
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
147
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
148
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
149
+ emitExpression(writer, statement.expression, `${statementPath}/expression`, "java", identity)
150
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path: statementPath})
151
+ writer.synthetic("\n", "line break", [statement], [statementPath])
152
+ return
153
+ }
154
+
155
+ if (!statement.mutable) {
156
+ writer.mapped("final", {mappingKind: "anchor", node: statement, path: statementPath})
157
+ writer.synthetic(" ", "modifier spacing", [statement], [statementPath])
158
+ }
159
+ writer.mapped(emitScalarType("java", statement.type), {
160
+ mappingKind: "exact",
161
+ node: statement.type,
162
+ path: `${statementPath}/type`,
163
+ role: "type"
164
+ })
165
+ writer.synthetic(" ", "declaration spacing", [statement], [statementPath])
166
+ writer.mapped(statement.name, {mappingKind: "exact", node: statement, path: statementPath, role: "name"})
167
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
168
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
169
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
170
+ emitExpression(writer, statement.initializer, `${statementPath}/initializer`, "java", identity)
171
+ writer.mapped(";", {mappingKind: "anchor", node: statement, path: statementPath})
172
+ writer.synthetic("\n", "line break", [statement], [statementPath])
173
+ }
174
+
175
+ /**
176
+ * Returns an unchanged Java identifier.
177
+ * @param {string} name - Identifier.
178
+ * @returns {string} Identifier.
179
+ */
180
+ function identity(name) {
181
+ return name
182
+ }
@@ -0,0 +1,184 @@
1
+ // @ts-check
2
+
3
+ import {emitExpression, requiresCanonicalZeroRendering} from "./shared.js"
4
+ import {emitScalarType} from "./scalars.js"
5
+
6
+ /**
7
+ * Emits an independently executable JavaScript program with JSDoc types.
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 generateJavaScript(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.synthetic("/**\n * Generated semantic function.\n", "JavaScript type scaffolding", [declaration])
19
+ for (const [parameterIndex, parameter] of declaration.parameters.entries()) {
20
+ const parameterPath = `/functions/${functionIndex}/parameters/${parameterIndex}`
21
+
22
+ writer.synthetic(" * @param {", "JavaScript type scaffolding", [parameter], [parameterPath])
23
+ writer.mapped(emitScalarType("javascript", parameter.type), {
24
+ mappingKind: "exact",
25
+ node: parameter.type,
26
+ path: `${parameterPath}/type`,
27
+ role: "type"
28
+ })
29
+ writer.synthetic("} ", "JavaScript type scaffolding", [parameter], [parameterPath])
30
+ writer.mapped(parameter.name, {mappingKind: "exact", node: parameter, path: parameterPath, role: "name"})
31
+ writer.synthetic(" - Semantic parameter.\n", "JavaScript type scaffolding", [parameter], [parameterPath])
32
+ }
33
+ writer.synthetic(" * @returns {", "JavaScript type scaffolding", [declaration])
34
+ writer.mapped(emitScalarType("javascript", declaration.returnType), {
35
+ mappingKind: "exact",
36
+ node: declaration.returnType,
37
+ path: `/functions/${functionIndex}/returnType`,
38
+ role: "type"
39
+ })
40
+ writer.synthetic("} Semantic result.\n */\n", "JavaScript type scaffolding", [declaration])
41
+ writer.mapped("function", {mappingKind: "anchor", node: declaration})
42
+ writer.synthetic(" ", "function spacing", [declaration])
43
+ writer.mapped(declaration.name, {mappingKind: "exact", node: declaration, role: "name"})
44
+ writer.mapped("(", {mappingKind: "anchor", node: declaration})
45
+ declaration.parameters.forEach((parameter, index) => {
46
+ if (index > 0) writer.synthetic(", ", "parameter separator", [declaration])
47
+ writer.mapped(parameter.name, {
48
+ mappingKind: "exact",
49
+ node: parameter,
50
+ path: `/functions/${functionIndex}/parameters/${index}`,
51
+ role: "name"
52
+ })
53
+ })
54
+ writer.mapped(")", {mappingKind: "anchor", node: declaration})
55
+ writer.synthetic(" ", "function spacing", [declaration])
56
+ writer.mapped("{", {mappingKind: "anchor", node: declaration})
57
+ writer.synthetic("\n", "line break", [declaration])
58
+
59
+ emitBlock(writer, declaration.body, " ", `/functions/${functionIndex}/body`, canonicalizeZero)
60
+ writer.mapped("}", {mappingKind: "anchor", node: declaration})
61
+ })
62
+
63
+ writer.synthetic("\n\n", "entry-point separator", [module.entryPoint])
64
+ emitBlock(writer, module.entryPoint.body, "", "/entryPoint/body", canonicalizeZero)
65
+ }
66
+
67
+ /**
68
+ * Emits one ordered JavaScript block body.
69
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
70
+ * @param {import("../semantic/types.js").Block} block - Semantic block.
71
+ * @param {string} indent - Indentation.
72
+ * @param {string} blockPath - Exact JSON Pointer for the block.
73
+ * @param {boolean} canonicalizeZero - Whether printed integer zero must be canonicalized.
74
+ * @returns {void}
75
+ */
76
+ function emitBlock(writer, block, indent, blockPath, canonicalizeZero) {
77
+ block.statements.forEach((statement, index) => emitStatement(
78
+ writer, statement, indent, `${blockPath}/statements/${index}`, canonicalizeZero
79
+ ))
80
+ }
81
+
82
+ /**
83
+ * Emits one JavaScript statement.
84
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
85
+ * @param {import("../semantic/types.js").Statement} statement - Semantic statement.
86
+ * @param {string} indent - Indentation.
87
+ * @param {string} path - Exact statement path.
88
+ * @param {boolean} canonicalizeZero - Whether printed integer zero must be canonicalized.
89
+ * @returns {void}
90
+ */
91
+ function emitStatement(writer, statement, indent, path, canonicalizeZero) {
92
+ if (statement.kind == "LocalDeclaration" || statement.kind == "AssignmentStatement") return emitLocal(writer, statement, indent, path)
93
+
94
+ writer.synthetic(indent, "indentation", [statement], [path])
95
+ if (statement.kind == "ReturnStatement") {
96
+ writer.mapped("return", {mappingKind: "anchor", node: statement, path})
97
+ writer.synthetic(" ", "return spacing", [statement], [path])
98
+ emitExpression(writer, statement.expression, `${path}/expression`, "javascript", identity)
99
+ writer.synthetic("\n", "line break", [statement], [path])
100
+ return
101
+ }
102
+ if (statement.kind == "PrintStatement") {
103
+ writer.mapped("console.log", {mappingKind: "anchor", node: statement, path})
104
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
105
+ if (canonicalizeZero) writer.synthetic("(", "canonical integer output", [statement], [path])
106
+ emitExpression(writer, statement.expression, `${path}/expression`, "javascript", identity)
107
+ if (canonicalizeZero) writer.synthetic(").toString()", "canonical integer output", [statement], [path])
108
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
109
+ writer.synthetic("\n", "line break", [statement], [path])
110
+ return
111
+ }
112
+
113
+ writer.mapped("if", {mappingKind: "anchor", node: statement, path})
114
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
115
+ writer.mapped("(", {mappingKind: "anchor", node: statement, path})
116
+ emitExpression(writer, statement.condition, `${path}/condition`, "javascript", identity)
117
+ writer.mapped(")", {mappingKind: "anchor", node: statement, path})
118
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
119
+ writer.mapped("{", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
120
+ writer.synthetic("\n", "line break", [statement], [path])
121
+ emitBlock(writer, statement.consequent, `${indent} `, `${path}/consequent`, canonicalizeZero)
122
+ writer.synthetic(indent, "indentation", [statement], [path])
123
+ writer.mapped("}", {mappingKind: "anchor", node: statement.consequent, path: `${path}/consequent`})
124
+ if (statement.alternate) {
125
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
126
+ writer.mapped("else", {mappingKind: "anchor", node: statement, path})
127
+ writer.synthetic(" ", "conditional spacing", [statement], [path])
128
+ writer.mapped("{", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
129
+ writer.synthetic("\n", "line break", [statement], [path])
130
+ emitBlock(writer, statement.alternate, `${indent} `, `${path}/alternate`, canonicalizeZero)
131
+ writer.synthetic(indent, "indentation", [statement], [path])
132
+ writer.mapped("}", {mappingKind: "anchor", node: statement.alternate, path: `${path}/alternate`})
133
+ }
134
+ writer.synthetic("\n", "line break", [statement], [path])
135
+ }
136
+
137
+ /**
138
+ * Emits one JavaScript local statement and its JSDoc type carrier.
139
+ * @param {import("./writer.js").SourceWriter} writer - Source-aware writer.
140
+ * @param {import("../semantic/types.js").LocalStatement} statement - Local statement.
141
+ * @param {string} indent - Leading indentation.
142
+ * @param {string} statementPath - Exact JSON Pointer for this statement occurrence.
143
+ * @returns {void}
144
+ */
145
+ function emitLocal(writer, statement, indent, statementPath) {
146
+ writer.synthetic(indent, "indentation", [statement], [statementPath])
147
+
148
+ if (statement.kind == "AssignmentStatement") {
149
+ writer.mapped(statement.target.name, {mappingKind: "exact", node: statement.target, path: `${statementPath}/target`, role: "name"})
150
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
151
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
152
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
153
+ emitExpression(writer, statement.expression, `${statementPath}/expression`, "javascript", identity)
154
+ writer.synthetic("\n", "line break", [statement], [statementPath])
155
+ return
156
+ }
157
+
158
+ writer.synthetic("/** @type {", "JavaScript local type scaffolding", [statement], [statementPath])
159
+ writer.mapped(emitScalarType("javascript", statement.type), {
160
+ mappingKind: "exact",
161
+ node: statement.type,
162
+ path: `${statementPath}/type`,
163
+ role: "type"
164
+ })
165
+ writer.synthetic("} */\n", "JavaScript local type scaffolding", [statement], [statementPath])
166
+ writer.synthetic(indent, "indentation", [statement], [statementPath])
167
+ writer.mapped(statement.mutable ? "let" : "const", {mappingKind: "anchor", node: statement, path: statementPath})
168
+ writer.synthetic(" ", "declaration spacing", [statement], [statementPath])
169
+ writer.mapped(statement.name, {mappingKind: "exact", node: statement, path: statementPath, role: "name"})
170
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
171
+ writer.mapped("=", {mappingKind: "exact", node: statement, path: statementPath, role: "operator"})
172
+ writer.synthetic(" ", "assignment spacing", [statement], [statementPath])
173
+ emitExpression(writer, statement.initializer, `${statementPath}/initializer`, "javascript", identity)
174
+ writer.synthetic("\n", "line break", [statement], [statementPath])
175
+ }
176
+
177
+ /**
178
+ * Returns an unchanged JavaScript identifier.
179
+ * @param {string} name - Identifier.
180
+ * @returns {string} Identifier.
181
+ */
182
+ function identity(name) {
183
+ return name
184
+ }