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
8 changes: 8 additions & 0 deletions .github/scripts/run-wasm-without-imports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Cargo runner for `wasm32-unknown-unknown` tests: instantiates the module
// with no imports (instantiating fails if it needs any), so with no clock, no
// filesystem and no source of randomness. The target cannot print, so
// libtest's output is lost; a failing test panics, which aborts, which traps.
import { readFile } from 'node:fs/promises';

const { instance } = await WebAssembly.instantiate(await readFile(process.argv[2]), {});
process.exitCode = instance.exports.main(0, 0);
40 changes: 28 additions & 12 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ jobs:
run: cargo test --locked --all-targets
- name: Test with no default features
run: cargo test --locked --no-default-features --all-targets
# The tests needing model weights are ignored; this covers the rest.
- name: Test with neural synthesis
run: cargo test --locked --features neural-synthesis-hub --all-targets

# `--all-targets` skips doctests, and the public API is documented with
# them.
# them; with neural synthesis, so its module's examples compile too.
- name: Doctests
run: cargo test --locked --doc
run: cargo test --locked --features neural-synthesis-hub --doc

lint:
name: Format and Clippy
Expand All @@ -54,6 +57,8 @@ jobs:
run: cargo clippy --locked --all-targets -- -D warnings
- name: Clippy (no default features)
run: cargo clippy --locked --no-default-features --all-targets -- -D warnings
- name: Clippy (neural synthesis)
run: cargo clippy --locked --features neural-synthesis-hub --all-targets -- -D warnings

docs:
name: Documentation
Expand All @@ -66,9 +71,11 @@ jobs:
toolchain: nightly
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2

# Nightly with `--cfg docsrs`, to match how docs.rs builds the crate.
# Nightly with `--cfg docsrs`, to match how docs.rs builds the crate
# (its features: `package.metadata.docs.rs` in Cargo.toml). Not
# `--all-features`: the GPU backends need their toolkits.
- name: Build documentation
run: cargo doc --locked --all-features --no-deps
run: cargo doc --locked --features neural-synthesis-hub --no-deps
env:
RUSTDOCFLAGS: --cfg docsrs -D warnings

Expand All @@ -90,9 +97,11 @@ jobs:
toolchain: ${{ steps.msrv.outputs.msrv }}
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2

# `neural-synthesis-hub` is left out: its Hub client needs a newer Rust
# (documented in the README), and the GPU backends need their toolkits.
- name: Check on MSRV
run: |
cargo check --locked --all-features
cargo check --locked --features neural-synthesis
cargo check --locked --no-default-features

# `wasm32-unknown-unknown` has no threads, no clock and no system randomness,
Expand Down Expand Up @@ -120,18 +129,25 @@ jobs:
- name: Build with no default features
run: cargo build --locked --lib --no-default-features --target ${{ matrix.target }}

# `wasm32-wasip1` is the only one of the two that can run the test
# harness: `wasm32-unknown-unknown` has no way to start a process or
# print to stdout, which libtest needs. The tests that panic on purpose
# are `ignore`d for `wasm`, which is `panic = abort`.
# Neural synthesis needs no randomness at runtime: `getrandom` (via
# candle's `rand`) only has to compile, and its `unsupported` backend
# fails every request. The runner instantiates the test with no imports
# at all (no clock, filesystem or randomness); the target cannot print,
# but a failing test traps, which fails the step.
- name: Test neural synthesis without imports
if: matrix.target == 'wasm32-unknown-unknown'
env:
RUSTFLAGS: --cfg getrandom_backend="unsupported"
CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER: node .github/scripts/run-wasm-without-imports.mjs
run: cargo test --locked --features neural-synthesis --test neural_synthesis --target ${{ matrix.target }}

- uses: bytecodealliance/actions/wasmtime/setup@9152e710e9f7182e4c29ad218e4f335a7b203613 # v1
if: matrix.target == 'wasm32-wasip1'
- name: Test with default features
- name: Test with default features and neural synthesis
if: matrix.target == 'wasm32-wasip1'
env:
# `--dir` grants the guest the `tests/data` the integration tests read.
CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime --dir=.
run: cargo test --locked --tests --target ${{ matrix.target }}
run: cargo test --locked --tests --features neural-synthesis --target ${{ matrix.target }}
- name: Test with no default features
if: matrix.target == 'wasm32-wasip1'
env:
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ All notable changes to this crate are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added
- `FastAutomaton::to_regex_with` and `Term::to_regex_with` convert an automaton with any `fast_automaton::RegexSynthesizer`, such as `fast_automaton::StateElimination` (what `to_regex` uses) or a dependent's own.
- Neural synthesis (`neural-synthesis` feature, off by default): `neural_synthesis::NeuralSynthesizer`, a `RegexSynthesizer` that asks a learned model for simpler regexes. Every proposal is checked against the automaton, and state elimination's regex is kept when it is simpler, so the result is never wrong. Automata too large for the model are split into pieces it can take. Models are loaded from their files, or from the Hugging Face Hub with `neural-synthesis-hub`, and run on the CPU or, with `neural-synthesis-cuda`, `-cudnn` or `-metal`, a GPU (`-mkl` and `-accelerate` speed up the CPU). The first supported model is [Kleene](https://huggingface.co/alexvbrdn/kleene1-9m-b16-t128), for automata of up to 16 states. The `neural_synthesis` example compares both conversions.

### Changed
- `RegularExpression::evaluate_complexity` now returns the pattern's cognitive complexity, which neural synthesis models minimize: every class costs 1, and quantifiers, alternations and groups cost more the deeper they nest. State elimination uses it to order eliminations, so some converted patterns change; over the test corpus, patterns converted from DFAs are about 9% shorter.

## [1.0.2] - 2026-09-14

### Changed
Expand Down
14 changes: 12 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,18 @@ Run what CI runs:
cargo fmt --all --check
cargo clippy --locked --all-targets -- -D warnings
cargo clippy --locked --no-default-features --all-targets -- -D warnings
cargo clippy --locked --features neural-synthesis-hub --all-targets -- -D warnings
cargo test --locked --all-targets
cargo test --locked --no-default-features --all-targets
cargo test --locked --doc
RUSTDOCFLAGS="-D warnings" cargo doc --locked --no-deps
cargo test --locked --features neural-synthesis-hub --all-targets
cargo test --locked --features neural-synthesis-hub --doc
RUSTDOCFLAGS="-D warnings" cargo doc --locked --features neural-synthesis-hub --no-deps
```

The tests of `neural_synthesis` that run a model are ignored by default, as the weights are not in the repository. With a model release directory (`config.json` and `model.safetensors`, e.g. from [the Hugging Face Hub](https://huggingface.co/alexvbrdn/kleene1-9m-b16-t128)):

```sh
KLEENE_MODEL_DIR=<release dir> cargo test --release --features neural-synthesis neural_synthesis -- --ignored --skip bench
```

Please also:
Expand Down Expand Up @@ -50,6 +58,8 @@ Patterns are parsed by `regex-syntax`, whose Unicode tables are compiled in, and

`cargo test --test state_elimination_quality -- --ignored --nocapture` reports how large the patterns produced by automaton-to-regex conversion are over a corpus, which is the number to watch when changing a state-elimination heuristic.

With a model release directory, `KLEENE_MODEL_DIR=<release dir> cargo test --release --features neural-synthesis --lib -- --ignored --nocapture bench_generate bench_decompose` times the model (`bench_generate`, one DFA at a time and batched, with a checksum of the candidates so an optimization can be checked not to change them) and decomposition on automata beyond the model's limits (`bench_decompose`, with the complexity of the results against state elimination's).

## Releasing

Publishing is automated. Push a `v*` tag matching the version in `Cargo.toml`, and the release workflow checks formatting, clippy, tests, docs, the MSRV and a dry-run publish before it uploads to crates.io.
Loading
Loading