semantifold 0.10.0 → 0.12.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 (56) hide show
  1. package/README.md +2 -2
  2. package/build/src/backends/c.d.ts +2 -1
  3. package/build/src/backends/c.d.ts.map +1 -1
  4. package/build/src/backends/c.js +26 -5
  5. package/build/src/backends/cpp.js +3 -1
  6. package/build/src/backends/kotlin.d.ts +1 -1
  7. package/build/src/backends/kotlin.js +2 -2
  8. package/build/src/backends/ordered-expressions.d.ts +19 -7
  9. package/build/src/backends/ordered-expressions.d.ts.map +1 -1
  10. package/build/src/backends/ordered-expressions.js +46 -11
  11. package/build/src/backends/shared.d.ts.map +1 -1
  12. package/build/src/backends/shared.js +30 -2
  13. package/build/src/frontends/c.d.ts +34 -1
  14. package/build/src/frontends/c.d.ts.map +1 -1
  15. package/build/src/frontends/c.js +128 -21
  16. package/build/src/frontends/java.d.ts.map +1 -1
  17. package/build/src/frontends/java.js +23 -1
  18. package/build/src/frontends/javascript-typescript.d.ts.map +1 -1
  19. package/build/src/frontends/javascript-typescript.js +45 -4
  20. package/build/src/frontends/php.d.ts.map +1 -1
  21. package/build/src/frontends/php.js +54 -3
  22. package/build/src/frontends/ruby.d.ts.map +1 -1
  23. package/build/src/frontends/ruby.js +55 -5
  24. package/build/src/language-registry.d.ts.map +1 -1
  25. package/build/src/language-registry.js +33 -6
  26. package/build/src/semantic/effects.js +3 -1
  27. package/build/src/semantic/provenance.d.ts.map +1 -1
  28. package/build/src/semantic/provenance.js +11 -1
  29. package/build/src/semantic/types.d.ts +52 -2
  30. package/build/src/semantic/types.d.ts.map +1 -1
  31. package/build/src/semantic/types.js +14 -2
  32. package/build/src/semantic/validate.js +19 -2
  33. package/build/src/toolchains.js +3 -3
  34. package/docs/adr-expression-breadth.md +186 -0
  35. package/docs/c.md +1 -1
  36. package/docs/kotlin.md +2 -2
  37. package/docs/language-support.md +14 -5
  38. package/docs/parser-qualification.md +1 -1
  39. package/docs/testing.md +5 -4
  40. package/package.json +1 -1
  41. package/src/backends/c.js +23 -4
  42. package/src/backends/cpp.js +1 -0
  43. package/src/backends/kotlin.js +1 -1
  44. package/src/backends/ordered-expressions.js +46 -10
  45. package/src/backends/shared.js +31 -1
  46. package/src/frontends/c.js +135 -19
  47. package/src/frontends/java.js +24 -0
  48. package/src/frontends/javascript-typescript.js +49 -2
  49. package/src/frontends/php.js +61 -2
  50. package/src/frontends/ruby.js +62 -2
  51. package/src/language-registry.js +32 -5
  52. package/src/semantic/effects.js +1 -0
  53. package/src/semantic/provenance.js +8 -0
  54. package/src/semantic/types.js +14 -1
  55. package/src/semantic/validate.js +27 -1
  56. package/src/toolchains.js +2 -2
package/docs/testing.md CHANGED
@@ -36,7 +36,7 @@ These tests generate and publish source artifacts only. They do not run `javac`,
36
36
 
37
37
  ## Task 005 general function signatures and calls
38
38
 
39
- Task 005 stays bounded to PHP, Ruby, JavaScript with JSDoc, TypeScript, and Java. `general-function-signatures-and-calls.spec.js` proves equivalent zero-, one-, and three-parameter signatures, complete signature collection, nested calls, terminating recursion, scalar/void returns, and the stable semantic failures. `general-function-frontend-validation.spec.js` owns the excluded parameter, call, method, receiver, dispatch, annotation, overload, generic, and higher-order source forms. `general-function-backend-validation.spec.js` proves generation/reparse, exact external-IR signature revalidation, target conflicts, the public feature flag, and transactional rejection from every deferred Task 025/Wasm target. `general-function-provenance-and-mapping.spec.js` proves deterministic declaration/resolution identities and exact signature/call mappings. `general-function-runtime-execution.spec.js` invokes real PHP, Ruby, Node, local `tsc` followed by Node, `javac`, and `java`, requiring exact `ready\n6\n` output; a missing command fails.
39
+ Task 005 stays bounded to PHP, Ruby, JavaScript with JSDoc, TypeScript, Java, Dart, Zig, and C. `general-function-signatures-and-calls.spec.js` proves equivalent zero-, one-, and three-parameter signatures, complete signature collection, nested calls, terminating recursion, scalar/void returns, and the stable semantic failures. `general-function-frontend-validation.spec.js` owns the excluded parameter, call, method, receiver, dispatch, annotation, overload, generic, and higher-order source forms. `general-function-backend-validation.spec.js` proves generation/reparse, exact external-IR signature revalidation, target conflicts, the public feature flag, and transactional rejection from every deferred Task 025/Wasm target. `general-function-provenance-and-mapping.spec.js` proves deterministic declaration/resolution identities and exact signature/call mappings. `general-function-runtime-execution.spec.js` invokes real PHP, Ruby, Node, local `tsc` followed by Node, `javac`, and `java`, requiring exact `ready\n6\n` output; a missing command fails. `c-task005-acceptance.spec.js` proves the C cohort extension: generated-C reparse with identical meaning, the C fixture through every original-five text target, and real Clang execution of each original-five Task-005 fixture and the C fixture at `-O0`/`-O2` with and without sanitizers, including prototype-mediated mutual recursion and bare void returns.
40
40
 
41
41
  Run the Task 005 files individually and sequentially:
42
42
 
@@ -46,9 +46,10 @@ npx velocious-test spec/general-function-frontend-validation.spec.js
46
46
  npx velocious-test spec/general-function-backend-validation.spec.js
47
47
  npx velocious-test spec/general-function-provenance-and-mapping.spec.js
48
48
  npx velocious-test spec/general-function-runtime-execution.spec.js
49
+ npx velocious-test spec/c-task005-acceptance.spec.js
49
50
  ```
50
51
 
51
- Task 013 is the separate terminal original-five aggregate gate. Task 005 does not expand that matrix or adopt Python, C#, C, C++, Rust, Swift, Kotlin/JVM, Go, or browser Wasm beyond focused capability rejection.
52
+ Task 013 is the separate terminal original-five aggregate gate. Task 005 does not expand that matrix or adopt Python, C#, C++, Rust, Swift, Kotlin/JVM, Go, or browser Wasm beyond focused capability rejection.
52
53
 
53
54
  ## Task 011 typed errors and handling
54
55
 
@@ -380,7 +381,7 @@ Task 017 adds the distinct ordered `restore` stage and configured .NET SDK 10 di
380
381
 
381
382
  Task 024 adds configured Go 1.26.x discovery restricted to Linux/amd64; the patch release is read from the discovered toolchain rather than fixed by runtime tests. Acceptance obtains `GOROOT` from that exact `go`, requires the companion `gofmt` beside the configured or discovered executable to resolve to the same `GOROOT/bin/gofmt`, and checks `gofmt -d` before native execution. Each dependency-free generated module is run with `GOTOOLCHAIN=local`, `GOPROXY=off`, `GOSUMDB=off`, `GOVCS=off`, `CGO_ENABLED=0`, `GOENV=off`, `GOWORK=off`, `GOOS=linux`, `GOARCH=amd64`, and isolated HOME/GOPATH/build/module caches. The short-lived discovery fixture also writes exact `off` mode into its isolated Go telemetry directory before invoking the tool, then asserts that the mode file is the only telemetry state: Go 1.26 otherwise starts an unawaited uploader sidecar even in default `local` mode, which can recreate `upload/` while the fixture root is being removed. Cleanup errors remain fatal. Stages run offline `go build`, mandatory `go vet`, and `go run`, while compiler-only differential cases use the matching `go tool compile`. Tests require exact status/stdout, unchanged `go.mod`/`main.go`, no `go.sum`/`go.work`/vendor tree, and byte-identical binaries and output in two fresh directories. Missing, wrong-minor, wrong-architecture, mismatched-gofmt, download, cgo, workspace, vet, formatting, nondeterminism, telemetry leakage, or cleanup evidence fails without a skip.
382
383
 
383
- Task 023 adds exact Kotlin/JVM 2.4.20 discovery on pinned OpenJDK 25 Ubuntu runtimes. Ubuntu26.04 development uses exact `25.0.4.1+1-1~26.04.4`; Ubuntu24.04 TensorBuzz retains exact `25.0.4+7-1~24.04`. Bootstrap and discovery accept only those two qualified compiler-reported package identities, never arbitrary output. Original and generated `Program.kt` files compile with `-language-version 2.4 -api-version 2.4 -jvm-target 25 -Werror -include-runtime` into runnable `Program.jar` files, then execute through the exact `java25` discovery contract. Every process uses a fresh directory and exact status/stderr/UTF-8 stdout assertions; no test downloads a compiler or dependency.
384
+ Task 023 adds exact Kotlin/JVM 2.4.20 discovery on pinned OpenJDK 25 Ubuntu runtimes. Ubuntu26.04 development uses exact `25.0.4.1+1-1~26.04.4`; Ubuntu24.04 TensorBuzz uses exact `25.0.4.1+1-1~24.04.4`. Bootstrap and discovery accept only those two qualified compiler-reported package identities, never arbitrary output. Original and generated `Program.kt` files compile with `-language-version 2.4 -api-version 2.4 -jvm-target 25 -Werror -include-runtime` into runnable `Program.jar` files, then execute through the exact `java25` discovery contract. Every process uses a fresh directory and exact status/stderr/UTF-8 stdout assertions; no test downloads a compiler or dependency.
384
385
 
385
386
  Task 041's product checks are separate from runtime acceptance. Run the two focused files sequentially:
386
387
 
@@ -424,7 +425,7 @@ No Task 042 plan runs a generated executable, fetches a package, consults a user
424
425
  | `tsc` | `tsc` | `SEMANTIFOLD_TSC` | TypeScript 7.x |
425
426
  | `javac` | `javac` | `SEMANTIFOLD_JAVAC` | JDK 17–29 |
426
427
  | `java` | `java` | `SEMANTIFOLD_JAVA` | JRE 17–29 |
427
- | `java25` | `java` | `SEMANTIFOLD_JAVA` | exact OpenJDK 25.0.4.1+1 development or 25.0.4+7 TensorBuzz runtime for JVM25 artifacts |
428
+ | `java25` | `java` | `SEMANTIFOLD_JAVA` | exact OpenJDK 25.0.4.1+1 development or TensorBuzz runtime for JVM25 artifacts |
428
429
  | `kotlinc` | `kotlinc` | `SEMANTIFOLD_KOTLINC` | exact Kotlin/JVM 2.4.20 on the matching qualified OpenJDK 25 Ubuntu package |
429
430
  | `python` | `python3` | `SEMANTIFOLD_PYTHON` | Python 3.x |
430
431
  | `dotnet` | `dotnet` | `SEMANTIFOLD_DOTNET` | .NET SDK 10.x |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantifold",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Language-neutral semantic code analysis, transformation, generation, and execution toolkit",
5
5
  "type": "module",
6
6
  "main": "build/index.js",
package/src/backends/c.js CHANGED
@@ -16,7 +16,7 @@ import {SourceWriter} from "./writer.js"
16
16
  /** @typedef {import("../semantic/types.js").Block} Block */
17
17
  /** @typedef {import("./ordered-expressions.js").PlannedValue} PlannedValue */
18
18
  /** @typedef {import("./ordered-expressions.js").PlannedStep} PlannedStep */
19
- /** @typedef {{id: string, steps: PlannedStep[], value: PlannedValue}} StatementPlan */
19
+ /** @typedef {{id: string, steps: PlannedStep[], value?: PlannedValue, terminal?: PlannedStep}} StatementPlan */
20
20
 
21
21
  const binaryTokens = new Map([
22
22
  ["IntegerAdd", "semantifold_integer_add"], ["IntegerSubtract", "semantifold_integer_subtract"],
@@ -125,7 +125,7 @@ class CEmitter {
125
125
  module.functions.forEach((declaration, index) => {
126
126
  const parameters = declaration.parameters.map((parameter) => `${emitScalarType("c", parameter.type)} ${parameter.name}`).join(", ")
127
127
 
128
- this.synthetic(`static ${emitScalarType("c", declaration.returnType)} ${declaration.name}(${parameters});\n`, declaration, `/functions/${index}`)
128
+ this.synthetic(`static ${emitScalarType("c", declaration.returnType)} ${declaration.name}(${declaration.parameters.length ? parameters : "void"});\n`, declaration, `/functions/${index}`)
129
129
  })
130
130
  module.functions.forEach((declaration, index) => {
131
131
  const path = `/functions/${index}`
@@ -135,6 +135,7 @@ class CEmitter {
135
135
  this.synthetic(" ", declaration, path)
136
136
  this.mapped(declaration.name, declaration, path, "name")
137
137
  this.synthetic("(", declaration, path)
138
+ if (!declaration.parameters.length) this.synthetic("void", declaration, path)
138
139
  declaration.parameters.forEach((parameter, parameterIndex) => {
139
140
  const parameterPath = `${path}/parameters/${parameterIndex}`
140
141
 
@@ -181,7 +182,25 @@ class CEmitter {
181
182
 
182
183
  this.synthetic(`${indent}/* ${marker} begin ${plan.id} ${signature} */\n`, statement, path)
183
184
  this.steps(plan.steps, indent)
185
+ if (statement.kind == "ReturnStatement" && !statement.expression) {
186
+ this.synthetic(indent, statement, path)
187
+ this.mapped("return", statement, path)
188
+ this.synthetic(";\n", statement, path)
189
+ this.synthetic(`${indent}/* ${marker} end ${plan.id} ${signature} */\n`, statement, path)
190
+ return
191
+ }
192
+ if (statement.kind == "ExpressionStatement") {
193
+ if (!plan.terminal) throw new Error("Missing validated C terminal call.")
194
+ this.synthetic(indent, statement, path)
195
+ this.operation(plan.terminal)
196
+ this.synthetic(";\n", statement, path)
197
+ this.synthetic(`${indent}/* ${marker} end ${plan.id} ${signature} */\n`, statement, path)
198
+ return
199
+ }
184
200
  this.synthetic(indent, statement, path)
201
+ const value = plan.value
202
+
203
+ if (!value) throw new Error("Missing validated C statement value plan.")
185
204
  if (statement.kind == "LocalDeclaration") {
186
205
  if (!statement.mutable) this.synthetic("const ", statement, path)
187
206
  this.mapped(emitScalarType("c", statement.type), statement.type, `${path}/type`, "type")
@@ -196,9 +215,9 @@ class CEmitter {
196
215
  this.mapped("=", statement, path, "operator")
197
216
  this.synthetic(" ", statement, path)
198
217
  } else if (statement.kind == "ReturnStatement") this.mapped("return ", statement, path)
199
- else if (statement.kind == "PrintStatement") this.mapped(`semantifold_print_${plan.value.type}(`, statement, path)
218
+ else if (statement.kind == "PrintStatement") this.mapped(`semantifold_print_${value.type}(`, statement, path)
200
219
  else this.mapped("if (", statement, path)
201
- this.value(plan.value)
220
+ this.value(value)
202
221
  if (statement.kind == "IfStatement") {
203
222
  this.synthetic(") {\n", statement, path)
204
223
  this.block(statement.consequent, `${path}/consequent`, `${indent} `)
@@ -153,6 +153,7 @@ class CppEmitter {
153
153
  const plan = this.plans.get(path)
154
154
 
155
155
  if (!plan) throw new Error("Missing validated C++ statement plan.")
156
+ if (!plan.value) throw new Error("Missing validated C++ statement value plan.")
156
157
  const marker = `semantifold:ordered-expression:cpp:v1`
157
158
  const signature = statementSignature(statement)
158
159
 
@@ -15,7 +15,7 @@ export const kotlinArtifactMetadata = Object.freeze({
15
15
  toolchain: "kotlinc",
16
16
  version: "2.4.20"
17
17
  }),
18
- run: Object.freeze({arguments: Object.freeze(["-jar", "Program.jar"]), toolchain: "java25", version: "25.0.4+7"})
18
+ run: Object.freeze({arguments: Object.freeze(["-jar", "Program.jar"]), toolchain: "java25", version: "25.0.4.1"})
19
19
  })
20
20
 
21
21
  /**
@@ -5,7 +5,8 @@ import {unsupportedCapability} from "../diagnostic.js"
5
5
 
6
6
  /** @typedef {import("../semantic/types.js").Expression} Expression */
7
7
  /** @typedef {import("../semantic/types.js").SemanticTypeName} Scalar */
8
- /** @typedef {{expression: Expression, path: string, type: Scalar, name?: string}} PlannedValue */
8
+ /** @typedef {Scalar | "void"} PlannedType */
9
+ /** @typedef {{expression: Expression, path: string, type: PlannedType, name?: string}} PlannedValue */
9
10
  /** @typedef {{result: PlannedValue, operands: PlannedValue[], rightSteps?: PlannedStep[]}} PlannedStep */
10
11
  /** @typedef {Array<string | number | boolean | Signature>} Signature */
11
12
 
@@ -20,7 +21,7 @@ export class OrderedExpressionPlanner {
20
21
  this.language = language
21
22
  this.functions = new Map(module.functions.map((declaration) => [
22
23
  declaration.name,
23
- scalarName(declaration.returnType, language, declaration.location)
24
+ returnName(declaration.returnType, language, declaration.location)
24
25
  ]))
25
26
  this.nextTemporary = 1
26
27
  }
@@ -29,7 +30,7 @@ export class OrderedExpressionPlanner {
29
30
  * Resolves an expression type from the validated lexical environment.
30
31
  * @param {Expression} expression - Semantic expression.
31
32
  * @param {Map<string, Scalar>} bindings - Lexical scalar types.
32
- * @returns {Scalar} Exact semantic result type.
33
+ * @returns {PlannedType} Exact semantic result type.
33
34
  */
34
35
  type(expression, bindings) {
35
36
  if (expression.kind == "IntegerLiteral") return "integer"
@@ -76,12 +77,31 @@ export class OrderedExpressionPlanner {
76
77
  }
77
78
  operands.push(this.plan(expression.right, `${path}/right`, bindings, steps))
78
79
  }
80
+ if (result.type == "void") throw new Error("Void call reached native value planning.")
79
81
  const planned = {...result, name: this.allocate(expression)}
80
82
 
81
83
  steps.push({operands, result: planned})
82
84
  return planned
83
85
  }
84
86
 
87
+ /**
88
+ * Plans the arguments of one terminal void call without inventing a void temporary.
89
+ * @param {Expression} expression - Terminal expression occurrence.
90
+ * @param {string} path - Occurrence path.
91
+ * @param {Map<string, Scalar>} bindings - Visible scalar bindings.
92
+ * @param {PlannedStep[]} steps - Ordered enclosing prelude.
93
+ * @returns {PlannedStep} Terminal direct-call step.
94
+ */
95
+ terminal(expression, path, bindings, steps) {
96
+ if (expression.kind != "CallExpression" || this.type(expression, bindings) != "void") {
97
+ throw new Error("Non-void expression reached native terminal-call planning.")
98
+ }
99
+ const result = {expression, path, type: /** @type {const} */ ("void")}
100
+ const operands = expression.arguments.map((argument, index) => this.plan(argument, `${path}/arguments/${index}`, bindings, steps))
101
+
102
+ return {operands, result}
103
+ }
104
+
85
105
  /**
86
106
  * Allocates one bounded deterministic occurrence name.
87
107
  * @param {Expression} expression - Related expression.
@@ -108,9 +128,9 @@ export function statementSignature(statement) {
108
128
  statement.kind == "AssignmentStatement" || statement.kind == "ExpressionStatement" || statement.kind == "PrintStatement" ? statement.expression :
109
129
  statement.kind == "ReturnStatement" ? statement.expression : undefined
110
130
 
111
- if (!expression) throw new TypeError("Bare return reached native statement signature planning.")
131
+ if (!expression && statement.kind != "ReturnStatement") throw new TypeError("Bare return reached native statement signature planning.")
112
132
 
113
- return createHash("sha256").update(JSON.stringify([consumer, expressionSignature(expression)])).digest("hex")
133
+ return createHash("sha256").update(JSON.stringify([consumer, expression ? expressionSignature(expression) : null])).digest("hex")
114
134
  }
115
135
 
116
136
  /**
@@ -128,7 +148,7 @@ function expressionSignature(expression) {
128
148
  return [expression.kind, expression.operation, expression.type, expressionSignature(expression.left), expressionSignature(expression.right)]
129
149
  }
130
150
 
131
- /** @typedef {{id: string, steps: PlannedStep[], value: PlannedValue}} StatementPlan */
151
+ /** @typedef {{id: string, steps: PlannedStep[], value?: PlannedValue, terminal?: PlannedStep}} StatementPlan */
132
152
  /**
133
153
  * Plans every occurrence before constructing a writer or exposing either artifact.
134
154
  * @param {import("../semantic/types.js").SemanticModule} module - Validated module.
@@ -159,12 +179,13 @@ export function planNativeModule(module, language = "c") {
159
179
  statement.kind == "ReturnStatement" ? statement.expression : undefined
160
180
  /** @type {PlannedStep[]} */
161
181
  const steps = []
162
-
163
- if (!expression) throw new TypeError("Bare return reached native expression planning.")
164
- const value = planner.plan(expression, `${statementPath}/${field}`, bindings, steps)
182
+ /** @type {StatementPlan} */
183
+ const plan = {id: String(plans.size + 1).padStart(6, "0"), steps}
165
184
 
166
185
  if (plans.size >= 999999) unsupportedCapability(language, "ordered statement limit", statement.location)
167
- plans.set(statementPath, {id: String(plans.size + 1).padStart(6, "0"), steps, value})
186
+ if (statement.kind == "ExpressionStatement") plan.terminal = planner.terminal(statement.expression, `${statementPath}/${field}`, bindings, steps)
187
+ else if (expression) plan.value = planner.plan(expression, `${statementPath}/${field}`, bindings, steps)
188
+ plans.set(statementPath, plan)
168
189
  if (statement.kind == "LocalDeclaration") bindings.set(statement.name, scalarName(statement.type, language, statement.location))
169
190
  if (statement.kind == "IfStatement") {
170
191
  visit(statement.consequent, `${statementPath}/consequent`, bindings)
@@ -193,3 +214,18 @@ function scalarName(type, language, location) {
193
214
 
194
215
  return type.name
195
216
  }
217
+
218
+ /**
219
+ * Narrows a validated function return, admitting the non-value `void` result.
220
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType} type - Semantic type.
221
+ * @param {"c" | "cpp"} language - Diagnostic language.
222
+ * @param {import("../semantic/types.js").SourceLocation} location - Owning location.
223
+ * @returns {PlannedType} Scalar or void name.
224
+ */
225
+ function returnName(type, language, location) {
226
+ if (type.kind != "TypeReference") {
227
+ return unsupportedCapability(language, "collection type reached native scalar planning", location)
228
+ }
229
+
230
+ return type.name
231
+ }
@@ -27,7 +27,7 @@ const binaryOperationSyntax = Object.freeze({
27
27
  StringEqual: Object.freeze({default: "==", strict: "==="}),
28
28
  StringNotEqual: Object.freeze({default: "!=", strict: "!=="})
29
29
  })
30
- const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "dart", "zig"])
30
+ const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig"])
31
31
  const task006Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
32
32
  const task007Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
33
33
  const task008Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
@@ -37,6 +37,7 @@ const task012Languages = new Set(["php", "ruby", "javascript", "typescript", "ja
37
37
  const task014Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
38
38
  const task032Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
39
39
  const task033Languages = new Set(["php", "ruby", "javascript", "typescript", "java"])
40
+ const task048ConditionalLanguages = new Set(["php", "ruby", "javascript", "typescript", "java"])
40
41
  const javaReferenceMethodHooks = new Set(["clone", "equals", "finalize", "getClass", "hashCode", "notify", "notifyAll", "toString", "wait"])
41
42
  const javascriptReferenceMethodHooks = new Set([
42
43
  "__defineGetter__", "__defineSetter__", "__lookupGetter__", "__lookupSetter__", "__proto__", "constructor",
@@ -1129,6 +1130,11 @@ function expressionContainsKind(expression, kind) {
1129
1130
  if (expression.kind == "BinaryExpression") {
1130
1131
  return expressionContainsKind(expression.left, kind) || expressionContainsKind(expression.right, kind)
1131
1132
  }
1133
+ if (expression.kind == "ConditionalExpression") {
1134
+ return expressionContainsKind(expression.condition, kind) ||
1135
+ expressionContainsKind(expression.consequent, kind) ||
1136
+ expressionContainsKind(expression.alternate, kind)
1137
+ }
1132
1138
  if (expression.kind == "CallExpression") return expression.arguments.some((argument) => expressionContainsKind(argument, kind))
1133
1139
  if (expression.kind == "MethodCallExpression") {
1134
1140
  return expressionContainsKind(expression.receiver, kind) ||
@@ -1936,6 +1942,19 @@ function validateExpression(expression, language, ownerLocation, allowJavaNegate
1936
1942
  return
1937
1943
  }
1938
1944
 
1945
+ if (candidate.kind == "ConditionalExpression") {
1946
+ const fields = Object.keys(candidate).filter((key) => key != "sourceProvenance").sort().join(",")
1947
+
1948
+ if (fields != "alternate,condition,consequent,kind,location,type") {
1949
+ unsupportedCapability(language, "malformed ConditionalExpression", location)
1950
+ }
1951
+ if (!task048ConditionalLanguages.has(language)) unsupportedCapability(language, "Task 048 conditional expression", location)
1952
+ validateExpression(candidate.condition, language, location, false, expressionPath, externalDeclarationIds)
1953
+ validateExpression(candidate.consequent, language, location, false, expressionPath, externalDeclarationIds)
1954
+ validateExpression(candidate.alternate, language, location, false, expressionPath, externalDeclarationIds)
1955
+ return
1956
+ }
1957
+
1939
1958
  unsupportedCapability(language, String(Reflect.get(expression, "kind")), location)
1940
1959
  }
1941
1960
 
@@ -2364,6 +2383,17 @@ export function emitExpression(writer, expression, path, language, emitIdentifie
2364
2383
  return
2365
2384
  }
2366
2385
 
2386
+ if (expression.kind == "ConditionalExpression") {
2387
+ writer.mapped("(", {mappingKind: "anchor", node: expression, path})
2388
+ emitExpression(writer, expression.condition, `${path}/condition`, language, emitIdentifier)
2389
+ writer.mapped(" ? ", {mappingKind: "exact", node: expression, path, role: "operator"})
2390
+ emitExpression(writer, expression.consequent, `${path}/consequent`, language, emitIdentifier)
2391
+ writer.mapped(" : ", {mappingKind: "exact", node: expression, path, role: "colon"})
2392
+ emitExpression(writer, expression.alternate, `${path}/alternate`, language, emitIdentifier)
2393
+ writer.mapped(")", {mappingKind: "anchor", node: expression, path})
2394
+ return
2395
+ }
2396
+
2367
2397
  if (expression.kind == "UnaryExpression") {
2368
2398
  if (language == "dart" && expression.operation == "IntegerNegate") {
2369
2399
  emitDartIntegerHelper(writer, expression, path, "_semantifoldIntegerNegate", [
@@ -12,7 +12,7 @@ import {withAdaptedOperation} from "../semantic/operators.js"
12
12
  import {withParserRanges} from "../semantic/provenance.js"
13
13
  import {hasOnlyUnicodeScalars} from "../semantic/scalars.js"
14
14
  import {validateParsedModule} from "../semantic/validate.js"
15
- import {sourceScalarType} from "./scalars.js"
15
+ import {sourceScalarType, requireSourceReturnType} from "./scalars.js"
16
16
 
17
17
  /** @typedef {import("./c-parser.js").CstNode} CstNode */
18
18
  /** @typedef {import("../semantic/types.js").Expression} Expression */
@@ -29,6 +29,15 @@ const helpers = new Map([
29
29
  ["semantifold_string_equal", "StringEqual"], ["semantifold_string_not_equal", "StringNotEqual"]
30
30
  ])
31
31
 
32
+ /**
33
+ * Reads the exact scalar-or-void type name of a C declaration annotation.
34
+ * @param {import("../semantic/types.js").SemanticFunctionReturnType | import("../semantic/types.js").SemanticValueType} type - Converted C type.
35
+ * @returns {string} Type name.
36
+ */
37
+ function returnTypeName(type) {
38
+ return type.kind == "TypeReference" ? type.name : type.kind
39
+ }
40
+
32
41
  /** Strict field-and-token consumer for the private frozen C CST. */
33
42
  export class CReader {
34
43
  /**
@@ -48,7 +57,11 @@ export class CReader {
48
57
  /** @type {Map<Statement, {node: CstNode, type: string}>} */
49
58
  this.printTypes = new Map()
50
59
  /** @type {Set<string>} */
51
- this.declaredFunctions = new Set()
60
+ this.declaredFunctionNames = new Set()
61
+ /** @type {{name: string, node: CstNode, parameterTypes: string[], returnType: string}[]} */
62
+ this.prototypes = []
63
+ /** @type {boolean} */
64
+ this.voidFunction = false
52
65
  }
53
66
 
54
67
  /**
@@ -303,10 +316,9 @@ export class CReader {
303
316
  if (call.arguments.length != 2) this.fail(node, "scalar helper arity")
304
317
  return this.binary(node, intent, this.expression(call.arguments[0], true), this.expression(call.arguments[1], true), call.name)
305
318
  }
306
- if (call.arguments.length != 2) this.fail(node, "semantic call arity")
307
319
  const callee = this.identifier(call.name)
308
320
 
309
- if (!this.generated && !this.declaredFunctions.has(callee)) this.fail(call.name, "implicit function declaration")
321
+ if (!this.generated && !this.declaredFunctionNames.has(callee)) this.fail(call.name, "implicit function declaration")
310
322
  return withParserRanges({kind: "CallExpression", location, callee,
311
323
  arguments: call.arguments.map((argument) => this.expression(argument, true))}, {callee: this.location(call.name)})
312
324
  }
@@ -369,6 +381,11 @@ export class CReader {
369
381
  if (node.type == "return_statement") {
370
382
  const parts = this.parts(node)
371
383
 
384
+ if (parts.length == 2) {
385
+ this.shape(node, ["return", ";"])
386
+ if (!this.voidFunction) this.fail(node, "bare return requires a void function")
387
+ return {kind: "ReturnStatement", location}
388
+ }
372
389
  this.shape(node, ["return", parts[1], ";"])
373
390
  return {kind: "ReturnStatement", location, expression: this.expression(parts[1])}
374
391
  }
@@ -403,13 +420,18 @@ export class CReader {
403
420
  }
404
421
  const call = this.call(expression)
405
422
 
406
- if (!/^semantifold_print_(?:integer|boolean|string)$/u.test(this.text(call.name)) || call.arguments.length != 1) this.fail(node, "print scaffold")
407
- const value = this.expression(call.arguments[0])
423
+ if (/^semantifold_print_(?:integer|boolean|string)$/u.test(this.text(call.name))) {
424
+ if (call.arguments.length != 1) this.fail(node, "print scaffold")
425
+ const value = this.expression(call.arguments[0])
408
426
 
409
- const statement = {kind: /** @type {const} */ ("PrintStatement"), location, expression: value}
427
+ const statement = {kind: /** @type {const} */ ("PrintStatement"), location, expression: value}
410
428
 
411
- this.printTypes.set(statement, {node: call.name, type: this.text(call.name).slice("semantifold_print_".length)})
412
- return statement
429
+ this.printTypes.set(statement, {node: call.name, type: this.text(call.name).slice("semantifold_print_".length)})
430
+ return statement
431
+ }
432
+ return withParserRanges({kind: /** @type {const} */ ("ExpressionStatement"), location,
433
+ expression: /** @type {import("../semantic/types.js").CallExpression} */ (this.expression(expression))},
434
+ {operator: this.location(this.parts(node)[1])})
413
435
  }
414
436
  return this.fail(node)
415
437
  }
@@ -445,20 +467,98 @@ export class CReader {
445
467
  const parameters = this.field(declarator, "parameters")
446
468
 
447
469
  this.shape(declarator, [name, parameters])
448
- const parts = this.parts(parameters)
470
+ const converted = this.parameterList(parameters)
471
+ const returnType = requireSourceReturnType("c", this.text(type), "function return", this.location(node), this.location(type))
472
+ const outerVoid = this.voidFunction
473
+
474
+ this.voidFunction = returnType.kind == "TypeReference" && returnType.name == "void"
475
+ const convertedBody = this.block(body)
476
+
477
+ this.voidFunction = outerVoid
478
+ return withParserRanges({kind: "FunctionDeclaration", location: this.location(node), name: this.identifier(name), parameters: converted,
479
+ returnType, body: convertedBody}, {name: this.location(name)})
480
+ }
449
481
 
450
- this.shape(parameters, ["(", "parameter_declaration", ",", "parameter_declaration", ")"])
451
- const converted = [parts[1], parts[3]].map((parameter) => {
482
+ /**
483
+ * Validates one exact required positional parameter list and converts it in source order.
484
+ * @param {CstNode} parameters - Parameter list node.
485
+ * @returns {import("../semantic/types.js").Parameter[]} Converted parameters.
486
+ */
487
+ parameterList(parameters) {
488
+ const parts = this.parts(parameters)
489
+ const declarations = parts.slice(1, -1).filter((_part, index) => index % 2 == 0)
490
+ const voidOnly = declarations.length == 1 && parts.length == 3 && declarations[0].type == "parameter_declaration" &&
491
+ !declarations[0].children.some(({field}) => field == "declarator")
492
+
493
+ if (voidOnly) {
494
+ this.shape(parameters, ["(", "parameter_declaration", ")"])
495
+ if (this.text(declarations[0]) != "void") this.fail(declarations[0], "empty parameter list requires explicit void")
496
+ return []
497
+ }
498
+ if (declarations.length == 0) this.fail(parameters, "empty parameter list requires explicit void")
499
+ this.shape(parameters, ["(", ...declarations.flatMap((parameter, index) => index == 0 ? [parameter] : [",", parameter]), ")"])
500
+ return declarations.map((parameter) => {
452
501
  const typeNode = this.field(parameter, "type")
453
502
  const nameNode = this.field(parameter, "declarator")
454
503
 
455
504
  this.shape(parameter, [typeNode, nameNode])
456
505
  return withParserRanges({kind: /** @type {const} */ ("Parameter"), location: this.location(parameter), name: this.identifier(nameNode), type: this.type(typeNode)}, {name: this.location(nameNode)})
457
506
  })
507
+ }
458
508
 
459
- this.declaredFunctions.add(this.identifier(name))
460
- return withParserRanges({kind: "FunctionDeclaration", location: this.location(node), name: this.identifier(name), parameters: converted,
461
- returnType: this.type(type), body: this.block(body)}, {name: this.location(name)})
509
+ /**
510
+ * Recognizes a file-local function prototype declaration without descending further.
511
+ * @param {CstNode} node - Candidate translation-unit member.
512
+ * @returns {boolean} Whether this is a function prototype.
513
+ */
514
+ isPrototype(node) {
515
+ if (node.type != "declaration") return false
516
+ const declarator = node.children.find(({field}) => field == "declarator")?.node
517
+
518
+ return declarator?.type == "function_declarator"
519
+ }
520
+
521
+ /**
522
+ * Records one exact static prototype signature before any body conversion.
523
+ * @param {CstNode} node - Prototype declaration.
524
+ * @returns {void}
525
+ */
526
+ recordPrototype(node) {
527
+ const type = this.field(node, "type")
528
+ const declarator = this.field(node, "declarator")
529
+
530
+ this.shape(node, ["storage_class_specifier", type, declarator, ";"])
531
+ if (this.text(this.parts(node)[0]) != "static" || declarator.type != "function_declarator") this.fail(node, "file-local prototype required")
532
+ const name = this.field(declarator, "declarator")
533
+ const parameters = this.field(declarator, "parameters")
534
+
535
+ this.shape(declarator, [name, parameters])
536
+ const converted = this.parameterList(parameters)
537
+ const nameText = this.identifier(name)
538
+
539
+ this.declaredFunctionNames.add(nameText)
540
+ this.prototypes.push({name: nameText, node,
541
+ parameterTypes: converted.map((parameter) => /** @type {import("../semantic/types.js").TypeReference} */ (parameter.type).name),
542
+ returnType: /** @type {import("../semantic/types.js").TypeReference} */ (requireSourceReturnType("c", this.text(type), "prototype return", this.location(node), this.location(type))).name})
543
+ }
544
+
545
+ /**
546
+ * Confirms every recorded prototype agrees with its unique function definition.
547
+ * @param {import("../semantic/types.js").FunctionDeclaration[]} functions - Converted definitions.
548
+ * @returns {void}
549
+ */
550
+ validatePrototypes(functions) {
551
+ const definitions = new Map(functions.map((declaration) => [declaration.name, declaration]))
552
+
553
+ for (const prototype of this.prototypes) {
554
+ const definition = definitions.get(prototype.name)
555
+
556
+ if (!definition) this.fail(prototype.node, "prototype without definition")
557
+ if (returnTypeName(definition.returnType) != prototype.returnType || definition.parameters.length != prototype.parameterTypes.length ||
558
+ definition.parameters.some((parameter, index) => returnTypeName(parameter.type) != prototype.parameterTypes[index])) {
559
+ this.fail(prototype.node, "prototype conflicts with function definition")
560
+ }
561
+ }
462
562
  }
463
563
 
464
564
  /**
@@ -511,17 +611,33 @@ export class CReader {
511
611
  if (!this.generated) this.rejectScaffoldComments(root)
512
612
  if (root.type != "translation_unit") this.fail(root, "translation unit required")
513
613
  const parts = this.parts(root)
514
- const definitions = parts.slice(1).filter((node) => !this.generated || node.type != "declaration")
614
+ const members = parts.slice(1).filter((node) => !this.generated || node.type == "function_definition" || this.isPrototype(node))
515
615
 
516
- if (parts.length < 3 || parts[0].type != "preproc_include" || definitions.some((node) => node.type != "function_definition")) this.fail(root, "translation unit members")
616
+ if (parts.length < 3 || parts[0].type != "preproc_include" ||
617
+ members.some((node) => node.type != "function_definition" && !this.isPrototype(node))) this.fail(root, "translation unit members")
517
618
  const include = parts[0]
518
619
  const header = this.field(include, "path")
519
620
 
520
621
  this.shape(include, ["#include", header])
521
622
  if (header.type != "string_literal" || this.text(header) != '"semantifold_runtime.h"') this.fail(include, "support header include")
623
+ const definitions = members.filter((node) => node.type == "function_definition")
624
+
522
625
  if (definitions.length < 2) this.fail(root, "semantic function and main required")
523
- const module = {kind: /** @type {const} */ ("Module"), location: this.location(root), functions: definitions.slice(0, -1).map((node) => this.function(node)),
524
- entryPoint: this.main(definitions[definitions.length - 1])}
626
+ const mainNode = definitions[definitions.length - 1]
627
+ const functions = []
628
+
629
+ for (const node of members) {
630
+ if (node === mainNode) continue
631
+ if (node.type != "function_definition") {
632
+ this.recordPrototype(node)
633
+ continue
634
+ }
635
+ this.declaredFunctionNames.add(this.identifier(this.field(this.field(node, "declarator"), "declarator")))
636
+ functions.push(this.function(node))
637
+ }
638
+ this.validatePrototypes(functions)
639
+ const module = {kind: /** @type {const} */ ("Module"), location: this.location(root), functions,
640
+ entryPoint: this.main(mainNode)}
525
641
 
526
642
  if (this.generated) validateNativeGraph(module)
527
643
  const validated = validateParsedModule(module, "c")
@@ -329,6 +329,29 @@ function convertExpression(node, filename, source, context, expectedType) {
329
329
  return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (semantic))
330
330
  }
331
331
 
332
+ if (node.name == "TernaryExpression") {
333
+ const children = directChildren(node)
334
+ const question = children.find((child) => child.name == "?" || (child.name == "LogicOp" && nodeText(child, source) == "?"))
335
+ const colon = children.find((child) => child.name == ":" || (child.name == "LogicOp" && nodeText(child, source) == ":"))
336
+ const named = children.filter((child) => child !== question && child !== colon)
337
+
338
+ if (!question || !colon || named.length != 3) return unsupportedSyntax("java", "ternary expression", location)
339
+
340
+ return /** @type {import("../semantic/types.js").Expression} */ (/** @type {unknown} */ (withParserRanges({
341
+ alternate: convertExpression(named[2], filename, source, context),
342
+ condition: convertExpression(named[0], filename, source, context),
343
+ consequent: convertExpression(named[1], filename, source, context),
344
+ kind: /** @type {const} */ ("ConditionalExpression"),
345
+ location
346
+ }, {
347
+ alternate: nodeLocation(named[2], filename, source),
348
+ colon: nodeLocation(colon, filename, source),
349
+ condition: nodeLocation(named[0], filename, source),
350
+ consequent: nodeLocation(named[1], filename, source),
351
+ operator: nodeLocation(question, filename, source)
352
+ })))
353
+ }
354
+
332
355
  if (node.name == "MethodInvocation") {
333
356
  if (isEqualsInvocation(node, source) && !isZeroArgumentRecordMemberInvocation(node, filename, source, context)) {
334
357
  const methodName = requiredChild(node, "MethodName", filename, source)
@@ -626,6 +649,7 @@ function isSupportedExpressionNode(node) {
626
649
  "StringLiteral",
627
650
  "UnaryExpression",
628
651
  "BinaryExpression",
652
+ "TernaryExpression",
629
653
  "MethodInvocation",
630
654
  "ObjectCreationExpression",
631
655
  "FieldAccess",