A Rust implementation of the Parable bash parser
Project description
Rable
A complete GNU Bash 5.3-compatible parser, written in Rust.
Rable is a from-scratch reimplementation of Parable — the excellent Python-based bash parser by @ldayton. It produces identical S-expression output and provides a drop-in replacement Python API via PyO3.
Acknowledgments
This project would not exist without Parable.
Parable is a remarkable piece of work — a complete, well-tested bash parser that produces clean S-expression AST output validated against bash's own internal parser. Its comprehensive test suite (1,604 tests across 36 files) defines the gold standard for bash parsing correctness, and Rable's compatibility is measured entirely against it.
We are deeply grateful to @ldayton for:
- Building a high-quality, MIT-licensed bash parser that others can learn from and build upon
- Creating the
bash-oracleapproach that validates parser output against bash itself - Maintaining the extensive
.testscorpus that made Rable's development possible - Designing the clean S-expression output format that Rable faithfully reproduces
Rable exists because Parable showed the way. Thank you.
Compatibility
| Metric | Value |
|---|---|
| Parable test compatibility | 1,604 / 1,604 (100%) |
| Test files at 100% | 36 / 36 |
| S-expression output | Identical to Parable |
Performance
Rable is approximately 9.5x faster than Parable across all test inputs:
| Input Type | Parable | Rable | Speedup |
|---|---|---|---|
| Simple command | 41us | 5us | 8.1x |
| Pipeline (5 stages) | 144us | 14us | 10.6x |
| Nested compound | 265us | 27us | 10.0x |
| Complex real-world script | 640us | 67us | 9.5x |
| Overall | 2.1ms | 221us | 9.5x |
Run just benchmark to reproduce these results on your machine.
Installation
As a Rust library
[dependencies]
rable = "0.1"
As a Python package
pip install rable
Or build from source:
just setup # creates venv, builds bindings, installs Parable
just develop # rebuild after code changes
Usage
Rust
use rable::parse;
fn main() {
let nodes = parse("echo hello | grep h", false).unwrap();
for node in &nodes {
println!("{node}");
}
// Output: (pipe (command (word "echo") (word "hello")) (command (word "grep") (word "h")))
}
Python
from rable import parse, ParseError, MatchedPairError
# Parse bash source into AST nodes
nodes = parse('if [ -f file ]; then cat file; fi')
for node in nodes:
print(node.to_sexp())
# Output: (if (command (word "[") (word "-f") (word "file") (word "]")) (command (word "cat") (word "file")))
# Errors are raised as exceptions
try:
parse('if')
except ParseError as e:
print(f"Syntax error: {e}")
# Enable extended glob patterns
nodes = parse('echo @(foo|bar)', extglob=True)
The Python API is a drop-in replacement for Parable:
# Before (Parable)
from parable import parse, ParseError, MatchedPairError
# After (Rable) — same API, ~10x faster
from rable import parse, ParseError, MatchedPairError
Development
Prerequisites
- Rust 1.93+ (see
rust-toolchain.toml) - Python 3.12+ (for Python bindings)
- just (task runner)
Quick start
just # format, lint, test
just check # same as above
just test-parable # run full Parable compatibility suite
just setup # set up Python environment + benchmarks
just benchmark # compare performance vs Parable
Available commands
| Command | Description |
|---|---|
just |
Format, lint, and test (default) |
just fmt |
Format all Rust code |
just clippy |
Run clippy with strict settings |
just test |
Run all Rust tests |
just test-parable |
Run Parable compatibility suite |
just test-file NAME |
Run a specific test file |
just setup |
Full Python environment setup |
just develop |
Build and install Python bindings |
just test-python |
Run Parable's test runner with Rable |
just benchmark |
Performance benchmark vs Parable |
just ci |
Run exactly what CI runs |
just clean |
Clean build artifacts |
Architecture
Rable is a hand-written recursive descent parser with a context-sensitive lexer:
| Module | Responsibility |
|---|---|
lexer/ |
Context-sensitive tokenizer with heredoc, quote, and expansion handling |
parser/ |
Recursive descent parser for all bash constructs |
ast.rs |
50+ AST node types covering the full bash grammar |
sexp/ |
S-expression output with word segment processing |
format/ |
Canonical bash reformatter (used for command substitution content) |
python.rs |
PyO3 bindings (feature-gated) |
Design principles
- Compatibility is correctness — output matches Parable's S-expressions exactly
- If it is not tested, it is not shipped — 1,604 integration tests + unit tests
- Simplicity is king — solve problems with least complexity
- Correctness over speed — match bash-oracle behavior, optimize later
Contributing
Contributions are welcome! Please ensure:
- All tests pass:
just checkmust succeed - Parable compatibility:
just test-parablemust show 1604/1604 - Code quality: No clippy warnings (
just clippy) - Formatting: Code is formatted (
just fmt)
Adding new features
If you're adding support for new bash syntax:
- Add test cases to the appropriate
.testsfile intests/parable/ - Implement the lexer/parser changes
- Verify S-expression output matches what Parable would produce
- Run
just test-parableto confirm no regressions
Code limits
| Limit | Value |
|---|---|
| Line width | 100 chars |
| Function length | 60 lines |
| Cognitive complexity | 15 |
| Function arguments | 5 |
| Clippy | deny(unwrap_used, expect_used, panic, todo) |
License
MIT License. See LICENSE for details.
Disclosure
Rable is a complete reimplementation of Parable in Rust. It was built by studying Parable's test suite and output format, not by translating Parable's Python source code. The test corpus (tests/parable/*.tests) originates from the Parable project and is used under its MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rable-0.1.7-cp313-cp313-win_amd64.whl.
File metadata
- Download URL: rable-0.1.7-cp313-cp313-win_amd64.whl
- Upload date:
- Size: 223.1 kB
- Tags: CPython 3.13, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f8d98156a417bd96974efb0a267deef8c3903e89d31d650c98385feedd25cc0
|
|
| MD5 |
70dfac16234a63cf0d4274eeabb5f6f2
|
|
| BLAKE2b-256 |
d1c1e4ee43c4a35683af33a3fed37c6353976f6e159dc1c5a9fab29ba3b52436
|
Provenance
The following attestation bundles were made for rable-0.1.7-cp313-cp313-win_amd64.whl:
Publisher:
publish.yml on mpecan/rable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rable-0.1.7-cp313-cp313-win_amd64.whl -
Subject digest:
1f8d98156a417bd96974efb0a267deef8c3903e89d31d650c98385feedd25cc0 - Sigstore transparency entry: 1179559987
- Sigstore integration time:
-
Permalink:
mpecan/rable@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Branch / Tag:
refs/tags/rable-v0.1.7 - Owner: https://github.com/mpecan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Trigger Event:
release
-
Statement type:
File details
Details for the file rable-0.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: rable-0.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 369.1 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cff20b6d71a8740943d8c478dc752ff9d74c970fe22ad4410a7c4673c777f01c
|
|
| MD5 |
49f6e6e1c12a32a9f0c66a8ebdbf2bf8
|
|
| BLAKE2b-256 |
e52c41959a1aa1b858c057320d83a81b46dde66b70a585a6f9e9f52b6aa4a102
|
Provenance
The following attestation bundles were made for rable-0.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
publish.yml on mpecan/rable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rable-0.1.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
cff20b6d71a8740943d8c478dc752ff9d74c970fe22ad4410a7c4673c777f01c - Sigstore transparency entry: 1179559745
- Sigstore integration time:
-
Permalink:
mpecan/rable@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Branch / Tag:
refs/tags/rable-v0.1.7 - Owner: https://github.com/mpecan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Trigger Event:
release
-
Statement type:
File details
Details for the file rable-0.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: rable-0.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 363.3 kB
- Tags: CPython 3.13, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4b744bd80b0a6388d4b516c4fc7b8328ef6f14b1537bafce6d9a6ba3004b326b
|
|
| MD5 |
df15e212063646e11bb13ab12f1fa561
|
|
| BLAKE2b-256 |
c3ffcc1c20f5444176a8a0004693eb18eac10331be627492391a658e5161cb84
|
Provenance
The following attestation bundles were made for rable-0.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
publish.yml on mpecan/rable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rable-0.1.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
4b744bd80b0a6388d4b516c4fc7b8328ef6f14b1537bafce6d9a6ba3004b326b - Sigstore transparency entry: 1179559817
- Sigstore integration time:
-
Permalink:
mpecan/rable@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Branch / Tag:
refs/tags/rable-v0.1.7 - Owner: https://github.com/mpecan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Trigger Event:
release
-
Statement type:
File details
Details for the file rable-0.1.7-cp313-cp313-macosx_11_0_arm64.whl.
File metadata
- Download URL: rable-0.1.7-cp313-cp313-macosx_11_0_arm64.whl
- Upload date:
- Size: 325.5 kB
- Tags: CPython 3.13, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a1dd8781a07978cb51170fc3471e6f6e194bf0cca74f140d983e169297ad0a94
|
|
| MD5 |
e546513c35edcb9d1cdebca66a1df830
|
|
| BLAKE2b-256 |
e632cc78e33aebbc9da4f585346d36163c12bb2147ce952e431fe21be0159f15
|
Provenance
The following attestation bundles were made for rable-0.1.7-cp313-cp313-macosx_11_0_arm64.whl:
Publisher:
publish.yml on mpecan/rable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rable-0.1.7-cp313-cp313-macosx_11_0_arm64.whl -
Subject digest:
a1dd8781a07978cb51170fc3471e6f6e194bf0cca74f140d983e169297ad0a94 - Sigstore transparency entry: 1179559868
- Sigstore integration time:
-
Permalink:
mpecan/rable@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Branch / Tag:
refs/tags/rable-v0.1.7 - Owner: https://github.com/mpecan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Trigger Event:
release
-
Statement type:
File details
Details for the file rable-0.1.7-cp313-cp313-macosx_10_12_x86_64.whl.
File metadata
- Download URL: rable-0.1.7-cp313-cp313-macosx_10_12_x86_64.whl
- Upload date:
- Size: 334.2 kB
- Tags: CPython 3.13, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a384bae63141c20c0af21b0e11df9defc85986e9aba6d71e74644918f67a11bb
|
|
| MD5 |
56b97ed515f44ca4f72110bdda2d8817
|
|
| BLAKE2b-256 |
15c3d72354ac182801d369fdfb50e4f9e20a56917d905b305182e9da7fecb373
|
Provenance
The following attestation bundles were made for rable-0.1.7-cp313-cp313-macosx_10_12_x86_64.whl:
Publisher:
publish.yml on mpecan/rable
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rable-0.1.7-cp313-cp313-macosx_10_12_x86_64.whl -
Subject digest:
a384bae63141c20c0af21b0e11df9defc85986e9aba6d71e74644918f67a11bb - Sigstore transparency entry: 1179559921
- Sigstore integration time:
-
Permalink:
mpecan/rable@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Branch / Tag:
refs/tags/rable-v0.1.7 - Owner: https://github.com/mpecan
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@579a7a4c3ac89b93fd3916b942ab0446a0123d5e -
Trigger Event:
release
-
Statement type: