From ac85bc4273b076e3292c94af3a589ed4b849ffed Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 00:59:23 +0200 Subject: [PATCH 01/26] Quoter skeleton --- .../Syntax/Quoting/QuoteExpressionModel.cs | 47 +++++++ .../Api/Syntax/Quoting/SyntaxQuoter.cs | 115 ++++++++++++++++++ src/Draco.SourceGeneration/CodeGenerator.cs | 2 + .../SyntaxTree/SyntaxTreeSourceGenerator.cs | 2 + .../Templates/Quoter.sbncs | 29 +++++ 5 files changed, 195 insertions(+) create mode 100644 src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs create mode 100644 src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs create mode 100644 src/Draco.SourceGeneration/Templates/Quoter.sbncs diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs new file mode 100644 index 000000000..d7b3b9265 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs @@ -0,0 +1,47 @@ +using System.Collections.Immutable; + +namespace Draco.Compiler.Api.Syntax.Quoting; + +/// +/// An expression generated by the quoter. +/// +internal abstract record QuoteExpression; + +/// +/// A function call quote expression. +/// +/// The name of the function in . +/// The arguments to the function. +internal sealed record QuoteFunctionCall( + string Function, + ImmutableArray Arguments) + : QuoteExpression; + +/// +/// A property access quote expression. +/// +/// The name of the property in . +internal sealed record QuoteProperty(string Property) : QuoteExpression; + +/// +/// A null quote expression. +/// +internal sealed record QuoteNull : QuoteExpression; + +/// +/// An integer quote expression. +/// +/// The integer value. +internal sealed record QuoteInteger(int Value) : QuoteExpression; + +/// +/// A boolean quote expression. +/// +/// The boolean value. +internal sealed record QuoteBoolean(bool Value) : QuoteExpression; + +/// +/// A string quote expression. +/// +/// The string value. +internal sealed record QuoteString(string Value) : QuoteExpression; diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs new file mode 100644 index 000000000..4f5afde72 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -0,0 +1,115 @@ +using System; +using Draco.Compiler.Internal.Syntax; + +namespace Draco.Compiler.Api.Syntax.Quoting; + +/// +/// Specifies the output language for the quoter. +/// +public enum OutputLanguage +{ + /// + /// Output as C# code. + /// + CSharp + + // Probably going to add an option for Draco at some point +} + +/// +/// Specifies what the quoter should +/// +public enum QuoteMode +{ + /// + /// Quote an entire file. + /// + File, + + /// + /// Quote a single declaration. + /// + Declaration, + + /// + /// Quote a single statement. + /// + Statement, + + /// + /// Quote a single expression. + /// + Expression +} + +/// +/// Produces quoted text from s or s. +/// +/// The target output language for the quoter. +/// The mode the quoter should operate in. +public sealed partial class SyntaxQuoter(OutputLanguage outputLanguage) +{ + /// + /// Produces a string containing the factory method calls required to produce a string of source code. + /// + /// The to quote. + /// A string containing the quoted text. + public string Quote(SourceText text, QuoteMode mode) + { + // Todo: probably factor out this duplicate code + var diags = new SyntaxDiagnosticTable(); + var srcReader = text.SourceReader; + var lexer = new Lexer(srcReader, diags); + var tokenSource = TokenSource.From(lexer); + var parser = new Parser(tokenSource, diags); + + Internal.Syntax.SyntaxNode node = mode switch + { + QuoteMode.File => parser.ParseCompilationUnit(), + QuoteMode.Declaration => parser.ParseDeclaration(), + QuoteMode.Statement => parser.ParseStatement(false), // Todo: allow declarations? + QuoteMode.Expression => parser.ParseExpression(), + _ => throw new ArgumentOutOfRangeException(nameof(mode)) + }; + + return this.Quote(node); + } + + /// + /// Produces a string containing the factory method calls required to produce a syntax node. + /// + /// The to quote. + /// A string containing the quoted text. + public string Quote(Api.Syntax.SyntaxNode node) => + this.Quote(node.Green); + + private string Quote(Internal.Syntax.SyntaxNode node) + { + var visitor = new QuoteVisitor(); + var expr = node.Accept(visitor); + throw new NotImplementedException(); + } + + private sealed partial class QuoteVisitor : Internal.Syntax.SyntaxVisitor + { + public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken node) + { + throw new NotImplementedException(); + } + + public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) + { + throw new NotImplementedException(); + } + + public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) + { + throw new NotImplementedException(); + } + + public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Draco.SourceGeneration/CodeGenerator.cs b/src/Draco.SourceGeneration/CodeGenerator.cs index 2790694bf..2f7dcc6fe 100644 --- a/src/Draco.SourceGeneration/CodeGenerator.cs +++ b/src/Draco.SourceGeneration/CodeGenerator.cs @@ -13,6 +13,8 @@ public static string GenerateGreenSyntaxTree(SyntaxTree.Tree tree, CancellationT Render("GreenSyntaxTree.sbncs", tree, cancellationToken); public static string GenerateRedSyntaxTree(SyntaxTree.Tree tree, CancellationToken cancellationToken) => Render("RedSyntaxTree.sbncs", tree, cancellationToken); + internal static string GenerateQuoter(SyntaxTree.Tree tree, CancellationToken cancellationToken) => + Render("Quoter.sbncs", tree, cancellationToken); public static string GenerateBoundTree(BoundTree.Tree tree, CancellationToken cancellationToken) => Render("BoundTree.sbncs", tree, cancellationToken); public static string GenerateOneOf(OneOf.Config config, CancellationToken cancellationToken) => diff --git a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs index 053632c72..78d3cb38a 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs @@ -17,11 +17,13 @@ protected override IEnumerable> GenerateSources(obj var greenTreeCode = CodeGenerator.GenerateGreenSyntaxTree(domainModel, cancellationToken); var redTreeCode = CodeGenerator.GenerateRedSyntaxTree(domainModel, cancellationToken); + var quoterCode = CodeGenerator.GenerateQuoter(domainModel, cancellationToken); return [ new("GreenSyntaxTree.Generated.cs", greenTreeCode), new("RedSyntaxTree.Generated.cs", redTreeCode), + new("Quoter.Generated.cs", quoterCode), ]; } } diff --git a/src/Draco.SourceGeneration/Templates/Quoter.sbncs b/src/Draco.SourceGeneration/Templates/Quoter.sbncs new file mode 100644 index 000000000..96216677e --- /dev/null +++ b/src/Draco.SourceGeneration/Templates/Quoter.sbncs @@ -0,0 +1,29 @@ +using System.Collections.Generic; + +namespace Draco.Compiler.Api.Syntax.Quoting; + +{{include 'Utils.sbncs'}} + +#nullable enable +public sealed partial class SyntaxQuoter +{ + private sealed partial class QuoteVisitor + { + {{for $node in Nodes}} + {{if !$node.IsAbstract}} + public override QuoteExpression Visit{{remove_suffix($node.Name, 'Syntax')}}(Internal.Syntax.{{$node.Name}} node) => + new QuoteFunctionCall("{{remove_suffix($node.Name, 'Syntax')}}", [ + {{for $element in $node.Fields}} + {{if $element.IsNullable}} + node.{{$element.Name}}?.Accept(this) ?? new QuoteNull() + {{else}} + node.{{$element.Name}}.Accept(this) + {{end}} + {{if !for.last}},{{end}} + {{end}} + ]); + {{end}} + {{end}} + } +} +#nullable restore From f7c98e3843a05c86acbb2fde453f758dc64e1b06 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:34:15 +0200 Subject: [PATCH 02/26] Implement quoting syntax tokens --- .../Api/Syntax/Quoting/SyntaxQuoter.cs | 30 ++++++++++++++++++- .../Api/Syntax/SyntaxFactory.cs | 8 ++--- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 4f5afde72..06ab256a7 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -90,11 +90,39 @@ private string Quote(Internal.Syntax.SyntaxNode node) throw new NotImplementedException(); } + private static QuoteExpression QuoteObjectLiteral(object? value) => value switch + { + null => new QuoteNull(), + int @int => new QuoteInteger(@int), + float @float => new QuoteFloat(@float), + string @string => new QuoteString(@string), + bool @bool => new QuoteBoolean(@bool), + // Todo: are there any more literal values tokens can have? + _ => throw new ArgumentOutOfRangeException(nameof(value)) + }; + private sealed partial class QuoteVisitor : Internal.Syntax.SyntaxVisitor { public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken node) { - throw new NotImplementedException(); + var kindQuote = new QuoteTokenKind(node.Kind); + return (SyntaxFacts.GetTokenText(node.Kind), node.Value) switch + { + (not null, null) => new QuoteFunctionCall("MakeToken", [kindQuote]), + (null, null) => new QuoteFunctionCall("MakeToken", [ + kindQuote, + new QuoteString(node.Text) + ]), + (not null, not null) => new QuoteFunctionCall("MakeToken", [ + kindQuote, + QuoteObjectLiteral(node.Value) + ]), + (null, not null) => new QuoteFunctionCall("MakeToken", [ + kindQuote, + new QuoteString(node.Text), + QuoteObjectLiteral(node.Value) + ]) + }; } public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index bc8fb1c5a..08195bd7e 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -361,12 +361,12 @@ public static TextStringPartSyntax TextStringPart(string value) => public static SyntaxToken LineStringEnd { get; } = MakeToken(TokenKind.LineStringEnd, "\""); public static SyntaxToken Ellipsis { get; } = MakeToken(TokenKind.Ellipsis); - private static SyntaxToken MakeToken(TokenKind tokenKind) => + public static SyntaxToken MakeToken(TokenKind tokenKind) => Internal.Syntax.SyntaxToken.From(tokenKind).ToRedNode(null!, null, 0); - private static SyntaxToken MakeToken(TokenKind tokenKind, string text) => + public static SyntaxToken MakeToken(TokenKind tokenKind, string text) => Internal.Syntax.SyntaxToken.From(tokenKind, text).ToRedNode(null!, null, 0); - private static SyntaxToken MakeToken(TokenKind tokenKind, string text, object? value) => + public static SyntaxToken MakeToken(TokenKind tokenKind, string text, object? value) => Internal.Syntax.SyntaxToken.From(tokenKind, text, value).ToRedNode(null!, null, 0); - private static SyntaxToken MakeToken(TokenKind tokenKind, object? value) => + public static SyntaxToken MakeToken(TokenKind tokenKind, object? value) => Internal.Syntax.SyntaxToken.From(tokenKind, value: value).ToRedNode(null!, null, 0); } From 6bdf06689f1094884fae46cc8ba752105370bd6f Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:36:00 +0200 Subject: [PATCH 03/26] Add NotSupportedException for quoting syntax trivia --- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 06ab256a7..38f963b64 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -125,10 +125,8 @@ public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken nod }; } - public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) - { - throw new NotImplementedException(); - } + public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) => + throw new NotSupportedException("Quoter does currently not support quoting syntax trivia."); public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) { From 87a3f477b08a82162f9ac9bdeb3a761e0daf530e Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:36:13 +0200 Subject: [PATCH 04/26] Implement quoting syntax lists --- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 38f963b64..e7ca81bd7 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -128,10 +128,8 @@ public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken nod public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) => throw new NotSupportedException("Quoter does currently not support quoting syntax trivia."); - public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) - { - throw new NotImplementedException(); - } + public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) => + new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray()); public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) { From 8959cd9e74e81dac62a7fe0b9ed9ec3283715d46 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:36:39 +0200 Subject: [PATCH 05/26] Add Float and Character methods to SyntaxFactory --- src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index 08195bd7e..3c8bc9493 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -64,6 +64,9 @@ public static SyntaxToken Missing(TokenKind kind) => public static SyntaxToken Name(string text) => MakeToken(TokenKind.Identifier, text); public static SyntaxToken Integer(int value) => MakeToken(TokenKind.LiteralInteger, value.ToString(), value); + public static SyntaxToken Float(float value) => MakeToken(TokenKind.LiteralFloat, value.ToString(), value); + public static SyntaxToken Character(char value) => MakeToken(TokenKind.LiteralCharacter, value.ToString(), value); + public static SyntaxToken? VisibilityToken(Visibility visibility) => visibility switch { Visibility.Private => null, From 8961f1d72689811bf4e8d64c878acf180514d148 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:37:13 +0200 Subject: [PATCH 06/26] Add CreateInterleaved utility for SeparatedSyntaxList --- .../Internal/Syntax/SeparatedSyntaxList.cs | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs b/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs index 9f7ed53a1..40e9111e4 100644 --- a/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs +++ b/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs @@ -19,6 +19,40 @@ internal static class SeparatedSyntaxList /// The created builder. public static SeparatedSyntaxList.Builder CreateBuilder() where TNode : SyntaxNode => new(); + + /// + /// Creates a by interleaving a sequence of values with a sequence of separators. + /// + /// The node type. + /// The separator tokens. + /// The value nodes. Has to be equal to or one more than the length of . + /// The constructed . + public static SeparatedSyntaxList CreateInterleaved(IEnumerable separators, IEnumerable values) + where TNode : SyntaxNode + { + IEnumerable MakeNodes() + { + using var valuesEnum = values.GetEnumerator(); + using var separatorsEnum = separators.GetEnumerator(); + + while (valuesEnum.MoveNext()) + { + yield return valuesEnum.Current; + + if (!separatorsEnum.MoveNext()) + { + if (valuesEnum.MoveNext()) throw new ArgumentException("Found more elements than separators.", nameof(values)); + yield break; + } + + yield return separatorsEnum.Current; + } + + if (separatorsEnum.MoveNext()) throw new ArgumentException("Found more separators than elements.", nameof(separators)); + } + + return new(MakeNodes()); + } } /// From a13dd3defa6bf0e57a5d4b1195bcb02c265cb401 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:37:35 +0200 Subject: [PATCH 07/26] Implement quoting separated syntax lists --- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 10 ++++++---- src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs | 8 ++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index e7ca81bd7..156ffd759 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Immutable; +using System.Linq; using Draco.Compiler.Internal.Syntax; namespace Draco.Compiler.Api.Syntax.Quoting; @@ -131,9 +133,9 @@ public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia n public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) => new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray()); - public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) - { - throw new NotImplementedException(); - } + public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) => + new QuoteFunctionCall("SeparatedSyntaxList", [ + new QuoteList(node.Separators.Select(x => x.Accept(this)).ToImmutableArray()), + new QuoteList(node.Values.Select(x => x.Accept(this)).ToImmutableArray())]); } } diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index 3c8bc9493..d1c17858e 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -84,6 +84,14 @@ public static SyntaxList SyntaxList(IEnumerable elements) public static SyntaxList SyntaxList(params TNode[] elements) where TNode : SyntaxNode => SyntaxList(elements.AsEnumerable()); + public static SeparatedSyntaxList SeparatedSyntaxList(IEnumerable separators, IEnumerable elements) + where TNode : SyntaxNode => new( + tree: null!, + parent: null, + fullPosition: 0, + green: Internal.Syntax.SeparatedSyntaxList.CreateInterleaved( + separators.Select(x => x.Green), + elements.Select(x => x.Green))); public static SeparatedSyntaxList SeparatedSyntaxList(SyntaxToken separator, IEnumerable elements) where TNode : SyntaxNode => new( tree: null!, From 02ac879a78c3041bb16980559d28175b5dea141e Mon Sep 17 00:00:00 2001 From: thinker227 Date: Wed, 4 Sep 2024 23:38:02 +0200 Subject: [PATCH 08/26] Add QuoteList, QuoteTokenKind, and QuoteFloat --- .../Api/Syntax/Quoting/QuoteExpressionModel.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs index d7b3b9265..901939b83 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs @@ -23,17 +23,35 @@ internal sealed record QuoteFunctionCall( /// The name of the property in . internal sealed record QuoteProperty(string Property) : QuoteExpression; +/// +/// A list quote expression. +/// +/// The values in the list. +internal sealed record QuoteList(ImmutableArray Values) : QuoteExpression; + /// /// A null quote expression. /// internal sealed record QuoteNull : QuoteExpression; +/// +/// A token kind quote expression. +/// +/// The token kind. +internal sealed record QuoteTokenKind(TokenKind Value) : QuoteExpression; + /// /// An integer quote expression. /// /// The integer value. internal sealed record QuoteInteger(int Value) : QuoteExpression; +/// +/// A float quote expression. +/// +/// The float value. +internal sealed record QuoteFloat(float Value) : QuoteExpression; + /// /// A boolean quote expression. /// From d180c9c16a9d709c3d5192307947811df180b966 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 00:08:02 +0200 Subject: [PATCH 09/26] Fix string escape issues --- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 156ffd759..7cf3a1388 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -2,6 +2,7 @@ using System.Collections.Immutable; using System.Linq; using Draco.Compiler.Internal.Syntax; +using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Api.Syntax.Quoting; @@ -113,7 +114,7 @@ public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken nod (not null, null) => new QuoteFunctionCall("MakeToken", [kindQuote]), (null, null) => new QuoteFunctionCall("MakeToken", [ kindQuote, - new QuoteString(node.Text) + new QuoteString(StringUtils.Unescape(node.Text)) ]), (not null, not null) => new QuoteFunctionCall("MakeToken", [ kindQuote, @@ -121,7 +122,7 @@ public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken nod ]), (null, not null) => new QuoteFunctionCall("MakeToken", [ kindQuote, - new QuoteString(node.Text), + new QuoteString(StringUtils.Unescape(node.Text)), QuoteObjectLiteral(node.Value) ]) }; From 7c07f5fb18c7127de4bbb4d4d9fd67879280f3c7 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 00:10:53 +0200 Subject: [PATCH 10/26] Make quoting a syntax list use SyntaxList method --- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 7cf3a1388..306b6aa1a 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -132,7 +132,9 @@ public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia n throw new NotSupportedException("Quoter does currently not support quoting syntax trivia."); public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) => - new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray()); + new QuoteFunctionCall( + "SyntaxList", + [new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray())]); public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) => new QuoteFunctionCall("SeparatedSyntaxList", [ From 6bd7495f9fd299433123cca01d5b9933a3b815eb Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 00:34:55 +0200 Subject: [PATCH 11/26] Use red tree instead of green tree --- .../Api/Syntax/Quoting/SyntaxQuoter.cs | 25 ++++++++++--------- .../Templates/Quoter.sbncs | 2 +- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 306b6aa1a..b210e060c 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -75,18 +75,17 @@ public string Quote(SourceText text, QuoteMode mode) _ => throw new ArgumentOutOfRangeException(nameof(mode)) }; - return this.Quote(node); + var red = node.ToRedNode(null!, null, 0); + + return this.Quote(red); } /// /// Produces a string containing the factory method calls required to produce a syntax node. /// - /// The to quote. + /// The to quote. /// A string containing the quoted text. - public string Quote(Api.Syntax.SyntaxNode node) => - this.Quote(node.Green); - - private string Quote(Internal.Syntax.SyntaxNode node) + public string Quote(SyntaxNode node) { var visitor = new QuoteVisitor(); var expr = node.Accept(visitor); @@ -104,9 +103,9 @@ private string Quote(Internal.Syntax.SyntaxNode node) _ => throw new ArgumentOutOfRangeException(nameof(value)) }; - private sealed partial class QuoteVisitor : Internal.Syntax.SyntaxVisitor + private sealed partial class QuoteVisitor : SyntaxVisitor { - public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken node) + public override QuoteExpression VisitSyntaxToken(SyntaxToken node) { var kindQuote = new QuoteTokenKind(node.Kind); return (SyntaxFacts.GetTokenText(node.Kind), node.Value) switch @@ -128,16 +127,18 @@ public override QuoteExpression VisitSyntaxToken(Internal.Syntax.SyntaxToken nod }; } - public override QuoteExpression VisitSyntaxTrivia(Internal.Syntax.SyntaxTrivia node) => + public override QuoteExpression VisitSyntaxTrivia(SyntaxTrivia node) => throw new NotSupportedException("Quoter does currently not support quoting syntax trivia."); - public override QuoteExpression VisitSyntaxList(Internal.Syntax.SyntaxList node) => + public override QuoteExpression VisitSyntaxList(SyntaxList node) => new QuoteFunctionCall( "SyntaxList", [new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray())]); - public override QuoteExpression VisitSeparatedSyntaxList(Internal.Syntax.SeparatedSyntaxList node) => - new QuoteFunctionCall("SeparatedSyntaxList", [ + public override QuoteExpression VisitSeparatedSyntaxList(SeparatedSyntaxList node) => + new QuoteFunctionCall( + "SeparatedSyntaxList", + [ new QuoteList(node.Separators.Select(x => x.Accept(this)).ToImmutableArray()), new QuoteList(node.Values.Select(x => x.Accept(this)).ToImmutableArray())]); } diff --git a/src/Draco.SourceGeneration/Templates/Quoter.sbncs b/src/Draco.SourceGeneration/Templates/Quoter.sbncs index 96216677e..2a1832679 100644 --- a/src/Draco.SourceGeneration/Templates/Quoter.sbncs +++ b/src/Draco.SourceGeneration/Templates/Quoter.sbncs @@ -11,7 +11,7 @@ public sealed partial class SyntaxQuoter { {{for $node in Nodes}} {{if !$node.IsAbstract}} - public override QuoteExpression Visit{{remove_suffix($node.Name, 'Syntax')}}(Internal.Syntax.{{$node.Name}} node) => + public override QuoteExpression Visit{{remove_suffix($node.Name, 'Syntax')}}({{$node.Name}} node) => new QuoteFunctionCall("{{remove_suffix($node.Name, 'Syntax')}}", [ {{for $element in $node.Fields}} {{if $element.IsNullable}} From bb095f8cc0a2830576e0781ca14e267e32a6e528 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 00:36:04 +0200 Subject: [PATCH 12/26] Add type arguments --- .../Api/Syntax/Quoting/QuoteExpressionModel.cs | 10 +++++++++- src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs index 901939b83..08b276273 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs @@ -11,11 +11,19 @@ internal abstract record QuoteExpression; /// A function call quote expression. /// /// The name of the function in . +/// The type arguments to the function. /// The arguments to the function. internal sealed record QuoteFunctionCall( string Function, + ImmutableArray TypeArguments, ImmutableArray Arguments) - : QuoteExpression; + : QuoteExpression +{ + public QuoteFunctionCall(string function, ImmutableArray arguments) + : this(function, [], arguments) + { + } +} /// /// A property access quote expression. diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index b210e060c..34108b57c 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -133,11 +133,13 @@ public override QuoteExpression VisitSyntaxTrivia(SyntaxTrivia node) => public override QuoteExpression VisitSyntaxList(SyntaxList node) => new QuoteFunctionCall( "SyntaxList", + [typeof(TNode).FullName!], // Todo: hack [new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray())]); public override QuoteExpression VisitSeparatedSyntaxList(SeparatedSyntaxList node) => new QuoteFunctionCall( "SeparatedSyntaxList", + [typeof(TNode).FullName!], // Todo: hack [ new QuoteList(node.Separators.Select(x => x.Accept(this)).ToImmutableArray()), new QuoteList(node.Values.Select(x => x.Accept(this)).ToImmutableArray())]); From 9687afabf527ffefa70d93877d7c0f2341063497 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 01:13:48 +0200 Subject: [PATCH 13/26] Little conundrum --- src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index d1c17858e..7cdf3a598 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -89,6 +89,7 @@ public static SeparatedSyntaxList SeparatedSyntaxList(IEnumerable< tree: null!, parent: null, fullPosition: 0, + // Todo: this needs to create a SeparatedSyntaxList, how? green: Internal.Syntax.SeparatedSyntaxList.CreateInterleaved( separators.Select(x => x.Green), elements.Select(x => x.Green))); From 865dab8386a0b077f12fe4fd649020ea267d1b2f Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 5 Sep 2024 10:10:58 +0200 Subject: [PATCH 14/26] Fix type issue with SeparatedSyntaxList in syntax factory --- .../Api/Syntax/SyntaxFactory.cs | 9 +++--- .../Internal/Syntax/SeparatedSyntaxList.cs | 32 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index 7cdf3a598..429e09419 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -89,10 +89,11 @@ public static SeparatedSyntaxList SeparatedSyntaxList(IEnumerable< tree: null!, parent: null, fullPosition: 0, - // Todo: this needs to create a SeparatedSyntaxList, how? - green: Internal.Syntax.SeparatedSyntaxList.CreateInterleaved( - separators.Select(x => x.Green), - elements.Select(x => x.Green))); + green: Syntax.SeparatedSyntaxList.MakeGreen( + Internal.Syntax.SeparatedSyntaxList.CreateInterleavedSequence( + separators.Select(x => x.Green), + elements.Select(x => x.Green)))); + public static SeparatedSyntaxList SeparatedSyntaxList(SyntaxToken separator, IEnumerable elements) where TNode : SyntaxNode => new( tree: null!, diff --git a/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs b/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs index 40e9111e4..0dd270f6b 100644 --- a/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs +++ b/src/Draco.Compiler/Internal/Syntax/SeparatedSyntaxList.cs @@ -21,37 +21,31 @@ public static SeparatedSyntaxList.Builder CreateBuilder() where TNode : SyntaxNode => new(); /// - /// Creates a by interleaving a sequence of values with a sequence of separators. + /// Creates an of syntax ndoes by interleaving a sequence of values with a sequence of separators. /// /// The node type. /// The separator tokens. /// The value nodes. Has to be equal to or one more than the length of . - /// The constructed . - public static SeparatedSyntaxList CreateInterleaved(IEnumerable separators, IEnumerable values) - where TNode : SyntaxNode + /// The constructed interleaved sequence. + public static IEnumerable CreateInterleavedSequence(IEnumerable separators, IEnumerable values) { - IEnumerable MakeNodes() + using var valuesEnum = values.GetEnumerator(); + using var separatorsEnum = separators.GetEnumerator(); + + while (valuesEnum.MoveNext()) { - using var valuesEnum = values.GetEnumerator(); - using var separatorsEnum = separators.GetEnumerator(); + yield return valuesEnum.Current; - while (valuesEnum.MoveNext()) + if (!separatorsEnum.MoveNext()) { - yield return valuesEnum.Current; - - if (!separatorsEnum.MoveNext()) - { - if (valuesEnum.MoveNext()) throw new ArgumentException("Found more elements than separators.", nameof(values)); - yield break; - } - - yield return separatorsEnum.Current; + if (valuesEnum.MoveNext()) throw new ArgumentException("Found more elements than separators.", nameof(values)); + yield break; } - if (separatorsEnum.MoveNext()) throw new ArgumentException("Found more separators than elements.", nameof(separators)); + yield return separatorsEnum.Current; } - return new(MakeNodes()); + if (separatorsEnum.MoveNext()) throw new ArgumentException("Found more separators than elements.", nameof(separators)); } } From f96fd5aebabd7d6988accdc55b21dbe5f4c2ad4b Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 20:49:30 +0200 Subject: [PATCH 15/26] Vestigial stuff from merge --- src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index 600a4b34f..b909f8f6e 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -277,7 +277,6 @@ public static TextStringPartSyntax TextStringPart(string value) => private static SyntaxToken Token(TokenKind tokenKind) => Internal.Syntax.SyntaxToken.From(tokenKind).ToRedNode(null!, null, 0); - public static SyntaxToken MakeToken(TokenKind tokenKind, string text) => private static SyntaxToken Token(TokenKind tokenKind, string text) => Internal.Syntax.SyntaxToken.From(tokenKind, text).ToRedNode(null!, null, 0); private static SyntaxToken Token(TokenKind tokenKind, string text, object? value) => From 8fc4aa9603bde3b25cfd3e5d3ecabc5e942e1e45 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 21:10:58 +0200 Subject: [PATCH 16/26] Rewrite quoter sg --- .../SyntaxTree/SyntaxTreeSourceGenerator.cs | 1 + .../SyntaxTree/Template.cs | 27 ++++++++++++++++- .../Templates/Quoter.sbncs | 29 ------------------- 3 files changed, 27 insertions(+), 30 deletions(-) delete mode 100644 src/Draco.SourceGeneration/Templates/Quoter.sbncs diff --git a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs index 3de5f08f2..9de603c76 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs @@ -18,6 +18,7 @@ protected override IEnumerable> GenerateSources(obj var tokenCode = Template.GenerateTokens(domainModel); var greenTreeCode = Template.GenerateGreenTree(domainModel); var redTreeCode = Template.GenerateRedTree(domainModel); + var quoterCode = Template.GenerateQuoter(domainModel); return [ diff --git a/src/Draco.SourceGeneration/SyntaxTree/Template.cs b/src/Draco.SourceGeneration/SyntaxTree/Template.cs index 2ae7b25b2..26773e8a8 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/Template.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/Template.cs @@ -125,6 +125,22 @@ public static partial class SyntaxFactory } #nullable restore +"""); + + public static string GenerateQuoter(Tree tree) => FormatCSharp($$""" +using System.Collections.Generic; + +namespace Draco.Compiler.Api.Syntax.Quoting; + +#nullable enable + +public sealed partial class SyntaxQuoter +{ + private sealed partial class QuoteVisitor + { + {{ForEach(tree.Nodes.Where(x => !x.IsAbstract), QuoterMethod)}} + } +} """); private static string GreenNodeClass(Tree tree, Node node) => $$""" @@ -371,7 +387,7 @@ public override IEnumerable<{{tree.Root.Name}}> Children yield break; } } - + """); private static string Accept(Node node) => When(node.IsAbstract && node.Base is null, @@ -429,6 +445,15 @@ private static string FieldPrefix(Field field) => $$""" private static string InternalType(string type) => $"Internal.Syntax.{type.Replace("<", " $$""" +public override QuoteExpression {{VisitorName(node)}}({{node.Name}} node) => + new QuoteFunctionCall("{{FactoryName(node)}}", [ + {{ForEach(node.Fields, field => $$""" + node.{{field.Name}}{{When(field.IsNullable, "?")}}.Accept(this){{When(field.IsNullable, " ?? new QuoteNull()")}}, + """)}} + ]); +"""; + private static string FactoryName(Node node) => RemoveSuffix(node.Name, "Syntax"); private static string AbstractSealed(Node node) => When(node.IsAbstract, "abstract", "sealed"); private static string ProtectedPublic(Node node) => When(node.IsAbstract, "protected", "public"); diff --git a/src/Draco.SourceGeneration/Templates/Quoter.sbncs b/src/Draco.SourceGeneration/Templates/Quoter.sbncs deleted file mode 100644 index 2a1832679..000000000 --- a/src/Draco.SourceGeneration/Templates/Quoter.sbncs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; - -namespace Draco.Compiler.Api.Syntax.Quoting; - -{{include 'Utils.sbncs'}} - -#nullable enable -public sealed partial class SyntaxQuoter -{ - private sealed partial class QuoteVisitor - { - {{for $node in Nodes}} - {{if !$node.IsAbstract}} - public override QuoteExpression Visit{{remove_suffix($node.Name, 'Syntax')}}({{$node.Name}} node) => - new QuoteFunctionCall("{{remove_suffix($node.Name, 'Syntax')}}", [ - {{for $element in $node.Fields}} - {{if $element.IsNullable}} - node.{{$element.Name}}?.Accept(this) ?? new QuoteNull() - {{else}} - node.{{$element.Name}}.Accept(this) - {{end}} - {{if !for.last}},{{end}} - {{end}} - ]); - {{end}} - {{end}} - } -} -#nullable restore From 4a42f4d03ed4d22b7716e78b515305b077238852 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 21:12:05 +0200 Subject: [PATCH 17/26] Fix some additional stuff --- src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs index b909f8f6e..dfcc676d7 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFactory.cs @@ -64,9 +64,8 @@ public static SyntaxToken Missing(TokenKind kind) => public static SyntaxToken Identifier(string text) => Token(TokenKind.Identifier, text); public static SyntaxToken Integer(int value) => Token(TokenKind.LiteralInteger, value.ToString(), value); - - public static SyntaxToken Float(float value) => MakeToken(TokenKind.LiteralFloat, value.ToString(), value); - public static SyntaxToken Character(char value) => MakeToken(TokenKind.LiteralCharacter, value.ToString(), value); + public static SyntaxToken Float(float value) => Token(TokenKind.LiteralFloat, value.ToString(), value); + public static SyntaxToken Character(char value) => Token(TokenKind.LiteralCharacter, value.ToString(), value); public static TokenKind? Visibility(Visibility visibility) => visibility switch { From a91bfc08203f5c0b8944cbaf48c8e5e9a8935d26 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 21:55:28 +0200 Subject: [PATCH 18/26] Update SyntaxToken quoting --- .../Api/Syntax/Quoting/SyntaxQuoter.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 34108b57c..acd61a2a9 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -110,17 +110,21 @@ public override QuoteExpression VisitSyntaxToken(SyntaxToken node) var kindQuote = new QuoteTokenKind(node.Kind); return (SyntaxFacts.GetTokenText(node.Kind), node.Value) switch { - (not null, null) => new QuoteFunctionCall("MakeToken", [kindQuote]), - (null, null) => new QuoteFunctionCall("MakeToken", [ - kindQuote, + // Token kind does not require any text nor a value + (not null, null) => new QuoteProperty(node.Kind.ToString()), + + // Token kind requires text + (null, null) => new QuoteFunctionCall(node.Kind.ToString(), [ new QuoteString(StringUtils.Unescape(node.Text)) ]), - (not null, not null) => new QuoteFunctionCall("MakeToken", [ - kindQuote, + + // Token kind requires a value + (not null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ QuoteObjectLiteral(node.Value) ]), - (null, not null) => new QuoteFunctionCall("MakeToken", [ - kindQuote, + + // Token kind requires both text and a value + (null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ new QuoteString(StringUtils.Unescape(node.Text)), QuoteObjectLiteral(node.Value) ]) From 45b463514b17d908ab1468a13ab42cd93e76a286 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 22:06:54 +0200 Subject: [PATCH 19/26] Add C# output for quoter --- .../Syntax/Quoting/CSharpQuoterTemplate.cs | 147 ++++++++++++++++++ .../Api/Syntax/Quoting/SyntaxQuoter.cs | 35 ++++- .../SyntaxTree/Template.cs | 2 +- 3 files changed, 175 insertions(+), 9 deletions(-) create mode 100644 src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs new file mode 100644 index 000000000..174170d30 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs @@ -0,0 +1,147 @@ +using System; +using System.Text; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Api.Syntax.Quoting; + +/// +/// Outputs quoted code as C# code. +/// +/// The string builder for the template to append text to. +/// Whether to append whitespace. +/// Whether to append SyntaxFactory. before properties and function calls. +internal sealed class CSharpQuoterTemplate(StringBuilder builder, bool prettyPrint, bool staticImport) +{ + private int indentLevel = 0; + + /// + /// Generates C# code from a . + /// + /// The expression to generate code from. + /// Whether to append whitespace. + /// Whether to append SyntaxFactory. before properties and function calls. + /// + public static string Generate(QuoteExpression expr, bool prettyPrint, bool staticImport) + { + var builder = new StringBuilder(); + var template = new CSharpQuoterTemplate(builder, prettyPrint, staticImport); + template.AppendExpr(expr); + return builder.ToString(); + } + + private void AppendExpr(QuoteExpression expr) + { + switch (expr) + { + case QuoteFunctionCall call: + this.AppendFunctionCall(call); + break; + + case QuoteProperty(var property): + this.TryAppendSyntaxFactory(); + builder.Append(property); + break; + + case QuoteList list: + this.AppendList(list); + break; + + case QuoteNull: + builder.Append("null"); + break; + + case QuoteTokenKind(var kind): + builder.Append(kind.ToString()); + break; + + case QuoteInteger(var value): + builder.Append(value); + break; + + case QuoteFloat(var value): + builder.Append(value); + break; + + case QuoteBoolean(var value): + builder.Append(value ? "true" : "false"); + break; + + case QuoteString(var value): + builder.Append('"'); + builder.Append(StringUtils.Unescape(value)); + builder.Append('"'); + break; + + default: + throw new ArgumentOutOfRangeException(nameof(expr)); + } + } + + private void AppendFunctionCall(QuoteFunctionCall call) + { + var (function, typeArgs, args) = call; + + this.TryAppendSyntaxFactory(); + builder.Append(function); + + if (typeArgs is not []) + { + builder.Append('<'); + for (var i = 0; i < typeArgs.Length; i++) + { + builder.Append(typeArgs[i]); + + if (i < typeArgs.Length - 1) builder.Append(", "); + } + builder.Append('>'); + } + + builder.Append('('); + this.indentLevel += 1; + for (var i = 0; i < args.Length; i++) + { + this.TryAppendNewLine(); + this.TryAppendIndentation(); + + this.AppendExpr(args[i]); + + if (i < args.Length - 1) builder.Append(prettyPrint ? "," : ", "); + } + this.indentLevel -= 1; + builder.Append(')'); + } + + private void AppendList(QuoteList list) + { + var values = list.Values; + + builder.Append('['); + this.indentLevel += 1; + for (var i = 0; i < values.Length; i++) + { + this.TryAppendNewLine(); + this.TryAppendIndentation(); + + this.AppendExpr(values[i]); + + if (i < values.Length - 1) builder.Append(prettyPrint ? "," : ", "); + } + this.indentLevel -= 1; + builder.Append(']'); + } + + private void TryAppendSyntaxFactory() + { + if (!staticImport) builder.Append("SyntaxFactory."); + } + + private void TryAppendIndentation() + { + if (prettyPrint) builder.Append(' ', this.indentLevel); + } + + private void TryAppendNewLine() + { + if (prettyPrint) builder.Append(Environment.NewLine); + } +} diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index acd61a2a9..cf59e0ca6 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -49,15 +49,23 @@ public enum QuoteMode /// Produces quoted text from s or s. /// /// The target output language for the quoter. -/// The mode the quoter should operate in. -public sealed partial class SyntaxQuoter(OutputLanguage outputLanguage) +public static partial class SyntaxQuoter { /// /// Produces a string containing the factory method calls required to produce a string of source code. /// /// The to quote. + /// What kind of syntactic element to quote. + /// The language to output the quoted code as. + /// Whether to append whitespace to the output quote. + /// Whether to require a static import of in the quoted code. /// A string containing the quoted text. - public string Quote(SourceText text, QuoteMode mode) + public static string Quote( + SourceText text, + QuoteMode mode, + OutputLanguage outputLanguage, + bool prettyPrint, + bool requireStaticImport) { // Todo: probably factor out this duplicate code var diags = new SyntaxDiagnosticTable(); @@ -77,19 +85,30 @@ public string Quote(SourceText text, QuoteMode mode) var red = node.ToRedNode(null!, null, 0); - return this.Quote(red); + return Quote(red, outputLanguage, prettyPrint, requireStaticImport); } /// /// Produces a string containing the factory method calls required to produce a syntax node. /// /// The to quote. + /// The language to output the quoted code as. + /// Whether to append whitespace to the output quote. + /// Whether to require a static import of in the quoted code. /// A string containing the quoted text. - public string Quote(SyntaxNode node) + public static string Quote( + SyntaxNode node, + OutputLanguage outputLanguage, + bool prettyPrint, + bool requireStaticImport) { - var visitor = new QuoteVisitor(); - var expr = node.Accept(visitor); - throw new NotImplementedException(); + var expr = node.Accept(new QuoteVisitor()); + + return outputLanguage switch + { + OutputLanguage.CSharp => CSharpQuoterTemplate.Generate(expr, prettyPrint, requireStaticImport), + _ => throw new ArgumentOutOfRangeException(nameof(outputLanguage)) + }; } private static QuoteExpression QuoteObjectLiteral(object? value) => value switch diff --git a/src/Draco.SourceGeneration/SyntaxTree/Template.cs b/src/Draco.SourceGeneration/SyntaxTree/Template.cs index 26773e8a8..1b0f9a14c 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/Template.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/Template.cs @@ -134,7 +134,7 @@ namespace Draco.Compiler.Api.Syntax.Quoting; #nullable enable -public sealed partial class SyntaxQuoter +public static partial class SyntaxQuoter { private sealed partial class QuoteVisitor { From 78b82672c09c5e948229f58a44b4d1f34eaf544b Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 22:58:18 +0200 Subject: [PATCH 20/26] Update indentation size --- src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs index 174170d30..1142b00d8 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs @@ -137,7 +137,8 @@ private void TryAppendSyntaxFactory() private void TryAppendIndentation() { - if (prettyPrint) builder.Append(' ', this.indentLevel); + // Todo: perhaps parameterize indentation size + if (prettyPrint) builder.Append(' ', this.indentLevel * 2); } private void TryAppendNewLine() From 785661da52b1eae164ef5101e9d0309bc38a4f20 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Tue, 10 Sep 2024 22:59:56 +0200 Subject: [PATCH 21/26] Add quote command to dev host CLI --- src/Draco.Compiler.DevHost/Program.cs | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/Draco.Compiler.DevHost/Program.cs b/src/Draco.Compiler.DevHost/Program.cs index bf12e5ab0..f24593547 100644 --- a/src/Draco.Compiler.DevHost/Program.cs +++ b/src/Draco.Compiler.DevHost/Program.cs @@ -7,6 +7,7 @@ using Draco.Compiler.Api; using Draco.Compiler.Api.Scripting; using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Api.Syntax.Quoting; using static Basic.Reference.Assemblies.Net80; namespace Draco.Compiler.DevHost; @@ -27,6 +28,10 @@ private static RootCommand ConfigureCommands() var referencesOption = new Option(["-r", "--reference"], Array.Empty, "Specifies additional assembly references to use when compiling"); var filesArgument = new Argument("source files", Array.Empty, "Specifies draco source files that should be compiled"); var rootModuleOption = new Option(["-m", "--root-module"], () => null, "Specifies the root module folder of the compiled files"); + var quoteModeOption = new Option(["-m", "--quote-mode"], () => QuoteMode.File, "Specifies the kind of syntactic element to quote"); + var outputLanguageOption = new Option(["-l", "--language"], () => OutputLanguage.CSharp, "Specifies the language to output the quoted code as"); + var prettyPrintOption = new Option(["-p", "--pretty-print"], () => false, "Whether to append whitespace to the output code"); + var staticImportOption = new Option(["-s", "--require-static-import"], () => false, "Whether to require the SyntaxFactory class to be imported statically in the output code"); // Compile @@ -88,6 +93,18 @@ private static RootCommand ConfigureCommands() }; formatCommand.SetHandler(FormatCommand, fileArgument, optionalOutputOption); + // Quoting + + var quoteCommand = new Command("quote", "Generates a string of code in an output language which produces syntax nodes equivalent to the input code") + { + fileArgument, + quoteModeOption, + outputLanguageOption, + prettyPrintOption, + staticImportOption, + }; + quoteCommand.SetHandler(QuoteCommand, fileArgument, quoteModeOption, outputLanguageOption, prettyPrintOption, staticImportOption); + return new RootCommand("CLI for the Draco compiler") { compileCommand, @@ -95,7 +112,8 @@ private static RootCommand ConfigureCommands() irCommand, symbolsCommand, declarationsCommand, - formatCommand + formatCommand, + quoteCommand, }; } @@ -179,6 +197,13 @@ private static void FormatCommand(FileInfo input, FileInfo? output) new StreamWriter(outputStream).Write(syntaxTree.Format().ToString()); } + private static void QuoteCommand(FileInfo input, QuoteMode mode, OutputLanguage outputLanguage, bool prettyPrint, bool staticImport) + { + var sourceText = SourceText.FromFile(input.FullName); + var quotedText = SyntaxQuoter.Quote(sourceText, mode, outputLanguage, prettyPrint, staticImport); + Console.WriteLine(quotedText); + } + private static ImmutableArray GetSyntaxTrees(params FileInfo[] input) { var result = ImmutableArray.CreateBuilder(); From cde4f2e9e4c8457c586a637dbdc6ae52cf49c4a5 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 12 Sep 2024 18:01:52 +0200 Subject: [PATCH 22/26] Add QuoteCharacter --- .../Api/Syntax/Quoting/CSharpQuoterTemplate.cs | 7 +++++++ .../Api/Syntax/Quoting/QuoteExpressionModel.cs | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs index 1142b00d8..6d7bc6a6d 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs @@ -66,6 +66,13 @@ private void AppendExpr(QuoteExpression expr) builder.Append(value ? "true" : "false"); break; + case QuoteCharacter(var value): + builder.Append('\''); + // Need this for escape characters. + builder.Append(StringUtils.Unescape(value.ToString())); + builder.Append('\''); + break; + case QuoteString(var value): builder.Append('"'); builder.Append(StringUtils.Unescape(value)); diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs index 08b276273..457a90638 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/QuoteExpressionModel.cs @@ -66,6 +66,12 @@ internal sealed record QuoteFloat(float Value) : QuoteExpression; /// The boolean value. internal sealed record QuoteBoolean(bool Value) : QuoteExpression; +/// +/// A character quote expression. +/// +/// The character value. +internal sealed record QuoteCharacter(char Value) : QuoteExpression; + /// /// A string quote expression. /// From 9a4778f627dc3ed803729fe0fb2379da0d34848b Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 12 Sep 2024 18:02:25 +0200 Subject: [PATCH 23/26] Fix some stuff for identifiers and literals --- .../Api/Syntax/Quoting/SyntaxQuoter.cs | 31 +++++++++++++++---- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index cf59e0ca6..9079267ff 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -126,24 +126,43 @@ private sealed partial class QuoteVisitor : SyntaxVisitor { public override QuoteExpression VisitSyntaxToken(SyntaxToken node) { - var kindQuote = new QuoteTokenKind(node.Kind); - return (SyntaxFacts.GetTokenText(node.Kind), node.Value) switch + return (node.Kind, SyntaxFacts.GetTokenText(node.Kind), node.Value) switch { + // Identifiers, integers, floats, and character literals all have special factory methods. + + (TokenKind.Identifier, _, _) => new QuoteFunctionCall("Identifier", [ + new QuoteString(node.Text!) + ]), + + (TokenKind.LiteralInteger, _, var value) => new QuoteFunctionCall("Integer", [ + new QuoteInteger((int)value!) + ]), + + (TokenKind.LiteralFloat, _, var value) => new QuoteFunctionCall("Float", [ + new QuoteFloat((float)value!) + ]), + (TokenKind.LiteralCharacter, _, var value) => new QuoteFunctionCall("Character", [ + new QuoteCharacter((char)value!) + ]), + + // True and false have their respective values in the token, but we don't want that. + (TokenKind.KeywordTrue or TokenKind.KeywordFalse, _, _) => new QuoteProperty(node.Kind.ToString()), + // Token kind does not require any text nor a value - (not null, null) => new QuoteProperty(node.Kind.ToString()), + (_, not null, null) => new QuoteProperty(node.Kind.ToString()), // Token kind requires text - (null, null) => new QuoteFunctionCall(node.Kind.ToString(), [ + (_, null, null) => new QuoteFunctionCall(node.Kind.ToString(), [ new QuoteString(StringUtils.Unescape(node.Text)) ]), // Token kind requires a value - (not null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ + (_, not null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ QuoteObjectLiteral(node.Value) ]), // Token kind requires both text and a value - (null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ + (_, null, not null) => new QuoteFunctionCall(node.Kind.ToString(), [ new QuoteString(StringUtils.Unescape(node.Text)), QuoteObjectLiteral(node.Value) ]) From 3bdcb0f9538f09a3ab50ac51627716353f75cc04 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 12 Sep 2024 18:14:35 +0200 Subject: [PATCH 24/26] Add CLI enums --- src/Draco.Compiler.DevHost/Program.cs | 42 ++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/src/Draco.Compiler.DevHost/Program.cs b/src/Draco.Compiler.DevHost/Program.cs index f24593547..886f6fe46 100644 --- a/src/Draco.Compiler.DevHost/Program.cs +++ b/src/Draco.Compiler.DevHost/Program.cs @@ -14,6 +14,19 @@ namespace Draco.Compiler.DevHost; internal class Program { + private enum CliQuoteMode + { + file, + decl, + stmt, + expr, + } + + private enum CliOutputLanguage + { + cs, + } + private static IEnumerable BclReferences => ReferenceInfos.All .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))); @@ -28,8 +41,8 @@ private static RootCommand ConfigureCommands() var referencesOption = new Option(["-r", "--reference"], Array.Empty, "Specifies additional assembly references to use when compiling"); var filesArgument = new Argument("source files", Array.Empty, "Specifies draco source files that should be compiled"); var rootModuleOption = new Option(["-m", "--root-module"], () => null, "Specifies the root module folder of the compiled files"); - var quoteModeOption = new Option(["-m", "--quote-mode"], () => QuoteMode.File, "Specifies the kind of syntactic element to quote"); - var outputLanguageOption = new Option(["-l", "--language"], () => OutputLanguage.CSharp, "Specifies the language to output the quoted code as"); + var quoteModeOption = new Option(["-m", "--quote-mode"], () => CliQuoteMode.file, "Specifies the kind of syntactic element to quote"); + var outputLanguageOption = new Option(["-l", "--language"], () => CliOutputLanguage.cs, "Specifies the language to output the quoted code as"); var prettyPrintOption = new Option(["-p", "--pretty-print"], () => false, "Whether to append whitespace to the output code"); var staticImportOption = new Option(["-s", "--require-static-import"], () => false, "Whether to require the SyntaxFactory class to be imported statically in the output code"); @@ -197,10 +210,31 @@ private static void FormatCommand(FileInfo input, FileInfo? output) new StreamWriter(outputStream).Write(syntaxTree.Format().ToString()); } - private static void QuoteCommand(FileInfo input, QuoteMode mode, OutputLanguage outputLanguage, bool prettyPrint, bool staticImport) + private static void QuoteCommand( + FileInfo input, + CliQuoteMode mode, + CliOutputLanguage outputLanguage, + bool prettyPrint, + bool staticImport) { var sourceText = SourceText.FromFile(input.FullName); - var quotedText = SyntaxQuoter.Quote(sourceText, mode, outputLanguage, prettyPrint, staticImport); + var quotedText = SyntaxQuoter.Quote( + sourceText, + mode switch + { + CliQuoteMode.file => QuoteMode.File, + CliQuoteMode.decl => QuoteMode.Declaration, + CliQuoteMode.stmt => QuoteMode.Statement, + CliQuoteMode.expr => QuoteMode.Expression, + _ => throw new ArgumentOutOfRangeException(nameof(mode)) + }, + outputLanguage switch + { + CliOutputLanguage.cs => OutputLanguage.CSharp, + _ => throw new ArgumentOutOfRangeException(nameof(outputLanguage)) + }, + prettyPrint, + staticImport); Console.WriteLine(quotedText); } From ca796d9d0b647d91af19b72693ce00504778ab96 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 12 Sep 2024 18:24:40 +0200 Subject: [PATCH 25/26] Add a couple simplification rules --- .../Syntax/Quoting/CSharpQuoterTemplate.cs | 25 ++++++++++++ .../Api/Syntax/Quoting/SyntaxQuoter.cs | 9 +---- .../SyntaxTree/Template.cs | 39 +++++++++++++++++-- 3 files changed, 63 insertions(+), 10 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs index 6d7bc6a6d..3fbd9f91b 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs @@ -103,6 +103,16 @@ private void AppendFunctionCall(QuoteFunctionCall call) builder.Append('>'); } + // Place function calls with a single simple expression as its argument on a single line. + if (call.Arguments is [var singleArg] && IsSimple(singleArg)) + { + builder.Append('('); + this.AppendExpr(singleArg); + builder.Append(')'); + + return; + } + builder.Append('('); this.indentLevel += 1; for (var i = 0; i < args.Length; i++) @@ -152,4 +162,19 @@ private void TryAppendNewLine() { if (prettyPrint) builder.Append(Environment.NewLine); } + + /// + /// Checks whether an expression is "simple" and can be placed on the same line as a function call when passed as an argument. + /// + /// The expression to check. + /// Whether the expression is simple. + private static bool IsSimple(QuoteExpression expr) => expr + is QuoteProperty + or QuoteNull + or QuoteTokenKind + or QuoteInteger + or QuoteFloat + or QuoteBoolean + or QuoteCharacter + or QuoteString; } diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs index 9079267ff..b28fcae9f 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/SyntaxQuoter.cs @@ -130,9 +130,7 @@ public override QuoteExpression VisitSyntaxToken(SyntaxToken node) { // Identifiers, integers, floats, and character literals all have special factory methods. - (TokenKind.Identifier, _, _) => new QuoteFunctionCall("Identifier", [ - new QuoteString(node.Text!) - ]), + (TokenKind.Identifier, _, _) => new QuoteString(node.Text!), (TokenKind.LiteralInteger, _, var value) => new QuoteFunctionCall("Integer", [ new QuoteInteger((int)value!) @@ -173,10 +171,7 @@ public override QuoteExpression VisitSyntaxTrivia(SyntaxTrivia node) => throw new NotSupportedException("Quoter does currently not support quoting syntax trivia."); public override QuoteExpression VisitSyntaxList(SyntaxList node) => - new QuoteFunctionCall( - "SyntaxList", - [typeof(TNode).FullName!], // Todo: hack - [new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray())]); + new QuoteList(node.Select(n => n.Accept(this)).ToImmutableArray()); public override QuoteExpression VisitSeparatedSyntaxList(SeparatedSyntaxList node) => new QuoteFunctionCall( diff --git a/src/Draco.SourceGeneration/SyntaxTree/Template.cs b/src/Draco.SourceGeneration/SyntaxTree/Template.cs index 1b0f9a14c..ce7dd22ab 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/Template.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/Template.cs @@ -138,7 +138,7 @@ public static partial class SyntaxQuoter { private sealed partial class QuoteVisitor { - {{ForEach(tree.Nodes.Where(x => !x.IsAbstract), QuoterMethod)}} + {{ForEach(tree.Nodes.Where(x => !x.IsAbstract), node => QuoterMethod(node, tree))}} } } """); @@ -445,15 +445,36 @@ private static string FieldPrefix(Field field) => $$""" private static string InternalType(string type) => $"Internal.Syntax.{type.Replace("<", " $$""" + private static string QuoterMethod(Node node, Tree tree) => $$""" public override QuoteExpression {{VisitorName(node)}}({{node.Name}} node) => new QuoteFunctionCall("{{FactoryName(node)}}", [ {{ForEach(node.Fields, field => $$""" - node.{{field.Name}}{{When(field.IsNullable, "?")}}.Accept(this){{When(field.IsNullable, " ?? new QuoteNull()")}}, + {{When(!FieldCanBeOmitted(field, tree), $$""" + {{QuoterMethodField(field, tree)}}, + """)}} """)}} ]); """; + private static string QuoterMethodField(Field field, Tree tree) => $$""" +{{When(field.IsNullable, $$""" + {{When(FieldCanBeTokenKind(field, tree), $$""" + node.{{field.Name}} is not null ? new QuoteTokenKind(node.{{field.Name}}.Kind) : new QuoteNull() + """, + $$""" + node.{{field.Name}}?.Accept(this) ?? new QuoteNull() + """)}} + """, +$$""" + {{When(FieldCanBeTokenKind(field, tree), $$""" + new QuoteTokenKind(node.{{field.Name}}.Kind) + """, + $$""" + node.{{field.Name}}.Accept(this) + """)}} + """)}} +"""; + private static string FactoryName(Node node) => RemoveSuffix(node.Name, "Syntax"); private static string AbstractSealed(Node node) => When(node.IsAbstract, "abstract", "sealed"); private static string ProtectedPublic(Node node) => When(node.IsAbstract, "protected", "public"); @@ -463,6 +484,18 @@ private static string QuoterMethod(Node node) => $$""" private static string DocName(FieldFacade field) => field.ParameterName is null ? string.Empty : RemovePrefix(field.ParameterName, "@"); + private static bool FieldCanBeOmitted(Field field, Tree tree) => + field.IsToken && + field.TokenKinds is [var kind] && + tree.Tokens + .First(token => token.Name == kind) + .Text is not null; + private static bool FieldCanBeTokenKind(Field field, Tree tree) => + field.IsToken && + field.TokenKinds.All(kind => + tree.Tokens + .First(token => token.Name == kind) + .Text is not null); private readonly record struct FieldFacade( bool IsOriginal, From 7f84576f40a827cd0c96446cd0f202a8eabbcbe1 Mon Sep 17 00:00:00 2001 From: thinker227 Date: Thu, 12 Sep 2024 19:13:47 +0200 Subject: [PATCH 26/26] Fix QuoteTokenKind not appending TokenKind. --- src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs index 3fbd9f91b..a704808be 100644 --- a/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs +++ b/src/Draco.Compiler/Api/Syntax/Quoting/CSharpQuoterTemplate.cs @@ -51,6 +51,7 @@ private void AppendExpr(QuoteExpression expr) break; case QuoteTokenKind(var kind): + builder.Append("TokenKind."); builder.Append(kind.ToString()); break;