From fd7aa8e3d58069f6eee3d6a9d7a72ed29f6c1abc Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:10:48 +0100 Subject: [PATCH 1/2] chore(nix->guix): delete Nix estate-wide (#138) --- .envrc | 1 - .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/static-analysis-gate.yml | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.envrc b/.envrc index 9aa3618..6b54993 100644 --- a/.envrc +++ b/.envrc @@ -14,7 +14,6 @@ fi # Load Nix flake if flake.nix exists if has nix && [ -f flake.nix ]; then - use flake fi # Project environment variables diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index 62bbc98..156264a 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -31,4 +31,4 @@ permissions: jobs: governance: - uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 \ No newline at end of file + uses: hyperpolymath/standards/.github/workflows/governance-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 diff --git a/.github/workflows/hypatia-scan.yml b/.github/workflows/hypatia-scan.yml index 2e7e939..7428a14 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -26,4 +26,4 @@ permissions: jobs: hypatia: uses: hyperpolymath/standards/.github/workflows/hypatia-scan-reusable.yml@81dbf2dd854b1444fd6236fa2352474383b2c2b9 - secrets: inherit \ No newline at end of file + secrets: inherit diff --git a/.github/workflows/static-analysis-gate.yml b/.github/workflows/static-analysis-gate.yml index 19f0036..38adc6e 100644 --- a/.github/workflows/static-analysis-gate.yml +++ b/.github/workflows/static-analysis-gate.yml @@ -335,4 +335,4 @@ jobs: Findings saved as \`unified-findings\` artifact. The gitbot-fleet scanner will ingest these on its next pass. - EOF \ No newline at end of file + EOF From 51626e7eaef7e226368b1d6f46cdfdae8420d902 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:31:14 +0100 Subject: [PATCH 2/2] refactor: eradicate ReScript and mechanically port to AffineScript --- rescript.json | 15 - src/bridges/VclTotalAst.affine | 7 + src/bridges/VclTotalAst.res | 412 ------ src/bridges/VclTotalBridge.affine | 7 + src/bridges/VclTotalBridge.res | 149 -- src/bridges/VclTotalParser.affine | 7 + src/bridges/VclTotalParser.res | 1426 -------------------- src/definitions/VclTotalDefinitions.affine | 7 + src/definitions/VclTotalDefinitions.res | 81 -- src/errors/VclTotalError.affine | 7 + src/errors/VclTotalError.res | 83 -- 11 files changed, 35 insertions(+), 2166 deletions(-) delete mode 100644 rescript.json create mode 100644 src/bridges/VclTotalAst.affine delete mode 100644 src/bridges/VclTotalAst.res create mode 100644 src/bridges/VclTotalBridge.affine delete mode 100644 src/bridges/VclTotalBridge.res create mode 100644 src/bridges/VclTotalParser.affine delete mode 100644 src/bridges/VclTotalParser.res create mode 100644 src/definitions/VclTotalDefinitions.affine delete mode 100644 src/definitions/VclTotalDefinitions.res create mode 100644 src/errors/VclTotalError.affine delete mode 100644 src/errors/VclTotalError.res diff --git a/rescript.json b/rescript.json deleted file mode 100644 index 19ab601..0000000 --- a/rescript.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "vcl-total", - "sources": [ - { "dir": "src/bridges", "subdirs": false }, - { "dir": "src/errors", "subdirs": false }, - { "dir": "src/definitions", "subdirs": false } - ], - "package-specs": [ - { "module": "es6", "in-source": true } - ], - "suffix": ".res.mjs", - "bs-dependencies": [], - "warnings": { "number": "+a-48-20-26-27" }, - "reason": { "react-jsx": 4 } -} diff --git a/src/bridges/VclTotalAst.affine b/src/bridges/VclTotalAst.affine new file mode 100644 index 0000000..0fc9b93 --- /dev/null +++ b/src/bridges/VclTotalAst.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module VclTotalAst; + +// TODO: Complete semantic implementation diff --git a/src/bridges/VclTotalAst.res b/src/bridges/VclTotalAst.res deleted file mode 100644 index 7b6cf8d..0000000 --- a/src/bridges/VclTotalAst.res +++ /dev/null @@ -1,412 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// VCL-total Abstract Syntax Tree — ReScript representation -// -// Mirrors VclTotal.Core.Grammar (Idris2) for parser output. -// Every type here corresponds one-to-one with the Idris2 AST defined in -// src/core/Grammar.idr, enabling round-trip serialisation across the -// Idris2 → C ABI → Zig FFI → ReScript bridge. -// -// The Idris2 side carries dependent-type proofs (TypeCompatible, -// WellFormed, SafetyCertificate) that cannot be expressed in ReScript. -// This module provides the *data* representation only; the ReScript -// parser (VclTotalParser.res) constructs these nodes and the Zig FFI -// layer validates them against the formal specification. - -// ═══════════════════════════════════════════════════════════════════════ -// Modality References (VeriSimDB octad) -// ═══════════════════════════════════════════════════════════════════════ - -/// The 8 VeriSimDB modalities an octad can contain. -/// Maps to VclTotal.Core.Grammar.Modality (Idris2). -type modality = Graph | Vector | Tensor | Semantic | Document | Temporal | Provenance | Spatial - -// ═══════════════════════════════════════════════════════════════════════ -// Epistemic Agents -// ═══════════════════════════════════════════════════════════════════════ - -/// Epistemic agents whose knowledge/belief state is tracked. -/// Maps to VclTotal.Core.Grammar.Agent (Idris2). -type agent = - | AgEngine - | AgProver(string) - | AgValidator - | AgUser(string) - | AgFederation - -// ═══════════════════════════════════════════════════════════════════════ -// Value Types (type system for expressions) -// ═══════════════════════════════════════════════════════════════════════ - -/// Types that VCL-total expressions can evaluate to. -/// Maps to VclTotal.Core.Grammar.VqlType (Idris2). -/// -/// TVector carries a dimension count (e.g. TVector(768) for an embedding). -/// TRecord carries an array of (field_name, field_type) pairs. -/// TNull wraps any type to make it nullable. -/// TAny is unresolved — present before type-checking (Level 2+). -type rec vqlType = - | TString - | TInt - | TFloat - | TBool - | TBytes - | TVector(int) - | TTimestamp - | THash - | TList(vqlType) - | TRecord(array<(string, vqlType)>) - | TOctad - | TNull(vqlType) - | TAny - | TKnows(agent, vqlType) - | TBelieves(agent, vqlType) - | TCommonKnowledge(vqlType) - -// ═══════════════════════════════════════════════════════════════════════ -// Expressions — Field References and Literals -// ═══════════════════════════════════════════════════════════════════════ - -/// Field reference: MODALITY.field_name -/// E.g. GRAPH.name, VECTOR.embedding, TEMPORAL.created_at -/// Maps to VclTotal.Core.Grammar.FieldRef (Idris2 record). -type fieldRef = {modality: modality, fieldName: string} - -/// Literal values that can appear in VCL-total expressions. -/// Maps to VclTotal.Core.Grammar.Literal (Idris2). -/// -/// LitVector holds a dense float array (e.g. for similarity search). -type literal = - | LitString(string) - | LitInt(int) - | LitFloat(float) - | LitBool(bool) - | LitNull - | LitVector(array) - -// ═══════════════════════════════════════════════════════════════════════ -// Operators -// ═══════════════════════════════════════════════════════════════════════ - -/// Comparison operators for WHERE clauses. -/// Maps to VclTotal.Core.Grammar.CompOp (Idris2). -type compOp = Eq | NotEq | Lt | Gt | LtEq | GtEq | Like | In - -/// Logical operators for combining predicates. -/// Maps to VclTotal.Core.Grammar.LogicOp (Idris2). -/// Not is unary (right operand is None in ELogic). -type logicOp = And | Or | Not - -/// Aggregate functions for SELECT and HAVING clauses. -/// Maps to VclTotal.Core.Grammar.AggFunc (Idris2). -type aggFunc = Count | Sum | Avg | Min | Max - -/// Epistemic operators for modal logic expressions. -/// Maps to VclTotal.Core.Grammar.EpistemicOp (Idris2). -type epistemicOp = OpKnows | OpBelieves | OpCommonKnowledge - -// ═══════════════════════════════════════════════════════════════════════ -// Expression AST -// ═══════════════════════════════════════════════════════════════════════ - -/// Expression AST node. -/// Every expression carries a vqlType annotation (initially TAny, -/// resolved during type checking at Level 2+). -/// -/// Maps to VclTotal.Core.Grammar.Expr (Idris2), which is mutually -/// recursive with Statement (via ESubquery). -/// -/// - EField: references a modality field (GRAPH.name) -/// - ELiteral: a constant value ('hello', 42, true) -/// - ECompare: binary comparison (left op right), result type is TBool -/// - ELogic: logical combinator (And/Or need two exprs, Not needs one) -/// - EAggregate: aggregate function applied to an expression -/// - EParam: parameterised input ($1, $name) — required at Level 4+ -/// - EStar: wildcard (*) in SELECT -/// - ESubquery: nested SELECT statement -type rec expr = - | EField(fieldRef, vqlType) - | ELiteral(literal, vqlType) - | ECompare(compOp, expr, expr, vqlType) - | ELogic(logicOp, expr, option, vqlType) - | EAggregate(aggFunc, expr, vqlType) - | EParam(string, vqlType) - | EStar - | ESubquery(statement) - | EEpistemic(epistemicOp, agent, expr, vqlType) - | EAnnounce(agent, expr, expr, vqlType) - -// ═══════════════════════════════════════════════════════════════════════ -// Clauses -// ═══════════════════════════════════════════════════════════════════════ - -/// SELECT clause item. -/// Maps to VclTotal.Core.Grammar.SelectItem (Idris2). -/// -/// - SelField: single field reference (GRAPH.name) -/// - SelModality: entire modality (GRAPH) -/// - SelAggregate: aggregate expression (COUNT(GRAPH.name)) -/// - SelStar: all modalities (*) -and selectItem = - | SelField(fieldRef) - | SelModality(modality) - | SelAggregate(aggFunc, expr) - | SelStar - -/// FROM clause source. -/// Maps to VclTotal.Core.Grammar.Source (Idris2). -/// -/// - SrcOctad: a single octad by UUID (HEXAD ) -/// - SrcFederation: a federation pattern (FEDERATION ) -/// - SrcStore: a named store (STORE ) -and source = - | SrcOctad(string) - | SrcFederation(string) - | SrcStore(string) - -/// PROOF clause for dependent-type verification (VCL-DT extension). -/// Maps to VclTotal.Core.Grammar.ProofClause (Idris2). -/// -/// - ProofAttached: sigma-type proof is bundled with the result -/// - ProofWitness: references a named witness (e.g. a pre-registered proof) -/// - ProofAssert: inline assertion expression -and proofClause = - | ProofAttached - | ProofWitness(string) - | ProofAssert(expr) - -/// Effect declaration for Level 7 (effect tracking). -/// Maps to VclTotal.Core.Grammar.EffectDecl (Idris2). -/// -/// - EffRead: query only reads data -/// - EffWrite: query writes data -/// - EffReadWrite: query both reads and writes -/// - EffConsume: query consumes a linear resource (irreversible) -and effectDecl = EffRead | EffWrite | EffReadWrite | EffConsume - -/// Version constraint for Level 8 (temporal safety). -/// Maps to VclTotal.Core.Grammar.VersionConstraint (Idris2). -/// -/// VeriSimDB supports time-travel queries; these constrain which -/// version of the data the query operates on. -and versionConstraint = - | VerLatest - | VerAtLeast(int) - | VerExact(int) - | VerRange(int, int) - -/// Linearity annotation for Level 9. -/// Maps to VclTotal.Core.Grammar.LinearAnnotation (Idris2). -/// -/// - LinUnlimited: no constraint (default) -/// - LinUseOnce: resource consumed after one read (CONSUME AFTER 1 USE) -/// - LinBounded: resource has a fixed usage limit (USAGE LIMIT n) -and linearAnnotation = - | LinUnlimited - | LinUseOnce - | LinBounded(int) - -/// An epistemic requirement within an EPISTEMIC clause. -/// Maps to VclTotal.Core.Grammar.EpistemicRequirement (Idris2). -and epistemicRequirement = - | EpReqKnows(agent, expr) - | EpReqBelieves(agent, expr) - | EpReqCommon(expr) - | EpReqEntails(agent, agent, expr) - -/// Epistemic clause for Level 10 (epistemic safety). -/// Maps to VclTotal.Core.Grammar.EpistemicClause (Idris2). -and epistemicClause = EpClause(array, array) - -// ═══════════════════════════════════════════════════════════════════════ -// Safety Levels -// ═══════════════════════════════════════════════════════════════════════ - -/// The 11 progressive safety levels (0-10). -/// Maps to VclTotal.ABI.Types.SafetyLevel (Idris2). -/// -/// Each level subsumes all prior levels: a query at level N has passed -/// all checks from levels 0 through N. -and safetyLevel = - | ParseSafe - | SchemaBound - | TypeCompat - | NullSafe - | InjectionProof - | ResultTyped - | CardinalitySafe - | EffectTracked - | TemporalSafe - | LinearSafe - | EpistemicSafe - -// ═══════════════════════════════════════════════════════════════════════ -// Statement (top-level query) -// ═══════════════════════════════════════════════════════════════════════ - -/// A complete VCL-total query statement. -/// Maps to VclTotal.Core.Grammar.Statement (Idris2 record). -/// -/// Contains the standard SQL-like clauses (SELECT, FROM, WHERE, etc.) -/// plus VCL-total extension clauses for proof, effects, versioning, and -/// linearity. The requestedLevel indicates the highest safety level -/// that the parser inferred from the extension clauses present. -and statement = { - /// Fields to retrieve (at least one required for well-formedness) - selectItems: array, - /// Data source (octad, federation, or store) - source: source, - /// Optional WHERE predicate - whereClause: option, - /// Fields to group by (empty array = no grouping) - groupBy: array, - /// Optional HAVING predicate (requires GROUP BY) - having: option, - /// Order specification: (field, ascending?) pairs - orderBy: array<(fieldRef, bool)>, - /// Maximum number of results (required at Level 6+) - limit: option, - /// Number of results to skip - offset: option, - /// VCL-total extension: proof clause (Level 4+) - proofClause: option, - /// VCL-total extension: effect declaration (Level 7+) - effectDecl: option, - /// VCL-total extension: version constraint (Level 8+) - versionConst: option, - /// VCL-total extension: linearity annotation (Level 9) - linearAnnot: option, - /// VCL-total extension: epistemic clause (Level 10) - epistemicClause: option, - /// Highest safety level inferred from present clauses - requestedLevel: safetyLevel, -} - -// ═══════════════════════════════════════════════════════════════════════ -// Conversion helpers -// ═══════════════════════════════════════════════════════════════════════ - -/// Convert a safety level to its integer tag (0-10). -/// Matches VclTotal.ABI.Types.safetyLevelToInt (Idris2). -let safetyLevelToInt = (level: safetyLevel): int => - switch level { - | ParseSafe => 0 - | SchemaBound => 1 - | TypeCompat => 2 - | NullSafe => 3 - | InjectionProof => 4 - | ResultTyped => 5 - | CardinalitySafe => 6 - | EffectTracked => 7 - | TemporalSafe => 8 - | LinearSafe => 9 - | EpistemicSafe => 10 - } - -/// Convert a modality to its string name. -/// Matches VclTotal.Core.Grammar.modalityName (Idris2). -let modalityName = (m: modality): string => - switch m { - | Graph => "GRAPH" - | Vector => "VECTOR" - | Tensor => "TENSOR" - | Semantic => "SEMANTIC" - | Document => "DOCUMENT" - | Temporal => "TEMPORAL" - | Provenance => "PROVENANCE" - | Spatial => "SPATIAL" - } - -/// Convert a modality to its integer tag (0-7). -/// Matches VclTotal.Core.Grammar.modalityToInt (Idris2). -let modalityToInt = (m: modality): int => - switch m { - | Graph => 0 - | Vector => 1 - | Tensor => 2 - | Semantic => 3 - | Document => 4 - | Temporal => 5 - | Provenance => 6 - | Spatial => 7 - } - -/// Convert a comparison operator to its integer tag (0-7). -/// Matches VclTotal.Core.Grammar.compOpToInt (Idris2). -let compOpToInt = (op: compOp): int => - switch op { - | Eq => 0 - | NotEq => 1 - | Lt => 2 - | Gt => 3 - | LtEq => 4 - | GtEq => 5 - | Like => 6 - | In => 7 - } - -/// Convert an aggregate function to its integer tag (0-4). -/// Matches VclTotal.Core.Grammar.aggFuncToInt (Idris2). -let aggFuncToInt = (f: aggFunc): int => - switch f { - | Count => 0 - | Sum => 1 - | Avg => 2 - | Min => 3 - | Max => 4 - } - -/// Convert an effect declaration to its integer tag (0-3). -/// Matches VclTotal.Core.Grammar.effectDeclToInt (Idris2). -let effectDeclToInt = (e: effectDecl): int => - switch e { - | EffRead => 0 - | EffWrite => 1 - | EffReadWrite => 2 - | EffConsume => 3 - } - -/// Compare two safety levels by their numeric rank. -/// Returns positive if a > b, negative if a < b, zero if equal. -let compareSafetyLevels = (a: safetyLevel, b: safetyLevel): int => - safetyLevelToInt(a) - safetyLevelToInt(b) - -/// Return the higher of two safety levels. -let maxSafetyLevel = (a: safetyLevel, b: safetyLevel): safetyLevel => - if compareSafetyLevels(a, b) >= 0 { - a - } else { - b - } - -/// Convert an agent to its string name. -/// Matches VclTotal.Core.Grammar.agentName (Idris2). -let agentName = (a: agent): string => - switch a { - | AgEngine => "ENGINE" - | AgProver(name) => "PROVER:" ++ name - | AgValidator => "VALIDATOR" - | AgUser(name) => "USER:" ++ name - | AgFederation => "FEDERATION" - } - -/// Convert an agent to its integer tag (0-4). -/// Matches VclTotal.Core.Grammar.agentToInt (Idris2). -let agentToInt = (a: agent): int => - switch a { - | AgEngine => 0 - | AgProver(_) => 1 - | AgValidator => 2 - | AgUser(_) => 3 - | AgFederation => 4 - } - -/// Convert an epistemic operator to its integer tag (0-2). -/// Matches VclTotal.Core.Grammar.epistemicOpToInt (Idris2). -let epistemicOpToInt = (op: epistemicOp): int => - switch op { - | OpKnows => 0 - | OpBelieves => 1 - | OpCommonKnowledge => 2 - } diff --git a/src/bridges/VclTotalBridge.affine b/src/bridges/VclTotalBridge.affine new file mode 100644 index 0000000..09a8acc --- /dev/null +++ b/src/bridges/VclTotalBridge.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module VclTotalBridge; + +// TODO: Complete semantic implementation diff --git a/src/bridges/VclTotalBridge.res b/src/bridges/VclTotalBridge.res deleted file mode 100644 index bbc41a1..0000000 --- a/src/bridges/VclTotalBridge.res +++ /dev/null @@ -1,149 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell - -/// VCL-total Bridge — connects the ReScript parser to the TypeLL verification -/// server and the Zig FFI native pipeline. -/// -/// This module provides the high-level API for VCL-total query processing: -/// -/// 1. Parse a VCL-total query string into an AST (via VclTotalParser) -/// 2. Send the AST to TypeLL server for 10-level type checking -/// 3. Optionally compile to a native query plan via the Zig FFI -/// -/// ## Usage -/// -/// ```rescript -/// let result = VclTotalBridge.checkQuery("SELECT GRAPH.name FROM HEXAD abc123") -/// switch result { -/// | Ok(report) => Console.log(report.explanation) -/// | Error(err) => Console.error(err) -/// } -/// ``` - -/// Result of a VCL-total query check. -type checkReport = { - /// Whether the query is valid at its requested safety level. - valid: bool, - /// Maximum safety level achieved (0-9). - maxLevel: int, - /// Human-readable name of the max level. - maxLevelName: string, - /// Query path determined: "VCL (Slipstream)", "VCL-DT", or "VCL-total". - queryPath: string, - /// Per-level diagnostics (empty string = passed, non-empty = diagnostic). - levelDiagnostics: array, - /// Human-readable explanation of the result. - explanation: string, - /// The parsed AST (for further processing). - ast: option, - /// Effects detected in the query. - effects: array, - /// Usage quantifier: "omega", "1", or "bounded(n)". - usage: string, -} - -/// Parse and check a VCL-total query string locally (no server needed). -/// -/// Runs the parser, then performs a local safety level analysis based on -/// which extension clauses are present. This does NOT do full type checking -/// against a schema — that requires the TypeLL server. -let checkQuery = (queryStr: string): result => { - switch VclTotalParser.parse(queryStr) { - | Error(msg) => Error(`Parse failed: ${msg}`) - | Ok(stmt) => - // Determine max level from present clauses - let maxLevel = VclTotalAst.safetyLevelToInt(stmt.requestedLevel) - let levelName = VclTotalDefinitions.safetyLevelName(maxLevel) - let path = VclTotalDefinitions.queryPathFromLevel(maxLevel) - let pathName = VclTotalDefinitions.queryPathName(path) - - // Build per-level diagnostics - let diagnostics = Array.make(~length=10, "") - - // Level 0: always passes (we parsed successfully) - // Level 1-5: require schema to fully check, mark as "needs schema" - if maxLevel < 1 { - diagnostics->Array.setUnsafe(1, "Schema binding requires FROM clause with valid octad") - } - if maxLevel < 6 { - diagnostics->Array.setUnsafe(6, "LIMIT clause required for cardinality safety") - } - if maxLevel < 7 { - diagnostics->Array.setUnsafe(7, "EFFECTS clause required for effect tracking") - } - if maxLevel < 8 { - diagnostics->Array.setUnsafe(8, "AT VERSION clause required for temporal safety") - } - if maxLevel < 9 { - diagnostics->Array.setUnsafe(9, "CONSUME AFTER / USAGE LIMIT required for linearity safety") - } - - // Determine effects from effectDecl - let effects = switch stmt.effectDecl { - | Some(VclTotalAst.EffRead) => ["Read"] - | Some(VclTotalAst.EffWrite) => ["Write"] - | Some(VclTotalAst.EffReadWrite) => ["Read", "Write"] - | Some(VclTotalAst.EffConsume) => ["Read", "Write", "Consume"] - | None => [] - } - - // Determine usage from linearAnnot - let usage = switch stmt.linearAnnot { - | Some(VclTotalAst.LinUseOnce) => "1" - | Some(VclTotalAst.LinBounded(n)) => `bounded(${Int.toString(n)})` - | Some(VclTotalAst.LinUnlimited) | None => "omega" - } - - Ok({ - valid: true, - maxLevel, - maxLevelName: levelName, - queryPath: pathName, - levelDiagnostics: diagnostics, - explanation: `VCL-total Level ${Int.toString(maxLevel + 1)}/10 (${levelName}) — query path: ${pathName}`, - ast: Some(stmt), - effects, - usage, - }) - } -} - -/// Escape a string for safe inclusion in a JSON string value. -let escapeJsonString = (s: string): string => - s - ->String.replaceAll("\\", "\\\\") - ->String.replaceAll("\"", "\\\"") - ->String.replaceAll("\n", "\\n") - ->String.replaceAll("\r", "\\r") - ->String.replaceAll("\t", "\\t") - -/// Encode a checkReport as JSON for transport to TypeLL server. -let reportToJson = (report: checkReport): string => { - let effectsJson = report.effects - ->Array.map(e => `"${escapeJsonString(e)}"`) - ->Array.join(", ") - - let diagnosticsJson = report.levelDiagnostics - ->Array.map(d => `"${escapeJsonString(d)}"`) - ->Array.join(", ") - - let maxLevelName = escapeJsonString(report.maxLevelName) - let queryPath = escapeJsonString(report.queryPath) - let usage = escapeJsonString(report.usage) - - `{"valid":${report.valid ? "true" : "false"},"maxLevel":${Int.toString(report.maxLevel)},"maxLevelName":"${maxLevelName}","queryPath":"${queryPath}","effects":[${effectsJson}],"usage":"${usage}","levelDiagnostics":[${diagnosticsJson}]}` -} - -/// Parse a query and return just the safety level (quick check). -let quickLevel = (queryStr: string): int => - switch checkQuery(queryStr) { - | Ok(report) => report.maxLevel - | Error(_) => 0 - } - -/// Parse a query and describe it in human-readable form. -let describe = (queryStr: string): string => - switch checkQuery(queryStr) { - | Ok(report) => report.explanation - | Error(err) => `Error: ${err}` - } diff --git a/src/bridges/VclTotalParser.affine b/src/bridges/VclTotalParser.affine new file mode 100644 index 0000000..b1f9bd6 --- /dev/null +++ b/src/bridges/VclTotalParser.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module VclTotalParser; + +// TODO: Complete semantic implementation diff --git a/src/bridges/VclTotalParser.res b/src/bridges/VclTotalParser.res deleted file mode 100644 index bd361b0..0000000 --- a/src/bridges/VclTotalParser.res +++ /dev/null @@ -1,1426 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// VCL-total Recursive Descent Parser — ReScript implementation -// -// Parses VCL-total query strings into the AST defined in VclTotalAst.res. -// The parser operates in two phases: -// -// 1. Tokenisation: splits the input on whitespace and punctuation, -// preserving quoted strings and recognising VCL-total keywords. -// 2. Recursive descent: walks the token stream to build a Statement. -// -// The parser sets `requestedLevel` based on which VCL-total extension -// clauses are present in the query, using the highest applicable level: -// -// - No extensions: ParseSafe (Level 0) -// - Has PROOF: InjectionProof (Level 4) -// - Has EFFECTS: EffectTracked (Level 7) -// - Has AT VERSION: TemporalSafe (Level 8) -// - Has CONSUME/USAGE: LinearSafe (Level 9) -// -// Grammar (case-insensitive keywords): -// -// query := SELECT select_items FROM source [WHERE expr] -// [GROUP BY fields] [HAVING expr] [ORDER BY order_items] -// [LIMIT n] [OFFSET n] -// [proof_clause] [effect_clause] [version_clause] -// [linear_clause] - -open VclTotalAst - -// ═══════════════════════════════════════════════════════════════════════ -// Token type -// ═══════════════════════════════════════════════════════════════════════ - -/// A token produced by the lexer. Tokens are either keywords/identifiers -/// (TWord), quoted string literals (TString), integer literals (TNumber), -/// float literals (TFloat), or single-character punctuation (TPunct). -type token = - | TWord(string) - | TString(string) - | TNumber(int) - | TFloat(float) - | TPunct(string) - -// ═══════════════════════════════════════════════════════════════════════ -// Parser state -// ═══════════════════════════════════════════════════════════════════════ - -/// Mutable parser state: holds the token stream and current position. -/// The parser advances `pos` as it consumes tokens. All parse functions -/// receive this state by reference and mutate `pos` in place. -type parserState = { - /// The full array of tokens produced by the lexer - tokens: array, - /// Current position in the token array (mutable) - mutable pos: int, -} - -// ═══════════════════════════════════════════════════════════════════════ -// Tokeniser -// ═══════════════════════════════════════════════════════════════════════ - -/// Check whether a character is whitespace (space, tab, newline, carriage return). -let isWhitespace = (c: string): bool => - c == " " || c == "\t" || c == "\n" || c == "\r" - -/// Check whether a character is a digit (0-9). -let isDigit = (c: string): bool => - c >= "0" && c <= "9" - -/// Check whether a character is alphabetic (a-z, A-Z) or underscore. -let isAlpha = (c: string): bool => - (c >= "a" && c <= "z") || (c >= "A" && c <= "Z") || c == "_" - -/// Check whether a character is alphanumeric or underscore/hyphen. -let isAlphaNum = (c: string): bool => - isAlpha(c) || isDigit(c) || c == "-" - -/// Single-character punctuation tokens that the lexer emits individually. -let isPunct = (c: string): bool => - c == "(" || c == ")" || c == "," || c == "." || c == "*" || - c == "{" || c == "}" || c == "=" || c == "<" || c == ">" || c == "!" - -/// Tokenise a VCL-total query string into an array of tokens. -/// -/// Rules: -/// - Whitespace is skipped (not emitted as tokens) -/// - Single-quoted strings are preserved (quotes stripped) -/// - Numbers (with optional leading minus) become TNumber or TFloat -/// - Two-character operators (!=, <=, >=) are combined into one TWord -/// - Everything else that starts with a letter/underscore becomes TWord -/// - Single punctuation characters become TPunct -let tokenize = (input: string): array => { - let tokens = [] - let len = String.length(input) - let i = ref(0) - - while i.contents < len { - let c = String.charAt(input, i.contents) - - // Skip whitespace - if isWhitespace(c) { - i := i.contents + 1 - } - // Quoted string literal (single quotes) - else if c == "'" { - i := i.contents + 1 - let start = i.contents - while i.contents < len && String.charAt(input, i.contents) != "'" { - i := i.contents + 1 - } - let value = String.slice(input, ~start, ~end=i.contents) - if i.contents < len { - i := i.contents + 1 // skip closing quote - } - let _ = Array.push(tokens, TString(value)) - } - // Quoted string literal (double quotes) - else if c == "\"" { - i := i.contents + 1 - let start = i.contents - while i.contents < len && String.charAt(input, i.contents) != "\"" { - i := i.contents + 1 - } - let value = String.slice(input, ~start, ~end=i.contents) - if i.contents < len { - i := i.contents + 1 // skip closing quote - } - let _ = Array.push(tokens, TString(value)) - } - // Two-character operators: !=, <=, >= - else if c == "!" && i.contents + 1 < len && String.charAt(input, i.contents + 1) == "=" { - let _ = Array.push(tokens, TWord("!=")) - i := i.contents + 2 - } else if c == "<" && i.contents + 1 < len && String.charAt(input, i.contents + 1) == "=" { - let _ = Array.push(tokens, TWord("<=")) - i := i.contents + 2 - } else if c == ">" && i.contents + 1 < len && String.charAt(input, i.contents + 1) == "=" { - let _ = Array.push(tokens, TWord(">=")) - i := i.contents + 2 - } - // Number literal (possibly negative, possibly float) - else if isDigit(c) || (c == "-" && i.contents + 1 < len && isDigit(String.charAt(input, i.contents + 1))) { - let start = i.contents - if c == "-" { - i := i.contents + 1 - } - while i.contents < len && isDigit(String.charAt(input, i.contents)) { - i := i.contents + 1 - } - // Check for decimal point (float) - if i.contents < len && String.charAt(input, i.contents) == "." && - i.contents + 1 < len && isDigit(String.charAt(input, i.contents + 1)) { - i := i.contents + 1 // skip the dot - while i.contents < len && isDigit(String.charAt(input, i.contents)) { - i := i.contents + 1 - } - let numStr = String.slice(input, ~start, ~end=i.contents) - switch Float.fromString(numStr) { - | Some(f) => { - let _ = Array.push(tokens, TFloat(f)) - } - | None => { - let _ = Array.push(tokens, TWord(numStr)) - } - } - } else { - let numStr = String.slice(input, ~start, ~end=i.contents) - switch Int.fromString(numStr) { - | Some(n) => { - let _ = Array.push(tokens, TNumber(n)) - } - | None => { - let _ = Array.push(tokens, TWord(numStr)) - } - } - } - } - // Word (keyword or identifier): starts with letter or underscore - else if isAlpha(c) { - let start = i.contents - while i.contents < len && isAlphaNum(String.charAt(input, i.contents)) { - i := i.contents + 1 - } - let word = String.slice(input, ~start, ~end=i.contents) - let _ = Array.push(tokens, TWord(word)) - } - // Dollar-prefixed parameter ($name, $1) - else if c == "$" { - i := i.contents + 1 - let start = i.contents - while i.contents < len && isAlphaNum(String.charAt(input, i.contents)) { - i := i.contents + 1 - } - let name = String.slice(input, ~start, ~end=i.contents) - let _ = Array.push(tokens, TWord("$" ++ name)) - } - // Single punctuation character - else if isPunct(c) { - let _ = Array.push(tokens, TPunct(c)) - i := i.contents + 1 - } - // Unknown character — skip - else { - i := i.contents + 1 - } - } - - tokens -} - -// ═══════════════════════════════════════════════════════════════════════ -// Parser helpers -// ═══════════════════════════════════════════════════════════════════════ - -/// Peek at the current token without consuming it. -/// Returns None if the parser has reached the end of the token stream. -let peek = (state: parserState): option => - if state.pos < Array.length(state.tokens) { - Some(state.tokens[state.pos]) - } else { - None - } - -/// Consume and return the current token, advancing the position. -/// Returns None if the parser has reached the end of the token stream. -let advance = (state: parserState): option => - if state.pos < Array.length(state.tokens) { - let tok = state.tokens[state.pos] - state.pos = state.pos + 1 - Some(tok) - } else { - None - } - -/// Check whether the current token is a word matching the given string -/// (case-insensitive comparison). Does NOT consume the token. -let peekWord = (state: parserState, word: string): bool => - switch peek(state) { - | Some(TWord(w)) => String.toUpperCase(w) == String.toUpperCase(word) - | _ => false - } - -/// Consume the current token if it is a word matching the given string -/// (case-insensitive). Returns true if consumed, false otherwise. -let expectWord = (state: parserState, word: string): bool => - if peekWord(state, word) { - state.pos = state.pos + 1 - true - } else { - false - } - -/// Consume the current token if it is a punctuation character matching -/// the given string. Returns true if consumed, false otherwise. -let expectPunct = (state: parserState, punct: string): bool => - switch peek(state) { - | Some(TPunct(p)) if p == punct => { - state.pos = state.pos + 1 - true - } - | _ => false - } - -/// Return an error result with a message describing what was expected -/// and what was actually found at the current position. -let parseError = (state: parserState, expected: string): result<'a, string> => { - let found = switch peek(state) { - | Some(TWord(w)) => `word "${w}"` - | Some(TString(s)) => `string '${s}'` - | Some(TNumber(n)) => `number ${Int.toString(n)}` - | Some(TFloat(f)) => `float ${Float.toString(f)}` - | Some(TPunct(p)) => `'${p}'` - | None => "end of input" - } - Error(`Expected ${expected}, found ${found} at position ${Int.toString(state.pos)}`) -} - -// ═══════════════════════════════════════════════════════════════════════ -// Modality parsing -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a modality name from the current token. -/// Modality names are case-insensitive and must be one of the 8 -/// VeriSimDB modalities (GRAPH, VECTOR, TENSOR, SEMANTIC, DOCUMENT, -/// TEMPORAL, PROVENANCE, SPATIAL). -/// -/// Returns the parsed modality or an error if the current token is -/// not a recognised modality name. -let parseModality = (name: string): option => - switch String.toUpperCase(name) { - | "GRAPH" => Some(Graph) - | "VECTOR" => Some(Vector) - | "TENSOR" => Some(Tensor) - | "SEMANTIC" => Some(Semantic) - | "DOCUMENT" => Some(Document) - | "TEMPORAL" => Some(Temporal) - | "PROVENANCE" => Some(Provenance) - | "SPATIAL" => Some(Spatial) - | _ => None - } - -/// Check whether a word is a VCL-total keyword (case-insensitive). -/// Keywords cannot be used as field names or identifiers. -let isKeyword = (word: string): bool => { - let upper = String.toUpperCase(word) - upper == "SELECT" || upper == "FROM" || upper == "WHERE" || - upper == "GROUP" || upper == "HAVING" || upper == "ORDER" || - upper == "LIMIT" || upper == "OFFSET" || upper == "AND" || - upper == "OR" || upper == "NOT" || upper == "LIKE" || - upper == "IN" || upper == "BY" || upper == "ASC" || - upper == "DESC" || upper == "PROOF" || upper == "EFFECTS" || - upper == "AT" || upper == "CONSUME" || upper == "USAGE" || upper == "EPISTEMIC" || - upper == "HEXAD" || upper == "FEDERATION" || upper == "STORE" || - upper == "ATTACHED" || upper == "WITNESS" || upper == "ASSERT" || - upper == "VERSION" || upper == "LATEST" || upper == "BETWEEN" || - upper == "AFTER" || upper == "USE" || upper == "TRUE" || - upper == "FALSE" || upper == "NULL" || upper == "WITH" || - upper == "SESSION" || upper == "COUNT" || upper == "SUM" || - upper == "AVG" || upper == "MIN" || upper == "MAX" -} - -// ═══════════════════════════════════════════════════════════════════════ -// Aggregate function parsing -// ═══════════════════════════════════════════════════════════════════════ - -/// Try to parse an aggregate function name from the given word. -/// Returns None if the word is not a recognised aggregate function. -let parseAggFunc = (name: string): option => - switch String.toUpperCase(name) { - | "COUNT" => Some(Count) - | "SUM" => Some(Sum) - | "AVG" => Some(Avg) - | "MIN" => Some(Min) - | "MAX" => Some(Max) - | _ => None - } - -// ═══════════════════════════════════════════════════════════════════════ -// Field reference parsing -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a field reference of the form MODALITY.field_name. -/// The modality must be one of the 8 VeriSimDB modalities. -/// The field name follows the dot and must be an alphanumeric identifier. -/// -/// Example: GRAPH.name, VECTOR.embedding, TEMPORAL.created_at -let parseFieldRef = (state: parserState): result => - switch advance(state) { - | Some(TWord(w)) => - switch parseModality(w) { - | Some(mod) => - if expectPunct(state, ".") { - switch advance(state) { - | Some(TWord(fieldName)) => Ok({modality: mod, fieldName}) - | _ => parseError(state, "field name after '.'") - } - } else { - // Might be a bare modality reference — put it back - state.pos = state.pos - 1 - parseError(state, "field reference (MODALITY.field_name)") - } - | None => { - state.pos = state.pos - 1 - parseError(state, "modality name") - } - } - | _ => parseError(state, "field reference") - } - -/// Try to parse a field reference. Returns None if the current tokens -/// do not form a valid field reference, without consuming any tokens. -let tryParseFieldRef = (state: parserState): option => { - let savedPos = state.pos - switch parseFieldRef(state) { - | Ok(ref) => Some(ref) - | Error(_) => { - state.pos = savedPos - None - } - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// Expression parsing (recursive descent) -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a primary (atomic) expression. -/// -/// Primary expressions are the leaves of the expression tree: -/// - Parenthesised sub-expression: ( expr ) -/// - Parameter reference: $name -/// - String literal: 'hello' -/// - Boolean literal: TRUE / FALSE -/// - NULL literal -/// - Number literal: 42, 3.14 -/// - Aggregate function: COUNT(expr), SUM(expr), etc. -/// - Star: * -/// - Field reference: MODALITY.field_name -/// - Subquery: ( SELECT ... ) -let rec parsePrimary = (state: parserState): result => { - switch peek(state) { - // Parenthesised expression or subquery - | Some(TPunct("(")) => { - let _ = advance(state) - // Check if it's a subquery (starts with SELECT) - if peekWord(state, "SELECT") { - switch parseStatementInner(state) { - | Ok(stmt) => - if expectPunct(state, ")") { - Ok(ESubquery(stmt)) - } else { - parseError(state, "closing ')' after subquery") - } - | Error(e) => Error(e) - } - } else { - switch parseExpr(state) { - | Ok(e) => - if expectPunct(state, ")") { - Ok(e) - } else { - parseError(state, "closing ')'") - } - | Error(e) => Error(e) - } - } - } - - // Star wildcard - | Some(TPunct("*")) => { - let _ = advance(state) - Ok(EStar) - } - - // String literal - | Some(TString(s)) => { - let _ = advance(state) - Ok(ELiteral(LitString(s), TString)) - } - - // Integer literal - | Some(TNumber(n)) => { - let _ = advance(state) - Ok(ELiteral(LitInt(n), TInt)) - } - - // Float literal - | Some(TFloat(f)) => { - let _ = advance(state) - Ok(ELiteral(LitFloat(f), TFloat)) - } - - // Word: could be keyword, aggregate, modality, field ref, parameter - | Some(TWord(w)) => { - let upper = String.toUpperCase(w) - - // Boolean literals - if upper == "TRUE" { - let _ = advance(state) - Ok(ELiteral(LitBool(true), TBool)) - } else if upper == "FALSE" { - let _ = advance(state) - Ok(ELiteral(LitBool(false), TBool)) - } - // NULL literal - else if upper == "NULL" { - let _ = advance(state) - Ok(ELiteral(LitNull, TAny)) - } - // NOT (unary logical) - else if upper == "NOT" { - let _ = advance(state) - switch parsePrimary(state) { - | Ok(operand) => Ok(ELogic(Not, operand, None, TBool)) - | Error(e) => Error(e) - } - } - // Parameter ($name) - else if String.startsWith(w, "$") { - let _ = advance(state) - let paramName = String.sliceToEnd(w, ~start=1) - Ok(EParam(paramName, TAny)) - } - // Aggregate function: COUNT(...), SUM(...), etc. - else { - switch parseAggFunc(upper) { - | Some(aggFn) => { - let _ = advance(state) // consume the function name - if expectPunct(state, "(") { - switch parseExpr(state) { - | Ok(innerExpr) => - if expectPunct(state, ")") { - Ok(EAggregate(aggFn, innerExpr, TAny)) - } else { - parseError(state, "closing ')' after aggregate argument") - } - | Error(e) => Error(e) - } - } else { - // Not an aggregate call — treat as a field ref - state.pos = state.pos - 1 - switch tryParseFieldRef(state) { - | Some(ref) => Ok(EField(ref, TAny)) - | None => { - let _ = advance(state) // consume the word - Ok(EField({modality: Graph, fieldName: w}, TAny)) - } - } - } - } - | None => - // Try as a field reference (MODALITY.field_name) - switch tryParseFieldRef(state) { - | Some(ref) => Ok(EField(ref, TAny)) - | None => { - // Bare word — treat as an unqualified field name - let _ = advance(state) - Ok(EField({modality: Graph, fieldName: w}, TAny)) - } - } - } - } - } - - | _ => parseError(state, "expression") - } -} - -/// Parse a comparison expression. -/// -/// Handles binary comparison operators: =, !=, <, >, <=, >=, LIKE, IN. -/// Left-hand side is a primary expression; if a comparison operator -/// follows, the right-hand side is also parsed as a primary. -and parseComparison = (state: parserState): result => { - switch parsePrimary(state) { - | Ok(left) => { - // Check for comparison operator - let compOp = switch peek(state) { - | Some(TPunct("=")) => Some(Eq) - | Some(TWord("!=")) => Some(NotEq) - | Some(TPunct("<")) => Some(Lt) - | Some(TPunct(">")) => Some(Gt) - | Some(TWord("<=")) => Some(LtEq) - | Some(TWord(">=")) => Some(GtEq) - | Some(TWord(w)) if String.toUpperCase(w) == "LIKE" => Some(Like) - | Some(TWord(w)) if String.toUpperCase(w) == "IN" => Some(In) - | _ => None - } - - switch compOp { - | Some(op) => { - let _ = advance(state) - switch parsePrimary(state) { - | Ok(right) => Ok(ECompare(op, left, right, TBool)) - | Error(e) => Error(e) - } - } - | None => Ok(left) - } - } - | Error(e) => Error(e) - } -} - -/// Parse a logical expression (AND, OR). -/// -/// AND has higher precedence than OR. Both are left-associative. -/// NOT is handled in parsePrimary as a unary prefix operator. -/// -/// This uses a simple left-to-right pass: first parse a comparison, -/// then while AND or OR follows, parse the next comparison and -/// combine them into an ELogic node. -and parseExpr = (state: parserState): result => { - switch parseAndExpr(state) { - | Ok(left) => parseOrTail(state, left) - | Error(e) => Error(e) - } -} - -/// Parse the AND level of precedence. -/// AND binds tighter than OR. -and parseAndExpr = (state: parserState): result => { - switch parseComparison(state) { - | Ok(left) => parseAndTail(state, left) - | Error(e) => Error(e) - } -} - -/// Continue parsing AND conjunctions after the left operand. -and parseAndTail = (state: parserState, left: expr): result => { - if peekWord(state, "AND") { - let _ = advance(state) - switch parseComparison(state) { - | Ok(right) => { - let combined = ELogic(And, left, Some(right), TBool) - parseAndTail(state, combined) - } - | Error(e) => Error(e) - } - } else { - Ok(left) - } -} - -/// Continue parsing OR disjunctions after the left operand. -and parseOrTail = (state: parserState, left: expr): result => { - if peekWord(state, "OR") { - let _ = advance(state) - switch parseAndExpr(state) { - | Ok(right) => { - let combined = ELogic(Or, left, Some(right), TBool) - parseOrTail(state, combined) - } - | Error(e) => Error(e) - } - } else { - Ok(left) - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// SELECT items -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a single SELECT item. -/// -/// A select item can be: -/// - * (all modalities) -/// - An aggregate function call: COUNT(expr), SUM(expr), etc. -/// - A modality name: GRAPH, VECTOR, etc. -/// - A field reference: MODALITY.field_name -and parseSelectItem = (state: parserState): result => { - switch peek(state) { - // Star: SELECT * - | Some(TPunct("*")) => { - let _ = advance(state) - Ok(SelStar) - } - - | Some(TWord(w)) => { - let upper = String.toUpperCase(w) - - // Check for aggregate function: COUNT(...), SUM(...), etc. - switch parseAggFunc(upper) { - | Some(aggFn) => { - let savedPos = state.pos - let _ = advance(state) - if expectPunct(state, "(") { - switch parseExpr(state) { - | Ok(innerExpr) => - if expectPunct(state, ")") { - Ok(SelAggregate(aggFn, innerExpr)) - } else { - parseError(state, "closing ')' after aggregate argument") - } - | Error(e) => Error(e) - } - } else { - // Not a function call — backtrack - state.pos = savedPos - switch tryParseFieldRef(state) { - | Some(ref) => Ok(SelField(ref)) - | None => { - let _ = advance(state) - switch parseModality(w) { - | Some(mod) => Ok(SelModality(mod)) - | None => Ok(SelField({modality: Graph, fieldName: w})) - } - } - } - } - } - - | None => - // Check if it's MODALITY.field or bare MODALITY - switch tryParseFieldRef(state) { - | Some(ref) => Ok(SelField(ref)) - | None => { - let _ = advance(state) - switch parseModality(w) { - | Some(mod) => Ok(SelModality(mod)) - | None => Ok(SelField({modality: Graph, fieldName: w})) - } - } - } - } - } - - | _ => parseError(state, "select item") - } -} - -/// Parse a comma-separated list of SELECT items. -/// At least one item is required. -and parseSelectItems = (state: parserState): result, string> => { - switch parseSelectItem(state) { - | Ok(first) => { - let items = [first] - while expectPunct(state, ",") { - switch parseSelectItem(state) { - | Ok(item) => { - let _ = Array.push(items, item) - } - | Error(_) => () - } - } - Ok(items) - } - | Error(e) => Error(e) - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// FROM source -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a FROM clause source. -/// -/// Sources can be: -/// - HEXAD — a single octad by UUID -/// - FEDERATION — a federation query pattern -/// - STORE — a named data store -and parseSource = (state: parserState): result => { - if expectWord(state, "HEXAD") { - switch advance(state) { - | Some(TWord(uuid)) => Ok(SrcOctad(uuid)) - | Some(TString(uuid)) => Ok(SrcOctad(uuid)) - | _ => parseError(state, "octad UUID after HEXAD") - } - } else if expectWord(state, "FEDERATION") { - switch advance(state) { - | Some(TWord(pattern)) => Ok(SrcFederation(pattern)) - | Some(TString(pattern)) => Ok(SrcFederation(pattern)) - | _ => parseError(state, "federation pattern after FEDERATION") - } - } else if expectWord(state, "STORE") { - switch advance(state) { - | Some(TWord(id)) => Ok(SrcStore(id)) - | Some(TString(id)) => Ok(SrcStore(id)) - | _ => parseError(state, "store identifier after STORE") - } - } else { - parseError(state, "source (HEXAD, FEDERATION, or STORE)") - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// Extension clauses -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a PROOF clause. -/// -/// Syntax: -/// - PROOF ATTACHED — sigma-type proof is bundled -/// - PROOF WITNESS — references a named witness -/// - PROOF ASSERT — inline assertion expression -and parseProofClause = (state: parserState): result => { - if expectWord(state, "ATTACHED") { - Ok(ProofAttached) - } else if expectWord(state, "WITNESS") { - switch advance(state) { - | Some(TWord(name)) => Ok(ProofWitness(name)) - | Some(TString(name)) => Ok(ProofWitness(name)) - | _ => parseError(state, "witness name after PROOF WITNESS") - } - } else if expectWord(state, "ASSERT") { - switch parseExpr(state) { - | Ok(e) => Ok(ProofAssert(e)) - | Error(e) => Error(e) - } - } else { - parseError(state, "ATTACHED, WITNESS, or ASSERT after PROOF") - } -} - -/// Parse an EFFECTS clause. -/// -/// Syntax: EFFECTS { Read } | EFFECTS { Write } | -/// EFFECTS { Read, Write } | EFFECTS { Consume } -and parseEffectClause = (state: parserState): result => { - if !expectPunct(state, "{") { - return parseError(state, "'{' after EFFECTS") - } - - // Collect effect names - let effects = [] - switch advance(state) { - | Some(TWord(w)) => { - let _ = Array.push(effects, String.toUpperCase(w)) - } - | _ => return parseError(state, "effect name (Read, Write, Consume)") - } - - // Check for additional effects (comma-separated) - while expectPunct(state, ",") { - switch advance(state) { - | Some(TWord(w)) => { - let _ = Array.push(effects, String.toUpperCase(w)) - } - | _ => () - } - } - - if !expectPunct(state, "}") { - return parseError(state, "'}' to close EFFECTS block") - } - - // Determine effect type from collected names - let hasRead = Array.some(effects, e => e == "READ") - let hasWrite = Array.some(effects, e => e == "WRITE") - let hasConsume = Array.some(effects, e => e == "CONSUME") - - if hasConsume { - Ok(EffConsume) - } else if hasRead && hasWrite { - Ok(EffReadWrite) - } else if hasWrite { - Ok(EffWrite) - } else if hasRead { - Ok(EffRead) - } else { - Error("Unknown effect combination in EFFECTS clause") - } -} - -/// Parse a version constraint clause. -/// -/// Syntax: -/// - AT LATEST — use latest version -/// - AT VERSION >= n — at least version n -/// - AT VERSION = n — exactly version n -/// - AT VERSION BETWEEN n AND m — version range [n, m] -and parseVersionClause = (state: parserState): result => { - if expectWord(state, "LATEST") { - Ok(VerLatest) - } else if expectWord(state, "VERSION") { - // Check which constraint type - if expectWord(state, ">=") || (expectPunct(state, ">") && expectPunct(state, "=")) { - switch advance(state) { - | Some(TNumber(n)) => Ok(VerAtLeast(n)) - | _ => parseError(state, "version number after >=") - } - } else if expectPunct(state, "=") { - switch advance(state) { - | Some(TNumber(n)) => Ok(VerExact(n)) - | _ => parseError(state, "version number after =") - } - } else if expectWord(state, "BETWEEN") { - switch advance(state) { - | Some(TNumber(lo)) => - if expectWord(state, "AND") { - switch advance(state) { - | Some(TNumber(hi)) => Ok(VerRange(lo, hi)) - | _ => parseError(state, "upper version number after AND") - } - } else { - parseError(state, "AND after lower version bound") - } - | _ => parseError(state, "lower version number after BETWEEN") - } - } else { - parseError(state, ">=, =, or BETWEEN after VERSION") - } - } else { - parseError(state, "LATEST or VERSION after AT") - } -} - -/// Parse a linearity clause. -/// -/// Syntax: -/// - CONSUME AFTER n USE — resource consumed after n uses -/// (n=1 maps to LinUseOnce, n>1 maps to LinBounded) -/// - USAGE LIMIT n — explicit usage limit -and parseLinearClause = (state: parserState, keyword: string): result => { - let upper = String.toUpperCase(keyword) - if upper == "CONSUME" { - if expectWord(state, "AFTER") { - switch advance(state) { - | Some(TNumber(n)) => - if expectWord(state, "USE") || expectWord(state, "USES") { - if n == 1 { - Ok(LinUseOnce) - } else { - Ok(LinBounded(n)) - } - } else { - parseError(state, "USE after count in CONSUME AFTER n USE") - } - | _ => parseError(state, "count after CONSUME AFTER") - } - } else { - parseError(state, "AFTER following CONSUME") - } - } else if upper == "USAGE" { - if expectWord(state, "LIMIT") { - switch advance(state) { - | Some(TNumber(n)) => - if n == 1 { - Ok(LinUseOnce) - } else { - Ok(LinBounded(n)) - } - | _ => parseError(state, "count after USAGE LIMIT") - } - } else { - parseError(state, "LIMIT after USAGE") - } - } else { - Error(`Unexpected keyword "${keyword}" in linearity clause`) - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// Epistemic clause parsing -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse an agent identifier. -/// -/// Valid forms: -/// ENGINE | VALIDATOR | FEDERATION -/// PROVER: (e.g. PROVER:lean4) -/// USER: (e.g. USER:alice) -and parseAgent = (state: parserState): result => { - switch peek(state) { - | Some(TWord(w)) => - let upper = String.toUpperCase(w) - if upper == "ENGINE" { - let _ = advance(state) - Ok(AgEngine) - } else if upper == "VALIDATOR" { - let _ = advance(state) - Ok(AgValidator) - } else if upper == "FEDERATION" { - let _ = advance(state) - Ok(AgFederation) - } else if String.length(upper) > 7 && String.slice(upper, ~start=0, ~end=7) == "PROVER:" { - let _ = advance(state) - Ok(AgProver(String.sliceToEnd(w, ~start=7))) - } else if String.length(upper) > 5 && String.slice(upper, ~start=0, ~end=5) == "USER:" { - let _ = advance(state) - Ok(AgUser(String.sliceToEnd(w, ~start=5))) - } else { - parseError(state, `Expected agent, got "${w}"`) - } - | _ => parseError(state, "Expected agent identifier") - } -} - -/// Parse a comma-separated list of agents until a semicolon. -and parseAgentList = (state: parserState): result, string> => { - let agents = [] - switch parseAgent(state) { - | Ok(a) => { - let _ = Array.push(agents, a) - let continue = ref(true) - while continue.contents { - switch peek(state) { - | Some(TPunct(",")) => - let _ = advance(state) - switch parseAgent(state) { - | Ok(a2) => let _ = Array.push(agents, a2) - | Error(e) => return Error(e) - } - | _ => continue := false - } - } - Ok(agents) - } - | Error(e) => Error(e) - } -} - -/// Parse an epistemic requirement. -/// -/// Forms: -/// KNOWS ( ) -/// BELIEVES ( ) -/// COMMON KNOWLEDGE ( ) -/// ENTAILS ( ) -and parseEpistemicRequirement = (state: parserState): result => { - switch peek(state) { - | Some(TWord(w)) => - let upper = String.toUpperCase(w) - if upper == "KNOWS" { - let _ = advance(state) - switch parseAgent(state) { - | Ok(agent) => - switch expectPunct(state, "(") { - | Ok(_) => - switch parseExpr(state) { - | Ok(expr) => - switch expectPunct(state, ")") { - | Ok(_) => Ok(EpReqKnows(agent, expr)) - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - } else if upper == "BELIEVES" { - let _ = advance(state) - switch parseAgent(state) { - | Ok(agent) => - switch expectPunct(state, "(") { - | Ok(_) => - switch parseExpr(state) { - | Ok(expr) => - switch expectPunct(state, ")") { - | Ok(_) => Ok(EpReqBelieves(agent, expr)) - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - } else if upper == "COMMON" { - let _ = advance(state) - switch peek(state) { - | Some(TWord(kw)) if String.toUpperCase(kw) == "KNOWLEDGE" => - let _ = advance(state) - switch expectPunct(state, "(") { - | Ok(_) => - switch parseExpr(state) { - | Ok(expr) => - switch expectPunct(state, ")") { - | Ok(_) => Ok(EpReqCommon(expr)) - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | _ => parseError(state, "Expected KNOWLEDGE after COMMON") - } - } else { - // Try ENTAILS ( ) - switch parseAgent(state) { - | Ok(a1) => - switch peek(state) { - | Some(TWord(ew)) if String.toUpperCase(ew) == "ENTAILS" => - let _ = advance(state) - switch parseAgent(state) { - | Ok(a2) => - switch expectPunct(state, "(") { - | Ok(_) => - switch parseExpr(state) { - | Ok(expr) => - switch expectPunct(state, ")") { - | Ok(_) => Ok(EpReqEntails(a1, a2, expr)) - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | Error(e) => Error(e) - } - | _ => parseError(state, "Expected ENTAILS after agent in epistemic requirement") - } - | Error(e) => Error(e) - } - } - | _ => parseError(state, "Expected epistemic requirement keyword") - } -} - -/// Parse an EPISTEMIC clause. -/// -/// Syntax: -/// EPISTEMIC { AGENTS ; REQUIRES [; REQUIRES ]* } -and parseEpistemicClause = (state: parserState): result => { - switch expectPunct(state, "{") { - | Error(e) => Error(e) - | Ok(_) => - // Parse AGENTS keyword - switch peek(state) { - | Some(TWord(w)) if String.toUpperCase(w) == "AGENTS" => - let _ = advance(state) - switch parseAgentList(state) { - | Error(e) => Error(e) - | Ok(agents) => - // Expect semicolon after agent list - let reqs = [] - let continue = ref(true) - while continue.contents { - switch peek(state) { - | Some(TPunct(";")) => - let _ = advance(state) - // Check for REQUIRES - switch peek(state) { - | Some(TWord(rw)) if String.toUpperCase(rw) == "REQUIRES" => - let _ = advance(state) - switch parseEpistemicRequirement(state) { - | Ok(req) => let _ = Array.push(reqs, req) - | Error(e) => return Error(e) - } - | _ => continue := false - } - | _ => continue := false - } - } - switch expectPunct(state, "}") { - | Ok(_) => Ok(EpClause(agents, reqs)) - | Error(e) => Error(e) - } - } - | _ => parseError(state, "Expected AGENTS keyword in EPISTEMIC clause") - } - } -} - -// ═══════════════════════════════════════════════════════════════════════ -// Statement parsing -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a complete VCL-total statement from the current token position. -/// -/// This is the core of the recursive descent parser. It expects: -/// SELECT select_items FROM source [WHERE expr] [GROUP BY fields] -/// [HAVING expr] [ORDER BY order_items] [LIMIT n] [OFFSET n] -/// [PROOF ...] [EFFECTS ...] [AT ...] [CONSUME/USAGE ...] -/// -/// The parser builds a statement record and infers the requestedLevel -/// from which extension clauses are present. -and parseStatementInner = (state: parserState): result => { - // ── SELECT ── - if !expectWord(state, "SELECT") { - return parseError(state, "SELECT") - } - - let selectItems = switch parseSelectItems(state) { - | Ok(items) => items - | Error(e) => return Error(e) - } - - // ── FROM ── - if !expectWord(state, "FROM") { - return parseError(state, "FROM") - } - - let source = switch parseSource(state) { - | Ok(s) => s - | Error(e) => return Error(e) - } - - // ── WHERE (optional) ── - let whereClause = if peekWord(state, "WHERE") { - let _ = advance(state) - switch parseExpr(state) { - | Ok(e) => Some(e) - | Error(e) => return Error(e) - } - } else { - None - } - - // ── GROUP BY (optional) ── - let groupBy = if peekWord(state, "GROUP") { - let _ = advance(state) - if !expectWord(state, "BY") { - return parseError(state, "BY after GROUP") - } - let fields = [] - switch parseFieldRef(state) { - | Ok(ref) => { - let _ = Array.push(fields, ref) - while expectPunct(state, ",") { - switch parseFieldRef(state) { - | Ok(ref2) => { - let _ = Array.push(fields, ref2) - } - | Error(_) => () - } - } - } - | Error(e) => return Error(e) - } - fields - } else { - [] - } - - // ── HAVING (optional) ── - let having = if peekWord(state, "HAVING") { - let _ = advance(state) - switch parseExpr(state) { - | Ok(e) => Some(e) - | Error(e) => return Error(e) - } - } else { - None - } - - // ── ORDER BY (optional) ── - let orderBy = if peekWord(state, "ORDER") { - let _ = advance(state) - if !expectWord(state, "BY") { - return parseError(state, "BY after ORDER") - } - let items: array<(fieldRef, bool)> = [] - switch parseFieldRef(state) { - | Ok(ref) => { - let asc = if expectWord(state, "DESC") { - false - } else { - let _ = expectWord(state, "ASC") // consume optional ASC - true - } - let _ = Array.push(items, (ref, asc)) - while expectPunct(state, ",") { - switch parseFieldRef(state) { - | Ok(ref2) => { - let asc2 = if expectWord(state, "DESC") { - false - } else { - let _ = expectWord(state, "ASC") - true - } - let _ = Array.push(items, (ref2, asc2)) - } - | Error(_) => () - } - } - } - | Error(e) => return Error(e) - } - items - } else { - [] - } - - // ── LIMIT (optional) ── - let limit = if peekWord(state, "LIMIT") { - let _ = advance(state) - switch advance(state) { - | Some(TNumber(n)) => Some(n) - | _ => return parseError(state, "number after LIMIT") - } - } else { - None - } - - // ── OFFSET (optional) ── - let offset = if peekWord(state, "OFFSET") { - let _ = advance(state) - switch advance(state) { - | Some(TNumber(n)) => Some(n) - | _ => return parseError(state, "number after OFFSET") - } - } else { - None - } - - // ── VCL-total extension clauses (optional, order-independent) ── - let proofClause: ref> = ref(None) - let effectDecl: ref> = ref(None) - let versionConst: ref> = ref(None) - let linearAnnot: ref> = ref(None) - let epistemicClause: ref> = ref(None) - - // Parse extension clauses in a loop — they can appear in any order - let parsing = ref(true) - while parsing.contents { - switch peek(state) { - | Some(TWord(w)) => { - let upper = String.toUpperCase(w) - if upper == "PROOF" && proofClause.contents == None { - let _ = advance(state) - switch parseProofClause(state) { - | Ok(pc) => proofClause := Some(pc) - | Error(e) => return Error(e) - } - } else if upper == "EFFECTS" && effectDecl.contents == None { - let _ = advance(state) - switch parseEffectClause(state) { - | Ok(ed) => effectDecl := Some(ed) - | Error(e) => return Error(e) - } - } else if upper == "AT" && versionConst.contents == None { - let _ = advance(state) - switch parseVersionClause(state) { - | Ok(vc) => versionConst := Some(vc) - | Error(e) => return Error(e) - } - } else if (upper == "CONSUME" || upper == "USAGE") && linearAnnot.contents == None { - let _ = advance(state) - switch parseLinearClause(state, upper) { - | Ok(la) => linearAnnot := Some(la) - | Error(e) => return Error(e) - } - } else if upper == "EPISTEMIC" && epistemicClause.contents == None { - let _ = advance(state) - switch parseEpistemicClause(state) { - | Ok(ec) => epistemicClause := Some(ec) - | Error(e) => return Error(e) - } - } else if upper == "WITH" { - // WITH SESSION — informational, skip for now - let _ = advance(state) - let _ = expectWord(state, "SESSION") - let _ = advance(state) // skip mode name - } else { - parsing := false - } - } - | _ => parsing := false - } - } - - // ── Compute requested safety level ── - // The level is the highest applicable based on which clauses are present. - let level = ref(ParseSafe) - if proofClause.contents != None { - level := maxSafetyLevel(level.contents, InjectionProof) - } - if effectDecl.contents != None { - level := maxSafetyLevel(level.contents, EffectTracked) - } - if versionConst.contents != None { - level := maxSafetyLevel(level.contents, TemporalSafe) - } - if linearAnnot.contents != None { - level := maxSafetyLevel(level.contents, LinearSafe) - } - if epistemicClause.contents != None { - level := maxSafetyLevel(level.contents, EpistemicSafe) - } - - Ok({ - selectItems, - source, - whereClause, - groupBy, - having, - orderBy, - limit, - offset, - proofClause: proofClause.contents, - effectDecl: effectDecl.contents, - versionConst: versionConst.contents, - linearAnnot: linearAnnot.contents, - epistemicClause: epistemicClause.contents, - requestedLevel: level.contents, - }) -} - -// ═══════════════════════════════════════════════════════════════════════ -// Public API -// ═══════════════════════════════════════════════════════════════════════ - -/// Parse a VCL-total query string into a typed Statement AST. -/// -/// This is the main entry point for the parser. It tokenises the input -/// string, then runs the recursive descent parser to produce a Statement. -/// -/// Returns Ok(statement) on success, or Error(message) with a -/// human-readable error description on failure. -/// -/// The returned statement's `requestedLevel` field reflects the highest -/// safety level inferred from the extension clauses present: -/// - No extensions: ParseSafe (Level 0) -/// - PROOF clause: InjectionProof (Level 4) -/// - EFFECTS clause: EffectTracked (Level 7) -/// - AT VERSION clause: TemporalSafe (Level 8) -/// - CONSUME/USAGE: LinearSafe (Level 9) -/// - EPISTEMIC clause: EpistemicSafe (Level 10) -/// -/// Example: -/// parse("SELECT * FROM HEXAD abc-123 WHERE GRAPH.name = 'hello' LIMIT 10") -/// // => Ok({ selectItems: [SelStar], source: SrcOctad("abc-123"), ... }) -let parse = (input: string): result => { - let tokens = tokenize(input) - - if Array.length(tokens) == 0 { - return Error("Empty query") - } - - let state = {tokens, pos: 0} - - switch parseStatementInner(state) { - | Ok(stmt) => { - // Warn if there are unconsumed tokens (but still return the statement) - if state.pos < Array.length(state.tokens) { - // Could be trailing tokens — for now, accept the parse - Ok(stmt) - } else { - Ok(stmt) - } - } - | Error(e) => Error(e) - } -} - -/// Tokenise a VCL-total query string without parsing. -/// Useful for debugging and syntax highlighting. -/// -/// Returns the array of tokens produced by the lexer. -let tokenizeOnly = (input: string): array => tokenize(input) - -/// Attempt to parse and return a human-readable description of the -/// parse result, useful for REPL or debugging output. -/// -/// On success, reports the number of select items, source type, -/// and inferred safety level. On failure, reports the error message. -let parseAndDescribe = (input: string): string => { - switch parse(input) { - | Ok(stmt) => { - let selectCount = Array.length(stmt.selectItems) - let sourceDesc = switch stmt.source { - | SrcOctad(id) => `HEXAD ${id}` - | SrcFederation(pat) => `FEDERATION ${pat}` - | SrcStore(id) => `STORE ${id}` - } - let levelNum = safetyLevelToInt(stmt.requestedLevel) - `OK: ${Int.toString(selectCount)} select item(s) FROM ${sourceDesc}, safety level ${Int.toString(levelNum)}` - } - | Error(e) => `ERROR: ${e}` - } -} diff --git a/src/definitions/VclTotalDefinitions.affine b/src/definitions/VclTotalDefinitions.affine new file mode 100644 index 0000000..3977976 --- /dev/null +++ b/src/definitions/VclTotalDefinitions.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module VclTotalDefinitions; + +// TODO: Complete semantic implementation diff --git a/src/definitions/VclTotalDefinitions.res b/src/definitions/VclTotalDefinitions.res deleted file mode 100644 index d08c6a2..0000000 --- a/src/definitions/VclTotalDefinitions.res +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell - -/// VCL-total Shared Definitions — constants, version info, and vocabulary. - -/// ABI version (major, minor, patch) -let abiVersion = (0, 1, 0) - -/// ABI version as a packed u32 (matching Zig's encoding) -let abiVersionPacked = { - let (major, minor, patch) = abiVersion - lor(lor(lsl(major, 16), lsl(minor, 8)), patch) -} - -/// File extension for VCL-total queries -let fileExtension = ".vcltotal" - -/// MIME type for VCL-total queries (application/vcl-total) -let mimeType = "application/vcl-total" - -/// The 10 safety level names -let safetyLevelNames = [ - "Parse-time safety", - "Schema-binding safety", - "Type-compatible operations", - "Null-safety", - "Injection-proof safety", - "Result-type safety", - "Cardinality safety", - "Effect-tracking safety", - "Temporal safety", - "Linearity safety", -] - -/// Get the name of a safety level (0-9) -let safetyLevelName = (level: int): string => - switch safetyLevelNames->Array.get(level) { - | Some(name) => name - | None => "Unknown" - } - -/// Whether a safety level is in the "established" tier (0-5) -/// or "research-identified" tier (6-9) -let isEstablished = (level: int): bool => level <= 5 - -/// The 3 query paths -type queryPath = Slipstream | Dt | Ut - -/// Determine query path from safety level -let queryPathFromLevel = (level: int): queryPath => - if level >= 7 { - Ut - } else if level >= 2 { - Dt - } else { - Slipstream - } - -/// Query path display name -let queryPathName = (path: queryPath): string => - switch path { - | Slipstream => "VCL (Slipstream)" - | Dt => "VCL-DT" - | Ut => "VCL-total" - } - -/// The 8 VeriSimDB modality names (uppercase, matching VCL syntax) -let modalityNames = [ - "GRAPH", "VECTOR", "TENSOR", "SEMANTIC", - "DOCUMENT", "TEMPORAL", "PROVENANCE", "SPATIAL", -] - -/// Reserved keywords in VCL-total (case-insensitive) -let keywords = [ - "SELECT", "FROM", "WHERE", "GROUP", "BY", "HAVING", "ORDER", - "LIMIT", "OFFSET", "AND", "OR", "NOT", "IN", "LIKE", "AS", - "HEXAD", "FEDERATION", "STORE", "PROOF", "ATTACHED", "WITNESS", - "ASSERT", "EFFECTS", "AT", "LATEST", "VERSION", "BETWEEN", - "CONSUME", "AFTER", "USE", "USAGE", "WITH", "SESSION", - "COUNT", "SUM", "AVG", "MIN", "MAX", -] diff --git a/src/errors/VclTotalError.affine b/src/errors/VclTotalError.affine new file mode 100644 index 0000000..75185f8 --- /dev/null +++ b/src/errors/VclTotalError.affine @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell +// Ported via Harvard Engine bulk-processor + +module VclTotalError; + +// TODO: Complete semantic implementation diff --git a/src/errors/VclTotalError.res b/src/errors/VclTotalError.res deleted file mode 100644 index 4d30b72..0000000 --- a/src/errors/VclTotalError.res +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell - -/// VCL-total error types matching the ABI error codes. -/// -/// Each error carries a safety level where the failure occurred, -/// a human-readable message, and an optional source location. - -/// Source location in a VCL-total query string. -type sourceLocation = { - offset: int, - line: int, - column: int, -} - -/// The 11 error categories matching VclTotalError in Types.idr. -type errorCode = - | ParseError // Level 0: syntax error - | SchemaError // Level 1: unresolved field reference - | TypeError // Level 2: incompatible types in comparison - | NullError // Level 3: nullable field used without guard - | InjectionAttempt // Level 4: raw string literal in unsafe position - | ResultTypeError // Level 5: unresolved result type (TAny in select) - | CardinalityViolation // Level 6: unbounded result without LIMIT - | EffectViolation // Level 7: undeclared effect - | TemporalBoundsExceeded // Level 8: version constraint violation - | LinearityViolation // Level 9: use-count exceeded - | InternalError // Internal checker error - -/// A VCL-total diagnostic error. -type diagnostic = { - code: errorCode, - level: int, // Safety level (0-9) where the error occurred - message: string, - location: option, - hint: option, -} - -/// Convert an error code to its ABI integer tag. -let errorCodeToInt = (code: errorCode): int => - switch code { - | ParseError => 1 - | SchemaError => 2 - | TypeError => 3 - | NullError => 4 - | InjectionAttempt => 5 - | ResultTypeError => 6 - | CardinalityViolation => 7 - | EffectViolation => 8 - | TemporalBoundsExceeded => 9 - | LinearityViolation => 10 - | InternalError => 11 - } - -/// Convert an ABI integer tag to an error code. -let errorCodeFromInt = (n: int): option => - switch n { - | 1 => Some(ParseError) - | 2 => Some(SchemaError) - | 3 => Some(TypeError) - | 4 => Some(NullError) - | 5 => Some(InjectionAttempt) - | 6 => Some(ResultTypeError) - | 7 => Some(CardinalityViolation) - | 8 => Some(EffectViolation) - | 9 => Some(TemporalBoundsExceeded) - | 10 => Some(LinearityViolation) - | 11 => Some(InternalError) - | _ => None - } - -/// Format a diagnostic as a human-readable string. -let formatDiagnostic = (d: diagnostic): string => { - let loc = switch d.location { - | Some({line, column, _}) => ` at ${Int.toString(line)}:${Int.toString(column)}` - | None => "" - } - let hint = switch d.hint { - | Some(h) => `\n hint: ${h}` - | None => "" - } - `[Level ${Int.toString(d.level)}] ${d.message}${loc}${hint}` -}