From a9709e56f83c00c5d3e047330ed2e890b593c322 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 14:00:27 +0100 Subject: [PATCH 1/2] chore(nix->guix): delete Nix estate-wide (#138) --- .github/workflows/governance.yml | 2 +- .github/workflows/hypatia-scan.yml | 2 +- .github/workflows/secret-scanner.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/governance.yml b/.github/workflows/governance.yml index ac1c323..a4219ff 100644 --- a/.github/workflows/governance.yml +++ b/.github/workflows/governance.yml @@ -32,4 +32,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 3b2c38b..23de7c3 100644 --- a/.github/workflows/hypatia-scan.yml +++ b/.github/workflows/hypatia-scan.yml @@ -27,4 +27,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/secret-scanner.yml b/.github/workflows/secret-scanner.yml index 758075a..8f5b215 100644 --- a/.github/workflows/secret-scanner.yml +++ b/.github/workflows/secret-scanner.yml @@ -19,4 +19,4 @@ jobs: permissions: contents: read uses: hyperpolymath/standards/.github/workflows/secret-scanner-reusable.yml@c65436ee3351cd6b0fa14b142938b195efc77586 - secrets: inherit \ No newline at end of file + secrets: inherit From 15f3d1cdc47826fa95e15e9b13b3bcc188ae887c Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:30:13 +0100 Subject: [PATCH 2/2] refactor: eradicate ReScript and mechanically port to AffineScript --- typeql-experimental/rescript.json | 14 - typeql-experimental/src/parser/TQLAst.affine | 7 + typeql-experimental/src/parser/TQLAst.res | 305 --------- .../src/parser/TQLParser.affine | 7 + typeql-experimental/src/parser/TQLParser.res | 405 ------------ .../test/rescript/TQLLexer_test.affine | 7 + .../test/rescript/TQLLexer_test.res | 621 ------------------ .../test/rescript/TQLParser_test.affine | 7 + .../test/rescript/TQLParser_test.res | 221 ------- 9 files changed, 28 insertions(+), 1566 deletions(-) delete mode 100644 typeql-experimental/rescript.json create mode 100644 typeql-experimental/src/parser/TQLAst.affine delete mode 100644 typeql-experimental/src/parser/TQLAst.res create mode 100644 typeql-experimental/src/parser/TQLParser.affine delete mode 100644 typeql-experimental/src/parser/TQLParser.res create mode 100644 typeql-experimental/test/rescript/TQLLexer_test.affine delete mode 100644 typeql-experimental/test/rescript/TQLLexer_test.res create mode 100644 typeql-experimental/test/rescript/TQLParser_test.affine delete mode 100644 typeql-experimental/test/rescript/TQLParser_test.res diff --git a/typeql-experimental/rescript.json b/typeql-experimental/rescript.json deleted file mode 100644 index 4fdc6e8..0000000 --- a/typeql-experimental/rescript.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "typeql-experimental", - "sources": [ - { "dir": "src/parser", "subdirs": true }, - { "dir": "test/rescript", "subdirs": true, "type": "dev" } - ], - "package-specs": [ - { "module": "es6", "in-source": true } - ], - "suffix": ".res.mjs", - "bs-dependencies": [ - "@rescript/core" - ] -} diff --git a/typeql-experimental/src/parser/TQLAst.affine b/typeql-experimental/src/parser/TQLAst.affine new file mode 100644 index 0000000..8bb7aff --- /dev/null +++ b/typeql-experimental/src/parser/TQLAst.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 TQLAst; + +// TODO: Complete semantic implementation diff --git a/typeql-experimental/src/parser/TQLAst.res b/typeql-experimental/src/parser/TQLAst.res deleted file mode 100644 index b3fb98a..0000000 --- a/typeql-experimental/src/parser/TQLAst.res +++ /dev/null @@ -1,305 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// TQLAst.res — Extended AST types for VCL-UT (TypeQL-experimental) -// -// Defines the AST nodes for the six new extension clauses that augment -// standard VCL queries. These types mirror the Idris2 kernel types but -// in a form suitable for parser output. - -// ============================================================================ -// VCL Base Types (adapted from VeriSimDB VCLParser.res) -// ============================================================================ - -// VCL modalities — the octad (8 modalities). -type modality = - | Graph - | Vector - | Tensor - | Semantic - | Document - | Temporal - | Provenance - | Spatial - | All - -// Data source for a query. -type source = - | Hexad(string) - | Federation(string, option) - | Store(string) - | Reflect - -and driftPolicy = - | Strict - | Repair - | Tolerate - | Latest - -// Comparison operators. -type operator = - | Eq - | Neq - | Gt - | Lt - | Gte - | Lte - | Like - | Contains - | Matches - -// Literal values. -type rec literal = - | String(string) - | Int(int) - | Float(float) - | Bool(bool) - | Array(array) - -// Field reference: MODALITY.field_name -type fieldRef = { - modality: modality, - field: string, -} - -// Aggregate functions. -type aggregateFunc = - | Count - | Sum - | Avg - | Min - | Max - -// Aggregate expressions. -type aggregateExpr = - | CountAll - | AggregateField(aggregateFunc, fieldRef) - -// Sort direction. -type sortDirection = - | Asc - | Desc - -// ORDER BY item. -type orderByItem = { - field: fieldRef, - direction: sortDirection, -} - -// Conditions (simplified for extension parsing). -type condition = - | Simple(simpleCondition) - | And(condition, condition) - | Or(condition, condition) - | Not(condition) - -and simpleCondition = - | FulltextContains(string) - | FulltextMatches(string) - | FieldCondition(string, operator, literal) - -// Proof types from VCL PROOF clause. -type proofType = - | Existence - | Citation - | Access - | Integrity - | ProvenanceProof - | Custom - -// Proof specification. -type proofSpec = { - proofType: proofType, - contractName: string, - customParams: option>, -} - -// ============================================================================ -// Extension 1: Linear Types — CONSUME AFTER N USE -// ============================================================================ - -// Specifies how many times a connection/resource can be used before -// it must be released. Maps to Idris2 QTT quantity annotations. -type usageSpec = { - count: int, // Must be positive (>= 1) -} - -// ============================================================================ -// Extension 2: Session Types — WITH SESSION protocol -// ============================================================================ - -// Named session protocol that constrains the allowed state transitions. -type sessionProtocol = - | ReadOnlyProtocol - | MutationProtocol - | StreamProtocol - | BatchProtocol - | CustomProtocol(string) - -// ============================================================================ -// Extension 3: Effect Systems — EFFECTS { Read, Write, ... } -// ============================================================================ - -// Individual effect labels. -type effectLabel = - | ReadEffect - | WriteEffect - | CiteEffect - | AuditEffect - | TransformEffect - | FederateEffect - | CustomEffect(string) - -// Declared effect set for a query. -type effectDecl = { - effects: array, -} - -// ============================================================================ -// Extension 4: Modal Types — IN TRANSACTION state -// ============================================================================ - -// Transaction scope states. -type transactionState = - | TxFresh - | TxActive - | TxCommitted - | TxRolledBack - | TxReadSnapshot - | TxCustom(string) - -// Modal scoping declaration. -type modalDecl = { - state: transactionState, -} - -// ============================================================================ -// Extension 5: Proof-Carrying Code — PROOF ATTACHED theorem -// ============================================================================ - -// A theorem to attach to query results. -type theoremRef = { - name: string, - params: option>, -} - -// ============================================================================ -// Extension 6: Quantitative Type Theory — USAGE LIMIT n -// ============================================================================ - -// Resource budget for the entire query plan. -type usageLimit = { - limit: int, // Must be positive (>= 1) -} - -// ============================================================================ -// Extension Annotations (all optional) -// ============================================================================ - -// Collected extension annotations from a VCL-UT query. -// Each field is None if the corresponding clause was not present. -type extensionAnnotations = { - consumeAfter: option, - sessionProtocol: option, - declaredEffects: option, - modalScope: option, - proofAttached: option, - usageLimit: option, -} - -// Construct empty annotations (no extension clauses). -let emptyAnnotations: extensionAnnotations = { - consumeAfter: None, - sessionProtocol: None, - declaredEffects: None, - modalScope: None, - proofAttached: None, - usageLimit: None, -} - -// ============================================================================ -// Extended Query AST -// ============================================================================ - -// A standard VCL query (base, before extensions). -type baseQuery = { - modalities: array, - projections: option>, - aggregates: option>, - source: source, - where: option, - groupBy: option>, - having: option, - proof: option>, - orderBy: option>, - limit: option, - offset: option, -} - -// A VCL-UT extended query = base VCL query + extension annotations. -type extendedQuery = { - base: baseQuery, - extensions: extensionAnnotations, -} - -// ============================================================================ -// String Representations (for debugging and error messages) -// ============================================================================ - -let showModality = (m: modality): string => { - switch m { - | Graph => "GRAPH" - | Vector => "VECTOR" - | Tensor => "TENSOR" - | Semantic => "SEMANTIC" - | Document => "DOCUMENT" - | Temporal => "TEMPORAL" - | Provenance => "PROVENANCE" - | Spatial => "SPATIAL" - | All => "*" - } -} - -let showSessionProtocol = (p: sessionProtocol): string => { - switch p { - | ReadOnlyProtocol => "ReadOnlyProtocol" - | MutationProtocol => "MutationProtocol" - | StreamProtocol => "StreamProtocol" - | BatchProtocol => "BatchProtocol" - | CustomProtocol(name) => name - } -} - -let showEffectLabel = (e: effectLabel): string => { - switch e { - | ReadEffect => "Read" - | WriteEffect => "Write" - | CiteEffect => "Cite" - | AuditEffect => "Audit" - | TransformEffect => "Transform" - | FederateEffect => "Federate" - | CustomEffect(name) => name - } -} - -let showTransactionState = (s: transactionState): string => { - switch s { - | TxFresh => "Fresh" - | TxActive => "Active" - | TxCommitted => "Committed" - | TxRolledBack => "RolledBack" - | TxReadSnapshot => "ReadSnapshot" - | TxCustom(name) => name - } -} - -let showProofType = (pt: proofType): string => { - switch pt { - | Existence => "EXISTENCE" - | Citation => "CITATION" - | Access => "ACCESS" - | Integrity => "INTEGRITY" - | ProvenanceProof => "PROVENANCE" - | Custom => "CUSTOM" - } -} diff --git a/typeql-experimental/src/parser/TQLParser.affine b/typeql-experimental/src/parser/TQLParser.affine new file mode 100644 index 0000000..79a7819 --- /dev/null +++ b/typeql-experimental/src/parser/TQLParser.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 TQLParser; + +// TODO: Complete semantic implementation diff --git a/typeql-experimental/src/parser/TQLParser.res b/typeql-experimental/src/parser/TQLParser.res deleted file mode 100644 index ac758d1..0000000 --- a/typeql-experimental/src/parser/TQLParser.res +++ /dev/null @@ -1,405 +0,0 @@ -// @taxonomy: compiler/parser -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// TQLParser.res — Parser for VCL-UT extended syntax -// -// Implements parser combinators for the six new VCL-UT clauses: -// 1. CONSUME AFTER n USE — Linear types -// 2. WITH SESSION protocol — Session types -// 3. EFFECTS { e1, e2, ... } — Effect systems -// 4. IN TRANSACTION state — Modal types -// 5. PROOF ATTACHED theorem — Proof-carrying code -// 6. USAGE LIMIT n — Quantitative type theory -// -// Follows the same combinator pattern as VeriSimDB's VCLParser.res. - -// ============================================================================ -// Parser Combinators (same pattern as VCLParser.res) -// ============================================================================ - -module Parser = { - type parseError = { - message: string, - position: int, - } - - type parseResult<'a> = Result<('a, int), parseError> - - type parser<'a> = string => parseResult<'a> - - // Basic combinators - let pure = (value: 'a): parser<'a> => { - _input => Ok((value, 0)) - } - - let fail = (message: string): parser<'a> => { - _input => Error({message, position: 0}) - } - - let map = (p: parser<'a>, f: 'a => 'b): parser<'b> => { - input => { - switch p(input) { - | Ok((value, consumed)) => Ok((f(value), consumed)) - | Error(e) => Error(e) - } - } - } - - let bind = (p: parser<'a>, f: 'a => parser<'b>): parser<'b> => { - input => { - switch p(input) { - | Ok((value, consumed)) => { - let remaining = Js.String2.sliceToEnd(input, ~from=consumed) - switch f(value)(remaining) { - | Ok((value2, consumed2)) => Ok((value2, consumed + consumed2)) - | Error(e) => Error({...e, position: e.position + consumed}) - } - } - | Error(e) => Error(e) - } - } - } - - let \"<|>" = (p1: parser<'a>, p2: parser<'a>): parser<'a> => { - input => { - switch p1(input) { - | Ok(result) => Ok(result) - | Error(_) => p2(input) - } - } - } - - // Whitespace handling - let ws: parser = input => { - let trimmed = Js.String2.trimStart(input) - let consumed = Js.String2.length(input) - Js.String2.length(trimmed) - Ok(((), consumed)) - } - - let lexeme = (p: parser<'a>): parser<'a> => { - bind(p, value => map(ws, _ => value)) - } - - // String matching (case-insensitive for keywords) - let string = (s: string): parser => { - input => { - let len = Js.String2.length(s) - let prefix = Js.String2.slice(input, ~from=0, ~to_=len) - if Js.String2.toUpperCase(prefix) == Js.String2.toUpperCase(s) { - Ok((s, len)) - } else { - Error({message: `Expected "${s}"`, position: 0}) - } - } - } - - let keyword = (k: string): parser => { - lexeme(string(k)) - } - - // Regex-based parsers - let regex = (pattern: string): parser => { - input => { - let re = Js.Re.fromStringWithFlags(pattern, ~flags="i") - switch Js.Re.exec_(re, input) { - | Some(result) => { - let matched = Js.Re.captures(result)[0] - switch Js.Nullable.toOption(matched) { - | Some(str) => Ok((str, Js.String2.length(str))) - | None => Error({message: `Regex ${pattern} failed`, position: 0}) - } - } - | None => Error({message: `Regex ${pattern} failed`, position: 0}) - } - } - } - - let identifier: parser = lexeme(regex("^[a-zA-Z_][a-zA-Z0-9_]*")) - - let integer: parser = { - input => { - let intStr = lexeme(regex("^[0-9]+")) - switch intStr(input) { - | Ok((str, consumed)) => { - switch Belt.Int.fromString(str) { - | Some(n) => Ok((n, consumed)) - | None => Error({message: "Invalid integer", position: 0}) - } - } - | Error(e) => Error(e) - } - } - } - - let stringLiteral: parser = { - input => { - let quoted = lexeme(regex("^\"([^\"\\\\]|\\\\.)*\"")) - switch quoted(input) { - | Ok((str, consumed)) => { - let unquoted = Js.String2.slice(str, ~from=1, ~to_=Js.String2.length(str) - 1) - Ok((unquoted, consumed)) - } - | Error(e) => Error(e) - } - } - } - - let sepBy = (p: parser<'a>, sep: parser<'b>): parser> => { - input => { - switch p(input) { - | Ok((first, consumed1)) => { - let rec loop = (remaining, acc, totalConsumed) => { - let parseRest = bind(sep, _ => p) - switch parseRest(remaining) { - | Ok((value, consumed2)) => { - let newRemaining = Js.String2.sliceToEnd(remaining, ~from=consumed2) - loop(newRemaining, acc->Js.Array2.concat([value]), totalConsumed + consumed2) - } - | Error(_) => Ok((acc, totalConsumed)) - } - } - let remaining = Js.String2.sliceToEnd(input, ~from=consumed1) - loop(remaining, [first], consumed1) - } - | Error(e) => Error(e) - } - } - } - - let optional = (p: parser<'a>): parser> => { - input => { - switch p(input) { - | Ok((value, consumed)) => Ok((Some(value), consumed)) - | Error(_) => Ok((None, 0)) - } - } - } -} - -// ============================================================================ -// Extension Clause Parsers -// ============================================================================ - -module ExtensionParsers = { - open Parser - open TQLAst - - // -------------------------------------------------------------------------- - // 1. CONSUME AFTER n USE — Linear Types - // -------------------------------------------------------------------------- - - let consumeClause: parser = { - bind(keyword("CONSUME"), _ => - bind(keyword("AFTER"), _ => - bind(integer, count => - map(keyword("USE"), _ => { - TQLAst.count: count, - }) - ) - ) - ) - } - - // -------------------------------------------------------------------------- - // 2. WITH SESSION protocol — Session Types - // -------------------------------------------------------------------------- - - let sessionProtocol: parser = { - let readOnly = map(keyword("ReadOnlyProtocol"), _ => ReadOnlyProtocol) - let mutation = map(keyword("MutationProtocol"), _ => MutationProtocol) - let stream = map(keyword("StreamProtocol"), _ => StreamProtocol) - let batch = map(keyword("BatchProtocol"), _ => BatchProtocol) - let custom = map(identifier, name => CustomProtocol(name)) - - \"<|>"(readOnly, \"<|>"(mutation, \"<|>"(stream, \"<|>"(batch, custom)))) - } - - let sessionClause: parser = { - bind(keyword("WITH"), _ => - bind(keyword("SESSION"), _ => - sessionProtocol - ) - ) - } - - // -------------------------------------------------------------------------- - // 3. EFFECTS { e1, e2, ... } — Effect Systems - // -------------------------------------------------------------------------- - - let effectLabel: parser = { - let read = map(keyword("Read"), _ => ReadEffect) - let write = map(keyword("Write"), _ => WriteEffect) - let cite = map(keyword("Cite"), _ => CiteEffect) - let audit = map(keyword("Audit"), _ => AuditEffect) - let transform = map(keyword("Transform"), _ => TransformEffect) - let federate = map(keyword("Federate"), _ => FederateEffect) - let custom = map(identifier, name => CustomEffect(name)) - - \"<|>"(read, \"<|>"(write, \"<|>"(cite, \"<|>"(audit, \"<|>"(transform, \"<|>"(federate, custom)))))) - } - - let effectsClause: parser = { - bind(keyword("EFFECTS"), _ => - bind(keyword("{"), _ => - bind(sepBy(effectLabel, keyword(",")), effects => - map(keyword("}"), _ => { - TQLAst.effects: effects, - }) - ) - ) - ) - } - - // -------------------------------------------------------------------------- - // 4. IN TRANSACTION state — Modal Types - // -------------------------------------------------------------------------- - - let transactionState: parser = { - let fresh = map(keyword("Fresh"), _ => TxFresh) - let active = map(keyword("Active"), _ => TxActive) - let committed = map(keyword("Committed"), _ => TxCommitted) - let rolledBack = map(keyword("RolledBack"), _ => TxRolledBack) - let readSnapshot = map(keyword("ReadSnapshot"), _ => TxReadSnapshot) - let custom = map(identifier, name => TxCustom(name)) - - \"<|>"(fresh, \"<|>"(active, \"<|>"(committed, \"<|>"(rolledBack, \"<|>"(readSnapshot, custom))))) - } - - let modalClause: parser = { - bind(keyword("IN"), _ => - bind(keyword("TRANSACTION"), _ => - map(transactionState, state => { - TQLAst.state: state, - }) - ) - ) - } - - // -------------------------------------------------------------------------- - // 5. PROOF ATTACHED theorem — Proof-Carrying Code - // -------------------------------------------------------------------------- - - let theoremParams: parser> = { - bind(keyword("("), _ => - bind(sepBy( - bind(identifier, key => - bind(keyword("="), _ => - map(stringLiteral, value => (key, value)) - ) - ), - keyword(","), - ), params => - map(keyword(")"), _ => params) - ) - ) - } - - let proofAttachedClause: parser = { - bind(keyword("PROOF"), _ => - bind(keyword("ATTACHED"), _ => - bind(identifier, name => - map(optional(theoremParams), params => { - TQLAst.name: name, - params: params, - }) - ) - ) - ) - } - - // -------------------------------------------------------------------------- - // 6. USAGE LIMIT n — Quantitative Type Theory - // -------------------------------------------------------------------------- - - let usageLimitClause: parser = { - bind(keyword("USAGE"), _ => - bind(keyword("LIMIT"), _ => - map(integer, limit => { - TQLAst.limit: limit, - }) - ) - ) - } - - // -------------------------------------------------------------------------- - // Combined: Parse all extension clauses (all optional) - // -------------------------------------------------------------------------- - - let extensionAnnotations: parser = { - bind(optional(consumeClause), consume => - bind(optional(sessionClause), session => - bind(optional(effectsClause), effects => - bind(optional(modalClause), modal => - bind(optional(proofAttachedClause), proof => - map(optional(usageLimitClause), usage => { - consumeAfter: consume, - sessionProtocol: session, - declaredEffects: effects, - modalScope: modal, - proofAttached: proof, - usageLimit: usage, - }) - ) - ) - ) - ) - ) - } -} - -// ============================================================================ -// Public API -// ============================================================================ - -type parseError = Parser.parseError - -// Parse just the extension annotations (after the base VCL query). -// Input should be the remainder of the query string after the base VCL -// grammar has been parsed. -let parseExtensions = (input: string): Result => { - let p = Parser.bind(Parser.ws, _ => ExtensionParsers.extensionAnnotations) - switch p(input) { - | Ok((annotations, _consumed)) => Ok(annotations) - | Error(e) => Error(e) - } -} - -// Validate parsed extension annotations for semantic correctness. -// Checks: -// - CONSUME AFTER count must be positive -// - USAGE LIMIT must be positive -// - USAGE LIMIT >= CONSUME AFTER when both present -let validateExtensions = (ann: TQLAst.extensionAnnotations): Result => { - // Check CONSUME AFTER is positive - switch ann.consumeAfter { - | Some({count}) if count <= 0 => - Error("CONSUME AFTER count must be positive, got " ++ Belt.Int.toString(count)) - | _ => { - // Check USAGE LIMIT is positive - switch ann.usageLimit { - | Some({limit}) if limit <= 0 => - Error("USAGE LIMIT must be positive, got " ++ Belt.Int.toString(limit)) - | _ => { - // Cross-check: USAGE LIMIT >= CONSUME AFTER - switch (ann.consumeAfter, ann.usageLimit) { - | (Some({count}), Some({limit})) if limit < count => - Error( - "USAGE LIMIT (" ++ Belt.Int.toString(limit) ++ - ") must be >= CONSUME AFTER (" ++ Belt.Int.toString(count) ++ ")" - ) - | _ => Ok(ann) - } - } - } - } - } -} - -// Parse and validate extension annotations in one step. -let parseAndValidateExtensions = (input: string): Result => { - switch parseExtensions(input) { - | Ok(ann) => validateExtensions(ann) - | Error(e) => Error(e.message) - } -} diff --git a/typeql-experimental/test/rescript/TQLLexer_test.affine b/typeql-experimental/test/rescript/TQLLexer_test.affine new file mode 100644 index 0000000..bf3f6db --- /dev/null +++ b/typeql-experimental/test/rescript/TQLLexer_test.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 TQLLexer_test; + +// TODO: Complete semantic implementation diff --git a/typeql-experimental/test/rescript/TQLLexer_test.res b/typeql-experimental/test/rescript/TQLLexer_test.res deleted file mode 100644 index e22e178..0000000 --- a/typeql-experimental/test/rescript/TQLLexer_test.res +++ /dev/null @@ -1,621 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// TQLLexer_test.res — Dedicated lexer-level unit tests for VCL-UT parser -// -// Tests the low-level parser combinators (string matching, keyword recognition, -// identifier parsing, integer parsing, string literal parsing) and the -// individual extension clause tokenizers in TQLParser.res. -// -// These tests complement TQLParser_test.res which tests at the clause level. -// Here we verify individual token recognition and combinator behaviour. -// -// Run with: deno task res:build && deno run test/rescript/TQLLexer_test.res.mjs - -// ============================================================================ -// Test Utilities -// ============================================================================ - -let passed = ref(0) -let failed = ref(0) -let total = ref(0) - -let assert_ = (name: string, condition: bool): unit => { - total := total.contents + 1 - if condition { - passed := passed.contents + 1 - Js.Console.log(" PASS: " ++ name) - } else { - failed := failed.contents + 1 - Js.Console.error(" FAIL: " ++ name) - } -} - -let assertParseOk = (name: string, parser: TQLParser.Parser.parser<'a>, input: string): unit => { - switch parser(input) { - | Ok(_) => assert_(name, true) - | Error(_) => assert_(name, false) - } -} - -let assertParseErr = (name: string, parser: TQLParser.Parser.parser<'a>, input: string): unit => { - switch parser(input) { - | Ok(_) => assert_(name ++ " (expected error)", false) - | Error(_) => assert_(name, true) - } -} - -let assertParseValue = ( - name: string, - parser: TQLParser.Parser.parser<'a>, - input: string, - check: 'a => bool, -): unit => { - switch parser(input) { - | Ok((value, _consumed)) => assert_(name, check(value)) - | Error(_) => assert_(name ++ " (parse failed)", false) - } -} - -let assertConsumed = ( - name: string, - parser: TQLParser.Parser.parser<'a>, - input: string, - expectedConsumed: int, -): unit => { - switch parser(input) { - | Ok((_value, consumed)) => assert_(name, consumed == expectedConsumed) - | Error(_) => assert_(name ++ " (parse failed)", false) - } -} - -// ============================================================================ -// Test: string combinator (case-insensitive keyword matching) -// ============================================================================ - -let testStringCombinator = (): unit => { - Js.Console.log("\n--- string combinator (case-insensitive matching) ---") - open TQLParser.Parser - - // Exact match - assertParseOk("exact match SELECT", string("SELECT"), "SELECT") - assertParseOk("exact match FROM", string("FROM"), "FROM") - - // Case insensitivity - assertParseOk("lowercase select", string("SELECT"), "select") - assertParseOk("mixed case SeLeCt", string("SELECT"), "SeLeCt") - assertParseOk("uppercase CONSUME", string("CONSUME"), "CONSUME") - assertParseOk("lowercase consume", string("CONSUME"), "consume") - - // Consumed length matches keyword length - assertConsumed("SELECT consumes 6", string("SELECT"), "SELECT rest", 6) - assertConsumed("FROM consumes 4", string("FROM"), "FROM rest", 4) - - // Non-match - assertParseErr("mismatch INSERT vs SELECT", string("SELECT"), "INSERT") - assertParseErr("partial match SEL", string("SELECT"), "SEL") - assertParseErr("empty input", string("SELECT"), "") -} - -// ============================================================================ -// Test: keyword combinator (lexeme-wrapped string) -// ============================================================================ - -let testKeywordCombinator = (): unit => { - Js.Console.log("\n--- keyword combinator ---") - open TQLParser.Parser - - // Keywords consume trailing whitespace - assertParseOk("keyword with trailing space", keyword("SELECT"), "SELECT ") - assertParseOk("keyword with trailing tab", keyword("SELECT"), "SELECT\t") - assertParseOk("keyword with trailing newline", keyword("SELECT"), "SELECT\n") - assertParseOk("keyword no trailing ws", keyword("SELECT"), "SELECT") - - // VCL-UT specific keywords - assertParseOk("CONSUME keyword", keyword("CONSUME"), "CONSUME") - assertParseOk("AFTER keyword", keyword("AFTER"), "AFTER") - assertParseOk("USE keyword", keyword("USE"), "USE") - assertParseOk("WITH keyword", keyword("WITH"), "WITH") - assertParseOk("SESSION keyword", keyword("SESSION"), "SESSION") - assertParseOk("EFFECTS keyword", keyword("EFFECTS"), "EFFECTS") - assertParseOk("IN keyword", keyword("IN"), "IN") - assertParseOk("TRANSACTION keyword", keyword("TRANSACTION"), "TRANSACTION") - assertParseOk("PROOF keyword", keyword("PROOF"), "PROOF") - assertParseOk("ATTACHED keyword", keyword("ATTACHED"), "ATTACHED") - assertParseOk("USAGE keyword", keyword("USAGE"), "USAGE") - assertParseOk("LIMIT keyword", keyword("LIMIT"), "LIMIT") -} - -// ============================================================================ -// Test: identifier parser -// ============================================================================ - -let testIdentifier = (): unit => { - Js.Console.log("\n--- identifier parser ---") - open TQLParser.Parser - - // Valid identifiers - assertParseValue("simple alpha", identifier, "hello rest", - v => v == "hello") - assertParseValue("alphanumeric", identifier, "col2 rest", - v => v == "col2") - assertParseValue("underscore start", identifier, "_private rest", - v => v == "_private") - assertParseValue("mixed", identifier, "my_table_name rest", - v => v == "my_table_name") - - // Protocol names used as identifiers - assertParseValue("ReadOnlyProtocol", identifier, "ReadOnlyProtocol rest", - v => v == "ReadOnlyProtocol") - assertParseValue("MutationProtocol", identifier, "MutationProtocol rest", - v => v == "MutationProtocol") - assertParseValue("StreamProtocol", identifier, "StreamProtocol rest", - v => v == "StreamProtocol") - assertParseValue("BatchProtocol", identifier, "BatchProtocol rest", - v => v == "BatchProtocol") - - // Effect labels used as identifiers - assertParseValue("Read as id", identifier, "Read rest", v => v == "Read") - assertParseValue("Write as id", identifier, "Write rest", v => v == "Write") - assertParseValue("Cite as id", identifier, "Cite rest", v => v == "Cite") - assertParseValue("Audit as id", identifier, "Audit rest", v => v == "Audit") - assertParseValue("Transform as id", identifier, "Transform rest", v => v == "Transform") - assertParseValue("Federate as id", identifier, "Federate rest", v => v == "Federate") - - // Transaction states as identifiers - assertParseValue("Fresh", identifier, "Fresh rest", v => v == "Fresh") - assertParseValue("Active", identifier, "Active rest", v => v == "Active") - assertParseValue("Committed", identifier, "Committed rest", v => v == "Committed") - assertParseValue("RolledBack", identifier, "RolledBack rest", v => v == "RolledBack") - assertParseValue("ReadSnapshot", identifier, "ReadSnapshot rest", v => v == "ReadSnapshot") - - // Identifier does not match digits - assertParseErr("digit start fails", identifier, "123abc") -} - -// ============================================================================ -// Test: integer parser -// ============================================================================ - -let testInteger = (): unit => { - Js.Console.log("\n--- integer parser ---") - open TQLParser.Parser - - assertParseValue("zero", integer, "0 rest", v => v == 0) - assertParseValue("single digit", integer, "5 rest", v => v == 5) - assertParseValue("multi digit", integer, "42 rest", v => v == 42) - assertParseValue("large number", integer, "123456 rest", v => v == 123456) - assertParseValue("one", integer, "1 USE", v => v == 1) - assertParseValue("hundred", integer, "100 rest", v => v == 100) - - // Should fail on non-numeric input - assertParseErr("alpha fails", integer, "abc") - assertParseErr("empty fails", integer, "") -} - -// ============================================================================ -// Test: stringLiteral parser -// ============================================================================ - -let testStringLiteral = (): unit => { - Js.Console.log("\n--- stringLiteral parser ---") - open TQLParser.Parser - - // Basic double-quoted strings - assertParseValue("simple string", stringLiteral, "\"hello\" rest", - v => v == "hello") - assertParseValue("empty string", stringLiteral, "\"\" rest", - v => v == "") - assertParseValue("string with spaces", stringLiteral, "\"hello world\" rest", - v => v == "hello world") - - // Strings with escape sequences - assertParseValue("escaped quote", stringLiteral, "\"say \\\"hi\\\"\" rest", - v => Js.String2.includes(v, "\\\"")) - - // Non-string input - assertParseErr("non-quoted fails", stringLiteral, "hello") - assertParseErr("single-quoted fails", stringLiteral, "'hello'") -} - -// ============================================================================ -// Test: ws (whitespace) parser -// ============================================================================ - -let testWhitespace = (): unit => { - Js.Console.log("\n--- whitespace parser ---") - open TQLParser.Parser - - assertConsumed("single space", ws, " rest", 1) - assertConsumed("multiple spaces", ws, " rest", 3) - assertConsumed("tab", ws, "\t rest", 1) - assertConsumed("newline", ws, "\n rest", 1) - assertConsumed("mixed whitespace", ws, " \t\n rest", 3) - assertConsumed("no whitespace", ws, "rest", 0) - assertConsumed("empty input", ws, "", 0) -} - -// ============================================================================ -// Test: sepBy combinator -// ============================================================================ - -let testSepBy = (): unit => { - Js.Console.log("\n--- sepBy combinator ---") - open TQLParser.Parser - - // Single element - assertParseValue("single element", sepBy(identifier, keyword(",")), "Read }", - v => Js.Array2.length(v) == 1) - - // Multiple elements - assertParseValue("three elements", sepBy(identifier, keyword(",")), "Read, Write, Cite }", - v => Js.Array2.length(v) == 3) - - // Six elements (all standard effects) - assertParseValue("six elements", - sepBy(identifier, keyword(",")), - "Read, Write, Cite, Audit, Transform, Federate }", - v => Js.Array2.length(v) == 6) -} - -// ============================================================================ -// Test: optional combinator -// ============================================================================ - -let testOptional = (): unit => { - Js.Console.log("\n--- optional combinator ---") - open TQLParser.Parser - - // Present value - assertParseValue("present value", optional(keyword("SELECT")), "SELECT rest", - v => { - switch v { - | Some(_) => true - | None => false - } - }) - - // Absent value (non-matching input) - assertParseValue("absent value", optional(keyword("SELECT")), "INSERT rest", - v => { - switch v { - | Some(_) => false - | None => true - } - }) - - // Absent on empty - assertParseValue("absent on empty", optional(keyword("SELECT")), "", - v => { - switch v { - | Some(_) => false - | None => true - } - }) -} - -// ============================================================================ -// Test: <|> (alternative) combinator -// ============================================================================ - -let testAlternative = (): unit => { - Js.Console.log("\n--- alternative (<|>) combinator ---") - open TQLParser.Parser - - let selectOrInsert = \"<|>"(keyword("SELECT"), keyword("INSERT")) - - assertParseOk("first alternative matches", selectOrInsert, "SELECT rest") - assertParseOk("second alternative matches", selectOrInsert, "INSERT rest") - assertParseErr("neither matches", selectOrInsert, "UPDATE rest") -} - -// ============================================================================ -// Test: Session protocol token recognition -// ============================================================================ - -let testSessionProtocolTokens = (): unit => { - Js.Console.log("\n--- Session protocol tokens ---") - - // Each built-in protocol - let test = (name, input, check) => { - switch TQLParser.parseExtensions(input) { - | Ok(ann) => assert_(name, check(ann.sessionProtocol)) - | Error(_) => assert_(name ++ " (parse failed)", false) - } - } - - test("ReadOnlyProtocol", - "WITH SESSION ReadOnlyProtocol", - p => p == Some(TQLAst.ReadOnlyProtocol)) - test("MutationProtocol", - "WITH SESSION MutationProtocol", - p => p == Some(TQLAst.MutationProtocol)) - test("StreamProtocol", - "WITH SESSION StreamProtocol", - p => p == Some(TQLAst.StreamProtocol)) - test("BatchProtocol", - "WITH SESSION BatchProtocol", - p => p == Some(TQLAst.BatchProtocol)) - test("CustomProtocol", - "WITH SESSION MyCustomThing", - p => { - switch p { - | Some(TQLAst.CustomProtocol(name)) => name == "MyCustomThing" - | _ => false - } - }) -} - -// ============================================================================ -// Test: Effect label token recognition -// ============================================================================ - -let testEffectLabelTokens = (): unit => { - Js.Console.log("\n--- Effect label tokens ---") - - let test = (name, input, expectedCount) => { - switch TQLParser.parseExtensions(input) { - | Ok(ann) => - switch ann.declaredEffects { - | Some({effects}) => assert_(name, Js.Array2.length(effects) == expectedCount) - | None => assert_(name ++ " (no effects)", false) - } - | Error(_) => assert_(name ++ " (parse failed)", false) - } - } - - test("single Read effect", "EFFECTS { Read }", 1) - test("single Write effect", "EFFECTS { Write }", 1) - test("single Cite effect", "EFFECTS { Cite }", 1) - test("single Audit effect", "EFFECTS { Audit }", 1) - test("single Transform effect", "EFFECTS { Transform }", 1) - test("single Federate effect", "EFFECTS { Federate }", 1) - test("custom effect", "EFFECTS { MyEffect }", 1) - test("two effects", "EFFECTS { Read, Write }", 2) - test("all six standard effects", - "EFFECTS { Read, Write, Cite, Audit, Transform, Federate }", 6) -} - -// ============================================================================ -// Test: Transaction state token recognition -// ============================================================================ - -let testTransactionStateTokens = (): unit => { - Js.Console.log("\n--- Transaction state tokens ---") - - let test = (name, input, expected) => { - switch TQLParser.parseExtensions(input) { - | Ok(ann) => - switch ann.modalScope { - | Some({state}) => assert_(name, state == expected) - | None => assert_(name ++ " (no modalScope)", false) - } - | Error(_) => assert_(name ++ " (parse failed)", false) - } - } - - test("Fresh", "IN TRANSACTION Fresh", TQLAst.TxFresh) - test("Active", "IN TRANSACTION Active", TQLAst.TxActive) - test("Committed", "IN TRANSACTION Committed", TQLAst.TxCommitted) - test("RolledBack", "IN TRANSACTION RolledBack", TQLAst.TxRolledBack) - test("ReadSnapshot", "IN TRANSACTION ReadSnapshot", TQLAst.TxReadSnapshot) - - // Custom transaction state - switch TQLParser.parseExtensions("IN TRANSACTION MyCustomState") { - | Ok(ann) => - switch ann.modalScope { - | Some({state: TQLAst.TxCustom(name)}) => assert_("custom state", name == "MyCustomState") - | _ => assert_("custom state (wrong type)", false) - } - | Error(_) => assert_("custom state (parse failed)", false) - } -} - -// ============================================================================ -// Test: Dependent type syntax (CONSUME AFTER n USE) -// ============================================================================ - -let testDependentTypeSyntax = (): unit => { - Js.Console.log("\n--- Dependent type syntax (CONSUME AFTER) ---") - - let test = (name, input, expectedCount) => { - switch TQLParser.parseExtensions(input) { - | Ok(ann) => - switch ann.consumeAfter { - | Some({count}) => assert_(name, count == expectedCount) - | None => assert_(name ++ " (no consumeAfter)", false) - } - | Error(_) => assert_(name ++ " (parse failed)", false) - } - } - - test("CONSUME AFTER 1 USE", "CONSUME AFTER 1 USE", 1) - test("CONSUME AFTER 3 USE", "CONSUME AFTER 3 USE", 3) - test("CONSUME AFTER 10 USE", "CONSUME AFTER 10 USE", 10) - test("CONSUME AFTER 100 USE", "CONSUME AFTER 100 USE", 100) -} - -// ============================================================================ -// Test: Proof carrying syntax (PROOF ATTACHED) -// ============================================================================ - -let testProofCarryingSyntax = (): unit => { - Js.Console.log("\n--- Proof carrying syntax (PROOF ATTACHED) ---") - - // Simple theorem reference - switch TQLParser.parseExtensions("PROOF ATTACHED IntegrityTheorem") { - | Ok(ann) => - switch ann.proofAttached { - | Some({name, params}) => - assert_("theorem name", name == "IntegrityTheorem") - assert_("no params", params == None) - | None => assert_("has proofAttached", false) - } - | Error(_) => assert_("parse PROOF ATTACHED (simple)", false) - } - - // Theorem with parameters - switch TQLParser.parseExtensions(`PROOF ATTACHED FreshnessGuarantee(maxAge="300")`) { - | Ok(ann) => - switch ann.proofAttached { - | Some({name, params}) => - assert_("theorem name with params", name == "FreshnessGuarantee") - assert_("has params", { - switch params { - | Some(p) => Js.Array2.length(p) == 1 - | None => false - } - }) - | None => assert_("has proofAttached (with params)", false) - } - | Error(_) => assert_("parse PROOF ATTACHED (with params)", false) - } -} - -// ============================================================================ -// Test: USAGE LIMIT (quantitative type theory) -// ============================================================================ - -let testUsageLimitSyntax = (): unit => { - Js.Console.log("\n--- USAGE LIMIT syntax ---") - - let test = (name, input, expectedLimit) => { - switch TQLParser.parseExtensions(input) { - | Ok(ann) => - switch ann.usageLimit { - | Some({limit}) => assert_(name, limit == expectedLimit) - | None => assert_(name ++ " (no usageLimit)", false) - } - | Error(_) => assert_(name ++ " (parse failed)", false) - } - } - - test("USAGE LIMIT 1", "USAGE LIMIT 1", 1) - test("USAGE LIMIT 10", "USAGE LIMIT 10", 10) - test("USAGE LIMIT 100", "USAGE LIMIT 100", 100) - test("USAGE LIMIT 1000", "USAGE LIMIT 1000", 1000) -} - -// ============================================================================ -// Test: Error cases -// ============================================================================ - -let testErrorCases = (): unit => { - Js.Console.log("\n--- Error cases ---") - - // Incomplete keywords - assertParseErr("CONSUME without AFTER", - TQLParser.ExtensionParsers.consumeClause, "CONSUME rest") - - assertParseErr("WITH without SESSION", - TQLParser.ExtensionParsers.sessionClause, "WITH rest") - - assertParseErr("IN without TRANSACTION", - TQLParser.ExtensionParsers.modalClause, "IN rest") - - assertParseErr("PROOF without ATTACHED", - TQLParser.ExtensionParsers.proofAttachedClause, "PROOF rest") - - assertParseErr("USAGE without LIMIT", - TQLParser.ExtensionParsers.usageLimitClause, "USAGE rest") - - assertParseErr("EFFECTS without braces", - TQLParser.ExtensionParsers.effectsClause, "EFFECTS Read") - - // Validation errors - assert_("negative CONSUME AFTER rejected", - switch TQLParser.parseAndValidateExtensions("CONSUME AFTER 0 USE") { - | Ok(ann) => - switch ann.consumeAfter { - | Some({count}) => count > 0 // may parse but validation catches it - | None => true - } - | Error(_) => true - }) - - // Misspelled keywords don't match - assertParseErr("SELEC (misspelled) fails", - TQLParser.Parser.keyword("SELECT"), "SELEC rest") - - // Completely wrong input - switch TQLParser.parseExtensions("GOBBLEDYGOOK") { - | Ok(ann) => - // Should parse as empty annotations (all None) since extensions are optional - assert_("garbage yields empty annotations", - ann.consumeAfter == None && - ann.sessionProtocol == None && - ann.declaredEffects == None && - ann.modalScope == None && - ann.proofAttached == None && - ann.usageLimit == None) - | Error(_) => assert_("garbage input handled", true) - } -} - -// ============================================================================ -// Test: Token ordering in combined queries -// ============================================================================ - -let testTokenOrdering = (): unit => { - Js.Console.log("\n--- Token ordering in combined queries ---") - - // Extensions must appear in specific order - let fullQuery = "CONSUME AFTER 1 USE WITH SESSION ReadOnlyProtocol EFFECTS { Read, Write } IN TRANSACTION Active PROOF ATTACHED TestTheorem USAGE LIMIT 50" - switch TQLParser.parseExtensions(fullQuery) { - | Ok(ann) => - assert_("all six clauses present", { - ann.consumeAfter != None && - ann.sessionProtocol != None && - ann.declaredEffects != None && - ann.modalScope != None && - ann.proofAttached != None && - ann.usageLimit != None - }) - | Error(_) => assert_("full combined query parses", false) - } - - // Partial combinations - switch TQLParser.parseExtensions("WITH SESSION BatchProtocol USAGE LIMIT 5") { - | Ok(ann) => - assert_("session + usage only", { - ann.consumeAfter == None && - ann.sessionProtocol != None && - ann.declaredEffects == None && - ann.modalScope == None && - ann.proofAttached == None && - ann.usageLimit != None - }) - | Error(_) => assert_("partial combination parses", false) - } -} - -// ============================================================================ -// Run all tests -// ============================================================================ - -let () = { - Js.Console.log("=== VCL-UT Lexer / Token Unit Tests ===") - testStringCombinator() - testKeywordCombinator() - testIdentifier() - testInteger() - testStringLiteral() - testWhitespace() - testSepBy() - testOptional() - testAlternative() - testSessionProtocolTokens() - testEffectLabelTokens() - testTransactionStateTokens() - testDependentTypeSyntax() - testProofCarryingSyntax() - testUsageLimitSyntax() - testErrorCases() - testTokenOrdering() - Js.Console.log( - "\n=== Results: " ++ - Belt.Int.toString(passed.contents) ++ " passed, " ++ - Belt.Int.toString(failed.contents) ++ " failed, " ++ - Belt.Int.toString(total.contents) ++ " total ===" - ) -} diff --git a/typeql-experimental/test/rescript/TQLParser_test.affine b/typeql-experimental/test/rescript/TQLParser_test.affine new file mode 100644 index 0000000..1d0737e --- /dev/null +++ b/typeql-experimental/test/rescript/TQLParser_test.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 TQLParser_test; + +// TODO: Complete semantic implementation diff --git a/typeql-experimental/test/rescript/TQLParser_test.res b/typeql-experimental/test/rescript/TQLParser_test.res deleted file mode 100644 index 1cdf871..0000000 --- a/typeql-experimental/test/rescript/TQLParser_test.res +++ /dev/null @@ -1,221 +0,0 @@ -// SPDX-License-Identifier: MPL-2.0 -// Copyright (c) 2026 Jonathan D.A. Jewell -// -// TQLParser_test.res — Parser tests for VCL-UT extension clauses -// -// Tests each of the six extension clause parsers individually and -// in combination. - -// ============================================================================ -// Test Utilities -// ============================================================================ - -let assertOk = (name: string, result: Result<'a, 'b>): unit => { - switch result { - | Ok(_) => Js.Console.log("PASS: " ++ name) - | Error(_) => Js.Console.error("FAIL: " ++ name) - } -} - -let assertError = (name: string, result: Result<'a, 'b>): unit => { - switch result { - | Ok(_) => Js.Console.error("FAIL (expected error): " ++ name) - | Error(_) => Js.Console.log("PASS: " ++ name) - } -} - -let assertSome = (name: string, opt: option<'a>): unit => { - switch opt { - | Some(_) => Js.Console.log("PASS: " ++ name) - | None => Js.Console.error("FAIL (expected Some): " ++ name) - } -} - -let assertNone = (name: string, opt: option<'a>): unit => { - switch opt { - | Some(_) => Js.Console.error("FAIL (expected None): " ++ name) - | None => Js.Console.log("PASS: " ++ name) - } -} - -// ============================================================================ -// Test: CONSUME AFTER N USE -// ============================================================================ - -let testConsumeAfter = (): unit => { - Js.Console.log("\n--- CONSUME AFTER tests ---") - - // Parse valid CONSUME AFTER - let r1 = TQLParser.parseExtensions("CONSUME AFTER 1 USE") - assertOk("CONSUME AFTER 1 USE parses", r1) - switch r1 { - | Ok(ann) => assertSome("has consumeAfter", ann.consumeAfter) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions("CONSUME AFTER 3 USE") - assertOk("CONSUME AFTER 3 USE parses", r2) - - // Empty input (no extensions) should parse to empty annotations - let r3 = TQLParser.parseExtensions("") - assertOk("empty input parses", r3) - switch r3 { - | Ok(ann) => assertNone("empty has no consumeAfter", ann.consumeAfter) - | Error(_) => () - } -} - -// ============================================================================ -// Test: WITH SESSION -// ============================================================================ - -let testWithSession = (): unit => { - Js.Console.log("\n--- WITH SESSION tests ---") - - let r1 = TQLParser.parseExtensions("WITH SESSION ReadOnlyProtocol") - assertOk("WITH SESSION ReadOnlyProtocol parses", r1) - switch r1 { - | Ok(ann) => assertSome("has sessionProtocol", ann.sessionProtocol) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions("WITH SESSION MutationProtocol") - assertOk("WITH SESSION MutationProtocol parses", r2) - - let r3 = TQLParser.parseExtensions("WITH SESSION MyCustomProto") - assertOk("WITH SESSION custom protocol parses", r3) -} - -// ============================================================================ -// Test: EFFECTS { ... } -// ============================================================================ - -let testEffects = (): unit => { - Js.Console.log("\n--- EFFECTS tests ---") - - let r1 = TQLParser.parseExtensions("EFFECTS { Read }") - assertOk("EFFECTS { Read } parses", r1) - switch r1 { - | Ok(ann) => assertSome("has declaredEffects", ann.declaredEffects) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions("EFFECTS { Read, Write, Cite }") - assertOk("EFFECTS { Read, Write, Cite } parses", r2) - - let r3 = TQLParser.parseExtensions("EFFECTS { Read, Write, Cite, Audit, Transform, Federate }") - assertOk("all standard effects parse", r3) -} - -// ============================================================================ -// Test: IN TRANSACTION -// ============================================================================ - -let testInTransaction = (): unit => { - Js.Console.log("\n--- IN TRANSACTION tests ---") - - let r1 = TQLParser.parseExtensions("IN TRANSACTION Active") - assertOk("IN TRANSACTION Active parses", r1) - switch r1 { - | Ok(ann) => assertSome("has modalScope", ann.modalScope) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions("IN TRANSACTION Committed") - assertOk("IN TRANSACTION Committed parses", r2) - - let r3 = TQLParser.parseExtensions("IN TRANSACTION ReadSnapshot") - assertOk("IN TRANSACTION ReadSnapshot parses", r3) -} - -// ============================================================================ -// Test: PROOF ATTACHED -// ============================================================================ - -let testProofAttached = (): unit => { - Js.Console.log("\n--- PROOF ATTACHED tests ---") - - let r1 = TQLParser.parseExtensions("PROOF ATTACHED IntegrityTheorem") - assertOk("PROOF ATTACHED IntegrityTheorem parses", r1) - switch r1 { - | Ok(ann) => assertSome("has proofAttached", ann.proofAttached) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions(`PROOF ATTACHED FreshnessGuarantee(maxAge="300")`) - assertOk("PROOF ATTACHED with params parses", r2) -} - -// ============================================================================ -// Test: USAGE LIMIT -// ============================================================================ - -let testUsageLimit = (): unit => { - Js.Console.log("\n--- USAGE LIMIT tests ---") - - let r1 = TQLParser.parseExtensions("USAGE LIMIT 100") - assertOk("USAGE LIMIT 100 parses", r1) - switch r1 { - | Ok(ann) => assertSome("has usageLimit", ann.usageLimit) - | Error(_) => () - } - - let r2 = TQLParser.parseExtensions("USAGE LIMIT 1") - assertOk("USAGE LIMIT 1 parses", r2) -} - -// ============================================================================ -// Test: Combined (all 6 extensions) -// ============================================================================ - -let testCombined = (): unit => { - Js.Console.log("\n--- Combined tests ---") - - let input = "CONSUME AFTER 1 USE WITH SESSION ReadOnlyProtocol EFFECTS { Read, Cite } IN TRANSACTION Committed PROOF ATTACHED IntegrityTheorem USAGE LIMIT 100" - let r1 = TQLParser.parseExtensions(input) - assertOk("maximal query parses", r1) - switch r1 { - | Ok(ann) => { - assertSome("has consumeAfter", ann.consumeAfter) - assertSome("has sessionProtocol", ann.sessionProtocol) - assertSome("has declaredEffects", ann.declaredEffects) - assertSome("has modalScope", ann.modalScope) - assertSome("has proofAttached", ann.proofAttached) - assertSome("has usageLimit", ann.usageLimit) - } - | Error(_) => () - } -} - -// ============================================================================ -// Test: Validation -// ============================================================================ - -let testValidation = (): unit => { - Js.Console.log("\n--- Validation tests ---") - - // Valid: CONSUME AFTER 1 USE with USAGE LIMIT 100 - let r1 = TQLParser.parseAndValidateExtensions("CONSUME AFTER 1 USE USAGE LIMIT 100") - assertOk("CONSUME 1 + USAGE 100 validates", r1) - - // Invalid: USAGE LIMIT < CONSUME AFTER - let r2 = TQLParser.parseAndValidateExtensions("CONSUME AFTER 10 USE USAGE LIMIT 5") - assertError("CONSUME 10 + USAGE 5 fails validation", r2) -} - -// ============================================================================ -// Run all tests -// ============================================================================ - -let () = { - Js.Console.log("=== VCL-UT Parser Tests ===") - testConsumeAfter() - testWithSession() - testEffects() - testInTransaction() - testProofAttached() - testUsageLimit() - testCombined() - testValidation() - Js.Console.log("\n=== Tests complete ===") -}