Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,11 @@ jobs:
- name: Build
run: swift build
- name: Test
run: swift test
run: swift test

- name: Emit IR v2 from examples
run: |
.build/debug/hypercode emit Examples/service.hc --hcs Examples/service.hcs --format json > /tmp/ir-dev.json
.build/debug/hypercode emit Examples/service.hc --hcs Examples/service.hcs --ctx env=production --format json > /tmp/ir-prod.json
- name: Validate IR v2 against schema
run: npm exec --yes --package=ajv-cli@5 -- ajv validate --spec=draft2020 -s Schema/hypercode-ir-v2.schema.json -d /tmp/ir-dev.json -d /tmp/ir-prod.json
79 changes: 74 additions & 5 deletions DOCS/Workplan.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ PR-1 substrate ──▶ PR-2 HC-112 IR v2 ──▶ PR-3 HC-110 explain
──▶ PR-4 HC-111 contracts (also needs PR-1+PR-2)
```

## PR-1 — Substrate (`feat/hc-112-substrate`)
## PR-1 — Substrate (`feat/hc-112-substrate`, [#19](https://github.com/0al-spec/Hypercode/pull/19) open)

All three features share these foundations; merges first with zero user-visible change.

Expand Down Expand Up @@ -86,7 +86,7 @@ Cascade semantics are **unchanged** — only retention of losing matches is adde

---

## PR-2 — HC-112 IR v2 (`feat/hc-112-ir-v2`)
## PR-2 — HC-112 IR v2 (`feat/hc-112-ir-v2`, [#20](https://github.com/0al-spec/Hypercode/pull/20) open)

Depends on PR-1.

Expand Down Expand Up @@ -138,7 +138,7 @@ validates `Examples/` IR output against `Schema/hypercode-ir-v2.schema.json`.

---

## PR-3 — HC-110 `hypercode explain` (`feat/hc-110-explain`)
## PR-3 — HC-110 `hypercode explain` (`feat/hc-110-explain`, [#21](https://github.com/0al-spec/Hypercode/pull/21) open)

Depends on PR-1 and PR-2.

Expand Down Expand Up @@ -178,7 +178,7 @@ Golden-file tests against `Examples/service.{hc,hcs}` for:

---

## PR-4 — HC-111 Monotonic contracts (`feat/hc-111-contracts`)
## PR-4 — HC-111 Monotonic contracts (`feat/hc-111-contracts`, [#22](https://github.com/0al-spec/Hypercode/pull/22) open)

Depends on PR-1, PR-2, and PR-3.

Expand Down Expand Up @@ -239,7 +239,7 @@ Update `EBNF/Hypercode_Syntax.md` with `.hcs` contract block grammar.
| File | PR-1 | PR-2 | PR-3 | PR-4 |
|---|---|---|---|---|
| `Sources/Hypercode/HCS/CascadeSheet.swift` | TypedValue, Match, Rule.file | — | — | Contract types |
| `Sources/Hypercode/HCS/Resolver.swift` | Provenance.file, losers retained | — | — | contract eval |
| `Sources/Hypercode/HCS/Resolver.swift` | Provenance.file, losers retained | — | — | — (value validation → PR-5, see R9) |
| `Sources/Hypercode/HCS/CascadeSheetReader.swift` | parseProperty types, read(file:) | — | — | @contract: block |
| `Sources/Hypercode/Emit/Emitter.swift` | TypedValue render | v2 emitter | — | contracts in v2 |
| `Sources/HypercodeCLI/main.swift` | pass file to reader | --ir-version flag | explain subcommand | — |
Expand All @@ -250,3 +250,72 @@ Update `EBNF/Hypercode_Syntax.md` with `.hcs` contract block grammar.
| `Tests/ContractTests.swift` | — | — | — | new |
| `RFC/Hypercode.md` | — | — | — | v0.2 bump |
| `EBNF/Hypercode_Syntax.md` | — | — | — | @contract syntax |

---

## Review follow-ups (strict review 2026-06-11, PR-1..PR-4)

All findings reproduced against `feat/hc-111-contracts` (69a38f0). R1–R8 block merging
[#22](https://github.com/0al-spec/Hypercode/pull/22); R9–R12 need an owner decision first.

### A — Blocking #22

- ✅ **R1 — IR v2 violates its own schema: child nodes have no `hash`.**
`Emitter.intermediateV2` post-inserts `hash` only into root forest nodes; the schema
requires it on every `resolvedNode`. Fix: compute/pass the hash inside `nodeV2`
recursion instead of the zip-insert. Add a nested-document schema-shape test.
- ✅ **R2 — emitter crash on large numerals.** `Emitter.json` `.double` whole-number
branch does `String(Int(number))`; a 26-digit `.hcs` value parses as `Double(1e26)`
and traps (`exit 133`). Render without `Int` conversion; add a regression test.
YAML path is unaffected.
- ✅ **R3 — ContractValidator false positive on disjoint selectors.** Pairs are compared
purely by specificity; `Service { timeout <= 100 }` vs `.slow { timeout <= 500 }`
errors (HC2102) even when no node matches both. Gate the pairwise check on
"∃ node in forest matched by both selectors" — `Validator.validate(_:against:)`
already has the forest.
- ✅ **R4 — equal-specificity contract conflicts pass silently.** Two `Service:` blocks
with `timeout: int` vs `timeout: float` produce no diagnostic (guard skips
`specificity ==`). At minimum flag type conflicts at equal specificity.
- ✅ **R5 — Foundation leak in core.** `Explainer.renderMatch` uses
`padding(toLength:withPad:startingAt:)` (Foundation/NSString) with no import in file —
compiles only via Swift 5 leaky member lookup; breaks under `MemberImportVisibility`.
Hand-roll the padding; core stays Foundation-free.
- ✅ **R6 — compiler warning.** `Explainer.swift:113` `var line1` never mutated → `let`.
- ✅ **R7 — test framework consistency.** `ContractTests.swift` uses Swift Testing;
the other 14 test files (incl. SHA256/Explain/Emitter tests from this same chain)
use XCTest. Convert to XCTest (or record a deliberate migration decision).
- ✅ **R8 — undelivered plan items.** Root `workplan.md` M8: HC-110/111/112 still ⬜
(mark only on merge); `RFC/Hypercode.md` not bumped (PR-4 promised v0.2; §Limitations
"untyped strings in IR v1" needs a v2 note); IR `contracts[]` not sorted by ascending
specificity as specified; `Package.swift` `0.5.0-dev` version comment missing (PR-1);
CI `ajv` schema-validation step missing (PR-2); EBNF v0.2: `<scalar>` wrongly requires
quoted strings (bare `driver: sqlite` is valid), header `Date:` stale, and the HC21xx
semantics table belongs in `Hypercode_Resolution.md`.

### B — Decided 2026-06-11, pending implementation

- ⬜ **R9 — contract value validation is not implemented.** Values are never checked
against contracts (`timeout: 999` under `int <= 300` passes; wrong-type values pass).
**Decision:** separate **PR-5** — value validation against contracts with a new
**HC2104** diagnostic (type mismatch, bounds violation, missing required property).
#22 stays scoped to grammar + monotonicity; files table fixed (`Resolver` row moved
to PR-5).
- ✅ **R10 — v1 emitter is now lossy for numeric-looking strings.** `version: 1.10` →
`"1.1"`, `build: 0123` → `"123"` under `--ir-version 1` (regression vs pre-PR-1 raw
strings). **Decision:** store the source lexeme alongside the typed value; v1 emits
the lexeme byte-for-byte, v2 keeps typed values. Implement in the open chain before
merge.
- ✅ **R11 — D4 deviation.** SHA-256 shipped as a CryptoKit wrapper, not the approved
vendored pure-Swift implementation; core is now Apple-only (no Linux).
**Decision:** switch to `swift-crypto` (same API surface, Linux-capable); accept it
as the project's second dependency after SpecificationCore. D4 row updated.
- ✅ **R12 — absent-bound semantics underspecified.** A more-specific contract that
omits `min`/`max` is not flagged as widening. **Decision:** omitted bound = inherited
via interval intersection — the effective contract for a node is the intersection of
all applicable contracts. Current validator behavior is correct; fix is to specify
this normatively in the RFC contracts section (bundle with the R8 RFC update).
- ✅ **R13 — public API construction gaps.** `Match`, `PropertyTrace`, `NodeTrace` have
no public inits, so external consumers cannot build `ResolvedValue`; `ResolvedValue`
duplicates winner data with no invariant. **Decision:** add explicit `public init` to
all three (project practice) and add `ResolvedValue.init(winner:losers:)` deriving
`value`/`provenance` from `winner` so inconsistent construction is impossible.
73 changes: 67 additions & 6 deletions EBNF/Hypercode_Resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

**Status:** Draft

**Version:** 0.1
**Version:** 0.2

**Date:** June 2, 2026
**Date:** June 11, 2026

**Author:** Egor Merkushev

Expand Down Expand Up @@ -101,7 +101,65 @@ ctx ⊢ n ⇓ ⟨ n.type, n.class, n.id,

The whole document resolves by applying this to each top-level node.

## 7. Conformance
## 7. Contracts (HC-111)

A `@contract:` block (syntax: [Hypercode_Syntax.md §6.3](Hypercode_Syntax.md))
attaches property constraints to selectors. Values cascade (last sufficiently
specific writer wins); contracts **accumulate** — every applicable contract
governs the node simultaneously.

### 7.1 Effective contract — intersection

For a node `n` and property key `k`, the effective contract is the
**intersection** of all contracts whose selector matches `n` and that
constrain `k`:

```text
applicable(n, k) = { c ∈ contracts | match(c.selector, n) ∧ k ∈ c.properties }

effective(n, k).type = the common type of all applicable (must agree)
effective(n, k).min = max over declared lower bounds
effective(n, k).max = min over declared upper bounds
effective(n, k).required = true if any applicable contract requires k
```

An **omitted bound is not a statement** — it inherits through the
intersection. A more specific contract that re-declares `k` without `min`
keeps the inherited lower bound; it does not lift it.

### 7.2 Monotonicity validation

Specificity relates two contracts only when they can govern the same node —
exactly as in the CSS cascade. The validator therefore checks a pair of
contracts only if **at least one node in the document matches both
selectors**. For such a pair where `spec(A) < spec(B)`:

| Violation | Code | Description |
|-----------|------|-------------|
| Type changed | HC2101 | `B` declares a different type than `A` for the same key |
| Interval widened | HC2102 | `B` lowers a declared `min` or raises a declared `max` of `A` |
| Required → optional | HC2103 | `B` marks `[?]` a key that `A` requires |

At **equal specificity** both contracts apply with equal force; a type
conflict makes the intersection unsatisfiable and is reported as HC2101.
Bounds at equal specificity simply intersect and are not a conflict.

All three are `error`-severity diagnostics; `hypercode validate` exits
non-zero.

### 7.3 Value validation (planned)

Checking the resolved values themselves against the effective contract
(type conformance, bounds, required presence) is diagnostic **HC2104**,
scheduled as PR-5 in [DOCS/Workplan.md](../DOCS/Workplan.md).

### 7.4 IR

IR v2 echoes the applicable contracts per property, sorted by ascending
specificity (declaration order as tie-breaker), so a consumer can re-derive
the effective contract without re-parsing the sheet.

## 8. Conformance

The reference fixtures are [`Examples/service.hc`](../Examples/service.hc)
and [`service.hcs`](../Examples/service.hcs), resolved in
Expand All @@ -114,12 +172,15 @@ and provenance. Any conforming resolver must reproduce those results, e.g.:
hypercode resolve Examples/service.hc --hcs Examples/service.hcs --ctx env=production
```

## 8. Deferred
## 9. Deferred

- **Origin / importance.** RFC §4.2.3 lists origin/importance above specificity
in the precedence order. There is no syntax for it yet (no `!important`,
no override-file origin), so the current precedence key is
`(specificity, source-order)`. When syntax is introduced, it becomes the
most-significant component of `precedence`.
- **Typed scalars.** Property values are currently raw strings (quotes stripped).
Typed scalars (bool/int/…) arrive with real YAML input, if ever needed.
- **Contract value validation.** §7.3 — HC2104, planned as PR-5.

*(Typed scalars, previously deferred, landed with IR v2: bare scalars are
type-inferred at parse time, with the source lexeme preserved for v1
round-tripping.)*
97 changes: 88 additions & 9 deletions EBNF/Hypercode_Syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

**Status:** Draft

**Version:** 0.1
**Version:** 0.2

**Date:** July 12, 2025
**Date:** June 11, 2026

**Author:** Egor Merkushev

Expand Down Expand Up @@ -108,20 +108,99 @@ Root
- Indentation is significant (off-side rule): a nested `<block>` must be indented deeper than its parent `<command-line>`. The lexer reads each line's leading `<indent>`, tracks it on an indentation stack, and emits the synthetic `<INDENT>` / `<DEDENT>` tokens when the depth increases or decreases. Because this context-sensitive relationship cannot be expressed in pure BNF, indentation handling is delegated to the lexer (see `HypercodeLexer.g4`).
- No support for inline attributes or arguments in `.hc` files (these belong in `.hcs`).

## 6. Future Work
## 6. `.hcs` Cascade Sheet Syntax

A `.hcs` file contains cascade rules and optional contract blocks.

### 6.1 Cascade Rule

```
<sheet> ::= { <top-level-block> }
<top-level-block> ::= <dimension-block> | <contract-block> | <rule-block>

<dimension-block> ::= "@" <identifier> "[" <value> "]" ":" <newline>
<INDENT> { <rule-block> } <DEDENT>
<value> ::= <identifier>

<rule-block> ::= <selector> ":" <newline>
<INDENT> { <property-line> } <DEDENT>
<property-line> ::= <identifier> ":" <scalar> <newline>
<scalar> ::= <quoted-string> | <bare-scalar>
<quoted-string> ::= '"' { <char> } '"' | "'" { <char> } "'"
<bare-scalar> ::= any run of characters up to end of line (trimmed)
```

A bare scalar is type-inferred by the reader: `true`/`false` → bool, integral
form → int, decimal form without letters → double, anything else → string
(e.g. `driver: sqlite` is a valid bare string). Quoting forces string. The
source lexeme is preserved verbatim for v1 IR round-tripping.

### 6.2 Selectors

```
<selector> ::= <simple-selector> { ">" <simple-selector> }
<simple-selector> ::= <type-sel> | <class-sel> | <id-sel>
<type-sel> ::= <identifier>
<class-sel> ::= "." <identifier>
<id-sel> ::= "#" <identifier>
```

Specificity (highest to lowest): id `#x` > class `.x` > type `x`.

### 6.3 `@contract:` Block (HC-111)

A `@contract:` block declares property constraints for nodes matching a selector.
More-specific selectors may only **narrow** constraints — never widen them (monotonicity invariant).

```
<contract-block> ::= "@contract:" <newline>
<INDENT> { <contract-selector> } <DEDENT>

<contract-selector> ::= <selector> ":" <newline>
<INDENT> { <constraint-line> } <DEDENT>

<constraint-line> ::= <constraint-key> ":" <constraint-type>
[ ">=" <number> ] [ "<=" <number> ] <newline>

<constraint-key> ::= <identifier> [ "[?]" ] // "[?]" marks optional property
<constraint-type> ::= "string" | "int" | "float" | "bool"
```

#### Constraint syntax example

```hcs
@contract:
service:
timeout[?]: int >= 1 <= 300
name: string
.primary:
timeout: int >= 10 <= 200 # narrows — allowed
```

#### Semantics

How contracts accumulate, intersect, and which monotonicity violations are
diagnostics (HC2101–HC2103) is defined in the
[resolution semantics](Hypercode_Resolution.md) — this document covers syntax
only.

## 7. Future Work

- Define EBNF with optional comments, arguments, and macro support.
- Add parser conformance test suite.
- Define formal AST schema (YAML or JSON).

## 7. Change Log
## 8. Change Log

**Version 0.2** (2026-06-11)

* Added Section 6: `.hcs` cascade sheet syntax (rules, selectors, dimension blocks).
* Added `@contract:` block grammar (HC-111): constraint-line syntax, `[?]` optional marker, bounds `>=`/`<=`.
* Bare scalars documented (type inference, lexeme preservation); semantics
(contract accumulation, monotonicity diagnostics) live in
`Hypercode_Resolution.md`.

**Version 0.1** (2025-07-12)

* Initial public draft of the Hypercode grammar in BNF.
* Describes core structural elements: command, class, ID, indentation-based hierarchy.
* Includes:
- BNF grammar for `.hc` files
- Visual AST example
- Positive and negative test cases
- Notes on scope and grammar limitations
18 changes: 18 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading