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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
- package-ecosystem: pip
directory: /
schedule:
interval: weekly
51 changes: 51 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

jobs:
conformance-data:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v7

- name: Set up Python
uses: actions/setup-python@v7
with:
python-version: "3.14"
cache: pip
cache-dependency-path: requirements-dev.txt

- name: Install validation tools
run: python -m pip install --requirement requirements-dev.txt

- name: Validate fixture layout
shell: bash
run: |
set -euo pipefail
for case in conformance/valid/*; do
test -d "$case"
test -f "$case/source.stack"
test -f "$case/expected.ir.json"
done
for case in conformance/invalid/*; do
test -d "$case"
test -f "$case/source.stack"
test -f "$case/expected.diagnostics.json"
done

- name: Validate JSON Schemas
run: check-jsonschema --check-metaschema schemas/*.json

- name: Validate normalized IR fixtures
run: check-jsonschema --schemafile schemas/normalized-ir.schema.json conformance/valid/*/expected.ir.json

- name: Validate diagnostic fixtures
run: find conformance -name expected.diagnostics.json -print0 | xargs -0 check-jsonschema --schemafile schemas/diagnostic-expectations.schema.json
185 changes: 185 additions & 0 deletions INTERCHANGE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# Stack Compiler Interchange Specification

## Status

This document is a normative part of the draft Stack 1.0 specification. It defines portable JSON representations for normalized compiler output, diagnostics, and canonical conformance expectations.

The JSON Schemas in [`schemas/`](./schemas) are normative for document shape. This document is normative for field meaning, ordering, and processing behavior.

## 1. Scope

The compiler interchange boundary follows language processing stages 1 through 4:

```text
UTF-8 source
-> tokenization and parsing
-> identifier and default resolution
-> semantic and complexity validation
-> normalized diagram IR
```

Theme and icon resolution, layout solving, and rendering occur after this boundary. They MUST NOT add fields to normalized compiler IR.

A native compiler API MAY expose implementation-specific types. An implementation claiming portable conformance MUST be able to map its successful output and diagnostics to the JSON contracts defined here.

## 2. Interchange Version

Every interchange document contains `schemaVersion`. Stack 1.0 defines schema version `1.0`.

The schema version is independent of the Stack language version:

- `languageVersion` identifies the source grammar and semantics;
- `schemaVersion` identifies the portable JSON representation.

Adding an optional field is a schema minor change. Removing a field, changing a field meaning, changing an enum meaning, or making an optional field required is a schema major change.

Consumers MUST reject unsupported schema major versions. They MUST NOT silently ignore unknown fields because an unknown field may carry required meaning.

## 3. Normalized Diagram IR

A compiler produces normalized IR only when stages 1 through 4 contain no error diagnostics. Warning diagnostics may accompany successful IR.

The normalized document conforms to [`normalized-ir.schema.json`](./schemas/normalized-ir.schema.json).

### 3.1 Normalization Rules

Normalized IR MUST:

- include the declared language version;
- apply `default` when no theme is authored;
- apply `service` when no node kind is authored;
- apply `flow` when no edge kind is authored;
- preserve node, group, edge, child, same-rank, and order list declaration order;
- represent absent optional values as JSON `null`;
- represent collections as arrays, including when empty;
- use source identifiers without case conversion;
- contain decoded string values rather than source escapes.

Normalized IR MUST NOT contain:

- source locations, comments, or formatting trivia;
- unresolved or partial declarations from invalid source;
- theme or icon catalog resolution results;
- coordinates, dimensions, edge paths, colors, typography, or SVG;
- implementation-specific filesystem, network, memory, or object handles.

JSON object member order has no meaning. Array order is normative.

### 3.2 Diagram Fields

| Field | Meaning |
| --- | --- |
| `schemaVersion` | Compiler interchange version; `1.0` for this document |
| `languageVersion` | Major and minor version declared by the source |
| `title` | Decoded visible diagram title |
| `themeId` | Authored theme identifier or `default` |
| `children` | Direct diagram children in declaration order |
| `nodes` | Every node in depth-first declaration order |
| `groups` | Every group in depth-first declaration order |
| `edges` | Diagram edges in declaration order |
| `layout` | Normalized diagram layout or `null` |

Each child reference contains `type` and `id`. The type is `node` or `group`. Although Stack identifiers are globally unique, the explicit type prevents consumers from inferring element category from another array.

### 3.3 Nodes and Groups

A node contains its effective semantic kind and nearest containing group. `parentGroupId` is `null` for a root node. `iconId` and `detail` remain `null` when omitted; icon fallback and visual detail treatment are downstream concerns.

A group contains its nearest containing group, direct children, and group-scoped layout. `parentGroupId` is `null` for a root group. Group entries use depth-first declaration order: a parent precedes all descendants.

Containment is intentionally represented in both directions:

- `children` supports ordered traversal of one scope;
- `parentGroupId` supports direct lookup from a node or group.

Both representations MUST agree.

### 3.4 Edges

`from` and `to` preserve the authored left and right endpoint order. `direction` is one of:

- `forward` for `->`;
- `bidirectional` for `<->`;
- `association` for `--`.

Bidirectional and association edges preserve authored endpoint order even though duplicate-edge validation treats their endpoint order as equivalent.

### 3.5 Layout

Layout contains:

- `direction`: `right`, `down`, or `null`;
- `sameRanks`: same-rank identifier lists in declaration order;
- `order`: the authored order list or `null`.

The identifiers remain scoped to the direct children of the diagram or group that owns the layout. Normalized IR does not contract edges or solve positions.

## 4. Diagnostic Interchange

A portable diagnostic conforms to [`diagnostic.schema.json`](./schemas/diagnostic.schema.json).

### 4.1 Positions and Ranges

A position contains:

- `byteOffset`: zero-based UTF-8 byte offset in the original source;
- `line`: one-based line number;
- `column`: one-based Unicode scalar column.

A range contains an inclusive `start` position and an exclusive `end` position. A point diagnostic has identical start and end positions. LF and CRLF each advance the line once; byte offsets still count their original encoded bytes.

### 4.2 Fields

`code` and its normative meaning are assigned by the language specification. `severity` is `error` or `warning`. `message` is concise human-readable text but its exact wording is not a compatibility guarantee.

`help` is either corrective guidance or `null`. `related` is always an array and identifies other source ranges involved in the diagnostic. Related-information message wording is not a compatibility guarantee.

Implementations may emit non-`STK` diagnostics. Canonical fixtures only require portable `STK` diagnostics unless a case explicitly documents an implementation extension.

## 5. Canonical Conformance Suite

The canonical suite lives in [`conformance/`](./conformance). Each case is one directory named with a lowercase ASCII identifier.

### 5.1 Valid Cases

A valid case contains:

```text
conformance/valid/<case-id>/source.stack
conformance/valid/<case-id>/expected.ir.json
conformance/valid/<case-id>/expected.diagnostics.json # optional
```

The source MUST compile to normalized IR semantically equal to `expected.ir.json`. If `expected.diagnostics.json` is absent, no portable diagnostics are expected. When present, it normally contains warning expectations.

### 5.2 Invalid Cases

An invalid case contains:

```text
conformance/invalid/<case-id>/source.stack
conformance/invalid/<case-id>/expected.diagnostics.json
```

The source MUST NOT produce normalized IR. Its portable diagnostics MUST match the expectation document.

### 5.3 Diagnostic Expectations

Expectation documents conform to [`diagnostic-expectations.schema.json`](./schemas/diagnostic-expectations.schema.json). Each expected diagnostic requires code, severity, and range.

Runners compare diagnostics in the deterministic order emitted by the compiler. They MUST compare the number of diagnostics and MUST NOT ignore additional portable diagnostics. They do not compare message, help, or related information.

### 5.4 Runner Behavior

A conforming runner MUST:

1. discover case directories in bytewise identifier order;
2. read `source.stack` as bytes so encoding fixtures remain possible;
3. compile with catalog, layout, and renderer stages disabled;
4. validate successful normalized output against the normalized IR schema;
5. validate expectation documents against their schemas;
6. compare JSON values semantically rather than by object-member order or whitespace;
7. fail a case when required files are missing or unexpected files use reserved names;
8. report the case identifier and mismatch location.

An implementation MUST record the specification release or commit revision used for its conformance run. Passing an older suite does not claim support for a newer specification revision.
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ The language is currently a proposal for Stack 1.0. No compatibility guarantee a
## Documents

- [Language specification](./SPECIFICATION.md)
- [Compiler interchange specification](./INTERCHANGE.md)
- [ADR-0001: Adopt a constrained declarative topology language](./docs/decisions/0001-constrained-declarative-language.md)
- [ADR-0002: Make the canonical theme catalog own icons](./docs/decisions/0002-theme-owned-icons.md)
- [ADR-0003: Standardize compiler interchange and conformance fixtures](./docs/decisions/0003-standardize-compiler-interchange-and-conformance.md)
- [Examples](./examples)
- [Conformance suite](./conformance)

## Example

Expand Down Expand Up @@ -49,6 +52,17 @@ diagram "Checkout" {

Stack sources conventionally use the `.stack` extension.

## Validation

Install the development requirements and validate the portable schemas and conformance data:

```sh
python -m pip install --requirement requirements-dev.txt
check-jsonschema --check-metaschema schemas/*.json
check-jsonschema --schemafile schemas/normalized-ir.schema.json conformance/valid/*/expected.ir.json
find conformance -name expected.diagnostics.json -print0 | xargs -0 check-jsonschema --schemafile schemas/diagnostic-expectations.schema.json
```

## Design Principles

- Describe architecture, not pixels.
Expand Down
8 changes: 7 additions & 1 deletion SPECIFICATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,7 @@ Example diagnostic shape:
}
```

The JSON shape is illustrative in Stack 1.0; parser APIs may expose diagnostics in another representation. Assigned codes and their meanings are normative.
The portable JSON representation is defined by the [Stack Compiler Interchange Specification](./INTERCHANGE.md). Native compiler APIs may expose diagnostics in another representation, but conformance adapters MUST preserve the portable field meanings. Assigned codes and their meanings are normative.

### 11.3 Recovery

Expand Down Expand Up @@ -577,6 +577,12 @@ At minimum, validators MUST distinguish:

Implementations should collect independent semantic errors in one pass rather than stopping after the first error.

### 11.5 Normalized IR and Conformance

A document that completes processing stages 1 through 4 without errors produces normalized, renderer-independent diagram IR. Normalization applies language defaults and makes containment, semantic kinds, edge directionality, and layout input explicit. It does not resolve themes or icons, solve layout, or contain renderer state.

The normative portable IR, diagnostic interchange, and conformance fixture contracts are defined in the [Stack Compiler Interchange Specification](./INTERCHANGE.md) and the JSON Schemas in [`schemas/`](./schemas). Canonical fixture data belongs to this specification repository. Implementations consume that data and record the specification release or revision they support.

## 12. Versioning and Backwards Compatibility

### 12.1 Language Version
Expand Down
32 changes: 32 additions & 0 deletions conformance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Stack Conformance Suite

This directory contains implementation-independent compiler conformance cases for the Stack language.

## Layout

Each case is a directory with a lowercase ASCII identifier:

```text
valid/<case-id>/source.stack
valid/<case-id>/expected.ir.json
valid/<case-id>/expected.diagnostics.json # optional

invalid/<case-id>/source.stack
invalid/<case-id>/expected.diagnostics.json
```

`source.stack` must be read as bytes. This permits future encoding-error fixtures even though valid Stack documents are UTF-8.

Expected IR documents conform to [`normalized-ir.schema.json`](../schemas/normalized-ir.schema.json). Diagnostic expectation documents conform to [`diagnostic-expectations.schema.json`](../schemas/diagnostic-expectations.schema.json).

## Comparison

- JSON values are compared semantically; formatting and object-member order do not matter.
- Array order is significant.
- A valid case must produce the expected normalized IR.
- An absent valid-case diagnostic file means no portable diagnostics are expected.
- An invalid case must not produce normalized IR.
- Diagnostic expectations compare code, severity, and range in emitted order.
- Diagnostic message, help, and related-information wording are not compared.

The complete normative behavior is defined in the [Stack Compiler Interchange Specification](../INTERCHANGE.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schemaVersion": "1.0",
"diagnostics": [
{
"code": "STK3003",
"severity": "error",
"range": {
"start": {
"byteOffset": 71,
"line": 5,
"column": 15
},
"end": {
"byteOffset": 78,
"line": 5,
"column": 22
}
}
}
]
}
6 changes: 6 additions & 0 deletions conformance/invalid/unknown-edge-endpoint/source.stack
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
stack 1.0

diagram "Unknown endpoint" {
node api "API"
edge api -> missing
}
Loading