openscad-rs parses OpenSCAD source into a typed Rust syntax tree. It is a
parser rather than a geometry evaluator: downstream compilers, formatters,
linters, and language servers can interpret the resulting AST for their own
purposes.
The crate provides a logos-based lexer, byte spans on every AST node,
structured parse errors, a recursion-depth guard, and reusable read-only AST
traversal. It forbids unsafe Rust.
Add the crate to a project:
[dependencies]
openscad-rs = "0.1"Parse source and inspect its statements:
use openscad_rs::{Statement, parse};
let source = r#"
module rounded_box(size = [10, 10, 10], r = 1) {
minkowski() {
cube(size - [2*r, 2*r, 2*r]);
sphere(r = r, $fn = 20);
}
}
rounded_box(size = [30, 20, 10], r = 2);
"#;
let file = parse(source)?;
for statement in &file.statements {
match statement {
Statement::ModuleDefinition { name, params, .. } => {
println!("module {name} has {} parameters", params.len());
}
Statement::ModuleInstantiation { name, args, .. } => {
println!("call to {name} has {} arguments", args.len());
}
_ => {}
}
}
# Ok::<(), openscad_rs::ParseError>(())parselexes and parses one source string into aSourceFile.Statementrepresents assignments, definitions, module calls, conditionals, blocks, andinclude/usedirectives.ExprandExprKindrepresent literals, operators, calls, indexing, ranges, anonymous functions, and list comprehensions.Spanis a half-open byte range into the original UTF-8 source.ParseErrorreports invalid tokens, unexpected syntax, incomplete input, and excessive nesting.Visitorprovides read-only recursive traversal. Itswalk_*helpers let an override inspect a node and then continue through that node's children.
For example, count nested module calls while preserving default traversal:
use openscad_rs::{Statement, Visitor, parse, walk_statement};
struct ModuleCounter(usize);
impl Visitor for ModuleCounter {
fn visit_statement(&mut self, statement: &Statement) {
if matches!(statement, Statement::ModuleInstantiation { .. }) {
self.0 += 1;
}
walk_statement(self, statement);
}
}
let file = parse("union() { cube(5); sphere(3); }")?;
let mut counter = ModuleCounter(0);
counter.visit_file(&file);
assert_eq!(counter.0, 3);
# Ok::<(), openscad_rs::ParseError>(())The parser recognizes OpenSCAD literals, expressions and precedence, vectors,
ranges, list comprehensions, assignments, user-defined functions and modules,
child statements, modifiers, and include/use syntax. String escape handling
and source locations are retained in the AST.
Numeric tokens are parsed directly into hyperreal::Real. Integer, decimal,
scientific-notation, and hexadecimal literals therefore retain exact rational
meaning in the AST instead of first passing through f64.
Parsing is intentionally syntactic. The crate does not resolve included files, evaluate expressions, type-check programs, or construct geometry. AST strings and expression boxes are owned; this favors a straightforward downstream API over arena allocation or a fully zero-copy tree.
An optional compatibility test runs against the vendored upstream OpenSCAD fixture corpus. That corpus also contains experimental and deliberately invalid inputs, so the test enforces a regression floor rather than claiming universal language acceptance.
cargo fmt --all --check
cargo test --all-targets
cargo clippy --all-targets -- -D warnings
cargo bench
cargo check --manifest-path fuzz/Cargo.toml --bins --lockedThe lexer, exact numeric literals, parser diagnostics, AST invariants, and
generated valid grammar have dedicated cargo-fuzz campaigns. See
fuzz/README.md for the target matrix and bounded nightly
commands.
To exercise the upstream fixtures and the command-line comparison benchmark:
git submodule update --init
cargo test --test openscad_compat -- --nocapture
./benches/compare_openscad.shThe comparison script additionally requires the openscad executable and
Python 3. Its numbers are local measurements, not a stable performance claim.
- OpenSCAD documentation and language reference
- OpenSCAD source grammar and test corpus
logoslexer documentationmiettediagnostic documentationthiserrorderive documentation
Related geometry work: csgrs turns
programmatic inputs into constructive-solid-geometry meshes, while
synaps-cad builds an interactive CAD
application around OpenSCAD-like source and csgrs.
Licensed under either of:
- Apache License, Version 2.0, (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option.