Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,23 @@ source in four target languages. The solution uses:
as a string can only paste it, so the caller would have to spell the target language itself.
`Parse` and `ToString` are inverses and text the grammar cannot read becomes a name holding it
verbatim, so a property that used to hold a string still takes and gives one.
- `Coder/Ast/SourceFile.cs`, `NamespaceDeclaration.cs`, `EnumDeclaration.cs`, `FieldDeclaration.cs` —
what a generated *header* is made of rather than a snippet. `FieldDeclaration` is deliberately not
`VariableDeclaration`: a field with no initialiser is value-initialised, so a default-constructed
instance is the one the declaration described, and a local with none is ordinary. `SourceFile`'s
`Imports` are the one part of the AST that does not translate — a C++ include path, a C# namespace
and a Python module are different kinds of thing — so a file is built for a language.
- `Coder/Ast/FunctionKind.cs`, `FunctionDefinition.cs` — what a function declares (method,
constructor, destructor, operator, conversion) and where its behaviour comes from (provided,
defaulted, deleted). A declaration with no statements is otherwise ambiguous between a function
that does nothing, one the language supplies, and one that exists to be refused. `IsAbstract` is
C++'s *pure virtual*, which is a different thing from `IsPure`: one says a declaration has no
definition, the other that a call has no effect.
- `Coder/Ast/UsingAlias.cs`, `MemberInitialiser.cs`, `ConstructionExpression.cs` — what a type that
shims another needs. A member is *initialised* rather than assigned, which is the only way to start
one that cannot be assigned at all; a language without an initialiser list assigns at the top of
the constructor instead. `ConstructionExpression` is the one expression that needs a type rather
than a name, which is why it could not exist before `TypeReference` did.
- `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
Expand Down
241 changes: 219 additions & 22 deletions Coder.Graph/AstFields.cs

Large diffs are not rendered by default.

88 changes: 87 additions & 1 deletion Coder.Graph/AstSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ public static class AstSchema
private static readonly AstSlot ParametersSlot = new("Parameters", AstSlotCardinality.Many, AstSlotKind.Parameter);
private static readonly AstSlot BodySlot = new("Body", AstSlotCardinality.Many, AstSlotKind.Statement);
private static readonly AstSlot MembersSlot = new("Members", AstSlotCardinality.Many, AstSlotKind.Member);
/// <summary>The name of the slot an expression's arguments sit in.</summary>
private const string ArgumentsSlotName = "Arguments";

private static readonly AstSlot ArgumentsSlot = new(ArgumentsSlotName, AstSlotCardinality.Many, AstSlotKind.Expression);
private static readonly AstSlot EnumMembersSlot = new("Members", AstSlotCardinality.Many, AstSlotKind.EnumMember);

/// <summary>
/// Lists the slots a node exposes, in the order the editor should draw them.
Expand All @@ -43,7 +48,13 @@ public static class AstSchema
/// <returns>The node's slots, empty for a leaf.</returns>
public static IReadOnlyList<AstSlot> SlotsOf(AstNode node) => node switch
{
SourceFile => [MembersSlot],
NamespaceDeclaration => [MembersSlot],
ClassDeclaration => [MembersSlot],
EnumDeclaration => [EnumMembersSlot],
FieldDeclaration => [InitialValueSlot],
MemberInitialiser => [ValueSlot],
ConstructionExpression => [ArgumentsSlot],
FunctionDeclaration => [ParametersSlot, BodySlot],
EntryPoint => [BodySlot],
ReturnStatement => [ExpressionSlot],
Expand Down Expand Up @@ -73,6 +84,8 @@ public static IReadOnlyList<AstNode> ChildrenOf(AstNode node, AstSlot slot)
(BinaryExpression binary, "Right") => binary.Right,
(UnaryExpression unary, "Operand") => unary.Operand,
(VariableDeclaration varDecl, "InitialValue") => varDecl.InitialValue,
(FieldDeclaration field, "InitialValue") => field.InitialValue,
(MemberInitialiser initialiser, "Value") => initialiser.Value,
(AssignmentStatement assignment, "Target") => assignment.Target,
(AssignmentStatement assignment, "Value") => assignment.Value,
_ => null,
Expand All @@ -85,7 +98,11 @@ public static IReadOnlyList<AstNode> ChildrenOf(AstNode node, AstSlot slot)

return (node, slot.Name) switch
{
(SourceFile file, "Members") => [.. file.Members],
(NamespaceDeclaration namespaceDecl, "Members") => [.. namespaceDecl.Members],
(ClassDeclaration classDecl, "Members") => [.. classDecl.Members],
(EnumDeclaration enumDecl, "Members") => [.. enumDecl.Members],
(ConstructionExpression construction, ArgumentsSlotName) => [.. construction.Arguments],
(FunctionDeclaration function, "Parameters") => [.. function.Parameters],
(FunctionDeclaration function, "Body") => [.. function.Body],
(EntryPoint entryPoint, "Body") => [.. entryPoint.Body],
Expand Down Expand Up @@ -147,6 +164,18 @@ public static bool TryAttachAt(AstNode parent, AstSlot slot, int index, AstNode
varDecl.InitialValue = initialExpr;
return true;

case (FieldDeclaration field, "InitialValue") when child is Expression fieldExpr:
field.InitialValue = fieldExpr;
return true;

case (MemberInitialiser initialiser, "Value") when child is Expression initialiserExpr:
initialiser.Value = initialiserExpr;
return true;

case (ConstructionExpression construction, ArgumentsSlotName):
construction.Arguments.Add(child);
return true;

case (AssignmentStatement assignment, "Target") when child is Expression targetExpr:
assignment.Target = targetExpr;
return true;
Expand All @@ -171,6 +200,18 @@ public static bool TryAttachAt(AstNode parent, AstSlot slot, int index, AstNode
classDecl.Members.Add(child);
return true;

case (SourceFile file, "Members"):
file.Members.Add(child);
return true;

case (NamespaceDeclaration namespaceDecl, "Members"):
namespaceDecl.Members.Add(child);
return true;

case (EnumDeclaration enumDecl, "Members") when child is EnumMember enumMember:
enumDecl.Members.Add(enumMember);
return true;

default:
return false;
}
Expand Down Expand Up @@ -204,6 +245,22 @@ private static bool TryReplaceAt(AstNode parent, AstSlot slot, int index, AstNod
classDecl.Members[index] = child;
return true;

case (SourceFile file, "Members"):
file.Members[index] = child;
return true;

case (NamespaceDeclaration namespaceDecl, "Members"):
namespaceDecl.Members[index] = child;
return true;

case (EnumDeclaration enumDecl, "Members") when child is EnumMember enumMember:
enumDecl.Members[index] = enumMember;
return true;

case (ConstructionExpression construction, ArgumentsSlotName):
construction.Arguments[index] = child;
return true;

default:
return false;
}
Expand Down Expand Up @@ -248,6 +305,16 @@ public static bool TryDetachAt(AstNode parent, AstSlot slot, int index)
varDecl.InitialValue = null;
return hadValue;

case (FieldDeclaration field, "InitialValue"):
bool hadFieldValue = field.InitialValue is not null;
field.InitialValue = null;
return hadFieldValue;

case (MemberInitialiser initialiser, "Value"):
bool hadInitialiserValue = initialiser.Value is not null;
initialiser.Value = null;
return hadInitialiserValue;

case (BinaryExpression binary, "Left"):
binary.Left = Unfilled();
return true;
Expand Down Expand Up @@ -284,6 +351,22 @@ public static bool TryDetachAt(AstNode parent, AstSlot slot, int index)
classDecl.Members.RemoveAt(index);
return true;

case (SourceFile file, "Members") when index < file.Members.Count:
file.Members.RemoveAt(index);
return true;

case (NamespaceDeclaration namespaceDecl, "Members") when index < namespaceDecl.Members.Count:
namespaceDecl.Members.RemoveAt(index);
return true;

case (EnumDeclaration enumDecl, "Members") when index < enumDecl.Members.Count:
enumDecl.Members.RemoveAt(index);
return true;

case (ConstructionExpression construction, ArgumentsSlotName) when index < construction.Arguments.Count:
construction.Arguments.RemoveAt(index);
return true;

default:
return false;
}
Expand All @@ -308,6 +391,7 @@ public static AstNode CreateDefaultChild(AstSlot slot)
AstSlotKind.Parameter => new Parameter("value", "int"),
AstSlotKind.Statement => new ReturnStatement(),
AstSlotKind.Member => new FunctionDeclaration("newMethod") { ReturnType = "void" },
AstSlotKind.EnumMember => new EnumMember("NewValue"),
_ => Unfilled(),
};
}
Expand Down Expand Up @@ -349,7 +433,9 @@ public static bool Accepts(AstSlot slot, AstNode candidate)
// A parameter is not a statement, and neither is an entry point: a program starts
// running at one, so it belongs to a class or to the document rather than inside a body.
AstSlotKind.Statement => candidate is not (Parameter or EntryPoint),
AstSlotKind.Member => candidate is FunctionDeclaration or VariableDeclaration or ClassDeclaration or EntryPoint,
AstSlotKind.Member => candidate is FunctionDeclaration or VariableDeclaration or FieldDeclaration
or ClassDeclaration or EnumDeclaration or NamespaceDeclaration or UsingAlias or EntryPoint,
AstSlotKind.EnumMember => candidate is EnumMember,
_ => false,
};
}
Expand Down
3 changes: 3 additions & 0 deletions Coder.Graph/AstSlot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,7 @@ public enum AstSlotKind

/// <summary>A declaration a class can hold: a method, a field, or a nested class.</summary>
Member,

/// <summary>One named value of an enumeration, which is nothing else in the AST.</summary>
EnumMember,
}
Loading