| Crates.io | oxur-ast |
| lib.rs | oxur-ast |
| version | 0.2.0 |
| created_at | 2026-01-03 17:51:21.654651+00 |
| updated_at | 2026-01-17 00:34:04.766416+00 |
| description | Rust AST ↔ S-expression conversion for Oxur |
| homepage | |
| repository | https://github.com/oxur/oxur |
| max_upload_size | |
| id | 2020522 |
| size | 1,105,618 |
Rust AST ↔ S-expression bidirectional conversion library with CLI tool.
oxur-ast provides a comprehensive toolkit for working with Rust Abstract Syntax Trees (AST) using S-expression syntax. It includes:
synaster command-line tool for conversions and verificationThe library supports the following S-expression types:
foo, bar, MyStruct:name, :type, :value"hello", "world\n"42, -17, 0nil(foo bar baz), (:key value)Read and write S-expressions directly from/to files:
use oxur_ast::sexp::{Parser, write_sexp_file};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Read from file
let sexp = Parser::parse_file("my-ast.sexp")?;
// Write to file
write_sexp_file(&sexp, "output.sexp")?;
// Round-trip
let original = Parser::parse_file("input.sexp")?;
write_sexp_file(&original, "output.sexp")?;
let reparsed = Parser::parse_file("output.sexp")?;
# Ok(())
# }
Parse S-expressions from strings:
use oxur_ast::sexp::Parser;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = r#"(Crate :items ())"#;
let sexp = Parser::parse_str(input)?;
# Ok(())
# }
Format S-expressions with customizable indentation:
use oxur_ast::sexp::{Parser, Printer, print_sexp, write_sexp_file};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# let sexp = Parser::parse_str("(example)")?;
// Default printer (2-space indentation)
let output = print_sexp(&sexp);
// Custom indentation
let printer = Printer::with_indent(4);
let output = printer.print(&sexp);
// Write to file
printer.write_file(&sexp, "output.sexp")?;
// Convenience function
write_sexp_file(&sexp, "output.sexp")?;
# Ok(())
# }
Convert S-expressions into Rust AST structures:
use oxur_ast::builder::AstBuilder;
use oxur_ast::sexp::Parser;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let input = r#"
(Crate
:attrs ()
:items ()
:spans (ModSpans :inner-span (Span :lo 0 :hi 0))
:id 0)
"#;
let sexp = Parser::parse_str(input)?;
let mut builder = AstBuilder::new();
let crate_ast = builder.build_crate(&sexp)?;
# Ok(())
# }
Parse real Rust source code into AST structures:
use oxur_ast::integration::parse_rust_file;
use oxur_ast::Generator;
use oxur_ast::sexp::print_sexp;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse Rust source
let source = r#"
fn main() {
println!("Hello, world!");
}
"#;
let crate_node = parse_rust_file(source)?;
// Generate S-expression
let gen = Generator::new();
let sexp = gen.generate_crate(&crate_node)?;
// Print formatted output
println!("{}", print_sexp(&sexp));
# Ok(())
# }
asterThe aster command-line tool provides convenient commands for AST conversions and verification.
cargo install --path .
Convert Rust to S-expression:
# Output to stdout
aster to-ast hello.rs
# Save to file
aster to-ast hello.rs -o hello.sexp
# Compact format
aster to-ast hello.rs --compact
Convert S-expression to Rust:
# Output to stdout
aster to-rust hello.sexp
# Save to file
aster to-rust hello.sexp -o output.rs
Verify round-trip conversion:
# Basic verification
aster verify hello.rs
# Verbose output
aster verify hello.rs --verbose
This example demonstrates creating a Rust program from an S-expression representation, compiling it, and running it.
Step 1: Create project structure
mkdir -p /tmp/oxur-hw1/src
Step 2: Create Cargo.toml
cat > /tmp/oxur-hw1/Cargo.toml <<'EOF'
[package]
name = "oxur-hw1"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "oxur-hw1"
path = "src/main.rs"
EOF
Step 3: Create src/main.sexp (hello world in S-expression format)
cat > /tmp/oxur-hw1/src/main.sexp <<'EOF'
(Crate
:attrs ()
:items ((Item
:attrs ()
:id 2
:span (Span :lo 0 :hi 0)
:vis (Inherited)
:ident (Ident :name "main" :span (Span :lo 0 :hi 0))
:kind (Fn
(Fn
:defaultness Final
:sig (FnSig
:header (FnHeader
:safety Default
:constness NotConst
:ext None
:coroutine-kind nil)
:decl (FnDecl
:inputs ()
:output (Default (Span :lo 0 :hi 0)))
:span (Span :lo 0 :hi 0))
:generics (Generics
:params ()
:where-clause (WhereClause
:has-where-token false
:predicates ()
:span (Span :lo 0 :hi 0))
:span (Span :lo 0 :hi 0))
:body (Block
:stmts ((Stmt
:id 0
:kind (MacCall
(MacCallStmt
:mac (MacCall
:path (Path
:span (Span :lo 0 :hi 0)
:segments ((PathSegment
:ident (Ident :name "println" :span (Span :lo 0 :hi 0))
:id 4294967295
:args nil)))
:args (Delimited
:dspan (DelSpan
:open (Span :lo 0 :hi 0)
:close (Span :lo 0 :hi 0))
:delim Paren
:tokens (TokenStream :source "\"Hello from Oxur (via S-expression)!\""))
:prior-type-ascription nil)
:style Semicolon
:attrs ()))
:span (Span :lo 0 :hi 0)))
:id 1
:rules Default
:span (Span :lo 0 :hi 0)
:could-be-bare-literal false)))))
:spans (ModSpans
:inner-span (Span :lo 0 :hi 0)
:inject-use-span (Span :lo 0 :hi 0))
:id 3
:is-placeholder false)
EOF
Step 4: Convert S-expression to Rust
cd /tmp/oxur-hw1
aster to-rust src/main.sexp -o src/main.rs
Step 5: Compile the program
cargo build
Step 6: Run the compiled binary
./target/debug/oxur-hw1
Output:
Hello from Oxur (via S-expression)!
This example demonstrates converting Rust code to S-expression format and comparing it with the previous example.
Step 1: Create project structure
mkdir -p /tmp/oxur-hw2/src
Step 2: Create Cargo.toml
cat > /tmp/oxur-hw2/Cargo.toml <<'EOF'
[package]
name = "oxur-hw2"
version = "0.1.0"
edition = "2021"
[[bin]]
name = "oxur-hw2"
path = "src/main.rs"
EOF
Step 3: Create src/main.rs (hello world in Rust)
cat > /tmp/oxur-hw2/src/main.rs <<'EOF'
fn main() {
println!("Hello from Oxur (via Rust)!");
}
EOF
Step 4: Convert Rust to S-expression
cd /tmp/oxur-hw2
aster to-ast src/main.rs -o src/main.sexp
Step 5: Compare the two S-expression representations
diff -u /tmp/oxur-hw1/src/main.sexp /tmp/oxur-hw2/src/main.sexp
The diff shows the minimal differences - primarily just the string literal content:
--- /tmp/oxur-hw1/src/main.sexp
+++ /tmp/oxur-hw2/src/main.sexp
@@ -xx,x +xx,x @@
- :tokens (TokenStream :source "\"Hello from Oxur (via S-expression)!\""))
+ :tokens (TokenStream :source "\"Hello from Oxur (via Rust)!\""))
This demonstrates that:
Rust Source → syn → oxur AST → Generator → S-expression
↑ ↓
└────── Builder ← Parser ──┘
The crate includes several examples demonstrating different features:
Parse a Rust source file and display AST information:
cargo run --example parse_rust_file tests/fixtures/simple_fn.rs
Convert a Rust source file to S-expression format:
cargo run --example convert_file tests/fixtures/hello_world.rs /tmp/hello.sexp
Basic S-expression parsing from files and strings:
cargo run --example parse_example
Building Rust AST structures from S-expression files:
cargo run --example build_simple_crate
Comprehensive file I/O operations including reading, writing, and round-trip:
cargo run --example file_io
The crate includes a comprehensive test data directory (test-data/) with:
Test files that should fail to parse:
See test-data/README.md for detailed documentation.
use oxur_ast::sexp::Parser;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Parse from string
let sexp = Parser::parse_str("(foo bar)")?;
// Parse from file
let sexp = Parser::parse_file("example.sexp")?;
# Ok(())
# }
use oxur_ast::sexp::{Parser, Printer, print_sexp, write_sexp_file};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# let sexp = Parser::parse_str("(example)")?;
// Convenience function (2-space indentation)
let output = print_sexp(&sexp);
// Custom printer
let printer = Printer::with_indent(4);
let output = printer.print(&sexp);
// Write to file
printer.write_file(&sexp, "output.sexp")?;
// Convenience function for writing
write_sexp_file(&sexp, "output.sexp")?;
# Ok(())
# }
use oxur_ast::builder::AstBuilder;
use oxur_ast::sexp::Parser;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
# let sexp = Parser::parse_str("(Crate :attrs () :items () :spans (ModSpans :inner-span (Span :lo 0 :hi 0)) :id 0)")?;
let mut builder = AstBuilder::new();
// Build different AST nodes
let crate_ast = builder.build_crate(&sexp)?;
# Ok(())
# }
The library provides detailed error types:
ParseError::EmptyInput: Empty input providedParseError::UnterminatedList: Missing closing parenthesisParseError::UnexpectedCloseParen: Unexpected closing parenthesisParseError::LexError: Lexical analysis errors (invalid escape, unterminated string)ParseError::FileReadError: Failed to read fileBuildError: AST building errors with position informationRun all tests:
cargo test
Run specific test suite:
cargo test --test parser_tests
cargo test --test builder_tests
cargo test --test test_data_validation
cargo test --test integration_tests
Run performance benchmarks:
cargo bench
The benchmark suite includes:
See the main repository for license information.
Contributions are welcome! Please see the main repository for guidelines.