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
17 changes: 17 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,20 @@ jobs:
- run: specs/check.sh --check
- name: VERSION is a spec version
run: grep -Eqx '[0-9]+\.[0-9]+' VERSION

# The comline-catalog crate (rust/): its tests, its minimum Rust, and a
# no_std build. specs/check.sh above keeps its generated data current.
rust:
name: rust crate
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install stable 1.82.0 --profile minimal --component clippy
- run: rustup target add wasm32-unknown-unknown --toolchain stable
- run: cargo +stable test
- run: cargo +stable clippy --all-targets -- -D warnings
- run: cargo +1.82.0 check
- run: cargo +stable build --target wasm32-unknown-unknown
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ program has to agree on: the guarantees, the facts and the values each may
take, the official encodings with their aliases, and the wire features, with
the sets they form. The profiles' facts and `features.json` come from it, and
[`catalog/gen/catalog.json`](catalog/gen/catalog.json) holds it as JSON for
readers without CUE. See [the proposal](https://github.com/ComlineProject/docs/pull/162)
readers without CUE, and the `comline-catalog` crate ([`rust/`](rust)) as
Rust constants. See [the proposal](https://github.com/ComlineProject/docs/pull/162)
and [core#219](https://github.com/ComlineProject/core/issues/219) for what
will read it.

Expand Down
15 changes: 11 additions & 4 deletions catalog/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,15 @@ What an entry may say, and the rules across entries, are in
their values from here, so a profile is held to the catalog's domains.
- [`features.json`](../features.json) is generated from `features`.
- [`gen/catalog.json`](gen/catalog.json) is the whole catalog as JSON, for
readers without CUE: the compiler, the runtimes, generators, the language
server, the Observatory and the docs.
readers without CUE: the Observatory, the docs, generators in other
languages.
- The **`comline-catalog`** Rust crate ([`rust/`](../rust)) holds it as
constants and tables, `no_std` and without dependencies: core, the runtime
and the language server depend on it by git rev. Its types and lookups are
written by hand in `rust/src/lib.rs`; its data, `rust/src/catalog.rs`, is
generated by [`rust.cue`](rust.cue). An id's constant is its name in upper
case, with anything but letters and digits as `_`: `packed@1` is
`encoding::PACKED_1`.
- `specs/check.sh` checks that `profiles/` holds exactly the encodings with a
profile, each of its encoding's kind, and `vectors/` exactly the features.

Expand All @@ -43,8 +50,8 @@ A third party's encoding isn't an entry here: its package declares it in
## Changing it

Edit the `.cue` files, then run `specs/check.sh` to regenerate
`features.json` and `gen/catalog.json`. CI runs `specs/check.sh --check`,
which fails when either is stale.
`features.json`, `gen/catalog.json` and `rust/src/catalog.rs`. CI runs
`specs/check.sh --check`, which fails when any of them is stale.

Order matters where it shows: facts are listed in the order profiles list
them, and features in the order `features.json` does.
185 changes: 185 additions & 0 deletions catalog/rust.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
package catalog

import (
"list"
"regexp"
"strings"
)

// `rust/src/catalog.rs`: the catalog as Rust data, for the `comline-catalog`
// crate. `rust/src/lib.rs` defines the types it fills. Strings are raw
// literals, so no text may hold `"#`.

_rust: {
// A Rust raw string literal.
str: {
#in: string
out: "r#\"\(#in)\"#"
}
opt: {
#in: string | *null
let v = #in
out: [if v != null {"Some(\((str & {#in: v}).out))"}, "None"][0]
}
list: {
#in: [...string]
out: "&[" + strings.Join([for s in #in {(str & {#in: s}).out}], ", ") + "]"
}
// A constant's name: `packed@1` -> `PACKED_1`.
const: {
#in: string
out: strings.ToUpper(regexp.ReplaceAll("[^a-zA-Z0-9]+", #in, "_"))
}
kind: {
#in: "format" | "framing" | "handshake"
out: {format: "Kind::Format", framing: "Kind::Framing", handshake: "Kind::Handshake"}[#in]
}
status: {
#in: "stable" | "experimental" | "deprecated"
out: {stable: "Status::Stable", experimental: "Status::Experimental", deprecated: "Status::Deprecated"}[#in]
}
lifecycle: {
#in: {...}
let e = #in
out: """
status: \((status & {#in: e.status}).out),
replaced_by: \((opt & {#in: [if e.replaced_by != _|_ {e.replaced_by}, null][0]}).out),
since: \((opt & {#in: [if e.since != _|_ {e.since}, null][0]}).out),
"""
}
}

// No text may end a raw string early.
_raw_safe: [
for k, entries in {"guarantees": guarantees, "facts": facts, "encodings": encodings, "features": features}
for id, e in entries
for f, v in e
if (v & string) != _|_
if strings.Contains(v, "\"#") {"\(k).\(id).\(f)"},
] & []

// Constant names stay distinct within each kind.
_unique_consts: {
for k, entries in {"guarantees": guarantees, "facts": facts, "encodings": encodings, "features": features} {
(k): list.UniqueItems() & [for id, _ in entries {(_rust.const & {#in: id}).out}]
}
}

_ids: {
#in: {...}
out: strings.Join([for id, _ in #in {" pub const \((_rust.const & {#in: id}).out): &str = \((_rust.str & {#in: id}).out);"}], "\n")
}

_set: {
#name: string
#in: [...string]
let items = #in
out: " pub const \(#name): &[&str] = \((_rust.list & {#in: items}).out);"
}

rust_catalog: """
// Generated from the catalog's CUE by `specs/check.sh`: don't edit by hand.

use crate::{Encoding, Fact, Feature, Guarantee, Kind, Origin, Status, Value};

pub const SPEC_VERSION: &str = \((_rust.str & {#in: _spec_version}).out);

/// Every guarantee, in the catalog's order.
pub const GUARANTEES: &[Guarantee] = &[
\(strings.Join([for id, g in guarantees {"""
Guarantee {
id: \((_rust.str & {#in: id}).out),
title: \((_rust.str & {#in: g.title}).out),
description: \((_rust.str & {#in: g.description}).out),
comes_from: \({handshake: "Origin::Handshake", encoding: "Origin::Encoding", policy: "Origin::Policy", type: "Origin::Type"}[g.comes_from]),
\((_rust.lifecycle & {#in: g}).out)
},
"""}], "\n"))
];

/// Every fact, in the order profiles list them.
pub const FACTS: &[Fact] = &[
\(strings.Join([for id, f in facts {"""
Fact {
id: \((_rust.str & {#in: id}).out),
applies_to: \((_rust.kind & {#in: f.applies_to}).out),
title: \((_rust.str & {#in: f.title}).out),
description: \((_rust.str & {#in: f.description}).out),
values: &[\(strings.Join([for v, meaning in f.values {"Value { id: \((_rust.str & {#in: v}).out), meaning: \((_rust.str & {#in: meaning}).out) }"}], ", "))],
establishes: \((_rust.opt & {#in: [if f.establishes != _|_ {f.establishes}, null][0]}).out),
\((_rust.lifecycle & {#in: f}).out)
},
"""}], "\n"))
];

/// Every official encoding, by its wire name.
pub const ENCODINGS: &[Encoding] = &[
\(strings.Join([for id, e in encodings {"""
Encoding {
id: \((_rust.str & {#in: id}).out),
kind: \((_rust.kind & {#in: e.kind}).out),
title: \((_rust.str & {#in: e.title}).out),
description: \((_rust.str & {#in: e.description}).out),
aliases: \((_rust.list & {#in: e.aliases}).out),
frozen_as: \((_rust.opt & {#in: [if e.frozen_as != _|_ {e.frozen_as}, null][0]}).out),
profile: \(e.profile),
feature: \((_rust.opt & {#in: [if e.feature != _|_ {e.feature}, null][0]}).out),
tags: \((_rust.list & {#in: e.tags}).out),
\((_rust.lifecycle & {#in: e}).out)
},
"""}], "\n"))
];

/// Every wire feature, in the order `features.json` lists them.
pub const FEATURES: &[Feature] = &[
\(strings.Join([for id, f in features {"""
Feature {
id: \((_rust.str & {#in: id}).out),
title: \((_rust.str & {#in: f.title}).out),
description: \((_rust.str & {#in: f.description}).out),
\((_rust.lifecycle & {#in: f}).out)
},
"""}], "\n"))
];

/// Guarantee ids.
pub mod guarantee {
\((_ids & {#in: guarantees}).out)
}

/// Fact ids.
pub mod fact {
\((_ids & {#in: facts}).out)
}

/// Encoding wire names.
pub mod encoding {
\((_ids & {#in: encodings}).out)
}

/// Feature ids.
pub mod feature {
\((_ids & {#in: features}).out)
}

/// The catalog's sets.
pub mod sets {
\((_set & {#name: "GUARANTEES", #in: sets.guarantees}).out)
\((_set & {#name: "FORMATS", #in: sets.formats}).out)
\((_set & {#name: "FRAMINGS", #in: sets.framings}).out)
\((_set & {#name: "HANDSHAKES", #in: sets.handshakes}).out)
\((_set & {#name: "OFFICIAL", #in: sets.official}).out)
\((_set & {#name: "PROFILED", #in: sets.profiled}).out)
\((_set & {#name: "FORMAT_FACTS", #in: sets.format_facts}).out)
\((_set & {#name: "FRAMING_FACTS", #in: sets.framing_facts}).out)
\((_set & {#name: "HANDSHAKE_FACTS", #in: sets.handshake_facts}).out)
\((_set & {#name: "FEATURES", #in: sets.features}).out)
/// Every alias, with the wire name it resolves to.
pub const ALIASES: &[(&str, &str)] = &[\(strings.Join([for a, id in sets.aliases {"(\((_rust.str & {#in: a}).out), \((_rust.str & {#in: id}).out))"}], ", "))];
}

"""

// The spec's version, from `VERSION`: `specs/check.sh` passes it as
// `-t version=...`.
_spec_version: *"" | =~"^[0-9]+\\.[0-9]+$" @tag(version)
2 changes: 2 additions & 0 deletions rust/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
target/
Cargo.lock
13 changes: 13 additions & 0 deletions rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "comline-catalog"
version = "0.10.0"
edition = "2021"
rust-version = "1.82"
license = "MPL-2.0"
description = "The Comline specification's catalog of shared names, as Rust constants"
repository = "https://github.com/ComlineProject/specification"
publish = false

# No dependencies, and no_std: core's IR-only build, the runtime and the
# language server can all use it.
[dependencies]
Loading
Loading