From 5c5322b0a0a77742a5e413acc3166e312cc8bdcd Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 12 Sep 2026 04:38:12 +0000 Subject: [PATCH 1/2] feat: add a Rust language generator [minor] Rust is the sixth target and the first with a real answer to most of what the AST says rather than a convention standing in for one: - data goes in a `struct` and behaviour in an `impl` block - an interface is a `trait`, and a base type on one is a supertrait - a destructor is `impl Drop`, an operator is its `std::ops` trait, a conversion is `impl From`, and a specialisation is `impl Trait for Type` - the one place a target answers C++'s explicit specialisation exactly - an enumeration's variants are scoped by it, so nothing needs prefixing, and a fixed underlying type is `#[repr]` - a pure function is `#[must_use]`, one fixed at build time is a `const fn`, and an assertion is `const _: () = assert!(...)` - `IsReadOnly` decides the receiver: `&self` against `&mut self`, which is the promise C++ spells as a trailing `const`, stated more strongly What is left over is inheritance, which Rust does not have and which becomes a field with a note, and the operators Rust supplies from another one and will not let a type define by itself - `!=`, the orderings, and the short-circuiting pair - each written as a note naming the trait to implement instead. Two rules are the opposite of every other target here and so are written out: a `let` binds immutably, so an ordinary declaration is `let mut`; and an inferred declaration writes no type at all, which needs no `auto` and no `var`. `RustGeneratedSourceCompilesTests` compiles what it writes with rustc. `CompiledExemplar` is the AST it and the C test are both checked against - one AST, two real compilers, which says more than two parallel fixtures could. The braced-list writer moves from `CFamilyGenerator` up to `StandardLanguageGenerator`, parameterised by its delimiters and by how a language says which member an element is for, so Rust's struct literal and array literal share it rather than copying it. `CGenerator` now says something about a specialisation instead of dropping it silently, which is what the other targets already did. `Coder.Editor` registers a Rust definition with the syntax highlighter, which ships fifteen languages and has no Rust among them. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01D7Exu6cR3QLkHY5F8uSks7 --- CLAUDE.md | 31 +- Coder.Editor/Coder.Editor.csproj | 1 + Coder.Editor/CoderEditorApp.cs | 10 + Coder.Editor/RustSyntax.cs | 97 ++ Coder.Test/Editor/CoderEditorAppTests.cs | 2 +- Coder.Test/Editor/EditorWiringTests.cs | 2 +- .../Editor/GeneratedCodeHighlightingTests.cs | 16 +- .../CGeneratedSourceCompilesTests.cs | 130 +- Coder.Test/Languages/CompiledExemplar.cs | 130 ++ .../Languages/GeneratedLineEndingTests.cs | 2 + .../RustGeneratedSourceCompilesTests.cs | 257 ++++ Coder.Test/Languages/RustGeneratorTests.cs | 701 +++++++++ Coder.Test/Languages/ToolchainHarness.cs | 87 ++ .../ServiceCollectionExtensionsTests.cs | 3 +- Coder/Ast/UnaryExpression.cs | 2 +- Coder/Languages/CFamilyGenerator.cs | 114 +- Coder/Languages/CGenerator.cs | 7 + Coder/Languages/RustGenerator.cs | 1315 +++++++++++++++++ Coder/Languages/StandardLanguageGenerator.cs | 139 ++ Coder/ServiceCollectionExtensions.cs | 1 + Directory.Packages.props | 1 + README.md | 13 +- docs/design.md | 6 +- 23 files changed, 2829 insertions(+), 238 deletions(-) create mode 100644 Coder.Editor/RustSyntax.cs create mode 100644 Coder.Test/Languages/CompiledExemplar.cs create mode 100644 Coder.Test/Languages/RustGeneratedSourceCompilesTests.cs create mode 100644 Coder.Test/Languages/RustGeneratorTests.cs create mode 100644 Coder.Test/Languages/ToolchainHarness.cs create mode 100644 Coder/Languages/RustGenerator.cs diff --git a/CLAUDE.md b/CLAUDE.md index 23b8e22..20c9e80 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,7 @@ dotnet run --project Coder.Editor ## Project Structure `ktsu.Coder` represents code as a language-agnostic AST, round-trips it through YAML, and generates -source in five target languages. The solution uses: +source in six target languages. The solution uses: - **ktsu.Sdk** — custom SDK providing shared build configuration - **MSTest.Sdk** — test project SDK with Microsoft Testing Platform @@ -79,14 +79,17 @@ source in five target languages. The solution uses: C++ is the one language here that puts them on the declarator rather than the type. `IsConstant` is the intent rather than the keyword: C++ writes `inline constexpr` at namespace scope and `static constexpr` inside a type, C# writes `static readonly`, C writes `static const` - at file scope and a note inside a struct, having no static data member at all, and a language with no - spelling for it omits it the way it omits an indirection. + at file scope and a note inside a struct, having no static data member at all, Rust picks between + a `const` and a `static` with it and lets it decide whether a local is `let` or `let mut`, and a + language with no spelling for it omits it the way it omits an indirection. - `Coder/Ast/ClassDeclaration.cs`'s `SpecialisationArguments` — what makes a declaration be *for* a type rather than *of* one. `template<> struct Describe` is how C++ attaches a fact to a type without touching the type, which is what a generated reflection table needs: the alternative is naming, and a `DescribeRigidBody` every consumer has to spell for itself is the thing a lookup - by type exists to avoid. Only C++ has it and the other three write a comment, the same as - `CompileTimeAssertion`; the arguments are `TypeReference` rather than text, though, because a + by type exists to avoid. C++ has it, and Rust answers it exactly — `impl Describe for RigidBody` + attaches facts to a type without touching the type, which is the whole of what the specialisation + is for; the rest write a comment, the same as `CompileTimeAssertion`. The arguments are + `TypeReference` rather than text, though, because a specialisation argument is a type and the comma in `Result` belongs to one of them rather than separating two. - `Coder/Ast/CompileTimeAssertion.cs` — what a generated type promises that the type itself cannot @@ -94,8 +97,9 @@ source in five target languages. The solution uses: is language-specific in a way most of the AST is not, and there is no shared idea underneath `std::is_trivially_copyable_v` to model. Only C++ and C have one — `static_assert` and `_Static_assert`, the second of which requires a message, so an assertion with none is given its - own condition; the others write a comment, because a file that quietly loses a guarantee looks - like one that still makes it. + own condition — and Rust, whose `const _: () = assert!(…)` needs no macro crate because a constant + nobody names still has to be evaluated for the program to build; the others write a comment, + because a file that quietly loses a guarantee looks like one that still makes it. - `Coder/Languages/LanguageGeneratorBase.cs` — the emitters every generator shares. - `Coder/Languages/StandardLanguageGenerator.cs` — owns the node dispatch, so a derived generator supplies only the syntax its language does not share. `CSharpGenerator` deliberately @@ -116,6 +120,19 @@ source in five target languages. The solution uses: underlying type. `Coder.Test/Languages/CGeneratedSourceCompilesTests.cs` compiles what it writes, because C's rules about linkage, empty parameter lists and what may initialise an object with static storage duration are not visible in the text. +- `Coder/Languages/RustGenerator.cs` — the target with the most to map onto, and so the one where the + interesting question is which feature each part of a declaration became rather than what to write + in place of it. Data goes in a `struct` and behaviour in an `impl` block; an interface is a `trait` + and a base type on one is a supertrait; a destructor is `impl Drop`, an operator is its `std::ops` + trait, a conversion is `impl From`, and a specialisation is `impl Trait for Type` — which is the + one place a target answers C++'s explicit specialisation exactly. What is left over is inheritance, + which Rust does not have, and the operators it supplies from another one and will not let a type + define by itself. `Coder.Editor/RustSyntax.cs` registers the highlighter definition, because the + highlighter ships fifteen languages and Rust is not one of them. +- `Coder.Test/Languages/CompiledExemplar.cs` — one AST, compiled by two real compilers. The C and + Rust generators are each checked by compiling what they write, and they are checked against the + same declarations, which says more than two parallel fixtures could: the claim being made is that + the same AST comes out as valid source in each target. - `Coder.Graph/AstSchema.cs` — the uniform view of the AST's parent/child structure, hand-written rather than reflective. Adding a node type means adding it here. - `Coder.Graph/AstFields.cs` — a node's editable properties as named fields of a kind, which is what diff --git a/Coder.Editor/Coder.Editor.csproj b/Coder.Editor/Coder.Editor.csproj index 2ca9794..2325efc 100644 --- a/Coder.Editor/Coder.Editor.csproj +++ b/Coder.Editor/Coder.Editor.csproj @@ -14,6 +14,7 @@ +