semantifold 0.11.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.
package/docs/c.md CHANGED
@@ -28,7 +28,7 @@ Canonical `main` calls `semantifold_cleanup()` once after semantic entry stateme
28
28
 
29
29
  The unchanged root-bundled private legacy boundary supplies recursively frozen C CST data from exact `tree-sitter@0.21.1` and `tree-sitter-c@0.23.2`. `src/frontends/c-parser.js` describes that private plain-data boundary; native handles and parser-specific objects never enter semantic values. Every child, field, anonymous token, extra, error, missing node and recovery flag is traversed before meaning is returned. Coordinates and token origins refer to the original UTF-16 source, including astral characters and CRLF. There is no parser-source scan, compiler-based interpretation, preprocessing fallback or source-text recovery.
30
30
 
31
- Ordinary source contains the exact support-header include, at least one `static` semantic function with two named explicitly typed parameters, and final `int main(void)`. Functions return exact scalars. Bodies admit initialized single scalar locals, plain assignment, prints through the matching scalar helper, explicit returns and braced conditionals, including `else if`. `const` locals are immutable; other locals are mutable, and parameters are immutable. Main ends with the exact cleanup call and `return 0`. Raw calls must refer to a preceding or current function definition. Caller-authored forward calls requiring implicit C declarations are rejected; canonical generated prototypes support forward calls and recursion.
31
+ Ordinary source contains the exact support-header include, at least one `static` semantic function plus final `int main(void)`. Task 005 semantic functions accept any number of explicitly typed named parameters, including zero — spelled `void` for an empty list — and return exact scalars or `void`. A file-scope `static` prototype may precede a function definition for forward and mutual recursion; every prototype must agree exactly with its unique definition, and a prototype without a definition is rejected. Bodies admit initialized single scalar locals, plain assignment, prints through the matching scalar helper, explicit returns and braced conditionals, including `else if`. A non-void function returns a value on every reachable path; a void function may fall through or use a bare `return;`, and a bare return in a non-void function is rejected. A void call is legal only as a standalone statement. `const` locals are immutable; other locals are mutable, and parameters are immutable. Main ends with the exact cleanup call and `return 0`. Raw calls must refer to a preceding function definition or prototype; caller-authored forward calls requiring implicit C declarations are rejected, and canonical generated prototypes support forward calls and recursion.
32
32
 
33
33
  Only direct semantic calls and the exact scalar/literal/print helpers are recognized. Caller-authored calls nested in an operator operand or another call's argument are rejected because ordinary C does not establish the required semantic evaluation order. Trigraph preprocessing in parser-backed comments or string content is rejected. No user preprocessing, typedef substitution, global data, complex/multiple declarator, qualifier beyond local `const`, array, struct, union, enum, pointer, function pointer, cast, address/dereference, member access, ownership call, `sizeof`, comma/ternary, loop, switch, goto, update, compound operator, conversion or arbitrary external call is accepted.
34
34
 
@@ -84,7 +84,7 @@ The shared negative corpus covers parser recovery, missing types, truthiness, mi
84
84
 
85
85
  ## Task 005 general functions and calls
86
86
 
87
- PHP, Ruby, JavaScript with JSDoc, TypeScript, Java, Dart, and Zig accept any number of required positional parameters, including zero. The module resolver collects every unique function signature before validating bodies, assigns declarations deterministic `function:N` identities, and binds each direct call to that identity plus its ordered parameter and return types. This permits forward calls, nested calls, direct recursion, and unambiguous mutual recursion without overload or dynamic dispatch. Arity and every positional scalar type must match exactly.
87
+ PHP, Ruby, JavaScript with JSDoc, TypeScript, Java, C, Dart, and Zig accept any number of required positional parameters, including zero. The module resolver collects every unique function signature before validating bodies, assigns declarations deterministic `function:N` identities, and binds each direct call to that identity plus its ordered parameter and return types. This permits forward calls, nested calls, direct recursion, and unambiguous mutual recursion without overload or dynamic dispatch. Arity and every positional scalar type must match exactly.
88
88
 
89
89
  Function returns are `integer`, `boolean`, `string`, or `void`. A non-void function has an expression on every reachable return and returns on every reachable path. A void function may fall through or use a bare return, and its call is legal only as an `ExpressionStatement`. Void is never a value: it cannot type a parameter or local, initialize or assign a binding, become an argument or operand, be printed, or be returned as a value. Generation revalidates declaration identities and complete resolved signatures in externally supplied IR. Function names, parameters, return annotations, callees, arguments, and resolution references retain deterministic parser-backed provenance and mappings.
90
90
 
@@ -94,10 +94,11 @@ The source profiles remain deliberately exact:
94
94
  - JavaScript accepts only declarations spelled `function name(...)` with a JSDoc annotation for every identifier parameter and an explicit scalar or `void` return annotation. TypeScript requires the corresponding explicit keyword annotations. Both reject optional/default/rest/destructured parameters, overloads, async/generators, arrows/function expressions, methods, callbacks and higher-order callees, optional/receiver/spread calls, and general TypeScript assertions. Task 012 adds only its exact generic declaration/application profile; Task 006 has one type-only exception for the exact independently validated `Map#get(...)!` round-trip form.
95
95
  - PHP accepts simple typed by-value required parameters, an explicit scalar or `void` return, and unqualified direct calls. Defaults, variadics, named/unpacked/by-reference arguments, by-reference parameters or returns, methods, dynamic/qualified calls, implicit nullability, and missing types are rejected; Task 007 adds only its exact explicit nullable profile.
96
96
  - Java accepts the established private-static semantic method profile with explicit supported parameter and scalar-or-void result types inside `public final class Main`, plus receiver-free direct calls and the exact entry method. Generated zero-parameter signatures named `getClass`, `hashCode`, `clone`, `toString`, `notify`, `notifyAll`, `wait`, or `finalize` conflict with inherited `java.lang.Object` instance methods and are rejected before emission; nonconflicting scalar-parameter overloads of those names remain available. Overload sets, instance/receiver/qualified/static-import ambiguity, varargs, bounded/defaulted/wildcard/raw generics, constructors, throws clauses, and non-static semantic methods are rejected.
97
+ - C accepts file-scope `static` semantic functions with any number of explicitly typed named parameters — spelled `void` for an empty list — exact scalar or `void` returns, and unqualified direct calls. A `static` prototype may precede a definition for forward or mutual recursion and must agree exactly with its unique definition; a prototype without a definition, a conflicting prototype, an implicit declaration, a bare return in a non-void function, or `void` used as a parameter, local, argument, operand or printed value is rejected. Generated zero-parameter signatures spell `(void)` and generated prototypes make forward calls and recursion compilable under `-Wstrict-prototypes`/`-Wmissing-prototypes`. See [C source and target](c.md).
97
98
  - Dart accepts synchronous top-level functions with exact explicit `int`, `bool`, `String`, or `void` returns, explicitly typed required positional parameters, initialized typed locals, direct calls, and exact `void main()`/`print`. Inference, `var`, inferred `final`, optional/named/default arguments, methods/classes, generics, imports, dynamic/nullability, collections, exceptions, async, and isolates are rejected. Generated Dart alone may contain formatter-added trailing commas and the complete canonical private runtime prefix; neither grants those forms to ordinary source. See [Dart source and target](dart.md).
98
99
  - Zig accepts top-level functions with exact explicit `i64`, `bool`, `[]const u8`, or `void` returns, explicitly typed required positional parameters, initialized typed `const`/`var` locals, direct calls, and the exact generated support/`pub fn main()` shell. Inference, optional/default/rest parameters, methods, containers, user slices/pointers/allocators, imports beyond the canonical standard-library support, error unions, `comptime`, generics, reflection, FFI, async, and concurrency are rejected. See [Zig source and target](zig.md).
99
100
 
100
- Python, C#, C, C++, Rust, Swift, Kotlin/JVM, Go, and browser Wasm remain on their Task 025 Tasks 001–004 profile. Their registry feature flag is false, their existing exact-two/scalar-return behavior is unchanged, and a Task-005-only arity, void return, or void call statement is a located `UNSUPPORTED_CAPABILITY` before source, bytes, or a partial artifact set is exposed. This boundary does not broaden the future Task 013 aggregate acceptance.
101
+ Python, C#, C++, Rust, Swift, Kotlin/JVM, Go, and browser Wasm remain on their Task 025 Tasks 001–004 profile. Their registry feature flag is false, their existing exact-two/scalar-return behavior is unchanged, and a Task-005-only arity, void return, or void call statement is a located `UNSUPPORTED_CAPABILITY` before source, bytes, or a partial artifact set is exposed. This boundary does not broaden the future Task 013 aggregate acceptance.
101
102
 
102
103
  ## Task 006 immutable lists and maps
103
104
 
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "semantifold",
3
- "version": "0.11.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
 
@@ -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"])
@@ -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")
@@ -571,6 +571,21 @@ const records = [
571
571
  artifactMultiplicity: "multiple",
572
572
  check: {factory: createCCheckPlan, stages: ["compile", "link"], toolchains: ["clang"]},
573
573
  defaultFilename: "program.c",
574
+ features: {
575
+ closedRecords: false,
576
+ conditionControlledLoops: false,
577
+ conditionalExpressions: false,
578
+ stringInterpolation: false,
579
+ effectfulCapabilitiesAndResources: false,
580
+ generalFunctionsAndCalls: true,
581
+ immutableCollections: false,
582
+ optionalValues: false,
583
+ orderedListIteration: false,
584
+ orderedMapIteration: false,
585
+ referenceClasses: false,
586
+ typedErrors: false,
587
+ typeParametersAndGenerics: false
588
+ },
574
589
  frontend: parseC,
575
590
  id: "c",
576
591
  mediaType: "text/x-c",
@@ -8,7 +8,7 @@ import {parserRangeFor} from "./provenance.js"
8
8
  import {validateEffectGraph} from "./effects.js"
9
9
  import {validateResourceLifetimes} from "./lifetime.js"
10
10
 
11
- const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "dart", "zig"])
11
+ const task005Languages = new Set(["php", "ruby", "javascript", "typescript", "java", "c", "dart", "zig"])
12
12
 
13
13
  /** @typedef {Map<string, import("./types.js").RecordDeclaration>} RecordRegistry */
14
14
  /** @typedef {Map<string, import("./types.js").ClassDeclaration>} ClassRegistry */