pASTadd
A Python attribute grammar framework inspired by JastAdd. Uses Python's dynamic mechanisms (descriptors, decorators, __getattr__) instead of code generation — making attribute grammar semantics visible and interactive.
Quick Example
Define a grammar (lang.ast):
Program ::= Func*;
Func ::= <Name:String> Body:Expr;
abstract Expr;
IdUse : Expr ::= <Name:String>;
BinOp : Expr ::= Left:Expr <Op:String> Right:Expr;
Numeral : Expr ::= <Value:int>;
Load it and define attributes:
from pastadd import load_grammar, inh, syn
# Load grammar — creates ASTNode classes dynamically
load_grammar("lang.ast")
# Synthesized attribute (lambda):
Func.qualified_name = lambda self: f"program.{self.name}"
# Inherited attribute (decorator):
@inh(Program)
def env(self, child_index):
return {f.name: f for f in self.children}
# Or as a lambda:
# inh(Program).env = lambda self, ci: {f.name: f for f in self.children}
# Use it
prog = Program()
f = Func(name="main")
prog.children.append(f)
print(f.env) # {'main': <Func object>}
print(f.qualified_name) # 'program.main'
Inherited attributes resolve by walking up the tree — no pass-through equations needed on intermediate nodes.
Or generate a Python file from the grammar:
python -m pastadd.grammar lang.ast -o lang_ast.py
Design Principles
- Grammar from
.astfiles —load_grammar()creates node classes dynamically;generate_grammar()writes Python source. - Equations live in modules — all
syn,inh,circular,collectiondefinitions are external to the grammar. - One mechanism per concern — synthesized, inherited, circular, collection, NTA, forwarding — each has a clear API.
- Educational transparency — every evaluation step is traceable via
observe().
Features
| Feature | API | Description |
|---|---|---|
| Grammar loading | load_grammar() / generate_grammar() |
Parse .ast files into ASTNode classes |
| Synthesized attributes | @syn(Cls) / Cls.attr = lambda |
Demand-driven, cached |
| Inherited attributes | @inh(Cls) / @inh(Cls.child) |
Parent provides equation for children |
| Collection attributes | collection() + contribute() |
Gather values from tree via survey |
| Circular attributes | circular(bottom=...)(fn) / circular(bottom=...) |
Fixed-point iteration (syn and inh) |
| Non-Terminal Attributes | @nta(Cls) |
Computed subtrees with parent links |
| Rewrites | @rewrite / @rewrite(Cls) |
Conditional tree transformations on access |
| Parameterized attributes | extra args on equation fn | Cached per argument tuple |
| Child targeting | @inh(Cls.child) |
Target equations to specific children |
| Incremental evaluation | automatic | Dependency tracking + auto-invalidation |
| Forwarding | forwards_to attribute |
Silver-style delegation |
| Tokens | token(default) |
Mutable input data (triggers invalidation) |
| Tracing / Profiling | observe() |
Observe evaluation, timing, cache hits |
| Abstract nodes | abstract Foo : Bar; in .ast |
Cannot instantiate directly |
Key Patterns
# One-liner synthesized:
Cls.attr = lambda self: expr
# Multi-line synthesized:
@syn(Cls)
def attr(self):
return complex_computation
# Inherited (all children, lambda):
inh(Parent).attr = lambda self, ci: value
# Inherited (all children, multi-line):
@inh(Parent)
def attr(self, child_index):
return complex_lookup
# Inherited (targeted child):
inh(Parent.child_name).attr = lambda self, ci: value
# Circular synthesized:
Cls.attr = circular(bottom=True)(lambda self, t: t.supertype(self))
# Circular inherited:
Expr.target_type = circular(bottom=None)
# Collection:
Cls.errors = collection(bottom=set, combine=set.add)
contribute(SubCls.check, to=Cls.errors)
# NTA (computed subtree):
@nta(Cls)
def computed_child(self):
return SomeNode(...)
Requirements
Python 3.13+ required. Depends on pytracked for dependency tracking.
pip install pastadd
Testing
python -m pytest tests/ -v
See Also
- JastAdd — the Java attribute grammar system that inspired pASTadd
- ExtendJ-pASTadd — a Java 5 compiler frontend ported to pASTadd (504 tests passing)
License
BSD-3-Clause
Metadata
Release files for pastadd 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pastadd-0.1.3.tar.gz | 63.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pastadd-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 101.8 kB
Release files / pastadd-0.1.3.tar.gz
| Download URL | pastadd-0.1.3.tar.gz |
|---|---|
| Size | 63.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e25495aece4da8a82dc109ac9fbf28b3f64460155e9f70a4036d95a731805018
|
|
BLAKE2b-256 checksum How to use checksums |
35d0f1c73acbb3352cea782413fae304a96888877eeba1cae2c4a5058d2ea2e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|
Release files / pastadd-0.1.3-py3-none-any.whl
| Download URL | pastadd-0.1.3-py3-none-any.whl |
|---|---|
| Size | 38.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
35827338ae08f0d6cc65c8f246b429c72f427a752a01b245952734c46aeb4d74
|
|
BLAKE2b-256 checksum How to use checksums |
2d3d06069742d7d8b9adab022a07aad02b8dcc118ac46411e62379bff13a7af9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.7
|