Skip to main content

Generate C++ code from declarative node descriptions.

Project description

Silwright

Documentation PyPI

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; uv can 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
  • node declares a future C++ struct. Each field is written as <name>: <type>.
  • trait declares reusable fields. A node applies one or more traits with node FunctionHead with Location, Documented; generated traits are public base structs.
  • Prefixing a field type with *, as in code: *Expr, makes it a repeated field.
  • Prefixing a field type with ?, as in label: ?identifier, makes it optional. Built-in, enum, and value fields use std::optional; pointer-backed node and choice fields remain std::unique_ptr because they already represent absence. * and ? cannot be combined.
  • Prefixing a field type with value, as in head: value FunctionHead, embeds node or choice types directly instead of using std::unique_ptr. The modifiers compose as parameters: *value Parameter for std::vector<parameter>.
  • Prefixing a field type with transient, as in function_scope: transient scope, emits the C++ member but excludes it from structural dump and visitor generation.
  • choice declares 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.
  • enum declares 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.
  • end closes every node, trait, choice, and enum declaration.
  • 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 uv for environments, dependencies, locking, and command execution.
  • A .ndef file contains exactly one module and must start with a module declaration.
  • .ndef comments start with // and continue through the end of the current line; there is no multiline comment syntax.
  • A module may contain node, trait, choice, and enum declarations 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.map file beside each parsed .ndef file. C++ is currently selected statically; no dynamic backend selection is supported yet.
  • backend_cpp.map may declare generated model-header dependencies with @include <header> and @include "header". Includes preserve declaration order and duplicates are removed.
  • Generated .hpp and .cpp files use the .ndef basename 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 _t suffix. Enum fields are scalar; node and choice fields use std::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::vector around the otherwise generated field type. For example, *identifier becomes std::vector<std::string> and *Expr becomes std::vector<std::unique_ptr<expr>>.
  • Optional fields use std::optional around the otherwise generated value type. Missing optional values and null node pointers are both emitted as null by 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 value to them has no additional effect.
  • Choices are std::variant aliases. 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 dump overloads emit YAML-like objects with four-space nesting, an artificial _type field, .ndef field names, lists for repeated fields, and null for null pointers. Strings are quoted and escaped, and enums use their original .ndef entry names.
  • Scalar values pass through an inline generic dump_value customization point. Its default uses operator<<; 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.hpp and _visitor.cpp. Public visit overloads accept every node and choice as an entry point. Node traversal calls protected virtual enter and leave hooks around pointer-backed children; scalars and value fields are deliberately skipped. Definitions referenced exclusively through value fields are omitted from the visitor API, while unreferenced definitions remain possible entry points. Derived visitors can override only the hooks they need.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

silwright-0.1.3.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

silwright-0.1.3-py3-none-any.whl (22.0 kB view details)

Uploaded Python 3

File details

Details for the file silwright-0.1.3.tar.gz.

File metadata

  • Download URL: silwright-0.1.3.tar.gz
  • Upload date:
  • Size: 17.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for silwright-0.1.3.tar.gz
Algorithm Hash digest
SHA256 00aab4608c2d1dd2a614431007427e1ea4f895709f64f7694abf3a9bf24c41ed
MD5 4eca0aa14ce97d82720aa664d2797217
BLAKE2b-256 608074653818bf67e5421b1247ecf3774a5dc20782e60bd9a5da0e9e9dc8f889

See more details on using hashes here.

Provenance

The following attestation bundles were made for silwright-0.1.3.tar.gz:

Publisher: publish-pypi.yml on Jokymon/silwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file silwright-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: silwright-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 22.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for silwright-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 1b7161d201f11214df1439b1e423068e6de01c75f75f88d19965a530ef71da22
MD5 60396f6b0c33a0caae9ae39e4f184a8d
BLAKE2b-256 7f9943e83738d744c0a2851606ef2aa6f94a4cda042568c1f454d3bdc7353938

See more details on using hashes here.

Provenance

The following attestation bundles were made for silwright-0.1.3-py3-none-any.whl:

Publisher: publish-pypi.yml on Jokymon/silwright

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page