Add a C language generator - #54
Merged
Merged
Conversation
C is the first target with no classes, namespaces, overloading or generics, so CGenerator writes what C uses in their place rather than dropping them: - a type is a `typedef struct` keeping its tag - a member function is a free `Type_name(Type* self, ...)`, and a `const` member function takes a pointer to const - a constructor returns the value it built from a designated initialiser - an interface is a struct of function pointers taking an untyped receiver - a base type is the first member, which is what makes the two layout-compatible - an enumeration's members are qualified by its name, since a C enumeration is unscoped - a namespace is a comment over flat members: folding the name into the declarations would rename them without renaming the references to them The dialect is C99 plus C11's `_Static_assert`, so a pure function gets no `[[nodiscard]]` and an enumeration no fixed underlying type. What C cannot say - a member's initial value, a default argument, a deleted declaration - is written as a note rather than dropped. CGeneratedSourceCompilesTests compiles a generated header with a real C compiler, including it twice from one translation unit, 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. It is inconclusive where no compiler is on the path. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7Exu6cR3QLkHY5F8uSks7
SonarCloud's quality gate failed the PR on duplication: 199 of the new lines in CGenerator were duplicates of CppGenerator, in three blocks. CFamilyGenerator now sits between the two and StandardLanguageGenerator and owns what they share, all of which is about C rather than about the AST: `#pragma once` and `#include`, the braced list and its designated initialisers, the declarator that puts an array's brackets after the name, the member-grouping rule, and the dispatch from a bare function declaration into the emitter that knows which type it belongs to. A generator supplies `SpellType` and, where it differs, how a value inside a list is written - which is the one thing C needs, since only there may it leave the type out. The type mappings stay with each generator: `str` is a `std::string` in one language and a `const char*` in the other, and the whole of what a mapping is is the spelling. C's operator names are now derived from the AST's own operator vocabulary rather than listed beside it, so an operator added to `BinaryOperator` is named without anybody remembering to, and the names cannot drift from the symbols `OperatorSymbols` spells. No output changes: all 534 tests pass, including the ones that pin C++'s generated source exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7Exu6cR3QLkHY5F8uSks7
The review bot's finding: both loops in BuildOperatorNames filtered inside the body, so the sequence being iterated was not the sequence being used. `HasSymbol` is what the filter now reads as, and it is also what makes `GetSymbol` safe to call on what survives it - an operator the AST has no spelling for is left out rather than throwing before anything has run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D7Exu6cR3QLkHY5F8uSks7
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Adds
CGenerator, a fifth target alongside C#, C++, Python and JavaScript.C is the first target with no classes, no namespaces, no overloading and no generics, so the interesting part is what it writes in their place. Each substitution is the one C code written by hand uses, rather than a comment apologising for the language:
ClassDeclarationtypedef struct Point { … } Point;, keeping the tag so a struct can point at its own kindvoid Point_translate(Point* self, int dx);IsReadOnlybecomesconst Point* self, and a static member takes no instancevoid*— what every C library that dispatches dynamically doesBaseTypetypedef enum Colour { Colour_Red, … } Colour;— a C enumeration is unscoped, so two with aNoneeach would be one name declared twiceVec_add), since C cannot declare onestatic constat file scope — a file-scopeconstin C has external linkage, so a header declaring one and included twice would not linkThe dialect is C99 plus C11's
_Static_assert. Nothing needs C23, which is why a pure function gets no[[nodiscard]]and an enumeration no fixed underlying type — both would restrict the output to a dialect most C is still not compiled as, and neither changes what the program means.What C genuinely cannot say is written as a note rather than dropped: a struct member's initial value, a parameter's default, a deleted declaration (writing its prototype would make legal exactly what the declaration exists to forbid).
One distinction turned out to be load-bearing and is worth flagging: a braced list is written as an initialiser (
{ .x = 1 }) where a declaration has already said its type, and as a compound literal ((Point){ .x = 1 }) only where a value stands on its own. Only the first is a constant expression, which is what an object with static storage duration has to be initialised by — so a generated constant table written the other way does not compile at file scope.Testing
CGeneratorTests— 35 tests pinning the spellings and the decisions above.CGeneratedSourceCompilesTests— generates a header holding one of everything and compiles it with a real C compiler (cc/gcc/clang), from a driver that includes it twice and uses every declaration it makes. C's rules about linkage, what an empty parameter list declares, and what may initialise a static-storage object are not visible in the text, so a spelling can be pinned and still be wrong. Inconclusive where no compiler is on the path, which is the honest result there.c.README,
CLAUDE.mdanddocs/design.mdare updated, including the claims that were true only while C++ was the one target with a compile-time assertion.🤖 Generated with Claude Code
https://claude.ai/code/session_01D7Exu6cR3QLkHY5F8uSks7
Generated by Claude Code