Generate C++ code from declarative node descriptions.
Project description
Silwright
Silwright parses declarative node descriptions and generates C++ data types.
Installation
Install the command-line tool from PyPI:
pip install silwright
Alternatively, run it in an isolated environment without installing it permanently:
uvx silwright path/to/module.ndef
Silwright requires Python 3.14 or newer. Generated C++ currently requires C++20; in particular,
the generated model and dump support use std::variant and C++20 constraints.
Project status
Silwright is beta software. Its current language and C++ generation workflow are usable, but the project is still pre-1.0, has limited external production validation, and may make compatibility-affecting changes while its contracts are finalized.
Development setup
Prerequisites:
- Git
- uv
- Python 3.14 or newer;
uvcan install the requested interpreter when necessary
Clone the repository and create the locked development environment:
uv sync
Run all project checks:
uv run pytest
uv run ruff check .
uv run mypy
The package uses a src layout. Runtime dependencies and development tools are declared in
pyproject.toml; exact resolved versions are committed in uv.lock. Add dependencies with
uv add <package> and development dependencies with uv add --dev <package>.
Usage
Generate C++ from the example node definition:
uv run silwright examples/simple_lang.ndef
The command automatically loads the required backend_cpp.map from the same directory, then
writes the model .hpp/.cpp, dump _dump.hpp/_dump.ipp/_dump.cpp, and visitor
_visitor.hpp/_visitor.cpp files beside simple_lang.ndef. Existing generated output files
are replaced. Syntax errors include their source location.
Library users can call silwright.parse_definition_file(path) for the same combined lookup
behavior. silwright.parse_definitions(text) and silwright.parse_type_mappings(text) are
available when parsing already-loaded content independently.
silwright.analyze(parsed) performs centralized semantic validation and returns a
ValidatedModel containing resolved declarations, backend mappings, flattened trait fields,
dependency order, and visitor reachability. The model, dump, and visitor generators all accept
this validated model; passing raw parsed input remains supported and invokes the same analysis
boundary automatically.
The complete current syntax and generation contract are documented in
docs/language-reference.md.
Documentation
The project documentation can be previewed locally with:
uv sync --group docs
uv run mkdocs serve
The GitHub Pages workflow publishes the documentation from master when its source files
change. The built site/ directory is local output and is not version controlled.
Releasing
Python package releases use v<version> tags and are independent of VS Code extension
releases. Before creating a release, update the version in pyproject.toml, move the relevant
entries in CHANGELOG.md into a matching version section, and commit both changes. Then create
and push the matching tag; for version 0.1.0 this is:
git tag v0.1.0
git push origin v0.1.0
The release workflow verifies that the tag, project version, and changelog agree before it builds and publishes the distributions to PyPI.
Definition language
One .ndef file describes one module. Its required first declaration is module <name>;
the module name is intended to become the generated C++ namespace.
module expressions
node Variable
name: identifier
end
node Number
value: number
end
choice Expr
Variable | Number
end
enum Op
Add | Subtract | Multiply | Divide | Modulus
end
node BinaryExpression
op: Op
left: Expr
right: Expr
end
node FunctionDefinition
head: value FunctionHead
code: *Expr
end
nodedeclares a future C++ struct. Each field is written as<name>: <type>.traitdeclares reusable fields. A node applies one or more traits withnode FunctionHead with Location, Documented; generated traits are public base structs.- Prefixing a field type with
*, as incode: *Expr, makes it a repeated field. - Prefixing a field type with
?, as inlabel: ?identifier, makes it optional. Built-in, enum, andvaluefields usestd::optional; pointer-backed node and choice fields remainstd::unique_ptrbecause they already represent absence.*and?cannot be combined. - Prefixing a field type with
value, as inhead: value FunctionHead, embeds node or choice types directly instead of usingstd::unique_ptr. The modifiers compose asparameters: *value Parameterforstd::vector<parameter>. - Prefixing a field type with
transient, as infunction_scope: transient scope, emits the C++ member but excludes it from structural dump and visitor generation. choicedeclares a sum type. Its alternatives refer to node types or other declared types.- Choice alternatives may span lines. The
|separator may be placed after the preceding option or at the start of the following option's line, and these layouts may be mixed within one choice definition. enumdeclares a set of new enumerator names rather than references to node types.- Enum entries support the same inline, multiline, and mixed
|layouts as choice alternatives. endcloses everynode,trait,choice, andenumdeclaration.- Names currently use ASCII letters, digits, and underscores and cannot start with a digit.
//starts a comment that continues to the end of the line. Comments may occupy a complete line or follow regular definition code. Multiline comments are not supported. Blank lines are accepted around declarations; blank lines inside declaration bodies are not currently supported. Horizontal spacing is insignificant.
C++ backend types are mapped separately in backend_cpp.map:
@include <cstddef>
@include "project/symbol.hpp"
identifier: std::string
number: long
index: std::size_t
type: project::type_id
Each mapping has a Silwright type on the left and its C++ type spelling on the right.
@include accepts either a system header in <...> or a project header in "..."; these
headers are deduplicated and emitted into the generated model header. For a definition such
as simple_lang.ndef, the parser always looks for backend_cpp.map in the same directory.
See examples/simple_lang.ndef and examples/backend_cpp.map for complete examples.
End-to-end C++ example
examples/simple_lang_main.cpp constructs a function definition containing several expression
nodes and writes its YAML-like dump to std::cout. examples/CMakeLists.txt defines the
simple_lang_example C++20 executable from the sample and generated translation units. The
example files are currently provided as a proof of concept; automated C++ compilation is not
yet part of the project checks.
Requirements and decisions
This section is maintained as requirements are discovered or changed during development.
- The project requires Python 3.14 or newer and uses
uvfor environments, dependencies, locking, and command execution. - A
.ndeffile contains exactly one module and must start with a module declaration. .ndefcomments start with//and continue through the end of the current line; there is no multiline comment syntax.- A module may contain
node,trait,choice, andenumdeclarations in source order. - Node fields and choice alternatives reference types by name. Enums introduce values by name. This distinction prevents enum values from being mistaken for type references.
- Backend types and their C++ spellings live in the required
backend_cpp.mapfile beside each parsed.ndeffile. C++ is currently selected statically; no dynamic backend selection is supported yet. backend_cpp.mapmay declare generated model-header dependencies with@include <header>and@include "header". Includes preserve declaration order and duplicates are removed.- Generated
.hppand.cppfiles use the.ndefbasename and are written beside it. The module name is used directly as the C++ namespace. - Definition names are converted from CamelCase to snake_case. Enum names additionally use
a
_tsuffix. Enum fields are scalar; node and choice fields usestd::unique_ptr; built-in fields use their mapped C++ spelling. - Traits generate public base structs and do not participate in choices or visitor traversal. Trait fields are flattened into node dump output. Unknown traits, duplicate applications, and inherited/local field-name collisions are rejected before C++ is emitted.
- Transient fields hold tooling or later-stage state that belongs on the in-memory node but is not part of its structural representation. They are generated normally but omitted from dump output and visitor child traversal.
- Repeated fields use
std::vectoraround the otherwise generated field type. For example,*identifierbecomesstd::vector<std::string>and*Exprbecomesstd::vector<std::unique_ptr<expr>>. - Optional fields use
std::optionalaround the otherwise generated value type. Missing optional values and null node pointers are both emitted asnullby generated dumpers. - Value fields require complete C++ types. The generator dependency-orders affected structs
and rejects recursive value-member cycles. Built-in and enum fields are already values, so
applying
valueto them has no additional effect. - Choices are
std::variantaliases. Node structs are forward-declared before choice aliases, then defined afterward. Choice-to-choice dependencies are ordered, and cycles between choice aliases are rejected because C++ aliases cannot be forward-declared. - Parsing currently validates syntax only. Duplicate declarations, unresolved references, naming rules for generated C++, mapping conflicts, and recursive type constraints remain future semantic-validation decisions.
- Generated headers currently include
<memory>,<optional>,<string>,<variant>, and<vector>, plus dependencies declared by the backend map. - Dump support is generated separately as
_dump.hpp,_dump.ipp, and_dump.cpp. The IPP contains template implementations and is included at the end of the dump header. - Generated
dumpoverloads emit YAML-like objects with four-space nesting, an artificial_typefield,.ndeffield names, lists for repeated fields, andnullfor null pointers. Strings are quoted and escaped, and enums use their original.ndefentry names. - Scalar values pass through an inline generic
dump_valuecustomization point. Its default usesoperator<<; context-sensitive custom types can provide a more-specific overload in the value type's namespace so it is found through argument-dependent lookup. A mapped type that is neither streamable nor customized produces a targeted compile-time diagnostic. - Mutable visitor support is generated as
_visitor.hppand_visitor.cpp. Publicvisitoverloads accept every node and choice as an entry point. Node traversal calls protected virtualenterandleavehooks around pointer-backed children; scalars andvaluefields are deliberately skipped. Definitions referenced exclusively throughvaluefields are omitted from the visitor API, while unreferenced definitions remain possible entry points. Derived visitors can override only the hooks they need.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file silwright-0.2.0.tar.gz.
File metadata
- Download URL: silwright-0.2.0.tar.gz
- Upload date:
- Size: 17.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
956a9cdeb992adae48b46bbcf144e4a6be88fa3b765fa7fd7b053b31aaf16bae
|
|
| MD5 |
6bbff52b904c9339dd01a1f4f13343e4
|
|
| BLAKE2b-256 |
804746f88d509d7177d8a7ca07f88b96878ddb62a50d440f95f2d7fea2fd0122
|
Provenance
The following attestation bundles were made for silwright-0.2.0.tar.gz:
Publisher:
publish-pypi.yml on Jokymon/silwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
silwright-0.2.0.tar.gz -
Subject digest:
956a9cdeb992adae48b46bbcf144e4a6be88fa3b765fa7fd7b053b31aaf16bae - Sigstore transparency entry: 2084935584
- Sigstore integration time:
-
Permalink:
Jokymon/silwright@ff14fa6c4c5cc312d12c0d5f0ab7157d799bb86f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Jokymon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ff14fa6c4c5cc312d12c0d5f0ab7157d799bb86f -
Trigger Event:
push
-
Statement type:
File details
Details for the file silwright-0.2.0-py3-none-any.whl.
File metadata
- Download URL: silwright-0.2.0-py3-none-any.whl
- Upload date:
- Size: 22.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
06d3c93c9857303e690c178779dcd51259fe060ff78af3e6dc572b5c52b55a54
|
|
| MD5 |
41ac31b68a7db04755b8822b51d2090a
|
|
| BLAKE2b-256 |
2d64edd66e4b1952b6389330ece5bb6bc8daf8b7afda56aac487e0ecbd9892e7
|
Provenance
The following attestation bundles were made for silwright-0.2.0-py3-none-any.whl:
Publisher:
publish-pypi.yml on Jokymon/silwright
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
silwright-0.2.0-py3-none-any.whl -
Subject digest:
06d3c93c9857303e690c178779dcd51259fe060ff78af3e6dc572b5c52b55a54 - Sigstore transparency entry: 2084935594
- Sigstore integration time:
-
Permalink:
Jokymon/silwright@ff14fa6c4c5cc312d12c0d5f0ab7157d799bb86f -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/Jokymon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-pypi.yml@ff14fa6c4c5cc312d12c0d5f0ab7157d799bb86f -
Trigger Event:
push
-
Statement type: