Skip to content

ierror/openscad-rs

Repository files navigation

openscad-rs

Crates.io docs.rs CI

A OpenSCAD parser library for Rust.

Parses .scad source files into a well-typed AST suitable for building compilers, formatters, linters, and language servers.

Features

  • Fastlogos-based zero-copy lexer compiles to jump tables
  • Complete — Covers the full OpenSCAD language grammar (98.5% pass rate on OpenSCAD's own test suite)
  • Typed AST — Every node carries source spans for precise error reporting and tooling
  • Compiler-ready — Designed as a foundation for downstream compiler applications
  • Safe#[forbid(unsafe_code)], pedantic clippy, comprehensive tests
  • Zero dependencies at runtime — Only logos, miette, and thiserror

Quick Start

Add to your Cargo.toml:

[dependencies]
openscad-rs = "0.1.0"

Parse a source file:

use openscad_rs::{parse, Statement, ExprKind};

let source = r#"
    module rounded_box(size = [10, 10, 10], r = 1) {
        if (r > 0) {
            minkowski() {
                cube(size - [2*r, 2*r, 2*r]);
                sphere(r = r, $fn = 20);
            }
        } else {
            cube(size);
        }
    }

    rounded_box(size = [30, 20, 10], r = 2);
"#;

let ast = parse(source).expect("parse error");

for stmt in &ast.statements {
    match stmt {
        Statement::ModuleDefinition { name, params, .. } => {
            println!("module {name}({} params)", params.len());
        }
        Statement::ModuleInstantiation { name, args, .. } => {
            println!("call {name}({} args)", args.len());
        }
        _ => {}
    }
}
// Output:
// module rounded_box(2 params)
// call rounded_box(2 args)

Architecture

src/
├── lib.rs       # Public API: parse(), re-exports
├── token.rs     # Token enum (logos-generated)
├── lexer.rs     # Tokenizer: source → tokens with spans
├── ast.rs       # AST node types: Expr, Statement, etc.
├── parser.rs    # Recursive-descent parser
├── span.rs      # Source location tracking
├── error.rs     # ParseError with miette diagnostics
└── visit.rs     # AST visitor trait

Supported Language Features

Feature Status
Literals (numbers, hex, strings, booleans, undef)
String escape sequences (\n, \t, \xHH, \uHHHH, \UHHHHHH)
Variables & assignments
Full operator precedence (17 levels)
Ternary expressions
Vectors & ranges
Module definitions & instantiation
Function definitions
Anonymous functions
if/else (statement & expression)
for, let, each list comprehensions
echo(), assert()
include <file>, use <file>
Modifier prefixes (!, #, %, *)
Comments (//, /* */)
Member access (obj.x) & indexing (v[i])
Bitwise operators (&, |, <<, >>, ~)
Exponentiation (^)
Named & positional arguments
Trailing commas

AST Visitor

Traverse the AST with the built-in visitor trait:

use openscad_rs::{parse, Visitor, Expr, ExprKind, Statement};

struct ModuleCounter(usize);

impl Visitor for ModuleCounter {
    fn visit_statement(&mut self, stmt: &Statement) {
        if matches!(stmt, Statement::ModuleInstantiation { .. }) {
            self.0 += 1;
        }
        // Call default implementation to recurse into children
        openscad_rs::visit::Visitor::visit_statement(self, stmt);
    }
}

let ast = parse("union() { cube(5); sphere(3); }").unwrap();
let mut counter = ModuleCounter(0);
counter.visit_file(&ast);
assert_eq!(counter.0, 3); // union, cube, sphere

Compatibility

The parser is validated against the OpenSCAD test suite (523 .scad files), achieving a 98.5% pass rate.

To run the compatibility tests:

git submodule update --init
cargo test --test openscad_compat -- --nocapture

Benchmarking

cargo bench

Design Decisions

  • Parser only — No semantic analysis, type checking, or evaluation. This is purely syntactic parsing. Downstream crates handle the rest.
  • include/use are AST nodes — We parse the directive but don't resolve or load files. That's the compiler's responsibility.
  • Owned AST — Uses String and Box<Expr> for simplicity. Arena allocation can be added later for zero-copy parsing.
  • Lossless source locations — Every AST node carries a Span with byte offsets for precise source mapping.
  • Recursion depth guard — Prevents stack overflow on adversarial/deeply nested inputs.

Contact

@[email protected]

License

GPL v3

About

A OpenSCAD parser library for Rust.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors