Skip to main content

openscad_lalr_parser

A fast LALR(1) parser for the OpenSCAD language with AST generation, powered by Lark.

Tests PyPI version License: MIT

Overview

openscad_lalr_parser parses OpenSCAD source code using a Lark-based LALR(1) parser and produces an abstract syntax tree (AST). The AST node classes are identical to those in the openscad_parser library, making it a drop-in replacement with significantly better performance.

Why LALR?

  • Speed: LALR(1) parsing is O(n) in the input size — no PEG backtracking overhead.
  • Predictability: Parse time is always proportional to input size.
  • Compatibility: Produces the same AST node types as openscad_parser.

Installation

pip install openscad-lalr-parser

For YAML output support:

pip install openscad-lalr-parser[yaml]

From Source

git clone https://github.com/BelfrySCAD/openscad_lalr_parser.git
cd openscad_lalr_parser
pip install -e ".[dev]"

Quick Start

Parse a String

from openscad_lalr_parser import getASTfromString

ast = getASTfromString("cube([1, 2, 3]);")
for node in ast:
    print(node)

Parse a File

from openscad_lalr_parser import getASTfromFile

ast = getASTfromFile("model.scad")
for node in ast:
    print(node)

Files are cached by modification time — repeated calls to getASTfromFile() with the same unchanged file return instantly. A disk cache is also maintained for persistence across interpreter sessions.

Include Comments

ast = getASTfromString("""
    // A comment
    x = 42; /* inline */
""", include_comments=True)

Comments are returned as CommentLine, CommentSpan, and BlankLine nodes at the top level. Inline comments adjacent to expressions are wrapped in CommentedExpr nodes with leading_comments and trailing_comments fields.

Scope Analysis

from openscad_lalr_parser import getASTfromString, build_scopes

ast = getASTfromString('''
    x = 10;
    function double(n) = n * 2;
    module box(size) { cube(size); }
''')

root_scope = build_scopes(ast)
print(root_scope.lookup_variable("x"))
print(root_scope.lookup_function("double"))
print(root_scope.lookup_module("box"))

# A node's enclosing scope, as this pass built it
box = ast[2]
print(root_scope.scope_of(box.children[0]).lookup_variable("size"))

A node's scope is not an attribute of the node: an included file is parsed once and its nodes are shared by every file that includes it, but include puts them in the includer's scope. Each build_scopes() call records into its own ScopeTable (root_scope.table), so two files including the same library can be scoped and used at once. build_scopes_into(ast, table) records into a table you own, for several roots read back together (one per used file). Before 2.0, node.scope held whichever file's scope was built last.

Serialization

from openscad_lalr_parser import getASTfromString, ast_to_json, ast_from_json

ast = getASTfromString("cube(10);")

# Serialize to JSON
json_str = ast_to_json(ast, indent=2)

# Deserialize back to AST nodes
ast2 = ast_from_json(json_str)

YAML serialization is available when PyYAML is installed:

from openscad_lalr_parser import ast_to_yaml, ast_from_yaml

yaml_str = ast_to_yaml(ast)
ast2 = ast_from_yaml(yaml_str)

Pretty-Printing

from openscad_lalr_parser import getASTfromString, to_openscad

ast = getASTfromString("module box(w,h){cube([w,h,1]);}")
print(to_openscad(ast, indent_width=4))

Output:

module box(w, h) {
    cube([w, h, 1]);
}

CLI

The openscad-lalr command-line tool parses OpenSCAD files and outputs JSON, YAML, or reformatted source.

# Parse to JSON (default)
openscad-lalr model.scad

# Pretty-print / reformat
openscad-lalr --format model.scad

# YAML output
openscad-lalr --yaml model.scad

# Read from stdin
echo 'cube(10);' | openscad-lalr -

# Include comments in output
openscad-lalr --with-comments model.scad

# Custom indentation
openscad-lalr --format --indent 2 model.scad

# Skip include resolution
openscad-lalr --no-includes model.scad

API Reference

Parsing Functions

Function Description
getASTfromString(code, include_comments=False, origin="<string>") Parse OpenSCAD source code and return its AST.
getASTfromFile(file, include_comments=False, process_includes=True) Parse a file with mtime-based caching and optional include resolution.
getASTfromLibraryFile(currfile, libfile, ...) Find and parse a library file using OpenSCAD's search path rules.
parse_ast(code, origin="<string>") Low-level parse returning AST nodes (no comment processing).
findLibraryFile(currfile, libfile) Find a library file path without parsing it.
librarySearchDirs(currfile) The directories searched, in order: the including file's, each OPENSCADPATH entry, then the user's OpenSCAD libraries folder (as OpenSCAD orders them).
strict_commas(enabled=True) Context manager: parse as OpenSCAD 2021.01 did, rejecting a trailing comma in call arguments and let/for/intersection_for assignments (cube(1,), let(x=1,)), while list literals, comprehensions and parameter lists keep theirs. Nests, restores on exit, and is part of every cache key. CLI: --strict-commas.
clear_ast_cache() Clear the in-memory and on-disk AST caches.

Serialization Functions

Function Description
ast_to_json(ast, indent=None) Serialize AST nodes to a JSON string.
ast_from_json(json_str) Deserialize AST nodes from a JSON string.
ast_to_dict(ast) Convert AST nodes to plain dicts.
ast_from_dict(data) Reconstruct AST nodes from dicts.
ast_to_yaml(ast) Serialize AST nodes to YAML (requires PyYAML).
ast_from_yaml(yaml_str) Deserialize AST nodes from YAML.
to_openscad(ast, indent_width=4) Pretty-print AST back to OpenSCAD source.

Scope Analysis

Function / Class Description
build_scopes(ast) Build scope tree for top-level AST nodes. Returns root Scope.
build_scopes_into(ast, table) The same, recording into a ScopeTable the caller owns.
Scope Lexical scope with lookup_variable(), lookup_function(), lookup_module().

AST Node Classes

All AST nodes inherit from ASTNode. The main categories are:

Literals: Identifier, StringLiteral, NumberLiteral, BooleanLiteral, UndefinedLiteral, RangeLiteral (its implicit_step is true for [a:b], whose step node is a synthesized 1)

Operators: AdditionOp, SubtractionOp, MultiplicationOp, DivisionOp, ModuloOp, ExponentOp, UnaryMinusOp, LogicalAndOp, LogicalOrOp, LogicalNotOp, BitwiseAndOp, BitwiseOrOp, BitwiseNotOp, BitwiseShiftLeftOp, BitwiseShiftRightOp, EqualityOp, InequalityOp, GreaterThanOp, GreaterThanOrEqualOp, LessThanOp, LessThanOrEqualOp, TernaryOp

Expressions: PrimaryCall, PrimaryIndex, PrimaryMember, LetOp, EchoOp, AssertOp, FunctionLiteral, ListComprehension, RenderExpression

RenderExpression is render() in expression position, obj = render() { cube(1); };, a language extension of openscad_cpp_evaluator/BelfrySCAD: its children's geometry as a value. The braces are required. To make it parseable, render is a reserved word wherever an expression can start, so it can't be a variable or an argument name; render() cube(1); as a statement is still a ModularCall.

List Comprehensions: ListCompFor, ListCompCFor, ListCompIf, ListCompIfElse, ListCompLet, ListCompEach

Declarations: Assignment, FunctionDeclaration, ModuleDeclaration, ParameterDeclaration

Module Instantiations: ModularCall, ModularFor, ModularIntersectionFor, ModularLet, ModularEcho, ModularAssert, ModularIf, ModularIfElse

Modifiers: ModularModifierShowOnly, ModularModifierHighlight, ModularModifierBackground, ModularModifierDisable

Imports: UseStatement, IncludeStatement

Arguments: PositionalArgument, NamedArgument

Comments: CommentLine, CommentSpan, CommentedExpr, BlankLine

Comparison with openscad_parser

Feature openscad_parser openscad_lalr_parser
Parser type PEG (Arpeggio) LALR(1) (Lark)
AST nodes ✓ ✓ (identical)
Scope analysis ✓ ✓
Comment preservation ✓ ✓
Inline comment attachment ✓ ✓
Serialization (JSON/YAML) ✓ ✓
Pretty-printing ✓ ✓
Source maps ✓ ✓
CLI tool ✓ (openscad-parser) ✓ (openscad-lalr)
Disk caching ✓ ✓
Performance Baseline Faster (LALR, no backtracking)

Development

Running Tests

pytest tests/ -v

Running Tests with Coverage

pytest tests/ --cov=src/openscad_lalr_parser --cov-report=term-missing

License

MIT License - Copyright (c) 2025 Belfry OpenSCAD Libraries

See LICENSE for full text.

Release files for openscad-lalr-parser 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openscad-lalr-parser 2.0.0
File Size Uploaded
openscad_lalr_parser-2.0.0.tar.gz 42.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for openscad-lalr-parser 2.0.0
File Interpreter ABI Platform
openscad_lalr_parser-2.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 89.1 kB

Release files / openscad_lalr_parser-2.0.0.tar.gz

Download URL openscad_lalr_parser-2.0.0.tar.gz
Size 42.3 kB
Tags Source
SHA-256 checksum
How to use checksums
84c118e672f7e002b5def28cd806024d0443bd5b3c0b9ca7ae4e26a9c8d182dc
BLAKE2b-256 checksum
How to use checksums
43a4f4a3019dbfe71613e3812ccf160e3fdb965311d37e315da9a5ef9adccea7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / openscad_lalr_parser-2.0.0-py3-none-any.whl

Download URL openscad_lalr_parser-2.0.0-py3-none-any.whl
Size 46.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
c5beffd3f2ef55431ee5f45e7aeb324a3c32fab378fc0e2fa8630733cb9bfd7c
BLAKE2b-256 checksum
How to use checksums
6f308f33996d4ca8c2953da50d42a1a16a7447b3f641a690cf45821cae69b590
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

2.1.0

2 release files

This release

2.0.0 This release

2 release files

1.5.0

2 release files

1.4.0

2 release files

1.3.1

2 release files

1.3.0

2 release files

1.2.1

2 release files

1.2.0

2 release files

1.1.0

2 release files

1.0.2

2 release files

1.0.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page