From 68065d6cd300f82be441d206b7bb87a46266ff75 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 26 Feb 2024 08:48:30 +0100 Subject: [PATCH 01/76] Just collapible values for now. --- .../Internal/Syntax/Formatting/Formatter.cs | 665 +++--------------- 1 file changed, 80 insertions(+), 585 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 39e345035..8bc24c15c 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -2,22 +2,58 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Threading.Tasks; using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Solver.Tasks; +using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Syntax.Formatting; -/// -/// A formatter for the syntax tree. -/// +static class MaybeWhitespaceExtension +{ + public static WhiteSpaceOrToken AsMaybe(this SyntaxToken token) => new(token, false, 0); +} + +class WhiteSpaceOrToken +{ + public WhiteSpaceOrToken(SyntaxToken? token, bool HasReturnLine, int whiteSpaceCount) + { + this.Token = token; + this.DoesReturnLine = HasReturnLine; + this.WhiteSpaceCount = whiteSpaceCount; + } + + public SyntaxToken? Token { get; } + public virtual bool DoesReturnLine { get; set; } + public int WhiteSpaceCount { get; set; } + + public static WhiteSpaceOrToken NewLine(int whiteSpaceCount) => new(null, true, whiteSpaceCount); +} + +class MaybeWhiteSpace : WhiteSpaceOrToken +{ + public MaybeWhiteSpace(int whiteSpaceCount) : base(null, false, whiteSpaceCount) + { + } + + public bool Collapsed { get; set; } +} + + + internal sealed class Formatter : SyntaxVisitor { + private readonly List _tokens = new(); + private readonly Stack _indents = new(); + private int CurrentWhiteSpaceCount => this._indents.Sum(); + /// /// Formats the given syntax tree. /// /// The syntax tree to format. /// The formatter settings to use. /// The formatted tree. - public static SyntaxTree Format(SyntaxTree tree, FormatterSettings? settings = null) + public static IEnumerable Format(SyntaxTree tree, FormatterSettings? settings = null) { settings ??= FormatterSettings.Default; var formatter = new Formatter(settings); @@ -25,25 +61,6 @@ public static SyntaxTree Format(SyntaxTree tree, FormatterSettings? settings = n // Construct token sequence tree.GreenRoot.Accept(formatter); - // Apply constraints - formatter.ApplyConstraints(); - - // Re-parse into tree - var tokens = formatter.tokens - .Select(t => t.Build()) - .ToArray(); - var tokenSource = TokenSource.From(tokens.AsMemory()); - // TODO: Pass in anything for diagnostics? - var parser = new Parser(tokenSource, diagnostics: new()); - // TODO: Is it correct to assume compilation unit? - var formattedRoot = parser.ParseCompilationUnit(); - - return new SyntaxTree( - // TODO: Is this correct to pass it in? - sourceText: tree.SourceText, - greenRoot: formattedRoot, - // TODO: Anything smarter to pass in? - syntaxDiagnostics: new()); } /// @@ -51,595 +68,73 @@ public static SyntaxTree Format(SyntaxTree tree, FormatterSettings? settings = n /// public FormatterSettings Settings { get; } - private readonly List tokens = new(); - private readonly SyntaxList.Builder currentTrivia = new(); - // A list of groups of indices of tokens that should be aligned together - private readonly List> alignmentGroupIndices = new(); - private int indentation; - private Formatter(FormatterSettings settings) { this.Settings = settings; } - // Constraints ///////////////////////////////////////////////////////////// - - private void ApplyConstraints() - { - foreach (var group in this.alignmentGroupIndices) this.ApplyAlignmentConstraint(group); - } - - private void ApplyAlignmentConstraint(ImmutableArray indices) - { - // Compute the offset of each token - var tokensWithOffsets = indices - .Select(i => (Index: i, Offset: this.GetColumnOfToken(i))) - .ToList(); - - // Find the largest offset among the tokens - var maxColumn = tokensWithOffsets.Max(p => p.Offset); - - // Add padding to all tokens - foreach (var (index, offset) in tokensWithOffsets) - { - var token = this.tokens[index]; - var padding = maxColumn - offset; - if (padding > 0) token.LeadingTrivia.Add(this.Settings.PaddingTrivia(padding)); - } - } - - // Formatters ////////////////////////////////////////////////////////////// - - public override void VisitCompilationUnit(CompilationUnitSyntax node) - { - this.FormatWithImports(node.Declarations); - // NOTE: Is is safe to clear this? - this.currentTrivia.Clear(); - this.Newline(); - this.Place(node.End); - } - - public override void VisitDeclarationStatement(DeclarationStatementSyntax node) - { - this.Place(node.Declaration); - this.Newline(); - } - public override void VisitImportDeclaration(ImportDeclarationSyntax node) { - this.Place(node.ImportKeyword); - this.Space(); - this.Place(node.Path); - this.Place(node.Semicolon); - this.Newline(); - } - - public override void VisitLabelDeclaration(LabelDeclarationSyntax node) - { - this.Unindent(); - this.Place(node.Name); - this.Place(node.Colon); - this.Indent(); - } - - public override void VisitVariableDeclaration(VariableDeclarationSyntax node) - { - this.Place(node.Keyword); - this.Space(); - this.Place(node.Name); - this.Place(node.Type); - this.Place(node.Value); - this.Place(node.Semicolon); - } - - public override void VisitFunctionDeclaration(FunctionDeclarationSyntax node) - { - this.Place(node.FunctionKeyword); - this.Space(); - this.Place(node.Name); - this.Place(node.Generics); - this.Place(node.OpenParen); - this.AfterSeparator(node.ParameterList, this.Space); - this.Place(node.CloseParen); - this.Place(node.ReturnType); - this.Space(); - this.Place(node.Body); - } - - public override void VisitGenericParameterList(GenericParameterListSyntax node) - { - this.Place(node.OpenBracket); - this.AfterSeparator(node.Parameters, this.Space); - this.Place(node.CloseBracket); - } - - public override void VisitBlockFunctionBody(BlockFunctionBodySyntax node) - { - this.Place(node.OpenBrace); - if (node.Statements.Count > 0) this.Newline(); - this.Indent(); - this.FormatWithImports(node.Statements); - this.Unindent(); - this.Place(node.CloseBrace); - this.Newline(2); - } - - public override void VisitInlineFunctionBody(InlineFunctionBodySyntax node) - { - this.Place(node.Assign); - this.Space(); - this.Indent(); - this.Place(node.Value); - this.Place(node.Semicolon); - this.Unindent(); - this.Newline(2); - } - - public override void VisitGenericType(GenericTypeSyntax node) - { - this.Place(node.Instantiated); - this.Place(node.OpenBracket); - this.AfterSeparator(node.Arguments, this.Space); - this.Place(node.CloseBracket); - } - - public override void VisitExpressionStatement(ExpressionStatementSyntax node) - { - this.Place(node.Expression); - this.Place(node.Semicolon); - this.Newline(); - } - - public override void VisitReturnExpression(ReturnExpressionSyntax node) - { - this.Place(node.ReturnKeyword); - this.SpaceBeforeNotNull(node.Value); - } - - public override void VisitGotoExpression(GotoExpressionSyntax node) - { - this.Place(node.GotoKeyword); - this.Space(); - this.Place(node.Target); - } - - public override void VisitIfExpression(IfExpressionSyntax node) - { - this.Place(node.IfKeyword); - this.Space(); - this.Place(node.OpenParen); - this.Place(node.Condition); - this.Place(node.CloseParen); - this.Space(); - this.Place(node.Then); - this.SpaceBeforeNotNull(node.Else); - } - - public override void VisitElseClause(ElseClauseSyntax node) - { - this.Place(node.ElseKeyword); - this.Space(); - this.Place(node.Expression); - } - - public override void VisitWhileExpression(WhileExpressionSyntax node) - { - this.Place(node.WhileKeyword); - this.Space(); - this.Place(node.OpenParen); - this.Place(node.Condition); - this.Place(node.CloseParen); - this.Space(); - this.Place(node.Then); - } - - public override void VisitForExpression(ForExpressionSyntax node) - { - this.Place(node.ForKeyword); - this.Space(); - this.Place(node.OpenParen); - this.Place(node.Iterator); - this.Place(node.ElementType); - this.Space(); - this.Place(node.InKeyword); - this.Space(); - this.Place(node.Sequence); - this.Place(node.CloseParen); - this.Space(); - this.Place(node.Then); - } - - public override void VisitBlockExpression(BlockExpressionSyntax node) - { - this.Place(node.OpenBrace); - if (node.Statements.Count > 0 || node.Value is not null) this.Newline(); - this.Indent(); - this.FormatWithImports(node.Statements); - if (node.Value is not null) - { - this.Place(node.Value); - this.Newline(); - } - this.Unindent(); - this.Place(node.CloseBrace); + this._tokens.AddRange(node.Tokens.Select(s => s.AsMaybe())); + this._tokens.Add(WhiteSpaceOrToken.NewLine(this.CurrentWhiteSpaceCount)); } public override void VisitCallExpression(CallExpressionSyntax node) { - this.Place(node.Function); - this.Place(node.OpenParen); - this.AfterSeparator(node.ArgumentList, this.Space); - this.Place(node.CloseParen); - } - - public override void VisitGenericExpression(GenericExpressionSyntax node) - { - this.Place(node.Instantiated); - this.Place(node.OpenBracket); - this.AfterSeparator(node.Arguments, this.Space); - this.Place(node.CloseBracket); - } - - public override void VisitUnaryExpression(UnaryExpressionSyntax node) - { - this.Place(node.Operator); - if (SyntaxFacts.IsKeyword(node.Operator.Kind)) this.Space(); - this.Place(node.Operand); - } - - public override void VisitBinaryExpression(BinaryExpressionSyntax node) - { - this.Place(node.Left); - this.Space(); - this.Place(node.Operator); - this.Space(); - this.Place(node.Right); - } - - public override void VisitComparisonElement(ComparisonElementSyntax node) - { - this.Space(); - this.Place(node.Operator); - this.Space(); - this.Place(node.Right); - } - - public override void VisitTypeSpecifier(TypeSpecifierSyntax node) - { - this.Place(node.Colon); - this.Space(); - this.Place(node.Type); - } - - public override void VisitValueSpecifier(ValueSpecifierSyntax node) - { - this.Space(); - this.Place(node.Assign); - this.Space(); - this.Place(node.Value); - } - - // Formatting a list with potential import declarations within - private void FormatWithImports(SyntaxList list) - where T : SyntaxNode - { - var lastWasImport = false; - foreach (var item in list) - { - if (item is ImportDeclarationSyntax) - { - if (!lastWasImport) this.Newline(2); - lastWasImport = true; - } - else - { - if (lastWasImport) this.Newline(2); - lastWasImport = false; - } - this.Place(item); - } - } - - public override void VisitStringExpression(StringExpressionSyntax node) - { - var isMultiline = node.OpenQuotes.Kind == TokenKind.MultiLineStringStart; - var cutoff = SyntaxFacts.ComputeCutoff(node); - - this.Place(node.OpenQuotes); - if (isMultiline) - { - this.Newline(); - this.Indent(); - } - - var cutoffString = this.Settings.IndentationString(this.indentation); - - var isNewLine = true; - foreach (var part in node.Parts) - { - var toInsert = part; - if (isMultiline && isNewLine) - { - if (part is TextStringPartSyntax { Content.Kind: TokenKind.StringContent } textPart) - { - var content = textPart.Content.ToBuilder(); - if (content.Text is not null && content.Text.StartsWith(cutoff)) - { - content.Text = content.Text[cutoff.Length..]; - content.Text = string.Concat(cutoffString, content.Text); - content.Value = content.Text; - } - toInsert = new TextStringPartSyntax(content.Build()); - } - } - this.Place(toInsert); - isNewLine = part is TextStringPartSyntax { Content.Kind: TokenKind.StringNewline }; - } - - if (isMultiline) this.Newline(); - this.Place(node.CloseQuotes); - if (isMultiline) this.Unindent(); - } - - // ELemental token formatting - public override void VisitSyntaxToken(SyntaxToken node) - { - var builder = node.ToBuilder(); - - if (this.Settings.NormalizeStringNewline && builder.Kind == TokenKind.StringNewline) - { - builder.Text = this.Settings.Newline; - } - - if (!IsStringContent(node.Kind)) - { - // Normalize trivia - this.NormalizeLeadingTrivia(builder.LeadingTrivia, this.indentation); - this.NormalizeTrailingTrivia(builder.TrailingTrivia, this.indentation); - } - - // Add what is accumulated - builder.LeadingTrivia.InsertRange(0, this.currentTrivia); - - // Indent - if (this.tokens.Count > 0 && !IsStringContent(node.Kind)) - { - this.EnsureIndentation(this.tokens[^1].TrailingTrivia, builder.LeadingTrivia, this.indentation); - } - - // Clear state - this.currentTrivia.Clear(); - - // Append - this.tokens.Add(builder); - } - - // Format actions ////////////////////////////////////////////////////////// - - private int Place(SyntaxNode? node) - { - var index = this.tokens.Count; - node?.Accept(this); - return index; - } - private void Indent() => ++this.indentation; - private void Unindent() => --this.indentation; - private void Space() - { - if (this.tokens.Count == 0) return; - this.EnsureSpace(this.tokens[^1].TrailingTrivia, this.currentTrivia); - } - private void Newline(int amount = 1) - { - if (this.tokens.Count == 0) return; - this.EnsureNewline(this.tokens[^1].TrailingTrivia, this.currentTrivia, amount); - } - private void SpaceBeforeNotNull(SyntaxNode? node) - { - if (node is null) return; - this.Space(); - this.Place(node); - } - private void AfterSeparator(SeparatedSyntaxList list, Action afterSep) - where T : SyntaxNode - { - var isSeparator = false; - foreach (var item in list) - { - this.Place(item); - if (isSeparator) afterSep(); - isSeparator = !isSeparator; - } + //this._tokens.Add(WhiteSpaceOrToken.NewLine(CurrentWhiteSpaceCount)); + base.VisitCallExpression(node); } +} - // Low level utilities ///////////////////////////////////////////////////// +internal class CollapsibleBool +{ + private readonly SolverTaskCompletionSource tcs = new(); - private void NormalizeLeadingTrivia( - SyntaxList.Builder trivia, - int indentation) - { - static bool IsSpace(SyntaxTrivia trivia) => - trivia.Kind is TriviaKind.Newline or TriviaKind.Whitespace; + public void Collapse(bool collapse) => this.tcs.SetResult(collapse); - static bool IsComment(SyntaxTrivia trivia) => - trivia.Kind is TriviaKind.LineComment or TriviaKind.DocumentationComment; + public SolverTask Collapsed => this.tcs.Task; +} - // Remove all space - for (var i = 0; i < trivia.Count;) - { - if (IsSpace(trivia[i])) trivia.RemoveAt(i); - else ++i; - } +internal class CollapsibleInt(int CurrentValue) +{ + private readonly SolverTaskCompletionSource tcs = new(); - // Indent the trivia if needed - if (this.tokens.Count > 0) - { - this.EnsureIndentation(this.tokens[^1].TrailingTrivia, trivia, indentation); - } + // order by desc + private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; - // Before each comment or doc comment, we add a newline, then indentation - // Except the first one, which just got indented - var isFirst = true; - for (var i = 0; i < trivia.Count; ++i) - { - if (!IsComment(trivia[i])) continue; - if (isFirst) - { - isFirst = false; - continue; - } - // A comment comes next, add newline then indentation - trivia.Insert(i, this.Settings.NewlineTrivia); - if (indentation > 0) trivia.Insert(i + 1, this.Settings.IndentationTrivia(indentation)); - } - } - - private void NormalizeTrailingTrivia( - SyntaxList.Builder trivia, - int indentation) + public void Add(int toAdd) { - static bool IsSpace(SyntaxTrivia trivia) => - trivia.Kind is TriviaKind.Newline or TriviaKind.Whitespace; - - // Remove all space - for (var i = 0; i < trivia.Count;) - { - if (IsSpace(trivia[i])) trivia.RemoveAt(i); - else ++i; - } - - // If nonempty, add a space and a newline at the end - if (trivia.Count > 0) + CurrentValue += toAdd; + if (this._whenTcs is null) return; + var i = this._whenTcs.Count; + for (; i >= 0; i--) { - trivia.Insert(0, this.Settings.SpaceTrivia); - trivia.Add(this.Settings.NewlineTrivia); + var (value, tcs) = this._whenTcs![i]; + if (CurrentValue < value) break; + tcs.SetResult(new Unit()); } + this._whenTcs.RemoveRange(i, this._whenTcs.Count - i + 1); } - private void EnsureIndentation( - SyntaxList.Builder first, - SyntaxList.Builder second, - int indentation) - { - // The first didn't end in a newline, no need to indent - if (first.Count == 0) return; - if (first[^1].Kind != TriviaKind.Newline) return; - - // Trim the second one - TrimLeft(second, TriviaKind.Whitespace); + public void Collapse() => this.tcs.SetResult(CurrentValue); - // Add the indentation, if it's > 0 - if (indentation > 0) second.Insert(0, this.Settings.IndentationTrivia(indentation)); - } + public SolverTask Collapsed => this.tcs.Task; - private void EnsureSpace( - SyntaxList.Builder first, - SyntaxList.Builder second) + public SolverTask WhenReaching(int number) { - static bool IsSpace(SyntaxTrivia trivia) => - trivia.Kind is TriviaKind.Newline or TriviaKind.Whitespace; - - if (first.Count > 0 && IsSpace(first[^1])) return; - if (second.Count > 0 && IsSpace(second[0])) return; - - // We can just append at the end of the first - first.Add(this.Settings.SpaceTrivia); + if (CurrentValue >= number) return SolverTask.FromResult(new Unit()); + this._whenTcs ??= []; + var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); + if (index > 0) return this._whenTcs[index].Tcs.Task; + var tcs = new SolverTaskCompletionSource(); + this._whenTcs.Insert(~index, (number, tcs)); + return tcs.Task; } - private void EnsureNewline( - SyntaxList.Builder first, - SyntaxList.Builder second, - int amount) + private class Comparer : IComparer<(int, SolverTaskCompletionSource)> { - // Count existing - var firstNewlines = 0; - for (var i = first.Count - 1; i >= 0; --i) - { - if (first[i].Kind != TriviaKind.Newline) break; - ++firstNewlines; - } - var secondNewlines = 0; - for (var i = 0; i < second.Count; ++i) - { - if (second[i].Kind != TriviaKind.Newline) break; - ++secondNewlines; - } - - // Append any that's needed - var missing = amount - (firstNewlines + secondNewlines); - for (var i = 0; i < missing; ++i) - { - if (i == 0 && firstNewlines == 0) - { - // The first didn't end in a newline, its trailing trivia can end in a newline - // Add the first one there - first.Add(this.Settings.NewlineTrivia); - } - else - { - // Add to second - second.Insert(0, this.Settings.NewlineTrivia); - } - } - } - - /// - /// Computes the starting column of a token. - /// - /// The index of the token in question. - /// The offset of the token from the start of its line. - private int GetColumnOfToken(int tokenIndex) - { - var token = this.tokens[tokenIndex]; - var offset = 0; - // First consider leading trivia - for (var i = token.LeadingTrivia.Count - 1; i >= 0; --i) - { - var trivia = token.LeadingTrivia[i]; - if (trivia.Kind == TriviaKind.Newline) return offset; - offset += trivia.FullWidth; - } - // Then all other tokens previously - for (var i = tokenIndex - 1; i >= 0; --i) - { - var prevToken = this.tokens[i]; - // Consider its trailing trivia - for (var j = prevToken.TrailingTrivia.Count - 1; j >= 0; --j) - { - var trivia = prevToken.TrailingTrivia[j]; - if (trivia.Kind == TriviaKind.Newline) return offset; - offset += trivia.FullWidth; - } - // Then the token itself - offset += prevToken.Text?.Length ?? 0; - // Then its leading trivia - for (var j = prevToken.LeadingTrivia.Count - 1; j >= 0; --j) - { - var trivia = prevToken.LeadingTrivia[j]; - if (trivia.Kind == TriviaKind.Newline) return offset; - offset += trivia.FullWidth; - } - } - // We're at the start of the token sequence, we were in the first line - return offset; - } - - private static void TrimLeft(SyntaxList.Builder builder, params TriviaKind[] toTrim) - { - var n = 0; - while (builder.Count > n && toTrim.Contains(builder[n].Kind)) ++n; - builder.RemoveRange(0, n); + public static Comparer Instance { get; } = new Comparer(); + // reverse comparison. + public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); } - - private static void TrimRight(SyntaxList.Builder builder, params TriviaKind[] toTrim) - { - var n = 0; - while (builder.Count > n && toTrim.Contains(builder[builder.Count - n - 1].Kind)) ++n; - builder.RemoveRange(builder.Count - n - 1, n); - } - - // Token facts ///////////////////////////////////////////////////////////// - - private static bool IsStringContent(TokenKind kind) => - kind is TokenKind.StringContent or TokenKind.StringNewline; -} +} \ No newline at end of file From ff63e2a27986f879b5d8e97ba021fab7efe84fe0 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 6 Mar 2024 01:22:43 +0100 Subject: [PATCH 02/76] wip --- src/Draco.Compiler.Cli/Program.cs | 2 +- .../Syntax/ParseTreeFormatterTests.cs | 15 +- src/Draco.Compiler/Api/Syntax/SyntaxTree.cs | 2 +- .../Internal/Syntax/Formatting/Formatter.cs | 279 +++++++++++++++--- .../Capabilities/TextDocumentFormatting.cs | 3 +- .../Templates/SyntaxTree.sbncs | 5 +- 6 files changed, 253 insertions(+), 53 deletions(-) diff --git a/src/Draco.Compiler.Cli/Program.cs b/src/Draco.Compiler.Cli/Program.cs index badbe653d..aa816ddb0 100644 --- a/src/Draco.Compiler.Cli/Program.cs +++ b/src/Draco.Compiler.Cli/Program.cs @@ -179,7 +179,7 @@ private static void FormatCommand(FileInfo input, FileInfo? output) { var syntaxTree = GetSyntaxTrees(input).First(); using var outputStream = OpenOutputOrStdout(output); - new StreamWriter(outputStream).Write(syntaxTree.Format().ToString()); + new StreamWriter(outputStream).Write(syntaxTree.Format()); } private static ImmutableArray GetSyntaxTrees(params FileInfo[] input) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 10bc8b841..a37174be7 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -1,9 +1,18 @@ using Draco.Compiler.Api.Syntax; +using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; +using Xunit.Abstractions; namespace Draco.Compiler.Tests.Syntax; public sealed class SyntaxTreeFormatterTests { + private readonly ITestOutputHelper logger; + + public SyntaxTreeFormatterTests(ITestOutputHelper logger) + { + this.logger = logger; + } + [Fact] public void TestFormatting() { @@ -89,7 +98,9 @@ func main() { """"; - var actual = SyntaxTree.Parse(input).Format().ToString(); + var actual = SyntaxTree.Parse(input).Format(); + Console.WriteLine(actual); + this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -116,7 +127,7 @@ func main() { } """; - var actual = SyntaxTree.Parse(input).Format().ToString(); + var actual = SyntaxTree.Parse(input).Format(); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } } diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs index eced7c668..e7aedb6c1 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs @@ -157,7 +157,7 @@ public ImmutableArray SyntaxTreeDiff(SyntaxTree other) => /// Syntactically formats this . /// /// The formatted tree. - public SyntaxTree Format() => Formatter.Format(this); + public string Format() => Formatter.Format(this); /// /// The internal root of the tree. diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 8bc24c15c..1e11becc0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Data.Common; +using System.Diagnostics; +using System.Diagnostics.Metrics; using System.Linq; +using System.Text; using System.Threading.Tasks; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; @@ -9,58 +13,67 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -static class MaybeWhitespaceExtension +internal sealed class Formatter : Api.Syntax.SyntaxVisitor { - public static WhiteSpaceOrToken AsMaybe(this SyntaxToken token) => new(token, false, 0); -} + private readonly List tokenDecorations = new(); + private readonly Stack scopes = new(); -class WhiteSpaceOrToken -{ - public WhiteSpaceOrToken(SyntaxToken? token, bool HasReturnLine, int whiteSpaceCount) + private TokenDecoration CurrentToken { - this.Token = token; - this.DoesReturnLine = HasReturnLine; - this.WhiteSpaceCount = whiteSpaceCount; + get => this.tokenDecorations[^1]; + set => this.tokenDecorations[^1] = value; } - public SyntaxToken? Token { get; } - public virtual bool DoesReturnLine { get; set; } - public int WhiteSpaceCount { get; set; } + private ScopeInfo CurrentScope => this.scopes.Peek(); - public static WhiteSpaceOrToken NewLine(int whiteSpaceCount) => new(null, true, whiteSpaceCount); -} + private void SetTokenDecoration(Func> stableWhen) => this.tokenDecorations[^1] = TokenDecoration.Create(stableWhen); -class MaybeWhiteSpace : WhiteSpaceOrToken -{ - public MaybeWhiteSpace(int whiteSpaceCount) : base(null, false, whiteSpaceCount) + public static async SolverTask GetIndentation(IReadOnlyCollection scopes) { + var indentation = ""; + foreach (var scope in scopes) + { + var isMaterialized = await scope.IsMaterialized.Collapsed; + if (isMaterialized) + { + indentation += scope.Indentation; + } + } + return indentation; } - public bool Collapsed { get; set; } -} - - - -internal sealed class Formatter : SyntaxVisitor -{ - private readonly List _tokens = new(); - private readonly Stack _indents = new(); - private int CurrentWhiteSpaceCount => this._indents.Sum(); - /// /// Formats the given syntax tree. /// /// The syntax tree to format. /// The formatter settings to use. /// The formatted tree. - public static IEnumerable Format(SyntaxTree tree, FormatterSettings? settings = null) + public static string Format(SyntaxTree tree, FormatterSettings? settings = null) { settings ??= FormatterSettings.Default; var formatter = new Formatter(settings); // Construct token sequence - tree.GreenRoot.Accept(formatter); + tree.Root.Accept(formatter); + var builder = new StringBuilder(); + var i = 0; + foreach (var node in tree.PreOrderTraverse()) + { + if (node is not Api.Syntax.SyntaxToken token) + continue; + var decoration = formatter.tokenDecorations[i]; + + if (decoration is not null) builder.Append(decoration.Indentation); + builder.Append(token.Text); + if (decoration is not null) + { + builder.Append(decoration.RightPadding); + builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? "\n" : ""); // will default to false if not collapsed, that what we want. + } + i++; + } + return builder.ToString(); } /// @@ -73,56 +86,230 @@ private Formatter(FormatterSettings settings) this.Settings = settings; } - public override void VisitImportDeclaration(ImportDeclarationSyntax node) + public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) + { + switch (node.Kind) + { + case TokenKind.Assign: + this.tokenDecorations.Add(TokenDecoration.Create(token => + { + token.RightPadding = " "; + return SolverTask.FromResult(" "); + })); + break; + case TokenKind.KeywordVar: + case TokenKind.KeywordVal: + case TokenKind.KeywordFunc: + case TokenKind.KeywordReturn: + case TokenKind.KeywordGoto: + this.tokenDecorations.Add(TokenDecoration.Whitespace()); + break; + default: + this.tokenDecorations.Add(null!); + break; + } + base.VisitSyntaxToken(node); + } + + public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) { - this._tokens.AddRange(node.Tokens.Select(s => s.AsMaybe())); - this._tokens.Add(WhiteSpaceOrToken.NewLine(this.CurrentWhiteSpaceCount)); + base.VisitMemberExpression(node); + this.CurrentScope.ItemsCount.Add(1); + } + + public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) + { + using var _ = this.CreateScope(this.Settings.Indentation); + var openIdx = this.tokenDecorations.Count; + base.VisitBlockFunctionBody(node); + Debug.Assert(this.tokenDecorations[openIdx] is null); + Debug.Assert(this.CurrentToken is null); + + this.tokenDecorations[openIdx] = TokenDecoration.Create(decoration => + { + decoration.DoesReturnLineCollapsible.Collapse(true); + return SolverTask.FromResult(" "); + }); + + this.CurrentToken = TokenDecoration.Create(async decoration => + { + decoration.DoesReturnLineCollapsible.Collapse(true); + return await GetIndentation(this.scopes.Skip(1).ToArray()); + }); } - public override void VisitCallExpression(CallExpressionSyntax node) + + public override void VisitCallExpression(Api.Syntax.CallExpressionSyntax node) { - //this._tokens.Add(WhiteSpaceOrToken.NewLine(CurrentWhiteSpaceCount)); + using var _ = this.CreateScope(this.Settings.Indentation); base.VisitCallExpression(node); } + + public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) + { + using var _ = this.CreateScope(this.Settings.Indentation); + var idx = this.tokenDecorations.Count; + base.VisitBlockExpression(node); + Debug.Assert(this.tokenDecorations[idx] is null); + Debug.Assert(this.CurrentToken is null); + var task = TokenDecoration.Create(async decoration => + { + decoration.DoesReturnLineCollapsible.Collapse(true); + return await GetIndentation(this.scopes.Skip(1).ToArray()); + }); + this.tokenDecorations[idx] = task; + this.CurrentToken = task; + } + + public override void VisitStatement(Api.Syntax.StatementSyntax node) + { + var idx = this.tokenDecorations.Count; + base.VisitStatement(node); + this.tokenDecorations[idx] = TokenDecoration.Create(async token => + { + token.DoesReturnLineCollapsible.Collapse(false); + return await GetIndentation(this.scopes.Skip(1).ToArray()); + }); + this.CurrentToken = TokenDecoration.Create(token => + { + token.DoesReturnLineCollapsible.Collapse(true); + return SolverTask.FromResult(""); + }); + } + + private IDisposable CreateScope(string indentation) + { + var scope = new ScopeInfo(indentation); + this.scopes.Push(scope); + return new DisposeAction(() => this.scopes.Pop()); + } +} + +internal class DisposeAction(Action action) : IDisposable +{ + public void Dispose() => action(); +} + +internal class ScopeInfo(string indentation) +{ + private readonly SolverTaskCompletionSource _stableTcs = new(); + public SolverTask WhenStable => this._stableTcs.Task; + /// + /// Represent if the scope is materialized or not. + /// An unmaterialized scope is a potential scope, which is not folded yet. + /// items.Select(x => x).ToList() have an unmaterialized scope. + /// It can be materialized like: + /// + /// items + /// .Select(x => x) + /// .ToList() + /// + /// + public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); + public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); + public string Indentation { get; } = indentation; } +internal class TokenDecoration +{ + private readonly SolverTaskCompletionSource _stableTcs = new(); + + public static TokenDecoration Create(Func> stableWhen) + { + var tokenDecoration = new TokenDecoration(); + var awaiter = stableWhen(tokenDecoration).Awaiter; + awaiter.OnCompleted(() => + { + tokenDecoration.SetStable(); + tokenDecoration.Indentation = awaiter.GetResult(); + }); + return tokenDecoration; + } + + public static TokenDecoration Whitespace() => Create(token => + { + token.DoesReturnLineCollapsible.Collapse(false); + token.RightPadding = " "; + return SolverTask.FromResult(""); + }); + + public CollapsibleBool DoesReturnLineCollapsible { get; } = CollapsibleBool.Create(); + + public SolverTask WhenStable => this._stableTcs.Task; + public string Indentation { get; private set; } = ""; + public string RightPadding { get; set; } = ""; + + private void SetStable() => this._stableTcs.SetResult(new Unit()); +} + + internal class CollapsibleBool { - private readonly SolverTaskCompletionSource tcs = new(); + private readonly SolverTaskCompletionSource? tcs; + private readonly SolverTask task; + + private CollapsibleBool(SolverTaskCompletionSource tcs) + { + this.tcs = tcs; + this.task = tcs.Task; + } + private CollapsibleBool(SolverTask task) + { + this.task = task; + } - public void Collapse(bool collapse) => this.tcs.SetResult(collapse); + public static CollapsibleBool Create() => new(new SolverTaskCompletionSource()); + public static CollapsibleBool Create(bool value) => new(SolverTask.FromResult(value)); - public SolverTask Collapsed => this.tcs.Task; + public void Collapse(bool collapse) => this.tcs?.SetResult(collapse); + + public SolverTask Collapsed => this.task; } -internal class CollapsibleInt(int CurrentValue) +internal class CollapsibleInt { - private readonly SolverTaskCompletionSource tcs = new(); + private readonly SolverTaskCompletionSource? tcs; + private readonly SolverTask task; + private int MinimumCurrentValue; + private CollapsibleInt(SolverTaskCompletionSource tcs) + { + this.tcs = tcs; + this.task = tcs.Task; + } + + private CollapsibleInt(SolverTask task) + { + this.task = task; + } + + public static CollapsibleInt Create() => new(new SolverTaskCompletionSource()); + public static CollapsibleInt Create(int value) => new(SolverTask.FromResult(value)); + // order by desc private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; public void Add(int toAdd) { - CurrentValue += toAdd; + this.MinimumCurrentValue += toAdd; if (this._whenTcs is null) return; var i = this._whenTcs.Count; for (; i >= 0; i--) { var (value, tcs) = this._whenTcs![i]; - if (CurrentValue < value) break; + if (this.MinimumCurrentValue < value) break; tcs.SetResult(new Unit()); } this._whenTcs.RemoveRange(i, this._whenTcs.Count - i + 1); } - public void Collapse() => this.tcs.SetResult(CurrentValue); + public void Collapse() => this.tcs?.SetResult(this.MinimumCurrentValue); - public SolverTask Collapsed => this.tcs.Task; + public SolverTask Collapsed => this.task; public SolverTask WhenReaching(int number) { - if (CurrentValue >= number) return SolverTask.FromResult(new Unit()); + if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(new Unit()); this._whenTcs ??= []; var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); if (index > 0) return this._whenTcs[index].Tcs.Task; @@ -137,4 +324,4 @@ private class Comparer : IComparer<(int, SolverTaskCompletionSource)> // reverse comparison. public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); } -} \ No newline at end of file +} diff --git a/src/Draco.LanguageServer/Capabilities/TextDocumentFormatting.cs b/src/Draco.LanguageServer/Capabilities/TextDocumentFormatting.cs index b887050f7..d5afc5bd0 100644 --- a/src/Draco.LanguageServer/Capabilities/TextDocumentFormatting.cs +++ b/src/Draco.LanguageServer/Capabilities/TextDocumentFormatting.cs @@ -21,10 +21,9 @@ internal sealed partial class DracoLanguageServer : ITextDocumentFormatting if (syntaxTree is null) return Task.FromResult(null as IList); var originalRange = syntaxTree.Root.Range; - syntaxTree = syntaxTree.Format(); var edit = new TextEdit() { - NewText = syntaxTree.ToString(), + NewText = syntaxTree.Format(), Range = Translator.ToLsp(originalRange), }; return Task.FromResult?>(new[] { edit }); diff --git a/src/Draco.SourceGeneration/Templates/SyntaxTree.sbncs b/src/Draco.SourceGeneration/Templates/SyntaxTree.sbncs index 71cda902f..5e9efe445 100644 --- a/src/Draco.SourceGeneration/Templates/SyntaxTree.sbncs +++ b/src/Draco.SourceGeneration/Templates/SyntaxTree.sbncs @@ -20,12 +20,15 @@ {{if return_value}} return node.Accept(this); {{else}} - node.Accept(this); + //node.Accept(this); {{end}} } {{else}} public virtual {{return_type}} Visit{{remove_suffix($node.Name, 'Syntax')}}({{$node.Name}} node) { + {{if $node.Base?.Base != null && !return_value}} + Visit{{remove_suffix($node.Base.Name, 'Syntax')}}(node); + {{end}} {{for $field in $node.Fields}} node.{{$field.Name}}{{nullable($field)}}.Accept(this); {{end}} From fc280956d8c18a5ba0025f1be729081372a43da6 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 8 Mar 2024 01:09:07 +0100 Subject: [PATCH 03/76] wip. --- .../Syntax/ParseTreeFormatterTests.cs | 2 + .../Internal/Syntax/Formatting/Formatter.cs | 234 +++++++++++------- 2 files changed, 140 insertions(+), 96 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index a37174be7..1b2ae5357 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -26,6 +26,7 @@ func main ( ) { val singleLineString = "" ; var multilineString = #""" something + test """# ; val y = 4-2 @@ -67,6 +68,7 @@ func main() { val singleLineString = ""; var multilineString = #""" something + test """#; val y = 4 - 2 mod 4 + 3; while (true) { diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 1e11becc0..99504c8f7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Data.Common; -using System.Diagnostics; -using System.Diagnostics.Metrics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; -using System.Threading.Tasks; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; using Draco.Compiler.Internal.Utilities; @@ -15,19 +11,14 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal sealed class Formatter : Api.Syntax.SyntaxVisitor { - private readonly List tokenDecorations = new(); + private TokenDecoration[] tokenDecorations = []; + private int currentIdx; private readonly Stack scopes = new(); - - private TokenDecoration CurrentToken - { - get => this.tokenDecorations[^1]; - set => this.tokenDecorations[^1] = value; - } + private readonly SyntaxTree tree; + private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; private ScopeInfo CurrentScope => this.scopes.Peek(); - private void SetTokenDecoration(Func> stableWhen) => this.tokenDecorations[^1] = TokenDecoration.Create(stableWhen); - public static async SolverTask GetIndentation(IReadOnlyCollection scopes) { var indentation = ""; @@ -51,7 +42,8 @@ public static async SolverTask GetIndentation(IReadOnlyCollection public FormatterSettings Settings { get; } - private Formatter(FormatterSettings settings) + private Formatter(FormatterSettings settings, SyntaxTree tree) { this.Settings = settings; + this.tree = tree; + } + + public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) + { + this.tokenDecorations = new TokenDecoration[node.Tokens.Count()]; + base.VisitCompilationUnit(node); } public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { switch (node.Kind) { + case TokenKind.Minus: + case TokenKind.Plus: case TokenKind.Assign: - this.tokenDecorations.Add(TokenDecoration.Create(token => - { - token.RightPadding = " "; - return SolverTask.FromResult(" "); - })); + case TokenKind.KeywordMod: + this.CurrentToken.SetWhitespace(); + this.CurrentToken.Indentation = SolverTask.FromResult(" "); break; case TokenKind.KeywordVar: case TokenKind.KeywordVal: case TokenKind.KeywordFunc: case TokenKind.KeywordReturn: case TokenKind.KeywordGoto: - this.tokenDecorations.Add(TokenDecoration.Whitespace()); - break; - default: - this.tokenDecorations.Add(null!); + case TokenKind.Colon: + case TokenKind.KeywordWhile: + this.CurrentToken.SetWhitespace(); break; } base.VisitSyntaxToken(node); + this.currentIdx++; + } + + public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax node) + { + if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) + { + base.VisitStringExpression(node); + return; + } + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.Create(true); + var i = 0; + for (; i < node.Parts.Count; i++) + { + if (node.Parts[i].Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }) + { + continue; + } + ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; + currentToken.Indentation = GetIndentation(this.scopes.ToArray()); + } + this.tokenDecorations[this.currentIdx + i].SetNewline(); + using var _ = this.CreateScope(this.Settings.Indentation, true); + this.tokenDecorations[this.currentIdx + i + 1].Indentation = GetIndentation(this.scopes.ToArray()); + base.VisitStringExpression(node); + } + + public override void VisitTextStringPart(Api.Syntax.TextStringPartSyntax node) + { + base.VisitTextStringPart(node); } public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) @@ -117,69 +145,44 @@ public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax nod this.CurrentScope.ItemsCount.Add(1); } + public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) + { + base.VisitSeparatedSyntaxList(node); + this.CurrentToken.SetWhitespace(); + } + public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { - using var _ = this.CreateScope(this.Settings.Indentation); - var openIdx = this.tokenDecorations.Count; + using var _ = this.CreateScope(this.Settings.Indentation, true); + this.CurrentScope.IsMaterialized.Collapse(true); + this.CurrentToken.SetNewline(); base.VisitBlockFunctionBody(node); - Debug.Assert(this.tokenDecorations[openIdx] is null); - Debug.Assert(this.CurrentToken is null); - - this.tokenDecorations[openIdx] = TokenDecoration.Create(decoration => - { - decoration.DoesReturnLineCollapsible.Collapse(true); - return SolverTask.FromResult(" "); - }); - - this.CurrentToken = TokenDecoration.Create(async decoration => - { - decoration.DoesReturnLineCollapsible.Collapse(true); - return await GetIndentation(this.scopes.Skip(1).ToArray()); - }); } public override void VisitCallExpression(Api.Syntax.CallExpressionSyntax node) { - using var _ = this.CreateScope(this.Settings.Indentation); + using var _ = this.CreateScope(this.Settings.Indentation, false); base.VisitCallExpression(node); } - public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) - { - using var _ = this.CreateScope(this.Settings.Indentation); - var idx = this.tokenDecorations.Count; - base.VisitBlockExpression(node); - Debug.Assert(this.tokenDecorations[idx] is null); - Debug.Assert(this.CurrentToken is null); - var task = TokenDecoration.Create(async decoration => - { - decoration.DoesReturnLineCollapsible.Collapse(true); - return await GetIndentation(this.scopes.Skip(1).ToArray()); - }); - this.tokenDecorations[idx] = task; - this.CurrentToken = task; - } - public override void VisitStatement(Api.Syntax.StatementSyntax node) { - var idx = this.tokenDecorations.Count; base.VisitStatement(node); - this.tokenDecorations[idx] = TokenDecoration.Create(async token => - { - token.DoesReturnLineCollapsible.Collapse(false); - return await GetIndentation(this.scopes.Skip(1).ToArray()); - }); - this.CurrentToken = TokenDecoration.Create(token => - { - token.DoesReturnLineCollapsible.Collapse(true); - return SolverTask.FromResult(""); - }); + ref var firstToken = ref this.tokenDecorations[this.currentIdx]; + firstToken.DoesReturnLineCollapsible = CollapsibleBool.Create(false); + firstToken.Indentation = GetIndentation(this.scopes.ToArray()); + + var endIdx = this.currentIdx + node.Tokens.Count() - 1; + ref var lastToken = ref this.tokenDecorations[endIdx]; + lastToken.DoesReturnLineCollapsible = CollapsibleBool.Create(true); + lastToken.Indentation = SolverTask.FromResult(""); } - private IDisposable CreateScope(string indentation) + private IDisposable CreateScope(string indentation, bool tangible) { var scope = new ScopeInfo(indentation); + if (tangible) scope.IsMaterialized.Collapse(true); this.scopes.Push(scope); return new DisposeAction(() => this.scopes.Pop()); } @@ -210,40 +213,60 @@ internal class ScopeInfo(string indentation) public string Indentation { get; } = indentation; } -internal class TokenDecoration + +internal static class TokenDecorationExtensions { - private readonly SolverTaskCompletionSource _stableTcs = new(); + public static void SetNewline(this ref TokenDecoration decoration) => decoration.DoesReturnLineCollapsible = CollapsibleBool.Create(true); + public static void SetWhitespace(this ref TokenDecoration decoration) => decoration.RightPadding = " "; + +} + +internal struct TokenDecoration +{ + private string? rightPadding; + private SolverTask? indentation; + private CollapsibleBool? doesReturnLineCollapsible; - public static TokenDecoration Create(Func> stableWhen) + [DisallowNull] + public CollapsibleBool? DoesReturnLineCollapsible { - var tokenDecoration = new TokenDecoration(); - var awaiter = stableWhen(tokenDecoration).Awaiter; - awaiter.OnCompleted(() => + readonly get => this.doesReturnLineCollapsible; + set { - tokenDecoration.SetStable(); - tokenDecoration.Indentation = awaiter.GetResult(); - }); - return tokenDecoration; + if (value.Equals(this.doesReturnLineCollapsible)) return; + if (this.doesReturnLineCollapsible is not null) throw new InvalidOperationException("DoesReturnLineCollapsible already set."); + this.doesReturnLineCollapsible = value; + } } - public static TokenDecoration Whitespace() => Create(token => + [DisallowNull] + public SolverTask? Indentation { - token.DoesReturnLineCollapsible.Collapse(false); - token.RightPadding = " "; - return SolverTask.FromResult(""); - }); - - public CollapsibleBool DoesReturnLineCollapsible { get; } = CollapsibleBool.Create(); - - public SolverTask WhenStable => this._stableTcs.Task; - public string Indentation { get; private set; } = ""; - public string RightPadding { get; set; } = ""; + readonly get => this.indentation; + set + { + if (this.indentation is not null) + { + if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; + throw new InvalidOperationException("Indentation already set."); + } + this.indentation = value; + } + } - private void SetStable() => this._stableTcs.SetResult(new Unit()); + public string? RightPadding + { + readonly get => this.rightPadding; + set + { + if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); + this.rightPadding = value; + } + } } -internal class CollapsibleBool +internal class CollapsibleBool : IEquatable { private readonly SolverTaskCompletionSource? tcs; private readonly SolverTask task; @@ -262,6 +285,25 @@ private CollapsibleBool(SolverTask task) public static CollapsibleBool Create(bool value) => new(SolverTask.FromResult(value)); public void Collapse(bool collapse) => this.tcs?.SetResult(collapse); + public bool Equals(CollapsibleBool? other) + { + if (other is null) return false; + + if (this.tcs is null) + { + if (other.tcs is not null) return false; + return this.task.Result == other.task.Result; + } + if (other.tcs is null) return false; + if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result == other.task.Result; + return false; + } + + public override bool Equals(object? obj) + { + if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); + return false; + } public SolverTask Collapsed => this.task; } From 1c475b108b196f1190747f04d9dcd383f781e161 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 10 Mar 2024 03:11:14 +0100 Subject: [PATCH 04/76] Simple test case pass. --- .../Internal/Syntax/Formatting/Formatter.cs | 241 ++++++++++++------ 1 file changed, 169 insertions(+), 72 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 99504c8f7..aa7013721 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -19,7 +19,7 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private ScopeInfo CurrentScope => this.scopes.Peek(); - public static async SolverTask GetIndentation(IReadOnlyCollection scopes) + public static async SolverTask GetIndentation(IReadOnlyCollection scopes) { var indentation = ""; foreach (var scope in scopes) @@ -50,21 +50,21 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var builder = new StringBuilder(); var i = 0; - foreach (var node in tree.PreOrderTraverse()) + foreach (var token in tree.Root.Tokens) { - if (node is not Api.Syntax.SyntaxToken token) - continue; var decoration = formatter.tokenDecorations[i]; - if (decoration.Indentation is not null) builder.Append(decoration.Indentation.Result); - builder.Append(token.Text); - builder.Append(decoration.RightPadding); - if (decoration.DoesReturnLineCollapsible is not null) + if (decoration.DoesReturnLineCollapsible is not null && !decoration.IndentationDontReturnLine) { builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? "\n" : ""); // will default to false if not collapsed, that what we want. } + if (decoration.Indentation is not null) builder.Append(decoration.Indentation.Result); + builder.Append(decoration.LeftPadding); + builder.Append(token.Text); + builder.Append(decoration.RightPadding); i++; } + builder.AppendLine(); return builder.ToString(); } @@ -91,19 +91,26 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { case TokenKind.Minus: case TokenKind.Plus: + case TokenKind.Star: + case TokenKind.Slash: + case TokenKind.GreaterThan: + case TokenKind.LessThan: + case TokenKind.GreaterEqual: + case TokenKind.LessEqual: + case TokenKind.Equal: case TokenKind.Assign: case TokenKind.KeywordMod: - this.CurrentToken.SetWhitespace(); - this.CurrentToken.Indentation = SolverTask.FromResult(" "); + this.CurrentToken.RightPadding = " "; + this.CurrentToken.LeftPadding = " "; break; case TokenKind.KeywordVar: case TokenKind.KeywordVal: case TokenKind.KeywordFunc: case TokenKind.KeywordReturn: case TokenKind.KeywordGoto: - case TokenKind.Colon: case TokenKind.KeywordWhile: - this.CurrentToken.SetWhitespace(); + case TokenKind.KeywordIf: + this.CurrentToken.RightPadding = " "; break; } base.VisitSyntaxToken(node); @@ -117,7 +124,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod base.VisitStringExpression(node); return; } - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.Create(true); var i = 0; for (; i < node.Parts.Count; i++) { @@ -127,17 +133,13 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod } ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; currentToken.Indentation = GetIndentation(this.scopes.ToArray()); + currentToken.IndentationDontReturnLine = i != 0; } - this.tokenDecorations[this.currentIdx + i].SetNewline(); using var _ = this.CreateScope(this.Settings.Indentation, true); this.tokenDecorations[this.currentIdx + i + 1].Indentation = GetIndentation(this.scopes.ToArray()); base.VisitStringExpression(node); } - public override void VisitTextStringPart(Api.Syntax.TextStringPartSyntax node) - { - base.VisitTextStringPart(node); - } public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) { @@ -145,38 +147,107 @@ public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax nod this.CurrentScope.ItemsCount.Add(1); } - public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) + public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { - base.VisitSeparatedSyntaxList(node); - this.CurrentToken.SetWhitespace(); + this.CurrentToken.LeftPadding = " "; + this.CreateScope(this.Settings.Indentation, true, () => base.VisitBlockFunctionBody(node)); + this.tokenDecorations[this.currentIdx - 1].Indentation = GetIndentation(this.scopes.ToArray()); } - public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) + public override void VisitStatement(Api.Syntax.StatementSyntax node) { - using var _ = this.CreateScope(this.Settings.Indentation, true); - this.CurrentScope.IsMaterialized.Collapse(true); - this.CurrentToken.SetNewline(); - base.VisitBlockFunctionBody(node); + this.CurrentScope.ItemsCount.Add(1); + + if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) + { + this.CurrentToken.Indentation = GetIndentation(this.scopes.Skip(1).ToArray()); + } + else if (node.Parent is Api.Syntax.BlockExpressionSyntax) + { + this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + } + else + { + async SolverTask Indentation() + { + var scope = this.CurrentScope; + var haveMoreThanOneStatement = await scope.ItemsCount.WhenGreaterOrEqual(2); + if (haveMoreThanOneStatement) return await GetIndentation(this.scopes.ToArray()); + return null; + } + this.CurrentToken.Indentation = Indentation(); + + } + base.VisitStatement(node); } + public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) + { + this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; + this.CreateScope(this.Settings.Indentation, false, () => base.VisitWhileExpression(node)); + } - public override void VisitCallExpression(Api.Syntax.CallExpressionSyntax node) + public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) { - using var _ = this.CreateScope(this.Settings.Indentation, false); - base.VisitCallExpression(node); + this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; + this.CreateScope(this.Settings.Indentation, false, () => + { + this.VisitExpression(node); + node.IfKeyword.Accept(this); + node.OpenParen.Accept(this); + node.Condition.Accept(this); + node.CloseParen.Accept(this); + node.Then.Accept(this); + }); + node.Else?.Accept(this); } - public override void VisitStatement(Api.Syntax.StatementSyntax node) + public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { - base.VisitStatement(node); - ref var firstToken = ref this.tokenDecorations[this.currentIdx]; - firstToken.DoesReturnLineCollapsible = CollapsibleBool.Create(false); - firstToken.Indentation = GetIndentation(this.scopes.ToArray()); - - var endIdx = this.currentIdx + node.Tokens.Count() - 1; - ref var lastToken = ref this.tokenDecorations[endIdx]; - lastToken.DoesReturnLineCollapsible = CollapsibleBool.Create(true); - lastToken.Indentation = SolverTask.FromResult(""); + this.CurrentToken.RightPadding = " "; + if (node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) + { + this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + } + else + { + this.CurrentToken.LeftPadding = " "; + } + this.CreateScope(this.Settings.Indentation, false, () => base.VisitElseClause(node)); + } + + public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) + { + // this means we are in a if/while/else, and *can* create an indentation with a regular expression folding: + // if (blabla) an expression; + // it can fold: + // if (blabla) + // an expression; + // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. + if (!this.CurrentScope.IsMaterialized.Collapsed.IsCompleted) + { + this.CurrentScope.IsMaterialized.Collapse(false); + } + + this.CreateScope(this.Settings.Indentation, true, () => + { + this.VisitExpression(node); + node.OpenBrace.Accept(this); + node.Statements.Accept(this); + if (node.Value != null) + { + this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + node.Value.Accept(this); + } + node.CloseBrace.Accept(this); + }); + this.tokenDecorations[this.currentIdx - 1].Indentation = GetIndentation(this.scopes.ToArray()); + } + + public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) + { + this.CurrentToken.RightPadding = " "; + base.VisitTypeSpecifier(node); } private IDisposable CreateScope(string indentation, bool tangible) @@ -184,7 +255,15 @@ private IDisposable CreateScope(string indentation, bool tangible) var scope = new ScopeInfo(indentation); if (tangible) scope.IsMaterialized.Collapse(true); this.scopes.Push(scope); - return new DisposeAction(() => this.scopes.Pop()); + return new DisposeAction(() => + { + var scope = this.scopes.Pop(); + scope.Dispose(); + }); + } + private void CreateScope(string indentation, bool tangible, Action action) + { + using (this.CreateScope(indentation, tangible)) action(); } } @@ -193,7 +272,7 @@ internal class DisposeAction(Action action) : IDisposable public void Dispose() => action(); } -internal class ScopeInfo(string indentation) +internal class ScopeInfo(string indentation) : IDisposable { private readonly SolverTaskCompletionSource _stableTcs = new(); public SolverTask WhenStable => this._stableTcs.Task; @@ -211,36 +290,24 @@ internal class ScopeInfo(string indentation) public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public string Indentation { get; } = indentation; -} - - -internal static class TokenDecorationExtensions -{ - public static void SetNewline(this ref TokenDecoration decoration) => decoration.DoesReturnLineCollapsible = CollapsibleBool.Create(true); - public static void SetWhitespace(this ref TokenDecoration decoration) => decoration.RightPadding = " "; + public void Dispose() => this.ItemsCount.Collapse(); } internal struct TokenDecoration { private string? rightPadding; - private SolverTask? indentation; - private CollapsibleBool? doesReturnLineCollapsible; + private string? leftPadding; + private SolverTask? indentation; [DisallowNull] - public CollapsibleBool? DoesReturnLineCollapsible - { - readonly get => this.doesReturnLineCollapsible; - set - { - if (value.Equals(this.doesReturnLineCollapsible)) return; - if (this.doesReturnLineCollapsible is not null) throw new InvalidOperationException("DoesReturnLineCollapsible already set."); - this.doesReturnLineCollapsible = value; - } - } + public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } + + public bool IndentationDontReturnLine { get; set; } + [DisallowNull] - public SolverTask? Indentation + public SolverTask? Indentation { readonly get => this.indentation; set @@ -250,10 +317,25 @@ public SolverTask? Indentation if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; throw new InvalidOperationException("Indentation already set."); } + var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); this.indentation = value; + var myThis = this; + this.indentation.Awaiter.OnCompleted(() => + { + doesReturnLine.Collapse(value.Result != null); + }); } } + public string? LeftPadding + { + readonly get => this.leftPadding; + set + { + if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); + this.leftPadding = value; + } + } public string? RightPadding { readonly get => this.rightPadding; @@ -329,41 +411,56 @@ private CollapsibleInt(SolverTask task) // order by desc - private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; + private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; public void Add(int toAdd) { this.MinimumCurrentValue += toAdd; if (this._whenTcs is null) return; - var i = this._whenTcs.Count; - for (; i >= 0; i--) + var i = this._whenTcs.Count - 1; + if (i < 0) return; + while (true) { var (value, tcs) = this._whenTcs![i]; if (this.MinimumCurrentValue < value) break; - tcs.SetResult(new Unit()); + tcs.SetResult(true); + if (i == 0) break; + i--; } - this._whenTcs.RemoveRange(i, this._whenTcs.Count - i + 1); + this._whenTcs.RemoveRange(i, this._whenTcs.Count - i); } - public void Collapse() => this.tcs?.SetResult(this.MinimumCurrentValue); + public void Collapse() + { + if (this._whenTcs is not null) + { + foreach (var (_, Tcs) in this._whenTcs ?? Enumerable.Empty<(int Value, SolverTaskCompletionSource Tcs)>()) + { + Tcs.SetResult(false); + } + this._whenTcs = null; + } + + this.tcs?.SetResult(this.MinimumCurrentValue); + } public SolverTask Collapsed => this.task; - public SolverTask WhenReaching(int number) + public SolverTask WhenGreaterOrEqual(int number) { - if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(new Unit()); + if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(true); this._whenTcs ??= []; var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); if (index > 0) return this._whenTcs[index].Tcs.Task; - var tcs = new SolverTaskCompletionSource(); + var tcs = new SolverTaskCompletionSource(); this._whenTcs.Insert(~index, (number, tcs)); return tcs.Task; } - private class Comparer : IComparer<(int, SolverTaskCompletionSource)> + private class Comparer : IComparer<(int, SolverTaskCompletionSource)> { public static Comparer Instance { get; } = new Comparer(); // reverse comparison. - public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); + public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); } } From 6be2c8c1786b1460d4f46aabff783931c6a24718 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 12 Mar 2024 23:50:15 +0100 Subject: [PATCH 05/76] wip --- .../Syntax/ParseTreeFormatterTests.cs | 22 +++++++- src/Draco.Compiler/Api/Syntax/SyntaxTree.cs | 2 +- .../Internal/Syntax/Formatting/Formatter.cs | 56 ++++++++++++++++--- .../Syntax/Formatting/FormatterSettings.cs | 14 +---- 4 files changed, 72 insertions(+), 22 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 1b2ae5357..60cd6b20f 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -101,8 +101,6 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -130,6 +128,26 @@ func main() { """; var actual = SyntaxTree.Parse(input).Format(); + + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } + + [Fact] + public void TestFoldExpression() + { + var input = """ + func aLongMethodName() = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; + """; + var actual = """ + func aLongMethodName() + = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; + """; + var expected = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 60 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } } diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs index e7aedb6c1..8481fa10d 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs @@ -157,7 +157,7 @@ public ImmutableArray SyntaxTreeDiff(SyntaxTree other) => /// Syntactically formats this . /// /// The formatted tree. - public string Format() => Formatter.Format(this); + public string Format(FormatterSettings? settings = null) => Formatter.Format(this, settings); /// /// The internal root of the tree. diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index aa7013721..50e12ff34 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Numerics; using System.Text; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; @@ -14,7 +15,7 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private TokenDecoration[] tokenDecorations = []; private int currentIdx; private readonly Stack scopes = new(); - private readonly SyntaxTree tree; + private readonly SyntaxTree tree; // debugging helper, to remove private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; private ScopeInfo CurrentScope => this.scopes.Peek(); @@ -52,11 +53,12 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var i = 0; foreach (var token in tree.Root.Tokens) { + if (token.Kind == TokenKind.StringNewline) continue; var decoration = formatter.tokenDecorations[i]; - if (decoration.DoesReturnLineCollapsible is not null && !decoration.IndentationDontReturnLine) + if (decoration.DoesReturnLineCollapsible is not null) { - builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? "\n" : ""); // will default to false if not collapsed, that what we want. + builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? settings.Newline : ""); // will default to false if not collapsed, that what we want. } if (decoration.Indentation is not null) builder.Append(decoration.Indentation.Result); builder.Append(decoration.LeftPadding); @@ -89,6 +91,8 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { switch (node.Kind) { + case TokenKind.StringNewline: // we ignore and don't render string newlines. our own indentation will take care of it. + return; case TokenKind.Minus: case TokenKind.Plus: case TokenKind.Star: @@ -110,10 +114,12 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) case TokenKind.KeywordGoto: case TokenKind.KeywordWhile: case TokenKind.KeywordIf: + case TokenKind.KeywordImport: this.CurrentToken.RightPadding = " "; break; } base.VisitSyntaxToken(node); + this.CurrentToken.TokenSize = node.Green.Width; this.currentIdx++; } @@ -133,7 +139,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod } ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; currentToken.Indentation = GetIndentation(this.scopes.ToArray()); - currentToken.IndentationDontReturnLine = i != 0; } using var _ = this.CreateScope(this.Settings.Indentation, true); this.tokenDecorations[this.currentIdx + i + 1].Indentation = GetIndentation(this.scopes.ToArray()); @@ -250,6 +255,12 @@ public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) base.VisitTypeSpecifier(node); } + public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) + { + this.tokenDecorations[this.currentIdx + node.Tokens.Count()].Indentation = GetIndentation(this.scopes.ToArray()); + base.VisitDeclaration(node); + } + private IDisposable CreateScope(string indentation, bool tangible) { var scope = new ScopeInfo(indentation); @@ -265,6 +276,37 @@ private void CreateScope(string indentation, bool tangible, Action action) { using (this.CreateScope(indentation, tangible)) action(); } + + + + private void FormatTooLongLine(int index) + { + async SolverTask GetLineWidth(int index) + { + var sum = 0; + while (index > 0) + { + index--; + var tokenDecoration = this.tokenDecorations[index]; + sum += tokenDecoration.TokenSize; + if (tokenDecoration.DoesReturnLineCollapsible is null) continue; + var doesReturnLine = await tokenDecoration.DoesReturnLineCollapsible.Collapsed; + if (doesReturnLine) + { + sum += tokenDecoration.Indentation!.Result!.Length; + break; + } + } + return sum; + } + + async SolverTask TrySplitLine(int index, int maxLine) + { + var width = await GetLineWidth(index); + if (width <= maxLine) return default; + + } + } } internal class DisposeAction(Action action) : IDisposable @@ -299,13 +341,12 @@ internal struct TokenDecoration private string? rightPadding; private string? leftPadding; private SolverTask? indentation; + public int TokenSize { get; set; } + public int TotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0); [DisallowNull] public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } - public bool IndentationDontReturnLine { get; set; } - - [DisallowNull] public SolverTask? Indentation { @@ -345,6 +386,7 @@ public string? RightPadding this.rightPadding = value; } } + } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs index fa3100be0..b65d45478 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; /// /// The settings of the formatter. /// -internal sealed class FormatterSettings +public sealed class FormatterSettings { /// /// The default formatting settings. @@ -22,10 +22,7 @@ internal sealed class FormatterSettings /// public string Indentation { get; init; } = " "; - /// - /// True, if newlines in strings should be normalized to the sequence. - /// - public bool NormalizeStringNewline { get; init; } = true; + public int LineWidth { get; init; } = 160; public string IndentationString(int amount = 1) { @@ -39,11 +36,4 @@ public string PaddingString(int width = 1) for (var i = 0; i < width; ++i) sb.Append(' '); return sb.ToString(); } - - public SyntaxTrivia NewlineTrivia => new(Api.Syntax.TriviaKind.Newline, this.Newline); - public SyntaxTrivia SpaceTrivia => new(Api.Syntax.TriviaKind.Whitespace, " "); - public SyntaxTrivia IndentationTrivia(int amount = 1) => - new(Api.Syntax.TriviaKind.Whitespace, this.IndentationString(amount)); - public SyntaxTrivia PaddingTrivia(int width = 1) => - new(Api.Syntax.TriviaKind.Whitespace, this.PaddingString(width)); } From bef4be77cffa40c8b83e79102828e2307ab9c013 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 17 Mar 2024 00:30:45 +0100 Subject: [PATCH 06/76] wip --- .../Internal/Syntax/Formatting/Formatter.cs | 58 +++++++++++++------ .../Draco.TracingDebugger.csproj | 14 +++++ src/Draco.TracingDebugger/Program.cs | 30 ++++++++++ src/Draco.sln | 13 ++++- 4 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 src/Draco.TracingDebugger/Draco.TracingDebugger.csproj create mode 100644 src/Draco.TracingDebugger/Program.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 50e12ff34..cdbb5db48 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -140,7 +140,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; currentToken.Indentation = GetIndentation(this.scopes.ToArray()); } - using var _ = this.CreateScope(this.Settings.Indentation, true); + using var _ = this.CreateFoldedScope(this.Settings.Indentation); this.tokenDecorations[this.currentIdx + i + 1].Indentation = GetIndentation(this.scopes.ToArray()); base.VisitStringExpression(node); } @@ -155,7 +155,7 @@ public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax nod public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { this.CurrentToken.LeftPadding = " "; - this.CreateScope(this.Settings.Indentation, true, () => base.VisitBlockFunctionBody(node)); + this.CreateFoldedScope(this.Settings.Indentation, () => base.VisitBlockFunctionBody(node)); this.tokenDecorations[this.currentIdx - 1].Indentation = GetIndentation(this.scopes.ToArray()); } @@ -189,13 +189,13 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateScope(this.Settings.Indentation, false, () => base.VisitWhileExpression(node)); + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateScope(this.Settings.Indentation, false, () => + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => { this.VisitExpression(node); node.IfKeyword.Accept(this); @@ -218,7 +218,7 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { this.CurrentToken.LeftPadding = " "; } - this.CreateScope(this.Settings.Indentation, false, () => base.VisitElseClause(node)); + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -234,7 +234,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) this.CurrentScope.IsMaterialized.Collapse(false); } - this.CreateScope(this.Settings.Indentation, true, () => + this.CreateFoldedScope(this.Settings.Indentation, () => { this.VisitExpression(node); node.OpenBrace.Accept(this); @@ -261,10 +261,11 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) base.VisitDeclaration(node); } - private IDisposable CreateScope(string indentation, bool tangible) + + private IDisposable CreateFoldedScope(string indentation) { var scope = new ScopeInfo(indentation); - if (tangible) scope.IsMaterialized.Collapse(true); + scope.IsMaterialized.Collapse(true); this.scopes.Push(scope); return new DisposeAction(() => { @@ -272,9 +273,26 @@ private IDisposable CreateScope(string indentation, bool tangible) scope.Dispose(); }); } - private void CreateScope(string indentation, bool tangible, Action action) + + private void CreateFoldedScope(string indentation, Action action) { - using (this.CreateScope(indentation, tangible)) action(); + using (this.CreateFoldedScope(indentation)) action(); + } + + private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior) + { + var scope = new ScopeInfo(indentation); + this.scopes.Push(scope); + return new DisposeAction(() => + { + var scope = this.scopes.Pop(); + scope.Dispose(); + }); + } + + private void CreateFoldableScope(string indentation, SolverTask foldBehavior, Action action) + { + using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } @@ -299,13 +317,6 @@ async SolverTask GetLineWidth(int index) } return sum; } - - async SolverTask TrySplitLine(int index, int maxLine) - { - var width = await GetLineWidth(index); - if (width <= maxLine) return default; - - } } } @@ -314,7 +325,7 @@ internal class DisposeAction(Action action) : IDisposable public void Dispose() => action(); } -internal class ScopeInfo(string indentation) : IDisposable +internal class ScopeInfo(string indentation, SolverTask? foldPriority = null) : IDisposable { private readonly SolverTaskCompletionSource _stableTcs = new(); public SolverTask WhenStable => this._stableTcs.Task; @@ -333,6 +344,9 @@ internal class ScopeInfo(string indentation) : IDisposable public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public string Indentation { get; } = indentation; + public SolverTask? FoldPriority { get; } = foldPriority; + + public void Dispose() => this.ItemsCount.Collapse(); } @@ -506,3 +520,11 @@ private class Comparer : IComparer<(int, SolverTaskCompletionSource)> public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); } } + + +public enum FoldPriority +{ + AsLateAsPossible, + AsSoonAsPossible, + AsSoonAsPossibleMerge +} diff --git a/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj b/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj new file mode 100644 index 000000000..b8fe9c9ab --- /dev/null +++ b/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/src/Draco.TracingDebugger/Program.cs b/src/Draco.TracingDebugger/Program.cs new file mode 100644 index 000000000..1bae90b42 --- /dev/null +++ b/src/Draco.TracingDebugger/Program.cs @@ -0,0 +1,30 @@ +using Draco.Debugger; + +namespace Draco.TracingDebugger; + +internal class Program +{ + static async Task Main(string[] args) + { + var debuggerHost = DebuggerHost.Create(); + var path = @"C:\dev\ConsoleApp36\ConsoleApp36\bin\Debug\net8.0\ConsoleApp36.exe"; + var debugger = debuggerHost.StartProcess(path); + await debugger.Ready; + while (!debugger.Terminated.IsCompleted) + { + var lastFrame = debugger.MainThread.CallStack[^1]; + var lines = lastFrame.Method.SourceFile?.Lines.AsEnumerable(); + if (lines is null || !lastFrame.Range.HasValue) + { + Console.WriteLine("???"); + continue; + } + var filtered = lines.Skip(lastFrame.Range.Value.Start.Line).Take(lastFrame.Range.Value.End.Line - lastFrame.Range.Value.Start.Line + 1); + + Console.WriteLine(string.Join("\n", filtered.Select(s => s.ToString()) ?? [])); + debugger.MainThread.StepOver(); + System.Threading.Thread.Sleep(5000); + } + + } +} diff --git a/src/Draco.sln b/src/Draco.sln index 33d752bc7..f4d9aa43f 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.2.32630.192 MinimumVisualStudioVersion = 10.0.40219.1 @@ -44,7 +45,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Compiler.Benchmarks", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.JsonRpc", "Draco.JsonRpc\Draco.JsonRpc.csproj", "{5C56C907-C614-49EE-B433-D88CB9CE8983}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.Debugger.Tests", "Draco.Debugger.Tests\Draco.Debugger.Tests.csproj", "{9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Debugger.Tests", "Draco.Debugger.Tests\Draco.Debugger.Tests.csproj", "{9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.TracingDebugger", "Draco.TracingDebugger\Draco.TracingDebugger.csproj", "{0D07F057-29C0-4BB3-AE17-32B73CDFB077}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -167,6 +170,12 @@ Global {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Nuget|Any CPU.Build.0 = Debug|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.Build.0 = Release|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 818944655d7e9ec55fa4eaba1a767998e41c1abe Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 18 Mar 2024 06:42:26 +0100 Subject: [PATCH 07/76] wip --- .../Syntax/ParseTreeFormatterTests.cs | 18 +- .../Syntax/Formatting/CollapsibleBool.cs | 55 +++ .../Syntax/Formatting/CollapsibleInt.cs | 80 ++++ .../Syntax/Formatting/DisposeAction.cs | 8 + .../Syntax/Formatting/FoldPriority.cs | 8 + .../Internal/Syntax/Formatting/Formatter.cs | 352 ++++-------------- .../Internal/Syntax/Formatting/ScopeInfo.cs | 75 ++++ .../Syntax/Formatting/TokenDecoration.cs | 71 ++++ src/Draco.Editor.Web/app/build.js | 58 +-- 9 files changed, 417 insertions(+), 308 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 60cd6b20f..4c6fbe91d 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -101,6 +101,8 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(); + Console.WriteLine(actual); + this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -138,11 +140,19 @@ public void TestFoldExpression() var input = """ func aLongMethodName() = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; """; - var actual = """ - func aLongMethodName() - = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; + var expected = """ + func aLongMethodName() = 1 + + 2 + + 3 + + 4 + + 5 + + 6 + + 7 + + 8 + + 9 + + 10; """; - var expected = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { LineWidth = 60 }); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs new file mode 100644 index 000000000..fbb5f3676 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs @@ -0,0 +1,55 @@ +using System; +using Draco.Compiler.Internal.Solver.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class CollapsibleBool : IEquatable +{ + private readonly SolverTaskCompletionSource? tcs; + private readonly SolverTask task; + + private CollapsibleBool(SolverTaskCompletionSource tcs) + { + this.tcs = tcs; + this.task = tcs.Task; + } + private CollapsibleBool(SolverTask task) + { + this.task = task; + } + + public static CollapsibleBool Create() => new(new SolverTaskCompletionSource()); + public static CollapsibleBool Create(bool value) => new(SolverTask.FromResult(value)); + + public void Collapse(bool collapse) => this.tcs?.SetResult(collapse); + public bool TryCollapse(bool collapse) + { + if (!this.Collapsed.IsCompleted) + { + this.Collapse(collapse); + return true; + } + return false; + } + public bool Equals(CollapsibleBool? other) + { + if (other is null) return false; + + if (this.tcs is null) + { + if (other.tcs is not null) return false; + return this.task.Result == other.task.Result; + } + if (other.tcs is null) return false; + if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result == other.task.Result; + return false; + } + + public override bool Equals(object? obj) + { + if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); + return false; + } + + public SolverTask Collapsed => this.task; +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs new file mode 100644 index 000000000..294992299 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using System.Linq; +using Draco.Compiler.Internal.Solver.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class CollapsibleInt +{ + private readonly SolverTaskCompletionSource? tcs; + private readonly SolverTask task; + private int MinimumCurrentValue; + private CollapsibleInt(SolverTaskCompletionSource tcs) + { + this.tcs = tcs; + this.task = tcs.Task; + } + + private CollapsibleInt(SolverTask task) + { + this.task = task; + } + + public static CollapsibleInt Create() => new(new SolverTaskCompletionSource()); + public static CollapsibleInt Create(int value) => new(SolverTask.FromResult(value)); + + + // order by desc + private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; + + public void Add(int toAdd) + { + this.MinimumCurrentValue += toAdd; + if (this._whenTcs is null) return; + var i = this._whenTcs.Count - 1; + if (i < 0) return; + while (true) + { + var (value, tcs) = this._whenTcs![i]; + if (this.MinimumCurrentValue < value) break; + tcs.SetResult(true); + if (i == 0) break; + i--; + } + this._whenTcs.RemoveRange(i, this._whenTcs.Count - i); + } + + public void Collapse() + { + if (this._whenTcs is not null) + { + foreach (var (_, Tcs) in this._whenTcs ?? Enumerable.Empty<(int Value, SolverTaskCompletionSource Tcs)>()) + { + Tcs.SetResult(false); + } + this._whenTcs = null; + } + + this.tcs?.SetResult(this.MinimumCurrentValue); + } + + public SolverTask Collapsed => this.task; + + public SolverTask WhenGreaterOrEqual(int number) + { + if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(true); + this._whenTcs ??= []; + var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); + if (index > 0) return this._whenTcs[index].Tcs.Task; + var tcs = new SolverTaskCompletionSource(); + this._whenTcs.Insert(~index, (number, tcs)); + return tcs.Task; + } + + private class Comparer : IComparer<(int, SolverTaskCompletionSource)> + { + public static Comparer Instance { get; } = new Comparer(); + // reverse comparison. + public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs new file mode 100644 index 000000000..10371190e --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs @@ -0,0 +1,8 @@ +using System; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class DisposeAction(Action action) : IDisposable +{ + public void Dispose() => action(); +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs new file mode 100644 index 000000000..dc47f1f3c --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs @@ -0,0 +1,8 @@ +namespace Draco.Compiler.Internal.Syntax.Formatting; + +public enum FoldPriority +{ + Never, + AsLateAsPossible, + AsSoonAsPossible +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index cdbb5db48..0e0c57c17 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Numerics; using System.Text; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; -using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -14,13 +12,11 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor { private TokenDecoration[] tokenDecorations = []; private int currentIdx; - private readonly Stack scopes = new(); + private ScopeInfo scope; private readonly SyntaxTree tree; // debugging helper, to remove private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; - private ScopeInfo CurrentScope => this.scopes.Peek(); - - public static async SolverTask GetIndentation(IReadOnlyCollection scopes) + public static async SolverTask GetIndentation(IEnumerable scopes) { var indentation = ""; foreach (var scope in scopes) @@ -48,12 +44,38 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) // Construct token sequence tree.Root.Accept(formatter); + var decorations = formatter.tokenDecorations; + var currentLineLength = 0; + var currentLineStart = 0; + for (var x = 0; x < decorations.Length; x++) + { + var curr = decorations[x]; + if (curr.DoesReturnLineCollapsible?.Collapsed.Result ?? false) + { + currentLineLength = 0; + currentLineStart = x; + } + + currentLineLength += curr.CurrentTotalSize; + if (currentLineLength > settings.LineWidth) + { + if (curr.ScopeInfo.Fold()) + { + x = currentLineStart; + continue; + } + } + } var builder = new StringBuilder(); var i = 0; foreach (var token in tree.Root.Tokens) { - if (token.Kind == TokenKind.StringNewline) continue; + if (token.Kind == TokenKind.StringNewline) + { + i++; + continue; + } var decoration = formatter.tokenDecorations[i]; if (decoration.DoesReturnLineCollapsible is not null) @@ -79,6 +101,8 @@ private Formatter(FormatterSettings settings, SyntaxTree tree) { this.Settings = settings; this.tree = tree; + this.scope = new(null, ""); + this.scope.IsMaterialized.Collapse(true); } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) @@ -89,10 +113,9 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { + this.CurrentToken.ScopeInfo = this.scope; switch (node.Kind) { - case TokenKind.StringNewline: // we ignore and don't render string newlines. our own indentation will take care of it. - return; case TokenKind.Minus: case TokenKind.Plus: case TokenKind.Star: @@ -138,49 +161,71 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod continue; } ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; - currentToken.Indentation = GetIndentation(this.scopes.ToArray()); + currentToken.SetIndentation(GetIndentation(this.scope.All)); } using var _ = this.CreateFoldedScope(this.Settings.Indentation); - this.tokenDecorations[this.currentIdx + i + 1].Indentation = GetIndentation(this.scopes.ToArray()); + this.tokenDecorations[this.currentIdx + i + 1].SetIndentation(GetIndentation(this.scope.All)); base.VisitStringExpression(node); } + public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) + { + IDisposable? closeScope = null; + var kind = node.Operator.Kind; + if (!(this.scope.Data?.Equals(kind) ?? false)) + { + closeScope = this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); + this.scope.Data = kind; + } + node.Left.Accept(this); + + static async SolverTask Indentation(ScopeInfo scope) + { + var isCollapsed = await scope.IsMaterialized.Collapsed; + if (!isCollapsed) return null; + return await GetIndentation(scope.All); + } + + this.CurrentToken.SetIndentation(Indentation(this.scope)); + node.Operator.Accept(this); + node.Right.Accept(this); + closeScope?.Dispose(); + } public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) { base.VisitMemberExpression(node); - this.CurrentScope.ItemsCount.Add(1); + this.scope.ItemsCount.Add(1); } public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { this.CurrentToken.LeftPadding = " "; this.CreateFoldedScope(this.Settings.Indentation, () => base.VisitBlockFunctionBody(node)); - this.tokenDecorations[this.currentIdx - 1].Indentation = GetIndentation(this.scopes.ToArray()); + this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.All)); } public override void VisitStatement(Api.Syntax.StatementSyntax node) { - this.CurrentScope.ItemsCount.Add(1); + this.scope.ItemsCount.Add(1); if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { - this.CurrentToken.Indentation = GetIndentation(this.scopes.Skip(1).ToArray()); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.Parents)); } else if (node.Parent is Api.Syntax.BlockExpressionSyntax) { - this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); } else { async SolverTask Indentation() { - var scope = this.CurrentScope; - var haveMoreThanOneStatement = await scope.ItemsCount.WhenGreaterOrEqual(2); - if (haveMoreThanOneStatement) return await GetIndentation(this.scopes.ToArray()); + var haveMoreThanOneStatement = await this.scope.ItemsCount.WhenGreaterOrEqual(2); + if (haveMoreThanOneStatement) return await GetIndentation(this.scope.All); return null; } - this.CurrentToken.Indentation = Indentation(); + this.CurrentToken.SetIndentation(Indentation()); } base.VisitStatement(node); @@ -212,7 +257,7 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) this.CurrentToken.RightPadding = " "; if (node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); } else { @@ -229,10 +274,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // if (blabla) // an expression; // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - if (!this.CurrentScope.IsMaterialized.Collapsed.IsCompleted) - { - this.CurrentScope.IsMaterialized.Collapse(false); - } + this.scope.IsMaterialized.TryCollapse(false); this.CreateFoldedScope(this.Settings.Indentation, () => { @@ -241,12 +283,12 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) node.Statements.Accept(this); if (node.Value != null) { - this.CurrentToken.Indentation = GetIndentation(this.scopes.ToArray()); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); node.Value.Accept(this); } node.CloseBrace.Accept(this); }); - this.tokenDecorations[this.currentIdx - 1].Indentation = GetIndentation(this.scopes.ToArray()); + this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.All)); } public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) @@ -255,22 +297,14 @@ public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) base.VisitTypeSpecifier(node); } - public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) - { - this.tokenDecorations[this.currentIdx + node.Tokens.Count()].Indentation = GetIndentation(this.scopes.ToArray()); - base.VisitDeclaration(node); - } - - private IDisposable CreateFoldedScope(string indentation) { - var scope = new ScopeInfo(indentation); - scope.IsMaterialized.Collapse(true); - this.scopes.Push(scope); + this.scope = new ScopeInfo(this.scope, indentation); + this.scope.IsMaterialized.Collapse(true); return new DisposeAction(() => { - var scope = this.scopes.Pop(); - scope.Dispose(); + this.scope.Dispose(); + this.scope = this.scope.Parent!; }); } @@ -281,12 +315,11 @@ private void CreateFoldedScope(string indentation, Action action) private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior) { - var scope = new ScopeInfo(indentation); - this.scopes.Push(scope); + this.scope = new ScopeInfo(this.scope, indentation, foldBehavior); return new DisposeAction(() => { - var scope = this.scopes.Pop(); - scope.Dispose(); + this.scope.Dispose(); + this.scope = this.scope.Parent!; }); } @@ -294,237 +327,4 @@ private void CreateFoldableScope(string indentation, SolverTask fo { using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } - - - - private void FormatTooLongLine(int index) - { - async SolverTask GetLineWidth(int index) - { - var sum = 0; - while (index > 0) - { - index--; - var tokenDecoration = this.tokenDecorations[index]; - sum += tokenDecoration.TokenSize; - if (tokenDecoration.DoesReturnLineCollapsible is null) continue; - var doesReturnLine = await tokenDecoration.DoesReturnLineCollapsible.Collapsed; - if (doesReturnLine) - { - sum += tokenDecoration.Indentation!.Result!.Length; - break; - } - } - return sum; - } - } -} - -internal class DisposeAction(Action action) : IDisposable -{ - public void Dispose() => action(); -} - -internal class ScopeInfo(string indentation, SolverTask? foldPriority = null) : IDisposable -{ - private readonly SolverTaskCompletionSource _stableTcs = new(); - public SolverTask WhenStable => this._stableTcs.Task; - /// - /// Represent if the scope is materialized or not. - /// An unmaterialized scope is a potential scope, which is not folded yet. - /// items.Select(x => x).ToList() have an unmaterialized scope. - /// It can be materialized like: - /// - /// items - /// .Select(x => x) - /// .ToList() - /// - /// - public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); - public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); - public string Indentation { get; } = indentation; - - public SolverTask? FoldPriority { get; } = foldPriority; - - - public void Dispose() => this.ItemsCount.Collapse(); -} - -internal struct TokenDecoration -{ - private string? rightPadding; - private string? leftPadding; - private SolverTask? indentation; - public int TokenSize { get; set; } - public int TotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0); - - [DisallowNull] - public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } - - [DisallowNull] - public SolverTask? Indentation - { - readonly get => this.indentation; - set - { - if (this.indentation is not null) - { - if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; - throw new InvalidOperationException("Indentation already set."); - } - var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); - this.indentation = value; - var myThis = this; - this.indentation.Awaiter.OnCompleted(() => - { - doesReturnLine.Collapse(value.Result != null); - }); - } - } - - public string? LeftPadding - { - readonly get => this.leftPadding; - set - { - if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); - this.leftPadding = value; - } - } - public string? RightPadding - { - readonly get => this.rightPadding; - set - { - if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); - this.rightPadding = value; - } - } - -} - - -internal class CollapsibleBool : IEquatable -{ - private readonly SolverTaskCompletionSource? tcs; - private readonly SolverTask task; - - private CollapsibleBool(SolverTaskCompletionSource tcs) - { - this.tcs = tcs; - this.task = tcs.Task; - } - private CollapsibleBool(SolverTask task) - { - this.task = task; - } - - public static CollapsibleBool Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleBool Create(bool value) => new(SolverTask.FromResult(value)); - - public void Collapse(bool collapse) => this.tcs?.SetResult(collapse); - public bool Equals(CollapsibleBool? other) - { - if (other is null) return false; - - if (this.tcs is null) - { - if (other.tcs is not null) return false; - return this.task.Result == other.task.Result; - } - if (other.tcs is null) return false; - if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result == other.task.Result; - return false; - } - - public override bool Equals(object? obj) - { - if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); - return false; - } - - public SolverTask Collapsed => this.task; -} - -internal class CollapsibleInt -{ - private readonly SolverTaskCompletionSource? tcs; - private readonly SolverTask task; - private int MinimumCurrentValue; - private CollapsibleInt(SolverTaskCompletionSource tcs) - { - this.tcs = tcs; - this.task = tcs.Task; - } - - private CollapsibleInt(SolverTask task) - { - this.task = task; - } - - public static CollapsibleInt Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleInt Create(int value) => new(SolverTask.FromResult(value)); - - - // order by desc - private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; - - public void Add(int toAdd) - { - this.MinimumCurrentValue += toAdd; - if (this._whenTcs is null) return; - var i = this._whenTcs.Count - 1; - if (i < 0) return; - while (true) - { - var (value, tcs) = this._whenTcs![i]; - if (this.MinimumCurrentValue < value) break; - tcs.SetResult(true); - if (i == 0) break; - i--; - } - this._whenTcs.RemoveRange(i, this._whenTcs.Count - i); - } - - public void Collapse() - { - if (this._whenTcs is not null) - { - foreach (var (_, Tcs) in this._whenTcs ?? Enumerable.Empty<(int Value, SolverTaskCompletionSource Tcs)>()) - { - Tcs.SetResult(false); - } - this._whenTcs = null; - } - - this.tcs?.SetResult(this.MinimumCurrentValue); - } - - public SolverTask Collapsed => this.task; - - public SolverTask WhenGreaterOrEqual(int number) - { - if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(true); - this._whenTcs ??= []; - var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); - if (index > 0) return this._whenTcs[index].Tcs.Task; - var tcs = new SolverTaskCompletionSource(); - this._whenTcs.Insert(~index, (number, tcs)); - return tcs.Task; - } - - private class Comparer : IComparer<(int, SolverTaskCompletionSource)> - { - public static Comparer Instance { get; } = new Comparer(); - // reverse comparison. - public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); - } -} - - -public enum FoldPriority -{ - AsLateAsPossible, - AsSoonAsPossible, - AsSoonAsPossibleMerge } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs new file mode 100644 index 000000000..716c6a296 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Draco.Compiler.Internal.Solver.Tasks; +using Draco.Compiler.Internal.Utilities; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class ScopeInfo(ScopeInfo? parent, string indentation, SolverTask? foldPriority = null) : IDisposable +{ + public ScopeInfo? Parent { get; } = parent; + private readonly SolverTaskCompletionSource _stableTcs = new(); + public SolverTask WhenStable => this._stableTcs.Task; + + public object? Data { get; set; } + + /// + /// Represent if the scope is materialized or not. + /// An unmaterialized scope is a potential scope, which is not folded yet. + /// items.Select(x => x).ToList() have an unmaterialized scope. + /// It can be materialized like: + /// + /// items + /// .Select(x => x) + /// .ToList() + /// + /// + public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); + public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); + public string Indentation { get; } = indentation; + + public SolverTask? FoldPriority { get; } = foldPriority; + public IEnumerable All => this.Parents.Prepend(this); + public IEnumerable Parents + { + get + { + if (this.Parent == null) yield break; + yield return this.Parent; + foreach (var item in this.Parent.Parents) + { + yield return item; + } + } + } + + public bool Fold() + { + foreach (var item in this.All.Reverse()) + { + if (item.IsMaterialized.Collapsed.IsCompleted) continue; + Debug.Assert(item.FoldPriority!.IsCompleted); + if (item.FoldPriority.Result == Formatting.FoldPriority.AsSoonAsPossible) + { + item.IsMaterialized.Collapse(true); + return true; + } + } + + foreach (var item in this.All) + { + if (item.IsMaterialized.Collapsed.IsCompleted) continue; + Debug.Assert(item.FoldPriority!.IsCompleted); + if (item.FoldPriority.Result == Formatting.FoldPriority.AsLateAsPossible) + { + item.IsMaterialized.Collapse(true); + return true; + } + } + return false; + } + + public void Dispose() => this.ItemsCount.Collapse(); +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs new file mode 100644 index 000000000..5c48c7177 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -0,0 +1,71 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using Draco.Compiler.Internal.Solver.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal struct TokenDecoration +{ + private string? rightPadding; + private string? leftPadding; + private SolverTask? indentation; + private ScopeInfo scopeInfo; + + public int TokenSize { get; set; } + public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; + + private readonly int CurrentIndentationSize => this.Indentation?.IsCompleted ?? false ? this.Indentation.Result?.Length ?? 0 : 0; + + [DisallowNull] + public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } + + public ScopeInfo ScopeInfo + { + readonly get => this.scopeInfo; + set + { + if (this.scopeInfo != null) + { + throw new InvalidOperationException(); + } + this.scopeInfo = value; + } + } + public readonly SolverTask? Indentation => this.indentation; + + public void SetIndentation(SolverTask? value) + { + if (this.indentation is not null) + { + if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; + throw new InvalidOperationException("Indentation already set."); + } + var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); + this.indentation = value; + var myThis = this; + this.indentation.Awaiter.OnCompleted(() => + { + doesReturnLine.Collapse(value.Result != null); + }); + } + + public string? LeftPadding + { + readonly get => this.leftPadding; + set + { + if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); + this.leftPadding = value; + } + } + public string? RightPadding + { + readonly get => this.rightPadding; + set + { + if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); + this.rightPadding = value; + } + } + +} diff --git a/src/Draco.Editor.Web/app/build.js b/src/Draco.Editor.Web/app/build.js index 0bf6f7448..eeab991df 100644 --- a/src/Draco.Editor.Web/app/build.js +++ b/src/Draco.Editor.Web/app/build.js @@ -130,31 +130,33 @@ const response = await octokit.repos.getContent({ repo: 'vscode', path: 'extensions/theme-defaults/themes' }); - -const themes = await Promise.all(response.data.map(async s => { - const resp = await fetch(s.download_url); - const txt = await resp.text(); - const parsed = JSON5.parse(txt); - const converted = convertTheme(parsed); - return { - name: parsed.name, - filename: s.name, - theme: converted - }; -})); -const themeObj = {}; -const themePackageJson = await (await fetch('https://raw.githubusercontent.com/microsoft/vscode/main/extensions/theme-defaults/package.json')).json(); -const themesMetadata = themePackageJson.contributes.themes; - -themes.forEach(s => { - themeObj[s.name] = s.theme; - themeObj[s.name].base = themesMetadata.find(t => path.basename(t.path) == s.filename).uiTheme; -}); - -const themeListJson = JSON.stringify(themeObj); -fs.writeFileSync(path.join(outDir, 'themes.json'), themeListJson); -const csharpTextmateYml = await (await fetch('https://raw.githubusercontent.com/dotnet/csharp-tmLanguage/main/src/csharp.tmLanguage.yml')).text(); -const csharpTextmate = JSON.stringify(YAML.parse(csharpTextmateYml)); -fs.writeFileSync(path.join(outDir, 'csharp.tmLanguage.json'), csharpTextmate); -const ilTextmate = await (await fetch('https://raw.githubusercontent.com/soltys/vscode-il/master/syntaxes/il.json')).text(); -fs.writeFileSync(path.join(outDir, 'il.tmLanguage.json'), ilTextmate); +// const timeout = setTimeout(() => controller.abort(), 100000); +// const themes = await Promise.all(response.data.map(async s => { +// const resp = await fetch(s.download_url, { +// signal: timeout +// }); +// const txt = await resp.text(); +// const parsed = JSON5.parse(txt); +// const converted = convertTheme(parsed); +// return { +// name: parsed.name, +// filename: s.name, +// theme: converted +// }; +// })); +// const themeObj = {}; +// const themePackageJson = await (await fetch('https://raw.githubusercontent.com/microsoft/vscode/main/extensions/theme-defaults/package.json')).json(); +// const themesMetadata = themePackageJson.contributes.themes; + +// themes.forEach(s => { +// themeObj[s.name] = s.theme; +// themeObj[s.name].base = themesMetadata.find(t => path.basename(t.path) == s.filename).uiTheme; +// }); + +// const themeListJson = JSON.stringify(themeObj); +// fs.writeFileSync(path.join(outDir, 'themes.json'), themeListJson); +// const csharpTextmateYml = await (await fetch('https://raw.githubusercontent.com/dotnet/csharp-tmLanguage/main/src/csharp.tmLanguage.yml')).text(); +// const csharpTextmate = JSON.stringify(YAML.parse(csharpTextmateYml)); +// fs.writeFileSync(path.join(outDir, 'csharp.tmLanguage.json'), csharpTextmate); +// const ilTextmate = await (await fetch('https://raw.githubusercontent.com/soltys/vscode-il/master/syntaxes/il.json')).text(); +// fs.writeFileSync(path.join(outDir, 'il.tmLanguage.json'), ilTextmate); From e582157bb53686b1d68828a7b6c79e17bbccd76c Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 23 Mar 2024 18:38:05 +0100 Subject: [PATCH 08/76] More progress --- .../Syntax/ParseTreeFormatterTests.cs | 7 +- .../Syntax/Formatting/CollapsibleBool.cs | 2 +- .../Syntax/Formatting/CollapsibleValue.cs | 56 +++++++ .../Internal/Syntax/Formatting/Formatter.cs | 137 ++++++++++++++---- .../Syntax/Formatting/MaterialisationKind.cs | 8 + .../Internal/Syntax/Formatting/ScopeInfo.cs | 54 ++++++- .../Syntax/Formatting/TokenDecoration.cs | 5 +- 7 files changed, 229 insertions(+), 40 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 4c6fbe91d..020bd8c9e 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -101,8 +101,6 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -130,7 +128,8 @@ func main() { """; var actual = SyntaxTree.Parse(input).Format(); - + Console.WriteLine(actual); + this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -151,6 +150,7 @@ func aLongMethodName() = 1 + 8 + 9 + 10; + """; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { @@ -158,6 +158,7 @@ func aLongMethodName() = 1 }); Console.WriteLine(actual); this.logger.WriteLine(actual); + this.logger.WriteLine(expected); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs index fbb5f3676..311e1185d 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs @@ -1,4 +1,4 @@ -using System; +using System; using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs new file mode 100644 index 000000000..cfaa2e623 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs @@ -0,0 +1,56 @@ +using System; +using Draco.Compiler.Internal.Solver.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class CollapsibleValue + where T : struct +{ + private readonly SolverTaskCompletionSource? tcs; + private readonly SolverTask task; + + private CollapsibleValue(SolverTaskCompletionSource tcs) + { + this.tcs = tcs; + this.task = tcs.Task; + } + private CollapsibleValue(SolverTask task) + { + this.task = task; + } + + public static CollapsibleValue Create() => new(new SolverTaskCompletionSource()); + public static CollapsibleValue Create(T value) => new(SolverTask.FromResult(value)); + + public void Collapse(T collapse) => this.tcs?.SetResult(collapse); + public bool TryCollapse(T collapse) + { + if (!this.Collapsed.IsCompleted) + { + this.Collapse(collapse); + return true; + } + return false; + } + public bool Equals(CollapsibleValue? other) + { + if (other is null) return false; + + if (this.tcs is null) + { + if (other.tcs is not null) return false; + return this.task.Result.Equals(other.task.Result); + } + if (other.tcs is null) return false; + if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result.Equals(other.task.Result); + return false; + } + + public override bool Equals(object? obj) + { + if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); + return false; + } + + public SolverTask Collapsed => this.task; +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 0e0c57c17..a10bf52a7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Numerics; +using System.Runtime.CompilerServices; using System.Text; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; @@ -15,15 +15,25 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private ScopeInfo scope; private readonly SyntaxTree tree; // debugging helper, to remove private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; + bool isInImportBlock; + bool isInAnImport; + bool firstDeclaration = true; public static async SolverTask GetIndentation(IEnumerable scopes) { var indentation = ""; + var prevKind = MaterialisationKind.Normal; foreach (var scope in scopes) { var isMaterialized = await scope.IsMaterialized.Collapsed; if (isMaterialized) { + var previousIsStrong = prevKind == MaterialisationKind.Strong; + prevKind = scope.MaterialisationKind; + if (scope.MaterialisationKind == MaterialisationKind.Weak && previousIsStrong) + { + continue; + } indentation += scope.Indentation; } } @@ -42,7 +52,6 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var formatter = new Formatter(settings, tree); - // Construct token sequence tree.Root.Accept(formatter); var decorations = formatter.tokenDecorations; var currentLineLength = 0; @@ -146,6 +155,65 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.currentIdx++; } + public override void VisitImportDeclaration(Api.Syntax.ImportDeclarationSyntax node) + { + this.isInAnImport = true; + this.isInImportBlock = true; + base.VisitImportDeclaration(node); + this.isInAnImport = false; + } + int parameterCount = 0; + public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) + { + if (node is Api.Syntax.SeparatedSyntaxList) + { + this.CreateFoldableScope(this.Settings.Indentation, + MaterialisationKind.Normal, + SolverTask.FromResult(FoldPriority.AsSoonAsPossible), + () => base.VisitSeparatedSyntaxList(node) + ); + this.parameterCount = 0; + } + else + { + base.VisitSeparatedSyntaxList(node); + } + } + + public override void VisitParameter(Api.Syntax.ParameterSyntax node) + { + static async SolverTask VariableIndentation(ScopeInfo scope) + { + return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; + } + if (this.parameterCount > 0) + { + this.CurrentToken.LeftPadding = " "; + } + this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); + base.VisitParameter(node); + this.parameterCount++; + } + + public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) + { + if (!this.isInAnImport && this.isInImportBlock) + { + this.isInImportBlock = false; + async SolverTask DoubleNewLine() => this.Settings.Newline + await GetIndentation(this.scope.ThisAndParents); + this.CurrentToken.SetIndentation(DoubleNewLine()); + } + else if (this.firstDeclaration) this.firstDeclaration = false; + else + { + if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) + { + this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); + } + } + base.VisitDeclaration(node); + } + public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax node) { if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) @@ -161,10 +229,10 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod continue; } ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; - currentToken.SetIndentation(GetIndentation(this.scope.All)); + currentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } - using var _ = this.CreateFoldedScope(this.Settings.Indentation); - this.tokenDecorations[this.currentIdx + i + 1].SetIndentation(GetIndentation(this.scope.All)); + using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); + this.tokenDecorations[this.currentIdx + i + 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); base.VisitStringExpression(node); } @@ -174,7 +242,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod var kind = node.Operator.Kind; if (!(this.scope.Data?.Equals(kind) ?? false)) { - closeScope = this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); + closeScope = this.CreateFoldableScope("", MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); this.scope.Data = kind; } node.Left.Accept(this); @@ -183,7 +251,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod { var isCollapsed = await scope.IsMaterialized.Collapsed; if (!isCollapsed) return null; - return await GetIndentation(scope.All); + return await GetIndentation(scope.ThisAndParents); } this.CurrentToken.SetIndentation(Indentation(this.scope)); @@ -198,11 +266,26 @@ public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax nod this.scope.ItemsCount.Add(1); } + public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) + { + base.VisitFunctionDeclaration(node); + } + public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { this.CurrentToken.LeftPadding = " "; - this.CreateFoldedScope(this.Settings.Indentation, () => base.VisitBlockFunctionBody(node)); - this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.All)); + this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => base.VisitBlockFunctionBody(node)); + this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); + } + + public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) + { + var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; + using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), + MaterialisationKind.Weak, + SolverTask.FromResult(FoldPriority.AsSoonAsPossible) + ); + base.VisitInlineFunctionBody(node); } public override void VisitStatement(Api.Syntax.StatementSyntax node) @@ -215,14 +298,14 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) } else if (node.Parent is Api.Syntax.BlockExpressionSyntax) { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } else { async SolverTask Indentation() { var haveMoreThanOneStatement = await this.scope.ItemsCount.WhenGreaterOrEqual(2); - if (haveMoreThanOneStatement) return await GetIndentation(this.scope.All); + if (haveMoreThanOneStatement) return await GetIndentation(this.scope.ThisAndParents); return null; } this.CurrentToken.SetIndentation(Indentation()); @@ -234,13 +317,13 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => { this.VisitExpression(node); node.IfKeyword.Accept(this); @@ -257,13 +340,13 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) this.CurrentToken.RightPadding = " "; if (node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } else { this.CurrentToken.LeftPadding = " "; } - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -276,19 +359,19 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. this.scope.IsMaterialized.TryCollapse(false); - this.CreateFoldedScope(this.Settings.Indentation, () => + this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => { this.VisitExpression(node); node.OpenBrace.Accept(this); node.Statements.Accept(this); if (node.Value != null) { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.All)); + this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); node.Value.Accept(this); } node.CloseBrace.Accept(this); }); - this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.All)); + this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); } public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) @@ -297,10 +380,11 @@ public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) base.VisitTypeSpecifier(node); } - private IDisposable CreateFoldedScope(string indentation) + private IDisposable CreateFoldedScope(string indentation, MaterialisationKind materialisationKind) { this.scope = new ScopeInfo(this.scope, indentation); this.scope.IsMaterialized.Collapse(true); + this.scope.MaterialisationKind = materialisationKind; return new DisposeAction(() => { this.scope.Dispose(); @@ -308,14 +392,17 @@ private IDisposable CreateFoldedScope(string indentation) }); } - private void CreateFoldedScope(string indentation, Action action) + private void CreateFoldedScope(string indentation, MaterialisationKind materialisationKind, Action action) { - using (this.CreateFoldedScope(indentation)) action(); + using (this.CreateFoldedScope(indentation, materialisationKind)) action(); } - private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior) + private IDisposable CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior) { - this.scope = new ScopeInfo(this.scope, indentation, foldBehavior); + this.scope = new ScopeInfo(this.scope, indentation, foldBehavior) + { + MaterialisationKind = materialisationKind + }; return new DisposeAction(() => { this.scope.Dispose(); @@ -323,8 +410,8 @@ private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior, Action action) + private void CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior, Action action) { - using (this.CreateFoldableScope(indentation, foldBehavior)) action(); + using (this.CreateFoldableScope(indentation, materialisationKind, foldBehavior)) action(); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs new file mode 100644 index 000000000..27581049c --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs @@ -0,0 +1,8 @@ +namespace Draco.Compiler.Internal.Syntax.Formatting; + +enum MaterialisationKind +{ + Normal, + Weak, + Strong +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 716c6a296..767d91908 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -7,10 +7,20 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class ScopeInfo(ScopeInfo? parent, string indentation, SolverTask? foldPriority = null) : IDisposable +internal class ScopeInfo : IDisposable { - public ScopeInfo? Parent { get; } = parent; + public ScopeInfo? Parent { get; } + public List Childs { get; } = []; private readonly SolverTaskCompletionSource _stableTcs = new(); + + public ScopeInfo(ScopeInfo? parent, string indentation, SolverTask? foldPriority = null) + { + this.Parent = parent; + parent?.Childs.Add(this); + this.Indentation = indentation; + this.FoldPriority = foldPriority; + } + public SolverTask WhenStable => this._stableTcs.Task; public object? Data { get; set; } @@ -27,11 +37,39 @@ internal class ScopeInfo(ScopeInfo? parent, string indentation, SolverTask /// public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); + public MaterialisationKind MaterialisationKind { get; set; } public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); - public string Indentation { get; } = indentation; + public string Indentation { get; } + + public SolverTask? FoldPriority { get; } + + public IEnumerable ThisAndAllChilds => this.AllChilds.Prepend(this); + public IEnumerable AllChilds + { + get + { + foreach (var child in this.Childs) + { + yield return child; + foreach (var subChild in child.AllChilds) + { + yield return subChild; + } + } + } + } + + public ScopeInfo Root + { + get + { + if (this.Parent == null) return this; + return this.Parent.Root; + } + } + + public IEnumerable ThisAndParents => this.Parents.Prepend(this); - public SolverTask? FoldPriority { get; } = foldPriority; - public IEnumerable All => this.Parents.Prepend(this); public IEnumerable Parents { get @@ -47,7 +85,7 @@ public IEnumerable Parents public bool Fold() { - foreach (var item in this.All.Reverse()) + foreach (var item in this.ThisAndParents.Reverse()) { if (item.IsMaterialized.Collapsed.IsCompleted) continue; Debug.Assert(item.FoldPriority!.IsCompleted); @@ -58,7 +96,7 @@ public bool Fold() } } - foreach (var item in this.All) + foreach (var item in this.ThisAndParents) { if (item.IsMaterialized.Collapsed.IsCompleted) continue; Debug.Assert(item.FoldPriority!.IsCompleted); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 5c48c7177..2d5d6ef50 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics.CodeAnalysis; using Draco.Compiler.Internal.Solver.Tasks; @@ -42,8 +42,7 @@ public void SetIndentation(SolverTask? value) } var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); this.indentation = value; - var myThis = this; - this.indentation.Awaiter.OnCompleted(() => + this.indentation!.Awaiter.OnCompleted(() => { doesReturnLine.Collapse(value.Result != null); }); From c29882a0429ad145a6feb55759ac6ce2357b32bc Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 24 Mar 2024 17:31:39 +0100 Subject: [PATCH 09/76] wip --- .../Syntax/ParseTreeFormatterTests.cs | 2 + .../Internal/Syntax/Formatting/Formatter.cs | 44 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 020bd8c9e..a2e53304e 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -101,6 +101,8 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(); + Console.WriteLine(actual); + this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index a10bf52a7..831da81dc 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -15,9 +15,8 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private ScopeInfo scope; private readonly SyntaxTree tree; // debugging helper, to remove private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; - bool isInImportBlock; - bool isInAnImport; - bool firstDeclaration = true; + + private bool firstDeclaration = true; public static async SolverTask GetIndentation(IEnumerable scopes) { @@ -155,17 +154,11 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.currentIdx++; } - public override void VisitImportDeclaration(Api.Syntax.ImportDeclarationSyntax node) - { - this.isInAnImport = true; - this.isInImportBlock = true; - base.VisitImportDeclaration(node); - this.isInAnImport = false; - } - int parameterCount = 0; + private int parameterCount = 0; public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) { - if (node is Api.Syntax.SeparatedSyntaxList) + if (node is Api.Syntax.SeparatedSyntaxList + || node is Api.Syntax.SeparatedSyntaxList) { this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, @@ -195,21 +188,26 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) this.parameterCount++; } + public override void VisitExpression(Api.Syntax.ExpressionSyntax node) + { + base.VisitExpression(node); + } + public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { - if (!this.isInAnImport && this.isInImportBlock) + if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) { - this.isInImportBlock = false; - async SolverTask DoubleNewLine() => this.Settings.Newline + await GetIndentation(this.scope.ThisAndParents); - this.CurrentToken.SetIndentation(DoubleNewLine()); - } - else if (this.firstDeclaration) this.firstDeclaration = false; - else - { - if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) + + if (!this.firstDeclaration) { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); + async SolverTask DoubleNewLine() => this.Settings.Newline + await GetIndentation(this.scope.ThisAndParents); + this.CurrentToken.SetIndentation(DoubleNewLine()); + } + else + { + this.CurrentToken.SetIndentation(SolverTask.FromResult(null as string)); } + this.firstDeclaration = false; } base.VisitDeclaration(node); } @@ -296,7 +294,7 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) { this.CurrentToken.SetIndentation(GetIndentation(this.scope.Parents)); } - else if (node.Parent is Api.Syntax.BlockExpressionSyntax) + else if (node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax) { this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } From e51f56bf7b79309891ab9e73aee5909235bbdd1c Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 31 Mar 2024 18:07:14 +0200 Subject: [PATCH 10/76] One more test green. --- .../Api/Syntax/ExpressionSyntax.cs | 23 ++++++++++++++++++ .../Api/Syntax/ParameterSyntax.cs | 24 +++++++++++++++++++ .../Internal/Syntax/Formatting/Formatter.cs | 9 +++---- .../SyntaxTree/SyntaxTreeSourceGenerator.cs | 6 ++--- 4 files changed, 55 insertions(+), 7 deletions(-) create mode 100644 src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs create mode 100644 src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs diff --git a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs new file mode 100644 index 000000000..865de5936 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Api.Syntax; +public partial class ExpressionSyntax +{ + public int ArgumentIndex + { + get + { + if (this.Parent is not CallExpressionSyntax callExpression) return -1; + if (this == callExpression.Function) return -1; + foreach (var (item, i) in callExpression.ArgumentList.Values.Select((s, i) => (s, i))) + { + if (item == this) return i; + } + throw new InvalidOperationException(); + } + } +} diff --git a/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs b/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs new file mode 100644 index 000000000..5cf32ceb3 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Api.Syntax; + +public partial class ParameterSyntax +{ + public new FunctionDeclarationSyntax Parent => (FunctionDeclarationSyntax)((SyntaxNode)this).Parent!; + public int Index + { + get + { + foreach (var (parameter, i) in this.Parent.ParameterList.Values.Select((s, i) => (s, i))) + { + if (parameter == this) return i; + } + throw new InvalidOperationException(); + } + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 831da81dc..0c6b6f665 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -154,7 +154,6 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.currentIdx++; } - private int parameterCount = 0; public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) { if (node is Api.Syntax.SeparatedSyntaxList @@ -165,7 +164,6 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitSeparatedSyntaxList(node) ); - this.parameterCount = 0; } else { @@ -179,17 +177,20 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) { return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; } - if (this.parameterCount > 0) + if (node.Index > 0) { this.CurrentToken.LeftPadding = " "; } this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); base.VisitParameter(node); - this.parameterCount++; } public override void VisitExpression(Api.Syntax.ExpressionSyntax node) { + if (node.ArgumentIndex > 0) + { + this.CurrentToken.LeftPadding = " "; + } base.VisitExpression(node); } diff --git a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs index e9050b591..053632c72 100644 --- a/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs +++ b/src/Draco.SourceGeneration/SyntaxTree/SyntaxTreeSourceGenerator.cs @@ -18,10 +18,10 @@ protected override IEnumerable> GenerateSources(obj var greenTreeCode = CodeGenerator.GenerateGreenSyntaxTree(domainModel, cancellationToken); var redTreeCode = CodeGenerator.GenerateRedSyntaxTree(domainModel, cancellationToken); - return new KeyValuePair[] - { + return + [ new("GreenSyntaxTree.Generated.cs", greenTreeCode), new("RedSyntaxTree.Generated.cs", redTreeCode), - }; + ]; } } From 61a1dbe53237e7e7a7cb127e0b733582c03101c1 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 6 Apr 2024 17:33:56 +0200 Subject: [PATCH 11/76] More tests are passing. --- .../Syntax/ParseTreeFormatterTests.cs | 35 ++++++++- .../Api/Syntax/StringExpressionSyntax.cs | 12 +++ .../Syntax/Formatting/CollapsibleBool.cs | 4 + .../Internal/Syntax/Formatting/Formatter.cs | 73 ++++++++++++++++--- .../Syntax/Formatting/TokenDecoration.cs | 11 +++ 5 files changed, 122 insertions(+), 13 deletions(-) create mode 100644 src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index a2e53304e..b871c9f06 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -14,7 +14,7 @@ public SyntaxTreeFormatterTests(ITestOutputHelper logger) } [Fact] - public void TestFormatting() + public void SomeCodeSampleShouldBeFormattedCorrectly() { var input = """" func main ( ) { @@ -107,7 +107,7 @@ func main() { } [Fact] - public void TestFormattingInlineMethod() + public void InlineMethodShouldBeFormattedCorrectly() { var input = """ import System.Console; @@ -136,7 +136,7 @@ func main() { } [Fact] - public void TestFoldExpression() + public void SimpleExpressionShouldBeFormattedCorrectly() { var input = """ func aLongMethodName() = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10; @@ -163,4 +163,33 @@ func aLongMethodName() = 1 this.logger.WriteLine(expected); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void ExpressionInMultiLineStringFolds() + { + var input = """" + func main() + { + val someMultiLineString = """ + the result:\{1 + 2 + 3 + 4 + 5 + + 6 + 7 + 8 + 9 + 10} + """; + } + """"; + var expected = """" + func main() { + val someMultiLineString = """ + the result:\{1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10} + """; + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 50 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs new file mode 100644 index 000000000..4a0e88043 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs @@ -0,0 +1,12 @@ +using System; +using System.Linq; + +namespace Draco.Compiler.Api.Syntax; +public partial class StringExpressionSyntax +{ + public int Padding => this.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => + { + if (right.Kind == TriviaKind.Newline) return 0; + return value + right.Span.Length; + }); +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs index 311e1185d..f9c452515 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -52,4 +53,7 @@ public override bool Equals(object? obj) } public SolverTask Collapsed => this.task; + + public static bool operator ==(CollapsibleBool? left, CollapsibleBool? right) => EqualityComparer.Default.Equals(left, right); + public static bool operator !=(CollapsibleBool? left, CollapsibleBool? right) => !(left == right); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 0c6b6f665..0a3b9b2f4 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -92,7 +92,8 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) } if (decoration.Indentation is not null) builder.Append(decoration.Indentation.Result); builder.Append(decoration.LeftPadding); - builder.Append(token.Text); + + builder.Append(decoration.TokenOverride ?? token.Text); builder.Append(decoration.RightPadding); i++; } @@ -220,19 +221,69 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod base.VisitStringExpression(node); return; } + node.OpenQuotes.Accept(this); + using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); + var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => + { + if (right.Kind == TriviaKind.Newline) return 0; + return value + right.Span.Length; + }); var i = 0; + var newLineCount = 1; + var shouldIndent = true; + for (; i < node.Parts.Count; i++) { - if (node.Parts[i].Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }) + var curr = node.Parts[i]; + + var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; + if (shouldIndent) { - continue; + var tokenText = curr.Tokens.First().ValueText!; + if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); + this.tokenDecorations[this.currentIdx].TokenOverride = tokenText.Substring(blockCurrentIndentCount); + MultiIndent(newLineCount); + shouldIndent = false; + } + + if (isNewLine) + { + newLineCount++; + shouldIndent = true; + } + else + { + newLineCount = 0; + } + + var tokenCount = curr.Tokens.Count(); + for (var j = 0; j < tokenCount; j++) + { + ref var decoration = ref this.tokenDecorations[this.currentIdx + j]; + if (decoration.Indentation is null) + { + decoration.SetIndentation(SolverTask.FromResult(null)); + } + } + curr.Accept(this); + } + MultiIndent(newLineCount); + this.tokenDecorations[this.currentIdx].SetIndentation(GetIndentation(this.scope.ThisAndParents)); + node.CloseQuotes.Accept(this); + + + void MultiIndent(int newLineCount) + { + if (newLineCount > 0) + { + ref var currentToken = ref this.tokenDecorations[this.currentIdx]; + currentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); + if (newLineCount > 1) + { + currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); + } } - ref var currentToken = ref this.tokenDecorations[this.currentIdx + i + 1]; - currentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } - using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); - this.tokenDecorations[this.currentIdx + i + 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); - base.VisitStringExpression(node); } public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) @@ -252,8 +303,10 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod if (!isCollapsed) return null; return await GetIndentation(scope.ThisAndParents); } - - this.CurrentToken.SetIndentation(Indentation(this.scope)); + if(this.CurrentToken.Indentation is null) + { + this.CurrentToken.SetIndentation(Indentation(this.scope)); + } node.Operator.Accept(this); node.Right.Accept(this); closeScope?.Dispose(); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 2d5d6ef50..e9f25b5c2 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -10,7 +10,18 @@ internal struct TokenDecoration private string? leftPadding; private SolverTask? indentation; private ScopeInfo scopeInfo; + private string? tokenOverride; + [DisallowNull] + public string? TokenOverride + { + get => this.tokenOverride; + set + { + if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); + this.tokenOverride = value; + } + } public int TokenSize { get; set; } public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; From dba4b227e18ee6d9bc30f7845b0a33d82f27f6f8 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 10 Apr 2024 11:44:21 +0200 Subject: [PATCH 12/76] Time for bulldozing. --- .../Syntax/ParseTreeFormatterTests.cs | 27 +++++++++++++++++ .../Syntax/Formatting/CollapsibleInt.cs | 4 +-- .../Internal/Syntax/Formatting/Formatter.cs | 29 ++++++++++++++----- 3 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index b871c9f06..d96876be0 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -192,4 +192,31 @@ func main() { this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void IfElseChainFormatsCorrectly() + { + var input = """" + func main() { + if (false) + expr1 + else if (false) + expr2 + else if (false) expr3 + else expr4 + } + """"; + var expected = """" + func main() { + if (false) expr1 + else if (false) expr2 + else if (false) expr3 + else expr4 + } + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs index 294992299..415d5c9e0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using Draco.Compiler.Internal.Solver.Tasks; @@ -8,7 +8,7 @@ internal class CollapsibleInt { private readonly SolverTaskCompletionSource? tcs; private readonly SolverTask task; - private int MinimumCurrentValue; + public int MinimumCurrentValue { get; private set; } private CollapsibleInt(SolverTaskCompletionSource tcs) { this.tcs = tcs; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 0a3b9b2f4..c7c9f6e69 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -22,6 +22,7 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor { var indentation = ""; var prevKind = MaterialisationKind.Normal; + foreach (var scope in scopes) { var isMaterialized = await scope.IsMaterialized.Collapsed; @@ -174,10 +175,6 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL public override void VisitParameter(Api.Syntax.ParameterSyntax node) { - static async SolverTask VariableIndentation(ScopeInfo scope) - { - return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; - } if (node.Index > 0) { this.CurrentToken.LeftPadding = " "; @@ -303,7 +300,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod if (!isCollapsed) return null; return await GetIndentation(scope.ThisAndParents); } - if(this.CurrentToken.Indentation is null) + if (this.CurrentToken.Indentation is null) { this.CurrentToken.SetIndentation(Indentation(this.scope)); } @@ -375,7 +372,7 @@ public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => + void Visit() { this.VisitExpression(node); node.IfKeyword.Accept(this); @@ -383,24 +380,40 @@ public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) node.Condition.Accept(this); node.CloseParen.Accept(this); node.Then.Accept(this); - }); + } + + if (this.scope.ItemsCount.MinimumCurrentValue > 1) + { + Visit(); + } + else + { + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); + } node.Else?.Accept(this); } public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { + var isElseIf = node.Expression is Api.Syntax.StatementExpressionSyntax a && a.Statement is Api.Syntax.ExpressionStatementSyntax s && s.Expression is Api.Syntax.IfExpressionSyntax; this.CurrentToken.RightPadding = " "; - if (node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) + if (isElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); } else { this.CurrentToken.LeftPadding = " "; + this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); } this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); } + private static async SolverTask VariableIndentation(ScopeInfo scope) + { + return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; + } + public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) { // this means we are in a if/while/else, and *can* create an indentation with a regular expression folding: From 4dbcc9ea0be05f0bf2c20e5c3007645c532b6f46 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 13 Apr 2024 16:36:24 +0200 Subject: [PATCH 13/76] Some buldozing. --- .../Internal/Solver/Tasks/SolverTask.cs | 13 ++++++ .../Solver/Tasks/SolverTaskAwaiter.cs | 1 + .../Internal/Syntax/Formatting/Formatter.cs | 41 +++++++------------ .../Syntax/Formatting/MaterialisationKind.cs | 8 ---- .../Internal/Syntax/Formatting/ScopeInfo.cs | 1 - .../Syntax/Formatting/TokenDecoration.cs | 28 ++++++++++--- 6 files changed, 51 insertions(+), 41 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs diff --git a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs index 6a5638fa0..842e4bae0 100644 --- a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs +++ b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using System.Runtime.CompilerServices; +using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Solver.Tasks; @@ -19,6 +21,17 @@ public static async SolverTask> WhenAll(IEnumerable WhenAny(IEnumerable> tasks) + { + if (tasks.Any(x => x.IsCompleted)) return FromResult(default); + var tcs = new SolverTaskCompletionSource(); + foreach (var task in tasks) + { + task.Awaiter.OnCompleted(() => tcs.SetResult(default)); + } + return tcs.Task; + } } [AsyncMethodBuilder(typeof(SolverTaskMethodBuilder<>))] diff --git a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs index c4505c9d3..662286f68 100644 --- a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs +++ b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs @@ -21,6 +21,7 @@ internal void SetResult(T? result) { completion(); } + this.completions = null; // avoid completing the completion multiples times. } internal void SetException(Exception? exception) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index c7c9f6e69..9d948dff7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -21,19 +21,12 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor public static async SolverTask GetIndentation(IEnumerable scopes) { var indentation = ""; - var prevKind = MaterialisationKind.Normal; foreach (var scope in scopes) { var isMaterialized = await scope.IsMaterialized.Collapsed; if (isMaterialized) { - var previousIsStrong = prevKind == MaterialisationKind.Strong; - prevKind = scope.MaterialisationKind; - if (scope.MaterialisationKind == MaterialisationKind.Weak && previousIsStrong) - { - continue; - } indentation += scope.Indentation; } } @@ -162,7 +155,6 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL || node is Api.Syntax.SeparatedSyntaxList) { this.CreateFoldableScope(this.Settings.Indentation, - MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitSeparatedSyntaxList(node) ); @@ -219,7 +211,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod return; } node.OpenQuotes.Accept(this); - using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); + using var _ = this.CreateFoldedScope(this.Settings.Indentation); var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => { if (right.Kind == TriviaKind.Newline) return 0; @@ -289,7 +281,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod var kind = node.Operator.Kind; if (!(this.scope.Data?.Equals(kind) ?? false)) { - closeScope = this.CreateFoldableScope("", MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); + closeScope = this.CreateFoldableScope("", SolverTask.FromResult(FoldPriority.AsLateAsPossible)); this.scope.Data = kind; } node.Left.Accept(this); @@ -323,7 +315,7 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { this.CurrentToken.LeftPadding = " "; - this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => base.VisitBlockFunctionBody(node)); + this.CreateFoldedScope(this.Settings.Indentation, () => base.VisitBlockFunctionBody(node)); this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); } @@ -331,7 +323,6 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax { var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), - MaterialisationKind.Weak, SolverTask.FromResult(FoldPriority.AsSoonAsPossible) ); base.VisitInlineFunctionBody(node); @@ -366,7 +357,7 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) @@ -388,7 +379,7 @@ void Visit() } else { - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); } node.Else?.Accept(this); } @@ -406,7 +397,7 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) this.CurrentToken.LeftPadding = " "; this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); } - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); + this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); } private static async SolverTask VariableIndentation(ScopeInfo scope) @@ -424,7 +415,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. this.scope.IsMaterialized.TryCollapse(false); - this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => + this.CreateFoldedScope(this.Settings.Indentation, () => { this.VisitExpression(node); node.OpenBrace.Accept(this); @@ -445,11 +436,10 @@ public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) base.VisitTypeSpecifier(node); } - private IDisposable CreateFoldedScope(string indentation, MaterialisationKind materialisationKind) + private IDisposable CreateFoldedScope(string indentation) { this.scope = new ScopeInfo(this.scope, indentation); this.scope.IsMaterialized.Collapse(true); - this.scope.MaterialisationKind = materialisationKind; return new DisposeAction(() => { this.scope.Dispose(); @@ -457,17 +447,14 @@ private IDisposable CreateFoldedScope(string indentation, MaterialisationKind ma }); } - private void CreateFoldedScope(string indentation, MaterialisationKind materialisationKind, Action action) + private void CreateFoldedScope(string indentation, Action action) { - using (this.CreateFoldedScope(indentation, materialisationKind)) action(); + using (this.CreateFoldedScope(indentation)) action(); } - private IDisposable CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior) + private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior) { - this.scope = new ScopeInfo(this.scope, indentation, foldBehavior) - { - MaterialisationKind = materialisationKind - }; + this.scope = new ScopeInfo(this.scope, indentation, foldBehavior); return new DisposeAction(() => { this.scope.Dispose(); @@ -475,8 +462,8 @@ private IDisposable CreateFoldableScope(string indentation, MaterialisationKind }); } - private void CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior, Action action) + private void CreateFoldableScope(string indentation, SolverTask foldBehavior, Action action) { - using (this.CreateFoldableScope(indentation, materialisationKind, foldBehavior)) action(); + using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs deleted file mode 100644 index 27581049c..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Draco.Compiler.Internal.Syntax.Formatting; - -enum MaterialisationKind -{ - Normal, - Weak, - Strong -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 767d91908..5c82b621e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -37,7 +37,6 @@ public ScopeInfo(ScopeInfo? parent, string indentation, SolverTask /// /// public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); - public MaterialisationKind MaterialisationKind { get; set; } public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public string Indentation { get; } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index e9f25b5c2..086407d2a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -8,7 +10,7 @@ internal struct TokenDecoration { private string? rightPadding; private string? leftPadding; - private SolverTask? indentation; + private IReadOnlyCollection>? indentation; private ScopeInfo scopeInfo; private string? tokenOverride; @@ -25,7 +27,7 @@ public string? TokenOverride public int TokenSize { get; set; } public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; - private readonly int CurrentIndentationSize => this.Indentation?.IsCompleted ?? false ? this.Indentation.Result?.Length ?? 0 : 0; + private readonly int CurrentIndentationSize => this.Indentation?.Select(x => x.IsCompleted ? x.Result : null).Sum(x => x?.Length ?? 0) ?? 0; [DisallowNull] public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } @@ -42,16 +44,32 @@ public ScopeInfo ScopeInfo this.scopeInfo = value; } } - public readonly SolverTask? Indentation => this.indentation; + public readonly IReadOnlyCollection>? Indentation => this.indentation; - public void SetIndentation(SolverTask? value) + public void SetIndentation(IReadOnlyCollection?> value) { if (this.indentation is not null) { - if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; + //if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; throw new InvalidOperationException("Indentation already set."); } + var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); + var cnt = value.Count; + foreach (var item in value) + { + item.Awaiter.OnCompleted(() => + { + cnt--; + if (item.Result != null) + { + doesReturnLine.TryCollapse(true); + } else if(cnt == 0) + { + doesReturnLine.TryCollapse(false); + } + }); + } this.indentation = value; this.indentation!.Awaiter.OnCompleted(() => { From e5f1661154588af6028a6e374f9c2c2075ef5669 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 21 Apr 2024 18:46:06 +0200 Subject: [PATCH 14/76] rebuldozer --- .../Syntax/ParseTreeFormatterTests.cs | 25 +- .../DracoToFormattingTreeVisitor.cs | 557 ++++++++++++++++++ .../Internal/Syntax/Formatting/Formatter.cs | 437 +------------- .../Syntax/Formatting/TokenDecoration.cs | 166 +++--- 4 files changed, 668 insertions(+), 517 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index d96876be0..9de80ed95 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -112,7 +112,7 @@ public void InlineMethodShouldBeFormattedCorrectly() var input = """ import System.Console; - func max(a:int32, b:int32): int32 = if (a > b) a else b; + func max(a:int32, b:int32, c:int32): int32 = if (a > b) a else b; func main() { WriteLine(max(12, 34)); @@ -122,7 +122,7 @@ func main() { var expected = """ import System.Console; - func max(a:int32, b:int32): int32 = if (a > b) a else b; + func max(a:int32, b:int32, c:int32): int32 = if (a > b) a else b; func main() { WriteLine(max(12, 34)); @@ -219,4 +219,25 @@ func main() { this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void ChainedComparisonOperatorsFormatsCorrectly() + { + var input = """" + func main() { + if (a > + b < c) + expr1 + } + """"; + var expected = """" + func main() { + if (a > b < c) expr1 + } + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs new file mode 100644 index 000000000..e8f2a8e67 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs @@ -0,0 +1,557 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; +internal class DracoToFormattingTreeVisitor : Api.Syntax.SyntaxVisitor +{ + private GroupsNode root = null!; + private FormattingNode current = null!; + + public DracoToFormattingTreeVisitor() + { + } + + public override FormattingNode VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) + { + this.root = new GroupsNode() { Parent = null }; + this.current = this.root; + using var _ = this.Scope(this.root); + foreach (var declaration in node.Declarations) + { + // TODO: separate group type by declaration kind + this.root.Items.Add(new GroupsNode.GroupInfo("TODO", declaration.Accept(this))); + } + return this.root; + } + + public override FormattingNode VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) + { + if (!node.Values.Any()) return default!; + if (!node.Values.Skip(1).Any()) + { + return node.Values.First().Accept(this); + } + var list = new SeparatedListNode() + { + Separator = node.Separators.First().Text, + Parent = this.current, + }; + using var _ = this.Scope(list); + foreach (var item in node.Values) + { + list.Items.Add(item.Accept(this)); + } + return list; + } + + public override FormattingNode VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) + { + var block = new CodeBlockNode() + { + CodeBlockKind = "BlockExpression", + Parent = this.current, + Opening = (TokenNode)node.OpenBrace.Accept(this), + ClosingString = (TokenNode)node.CloseBrace.Accept(this), + }; + var group = new GroupsNode() { Parent = block }; ; + block.Content = group; + using var _ = this.Scope(block); + foreach (var statement in node.Statements) + { + // TODO: separate group type by statement/declaration + group.Items.Add(new GroupsNode.GroupInfo("TODO", statement.Accept(this))); + } + if (node.Value != null) + { + group.Items.Add(new GroupsNode.GroupInfo("Value", node.Value.Accept(this))); + } + return block; + } + + public override FormattingNode VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) + { + var list = new ListNode() + { + ListKind = "FunctionDeclaration", + Parent = this.current, + }; + using var _ = this.Scope(list); + if (node.VisibilityModifier != null) + { + list.Items.Add(node.VisibilityModifier.Accept(this)); + } + list.Items.Add(node.FunctionKeyword.Accept(this)); + list.Items.Add(node.Name.Accept(this)); + list.Items.Add(node.OpenParen.Accept(this)); + if (node.ParameterList.Values.Any()) + { + list.Items.Add(node.ParameterList.Accept(this)); + } + list.Items.Add(node.CloseParen.Accept(this)); + if (node.ReturnType != null) + { + list.Items.Add(node.ReturnType.Accept(this)); + } + list.Items.Add(node.Body.Accept(this)); + return list; + } + + public override FormattingNode VisitParameter(Api.Syntax.ParameterSyntax node) + { + var list = new ListNode() + { + ListKind = "Parameter", + Parent = this.current, + }; + using var _ = this.Scope(list); + if (node.Variadic != null) + { + list.Items.Add(node.Variadic.Accept(this)); + } + list.Items.Add(node.Name.Accept(this)); + list.Items.Add(node.Colon.Accept(this)); + list.Items.Add(node.Type.Accept(this)); + return list; + } + + public override FormattingNode VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) + { + var block = new CodeBlockNode() + { + CodeBlockKind = "BlockFunctionBody", + Parent = this.current, + Opening = (TokenNode)node.OpenBrace.Accept(this), + ClosingString = (TokenNode)node.CloseBrace.Accept(this), + }; + var group = new GroupsNode() { Parent = block }; ; + block.Content = group; + using var _ = this.Scope(block); + foreach (var statement in node.Statements) + { + // TODO: separate group type by statement/declaration + group.Items.Add(new GroupsNode.GroupInfo("TODO", statement.Accept(this))); + } + return block; + } + + public override FormattingNode VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) + { + var list = new ListNode() + { + ListKind = "InlineFunctionBody", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Assign.Accept(this)); + list.Items.Add(node.Value.Accept(this)); + list.Items.Add(node.Semicolon.Accept(this)); + return list; + } + + public override FormattingNode VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) + { + var operatorNode = new OperatorNode() + { + Operator = node.Operator.Text, + Parent = this.current, + }; + using var _ = this.Scope(operatorNode); + operatorNode.Left = node.Left.Accept(this); + operatorNode.Right = node.Right.Accept(this); + return operatorNode; + } + + public override FormattingNode VisitCallExpression(Api.Syntax.CallExpressionSyntax node) + { + var list = new ListNode() + { + ListKind = "CallExpression", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Function.Accept(this)); + var argBlock = new CodeBlockNode() + { + CodeBlockKind = "Arguments", + Parent = list, + Opening = (TokenNode)node.OpenParen.Accept(this), + ClosingString = (TokenNode)node.CloseParen.Accept(this) + }; + list.Items.Add(argBlock); + using var __ = this.Scope(argBlock); + argBlock.Content = node.ArgumentList.Accept(this); + return list; + } + + public override FormattingNode VisitRelationalExpression(Api.Syntax.RelationalExpressionSyntax node) + { + var list = new ListNode() + { + ListKind = "RelationalExpression", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Left.Accept(this)); + foreach (var comparison in node.Comparisons) + { + list.Items.Add(comparison.Operator.Accept(this)); + list.Items.Add(comparison.Right.Accept(this)); + } + return list; + } + + public override FormattingNode VisitComparisonElement(Api.Syntax.ComparisonElementSyntax node) + { + var list = new ListNode() + { + ListKind = "ComparisonElement", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Operator.Accept(this)); + list.Items.Add(node.Right.Accept(this)); + return list; + } + + public override FormattingNode VisitIfExpression(Api.Syntax.IfExpressionSyntax node) + { + var list = new ListNode() + { + ListKind = "IfExpression", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.IfKeyword.Accept(this)); + list.Items.Add(node.OpenParen.Accept(this)); + list.Items.Add(node.Condition.Accept(this)); + list.Items.Add(node.CloseParen.Accept(this)); + list.Items.Add(node.Then.Accept(this)); + if (node.Else != null) + { + list.Items.Add(node.Else.Accept(this)); + } + return list; + } + + public override FormattingNode VisitElseClause(Api.Syntax.ElseClauseSyntax node) + { + var list = new ListNode() + { + ListKind = "ElseClause", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.ElseKeyword.Accept(this)); + list.Items.Add(node.Expression.Accept(this)); + return list; + } + + + public override FormattingNode VisitExpressionStatement(Api.Syntax.ExpressionStatementSyntax node) + { + var list = new ListNode() + { + ListKind = "ExpressionStatement", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Expression.Accept(this)); + if (node.Semicolon != null) + { + list.Items.Add(node.Semicolon.Accept(this)); + } + return list; + } + + public override FormattingNode VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) + { + var list = new ListNode() + { + ListKind = "VariableDeclaration", + Parent = this.current, + }; + using var _ = this.Scope(list); + if (node.VisibilityModifier != null) + { + list.Items.Add(node.VisibilityModifier.Accept(this)); + } + list.Items.Add(node.Keyword.Accept(this)); + list.Items.Add(node.Name.Accept(this)); + if (node.Type != null) + { + list.Items.Add(node.Type.Accept(this)); + } + + if (node.Value != null) + { + list.Items.Add(node.Value.Accept(this)); + } + if (node.Semicolon != null) + { + list.Items.Add(node.Semicolon.Accept(this)); + } + return list; + } + + public override FormattingNode VisitValueSpecifier(Api.Syntax.ValueSpecifierSyntax node) + { + var list = new ListNode() + { + ListKind = "ValueSpecifier", + Parent = this.current, + }; + + using var _ = this.Scope(list); + list.Items.Add(node.Assign.Accept(this)); + list.Items.Add(node.Value.Accept(this)); + return list; + } + + public override FormattingNode VisitStringExpression(Api.Syntax.StringExpressionSyntax node) + { + var list = new ListNode() + { + ListKind = "StringExpression", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.OpenQuotes.Accept(this)); + list.Items.Add(node.Parts.Accept(this)); + list.Items.Add(node.CloseQuotes.Accept(this)); + return list; + } + + public override FormattingNode VisitSyntaxList(Api.Syntax.SyntaxList node) + { + var list = new ListNode() + { + ListKind = "SyntaxList", + Parent = this.current, + }; + using var _ = this.Scope(list); + foreach (var item in node) + { + list.Items.Add(item.Accept(this)); + } + return list; + } + + public override FormattingNode VisitInterpolationStringPart(Api.Syntax.InterpolationStringPartSyntax node) + { + var codeBlock = new CodeBlockNode() + { + CodeBlockKind = "InterpolationStringPart", + Parent = this.current, + Opening = (TokenNode)node.Open.Accept(this), + ClosingString = (TokenNode)node.Close.Accept(this), + }; + using var _ = this.Scope(codeBlock); + codeBlock.Content = node.Expression.Accept(this); + return codeBlock; + } + + + public override FormattingNode VisitImportDeclaration(Api.Syntax.ImportDeclarationSyntax node) + { + var list = new ListNode() + { + ListKind = "ImportDeclaration", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.ImportKeyword.Accept(this)); + list.Items.Add(node.Path.Accept(this)); + list.Items.Add(node.Semicolon.Accept(this)); + return list; + } + + public override FormattingNode VisitMemberImportPath(Api.Syntax.MemberImportPathSyntax node) + { + // unrecursive the tree into a list + var list = new SeparatedListNode() + { + Parent = this.current, + Separator = "." + }; + using var _ = this.Scope(list); + var toReverse = new List(); + var current = node; + while (true) + { + toReverse.Add(current.Member.Accept(this)); + if (current.Parent is not Api.Syntax.MemberImportPathSyntax parent) + { + var asRoot = (Api.Syntax.RootImportPathSyntax)current.Accessed!; + toReverse.Add(asRoot.Name.Accept(this)); + break; + } + current = parent; + } + list.Items.AddRange(toReverse.Reverse()); + return list; + } + + public override FormattingNode VisitLabelDeclaration(Api.Syntax.LabelDeclarationSyntax node) + { + var list = new ListNode() + { + ListKind = "LabelDeclaration", + Parent = this.current, + }; + using var _ = this.Scope(list); + list.Items.Add(node.Name.Accept(this)); + list.Items.Add(node.Colon.Accept(this)); + return list; + } + + + public override FormattingNode VisitSyntaxToken(Api.Syntax.SyntaxToken node) => new TokenNode() + { + Parent = this.current, + Text = node.Text + }; + + public override FormattingNode VisitNameExpression(Api.Syntax.NameExpressionSyntax node) => new TokenNode() + { + Parent = this.current, + Text = node.Name.Text + }; + public override FormattingNode VisitTextStringPart(Api.Syntax.TextStringPartSyntax node) => new TokenNode() + { + //TODO: remove indentation of previous code here. + Parent = this.current, + Text = node.Content.Text + }; + + public override FormattingNode VisitLiteralExpression(Api.Syntax.LiteralExpressionSyntax node) => new TokenNode() + { + Parent = this.current, + Text = node.Literal.Text + }; + + public override FormattingNode VisitStatementExpression(Api.Syntax.StatementExpressionSyntax node) => node.Statement.Accept(this); + public override FormattingNode VisitDeclarationStatement(Api.Syntax.DeclarationStatementSyntax node) => node.Declaration.Accept(this); + public override FormattingNode VisitNameType(Api.Syntax.NameTypeSyntax node) => node.Name.Accept(this); + + + private DisposeAction Scope(FormattingNode node) + { + var prevCurrent = this.current; + this.current = node; + return new DisposeAction(() => + { + this.current = prevCurrent; + }); + } +} + +internal abstract class FormattingNode +{ + public required FormattingNode? Parent { get; init; } + + public string EscapedString => this.ToString().Replace("\n", "\\n").Replace("\r", "\\r"); +} + +/// +/// Groups separated by a new line. +/// For example, +/// +internal class GroupsNode : FormattingNode +{ + public List Items { get; } = []; + + public class GroupInfo + { + public GroupInfo(string ItemGroupKind, FormattingNode Node) + { + this.ItemGroupKind = ItemGroupKind ?? throw new ArgumentNullException(nameof(ItemGroupKind)); + this.Node = Node ?? throw new ArgumentNullException(nameof(Node)); + } + + public string ItemGroupKind { get; } + public FormattingNode Node { get; } + } + + public override string ToString() + { + var sb = new StringBuilder(); + foreach (var item in this.Items) + { + sb.AppendLine(item.Node.ToString()); + } + return sb.ToString(); + } +} + +internal class TokenNode : FormattingNode +{ + public required string Text { get; init; } + + public override string ToString() => this.Text; +} + +internal class CodeBlockNode : FormattingNode +{ + public required string CodeBlockKind { get; init; } + public required TokenNode Opening { get; init; } + public FormattingNode Content { get; set; } + public required TokenNode ClosingString { get; init; } + + public override string ToString() => $"{this.Opening}\n{this.Content}\n{this.ClosingString}"; +} + +internal class ListNode : FormattingNode +{ + public required string ListKind { get; init; } + public List Items { get; } = []; + + public override string ToString() + { + var sb = new StringBuilder(); + for (var i = 0; i < this.Items.Count; i++) + { + sb.Append(this.Items[i]); + var isLast = i == this.Items.Count - 1; + if (!isLast) + { + sb.Append(' '); + } + } + return sb.ToString(); + } +} + +internal class SeparatedListNode : FormattingNode +{ + public required string Separator { get; init; } + public List Items { get; } = []; + + public override string ToString() + { + var sb = new StringBuilder(); + for (var i = 0; i < this.Items.Count; i++) + { + if (i > 0) + { + sb.Append(this.Separator); + sb.Append(' '); + } + sb.Append(this.Items[i]); + } + return sb.ToString(); + } +} + +internal class OperatorNode : FormattingNode +{ + public required string Operator { get; init; } + public FormattingNode Left { get; set; } + public FormattingNode Right { get; set; } + + public override string ToString() => $"{this.Left} {this.Operator} {this.Right}"; +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 9d948dff7..9851e52cf 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -10,29 +10,6 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal sealed class Formatter : Api.Syntax.SyntaxVisitor { - private TokenDecoration[] tokenDecorations = []; - private int currentIdx; - private ScopeInfo scope; - private readonly SyntaxTree tree; // debugging helper, to remove - private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; - - private bool firstDeclaration = true; - - public static async SolverTask GetIndentation(IEnumerable scopes) - { - var indentation = ""; - - foreach (var scope in scopes) - { - var isMaterialized = await scope.IsMaterialized.Collapsed; - if (isMaterialized) - { - indentation += scope.Indentation; - } - } - return indentation; - } - /// /// Formats the given syntax tree. /// @@ -44,55 +21,11 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) settings ??= FormatterSettings.Default; var formatter = new Formatter(settings, tree); - + var dracoToFormattingTreeVisitor = new DracoToFormattingTreeVisitor(); + var res = tree.Root.Accept(dracoToFormattingTreeVisitor); + var resStr = res.ToString(); tree.Root.Accept(formatter); - var decorations = formatter.tokenDecorations; - var currentLineLength = 0; - var currentLineStart = 0; - for (var x = 0; x < decorations.Length; x++) - { - var curr = decorations[x]; - if (curr.DoesReturnLineCollapsible?.Collapsed.Result ?? false) - { - currentLineLength = 0; - currentLineStart = x; - } - - currentLineLength += curr.CurrentTotalSize; - if (currentLineLength > settings.LineWidth) - { - if (curr.ScopeInfo.Fold()) - { - x = currentLineStart; - continue; - } - } - } - - var builder = new StringBuilder(); - var i = 0; - foreach (var token in tree.Root.Tokens) - { - if (token.Kind == TokenKind.StringNewline) - { - i++; - continue; - } - var decoration = formatter.tokenDecorations[i]; - - if (decoration.DoesReturnLineCollapsible is not null) - { - builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? settings.Newline : ""); // will default to false if not collapsed, that what we want. - } - if (decoration.Indentation is not null) builder.Append(decoration.Indentation.Result); - builder.Append(decoration.LeftPadding); - - builder.Append(decoration.TokenOverride ?? token.Text); - builder.Append(decoration.RightPadding); - i++; - } - builder.AppendLine(); - return builder.ToString(); + return null; } /// @@ -103,367 +36,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) private Formatter(FormatterSettings settings, SyntaxTree tree) { this.Settings = settings; - this.tree = tree; - this.scope = new(null, ""); - this.scope.IsMaterialized.Collapse(true); - } - - public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) - { - this.tokenDecorations = new TokenDecoration[node.Tokens.Count()]; - base.VisitCompilationUnit(node); - } - - public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) - { - this.CurrentToken.ScopeInfo = this.scope; - switch (node.Kind) - { - case TokenKind.Minus: - case TokenKind.Plus: - case TokenKind.Star: - case TokenKind.Slash: - case TokenKind.GreaterThan: - case TokenKind.LessThan: - case TokenKind.GreaterEqual: - case TokenKind.LessEqual: - case TokenKind.Equal: - case TokenKind.Assign: - case TokenKind.KeywordMod: - this.CurrentToken.RightPadding = " "; - this.CurrentToken.LeftPadding = " "; - break; - case TokenKind.KeywordVar: - case TokenKind.KeywordVal: - case TokenKind.KeywordFunc: - case TokenKind.KeywordReturn: - case TokenKind.KeywordGoto: - case TokenKind.KeywordWhile: - case TokenKind.KeywordIf: - case TokenKind.KeywordImport: - this.CurrentToken.RightPadding = " "; - break; - } - base.VisitSyntaxToken(node); - this.CurrentToken.TokenSize = node.Green.Width; - this.currentIdx++; - } - - public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) - { - if (node is Api.Syntax.SeparatedSyntaxList - || node is Api.Syntax.SeparatedSyntaxList) - { - this.CreateFoldableScope(this.Settings.Indentation, - SolverTask.FromResult(FoldPriority.AsSoonAsPossible), - () => base.VisitSeparatedSyntaxList(node) - ); - } - else - { - base.VisitSeparatedSyntaxList(node); - } - } - - public override void VisitParameter(Api.Syntax.ParameterSyntax node) - { - if (node.Index > 0) - { - this.CurrentToken.LeftPadding = " "; - } - this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); - base.VisitParameter(node); - } - - public override void VisitExpression(Api.Syntax.ExpressionSyntax node) - { - if (node.ArgumentIndex > 0) - { - this.CurrentToken.LeftPadding = " "; - } - base.VisitExpression(node); - } - - public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) - { - if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) - { - - if (!this.firstDeclaration) - { - async SolverTask DoubleNewLine() => this.Settings.Newline + await GetIndentation(this.scope.ThisAndParents); - this.CurrentToken.SetIndentation(DoubleNewLine()); - } - else - { - this.CurrentToken.SetIndentation(SolverTask.FromResult(null as string)); - } - this.firstDeclaration = false; - } - base.VisitDeclaration(node); - } - - public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax node) - { - if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) - { - base.VisitStringExpression(node); - return; - } - node.OpenQuotes.Accept(this); - using var _ = this.CreateFoldedScope(this.Settings.Indentation); - var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => - { - if (right.Kind == TriviaKind.Newline) return 0; - return value + right.Span.Length; - }); - var i = 0; - var newLineCount = 1; - var shouldIndent = true; - - for (; i < node.Parts.Count; i++) - { - var curr = node.Parts[i]; - - var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; - if (shouldIndent) - { - var tokenText = curr.Tokens.First().ValueText!; - if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.tokenDecorations[this.currentIdx].TokenOverride = tokenText.Substring(blockCurrentIndentCount); - MultiIndent(newLineCount); - shouldIndent = false; - } - - if (isNewLine) - { - newLineCount++; - shouldIndent = true; - } - else - { - newLineCount = 0; - } - - var tokenCount = curr.Tokens.Count(); - for (var j = 0; j < tokenCount; j++) - { - ref var decoration = ref this.tokenDecorations[this.currentIdx + j]; - if (decoration.Indentation is null) - { - decoration.SetIndentation(SolverTask.FromResult(null)); - } - } - curr.Accept(this); - } - MultiIndent(newLineCount); - this.tokenDecorations[this.currentIdx].SetIndentation(GetIndentation(this.scope.ThisAndParents)); - node.CloseQuotes.Accept(this); - - - void MultiIndent(int newLineCount) - { - if (newLineCount > 0) - { - ref var currentToken = ref this.tokenDecorations[this.currentIdx]; - currentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); - if (newLineCount > 1) - { - currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); - } - } - } - } - - public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) - { - IDisposable? closeScope = null; - var kind = node.Operator.Kind; - if (!(this.scope.Data?.Equals(kind) ?? false)) - { - closeScope = this.CreateFoldableScope("", SolverTask.FromResult(FoldPriority.AsLateAsPossible)); - this.scope.Data = kind; - } - node.Left.Accept(this); - - static async SolverTask Indentation(ScopeInfo scope) - { - var isCollapsed = await scope.IsMaterialized.Collapsed; - if (!isCollapsed) return null; - return await GetIndentation(scope.ThisAndParents); - } - if (this.CurrentToken.Indentation is null) - { - this.CurrentToken.SetIndentation(Indentation(this.scope)); - } - node.Operator.Accept(this); - node.Right.Accept(this); - closeScope?.Dispose(); - } - - public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) - { - base.VisitMemberExpression(node); - this.scope.ItemsCount.Add(1); - } - - public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) - { - base.VisitFunctionDeclaration(node); - } - - public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) - { - this.CurrentToken.LeftPadding = " "; - this.CreateFoldedScope(this.Settings.Indentation, () => base.VisitBlockFunctionBody(node)); - this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); - } - - public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) - { - var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; - using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), - SolverTask.FromResult(FoldPriority.AsSoonAsPossible) - ); - base.VisitInlineFunctionBody(node); - } - - public override void VisitStatement(Api.Syntax.StatementSyntax node) - { - this.scope.ItemsCount.Add(1); - - if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) - { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.Parents)); - } - else if (node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax) - { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); - } - else - { - async SolverTask Indentation() - { - var haveMoreThanOneStatement = await this.scope.ItemsCount.WhenGreaterOrEqual(2); - if (haveMoreThanOneStatement) return await GetIndentation(this.scope.ThisAndParents); - return null; - } - this.CurrentToken.SetIndentation(Indentation()); - - } - base.VisitStatement(node); - } - - public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) - { - this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); - } - - public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) - { - this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - void Visit() - { - this.VisitExpression(node); - node.IfKeyword.Accept(this); - node.OpenParen.Accept(this); - node.Condition.Accept(this); - node.CloseParen.Accept(this); - node.Then.Accept(this); - } - - if (this.scope.ItemsCount.MinimumCurrentValue > 1) - { - Visit(); - } - else - { - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); - } - node.Else?.Accept(this); - } - - public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) - { - var isElseIf = node.Expression is Api.Syntax.StatementExpressionSyntax a && a.Statement is Api.Syntax.ExpressionStatementSyntax s && s.Expression is Api.Syntax.IfExpressionSyntax; - this.CurrentToken.RightPadding = " "; - if (isElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) - { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); - } - else - { - this.CurrentToken.LeftPadding = " "; - this.CurrentToken.SetIndentation(VariableIndentation(this.scope)); - } - this.CreateFoldableScope(this.Settings.Indentation, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); - } - - private static async SolverTask VariableIndentation(ScopeInfo scope) - { - return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; - } - - public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) - { - // this means we are in a if/while/else, and *can* create an indentation with a regular expression folding: - // if (blabla) an expression; - // it can fold: - // if (blabla) - // an expression; - // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - this.scope.IsMaterialized.TryCollapse(false); - - this.CreateFoldedScope(this.Settings.Indentation, () => - { - this.VisitExpression(node); - node.OpenBrace.Accept(this); - node.Statements.Accept(this); - if (node.Value != null) - { - this.CurrentToken.SetIndentation(GetIndentation(this.scope.ThisAndParents)); - node.Value.Accept(this); - } - node.CloseBrace.Accept(this); - }); - this.tokenDecorations[this.currentIdx - 1].SetIndentation(GetIndentation(this.scope.ThisAndParents)); - } - - public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) - { - this.CurrentToken.RightPadding = " "; - base.VisitTypeSpecifier(node); - } - - private IDisposable CreateFoldedScope(string indentation) - { - this.scope = new ScopeInfo(this.scope, indentation); - this.scope.IsMaterialized.Collapse(true); - return new DisposeAction(() => - { - this.scope.Dispose(); - this.scope = this.scope.Parent!; - }); } +} - private void CreateFoldedScope(string indentation, Action action) - { - using (this.CreateFoldedScope(indentation)) action(); - } - private IDisposable CreateFoldableScope(string indentation, SolverTask foldBehavior) - { - this.scope = new ScopeInfo(this.scope, indentation, foldBehavior); - return new DisposeAction(() => - { - this.scope.Dispose(); - this.scope = this.scope.Parent!; - }); - } - - private void CreateFoldableScope(string indentation, SolverTask foldBehavior, Action action) - { - using (this.CreateFoldableScope(indentation, foldBehavior)) action(); - } -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 086407d2a..ee9c634ea 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -6,94 +6,94 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal struct TokenDecoration -{ - private string? rightPadding; - private string? leftPadding; - private IReadOnlyCollection>? indentation; - private ScopeInfo scopeInfo; - private string? tokenOverride; +//internal struct TokenDecoration +//{ +// private string? rightPadding; +// private string? leftPadding; +// private IReadOnlyCollection>? indentation; +// private ScopeInfo scopeInfo; +// private string? tokenOverride; - [DisallowNull] - public string? TokenOverride - { - get => this.tokenOverride; - set - { - if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); - this.tokenOverride = value; - } - } - public int TokenSize { get; set; } - public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; +// [DisallowNull] +// public string? TokenOverride +// { +// get => this.tokenOverride; +// set +// { +// if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); +// this.tokenOverride = value; +// } +// } +// public int TokenSize { get; set; } +// public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; - private readonly int CurrentIndentationSize => this.Indentation?.Select(x => x.IsCompleted ? x.Result : null).Sum(x => x?.Length ?? 0) ?? 0; +// private readonly int CurrentIndentationSize => this.Indentation?.Select(x => x.IsCompleted ? x.Result : null).Sum(x => x?.Length ?? 0) ?? 0; - [DisallowNull] - public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } +// [DisallowNull] +// public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } - public ScopeInfo ScopeInfo - { - readonly get => this.scopeInfo; - set - { - if (this.scopeInfo != null) - { - throw new InvalidOperationException(); - } - this.scopeInfo = value; - } - } - public readonly IReadOnlyCollection>? Indentation => this.indentation; +// public ScopeInfo ScopeInfo +// { +// readonly get => this.scopeInfo; +// set +// { +// if (this.scopeInfo != null) +// { +// throw new InvalidOperationException(); +// } +// this.scopeInfo = value; +// } +// } +// public readonly IReadOnlyCollection>? Indentation => this.indentation; - public void SetIndentation(IReadOnlyCollection?> value) - { - if (this.indentation is not null) - { - //if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; - throw new InvalidOperationException("Indentation already set."); - } +// public void SetIndentation(IReadOnlyCollection?> value) +// { +// if (this.indentation is not null) +// { +// //if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; +// throw new InvalidOperationException("Indentation already set."); +// } - var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); - var cnt = value.Count; - foreach (var item in value) - { - item.Awaiter.OnCompleted(() => - { - cnt--; - if (item.Result != null) - { - doesReturnLine.TryCollapse(true); - } else if(cnt == 0) - { - doesReturnLine.TryCollapse(false); - } - }); - } - this.indentation = value; - this.indentation!.Awaiter.OnCompleted(() => - { - doesReturnLine.Collapse(value.Result != null); - }); - } +// var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); +// var cnt = value.Count; +// foreach (var item in value) +// { +// item.Awaiter.OnCompleted(() => +// { +// cnt--; +// if (item.Result != null) +// { +// doesReturnLine.TryCollapse(true); +// } else if(cnt == 0) +// { +// doesReturnLine.TryCollapse(false); +// } +// }); +// } +// this.indentation = value; +// this.indentation!.Awaiter.OnCompleted(() => +// { +// doesReturnLine.Collapse(value.Result != null); +// }); +// } - public string? LeftPadding - { - readonly get => this.leftPadding; - set - { - if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); - this.leftPadding = value; - } - } - public string? RightPadding - { - readonly get => this.rightPadding; - set - { - if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); - this.rightPadding = value; - } - } +// public string? LeftPadding +// { +// readonly get => this.leftPadding; +// set +// { +// if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); +// this.leftPadding = value; +// } +// } +// public string? RightPadding +// { +// readonly get => this.rightPadding; +// set +// { +// if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); +// this.rightPadding = value; +// } +// } -} +//} From 6987857a06a2287fb84895abcf83030598fb6b75 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 21 Apr 2024 23:38:38 +0200 Subject: [PATCH 15/76] re - re buldozing --- .../DracoToFormattingTreeVisitor.cs | 548 ++---------------- .../Internal/Syntax/Formatting/Formatter.cs | 33 +- 2 files changed, 84 insertions(+), 497 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs index e8f2a8e67..271543d45 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs @@ -7,551 +7,111 @@ using System.Threading.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class DracoToFormattingTreeVisitor : Api.Syntax.SyntaxVisitor +internal class DracoToFormattingTreeVisitor { - private GroupsNode root = null!; - private FormattingNode current = null!; - - public DracoToFormattingTreeVisitor() - { - } - - public override FormattingNode VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) - { - this.root = new GroupsNode() { Parent = null }; - this.current = this.root; - using var _ = this.Scope(this.root); - foreach (var declaration in node.Declarations) - { - // TODO: separate group type by declaration kind - this.root.Items.Add(new GroupsNode.GroupInfo("TODO", declaration.Accept(this))); - } - return this.root; - } - - public override FormattingNode VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) - { - if (!node.Values.Any()) return default!; - if (!node.Values.Skip(1).Any()) - { - return node.Values.First().Accept(this); - } - var list = new SeparatedListNode() - { - Separator = node.Separators.First().Text, - Parent = this.current, - }; - using var _ = this.Scope(list); - foreach (var item in node.Values) - { - list.Items.Add(item.Accept(this)); - } - return list; - } - - public override FormattingNode VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) - { - var block = new CodeBlockNode() - { - CodeBlockKind = "BlockExpression", - Parent = this.current, - Opening = (TokenNode)node.OpenBrace.Accept(this), - ClosingString = (TokenNode)node.CloseBrace.Accept(this), - }; - var group = new GroupsNode() { Parent = block }; ; - block.Content = group; - using var _ = this.Scope(block); - foreach (var statement in node.Statements) - { - // TODO: separate group type by statement/declaration - group.Items.Add(new GroupsNode.GroupInfo("TODO", statement.Accept(this))); - } - if (node.Value != null) - { - group.Items.Add(new GroupsNode.GroupInfo("Value", node.Value.Accept(this))); - } - return block; - } - - public override FormattingNode VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) - { - var list = new ListNode() - { - ListKind = "FunctionDeclaration", - Parent = this.current, - }; - using var _ = this.Scope(list); - if (node.VisibilityModifier != null) - { - list.Items.Add(node.VisibilityModifier.Accept(this)); - } - list.Items.Add(node.FunctionKeyword.Accept(this)); - list.Items.Add(node.Name.Accept(this)); - list.Items.Add(node.OpenParen.Accept(this)); - if (node.ParameterList.Values.Any()) - { - list.Items.Add(node.ParameterList.Accept(this)); - } - list.Items.Add(node.CloseParen.Accept(this)); - if (node.ReturnType != null) - { - list.Items.Add(node.ReturnType.Accept(this)); - } - list.Items.Add(node.Body.Accept(this)); - return list; - } - - public override FormattingNode VisitParameter(Api.Syntax.ParameterSyntax node) - { - var list = new ListNode() - { - ListKind = "Parameter", - Parent = this.current, - }; - using var _ = this.Scope(list); - if (node.Variadic != null) - { - list.Items.Add(node.Variadic.Accept(this)); - } - list.Items.Add(node.Name.Accept(this)); - list.Items.Add(node.Colon.Accept(this)); - list.Items.Add(node.Type.Accept(this)); - return list; - } - - public override FormattingNode VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) - { - var block = new CodeBlockNode() - { - CodeBlockKind = "BlockFunctionBody", - Parent = this.current, - Opening = (TokenNode)node.OpenBrace.Accept(this), - ClosingString = (TokenNode)node.CloseBrace.Accept(this), - }; - var group = new GroupsNode() { Parent = block }; ; - block.Content = group; - using var _ = this.Scope(block); - foreach (var statement in node.Statements) - { - // TODO: separate group type by statement/declaration - group.Items.Add(new GroupsNode.GroupInfo("TODO", statement.Accept(this))); - } - return block; - } - - public override FormattingNode VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) - { - var list = new ListNode() - { - ListKind = "InlineFunctionBody", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Assign.Accept(this)); - list.Items.Add(node.Value.Accept(this)); - list.Items.Add(node.Semicolon.Accept(this)); - return list; - } - - public override FormattingNode VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) - { - var operatorNode = new OperatorNode() - { - Operator = node.Operator.Text, - Parent = this.current, - }; - using var _ = this.Scope(operatorNode); - operatorNode.Left = node.Left.Accept(this); - operatorNode.Right = node.Right.Accept(this); - return operatorNode; - } - - public override FormattingNode VisitCallExpression(Api.Syntax.CallExpressionSyntax node) - { - var list = new ListNode() - { - ListKind = "CallExpression", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Function.Accept(this)); - var argBlock = new CodeBlockNode() - { - CodeBlockKind = "Arguments", - Parent = list, - Opening = (TokenNode)node.OpenParen.Accept(this), - ClosingString = (TokenNode)node.CloseParen.Accept(this) - }; - list.Items.Add(argBlock); - using var __ = this.Scope(argBlock); - argBlock.Content = node.ArgumentList.Accept(this); - return list; - } - - public override FormattingNode VisitRelationalExpression(Api.Syntax.RelationalExpressionSyntax node) + // default converter + public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.SyntaxNode node) { - var list = new ListNode() - { - ListKind = "RelationalExpression", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Left.Accept(this)); - foreach (var comparison in node.Comparisons) - { - list.Items.Add(comparison.Operator.Accept(this)); - list.Items.Add(comparison.Right.Accept(this)); - } - return list; - } - - public override FormattingNode VisitComparisonElement(Api.Syntax.ComparisonElementSyntax node) - { - var list = new ListNode() - { - ListKind = "ComparisonElement", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Operator.Accept(this)); - list.Items.Add(node.Right.Accept(this)); - return list; - } + if (node is Api.Syntax.SyntaxToken token) return new NodeOrToken(new FormattingToken() { Text = token.Text }); - public override FormattingNode VisitIfExpression(Api.Syntax.IfExpressionSyntax node) - { - var list = new ListNode() + var list = new List(); + var converted = new FormattingNode() { - ListKind = "IfExpression", - Parent = this.current, + NodeKind = node.GetType().Name, + Parent = parent, + Childrens = list }; - using var _ = this.Scope(list); - list.Items.Add(node.IfKeyword.Accept(this)); - list.Items.Add(node.OpenParen.Accept(this)); - list.Items.Add(node.Condition.Accept(this)); - list.Items.Add(node.CloseParen.Accept(this)); - list.Items.Add(node.Then.Accept(this)); - if (node.Else != null) + foreach (var child in node.Children) { - list.Items.Add(node.Else.Accept(this)); + list.Add(Convert(converted, child)); } - return list; - } - - public override FormattingNode VisitElseClause(Api.Syntax.ElseClauseSyntax node) - { - var list = new ListNode() - { - ListKind = "ElseClause", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.ElseKeyword.Accept(this)); - list.Items.Add(node.Expression.Accept(this)); - return list; + return new NodeOrToken(converted); } - public override FormattingNode VisitExpressionStatement(Api.Syntax.ExpressionStatementSyntax node) - { - var list = new ListNode() - { - ListKind = "ExpressionStatement", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Expression.Accept(this)); - if (node.Semicolon != null) - { - list.Items.Add(node.Semicolon.Accept(this)); - } - return list; - } + // flatify converter - public override FormattingNode VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) + public static NodeOrToken FlatConvert(FormattingNode parent, Api.Syntax.SyntaxNode node) { - var list = new ListNode() + var list = new List(); + var converted = new FormattingNode() { - ListKind = "VariableDeclaration", - Parent = this.current, + NodeKind = node.GetType().Name, + Parent = parent, + Childrens = list }; - using var _ = this.Scope(list); - if (node.VisibilityModifier != null) + foreach (var child in node.Tokens) { - list.Items.Add(node.VisibilityModifier.Accept(this)); + list.Add(new NodeOrToken(new FormattingToken() { Text = child.Text })); } - list.Items.Add(node.Keyword.Accept(this)); - list.Items.Add(node.Name.Accept(this)); - if (node.Type != null) - { - list.Items.Add(node.Type.Accept(this)); - } - - if (node.Value != null) - { - list.Items.Add(node.Value.Accept(this)); - } - if (node.Semicolon != null) - { - list.Items.Add(node.Semicolon.Accept(this)); - } - return list; + return new NodeOrToken(converted); } - public override FormattingNode VisitValueSpecifier(Api.Syntax.ValueSpecifierSyntax node) - { - var list = new ListNode() - { - ListKind = "ValueSpecifier", - Parent = this.current, - }; - - using var _ = this.Scope(list); - list.Items.Add(node.Assign.Accept(this)); - list.Items.Add(node.Value.Accept(this)); - return list; - } + public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.ImportDeclarationSyntax node) => FlatConvert(parent, node); - public override FormattingNode VisitStringExpression(Api.Syntax.StringExpressionSyntax node) + public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.FunctionDeclarationSyntax node) { - var list = new ListNode() + var list = new List(); + var converted = new FormattingNode() { - ListKind = "StringExpression", - Parent = this.current, + NodeKind = node.GetType().Name, + Parent = parent, + Childrens = list }; - using var _ = this.Scope(list); - list.Items.Add(node.OpenQuotes.Accept(this)); - list.Items.Add(node.Parts.Accept(this)); - list.Items.Add(node.CloseQuotes.Accept(this)); - return list; - } - public override FormattingNode VisitSyntaxList(Api.Syntax.SyntaxList node) - { - var list = new ListNode() + foreach (var child in node.Body.Children) { - ListKind = "SyntaxList", - Parent = this.current, - }; - using var _ = this.Scope(list); - foreach (var item in node) - { - list.Items.Add(item.Accept(this)); + list.Add(Convert(converted, child)); } - return list; - } - - public override FormattingNode VisitInterpolationStringPart(Api.Syntax.InterpolationStringPartSyntax node) - { - var codeBlock = new CodeBlockNode() - { - CodeBlockKind = "InterpolationStringPart", - Parent = this.current, - Opening = (TokenNode)node.Open.Accept(this), - ClosingString = (TokenNode)node.Close.Accept(this), - }; - using var _ = this.Scope(codeBlock); - codeBlock.Content = node.Expression.Accept(this); - return codeBlock; - } - - - public override FormattingNode VisitImportDeclaration(Api.Syntax.ImportDeclarationSyntax node) - { - var list = new ListNode() - { - ListKind = "ImportDeclaration", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.ImportKeyword.Accept(this)); - list.Items.Add(node.Path.Accept(this)); - list.Items.Add(node.Semicolon.Accept(this)); - return list; + return new NodeOrToken(converted); } +} - public override FormattingNode VisitMemberImportPath(Api.Syntax.MemberImportPathSyntax node) +internal class NodeOrToken +{ + public NodeOrToken(FormattingNode node) { - // unrecursive the tree into a list - var list = new SeparatedListNode() - { - Parent = this.current, - Separator = "." - }; - using var _ = this.Scope(list); - var toReverse = new List(); - var current = node; - while (true) - { - toReverse.Add(current.Member.Accept(this)); - if (current.Parent is not Api.Syntax.MemberImportPathSyntax parent) - { - var asRoot = (Api.Syntax.RootImportPathSyntax)current.Accessed!; - toReverse.Add(asRoot.Name.Accept(this)); - break; - } - current = parent; - } - list.Items.AddRange(toReverse.Reverse()); - return list; + this.Node = node; } - public override FormattingNode VisitLabelDeclaration(Api.Syntax.LabelDeclarationSyntax node) + public NodeOrToken(FormattingToken token) { - var list = new ListNode() - { - ListKind = "LabelDeclaration", - Parent = this.current, - }; - using var _ = this.Scope(list); - list.Items.Add(node.Name.Accept(this)); - list.Items.Add(node.Colon.Accept(this)); - return list; + this.Token = token; } + [MemberNotNullWhen(true, nameof(Node))] + [MemberNotNullWhen(false, nameof(Token))] + public bool IsNode => this.Node != null; + [MemberNotNullWhen(true, nameof(Token))] + [MemberNotNullWhen(false, nameof(Node))] + public bool IsToken => this.Token != null; - public override FormattingNode VisitSyntaxToken(Api.Syntax.SyntaxToken node) => new TokenNode() - { - Parent = this.current, - Text = node.Text - }; - - public override FormattingNode VisitNameExpression(Api.Syntax.NameExpressionSyntax node) => new TokenNode() - { - Parent = this.current, - Text = node.Name.Text - }; - public override FormattingNode VisitTextStringPart(Api.Syntax.TextStringPartSyntax node) => new TokenNode() - { - //TODO: remove indentation of previous code here. - Parent = this.current, - Text = node.Content.Text - }; - - public override FormattingNode VisitLiteralExpression(Api.Syntax.LiteralExpressionSyntax node) => new TokenNode() - { - Parent = this.current, - Text = node.Literal.Text - }; - - public override FormattingNode VisitStatementExpression(Api.Syntax.StatementExpressionSyntax node) => node.Statement.Accept(this); - public override FormattingNode VisitDeclarationStatement(Api.Syntax.DeclarationStatementSyntax node) => node.Declaration.Accept(this); - public override FormattingNode VisitNameType(Api.Syntax.NameTypeSyntax node) => node.Name.Accept(this); - + public FormattingNode? Node { get; } + public FormattingToken? Token { get; } - private DisposeAction Scope(FormattingNode node) - { - var prevCurrent = this.current; - this.current = node; - return new DisposeAction(() => - { - this.current = prevCurrent; - }); - } + public override string ToString() => this.IsNode ? this.Node.ToString() : this.Token.ToString(); } -internal abstract class FormattingNode +internal class FormattingNode { + public required string NodeKind { get; init; } public required FormattingNode? Parent { get; init; } - - public string EscapedString => this.ToString().Replace("\n", "\\n").Replace("\r", "\\r"); -} - -/// -/// Groups separated by a new line. -/// For example, -/// -internal class GroupsNode : FormattingNode -{ - public List Items { get; } = []; - - public class GroupInfo - { - public GroupInfo(string ItemGroupKind, FormattingNode Node) - { - this.ItemGroupKind = ItemGroupKind ?? throw new ArgumentNullException(nameof(ItemGroupKind)); - this.Node = Node ?? throw new ArgumentNullException(nameof(Node)); - } - - public string ItemGroupKind { get; } - public FormattingNode Node { get; } - } + public required IReadOnlyCollection Childrens { get; init; } public override string ToString() { var sb = new StringBuilder(); - foreach (var item in this.Items) + foreach (var child in this.Childrens) { - sb.AppendLine(item.Node.ToString()); + sb.Append(child.ToString()); } return sb.ToString(); } } -internal class TokenNode : FormattingNode +internal class FormattingToken { public required string Text { get; init; } public override string ToString() => this.Text; } - -internal class CodeBlockNode : FormattingNode -{ - public required string CodeBlockKind { get; init; } - public required TokenNode Opening { get; init; } - public FormattingNode Content { get; set; } - public required TokenNode ClosingString { get; init; } - - public override string ToString() => $"{this.Opening}\n{this.Content}\n{this.ClosingString}"; -} - -internal class ListNode : FormattingNode -{ - public required string ListKind { get; init; } - public List Items { get; } = []; - - public override string ToString() - { - var sb = new StringBuilder(); - for (var i = 0; i < this.Items.Count; i++) - { - sb.Append(this.Items[i]); - var isLast = i == this.Items.Count - 1; - if (!isLast) - { - sb.Append(' '); - } - } - return sb.ToString(); - } -} - -internal class SeparatedListNode : FormattingNode -{ - public required string Separator { get; init; } - public List Items { get; } = []; - - public override string ToString() - { - var sb = new StringBuilder(); - for (var i = 0; i < this.Items.Count; i++) - { - if (i > 0) - { - sb.Append(this.Separator); - sb.Append(' '); - } - sb.Append(this.Items[i]); - } - return sb.ToString(); - } -} - -internal class OperatorNode : FormattingNode -{ - public required string Operator { get; init; } - public FormattingNode Left { get; set; } - public FormattingNode Right { get; set; } - - public override string ToString() => $"{this.Left} {this.Operator} {this.Right}"; -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 9851e52cf..717dcfe1a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -8,7 +8,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class Formatter : Api.Syntax.SyntaxVisitor +internal sealed class Formatter { /// /// Formats the given syntax tree. @@ -21,8 +21,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) settings ??= FormatterSettings.Default; var formatter = new Formatter(settings, tree); - var dracoToFormattingTreeVisitor = new DracoToFormattingTreeVisitor(); - var res = tree.Root.Accept(dracoToFormattingTreeVisitor); + var res = DracoToFormattingTreeVisitor.Convert(null!, tree.Root); var resStr = res.ToString(); tree.Root.Accept(formatter); return null; @@ -39,4 +38,32 @@ private Formatter(FormatterSettings settings, SyntaxTree tree) } } +class Token +{ + +} + +enum TokenBehavior +{ + Inline, + OpenScope, + CloseScope, + NewLine, +} + +class ScopeOrLine +{ + +} + +class Line +{ + +} + +class Scope +{ + public List Childrens { get; } = new List(); +} + From 9326cd70dfa60bd4acefdb63cc124d6063efd712 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 22 Apr 2024 23:43:16 +0200 Subject: [PATCH 16/76] should i buldozer further ? --- .../Syntax/ParseTreeFormatterTests.cs | 25 +- .../Api/Syntax/ElseClauseSyntax.cs | 13 + .../Internal/Solver/Tasks/SolverTask.cs | 13 - .../Solver/Tasks/SolverTaskAwaiter.cs | 1 - .../Syntax/Formatting/CollapsibleBool.cs | 11 +- .../DracoToFormattingTreeVisitor.cs | 117 ----- .../Internal/Syntax/Formatting/Formatter.cs | 398 ++++++++++++++++-- .../Syntax/Formatting/MaterialisationKind.cs | 8 + .../Internal/Syntax/Formatting/ScopeInfo.cs | 1 + .../Syntax/Formatting/TokenDecoration.cs | 147 +++---- 10 files changed, 465 insertions(+), 269 deletions(-) create mode 100644 src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 9de80ed95..d96876be0 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -112,7 +112,7 @@ public void InlineMethodShouldBeFormattedCorrectly() var input = """ import System.Console; - func max(a:int32, b:int32, c:int32): int32 = if (a > b) a else b; + func max(a:int32, b:int32): int32 = if (a > b) a else b; func main() { WriteLine(max(12, 34)); @@ -122,7 +122,7 @@ func main() { var expected = """ import System.Console; - func max(a:int32, b:int32, c:int32): int32 = if (a > b) a else b; + func max(a:int32, b:int32): int32 = if (a > b) a else b; func main() { WriteLine(max(12, 34)); @@ -219,25 +219,4 @@ func main() { this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } - - [Fact] - public void ChainedComparisonOperatorsFormatsCorrectly() - { - var input = """" - func main() { - if (a > - b < c) - expr1 - } - """"; - var expected = """" - func main() { - if (a > b < c) expr1 - } - """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); - Console.WriteLine(actual); - this.logger.WriteLine(actual); - Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); - } } diff --git a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs new file mode 100644 index 000000000..97f015968 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Api.Syntax; +public partial class ElseClauseSyntax +{ + public bool IsElseIf => this.Expression is StatementExpressionSyntax statementExpression + && statementExpression.Statement is ExpressionStatementSyntax expressionStatement + && expressionStatement.Expression is IfExpressionSyntax; +} diff --git a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs index 842e4bae0..6a5638fa0 100644 --- a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs +++ b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTask.cs @@ -1,8 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using System.Runtime.CompilerServices; -using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Solver.Tasks; @@ -21,17 +19,6 @@ public static async SolverTask> WhenAll(IEnumerable WhenAny(IEnumerable> tasks) - { - if (tasks.Any(x => x.IsCompleted)) return FromResult(default); - var tcs = new SolverTaskCompletionSource(); - foreach (var task in tasks) - { - task.Awaiter.OnCompleted(() => tcs.SetResult(default)); - } - return tcs.Task; - } } [AsyncMethodBuilder(typeof(SolverTaskMethodBuilder<>))] diff --git a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs index 662286f68..c4505c9d3 100644 --- a/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs +++ b/src/Draco.Compiler/Internal/Solver/Tasks/SolverTaskAwaiter.cs @@ -21,7 +21,6 @@ internal void SetResult(T? result) { completion(); } - this.completions = null; // avoid completing the completion multiples times. } internal void SetException(Exception? exception) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs index f9c452515..bdafc956e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs @@ -20,9 +20,16 @@ private CollapsibleBool(SolverTask task) } public static CollapsibleBool Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleBool Create(bool value) => new(SolverTask.FromResult(value)); + public static CollapsibleBool Create(SolverTask solverTask) => new(solverTask); + public static CollapsibleBool True { get; } = new(SolverTask.FromResult(true)); + public static CollapsibleBool False { get; } = new(SolverTask.FromResult(false)); + + public void Collapse(bool collapse) + { + if (this.tcs is null) throw new InvalidOperationException(); + this.tcs?.SetResult(collapse); + } - public void Collapse(bool collapse) => this.tcs?.SetResult(collapse); public bool TryCollapse(bool collapse) { if (!this.Collapsed.IsCompleted) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs deleted file mode 100644 index 271543d45..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoToFormattingTreeVisitor.cs +++ /dev/null @@ -1,117 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class DracoToFormattingTreeVisitor -{ - // default converter - public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.SyntaxNode node) - { - if (node is Api.Syntax.SyntaxToken token) return new NodeOrToken(new FormattingToken() { Text = token.Text }); - - var list = new List(); - var converted = new FormattingNode() - { - NodeKind = node.GetType().Name, - Parent = parent, - Childrens = list - }; - foreach (var child in node.Children) - { - list.Add(Convert(converted, child)); - } - return new NodeOrToken(converted); - } - - - // flatify converter - - public static NodeOrToken FlatConvert(FormattingNode parent, Api.Syntax.SyntaxNode node) - { - var list = new List(); - var converted = new FormattingNode() - { - NodeKind = node.GetType().Name, - Parent = parent, - Childrens = list - }; - foreach (var child in node.Tokens) - { - list.Add(new NodeOrToken(new FormattingToken() { Text = child.Text })); - } - return new NodeOrToken(converted); - } - - public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.ImportDeclarationSyntax node) => FlatConvert(parent, node); - - public static NodeOrToken Convert(FormattingNode parent, Api.Syntax.FunctionDeclarationSyntax node) - { - var list = new List(); - var converted = new FormattingNode() - { - NodeKind = node.GetType().Name, - Parent = parent, - Childrens = list - }; - - foreach (var child in node.Body.Children) - { - list.Add(Convert(converted, child)); - } - return new NodeOrToken(converted); - } -} - -internal class NodeOrToken -{ - public NodeOrToken(FormattingNode node) - { - this.Node = node; - } - - public NodeOrToken(FormattingToken token) - { - this.Token = token; - } - [MemberNotNullWhen(true, nameof(Node))] - [MemberNotNullWhen(false, nameof(Token))] - public bool IsNode => this.Node != null; - - [MemberNotNullWhen(true, nameof(Token))] - [MemberNotNullWhen(false, nameof(Node))] - public bool IsToken => this.Token != null; - - public FormattingNode? Node { get; } - public FormattingToken? Token { get; } - - public override string ToString() => this.IsNode ? this.Node.ToString() : this.Token.ToString(); -} - -internal class FormattingNode -{ - public required string NodeKind { get; init; } - public required FormattingNode? Parent { get; init; } - public required IReadOnlyCollection Childrens { get; init; } - - public override string ToString() - { - var sb = new StringBuilder(); - foreach (var child in this.Childrens) - { - sb.Append(child.ToString()); - } - return sb.ToString(); - } -} - -internal class FormattingToken -{ - public required string Text { get; init; } - - public override string ToString() => this.Text; -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 717dcfe1a..6fc8dd927 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -8,8 +8,16 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class Formatter +internal sealed class Formatter : Api.Syntax.SyntaxVisitor { + private TokenDecoration[] tokenDecorations = []; + private int currentIdx; + private ScopeInfo scope; + private readonly SyntaxTree tree; // debugging helper, to remove + private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; + + private bool firstDeclaration = true; + /// /// Formats the given syntax tree. /// @@ -21,10 +29,31 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) settings ??= FormatterSettings.Default; var formatter = new Formatter(settings, tree); - var res = DracoToFormattingTreeVisitor.Convert(null!, tree.Root); - var resStr = res.ToString(); + tree.Root.Accept(formatter); - return null; + var builder = new StringBuilder(); + var i = 0; + foreach (var token in tree.Root.Tokens) + { + if (token.Kind == TokenKind.StringNewline) + { + i++; + continue; + } + var decoration = formatter.tokenDecorations[i]; + + if (decoration.DoesReturnLineCollapsible is not null) + { + builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? settings.Newline : ""); // will default to false if not collapsed, that what we want. + } + builder.Append(decoration.LeftPadding); + + builder.Append(decoration.TokenOverride ?? token.Text); + builder.Append(decoration.RightPadding); + i++; + } + builder.AppendLine(); + return builder.ToString(); } /// @@ -35,35 +64,354 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) private Formatter(FormatterSettings settings, SyntaxTree tree) { this.Settings = settings; + this.tree = tree; + this.scope = new(null, ""); + this.scope.IsMaterialized.Collapse(true); } -} -class Token -{ + public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) + { + this.tokenDecorations = new TokenDecoration[node.Tokens.Count()]; + base.VisitCompilationUnit(node); + } -} + public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) + { + this.CurrentToken.ScopeInfo = this.scope; + switch (node.Kind) + { + case TokenKind.Minus: + case TokenKind.Plus: + case TokenKind.Star: + case TokenKind.Slash: + case TokenKind.GreaterThan: + case TokenKind.LessThan: + case TokenKind.GreaterEqual: + case TokenKind.LessEqual: + case TokenKind.Equal: + case TokenKind.Assign: + case TokenKind.KeywordMod: + this.CurrentToken.RightPadding = " "; + this.CurrentToken.LeftPadding = " "; + break; + case TokenKind.KeywordVar: + case TokenKind.KeywordVal: + case TokenKind.KeywordFunc: + case TokenKind.KeywordReturn: + case TokenKind.KeywordGoto: + case TokenKind.KeywordWhile: + case TokenKind.KeywordIf: + case TokenKind.KeywordElse: + case TokenKind.KeywordImport: + this.CurrentToken.RightPadding = " "; + break; + } + base.VisitSyntaxToken(node); + this.CurrentToken.TokenSize = node.Green.Width; + this.currentIdx++; + } -enum TokenBehavior -{ - Inline, - OpenScope, - CloseScope, - NewLine, -} + public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) + { + if (node is Api.Syntax.SeparatedSyntaxList + || node is Api.Syntax.SeparatedSyntaxList) + { + this.CreateFoldableScope(this.Settings.Indentation, + MaterialisationKind.Normal, + SolverTask.FromResult(FoldPriority.AsSoonAsPossible), + () => base.VisitSeparatedSyntaxList(node) + ); + } + else + { + base.VisitSeparatedSyntaxList(node); + } + } -class ScopeOrLine -{ + public override void VisitParameter(Api.Syntax.ParameterSyntax node) + { + if (node.Index > 0) + { + this.CurrentToken.LeftPadding = " "; + } + this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + base.VisitParameter(node); + } -} + public override void VisitExpression(Api.Syntax.ExpressionSyntax node) + { + if (node.ArgumentIndex > 0) + { + this.CurrentToken.LeftPadding = " "; + } + base.VisitExpression(node); + } -class Line -{ + public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) + { + if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) + { -} + if (!this.firstDeclaration) + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + } + else + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.False; + } + this.firstDeclaration = false; + } + base.VisitDeclaration(node); + } -class Scope -{ - public List Childrens { get; } = new List(); -} + public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax node) + { + if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) + { + base.VisitStringExpression(node); + return; + } + node.OpenQuotes.Accept(this); + using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); + var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => + { + if (right.Kind == TriviaKind.Newline) return 0; + return value + right.Span.Length; + }); + var i = 0; + var newLineCount = 1; + var shouldIndent = true; + + for (; i < node.Parts.Count; i++) + { + var curr = node.Parts[i]; + + var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; + if (shouldIndent) + { + var tokenText = curr.Tokens.First().ValueText!; + if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); + this.tokenDecorations[this.currentIdx].TokenOverride = tokenText.Substring(blockCurrentIndentCount); + MultiIndent(newLineCount); + shouldIndent = false; + } + + if (isNewLine) + { + newLineCount++; + shouldIndent = true; + } + else + { + newLineCount = 0; + } + + var tokenCount = curr.Tokens.Count(); + for (var j = 0; j < tokenCount; j++) + { + ref var decoration = ref this.tokenDecorations[this.currentIdx + j]; + if (decoration.DoesReturnLineCollapsible is null) + { + decoration.DoesReturnLineCollapsible = CollapsibleBool.False; + } + } + curr.Accept(this); + } + MultiIndent(newLineCount); + this.tokenDecorations[this.currentIdx].DoesReturnLineCollapsible = CollapsibleBool.True; + node.CloseQuotes.Accept(this); + + + void MultiIndent(int newLineCount) + { + if (newLineCount > 0) + { + ref var currentToken = ref this.tokenDecorations[this.currentIdx]; + currentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + if (newLineCount > 1) + { + currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); + } + } + } + } + + public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) + { + IDisposable? closeScope = null; + var kind = node.Operator.Kind; + if (!(this.scope.Data?.Equals(kind) ?? false)) + { + closeScope = this.CreateFoldableScope("", MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); + this.scope.Data = kind; + } + node.Left.Accept(this); + + if (this.CurrentToken.DoesReturnLineCollapsible is null) + { + this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + } + node.Operator.Accept(this); + node.Right.Accept(this); + closeScope?.Dispose(); + } + + public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) + { + base.VisitMemberExpression(node); + this.scope.ItemsCount.Add(1); + } + + public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) + { + this.CurrentToken.LeftPadding = " "; + this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => base.VisitBlockFunctionBody(node)); + this.tokenDecorations[this.currentIdx - 1].DoesReturnLineCollapsible = CollapsibleBool.True; + } + + public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) + { + var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; + using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), + MaterialisationKind.Weak, + SolverTask.FromResult(FoldPriority.AsSoonAsPossible) + ); + base.VisitInlineFunctionBody(node); + } + + public override void VisitStatement(Api.Syntax.StatementSyntax node) + { + this.scope.ItemsCount.Add(1); + + if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) + { + // TODO: special case where we un-nest a level. + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + } + else if (node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax) + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + } + else + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.Create(this.scope.ItemsCount.WhenGreaterOrEqual(2)); + } + base.VisitStatement(node); + } + + public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) + { + this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); + } + + public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) + { + this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; + void Visit() + { + this.VisitExpression(node); + node.IfKeyword.Accept(this); + node.OpenParen.Accept(this); + node.Condition.Accept(this); + node.CloseParen.Accept(this); + node.Then.Accept(this); + } + + if (this.scope.ItemsCount.MinimumCurrentValue > 1) + { + Visit(); + } + else + { + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); + } + node.Else?.Accept(this); + } + + public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) + { + if (node.IsElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + } + else + { + this.CurrentToken.LeftPadding = " "; + this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + } + this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); + } + private static async SolverTask VariableIndentation(ScopeInfo scope) + { + return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; + } + + public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) + { + // this means we are in a if/while/else, and *can* create an indentation with a regular expression folding: + // if (blabla) an expression; + // it can fold: + // if (blabla) + // an expression; + // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. + this.scope.IsMaterialized.TryCollapse(false); + + this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => + { + this.VisitExpression(node); + node.OpenBrace.Accept(this); + node.Statements.Accept(this); + if (node.Value != null) + { + this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + node.Value.Accept(this); + } + node.CloseBrace.Accept(this); + }); + this.tokenDecorations[this.currentIdx - 1].DoesReturnLineCollapsible = CollapsibleBool.True; + } + public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) + { + this.CurrentToken.RightPadding = " "; + base.VisitTypeSpecifier(node); + } + + private IDisposable CreateFoldedScope(string indentation, MaterialisationKind materialisationKind) + { + this.scope = new ScopeInfo(this.scope, indentation); + this.scope.IsMaterialized.Collapse(true); + this.scope.MaterialisationKind = materialisationKind; + return new DisposeAction(() => + { + this.scope.Dispose(); + this.scope = this.scope.Parent!; + }); + } + + private void CreateFoldedScope(string indentation, MaterialisationKind materialisationKind, Action action) + { + using (this.CreateFoldedScope(indentation, materialisationKind)) action(); + } + + private IDisposable CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior) + { + this.scope = new ScopeInfo(this.scope, indentation, foldBehavior) + { + MaterialisationKind = materialisationKind + }; + return new DisposeAction(() => + { + this.scope.Dispose(); + this.scope = this.scope.Parent!; + }); + } + + private void CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior, Action action) + { + using (this.CreateFoldableScope(indentation, materialisationKind, foldBehavior)) action(); + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs new file mode 100644 index 000000000..27581049c --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs @@ -0,0 +1,8 @@ +namespace Draco.Compiler.Internal.Syntax.Formatting; + +enum MaterialisationKind +{ + Normal, + Weak, + Strong +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 5c82b621e..767d91908 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -37,6 +37,7 @@ public ScopeInfo(ScopeInfo? parent, string indentation, SolverTask /// /// public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); + public MaterialisationKind MaterialisationKind { get; set; } public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public string Indentation { get; } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index ee9c634ea..692d724ad 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,99 +1,70 @@ using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; -//internal struct TokenDecoration -//{ -// private string? rightPadding; -// private string? leftPadding; -// private IReadOnlyCollection>? indentation; -// private ScopeInfo scopeInfo; -// private string? tokenOverride; +internal struct TokenDecoration +{ + private string? rightPadding; + private string? leftPadding; + private ScopeInfo scopeInfo; + private string? tokenOverride; + private CollapsibleBool? doesReturnLineCollapsible; -// [DisallowNull] -// public string? TokenOverride -// { -// get => this.tokenOverride; -// set -// { -// if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); -// this.tokenOverride = value; -// } -// } -// public int TokenSize { get; set; } -// public readonly int CurrentTotalSize => this.TokenSize + (this.leftPadding?.Length ?? 0) + (this.rightPadding?.Length ?? 0) + this.CurrentIndentationSize; + [DisallowNull] + public string? TokenOverride + { + get => this.tokenOverride; + set + { + if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); + this.tokenOverride = value; + } + } + public int TokenSize { get; set; } -// private readonly int CurrentIndentationSize => this.Indentation?.Select(x => x.IsCompleted ? x.Result : null).Sum(x => x?.Length ?? 0) ?? 0; + [DisallowNull] + public CollapsibleBool? DoesReturnLineCollapsible + { + readonly get => this.doesReturnLineCollapsible; + set + { + if (this.doesReturnLineCollapsible != null) throw new InvalidOperationException("Collapsible already set"); + this.doesReturnLineCollapsible = value; + } + } -// [DisallowNull] -// public CollapsibleBool? DoesReturnLineCollapsible { get; private set; } + public ScopeInfo ScopeInfo + { + readonly get => this.scopeInfo; + set + { + if (this.scopeInfo != null) + { + throw new InvalidOperationException(); + } + this.scopeInfo = value; + } + } -// public ScopeInfo ScopeInfo -// { -// readonly get => this.scopeInfo; -// set -// { -// if (this.scopeInfo != null) -// { -// throw new InvalidOperationException(); -// } -// this.scopeInfo = value; -// } -// } -// public readonly IReadOnlyCollection>? Indentation => this.indentation; + public string? LeftPadding + { + readonly get => this.leftPadding; + set + { + if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); + this.leftPadding = value; + } + } + public string? RightPadding + { + readonly get => this.rightPadding; + set + { + if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); + this.rightPadding = value; + } + } -// public void SetIndentation(IReadOnlyCollection?> value) -// { -// if (this.indentation is not null) -// { -// //if (this.indentation.IsCompleted && value.IsCompleted && this.indentation.Result == value.Result) return; -// throw new InvalidOperationException("Indentation already set."); -// } - -// var doesReturnLine = this.DoesReturnLineCollapsible = CollapsibleBool.Create(); -// var cnt = value.Count; -// foreach (var item in value) -// { -// item.Awaiter.OnCompleted(() => -// { -// cnt--; -// if (item.Result != null) -// { -// doesReturnLine.TryCollapse(true); -// } else if(cnt == 0) -// { -// doesReturnLine.TryCollapse(false); -// } -// }); -// } -// this.indentation = value; -// this.indentation!.Awaiter.OnCompleted(() => -// { -// doesReturnLine.Collapse(value.Result != null); -// }); -// } - -// public string? LeftPadding -// { -// readonly get => this.leftPadding; -// set -// { -// if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); -// this.leftPadding = value; -// } -// } -// public string? RightPadding -// { -// readonly get => this.rightPadding; -// set -// { -// if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); -// this.rightPadding = value; -// } -// } - -//} +} From 6a9c8300b412c814550aeed6cb6c83c899b99954 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 26 Apr 2024 22:43:47 +0200 Subject: [PATCH 17/76] I drunk too much for not committing before coding. --- .../Syntax/ParseTreeFormatterTests.cs | 3 +- .../Internal/Syntax/Formatting/Formatter.cs | 296 ++++++++++++------ .../Syntax/Formatting/MaterialisationKind.cs | 8 - .../Internal/Syntax/Formatting/ScopeInfo.cs | 73 ++++- .../Syntax/Formatting/TokenDecoration.cs | 44 ++- 5 files changed, 286 insertions(+), 138 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index d96876be0..3b12f862e 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -122,7 +122,7 @@ func main() { var expected = """ import System.Console; - func max(a:int32, b:int32): int32 = if (a > b) a else b; + func max(a: int32, b: int32): int32 = if (a > b) a else b; func main() { WriteLine(max(12, 34)); @@ -213,6 +213,7 @@ func main() { else if (false) expr3 else expr4 } + """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); Console.WriteLine(actual); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 6fc8dd927..167b5a0f7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -13,7 +13,6 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private TokenDecoration[] tokenDecorations = []; private int currentIdx; private ScopeInfo scope; - private readonly SyntaxTree tree; // debugging helper, to remove private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; private bool firstDeclaration = true; @@ -28,30 +27,58 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) { settings ??= FormatterSettings.Default; - var formatter = new Formatter(settings, tree); + var formatter = new Formatter(settings); tree.Root.Accept(formatter); - var builder = new StringBuilder(); - var i = 0; - foreach (var token in tree.Root.Tokens) + + var decorations = formatter.tokenDecorations; + var tokens = tree.Root.Tokens.ToArray(); + var stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); + var currentLineStart = 0; + for (var x = 0; x < decorations.Length; x++) { - if (token.Kind == TokenKind.StringNewline) + var curr = decorations[x]; + var token = tokens[x]; + if (curr.DoesReturnLineCollapsible?.Collapsed.Result ?? false) + { + stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); + currentLineStart = x; + } + stateMachine.AddToken(curr, token, settings); + if (stateMachine.LineWidth > settings.LineWidth) { - i++; - continue; + if (curr.ScopeInfo.Fold()) + { + x = currentLineStart - 1; + stateMachine.Reset(); + continue; + } } - var decoration = formatter.tokenDecorations[i]; + } - if (decoration.DoesReturnLineCollapsible is not null) + var builder = new StringBuilder(); + stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); + for (var x = 0; x < decorations.Length; x++) + { + var token = tokens[x]; + if (token.Kind == TokenKind.StringNewline) continue; + + var decoration = decorations[x]; + + if (decoration.DoesReturnLineCollapsible?.Collapsed.Result ?? false) { - builder.Append(decoration.DoesReturnLineCollapsible.Collapsed.Result ? settings.Newline : ""); // will default to false if not collapsed, that what we want. + builder.Append(stateMachine); + builder.Append(settings.Newline); + stateMachine = new LineStateMachine(string.Concat(decoration.ScopeInfo.CurrentTotalIndent)); + } + if (decoration.Kind.HasFlag(FormattingTokenKind.ExtraNewline) && x > 0) + { + builder.Append(settings.Newline); } - builder.Append(decoration.LeftPadding); - builder.Append(decoration.TokenOverride ?? token.Text); - builder.Append(decoration.RightPadding); - i++; + stateMachine.AddToken(decoration, token, settings); } + builder.Append(stateMachine); builder.AppendLine(); return builder.ToString(); } @@ -61,11 +88,10 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) /// public FormatterSettings Settings { get; } - private Formatter(FormatterSettings settings, SyntaxTree tree) + private Formatter(FormatterSettings settings) { this.Settings = settings; - this.tree = tree; - this.scope = new(null, ""); + this.scope = new(null, FoldPriority.Never, ""); this.scope.IsMaterialized.Collapse(true); } @@ -77,37 +103,66 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { - this.CurrentToken.ScopeInfo = this.scope; - switch (node.Kind) + static FormattingTokenKind GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { - case TokenKind.Minus: - case TokenKind.Plus: - case TokenKind.Star: - case TokenKind.Slash: - case TokenKind.GreaterThan: - case TokenKind.LessThan: - case TokenKind.GreaterEqual: - case TokenKind.LessEqual: - case TokenKind.Equal: - case TokenKind.Assign: - case TokenKind.KeywordMod: - this.CurrentToken.RightPadding = " "; - this.CurrentToken.LeftPadding = " "; - break; - case TokenKind.KeywordVar: - case TokenKind.KeywordVal: - case TokenKind.KeywordFunc: - case TokenKind.KeywordReturn: - case TokenKind.KeywordGoto: - case TokenKind.KeywordWhile: - case TokenKind.KeywordIf: - case TokenKind.KeywordElse: - case TokenKind.KeywordImport: - this.CurrentToken.RightPadding = " "; - break; - } + TokenKind.KeywordAnd => FormattingTokenKind.PadLeft, + TokenKind.KeywordElse => FormattingTokenKind.PadLeft, + TokenKind.KeywordFalse => FormattingTokenKind.PadLeft, + TokenKind.KeywordFor => FormattingTokenKind.PadLeft, + TokenKind.KeywordGoto => FormattingTokenKind.PadLeft, + TokenKind.KeywordImport => FormattingTokenKind.PadLeft, + TokenKind.KeywordIn => FormattingTokenKind.PadLeft, + TokenKind.KeywordInternal => FormattingTokenKind.PadLeft, + TokenKind.KeywordMod => FormattingTokenKind.PadLeft, + TokenKind.KeywordModule => FormattingTokenKind.PadLeft, + TokenKind.KeywordOr => FormattingTokenKind.PadLeft, + TokenKind.KeywordRem => FormattingTokenKind.PadLeft, + TokenKind.KeywordReturn => FormattingTokenKind.PadLeft, + TokenKind.KeywordPublic => FormattingTokenKind.PadLeft, + TokenKind.KeywordTrue => FormattingTokenKind.PadLeft, + TokenKind.KeywordVar => FormattingTokenKind.PadLeft, + TokenKind.KeywordVal => FormattingTokenKind.PadLeft, + + + TokenKind.KeywordFunc => FormattingTokenKind.ExtraNewline, + + TokenKind.KeywordIf => FormattingTokenKind.PadAround, + TokenKind.KeywordWhile => FormattingTokenKind.PadAround, + + TokenKind.Semicolon => FormattingTokenKind.Semicolon, + TokenKind.CurlyOpen => FormattingTokenKind.PadLeft | FormattingTokenKind.TreatAsWhitespace, + TokenKind.ParenOpen => FormattingTokenKind.TreatAsWhitespace, + TokenKind.InterpolationStart => FormattingTokenKind.TreatAsWhitespace, + TokenKind.Dot => FormattingTokenKind.TreatAsWhitespace, + + TokenKind.Assign => FormattingTokenKind.PadLeft, + TokenKind.LineStringStart => FormattingTokenKind.PadLeft, + TokenKind.MultiLineStringStart => FormattingTokenKind.PadLeft, + TokenKind.Plus => FormattingTokenKind.PadLeft, + TokenKind.Minus => FormattingTokenKind.PadLeft, + TokenKind.Star => FormattingTokenKind.PadLeft, + TokenKind.Slash => FormattingTokenKind.PadLeft, + TokenKind.PlusAssign => FormattingTokenKind.PadLeft, + TokenKind.MinusAssign => FormattingTokenKind.PadLeft, + TokenKind.StarAssign => FormattingTokenKind.PadLeft, + TokenKind.SlashAssign => FormattingTokenKind.PadLeft, + TokenKind.GreaterEqual => FormattingTokenKind.PadLeft, + TokenKind.GreaterThan => FormattingTokenKind.PadLeft, + TokenKind.LessEqual => FormattingTokenKind.PadLeft, + TokenKind.LessThan => FormattingTokenKind.PadLeft, + + TokenKind.LiteralFloat => FormattingTokenKind.PadLeft, + TokenKind.LiteralInteger => FormattingTokenKind.PadLeft, + + TokenKind.Identifier => FormattingTokenKind.PadLeft, + + _ => FormattingTokenKind.NoFormatting + }; + + this.CurrentToken.ScopeInfo = this.scope; + this.CurrentToken.Kind = GetFormattingTokenKind(node); + this.CurrentToken.Token = node; base.VisitSyntaxToken(node); - this.CurrentToken.TokenSize = node.Green.Width; this.currentIdx++; } @@ -117,8 +172,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL || node is Api.Syntax.SeparatedSyntaxList) { this.CreateFoldableScope(this.Settings.Indentation, - MaterialisationKind.Normal, - SolverTask.FromResult(FoldPriority.AsSoonAsPossible), + FoldPriority.AsSoonAsPossible, () => base.VisitSeparatedSyntaxList(node) ); } @@ -130,28 +184,14 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL public override void VisitParameter(Api.Syntax.ParameterSyntax node) { - if (node.Index > 0) - { - this.CurrentToken.LeftPadding = " "; - } this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; base.VisitParameter(node); } - public override void VisitExpression(Api.Syntax.ExpressionSyntax node) - { - if (node.ArgumentIndex > 0) - { - this.CurrentToken.LeftPadding = " "; - } - base.VisitExpression(node); - } - public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) { - if (!this.firstDeclaration) { this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; @@ -173,7 +213,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod return; } node.OpenQuotes.Accept(this); - using var _ = this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Normal); + using var _ = this.CreateFoldedScope(this.Settings.Indentation); var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => { if (right.Kind == TriviaKind.Newline) return 0; @@ -231,7 +271,8 @@ void MultiIndent(int newLineCount) currentToken.DoesReturnLineCollapsible = CollapsibleBool.True; if (newLineCount > 1) { - currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); + // TODO + //currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); } } } @@ -243,7 +284,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod var kind = node.Operator.Kind; if (!(this.scope.Data?.Equals(kind) ?? false)) { - closeScope = this.CreateFoldableScope("", MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsLateAsPossible)); + closeScope = this.CreateFoldableScope("", FoldPriority.AsLateAsPossible); this.scope.Data = kind; } node.Left.Accept(this); @@ -265,21 +306,43 @@ public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax nod public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { - this.CurrentToken.LeftPadding = " "; - this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => base.VisitBlockFunctionBody(node)); - this.tokenDecorations[this.currentIdx - 1].DoesReturnLineCollapsible = CollapsibleBool.True; + node.OpenBrace.Accept(this); + this.CreateFoldedScope(this.Settings.Indentation, () => node.Statements.Accept(this)); + this.tokenDecorations[this.currentIdx].DoesReturnLineCollapsible = CollapsibleBool.True; + node.CloseBrace.Accept(this); } public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) { var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), - MaterialisationKind.Weak, - SolverTask.FromResult(FoldPriority.AsSoonAsPossible) + FoldPriority.AsSoonAsPossible ); base.VisitInlineFunctionBody(node); } + public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) + { + var stateMachine = new LineStateMachine(string.Concat(this.scope.CurrentTotalIndent)); + if (node.VisibilityModifier != null) + { + stateMachine.AddToken(this.CurrentToken, node.FunctionKeyword, this.Settings); + } + node.VisibilityModifier?.Accept(this); + stateMachine.AddToken(this.CurrentToken, node.FunctionKeyword, this.Settings); + node.FunctionKeyword.Accept(this); + stateMachine.AddToken(this.CurrentToken, node.Name, this.Settings); + node.Name.Accept(this); + + node.Generics?.Accept(this); + node.OpenParen.Accept(this); + + + this.CreateFoldableScope() + TODO. + base.VisitFunctionDeclaration(node); + } + public override void VisitStatement(Api.Syntax.StatementSyntax node) { this.scope.ItemsCount.Add(1); @@ -302,13 +365,11 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { - this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitWhileExpression(node)); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitWhileExpression(node)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) { - this.tokenDecorations[this.currentIdx + 2 + node.Condition.Tokens.Count()].RightPadding = " "; void Visit() { this.VisitExpression(node); @@ -325,7 +386,7 @@ void Visit() } else { - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), Visit); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, Visit); } node.Else?.Accept(this); } @@ -338,15 +399,9 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) } else { - this.CurrentToken.LeftPadding = " "; this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; } - this.CreateFoldableScope(this.Settings.Indentation, MaterialisationKind.Normal, SolverTask.FromResult(FoldPriority.AsSoonAsPossible), () => base.VisitElseClause(node)); - } - - private static async SolverTask VariableIndentation(ScopeInfo scope) - { - return await scope.IsMaterialized.Collapsed ? scope.Indentation[..^1] : null; + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitElseClause(node)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -359,7 +414,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. this.scope.IsMaterialized.TryCollapse(false); - this.CreateFoldedScope(this.Settings.Indentation, MaterialisationKind.Strong, () => + this.CreateFoldedScope(this.Settings.Indentation, () => { this.VisitExpression(node); node.OpenBrace.Accept(this); @@ -376,15 +431,13 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) { - this.CurrentToken.RightPadding = " "; base.VisitTypeSpecifier(node); } - private IDisposable CreateFoldedScope(string indentation, MaterialisationKind materialisationKind) + private IDisposable CreateFoldedScope(string indentation) { - this.scope = new ScopeInfo(this.scope, indentation); + this.scope = new ScopeInfo(this.scope, FoldPriority.Never, indentation); this.scope.IsMaterialized.Collapse(true); - this.scope.MaterialisationKind = materialisationKind; return new DisposeAction(() => { this.scope.Dispose(); @@ -392,17 +445,14 @@ private IDisposable CreateFoldedScope(string indentation, MaterialisationKind ma }); } - private void CreateFoldedScope(string indentation, MaterialisationKind materialisationKind, Action action) + private void CreateFoldedScope(string indentation, Action action) { - using (this.CreateFoldedScope(indentation, materialisationKind)) action(); + using (this.CreateFoldedScope(indentation)) action(); } - private IDisposable CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior) + private IDisposable CreateFoldableScope(string indentation, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, indentation, foldBehavior) - { - MaterialisationKind = materialisationKind - }; + this.scope = new ScopeInfo(this.scope, foldBehavior, indentation); return new DisposeAction(() => { this.scope.Dispose(); @@ -410,8 +460,62 @@ private IDisposable CreateFoldableScope(string indentation, MaterialisationKind }); } - private void CreateFoldableScope(string indentation, MaterialisationKind materialisationKind, SolverTask foldBehavior, Action action) + private void CreateFoldableScope(string indentation, FoldPriority foldBehavior, Action action) { - using (this.CreateFoldableScope(indentation, materialisationKind, foldBehavior)) action(); + using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } } + +[Flags] +internal enum FormattingTokenKind +{ + NoFormatting = 0, + PadLeft = 1, + PadRight = 1 << 1, + PadAround = PadLeft | PadRight, + TreatAsWhitespace = 1 << 2, + Semicolon = 1 << 3, + ExtraNewline = 1 << 4 +} + +internal class LineStateMachine +{ + private readonly StringBuilder sb = new(); + private readonly string indentation; + private bool previousIsWhitespace = true; + public LineStateMachine(string indentation) + { + this.sb.Append(indentation); + this.LineWidth = indentation.Length; + this.indentation = indentation; + } + + public int LineWidth { get; set; } + public void AddToken(TokenDecoration decoration, Api.Syntax.SyntaxToken token, FormatterSettings settings) + { + if (decoration.Kind.HasFlag(FormattingTokenKind.PadLeft) && !this.previousIsWhitespace) + { + this.sb.Append(' '); + this.LineWidth++; + } + var text = decoration.TokenOverride ?? token.Text; + this.sb.Append(text); + if (decoration.Kind.HasFlag(FormattingTokenKind.PadRight)) + { + this.sb.Append(' '); + this.LineWidth++; + } + this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.TreatAsWhitespace) || decoration.Kind.HasFlag(FormattingTokenKind.PadRight); + this.LineWidth += text.Length; + } + + public void Reset() + { + this.sb.Clear(); + this.sb.Append(this.indentation); + this.LineWidth = this.indentation.Length; + } + + + public override string ToString() => this.sb.ToString(); +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs deleted file mode 100644 index 27581049c..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/MaterialisationKind.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Draco.Compiler.Internal.Syntax.Formatting; - -enum MaterialisationKind -{ - Normal, - Weak, - Strong -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 767d91908..5eee36b3f 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata; using Draco.Compiler.Internal.Solver.Tasks; using Draco.Compiler.Internal.Utilities; @@ -12,13 +14,29 @@ internal class ScopeInfo : IDisposable public ScopeInfo? Parent { get; } public List Childs { get; } = []; private readonly SolverTaskCompletionSource _stableTcs = new(); + private readonly string? indentation; + private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; - public ScopeInfo(ScopeInfo? parent, string indentation, SolverTask? foldPriority = null) + [MemberNotNullWhen(true, nameof(levelingToken))] + [MemberNotNullWhen(false, nameof(indentation))] + private bool DrivenByLevelingToken => this.levelingToken.HasValue; + + public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority) { this.Parent = parent; - parent?.Childs.Add(this); - this.Indentation = indentation; this.FoldPriority = foldPriority; + parent?.Childs.Add(this); + } + + public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority, string indentation) : this(parent, foldPriority) + { + this.indentation = indentation; + } + + public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) + : this(parent, foldPriority) + { + this.levelingToken = levelingToken; } public SolverTask WhenStable => this._stableTcs.Task; @@ -37,11 +55,48 @@ public ScopeInfo(ScopeInfo? parent, string indentation, SolverTask /// /// public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); - public MaterialisationKind MaterialisationKind { get; set; } public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); - public string Indentation { get; } + public TokenDecoration? TokenDecoration { get; set; } + + + + public IEnumerable CurrentTotalIndent + { + get + { + if (!this.DrivenByLevelingToken) + { + if (this.Parent is null) return [this.indentation]; + return this.Parent.CurrentTotalIndent.Append(this.indentation); + } + + var (tokens, indexOfLevelingToken) = this.levelingToken.Value; + + int GetStartLineTokenIndex() + { + for (var i = indexOfLevelingToken; i >= 0; i--) + { + if (tokens[i].DoesReturnLineCollapsible?.Collapsed.Result ?? false) + { + return i; + } + } + return 0; + } + + var startLine = GetStartLineTokenIndex(); + var startToken = this.levelingToken.Value.tokens[startLine]; + var stateMachine = new LineStateMachine(string.Concat(startToken.ScopeInfo.CurrentTotalIndent)); + for (var i = startLine; i < this.levelingToken.Value.tokens.Count; i++) + { + var curr = this.levelingToken.Value.tokens[i]; + stateMachine.AddToken(curr, ) + } + + } + } - public SolverTask? FoldPriority { get; } + public FoldPriority FoldPriority { get; } public IEnumerable ThisAndAllChilds => this.AllChilds.Prepend(this); public IEnumerable AllChilds @@ -88,8 +143,7 @@ public bool Fold() foreach (var item in this.ThisAndParents.Reverse()) { if (item.IsMaterialized.Collapsed.IsCompleted) continue; - Debug.Assert(item.FoldPriority!.IsCompleted); - if (item.FoldPriority.Result == Formatting.FoldPriority.AsSoonAsPossible) + if (item.FoldPriority == FoldPriority.AsSoonAsPossible) { item.IsMaterialized.Collapse(true); return true; @@ -99,8 +153,7 @@ public bool Fold() foreach (var item in this.ThisAndParents) { if (item.IsMaterialized.Collapsed.IsCompleted) continue; - Debug.Assert(item.FoldPriority!.IsCompleted); - if (item.FoldPriority.Result == Formatting.FoldPriority.AsLateAsPossible) + if (item.FoldPriority == FoldPriority.AsLateAsPossible) { item.IsMaterialized.Collapse(true); return true; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 692d724ad..db5484012 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -6,11 +6,30 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal struct TokenDecoration { - private string? rightPadding; - private string? leftPadding; private ScopeInfo scopeInfo; private string? tokenOverride; private CollapsibleBool? doesReturnLineCollapsible; + private FormattingTokenKind kind; + private Api.Syntax.SyntaxToken token; + private bool _kindIsSet; + public FormattingTokenKind Kind + { + readonly get => this.kind; + set + { + if (this._kindIsSet) throw new InvalidOperationException("Kind already set"); + this.kind = value; + this._kindIsSet = true; + } + } + public Api.Syntax.SyntaxToken Token + { + readonly get => this.token; set + { + if (this.token != null) throw new InvalidOperationException("Token already set"); + this.token = value; + } + } [DisallowNull] public string? TokenOverride @@ -22,7 +41,6 @@ public string? TokenOverride this.tokenOverride = value; } } - public int TokenSize { get; set; } [DisallowNull] public CollapsibleBool? DoesReturnLineCollapsible @@ -47,24 +65,4 @@ public ScopeInfo ScopeInfo this.scopeInfo = value; } } - - public string? LeftPadding - { - readonly get => this.leftPadding; - set - { - if (this.leftPadding is not null) throw new InvalidOperationException("Left padding already set."); - this.leftPadding = value; - } - } - public string? RightPadding - { - readonly get => this.rightPadding; - set - { - if (this.rightPadding is not null) throw new InvalidOperationException("Right padding already set."); - this.rightPadding = value; - } - } - } From cca9727d3c72e768f86002bf3bf9a972eca218a5 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 27 Apr 2024 00:47:52 +0200 Subject: [PATCH 18/76] wip --- .../Internal/Syntax/Formatting/Formatter.cs | 81 +++---------------- .../Syntax/Formatting/FormattingTokenKind.cs | 15 ++++ .../Syntax/Formatting/LineStateMachine.cs | 45 +++++++++++ .../Internal/Syntax/Formatting/ScopeInfo.cs | 3 +- 4 files changed, 73 insertions(+), 71 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 167b5a0f7..2a1934702 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -44,7 +44,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); currentLineStart = x; } - stateMachine.AddToken(curr, token, settings); + stateMachine.AddToken(curr, token); if (stateMachine.LineWidth > settings.LineWidth) { if (curr.ScopeInfo.Fold()) @@ -76,7 +76,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) builder.Append(settings.Newline); } - stateMachine.AddToken(decoration, token, settings); + stateMachine.AddToken(decoration, token); } builder.Append(stateMachine); builder.AppendLine(); @@ -232,7 +232,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod { var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.tokenDecorations[this.currentIdx].TokenOverride = tokenText.Substring(blockCurrentIndentCount); + this.tokenDecorations[this.currentIdx].TokenOverride = tokenText[blockCurrentIndentCount..]; MultiIndent(newLineCount); shouldIndent = false; } @@ -323,24 +323,19 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { - var stateMachine = new LineStateMachine(string.Concat(this.scope.CurrentTotalIndent)); - if (node.VisibilityModifier != null) - { - stateMachine.AddToken(this.CurrentToken, node.FunctionKeyword, this.Settings); - } node.VisibilityModifier?.Accept(this); - stateMachine.AddToken(this.CurrentToken, node.FunctionKeyword, this.Settings); node.FunctionKeyword.Accept(this); - stateMachine.AddToken(this.CurrentToken, node.Name, this.Settings); node.Name.Accept(this); - - node.Generics?.Accept(this); + if (node.Generics is not null) + { + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); + } node.OpenParen.Accept(this); - - this.CreateFoldableScope() - TODO. - base.VisitFunctionDeclaration(node); + node.ParameterList.Accept(this); + node.CloseParen.Accept(this); + node.ReturnType?.Accept(this); + node.Body.Accept(this); } public override void VisitStatement(Api.Syntax.StatementSyntax node) @@ -465,57 +460,3 @@ private void CreateFoldableScope(string indentation, FoldPriority foldBehavior, using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } } - -[Flags] -internal enum FormattingTokenKind -{ - NoFormatting = 0, - PadLeft = 1, - PadRight = 1 << 1, - PadAround = PadLeft | PadRight, - TreatAsWhitespace = 1 << 2, - Semicolon = 1 << 3, - ExtraNewline = 1 << 4 -} - -internal class LineStateMachine -{ - private readonly StringBuilder sb = new(); - private readonly string indentation; - private bool previousIsWhitespace = true; - public LineStateMachine(string indentation) - { - this.sb.Append(indentation); - this.LineWidth = indentation.Length; - this.indentation = indentation; - } - - public int LineWidth { get; set; } - public void AddToken(TokenDecoration decoration, Api.Syntax.SyntaxToken token, FormatterSettings settings) - { - if (decoration.Kind.HasFlag(FormattingTokenKind.PadLeft) && !this.previousIsWhitespace) - { - this.sb.Append(' '); - this.LineWidth++; - } - var text = decoration.TokenOverride ?? token.Text; - this.sb.Append(text); - if (decoration.Kind.HasFlag(FormattingTokenKind.PadRight)) - { - this.sb.Append(' '); - this.LineWidth++; - } - this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.TreatAsWhitespace) || decoration.Kind.HasFlag(FormattingTokenKind.PadRight); - this.LineWidth += text.Length; - } - - public void Reset() - { - this.sb.Clear(); - this.sb.Append(this.indentation); - this.LineWidth = this.indentation.Length; - } - - - public override string ToString() => this.sb.ToString(); -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs new file mode 100644 index 000000000..d2dddfc24 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs @@ -0,0 +1,15 @@ +using System; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +[Flags] +internal enum FormattingTokenKind +{ + NoFormatting = 0, + PadLeft = 1, + PadRight = 1 << 1, + PadAround = PadLeft | PadRight, + TreatAsWhitespace = 1 << 2, + Semicolon = 1 << 3, + ExtraNewline = 1 << 4 +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs new file mode 100644 index 000000000..cf78061e3 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -0,0 +1,45 @@ +using System.Text; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +internal class LineStateMachine +{ + private readonly StringBuilder sb = new(); + private readonly string indentation; + private bool previousIsWhitespace = true; + public LineStateMachine(string indentation) + { + this.sb.Append(indentation); + this.LineWidth = indentation.Length; + this.indentation = indentation; + } + + public int LineWidth { get; set; } + public void AddToken(TokenDecoration decoration, Api.Syntax.SyntaxToken token) + { + if (decoration.Kind.HasFlag(FormattingTokenKind.PadLeft) && !this.previousIsWhitespace) + { + this.sb.Append(' '); + this.LineWidth++; + } + var text = decoration.TokenOverride ?? token.Text; + this.sb.Append(text); + if (decoration.Kind.HasFlag(FormattingTokenKind.PadRight)) + { + this.sb.Append(' '); + this.LineWidth++; + } + this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.TreatAsWhitespace) || decoration.Kind.HasFlag(FormattingTokenKind.PadRight); + this.LineWidth += text.Length; + } + + public void Reset() + { + this.sb.Clear(); + this.sb.Append(this.indentation); + this.LineWidth = this.indentation.Length; + } + + + public override string ToString() => this.sb.ToString(); +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 5eee36b3f..92f1f2fce 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -90,8 +90,9 @@ int GetStartLineTokenIndex() for (var i = startLine; i < this.levelingToken.Value.tokens.Count; i++) { var curr = this.levelingToken.Value.tokens[i]; - stateMachine.AddToken(curr, ) + stateMachine.AddToken(curr, curr.Token); } + return [new string(' ', stateMachine.LineWidth)]; } } From 2495a4197806de056c195c7b58b57dbea61fb7dd Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 27 Apr 2024 23:51:35 +0200 Subject: [PATCH 19/76] All tests pass. Now need more tests. --- .../Syntax/ParseTreeFormatterTests.cs | 37 ++- .../Internal/Syntax/Formatting/Box.cs | 28 ++ .../Syntax/Formatting/CollapsibleInt.cs | 46 ++-- .../Internal/Syntax/Formatting/Formatter.cs | 253 +++++++++++------- .../Syntax/Formatting/FormattingTokenKind.cs | 11 +- .../Syntax/Formatting/LineStateMachine.cs | 25 +- .../Internal/Syntax/Formatting/ScopeInfo.cs | 45 ++-- .../Syntax/Formatting/TokenDecoration.cs | 28 +- 8 files changed, 291 insertions(+), 182 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 3b12f862e..aef41f87b 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -165,23 +165,15 @@ func aLongMethodName() = 1 } [Fact] - public void ExpressionInMultiLineStringFolds() + public void ExpressionInMultiLineStringDoesNotChange() { var input = """" - func main() - { + func main() { val someMultiLineString = """ the result:\{1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10} """; } - """"; - var expected = """" - func main() { - val someMultiLineString = """ - the result:\{1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10} - """; - } """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() @@ -190,7 +182,7 @@ func main() { }); Console.WriteLine(actual); this.logger.WriteLine(actual); - Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } [Fact] @@ -220,4 +212,27 @@ func main() { this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void TooLongArgsFoldsInsteadOfExpr() + { + var input = """ + func main(lots: Of, arguments: That, will: Be, fold: But) = nnot + this; + """; + var expected = """ + func main( + lots: Of, + arguments: That, + will: Be, + fold: But) = nnot + this; + + """; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 60 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs new file mode 100644 index 000000000..5b70687e1 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Draco.Compiler.Internal.Syntax.Formatting; +internal class Box(T value) +{ + protected T value = value; + public T Value => this.value; + + public static implicit operator Box(T value) => new(value); +} + +internal class MutableBox(T value, bool canSetValue) : Box(value) +{ + public bool CanSetValue { get; } = canSetValue; + public new T Value + { + get => base.Value; + set + { + if (!this.CanSetValue) throw new InvalidOperationException("Cannot set value"); + this.value = value; + } + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs index 415d5c9e0..5d2644a72 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs @@ -25,34 +25,34 @@ private CollapsibleInt(SolverTask task) // order by desc - private List<(int Value, SolverTaskCompletionSource Tcs)>? _whenTcs; + private List<(int Value, MutableBox Box)>? _boxes; public void Add(int toAdd) { this.MinimumCurrentValue += toAdd; - if (this._whenTcs is null) return; - var i = this._whenTcs.Count - 1; + if (this._boxes is null) return; + var i = this._boxes.Count - 1; if (i < 0) return; while (true) { - var (value, tcs) = this._whenTcs![i]; + var (value, box) = this._boxes![i]; if (this.MinimumCurrentValue < value) break; - tcs.SetResult(true); + box.Value = true; if (i == 0) break; i--; } - this._whenTcs.RemoveRange(i, this._whenTcs.Count - i); + this._boxes.RemoveRange(i, this._boxes.Count - i); } public void Collapse() { - if (this._whenTcs is not null) + if (this._boxes is not null) { - foreach (var (_, Tcs) in this._whenTcs ?? Enumerable.Empty<(int Value, SolverTaskCompletionSource Tcs)>()) + foreach (var (_, box) in this._boxes ?? Enumerable.Empty<(int Value, MutableBox Tcs)>()) { - Tcs.SetResult(false); + box.Value = false; } - this._whenTcs = null; + this._boxes = null; } this.tcs?.SetResult(this.MinimumCurrentValue); @@ -60,21 +60,27 @@ public void Collapse() public SolverTask Collapsed => this.task; - public SolverTask WhenGreaterOrEqual(int number) + public Box WhenGreaterOrEqual(int number) { - if (this.MinimumCurrentValue >= number) return SolverTask.FromResult(true); - this._whenTcs ??= []; - var index = this._whenTcs.BinarySearch((number, null!), Comparer.Instance); - if (index > 0) return this._whenTcs[index].Tcs.Task; - var tcs = new SolverTaskCompletionSource(); - this._whenTcs.Insert(~index, (number, tcs)); - return tcs.Task; + if (this.MinimumCurrentValue >= number) return true; + this._boxes ??= []; + var index = this._boxes.BinarySearch((number, null!), Comparer.Instance); + if (index > 0) + { + return this._boxes[index].Box; + } + else + { + var box = new MutableBox(null, true); + this._boxes.Insert(~index, (number, box)); + return box; + } } - private class Comparer : IComparer<(int, SolverTaskCompletionSource)> + private class Comparer : IComparer<(int, MutableBox)> { public static Comparer Instance { get; } = new Comparer(); // reverse comparison. - public int Compare((int, SolverTaskCompletionSource) x, (int, SolverTaskCompletionSource) y) => y.Item1.CompareTo(x.Item1); + public int Compare((int, MutableBox) x, (int, MutableBox) y) => y.Item1.CompareTo(x.Item1); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 2a1934702..e18ae3e99 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -32,27 +33,63 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) tree.Root.Accept(formatter); var decorations = formatter.tokenDecorations; - var tokens = tree.Root.Tokens.ToArray(); var stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; + List foldedScopes = new(); for (var x = 0; x < decorations.Length; x++) { var curr = decorations[x]; - var token = tokens[x]; - if (curr.DoesReturnLineCollapsible?.Collapsed.Result ?? false) + if (curr.DoesReturnLine?.Value ?? false) { stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); currentLineStart = x; + foldedScopes.Clear(); } - stateMachine.AddToken(curr, token); + stateMachine.AddToken(curr, settings); if (stateMachine.LineWidth > settings.LineWidth) { - if (curr.ScopeInfo.Fold()) + var folded = curr.ScopeInfo.Fold(); + if (folded != null) { x = currentLineStart - 1; + foldedScopes.Add(folded); stateMachine.Reset(); continue; } + else if (curr.ScopeInfo.Parent != null) + { + // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. + // there can be other strategy taken in the future, parametrable through settings. + + // first rewind and fold any "as soon as possible" scopes. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = decorations[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) goto folded; + } + // there was no high priority scope to fold, we try to get the low priority then. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = decorations[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) goto folded; + } + + // we couldn't fold any scope, we just give up. + continue; + + folded: + foreach (var scope in foldedScopes) + { + scope.IsMaterialized.Value = null; + } + foldedScopes.Clear(); + x = currentLineStart - 1; + } } } @@ -60,12 +97,11 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); for (var x = 0; x < decorations.Length; x++) { - var token = tokens[x]; - if (token.Kind == TokenKind.StringNewline) continue; var decoration = decorations[x]; + if (decoration.Token.Kind == TokenKind.StringNewline) continue; - if (decoration.DoesReturnLineCollapsible?.Collapsed.Result ?? false) + if (decoration.DoesReturnLine?.Value ?? false) { builder.Append(stateMachine); builder.Append(settings.Newline); @@ -76,7 +112,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) builder.Append(settings.Newline); } - stateMachine.AddToken(decoration, token); + stateMachine.AddToken(decoration, settings); } builder.Append(stateMachine); builder.AppendLine(); @@ -91,8 +127,8 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) private Formatter(FormatterSettings settings) { this.Settings = settings; - this.scope = new(null, FoldPriority.Never, ""); - this.scope.IsMaterialized.Collapse(true); + this.scope = new(null, settings, FoldPriority.Never, ""); + this.scope.IsMaterialized.Value = true; } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) @@ -103,39 +139,40 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { - static FormattingTokenKind GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch + FormattingTokenKind GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { - TokenKind.KeywordAnd => FormattingTokenKind.PadLeft, - TokenKind.KeywordElse => FormattingTokenKind.PadLeft, - TokenKind.KeywordFalse => FormattingTokenKind.PadLeft, - TokenKind.KeywordFor => FormattingTokenKind.PadLeft, - TokenKind.KeywordGoto => FormattingTokenKind.PadLeft, - TokenKind.KeywordImport => FormattingTokenKind.PadLeft, - TokenKind.KeywordIn => FormattingTokenKind.PadLeft, - TokenKind.KeywordInternal => FormattingTokenKind.PadLeft, - TokenKind.KeywordMod => FormattingTokenKind.PadLeft, - TokenKind.KeywordModule => FormattingTokenKind.PadLeft, - TokenKind.KeywordOr => FormattingTokenKind.PadLeft, - TokenKind.KeywordRem => FormattingTokenKind.PadLeft, - TokenKind.KeywordReturn => FormattingTokenKind.PadLeft, - TokenKind.KeywordPublic => FormattingTokenKind.PadLeft, - TokenKind.KeywordTrue => FormattingTokenKind.PadLeft, - TokenKind.KeywordVar => FormattingTokenKind.PadLeft, - TokenKind.KeywordVal => FormattingTokenKind.PadLeft, - - - TokenKind.KeywordFunc => FormattingTokenKind.ExtraNewline, - - TokenKind.KeywordIf => FormattingTokenKind.PadAround, - TokenKind.KeywordWhile => FormattingTokenKind.PadAround, - - TokenKind.Semicolon => FormattingTokenKind.Semicolon, - TokenKind.CurlyOpen => FormattingTokenKind.PadLeft | FormattingTokenKind.TreatAsWhitespace, - TokenKind.ParenOpen => FormattingTokenKind.TreatAsWhitespace, - TokenKind.InterpolationStart => FormattingTokenKind.TreatAsWhitespace, - TokenKind.Dot => FormattingTokenKind.TreatAsWhitespace, - - TokenKind.Assign => FormattingTokenKind.PadLeft, + TokenKind.KeywordAnd => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordElse => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordFor => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordGoto => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordImport => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordIn => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordInternal => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordModule => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordOr => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordReturn => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordPublic => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordVar => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordVal => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordIf => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + TokenKind.KeywordWhile => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, + + TokenKind.KeywordTrue => FormattingTokenKind.PadAround, + TokenKind.KeywordFalse => FormattingTokenKind.PadAround, + TokenKind.KeywordMod => FormattingTokenKind.PadAround, + TokenKind.KeywordRem => FormattingTokenKind.PadAround, + + TokenKind.KeywordFunc => this.currentIdx == 0 ? FormattingTokenKind.PadAround : FormattingTokenKind.ExtraNewline, + + + TokenKind.Semicolon => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.CurlyOpen => FormattingTokenKind.PadLeft | FormattingTokenKind.BehaveAsWhiteSpaceForNextToken, + TokenKind.ParenOpen => FormattingTokenKind.Whitespace, + TokenKind.ParenClose => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.InterpolationStart => FormattingTokenKind.Whitespace, + TokenKind.Dot => FormattingTokenKind.Whitespace, + + TokenKind.Assign => FormattingTokenKind.PadAround, TokenKind.LineStringStart => FormattingTokenKind.PadLeft, TokenKind.MultiLineStringStart => FormattingTokenKind.PadLeft, TokenKind.Plus => FormattingTokenKind.PadLeft, @@ -150,7 +187,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) TokenKind.GreaterThan => FormattingTokenKind.PadLeft, TokenKind.LessEqual => FormattingTokenKind.PadLeft, TokenKind.LessThan => FormattingTokenKind.PadLeft, - + TokenKind.Equal => FormattingTokenKind.PadLeft, TokenKind.LiteralFloat => FormattingTokenKind.PadLeft, TokenKind.LiteralInteger => FormattingTokenKind.PadLeft, @@ -160,7 +197,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) }; this.CurrentToken.ScopeInfo = this.scope; - this.CurrentToken.Kind = GetFormattingTokenKind(node); + this.CurrentToken.Kind |= GetFormattingTokenKind(node); this.CurrentToken.Token = node; base.VisitSyntaxToken(node); this.currentIdx++; @@ -184,7 +221,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL public override void VisitParameter(Api.Syntax.ParameterSyntax node) { - this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; base.VisitParameter(node); } @@ -192,14 +229,7 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) { - if (!this.firstDeclaration) - { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; - } - else - { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.False; - } + this.CurrentToken.DoesReturnLine = !this.firstDeclaration; this.firstDeclaration = false; } base.VisitDeclaration(node); @@ -246,20 +276,28 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod { newLineCount = 0; } - + var startIdx = this.currentIdx; var tokenCount = curr.Tokens.Count(); - for (var j = 0; j < tokenCount; j++) + foreach (var token in curr.Tokens) { - ref var decoration = ref this.tokenDecorations[this.currentIdx + j]; - if (decoration.DoesReturnLineCollapsible is null) + var newLines = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).ToArray(); + if (newLines.Length > 0) { - decoration.DoesReturnLineCollapsible = CollapsibleBool.False; + this.tokenDecorations[this.currentIdx + 1].DoesReturnLine = true; + + this.CurrentToken.TokenOverride = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); } + token.Accept(this); + } + + for (var j = 0; j < tokenCount; j++) + { + ref var decoration = ref this.tokenDecorations[startIdx + j]; + decoration.DoesReturnLine ??= false; } - curr.Accept(this); } MultiIndent(newLineCount); - this.tokenDecorations[this.currentIdx].DoesReturnLineCollapsible = CollapsibleBool.True; + this.tokenDecorations[this.currentIdx].DoesReturnLine = true; node.CloseQuotes.Accept(this); @@ -268,7 +306,7 @@ void MultiIndent(int newLineCount) if (newLineCount > 0) { ref var currentToken = ref this.tokenDecorations[this.currentIdx]; - currentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + currentToken.DoesReturnLine = true; if (newLineCount > 1) { // TODO @@ -289,9 +327,9 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod } node.Left.Accept(this); - if (this.CurrentToken.DoesReturnLineCollapsible is null) + if (this.CurrentToken.DoesReturnLine is null) { - this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; } node.Operator.Accept(this); node.Right.Accept(this); @@ -308,21 +346,24 @@ public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax n { node.OpenBrace.Accept(this); this.CreateFoldedScope(this.Settings.Indentation, () => node.Statements.Accept(this)); - this.tokenDecorations[this.currentIdx].DoesReturnLineCollapsible = CollapsibleBool.True; + this.tokenDecorations[this.currentIdx].DoesReturnLine = true; node.CloseBrace.Accept(this); } public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) { var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; - using var _ = this.CreateFoldableScope(new string(' ', 7 + parent.Name.Span.Length + parent.ParameterList.Span.Length), - FoldPriority.AsSoonAsPossible - ); - base.VisitInlineFunctionBody(node); + var curr = this.currentIdx; + node.Assign.Accept(this); + + using var _ = this.CreateFoldableScope(curr, FoldPriority.AsSoonAsPossible); + node.Value.Accept(this); + node.Semicolon.Accept(this); } public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { + this.VisitDeclaration(node); node.VisibilityModifier?.Accept(this); node.FunctionKeyword.Accept(this); node.Name.Accept(this); @@ -331,7 +372,6 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); } node.OpenParen.Accept(this); - this.CreateFoldableScope() node.ParameterList.Accept(this); node.CloseParen.Accept(this); node.ReturnType?.Accept(this); @@ -344,59 +384,54 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { - // TODO: special case where we un-nest a level. - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + this.CurrentToken.DoesReturnLine = true; + this.CurrentToken.Kind = FormattingTokenKind.RemoveOneIndentation; } else if (node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax) { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + this.CurrentToken.DoesReturnLine = true; } else { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.Create(this.scope.ItemsCount.WhenGreaterOrEqual(2)); + this.CurrentToken.DoesReturnLine = this.scope.ItemsCount.WhenGreaterOrEqual(2); } base.VisitStatement(node); } public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitWhileExpression(node)); + node.WhileKeyword.Accept(this); + node.OpenParen.Accept(this); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); + node.CloseParen.Accept(this); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); } - public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) - { - void Visit() + public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => + this.CreateFoldableScope("", FoldPriority.AsSoonAsPossible, () => { - this.VisitExpression(node); node.IfKeyword.Accept(this); node.OpenParen.Accept(this); - node.Condition.Accept(this); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); node.CloseParen.Accept(this); - node.Then.Accept(this); - } - if (this.scope.ItemsCount.MinimumCurrentValue > 1) - { - Visit(); - } - else - { - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, Visit); - } - node.Else?.Accept(this); - } + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + + node.Else?.Accept(this); + }); public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { if (node.IsElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + this.CurrentToken.DoesReturnLine = true; } else { - this.CurrentToken.DoesReturnLineCollapsible = this.scope.IsMaterialized; + this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; } - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitElseClause(node)); + node.ElseKeyword.Accept(this); + this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -407,21 +442,21 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // if (blabla) // an expression; // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - this.scope.IsMaterialized.TryCollapse(false); + this.scope.IsMaterialized.Value ??= false; + + node.OpenBrace.Accept(this); this.CreateFoldedScope(this.Settings.Indentation, () => { - this.VisitExpression(node); - node.OpenBrace.Accept(this); node.Statements.Accept(this); if (node.Value != null) { - this.CurrentToken.DoesReturnLineCollapsible = CollapsibleBool.True; + this.CurrentToken.DoesReturnLine = true; node.Value.Accept(this); } - node.CloseBrace.Accept(this); }); - this.tokenDecorations[this.currentIdx - 1].DoesReturnLineCollapsible = CollapsibleBool.True; + node.CloseBrace.Accept(this); + this.tokenDecorations[this.currentIdx - 1].DoesReturnLine = true; } public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) @@ -431,8 +466,8 @@ public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) private IDisposable CreateFoldedScope(string indentation) { - this.scope = new ScopeInfo(this.scope, FoldPriority.Never, indentation); - this.scope.IsMaterialized.Collapse(true); + this.scope = new ScopeInfo(this.scope, Settings, FoldPriority.Never, indentation); + this.scope.IsMaterialized.Value = true; return new DisposeAction(() => { this.scope.Dispose(); @@ -447,7 +482,17 @@ private void CreateFoldedScope(string indentation, Action action) private IDisposable CreateFoldableScope(string indentation, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, foldBehavior, indentation); + this.scope = new ScopeInfo(this.scope, Settings, foldBehavior, indentation); + return new DisposeAction(() => + { + this.scope.Dispose(); + this.scope = this.scope.Parent!; + }); + } + + private IDisposable CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + { + this.scope = new ScopeInfo(this.scope, Settings, foldBehavior, (this.tokenDecorations, indexOfLevelingToken)); return new DisposeAction(() => { this.scope.Dispose(); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs index d2dddfc24..6103c2086 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -8,8 +8,11 @@ internal enum FormattingTokenKind NoFormatting = 0, PadLeft = 1, PadRight = 1 << 1, + ForceRightPad = 1 << 2, + BehaveAsWhiteSpaceForNextToken = 1 << 3, + BehaveAsWhiteSpaceForPreviousToken = 1 << 4, + ExtraNewline = 1 << 5, + RemoveOneIndentation = 1 << 6, PadAround = PadLeft | PadRight, - TreatAsWhitespace = 1 << 2, - Semicolon = 1 << 3, - ExtraNewline = 1 << 4 + Whitespace = BehaveAsWhiteSpaceForNextToken | BehaveAsWhiteSpaceForPreviousToken, } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index cf78061e3..6062bafb4 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -1,4 +1,4 @@ -using System.Text; +using System.Text; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -7,6 +7,7 @@ internal class LineStateMachine private readonly StringBuilder sb = new(); private readonly string indentation; private bool previousIsWhitespace = true; + private bool prevTokenNeedRightPad = false; public LineStateMachine(string indentation) { this.sb.Append(indentation); @@ -15,22 +16,32 @@ public LineStateMachine(string indentation) } public int LineWidth { get; set; } - public void AddToken(TokenDecoration decoration, Api.Syntax.SyntaxToken token) + public void AddToken(TokenDecoration decoration, FormatterSettings settings) { - if (decoration.Kind.HasFlag(FormattingTokenKind.PadLeft) && !this.previousIsWhitespace) + if (decoration.Kind.HasFlag(FormattingTokenKind.RemoveOneIndentation)) { + this.sb.Remove(0, settings.Indentation.Length); + } + var shouldLeftPad = (this.prevTokenNeedRightPad || decoration.Kind.HasFlag(FormattingTokenKind.PadLeft)) + && !decoration.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken) + && !this.previousIsWhitespace; + if (shouldLeftPad) + { + this.previousIsWhitespace = true; this.sb.Append(' '); this.LineWidth++; } - var text = decoration.TokenOverride ?? token.Text; + var text = decoration.TokenOverride ?? decoration.Token.Text; this.sb.Append(text); - if (decoration.Kind.HasFlag(FormattingTokenKind.PadRight)) + this.LineWidth += text.Length; + if (decoration.Kind.HasFlag(FormattingTokenKind.ForceRightPad)) { this.sb.Append(' '); this.LineWidth++; } - this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.TreatAsWhitespace) || decoration.Kind.HasFlag(FormattingTokenKind.PadRight); - this.LineWidth += text.Length; + this.prevTokenNeedRightPad = decoration.Kind.HasFlag(FormattingTokenKind.PadRight); + + this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForNextToken) | decoration.Kind.HasFlag(FormattingTokenKind.ForceRightPad); } public void Reset() diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 92f1f2fce..b718301f8 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -16,25 +16,27 @@ internal class ScopeInfo : IDisposable private readonly SolverTaskCompletionSource _stableTcs = new(); private readonly string? indentation; private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; + private readonly FormatterSettings settings; [MemberNotNullWhen(true, nameof(levelingToken))] [MemberNotNullWhen(false, nameof(indentation))] private bool DrivenByLevelingToken => this.levelingToken.HasValue; - public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority) + private ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority) { this.Parent = parent; + this.settings = settings; this.FoldPriority = foldPriority; parent?.Childs.Add(this); } - public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority, string indentation) : this(parent, foldPriority) + public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) { this.indentation = indentation; } - public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) - : this(parent, foldPriority) + public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) + : this(parent, settings, foldPriority) { this.levelingToken = levelingToken; } @@ -54,16 +56,22 @@ public ScopeInfo(ScopeInfo? parent, FoldPriority foldPriority, (IReadOnlyList /// - public CollapsibleBool IsMaterialized { get; } = CollapsibleBool.Create(); + public MutableBox IsMaterialized { get; } = new MutableBox(null, true); + private bool IsMaterializedValue => this.IsMaterialized.Value ?? false; public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public TokenDecoration? TokenDecoration { get; set; } - public IEnumerable CurrentTotalIndent { get { + if (!this.IsMaterializedValue) + { + if (this.Parent is null) return []; + return this.Parent.CurrentTotalIndent; + } + if (!this.DrivenByLevelingToken) { if (this.Parent is null) return [this.indentation]; @@ -76,7 +84,7 @@ int GetStartLineTokenIndex() { for (var i = indexOfLevelingToken; i >= 0; i--) { - if (tokens[i].DoesReturnLineCollapsible?.Collapsed.Result ?? false) + if (tokens[i].DoesReturnLine?.Value ?? false) { return i; } @@ -87,12 +95,13 @@ int GetStartLineTokenIndex() var startLine = GetStartLineTokenIndex(); var startToken = this.levelingToken.Value.tokens[startLine]; var stateMachine = new LineStateMachine(string.Concat(startToken.ScopeInfo.CurrentTotalIndent)); - for (var i = startLine; i < this.levelingToken.Value.tokens.Count; i++) + for (var i = startLine; i <= indexOfLevelingToken; i++) { var curr = this.levelingToken.Value.tokens[i]; - stateMachine.AddToken(curr, curr.Token); + stateMachine.AddToken(curr, this.settings); } - return [new string(' ', stateMachine.LineWidth)]; + var levelingToken = this.levelingToken.Value.tokens[indexOfLevelingToken]; + return [new string(' ', stateMachine.LineWidth - levelingToken.Token.Text.Length)]; } } @@ -139,28 +148,28 @@ public IEnumerable Parents } } - public bool Fold() + public ScopeInfo? Fold() { foreach (var item in this.ThisAndParents.Reverse()) { - if (item.IsMaterialized.Collapsed.IsCompleted) continue; + if (item.IsMaterialized.Value.HasValue) continue; if (item.FoldPriority == FoldPriority.AsSoonAsPossible) { - item.IsMaterialized.Collapse(true); - return true; + item.IsMaterialized.Value = true; + return item; } } foreach (var item in this.ThisAndParents) { - if (item.IsMaterialized.Collapsed.IsCompleted) continue; + if (item.IsMaterialized.Value.HasValue) continue; if (item.FoldPriority == FoldPriority.AsLateAsPossible) { - item.IsMaterialized.Collapse(true); - return true; + item.IsMaterialized.Value = true; + return item; } } - return false; + return null; } public void Dispose() => this.ItemsCount.Collapse(); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index db5484012..62d13cce7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -8,20 +9,11 @@ internal struct TokenDecoration { private ScopeInfo scopeInfo; private string? tokenOverride; - private CollapsibleBool? doesReturnLineCollapsible; - private FormattingTokenKind kind; + private Box? doesReturnLine; private Api.Syntax.SyntaxToken token; - private bool _kindIsSet; - public FormattingTokenKind Kind - { - readonly get => this.kind; - set - { - if (this._kindIsSet) throw new InvalidOperationException("Kind already set"); - this.kind = value; - this._kindIsSet = true; - } - } + + public FormattingTokenKind Kind { get; set; } + public Api.Syntax.SyntaxToken Token { readonly get => this.token; set @@ -34,7 +26,7 @@ public Api.Syntax.SyntaxToken Token [DisallowNull] public string? TokenOverride { - get => this.tokenOverride; + readonly get => this.tokenOverride; set { if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); @@ -43,13 +35,13 @@ public string? TokenOverride } [DisallowNull] - public CollapsibleBool? DoesReturnLineCollapsible + public Box? DoesReturnLine { - readonly get => this.doesReturnLineCollapsible; + readonly get => this.doesReturnLine; set { - if (this.doesReturnLineCollapsible != null) throw new InvalidOperationException("Collapsible already set"); - this.doesReturnLineCollapsible = value; + if (this.doesReturnLine != null) throw new InvalidOperationException("DoesReturnLine already set"); + this.doesReturnLine = value; } } From 41b425422d3d3726671c14b16c77e2dca7a8bbe1 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 29 Apr 2024 01:28:29 +0200 Subject: [PATCH 20/76] wip. --- .../Syntax/ParseTreeFormatterTests.cs | 88 +++++++++++++++++++ .../Internal/Syntax/Formatting/Formatter.cs | 9 +- .../Internal/Syntax/Formatting/ScopeInfo.cs | 25 ------ 3 files changed, 96 insertions(+), 26 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index aef41f87b..58a63bb97 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -235,4 +235,92 @@ func main( this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void NoLineReturnInSingleLineString() + { + var input = """" + func main() { + val value = "Value: \{if (input < value) "low" else "high"}"; + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 10 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } + + [Fact] + public void Sample1() + { + var input = """" + import System; + import System.Console; + + func main() { + val value = Random.Shared.Next(1, 101); + while (true) { + Write("Guess a number (1-100): "); + val input = Convert.ToInt32(ReadLine()); + if (input == value) goto break; + WriteLine("Incorrect. Too \{if (input < value) "low" else "high"}"); + } + WriteLine("You guessed it!"); + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 50 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } + + [Fact] + public void Sample2() + { + var input = """" + //test + func //test + main + // another + (){ + var opponent = "R"; + var me = "P"; + if(me == opponent) return println("draw"); + if(me == "R"){ + println( + if(opponent == "P") "lose" + else "win" + ); + } + else if (me == "P"){ + println( + if(opponent == "R") "win" + else "lose" + ); + } + else if(me == "S") { + println( + if(opponent == "P") "win" + else "lose" + ); + } + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + LineWidth = 50 + }); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index e18ae3e99..807d759d1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -239,7 +239,14 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod { if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) { - base.VisitStringExpression(node); + node.OpenQuotes.Accept(this); + foreach (var item in node.Parts.Tokens) + { + this.CurrentToken.DoesReturnLine = false; + item.Accept(this); + } + node.CloseQuotes.Accept(this); + this.CurrentToken.DoesReturnLine = false; return; } node.OpenQuotes.Accept(this); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index b718301f8..e12dcb522 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -1,19 +1,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Reflection.Metadata; -using Draco.Compiler.Internal.Solver.Tasks; -using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Syntax.Formatting; internal class ScopeInfo : IDisposable { public ScopeInfo? Parent { get; } - public List Childs { get; } = []; - private readonly SolverTaskCompletionSource _stableTcs = new(); private readonly string? indentation; private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; private readonly FormatterSettings settings; @@ -27,7 +21,6 @@ private ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority fo this.Parent = parent; this.settings = settings; this.FoldPriority = foldPriority; - parent?.Childs.Add(this); } public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) @@ -41,8 +34,6 @@ public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority fol this.levelingToken = levelingToken; } - public SolverTask WhenStable => this._stableTcs.Task; - public object? Data { get; set; } /// @@ -108,22 +99,6 @@ int GetStartLineTokenIndex() public FoldPriority FoldPriority { get; } - public IEnumerable ThisAndAllChilds => this.AllChilds.Prepend(this); - public IEnumerable AllChilds - { - get - { - foreach (var child in this.Childs) - { - yield return child; - foreach (var subChild in child.AllChilds) - { - yield return subChild; - } - } - } - } - public ScopeInfo Root { get From cec80c52dfeb060c8be3cbfe3fcfed75e167f78d Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 29 Apr 2024 10:26:21 +0200 Subject: [PATCH 21/76] wip. --- src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs | 1 + src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs | 7 +++++++ .../Internal/Syntax/Formatting/TokenDecoration.cs | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 58a63bb97..a1f33e830 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -290,6 +290,7 @@ public void Sample2() func //test main // another + // foobar (){ var opponent = "R"; var me = "P"; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 807d759d1..385629a9b 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -199,6 +199,13 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.CurrentToken.ScopeInfo = this.scope; this.CurrentToken.Kind |= GetFormattingTokenKind(node); this.CurrentToken.Token = node; + var trivia = this.CurrentToken.Token.TrailingTrivia; + if (trivia.Count > 0) + { + this.CurrentToken.TrailingComments = trivia + .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) + .Select(x => x.Text).ToArray(); + } base.VisitSyntaxToken(node); this.currentIdx++; } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 62d13cce7..96b785b6a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Draco.Compiler.Internal.Solver.Tasks; @@ -57,4 +58,6 @@ public ScopeInfo ScopeInfo this.scopeInfo = value; } } + + public IReadOnlyCollection? TrailingComments { get; set; } } From cfc2f20bc4d5b091ffadd8ff836425ed23571c69 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 29 Apr 2024 20:21:07 +0200 Subject: [PATCH 22/76] A bugfix. --- src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs | 5 ++--- src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs | 7 ++++--- .../Internal/Syntax/Formatting/TokenDecoration.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index a1f33e830..247e77044 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -313,12 +313,11 @@ public void Sample2() else "lose" ); } - } - + } // oh hello + // oops. """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { - LineWidth = 50 }); Console.WriteLine(actual); this.logger.WriteLine(actual); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 385629a9b..48cd87351 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -202,9 +202,10 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) var trivia = this.CurrentToken.Token.TrailingTrivia; if (trivia.Count > 0) { - this.CurrentToken.TrailingComments = trivia + this.CurrentToken.TrailingComment = trivia .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) - .Select(x => x.Text).ToArray(); + .Select(x => x.Text) + .SingleOrDefault(); } base.VisitSyntaxToken(node); this.currentIdx++; @@ -252,8 +253,8 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod this.CurrentToken.DoesReturnLine = false; item.Accept(this); } - node.CloseQuotes.Accept(this); this.CurrentToken.DoesReturnLine = false; + node.CloseQuotes.Accept(this); return; } node.OpenQuotes.Accept(this); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 96b785b6a..fc6c8518a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -59,5 +59,5 @@ public ScopeInfo ScopeInfo } } - public IReadOnlyCollection? TrailingComments { get; set; } + public string TrailingComment { get; set; } } From eef1ff0ce681150a851c0846f9fee652495fa05f Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 30 Apr 2024 00:25:24 +0200 Subject: [PATCH 23/76] Simplest comment case handled. --- .../Internal/Syntax/Formatting/Formatter.cs | 23 ++++++++++++++++--- .../Syntax/Formatting/TokenDecoration.cs | 2 -- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 48cd87351..380377f46 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; +using System.Security.Principal; using System.Text; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Solver.Tasks; @@ -202,10 +203,15 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) var trivia = this.CurrentToken.Token.TrailingTrivia; if (trivia.Count > 0) { - this.CurrentToken.TrailingComment = trivia + var comment = trivia .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) .Select(x => x.Text) .SingleOrDefault(); + if (comment != null) + { + this.CurrentToken.TokenOverride = this.CurrentToken.Token.Text + " " + comment; + this.tokenDecorations[this.currentIdx + 1].DoesReturnLine = true; + } } base.VisitSyntaxToken(node); this.currentIdx++; @@ -379,14 +385,25 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { this.VisitDeclaration(node); - node.VisibilityModifier?.Accept(this); - node.FunctionKeyword.Accept(this); + IDisposable disposable; + if (node.VisibilityModifier != null) + { + node.VisibilityModifier?.Accept(this); + disposable = this.CreateFoldedScope(this.Settings.Indentation); + node.FunctionKeyword.Accept(this); + } + else + { + node.FunctionKeyword.Accept(this); + disposable = this.CreateFoldedScope(this.Settings.Indentation); + } node.Name.Accept(this); if (node.Generics is not null) { this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); } node.OpenParen.Accept(this); + disposable.Dispose(); node.ParameterList.Accept(this); node.CloseParen.Accept(this); node.ReturnType?.Accept(this); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index fc6c8518a..5d7f1bf1d 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -58,6 +58,4 @@ public ScopeInfo ScopeInfo this.scopeInfo = value; } } - - public string TrailingComment { get; set; } } From 9b4edb6f64a6a8a9f998a3b474d528abda1ebf00 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 30 Apr 2024 00:46:05 +0200 Subject: [PATCH 24/76] Added comments support. --- .../Syntax/ParseTreeFormatterTests.cs | 28 +++++++------------ .../Internal/Syntax/Formatting/Formatter.cs | 14 ++++++++-- .../Syntax/Formatting/LineStateMachine.cs | 14 ++++++++++ .../Syntax/Formatting/TokenDecoration.cs | 15 ++-------- 4 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 247e77044..5d171702c 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -291,30 +291,22 @@ public void Sample2() main // another // foobar - (){ + () { var opponent = "R"; var me = "P"; - if(me == opponent) return println("draw"); - if(me == "R"){ - println( - if(opponent == "P") "lose" - else "win" - ); + if (me == opponent) return println("draw"); + if (me == "R") { + println(if (opponent == "P") "lose" else "win"); } - else if (me == "P"){ - println( - if(opponent == "R") "win" - else "lose" - ); - } - else if(me == "S") { - println( - if(opponent == "P") "win" - else "lose" - ); + else if (me == "P") { + println(if (opponent == "R") "win" else "lose"); + } + else if (me == "S") { + println(if (opponent == "P") "win" else "lose"); } } // oh hello // oops. + """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 380377f46..ff3c4b37c 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -102,7 +102,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var decoration = decorations[x]; if (decoration.Token.Kind == TokenKind.StringNewline) continue; - if (decoration.DoesReturnLine?.Value ?? false) + if (x > 0 && (decoration.DoesReturnLine?.Value ?? false)) { builder.Append(stateMachine); builder.Append(settings.Newline); @@ -116,7 +116,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) stateMachine.AddToken(decoration, settings); } builder.Append(stateMachine); - builder.AppendLine(); + builder.Append(settings.Newline); return builder.ToString(); } @@ -213,6 +213,16 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.tokenDecorations[this.currentIdx + 1].DoesReturnLine = true; } } + var leadingComments = this.CurrentToken.Token.LeadingTrivia + .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) + .Select(x => x.Text) + .ToArray(); + this.CurrentToken.LeadingComments = leadingComments; + if (leadingComments.Length > 0) + { + this.CurrentToken.DoesReturnLine = true; + } + base.VisitSyntaxToken(node); this.currentIdx++; } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 6062bafb4..f4a568fc3 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -18,6 +18,20 @@ public LineStateMachine(string indentation) public int LineWidth { get; set; } public void AddToken(TokenDecoration decoration, FormatterSettings settings) { + if (decoration.LeadingComments.Count > 0) + { + foreach (var comment in decoration.LeadingComments) + { + this.sb.Append(comment); + this.LineWidth += comment.Length; + if (decoration.Token.Kind != Api.Syntax.TokenKind.EndOfInput) + { + this.sb.Append(settings.Newline); + this.sb.Append(this.indentation); + } + } + } + if (decoration.Kind.HasFlag(FormattingTokenKind.RemoveOneIndentation)) { this.sb.Remove(0, settings.Indentation.Length); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 5d7f1bf1d..8bb61b2d1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Runtime.CompilerServices; -using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -10,7 +8,6 @@ internal struct TokenDecoration { private ScopeInfo scopeInfo; private string? tokenOverride; - private Box? doesReturnLine; private Api.Syntax.SyntaxToken token; public FormattingTokenKind Kind { get; set; } @@ -36,15 +33,7 @@ public string? TokenOverride } [DisallowNull] - public Box? DoesReturnLine - { - readonly get => this.doesReturnLine; - set - { - if (this.doesReturnLine != null) throw new InvalidOperationException("DoesReturnLine already set"); - this.doesReturnLine = value; - } - } + public Box? DoesReturnLine { readonly get; set; } public ScopeInfo ScopeInfo { @@ -58,4 +47,6 @@ public ScopeInfo ScopeInfo this.scopeInfo = value; } } + + public IReadOnlyCollection LeadingComments { get; set; } } From c43c83b7ee2b9aaed36baecb66ed3ebd3509c419 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 30 Apr 2024 09:41:48 +0200 Subject: [PATCH 25/76] Removed CollapsableInt. --- .../Syntax/Formatting/CollapsibleInt.cs | 86 ------------------- .../Internal/Syntax/Formatting/Formatter.cs | 32 ++----- .../Internal/Syntax/Formatting/ScopeInfo.cs | 5 +- 3 files changed, 8 insertions(+), 115 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs deleted file mode 100644 index 5d2644a72..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleInt.cs +++ /dev/null @@ -1,86 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Draco.Compiler.Internal.Solver.Tasks; - -namespace Draco.Compiler.Internal.Syntax.Formatting; - -internal class CollapsibleInt -{ - private readonly SolverTaskCompletionSource? tcs; - private readonly SolverTask task; - public int MinimumCurrentValue { get; private set; } - private CollapsibleInt(SolverTaskCompletionSource tcs) - { - this.tcs = tcs; - this.task = tcs.Task; - } - - private CollapsibleInt(SolverTask task) - { - this.task = task; - } - - public static CollapsibleInt Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleInt Create(int value) => new(SolverTask.FromResult(value)); - - - // order by desc - private List<(int Value, MutableBox Box)>? _boxes; - - public void Add(int toAdd) - { - this.MinimumCurrentValue += toAdd; - if (this._boxes is null) return; - var i = this._boxes.Count - 1; - if (i < 0) return; - while (true) - { - var (value, box) = this._boxes![i]; - if (this.MinimumCurrentValue < value) break; - box.Value = true; - if (i == 0) break; - i--; - } - this._boxes.RemoveRange(i, this._boxes.Count - i); - } - - public void Collapse() - { - if (this._boxes is not null) - { - foreach (var (_, box) in this._boxes ?? Enumerable.Empty<(int Value, MutableBox Tcs)>()) - { - box.Value = false; - } - this._boxes = null; - } - - this.tcs?.SetResult(this.MinimumCurrentValue); - } - - public SolverTask Collapsed => this.task; - - public Box WhenGreaterOrEqual(int number) - { - if (this.MinimumCurrentValue >= number) return true; - this._boxes ??= []; - var index = this._boxes.BinarySearch((number, null!), Comparer.Instance); - if (index > 0) - { - return this._boxes[index].Box; - } - else - { - var box = new MutableBox(null, true); - this._boxes.Insert(~index, (number, box)); - return box; - } - } - - private class Comparer : IComparer<(int, MutableBox)> - { - public static Comparer Instance { get; } = new Comparer(); - // reverse comparison. - public int Compare((int, MutableBox) x, (int, MutableBox) y) => y.Item1.CompareTo(x.Item1); - } -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index ff3c4b37c..0ca403fa7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -370,7 +370,6 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) { base.VisitMemberExpression(node); - this.scope.ItemsCount.Add(1); } public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) @@ -422,20 +421,15 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt public override void VisitStatement(Api.Syntax.StatementSyntax node) { - this.scope.ItemsCount.Add(1); - if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { this.CurrentToken.DoesReturnLine = true; this.CurrentToken.Kind = FormattingTokenKind.RemoveOneIndentation; } - else if (node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax) - { - this.CurrentToken.DoesReturnLine = true; - } else { - this.CurrentToken.DoesReturnLine = this.scope.ItemsCount.WhenGreaterOrEqual(2); + var shouldBeMultiLine = node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax; + this.CurrentToken.DoesReturnLine = shouldBeMultiLine; } base.VisitStatement(node); } @@ -510,11 +504,7 @@ private IDisposable CreateFoldedScope(string indentation) { this.scope = new ScopeInfo(this.scope, Settings, FoldPriority.Never, indentation); this.scope.IsMaterialized.Value = true; - return new DisposeAction(() => - { - this.scope.Dispose(); - this.scope = this.scope.Parent!; - }); + return new DisposeAction(() => this.scope = this.scope.Parent!); } private void CreateFoldedScope(string indentation, Action action) @@ -524,22 +514,14 @@ private void CreateFoldedScope(string indentation, Action action) private IDisposable CreateFoldableScope(string indentation, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, Settings, foldBehavior, indentation); - return new DisposeAction(() => - { - this.scope.Dispose(); - this.scope = this.scope.Parent!; - }); + this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, indentation); + return new DisposeAction(() => this.scope = this.scope.Parent!); } private IDisposable CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, Settings, foldBehavior, (this.tokenDecorations, indexOfLevelingToken)); - return new DisposeAction(() => - { - this.scope.Dispose(); - this.scope = this.scope.Parent!; - }); + this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, (this.tokenDecorations, indexOfLevelingToken)); + return new DisposeAction(() => this.scope = this.scope.Parent!); } private void CreateFoldableScope(string indentation, FoldPriority foldBehavior, Action action) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index e12dcb522..745e2447e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class ScopeInfo : IDisposable +internal class ScopeInfo { public ScopeInfo? Parent { get; } private readonly string? indentation; @@ -49,7 +49,6 @@ public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority fol /// public MutableBox IsMaterialized { get; } = new MutableBox(null, true); private bool IsMaterializedValue => this.IsMaterialized.Value ?? false; - public CollapsibleInt ItemsCount { get; } = CollapsibleInt.Create(); public TokenDecoration? TokenDecoration { get; set; } @@ -146,6 +145,4 @@ public IEnumerable Parents } return null; } - - public void Dispose() => this.ItemsCount.Collapse(); } From 5cdb31c718b2a6ea7b1f027f7945efe41b183053 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 1 May 2024 00:09:29 +0200 Subject: [PATCH 26/76] Remove dead code. --- .../Syntax/Formatting/CollapsibleBool.cs | 66 ------------------- .../Syntax/Formatting/CollapsibleValue.cs | 56 ---------------- .../Internal/Syntax/Formatting/Formatter.cs | 21 +----- 3 files changed, 3 insertions(+), 140 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs deleted file mode 100644 index bdafc956e..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleBool.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using Draco.Compiler.Internal.Solver.Tasks; - -namespace Draco.Compiler.Internal.Syntax.Formatting; - -internal class CollapsibleBool : IEquatable -{ - private readonly SolverTaskCompletionSource? tcs; - private readonly SolverTask task; - - private CollapsibleBool(SolverTaskCompletionSource tcs) - { - this.tcs = tcs; - this.task = tcs.Task; - } - private CollapsibleBool(SolverTask task) - { - this.task = task; - } - - public static CollapsibleBool Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleBool Create(SolverTask solverTask) => new(solverTask); - public static CollapsibleBool True { get; } = new(SolverTask.FromResult(true)); - public static CollapsibleBool False { get; } = new(SolverTask.FromResult(false)); - - public void Collapse(bool collapse) - { - if (this.tcs is null) throw new InvalidOperationException(); - this.tcs?.SetResult(collapse); - } - - public bool TryCollapse(bool collapse) - { - if (!this.Collapsed.IsCompleted) - { - this.Collapse(collapse); - return true; - } - return false; - } - public bool Equals(CollapsibleBool? other) - { - if (other is null) return false; - - if (this.tcs is null) - { - if (other.tcs is not null) return false; - return this.task.Result == other.task.Result; - } - if (other.tcs is null) return false; - if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result == other.task.Result; - return false; - } - - public override bool Equals(object? obj) - { - if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); - return false; - } - - public SolverTask Collapsed => this.task; - - public static bool operator ==(CollapsibleBool? left, CollapsibleBool? right) => EqualityComparer.Default.Equals(left, right); - public static bool operator !=(CollapsibleBool? left, CollapsibleBool? right) => !(left == right); -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs deleted file mode 100644 index cfaa2e623..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CollapsibleValue.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using Draco.Compiler.Internal.Solver.Tasks; - -namespace Draco.Compiler.Internal.Syntax.Formatting; - -internal class CollapsibleValue - where T : struct -{ - private readonly SolverTaskCompletionSource? tcs; - private readonly SolverTask task; - - private CollapsibleValue(SolverTaskCompletionSource tcs) - { - this.tcs = tcs; - this.task = tcs.Task; - } - private CollapsibleValue(SolverTask task) - { - this.task = task; - } - - public static CollapsibleValue Create() => new(new SolverTaskCompletionSource()); - public static CollapsibleValue Create(T value) => new(SolverTask.FromResult(value)); - - public void Collapse(T collapse) => this.tcs?.SetResult(collapse); - public bool TryCollapse(T collapse) - { - if (!this.Collapsed.IsCompleted) - { - this.Collapse(collapse); - return true; - } - return false; - } - public bool Equals(CollapsibleValue? other) - { - if (other is null) return false; - - if (this.tcs is null) - { - if (other.tcs is not null) return false; - return this.task.Result.Equals(other.task.Result); - } - if (other.tcs is null) return false; - if (this.tcs.IsCompleted && other.tcs.IsCompleted) return this.task.Result.Equals(other.task.Result); - return false; - } - - public override bool Equals(object? obj) - { - if (obj is CollapsibleBool collapsibleBool) return this.Equals(collapsibleBool); - return false; - } - - public SolverTask Collapsed => this.task; -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 0ca403fa7..7783aaaec 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,12 +1,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; -using System.Runtime.CompilerServices; -using System.Security.Principal; using System.Text; using Draco.Compiler.Api.Syntax; -using Draco.Compiler.Internal.Solver.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -367,11 +363,6 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod closeScope?.Dispose(); } - public override void VisitMemberExpression(Api.Syntax.MemberExpressionSyntax node) - { - base.VisitMemberExpression(node); - } - public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { node.OpenBrace.Accept(this); @@ -382,7 +373,6 @@ public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax n public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) { - var parent = (Api.Syntax.FunctionDeclarationSyntax)node.Parent!; var curr = this.currentIdx; node.Assign.Accept(this); @@ -495,12 +485,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) this.tokenDecorations[this.currentIdx - 1].DoesReturnLine = true; } - public override void VisitTypeSpecifier(Api.Syntax.TypeSpecifierSyntax node) - { - base.VisitTypeSpecifier(node); - } - - private IDisposable CreateFoldedScope(string indentation) + private DisposeAction CreateFoldedScope(string indentation) { this.scope = new ScopeInfo(this.scope, Settings, FoldPriority.Never, indentation); this.scope.IsMaterialized.Value = true; @@ -512,13 +497,13 @@ private void CreateFoldedScope(string indentation, Action action) using (this.CreateFoldedScope(indentation)) action(); } - private IDisposable CreateFoldableScope(string indentation, FoldPriority foldBehavior) + private DisposeAction CreateFoldableScope(string indentation, FoldPriority foldBehavior) { this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, indentation); return new DisposeAction(() => this.scope = this.scope.Parent!); } - private IDisposable CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + private DisposeAction CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, (this.tokenDecorations, indexOfLevelingToken)); return new DisposeAction(() => this.scope = this.scope.Parent!); From a62d233a7c3e654524a16f7ebeaa0cbb1607238b Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 1 May 2024 00:13:43 +0200 Subject: [PATCH 27/76] Cleanup --- src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 7783aaaec..07aa84b50 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -345,7 +345,7 @@ void MultiIndent(int newLineCount) public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) { - IDisposable? closeScope = null; + DisposeAction? closeScope = null; var kind = node.Operator.Kind; if (!(this.scope.Data?.Equals(kind) ?? false)) { @@ -384,7 +384,7 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { this.VisitDeclaration(node); - IDisposable disposable; + DisposeAction disposable; if (node.VisibilityModifier != null) { node.VisibilityModifier?.Accept(this); @@ -487,7 +487,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) private DisposeAction CreateFoldedScope(string indentation) { - this.scope = new ScopeInfo(this.scope, Settings, FoldPriority.Never, indentation); + this.scope = new ScopeInfo(this.scope, this.Settings, FoldPriority.Never, indentation); this.scope.IsMaterialized.Value = true; return new DisposeAction(() => this.scope = this.scope.Parent!); } From f5f943640d2ad99718d5f63d8f27146a7f7e9ef0 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 1 May 2024 00:17:29 +0200 Subject: [PATCH 28/76] Added failing test case. --- .../Syntax/ParseTreeFormatterTests.cs | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 5d171702c..8638a0357 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -185,6 +185,26 @@ func main() { Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } + [Fact] + public void MultiReturnInMultiLineStringArePreserved() + { + var input = """" + func main() { + val someMultiLineString = """ + bla bla + + + bla bla + """; + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + Console.WriteLine(actual); + this.logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } + [Fact] public void IfElseChainFormatsCorrectly() { From 75e2cad9c45adab8c3c806f14256ffd64817b884 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 2 May 2024 01:02:56 +0200 Subject: [PATCH 29/76] Fixed more edge case, added test for another one. --- .../Syntax/ParseTreeFormatterTests.cs | 56 ++++++----- .../Internal/Syntax/Formatting/Formatter.cs | 92 ++++++++++++++----- .../Syntax/Formatting/LineStateMachine.cs | 7 +- .../Syntax/Formatting/TokenDecoration.cs | 52 ++--------- 4 files changed, 104 insertions(+), 103 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 8638a0357..6aa95114e 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -6,13 +6,6 @@ namespace Draco.Compiler.Tests.Syntax; public sealed class SyntaxTreeFormatterTests { - private readonly ITestOutputHelper logger; - - public SyntaxTreeFormatterTests(ITestOutputHelper logger) - { - this.logger = logger; - } - [Fact] public void SomeCodeSampleShouldBeFormattedCorrectly() { @@ -101,8 +94,6 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -130,8 +121,6 @@ func main() { """; var actual = SyntaxTree.Parse(input).Format(); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -158,9 +147,6 @@ func aLongMethodName() = 1 { LineWidth = 60 }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); - this.logger.WriteLine(expected); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -180,8 +166,6 @@ func main() { { LineWidth = 50 }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -195,13 +179,32 @@ bla bla bla bla + + """; + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } + + [Fact] + public void MultiReturnInMultiLineStringArePreserved2() + { + var input = """" + func main() { + val someMultiLineString = """ + bla bla + + + bla bla + + """; } """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -228,8 +231,6 @@ func main() { """"; var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -251,8 +252,6 @@ func main( { LineWidth = 60 }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -269,8 +268,6 @@ func main() { { LineWidth = 10 }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -297,8 +294,6 @@ func main() { { LineWidth = 50 }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -312,9 +307,12 @@ public void Sample2() // another // foobar () { - var opponent = "R"; + var + // test + opponent = "R"; var me = "P"; - if (me == opponent) return println("draw"); + if // heh + (me == opponent) return println("draw"); if (me == "R") { println(if (opponent == "P") "lose" else "win"); } @@ -331,8 +329,6 @@ public void Sample2() var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { }); - Console.WriteLine(actual); - this.logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 07aa84b50..e8cb9f8dc 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using Draco.Compiler.Api.Syntax; @@ -11,7 +12,9 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private TokenDecoration[] tokenDecorations = []; private int currentIdx; private ScopeInfo scope; + private ref TokenDecoration PreviousToken => ref this.tokenDecorations[this.currentIdx - 1]; private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; + private ref TokenDecoration NextToken => ref this.tokenDecorations[this.currentIdx + 1]; private bool firstDeclaration = true; @@ -32,7 +35,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var decorations = formatter.tokenDecorations; var stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; - List foldedScopes = new(); + List foldedScopes = []; for (var x = 0; x < decorations.Length; x++) { var curr = decorations[x]; @@ -206,7 +209,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) if (comment != null) { this.CurrentToken.TokenOverride = this.CurrentToken.Token.Text + " " + comment; - this.tokenDecorations[this.currentIdx + 1].DoesReturnLine = true; + this.NextToken.DoesReturnLine = true; } } var leadingComments = this.CurrentToken.Token.LeadingTrivia @@ -279,52 +282,58 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var i = 0; var newLineCount = 1; var shouldIndent = true; - for (; i < node.Parts.Count; i++) { var curr = node.Parts[i]; var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; + if (isNewLine) + { + newLineCount++; + shouldIndent = true; + curr.Accept(this); + continue; + } if (shouldIndent) { var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.tokenDecorations[this.currentIdx].TokenOverride = tokenText[blockCurrentIndentCount..]; + this.CurrentToken.TokenOverride = tokenText[blockCurrentIndentCount..]; MultiIndent(newLineCount); shouldIndent = false; } - if (isNewLine) - { - newLineCount++; - shouldIndent = true; - } - else - { - newLineCount = 0; - } - var startIdx = this.currentIdx; - var tokenCount = curr.Tokens.Count(); + newLineCount = 0; + var startIdx = this.currentIdx; // capture position before visiting the tokens of this parts (this will move forward the position) + + // for parts that contains expressions and have return lines. foreach (var token in curr.Tokens) { var newLines = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).ToArray(); if (newLines.Length > 0) { - this.tokenDecorations[this.currentIdx + 1].DoesReturnLine = true; + this.NextToken.DoesReturnLine = true; this.CurrentToken.TokenOverride = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); } token.Accept(this); } + // default all tokens to never return. + var tokenCount = curr.Tokens.Count(); + for (var j = 0; j < tokenCount; j++) { - ref var decoration = ref this.tokenDecorations[startIdx + j]; - decoration.DoesReturnLine ??= false; + this.tokenDecorations[startIdx + j].DoesReturnLine ??= false; } } MultiIndent(newLineCount); - this.tokenDecorations[this.currentIdx].DoesReturnLine = true; + if (this.CurrentToken.DoesReturnLine?.Value ?? false) + { + var previousId = this.PreviousNonNewLineToken(); + this.tokenDecorations[previousId].TokenOverride += this.Settings.Newline; + } + this.CurrentToken.DoesReturnLine = true; node.CloseQuotes.Accept(this); @@ -332,17 +341,29 @@ void MultiIndent(int newLineCount) { if (newLineCount > 0) { - ref var currentToken = ref this.tokenDecorations[this.currentIdx]; - currentToken.DoesReturnLine = true; + this.CurrentToken.DoesReturnLine = true; if (newLineCount > 1) { - // TODO - //currentToken.LeftPadding = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); + var previousId = this.PreviousNonNewLineToken(); + this.tokenDecorations[previousId].TokenOverride += string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); } } } } + private int PreviousNonNewLineToken() + { + var previousId = 0; + for (var i = this.currentIdx - 1; i >= 0; i--) + { + if ((this.tokenDecorations[i].Token?.Kind ?? TokenKind.StringNewline) == TokenKind.StringNewline) continue; + previousId = i; + break; + } + + return previousId; + } + public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) { DisposeAction? closeScope = null; @@ -367,7 +388,7 @@ public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax n { node.OpenBrace.Accept(this); this.CreateFoldedScope(this.Settings.Indentation, () => node.Statements.Accept(this)); - this.tokenDecorations[this.currentIdx].DoesReturnLine = true; + this.CurrentToken.DoesReturnLine = true; node.CloseBrace.Accept(this); } @@ -482,7 +503,28 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) } }); node.CloseBrace.Accept(this); - this.tokenDecorations[this.currentIdx - 1].DoesReturnLine = true; + this.PreviousToken.DoesReturnLine = true; + } + + public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) + { + DisposeAction disposable; + if (node.VisibilityModifier != null) + { + node.VisibilityModifier.Accept(this); + disposable = this.CreateFoldedScope(this.Settings.Indentation); + node.Keyword.Accept(this); + } + else + { + node.Keyword.Accept(this); + disposable = this.CreateFoldedScope(this.Settings.Indentation); + } + node.Name.Accept(this); + disposable.Dispose(); + node.Type?.Accept(this); + node.Value?.Accept(this); + node.Semicolon.Accept(this); } private DisposeAction CreateFoldedScope(string indentation) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index f4a568fc3..b4e3b0bdc 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -8,6 +8,7 @@ internal class LineStateMachine private readonly string indentation; private bool previousIsWhitespace = true; private bool prevTokenNeedRightPad = false; + private bool forceWhiteSpace = false; public LineStateMachine(string indentation) { this.sb.Append(indentation); @@ -36,12 +37,15 @@ public void AddToken(TokenDecoration decoration, FormatterSettings settings) { this.sb.Remove(0, settings.Indentation.Length); } + var shouldLeftPad = (this.prevTokenNeedRightPad || decoration.Kind.HasFlag(FormattingTokenKind.PadLeft)) && !decoration.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken) && !this.previousIsWhitespace; + shouldLeftPad |= this.forceWhiteSpace; if (shouldLeftPad) { this.previousIsWhitespace = true; + this.forceWhiteSpace = false; this.sb.Append(' '); this.LineWidth++; } @@ -50,8 +54,7 @@ public void AddToken(TokenDecoration decoration, FormatterSettings settings) this.LineWidth += text.Length; if (decoration.Kind.HasFlag(FormattingTokenKind.ForceRightPad)) { - this.sb.Append(' '); - this.LineWidth++; + this.forceWhiteSpace = true; } this.prevTokenNeedRightPad = decoration.Kind.HasFlag(FormattingTokenKind.PadRight); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs index 8bb61b2d1..6c7aac723 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs @@ -4,49 +4,9 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal struct TokenDecoration -{ - private ScopeInfo scopeInfo; - private string? tokenOverride; - private Api.Syntax.SyntaxToken token; - - public FormattingTokenKind Kind { get; set; } - - public Api.Syntax.SyntaxToken Token - { - readonly get => this.token; set - { - if (this.token != null) throw new InvalidOperationException("Token already set"); - this.token = value; - } - } - - [DisallowNull] - public string? TokenOverride - { - readonly get => this.tokenOverride; - set - { - if (this.tokenOverride != null) throw new InvalidOperationException("Override already set"); - this.tokenOverride = value; - } - } - - [DisallowNull] - public Box? DoesReturnLine { readonly get; set; } - - public ScopeInfo ScopeInfo - { - readonly get => this.scopeInfo; - set - { - if (this.scopeInfo != null) - { - throw new InvalidOperationException(); - } - this.scopeInfo = value; - } - } - - public IReadOnlyCollection LeadingComments { get; set; } -} +internal record struct TokenDecoration(FormattingTokenKind Kind, + Api.Syntax.SyntaxToken Token, + [DisallowNull] string? TokenOverride, + [DisallowNull] Box? DoesReturnLine, + ScopeInfo ScopeInfo, + IReadOnlyCollection LeadingComments); From d3961b85b300c9bc7160cd256e2cda4a38a9ae7f Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 2 May 2024 02:02:32 +0200 Subject: [PATCH 30/76] Added more edgecases, more cleanup. --- .../Syntax/ParseTreeFormatterTests.cs | 33 ++++- .../Internal/Syntax/Formatting/Formatter.cs | 115 ++++++++++-------- .../Syntax/Formatting/LineStateMachine.cs | 22 ++-- .../Internal/Syntax/Formatting/ScopeInfo.cs | 25 ++-- .../{TokenDecoration.cs => TokenMetadata.cs} | 2 +- 5 files changed, 116 insertions(+), 81 deletions(-) rename src/Draco.Compiler/Internal/Syntax/Formatting/{TokenDecoration.cs => TokenMetadata.cs} (83%) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index 6aa95114e..ea6acb626 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -4,7 +4,7 @@ namespace Draco.Compiler.Tests.Syntax; -public sealed class SyntaxTreeFormatterTests +public sealed class SyntaxTreeFormatterTests(ITestOutputHelper logger) { [Fact] public void SomeCodeSampleShouldBeFormattedCorrectly() @@ -298,7 +298,7 @@ func main() { } [Fact] - public void Sample2() + public void CursedSample() { var input = """" //test @@ -313,10 +313,13 @@ public void Sample2() var me = "P"; if // heh (me == opponent) return println("draw"); - if (me == "R") { + if ( + // heh + me == "R") { println(if (opponent == "P") "lose" else "win"); } - else if (me == "P") { + else if ( // heh + me == "P") { println(if (opponent == "R") "win" else "lose"); } else if (me == "S") { @@ -329,6 +332,28 @@ public void Sample2() var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() { }); + logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } + + [Fact] + public void CursedSample2() + { + var input = """" + func foo(a: int32) { + if ({ + var x = a * 2; + x > 50 + }) { + WriteLine("ohno"); + } + } + + """"; + var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() + { + }); + logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index e8cb9f8dc..fe85eaad3 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Linq; using System.Text; using Draco.Compiler.Api.Syntax; @@ -9,12 +8,12 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal sealed class Formatter : Api.Syntax.SyntaxVisitor { - private TokenDecoration[] tokenDecorations = []; + private TokenMetadata[] tokensMetadata = []; private int currentIdx; private ScopeInfo scope; - private ref TokenDecoration PreviousToken => ref this.tokenDecorations[this.currentIdx - 1]; - private ref TokenDecoration CurrentToken => ref this.tokenDecorations[this.currentIdx]; - private ref TokenDecoration NextToken => ref this.tokenDecorations[this.currentIdx + 1]; + private ref TokenMetadata PreviousToken => ref this.tokensMetadata[this.currentIdx - 1]; + private ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.currentIdx]; + private ref TokenMetadata NextToken => ref this.tokensMetadata[this.currentIdx + 1]; private bool firstDeclaration = true; @@ -32,13 +31,13 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) tree.Root.Accept(formatter); - var decorations = formatter.tokenDecorations; - var stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); + var metadatas = formatter.tokensMetadata; + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; List foldedScopes = []; - for (var x = 0; x < decorations.Length; x++) + for (var x = 0; x < metadatas.Length; x++) { - var curr = decorations[x]; + var curr = metadatas[x]; if (curr.DoesReturnLine?.Value ?? false) { stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); @@ -64,7 +63,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) // first rewind and fold any "as soon as possible" scopes. for (var i = x - 1; i >= currentLineStart; i--) { - var scope = decorations[i].ScopeInfo; + var scope = metadatas[i].ScopeInfo; if (scope.IsMaterialized?.Value ?? false) continue; if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; var prevFolded = scope.Fold(); @@ -73,7 +72,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) // there was no high priority scope to fold, we try to get the low priority then. for (var i = x - 1; i >= currentLineStart; i--) { - var scope = decorations[i].ScopeInfo; + var scope = metadatas[i].ScopeInfo; if (scope.IsMaterialized?.Value ?? false) continue; var prevFolded = scope.Fold(); if (prevFolded != null) goto folded; @@ -94,25 +93,25 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) } var builder = new StringBuilder(); - stateMachine = new LineStateMachine(string.Concat(decorations[0].ScopeInfo.CurrentTotalIndent)); - for (var x = 0; x < decorations.Length; x++) + stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + for (var x = 0; x < metadatas.Length; x++) { - var decoration = decorations[x]; - if (decoration.Token.Kind == TokenKind.StringNewline) continue; + var metadata = metadatas[x]; + if (metadata.Token.Kind == TokenKind.StringNewline) continue; - if (x > 0 && (decoration.DoesReturnLine?.Value ?? false)) + if (x > 0 && (metadata.DoesReturnLine?.Value ?? false)) { builder.Append(stateMachine); builder.Append(settings.Newline); - stateMachine = new LineStateMachine(string.Concat(decoration.ScopeInfo.CurrentTotalIndent)); + stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); } - if (decoration.Kind.HasFlag(FormattingTokenKind.ExtraNewline) && x > 0) + if (metadata.Kind.HasFlag(FormattingTokenKind.ExtraNewline) && x > 0) { builder.Append(settings.Newline); } - stateMachine.AddToken(decoration, settings); + stateMachine.AddToken(metadata, settings); } builder.Append(stateMachine); builder.Append(settings.Newline); @@ -133,7 +132,7 @@ private Formatter(FormatterSettings settings) public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) { - this.tokenDecorations = new TokenDecoration[node.Tokens.Count()]; + this.tokensMetadata = new TokenMetadata[node.Tokens.Count()]; base.VisitCompilationUnit(node); } @@ -171,6 +170,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) TokenKind.ParenClose => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, TokenKind.InterpolationStart => FormattingTokenKind.Whitespace, TokenKind.Dot => FormattingTokenKind.Whitespace, + TokenKind.Colon => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, TokenKind.Assign => FormattingTokenKind.PadAround, TokenKind.LineStringStart => FormattingTokenKind.PadLeft, @@ -231,7 +231,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL if (node is Api.Syntax.SeparatedSyntaxList || node is Api.Syntax.SeparatedSyntaxList) { - this.CreateFoldableScope(this.Settings.Indentation, + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitSeparatedSyntaxList(node) ); @@ -273,7 +273,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod return; } node.OpenQuotes.Accept(this); - using var _ = this.CreateFoldedScope(this.Settings.Indentation); + using var _ = this.CreateScope(this.Settings.Indentation); var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => { if (right.Kind == TriviaKind.Newline) return 0; @@ -324,14 +324,14 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod for (var j = 0; j < tokenCount; j++) { - this.tokenDecorations[startIdx + j].DoesReturnLine ??= false; + this.tokensMetadata[startIdx + j].DoesReturnLine ??= false; } } MultiIndent(newLineCount); if (this.CurrentToken.DoesReturnLine?.Value ?? false) { var previousId = this.PreviousNonNewLineToken(); - this.tokenDecorations[previousId].TokenOverride += this.Settings.Newline; + this.tokensMetadata[previousId].TokenOverride += this.Settings.Newline; } this.CurrentToken.DoesReturnLine = true; node.CloseQuotes.Accept(this); @@ -345,7 +345,7 @@ void MultiIndent(int newLineCount) if (newLineCount > 1) { var previousId = this.PreviousNonNewLineToken(); - this.tokenDecorations[previousId].TokenOverride += string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); + this.tokensMetadata[previousId].TokenOverride += string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); } } } @@ -356,7 +356,7 @@ private int PreviousNonNewLineToken() var previousId = 0; for (var i = this.currentIdx - 1; i >= 0; i--) { - if ((this.tokenDecorations[i].Token?.Kind ?? TokenKind.StringNewline) == TokenKind.StringNewline) continue; + if ((this.tokensMetadata[i].Token?.Kind ?? TokenKind.StringNewline) == TokenKind.StringNewline) continue; previousId = i; break; } @@ -370,7 +370,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod var kind = node.Operator.Kind; if (!(this.scope.Data?.Equals(kind) ?? false)) { - closeScope = this.CreateFoldableScope("", FoldPriority.AsLateAsPossible); + closeScope = this.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); this.scope.Data = kind; } node.Left.Accept(this); @@ -387,7 +387,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { node.OpenBrace.Accept(this); - this.CreateFoldedScope(this.Settings.Indentation, () => node.Statements.Accept(this)); + this.CreateScope(this.Settings.Indentation, () => node.Statements.Accept(this)); this.CurrentToken.DoesReturnLine = true; node.CloseBrace.Accept(this); } @@ -397,7 +397,7 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax var curr = this.currentIdx; node.Assign.Accept(this); - using var _ = this.CreateFoldableScope(curr, FoldPriority.AsSoonAsPossible); + using var _ = this.CreateMaterializableScope(curr, FoldPriority.AsSoonAsPossible); node.Value.Accept(this); node.Semicolon.Accept(this); } @@ -409,18 +409,18 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt if (node.VisibilityModifier != null) { node.VisibilityModifier?.Accept(this); - disposable = this.CreateFoldedScope(this.Settings.Indentation); + disposable = this.CreateScope(this.Settings.Indentation); node.FunctionKeyword.Accept(this); } else { node.FunctionKeyword.Accept(this); - disposable = this.CreateFoldedScope(this.Settings.Indentation); + disposable = this.CreateScope(this.Settings.Indentation); } node.Name.Accept(this); if (node.Generics is not null) { - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); } node.OpenParen.Accept(this); disposable.Dispose(); @@ -449,20 +449,37 @@ public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { node.WhileKeyword.Accept(this); node.OpenParen.Accept(this); - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); node.CloseParen.Accept(this); - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => - this.CreateFoldableScope("", FoldPriority.AsSoonAsPossible, () => + this.CreateMaterializableScope("", FoldPriority.AsSoonAsPossible, () => { node.IfKeyword.Accept(this); + DisposeAction? disposable = null; node.OpenParen.Accept(this); - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); + if (this.PreviousToken.DoesReturnLine?.Value ?? false) + { + // there is no reason for an OpenParen to return line except if there is a comment. + disposable = this.CreateScope(this.Settings.Indentation); + this.PreviousToken.ScopeInfo = this.scope; // it's easier to change our mind that compute ahead of time. + } + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => + { + var firstTokenIdx = this.currentIdx; + node.Condition.Accept(this); + var firstToken = this.tokensMetadata[firstTokenIdx]; + if (firstToken.DoesReturnLine?.Value ?? false) + { + firstToken.ScopeInfo.IsMaterialized.Value = true; + } + }); node.CloseParen.Accept(this); + disposable?.Dispose(); - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); node.Else?.Accept(this); }); @@ -478,7 +495,7 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; } node.ElseKeyword.Accept(this); - this.CreateFoldableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); + this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -493,7 +510,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) node.OpenBrace.Accept(this); - this.CreateFoldedScope(this.Settings.Indentation, () => + this.CreateScope(this.Settings.Indentation, () => { node.Statements.Accept(this); if (node.Value != null) @@ -512,13 +529,13 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt if (node.VisibilityModifier != null) { node.VisibilityModifier.Accept(this); - disposable = this.CreateFoldedScope(this.Settings.Indentation); + disposable = this.CreateScope(this.Settings.Indentation); node.Keyword.Accept(this); } else { node.Keyword.Accept(this); - disposable = this.CreateFoldedScope(this.Settings.Indentation); + disposable = this.CreateScope(this.Settings.Indentation); } node.Name.Accept(this); disposable.Dispose(); @@ -527,32 +544,32 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt node.Semicolon.Accept(this); } - private DisposeAction CreateFoldedScope(string indentation) + private DisposeAction CreateScope(string indentation) { this.scope = new ScopeInfo(this.scope, this.Settings, FoldPriority.Never, indentation); this.scope.IsMaterialized.Value = true; return new DisposeAction(() => this.scope = this.scope.Parent!); } - private void CreateFoldedScope(string indentation, Action action) + private void CreateScope(string indentation, Action action) { - using (this.CreateFoldedScope(indentation)) action(); + using (this.CreateScope(indentation)) action(); } - private DisposeAction CreateFoldableScope(string indentation, FoldPriority foldBehavior) + private DisposeAction CreateMaterializableScope(string indentation, FoldPriority foldBehavior) { this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, indentation); return new DisposeAction(() => this.scope = this.scope.Parent!); } - private DisposeAction CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + private DisposeAction CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, (this.tokenDecorations, indexOfLevelingToken)); + this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); return new DisposeAction(() => this.scope = this.scope.Parent!); } - private void CreateFoldableScope(string indentation, FoldPriority foldBehavior, Action action) + private void CreateMaterializableScope(string indentation, FoldPriority foldBehavior, Action action) { - using (this.CreateFoldableScope(indentation, foldBehavior)) action(); + using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index b4e3b0bdc..83497cac0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -17,15 +17,15 @@ public LineStateMachine(string indentation) } public int LineWidth { get; set; } - public void AddToken(TokenDecoration decoration, FormatterSettings settings) + public void AddToken(TokenMetadata metadata, FormatterSettings settings) { - if (decoration.LeadingComments.Count > 0) + if (metadata.LeadingComments.Count > 0) { - foreach (var comment in decoration.LeadingComments) + foreach (var comment in metadata.LeadingComments) { this.sb.Append(comment); this.LineWidth += comment.Length; - if (decoration.Token.Kind != Api.Syntax.TokenKind.EndOfInput) + if (metadata.Token.Kind != Api.Syntax.TokenKind.EndOfInput) { this.sb.Append(settings.Newline); this.sb.Append(this.indentation); @@ -33,13 +33,13 @@ public void AddToken(TokenDecoration decoration, FormatterSettings settings) } } - if (decoration.Kind.HasFlag(FormattingTokenKind.RemoveOneIndentation)) + if (metadata.Kind.HasFlag(FormattingTokenKind.RemoveOneIndentation)) { this.sb.Remove(0, settings.Indentation.Length); } - var shouldLeftPad = (this.prevTokenNeedRightPad || decoration.Kind.HasFlag(FormattingTokenKind.PadLeft)) - && !decoration.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken) + var shouldLeftPad = (this.prevTokenNeedRightPad || metadata.Kind.HasFlag(FormattingTokenKind.PadLeft)) + && !metadata.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken) && !this.previousIsWhitespace; shouldLeftPad |= this.forceWhiteSpace; if (shouldLeftPad) @@ -49,16 +49,16 @@ public void AddToken(TokenDecoration decoration, FormatterSettings settings) this.sb.Append(' '); this.LineWidth++; } - var text = decoration.TokenOverride ?? decoration.Token.Text; + var text = metadata.TokenOverride ?? metadata.Token.Text; this.sb.Append(text); this.LineWidth += text.Length; - if (decoration.Kind.HasFlag(FormattingTokenKind.ForceRightPad)) + if (metadata.Kind.HasFlag(FormattingTokenKind.ForceRightPad)) { this.forceWhiteSpace = true; } - this.prevTokenNeedRightPad = decoration.Kind.HasFlag(FormattingTokenKind.PadRight); + this.prevTokenNeedRightPad = metadata.Kind.HasFlag(FormattingTokenKind.PadRight); - this.previousIsWhitespace = decoration.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForNextToken) | decoration.Kind.HasFlag(FormattingTokenKind.ForceRightPad); + this.previousIsWhitespace = metadata.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForNextToken) | metadata.Kind.HasFlag(FormattingTokenKind.ForceRightPad); } public void Reset() diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs index 745e2447e..14bb01a42 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs @@ -7,9 +7,8 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal class ScopeInfo { - public ScopeInfo? Parent { get; } private readonly string? indentation; - private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; + private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; private readonly FormatterSettings settings; [MemberNotNullWhen(true, nameof(levelingToken))] @@ -28,12 +27,18 @@ public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority fol this.indentation = indentation; } - public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) + public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) : this(parent, settings, foldPriority) { this.levelingToken = levelingToken; } + public ScopeInfo? Parent { get; } + + /// + /// Arbitrary data that can be attached to the scope. + /// Currently only used to group similar binary expressions together. + /// public object? Data { get; set; } /// @@ -48,15 +53,12 @@ public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority fol /// /// public MutableBox IsMaterialized { get; } = new MutableBox(null, true); - private bool IsMaterializedValue => this.IsMaterialized.Value ?? false; - public TokenDecoration? TokenDecoration { get; set; } - public IEnumerable CurrentTotalIndent { get { - if (!this.IsMaterializedValue) + if (!(this.IsMaterialized.Value ?? false)) { if (this.Parent is null) return []; return this.Parent.CurrentTotalIndent; @@ -98,15 +100,6 @@ int GetStartLineTokenIndex() public FoldPriority FoldPriority { get; } - public ScopeInfo Root - { - get - { - if (this.Parent == null) return this; - return this.Parent.Root; - } - } - public IEnumerable ThisAndParents => this.Parents.Prepend(this); public IEnumerable Parents diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs similarity index 83% rename from src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs rename to src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index 6c7aac723..3f6d5a163 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenDecoration.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -4,7 +4,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal record struct TokenDecoration(FormattingTokenKind Kind, +internal record struct TokenMetadata(FormattingTokenKind Kind, Api.Syntax.SyntaxToken Token, [DisallowNull] string? TokenOverride, [DisallowNull] Box? DoesReturnLine, From e3529776b5b5772261e5f2a223a4a6ca559cb4b9 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 2 May 2024 02:05:33 +0200 Subject: [PATCH 31/76] Moar renames. --- .../Internal/Syntax/Formatting/Formatter.cs | 124 +++++++++--------- .../Syntax/Formatting/LineStateMachine.cs | 12 +- .../Formatting/{ScopeInfo.cs => Scope.cs} | 16 +-- .../Syntax/Formatting/TokenMetadata.cs | 4 +- ...tingTokenKind.cs => WhitespaceBehavior.cs} | 2 +- 5 files changed, 79 insertions(+), 79 deletions(-) rename src/Draco.Compiler/Internal/Syntax/Formatting/{ScopeInfo.cs => Scope.cs} (86%) rename src/Draco.Compiler/Internal/Syntax/Formatting/{FormattingTokenKind.cs => WhitespaceBehavior.cs} (92%) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index fe85eaad3..5556fbd34 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -10,7 +10,7 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor { private TokenMetadata[] tokensMetadata = []; private int currentIdx; - private ScopeInfo scope; + private Scope scope; private ref TokenMetadata PreviousToken => ref this.tokensMetadata[this.currentIdx - 1]; private ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.currentIdx]; private ref TokenMetadata NextToken => ref this.tokensMetadata[this.currentIdx + 1]; @@ -34,7 +34,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var metadatas = formatter.tokensMetadata; var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; - List foldedScopes = []; + List foldedScopes = []; for (var x = 0; x < metadatas.Length; x++) { var curr = metadatas[x]; @@ -106,7 +106,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) builder.Append(settings.Newline); stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); } - if (metadata.Kind.HasFlag(FormattingTokenKind.ExtraNewline) && x > 0) + if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline) && x > 0) { builder.Append(settings.Newline); } @@ -138,62 +138,62 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { - FormattingTokenKind GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch + WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { - TokenKind.KeywordAnd => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordElse => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordFor => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordGoto => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordImport => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordIn => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordInternal => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordModule => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordOr => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordReturn => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordPublic => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordVar => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordVal => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordIf => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - TokenKind.KeywordWhile => FormattingTokenKind.PadLeft | FormattingTokenKind.ForceRightPad, - - TokenKind.KeywordTrue => FormattingTokenKind.PadAround, - TokenKind.KeywordFalse => FormattingTokenKind.PadAround, - TokenKind.KeywordMod => FormattingTokenKind.PadAround, - TokenKind.KeywordRem => FormattingTokenKind.PadAround, - - TokenKind.KeywordFunc => this.currentIdx == 0 ? FormattingTokenKind.PadAround : FormattingTokenKind.ExtraNewline, - - - TokenKind.Semicolon => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, - TokenKind.CurlyOpen => FormattingTokenKind.PadLeft | FormattingTokenKind.BehaveAsWhiteSpaceForNextToken, - TokenKind.ParenOpen => FormattingTokenKind.Whitespace, - TokenKind.ParenClose => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, - TokenKind.InterpolationStart => FormattingTokenKind.Whitespace, - TokenKind.Dot => FormattingTokenKind.Whitespace, - TokenKind.Colon => FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken, - - TokenKind.Assign => FormattingTokenKind.PadAround, - TokenKind.LineStringStart => FormattingTokenKind.PadLeft, - TokenKind.MultiLineStringStart => FormattingTokenKind.PadLeft, - TokenKind.Plus => FormattingTokenKind.PadLeft, - TokenKind.Minus => FormattingTokenKind.PadLeft, - TokenKind.Star => FormattingTokenKind.PadLeft, - TokenKind.Slash => FormattingTokenKind.PadLeft, - TokenKind.PlusAssign => FormattingTokenKind.PadLeft, - TokenKind.MinusAssign => FormattingTokenKind.PadLeft, - TokenKind.StarAssign => FormattingTokenKind.PadLeft, - TokenKind.SlashAssign => FormattingTokenKind.PadLeft, - TokenKind.GreaterEqual => FormattingTokenKind.PadLeft, - TokenKind.GreaterThan => FormattingTokenKind.PadLeft, - TokenKind.LessEqual => FormattingTokenKind.PadLeft, - TokenKind.LessThan => FormattingTokenKind.PadLeft, - TokenKind.Equal => FormattingTokenKind.PadLeft, - TokenKind.LiteralFloat => FormattingTokenKind.PadLeft, - TokenKind.LiteralInteger => FormattingTokenKind.PadLeft, - - TokenKind.Identifier => FormattingTokenKind.PadLeft, - - _ => FormattingTokenKind.NoFormatting + TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + + TokenKind.KeywordTrue => WhitespaceBehavior.PadAround, + TokenKind.KeywordFalse => WhitespaceBehavior.PadAround, + TokenKind.KeywordMod => WhitespaceBehavior.PadAround, + TokenKind.KeywordRem => WhitespaceBehavior.PadAround, + + TokenKind.KeywordFunc => this.currentIdx == 0 ? WhitespaceBehavior.PadAround : WhitespaceBehavior.ExtraNewline, + + + TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + TokenKind.ParenOpen => WhitespaceBehavior.Whitespace, + TokenKind.ParenClose => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.InterpolationStart => WhitespaceBehavior.Whitespace, + TokenKind.Dot => WhitespaceBehavior.Whitespace, + TokenKind.Colon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + + TokenKind.Assign => WhitespaceBehavior.PadAround, + TokenKind.LineStringStart => WhitespaceBehavior.PadLeft, + TokenKind.MultiLineStringStart => WhitespaceBehavior.PadLeft, + TokenKind.Plus => WhitespaceBehavior.PadLeft, + TokenKind.Minus => WhitespaceBehavior.PadLeft, + TokenKind.Star => WhitespaceBehavior.PadLeft, + TokenKind.Slash => WhitespaceBehavior.PadLeft, + TokenKind.PlusAssign => WhitespaceBehavior.PadLeft, + TokenKind.MinusAssign => WhitespaceBehavior.PadLeft, + TokenKind.StarAssign => WhitespaceBehavior.PadLeft, + TokenKind.SlashAssign => WhitespaceBehavior.PadLeft, + TokenKind.GreaterEqual => WhitespaceBehavior.PadLeft, + TokenKind.GreaterThan => WhitespaceBehavior.PadLeft, + TokenKind.LessEqual => WhitespaceBehavior.PadLeft, + TokenKind.LessThan => WhitespaceBehavior.PadLeft, + TokenKind.Equal => WhitespaceBehavior.PadLeft, + TokenKind.LiteralFloat => WhitespaceBehavior.PadLeft, + TokenKind.LiteralInteger => WhitespaceBehavior.PadLeft, + + TokenKind.Identifier => WhitespaceBehavior.PadLeft, + + _ => WhitespaceBehavior.NoFormatting }; this.CurrentToken.ScopeInfo = this.scope; @@ -435,7 +435,7 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { this.CurrentToken.DoesReturnLine = true; - this.CurrentToken.Kind = FormattingTokenKind.RemoveOneIndentation; + this.CurrentToken.Kind = WhitespaceBehavior.RemoveOneIndentation; } else { @@ -546,7 +546,7 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt private DisposeAction CreateScope(string indentation) { - this.scope = new ScopeInfo(this.scope, this.Settings, FoldPriority.Never, indentation); + this.scope = new Scope(this.scope, this.Settings, FoldPriority.Never, indentation); this.scope.IsMaterialized.Value = true; return new DisposeAction(() => this.scope = this.scope.Parent!); } @@ -558,13 +558,13 @@ private void CreateScope(string indentation, Action action) private DisposeAction CreateMaterializableScope(string indentation, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, indentation); + this.scope = new Scope(this.scope, this.Settings, foldBehavior, indentation); return new DisposeAction(() => this.scope = this.scope.Parent!); } private DisposeAction CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { - this.scope = new ScopeInfo(this.scope, this.Settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); + this.scope = new Scope(this.scope, this.Settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); return new DisposeAction(() => this.scope = this.scope.Parent!); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 83497cac0..6bd26ec44 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -33,13 +33,13 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings) } } - if (metadata.Kind.HasFlag(FormattingTokenKind.RemoveOneIndentation)) + if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) { this.sb.Remove(0, settings.Indentation.Length); } - var shouldLeftPad = (this.prevTokenNeedRightPad || metadata.Kind.HasFlag(FormattingTokenKind.PadLeft)) - && !metadata.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForPreviousToken) + var shouldLeftPad = (this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft)) + && !metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken) && !this.previousIsWhitespace; shouldLeftPad |= this.forceWhiteSpace; if (shouldLeftPad) @@ -52,13 +52,13 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings) var text = metadata.TokenOverride ?? metadata.Token.Text; this.sb.Append(text); this.LineWidth += text.Length; - if (metadata.Kind.HasFlag(FormattingTokenKind.ForceRightPad)) + if (metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad)) { this.forceWhiteSpace = true; } - this.prevTokenNeedRightPad = metadata.Kind.HasFlag(FormattingTokenKind.PadRight); + this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); - this.previousIsWhitespace = metadata.Kind.HasFlag(FormattingTokenKind.BehaveAsWhiteSpaceForNextToken) | metadata.Kind.HasFlag(FormattingTokenKind.ForceRightPad); + this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken) | metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); } public void Reset() diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs similarity index 86% rename from src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs rename to src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs index 14bb01a42..9ef40d2e3 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/ScopeInfo.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class ScopeInfo +internal class Scope { private readonly string? indentation; private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; @@ -15,25 +15,25 @@ internal class ScopeInfo [MemberNotNullWhen(false, nameof(indentation))] private bool DrivenByLevelingToken => this.levelingToken.HasValue; - private ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority) + private Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority) { this.Parent = parent; this.settings = settings; this.FoldPriority = foldPriority; } - public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) + public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) { this.indentation = indentation; } - public ScopeInfo(ScopeInfo? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) + public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) : this(parent, settings, foldPriority) { this.levelingToken = levelingToken; } - public ScopeInfo? Parent { get; } + public Scope? Parent { get; } /// /// Arbitrary data that can be attached to the scope. @@ -100,9 +100,9 @@ int GetStartLineTokenIndex() public FoldPriority FoldPriority { get; } - public IEnumerable ThisAndParents => this.Parents.Prepend(this); + public IEnumerable ThisAndParents => this.Parents.Prepend(this); - public IEnumerable Parents + public IEnumerable Parents { get { @@ -115,7 +115,7 @@ public IEnumerable Parents } } - public ScopeInfo? Fold() + public Scope? Fold() { foreach (var item in this.ThisAndParents.Reverse()) { diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index 3f6d5a163..33da1a30e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -4,9 +4,9 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal record struct TokenMetadata(FormattingTokenKind Kind, +internal record struct TokenMetadata(WhitespaceBehavior Kind, Api.Syntax.SyntaxToken Token, [DisallowNull] string? TokenOverride, [DisallowNull] Box? DoesReturnLine, - ScopeInfo ScopeInfo, + Scope ScopeInfo, IReadOnlyCollection LeadingComments); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs similarity index 92% rename from src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs rename to src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs index 6103c2086..dd5157bd4 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/FormattingTokenKind.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs @@ -3,7 +3,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; [Flags] -internal enum FormattingTokenKind +internal enum WhitespaceBehavior { NoFormatting = 0, PadLeft = 1, From 2c698a7b77a5d01c4a32f0ebabb8048ad46c3305 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 2 May 2024 12:53:17 +0200 Subject: [PATCH 32/76] PR feedback. --- .../Api/Syntax/ElseClauseSyntax.cs | 6 ----- .../Api/Syntax/ParameterSyntax.cs | 24 ------------------- .../Api/Syntax/StringExpressionSyntax.cs | 12 ---------- .../Internal/Syntax/Formatting/Box.cs | 6 +---- .../Syntax/Formatting/DisposeAction.cs | 4 ++-- .../Internal/Syntax/Formatting/Formatter.cs | 7 ++---- .../Syntax/Formatting/LineStateMachine.cs | 2 +- .../Internal/Syntax/Formatting/Scope.cs | 2 +- 8 files changed, 7 insertions(+), 56 deletions(-) delete mode 100644 src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs delete mode 100644 src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs diff --git a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs index 97f015968..cbde242f8 100644 --- a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Draco.Compiler.Api.Syntax; public partial class ElseClauseSyntax { diff --git a/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs b/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs deleted file mode 100644 index 5cf32ceb3..000000000 --- a/src/Draco.Compiler/Api/Syntax/ParameterSyntax.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Metadata; -using System.Text; -using System.Threading.Tasks; - -namespace Draco.Compiler.Api.Syntax; - -public partial class ParameterSyntax -{ - public new FunctionDeclarationSyntax Parent => (FunctionDeclarationSyntax)((SyntaxNode)this).Parent!; - public int Index - { - get - { - foreach (var (parameter, i) in this.Parent.ParameterList.Values.Select((s, i) => (s, i))) - { - if (parameter == this) return i; - } - throw new InvalidOperationException(); - } - } -} diff --git a/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs deleted file mode 100644 index 4a0e88043..000000000 --- a/src/Draco.Compiler/Api/Syntax/StringExpressionSyntax.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Linq; - -namespace Draco.Compiler.Api.Syntax; -public partial class StringExpressionSyntax -{ - public int Padding => this.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => - { - if (right.Kind == TriviaKind.Newline) return 0; - return value + right.Span.Length; - }); -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs index 5b70687e1..859e4d1dd 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Draco.Compiler.Internal.Syntax.Formatting; internal class Box(T value) @@ -13,7 +9,7 @@ internal class Box(T value) public static implicit operator Box(T value) => new(value); } -internal class MutableBox(T value, bool canSetValue) : Box(value) +internal sealed class MutableBox(T value, bool canSetValue) : Box(value) { public bool CanSetValue { get; } = canSetValue; public new T Value diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs index 10371190e..f44a937a0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs @@ -1,8 +1,8 @@ -using System; +using System; namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class DisposeAction(Action action) : IDisposable +internal sealed class DisposeAction(Action action) : IDisposable { public void Dispose() => action(); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 5556fbd34..602e618b0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -274,11 +274,8 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod } node.OpenQuotes.Accept(this); using var _ = this.CreateScope(this.Settings.Indentation); - var blockCurrentIndentCount = node.CloseQuotes.LeadingTrivia.Aggregate(0, (value, right) => - { - if (right.Kind == TriviaKind.Newline) return 0; - return value + right.Span.Length; - }); + var blockCurrentIndentCount = SyntaxFacts.ComputeCutoff(node).Length; + var i = 0; var newLineCount = 1; var shouldIndent = true; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 6bd26ec44..c3588cadd 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -2,7 +2,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class LineStateMachine +internal sealed class LineStateMachine { private readonly StringBuilder sb = new(); private readonly string indentation; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs index 9ef40d2e3..7b4365a0a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class Scope +internal sealed class Scope { private readonly string? indentation; private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; From 845a6118fc21d11039460c7ee5e32ea8927f0673 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 2 May 2024 20:10:42 +0200 Subject: [PATCH 33/76] Improved code readability. --- .../Internal/Syntax/Formatting/Formatter.cs | 64 +++++++++++-------- .../Syntax/Formatting/LineStateMachine.cs | 55 ++++++++-------- 2 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 602e618b0..74a678efc 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -28,9 +28,42 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) settings ??= FormatterSettings.Default; var formatter = new Formatter(settings); - tree.Root.Accept(formatter); + var metadatas = formatter.tokensMetadata; + FoldTooLongLine(formatter, settings); + + var builder = new StringBuilder(); + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + + stateMachine.AddToken(metadatas[0], settings); + + for (var x = 1; x < metadatas.Length; x++) + { + var metadata = metadatas[x]; + // we ignore multiline string newline tokens because we handle them in the string expression visitor. + if (metadata.Token.Kind == TokenKind.StringNewline) continue; + + if (metadata.DoesReturnLine?.Value ?? false) + { + builder.Append(stateMachine); + builder.Append(settings.Newline); + stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); + } + if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline)) + { + builder.Append(settings.Newline); + } + + stateMachine.AddToken(metadata, settings); + } + builder.Append(stateMachine); + builder.Append(settings.Newline); + return builder.ToString(); + } + + private static void FoldTooLongLine(Formatter formatter, FormatterSettings settings) + { var metadatas = formatter.tokensMetadata; var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; @@ -91,31 +124,6 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) } } } - - var builder = new StringBuilder(); - stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - for (var x = 0; x < metadatas.Length; x++) - { - - var metadata = metadatas[x]; - if (metadata.Token.Kind == TokenKind.StringNewline) continue; - - if (x > 0 && (metadata.DoesReturnLine?.Value ?? false)) - { - builder.Append(stateMachine); - builder.Append(settings.Newline); - stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); - } - if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline) && x > 0) - { - builder.Append(settings.Newline); - } - - stateMachine.AddToken(metadata, settings); - } - builder.Append(stateMachine); - builder.Append(settings.Newline); - return builder.ToString(); } /// @@ -138,7 +146,7 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { - WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch + static WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, @@ -161,7 +169,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) TokenKind.KeywordMod => WhitespaceBehavior.PadAround, TokenKind.KeywordRem => WhitespaceBehavior.PadAround, - TokenKind.KeywordFunc => this.currentIdx == 0 ? WhitespaceBehavior.PadAround : WhitespaceBehavior.ExtraNewline, + TokenKind.KeywordFunc => WhitespaceBehavior.ExtraNewline, TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index c3588cadd..662d276f0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -17,48 +17,53 @@ public LineStateMachine(string indentation) } public int LineWidth { get; set; } + public void AddToken(TokenMetadata metadata, FormatterSettings settings) + { + this.HandleLeadingComments(metadata, settings); + + if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) + { + this.sb.Remove(0, settings.Indentation.Length); + this.LineWidth -= settings.Indentation.Length; + } + + var requestedLeftPad = this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft); + var haveWhitespace = (metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken) || this.previousIsWhitespace); + var shouldLeftPad = (requestedLeftPad && !haveWhitespace) || this.forceWhiteSpace; + + if (shouldLeftPad) + { + this.Append(" "); + } + this.Append(metadata.TokenOverride ?? metadata.Token.Text); + + this.forceWhiteSpace = metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); + this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); + this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken) || metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); + } + + private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings) { if (metadata.LeadingComments.Count > 0) { foreach (var comment in metadata.LeadingComments) { this.sb.Append(comment); - this.LineWidth += comment.Length; if (metadata.Token.Kind != Api.Syntax.TokenKind.EndOfInput) { this.sb.Append(settings.Newline); this.sb.Append(this.indentation); + this.LineWidth = this.indentation.Length; } } } + } - if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) - { - this.sb.Remove(0, settings.Indentation.Length); - } - - var shouldLeftPad = (this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft)) - && !metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken) - && !this.previousIsWhitespace; - shouldLeftPad |= this.forceWhiteSpace; - if (shouldLeftPad) - { - this.previousIsWhitespace = true; - this.forceWhiteSpace = false; - this.sb.Append(' '); - this.LineWidth++; - } - var text = metadata.TokenOverride ?? metadata.Token.Text; + private void Append(string text) + { this.sb.Append(text); this.LineWidth += text.Length; - if (metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad)) - { - this.forceWhiteSpace = true; - } - this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); - - this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken) | metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); } public void Reset() From 02de0ecec9734944713906bdaa8d169d4b0a410e Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 3 May 2024 02:09:31 +0200 Subject: [PATCH 34/76] Improving readability. --- .../Internal/Syntax/Formatting/Formatter.cs | 131 +++++++++--------- .../Syntax/Formatting/LineStateMachine.cs | 17 ++- .../Internal/Syntax/Formatting/Scope.cs | 4 + 3 files changed, 77 insertions(+), 75 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 74a678efc..f7a5b999b 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -15,8 +15,6 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.currentIdx]; private ref TokenMetadata NextToken => ref this.tokensMetadata[this.currentIdx + 1]; - private bool firstDeclaration = true; - /// /// Formats the given syntax tree. /// @@ -42,7 +40,6 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) { var metadata = metadatas[x]; // we ignore multiline string newline tokens because we handle them in the string expression visitor. - if (metadata.Token.Kind == TokenKind.StringNewline) continue; if (metadata.DoesReturnLine?.Value ?? false) { @@ -71,57 +68,68 @@ private static void FoldTooLongLine(Formatter formatter, FormatterSettings setti for (var x = 0; x < metadatas.Length; x++) { var curr = metadatas[x]; - if (curr.DoesReturnLine?.Value ?? false) + if (curr.DoesReturnLine?.Value ?? false) // if it's a new line { + // we recreate a state machine for the new line. stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); currentLineStart = x; foldedScopes.Clear(); } + stateMachine.AddToken(curr, settings); - if (stateMachine.LineWidth > settings.LineWidth) + + if (stateMachine.LineWidth <= settings.LineWidth) continue; + + // the line is too long... + + var folded = curr.ScopeInfo.Fold(); // folding can fail if there is nothing else to fold. + if (folded != null) { - var folded = curr.ScopeInfo.Fold(); - if (folded != null) + x = currentLineStart - 1; + foldedScopes.Add(folded); + stateMachine.Reset(); + continue; + } + + // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. + // there can be other strategy taken in the future, parametrable through settings. + + // first rewind and fold any "as soon as possible" scopes. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) { - x = currentLineStart - 1; - foldedScopes.Add(folded); - stateMachine.Reset(); + ResetBacktracking(); continue; } - else if (curr.ScopeInfo.Parent != null) + } + // there was no high priority scope to fold, we try to get the low priority then. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) { - // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. - // there can be other strategy taken in the future, parametrable through settings. - - // first rewind and fold any "as soon as possible" scopes. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) goto folded; - } - // there was no high priority scope to fold, we try to get the low priority then. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) goto folded; - } - - // we couldn't fold any scope, we just give up. + ResetBacktracking(); continue; + } + } - folded: - foreach (var scope in foldedScopes) - { - scope.IsMaterialized.Value = null; - } - foldedScopes.Clear(); - x = currentLineStart - 1; + // we couldn't fold any scope, we just give up. + + void ResetBacktracking() + { + foreach (var scope in foldedScopes) + { + scope.IsMaterialized.Value = null; } + foldedScopes.Clear(); + x = currentLineStart - 1; } } } @@ -207,6 +215,14 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.CurrentToken.ScopeInfo = this.scope; this.CurrentToken.Kind |= GetFormattingTokenKind(node); this.CurrentToken.Token = node; + this.HandleTokenComments(); + + base.VisitSyntaxToken(node); + this.currentIdx++; + } + + private void HandleTokenComments() + { var trivia = this.CurrentToken.Token.TrailingTrivia; if (trivia.Count > 0) { @@ -229,9 +245,6 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { this.CurrentToken.DoesReturnLine = true; } - - base.VisitSyntaxToken(node); - this.currentIdx++; } public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) @@ -260,8 +273,7 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) { - this.CurrentToken.DoesReturnLine = !this.firstDeclaration; - this.firstDeclaration = false; + this.CurrentToken.DoesReturnLine = this.currentIdx > 0; // don't create empty line on first line in file. } base.VisitDeclaration(node); } @@ -285,7 +297,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var blockCurrentIndentCount = SyntaxFacts.ComputeCutoff(node).Length; var i = 0; - var newLineCount = 1; var shouldIndent = true; for (; i < node.Parts.Count; i++) { @@ -294,21 +305,25 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; if (isNewLine) { - newLineCount++; shouldIndent = true; curr.Accept(this); continue; } + if (shouldIndent) { + shouldIndent = false; + var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); this.CurrentToken.TokenOverride = tokenText[blockCurrentIndentCount..]; - MultiIndent(newLineCount); - shouldIndent = false; + this.CurrentToken.DoesReturnLine = true; + if (this.PreviousToken.Token.Kind == TokenKind.StringNewline) + { + this.PreviousToken.TokenOverride = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. + } } - newLineCount = 0; var startIdx = this.currentIdx; // capture position before visiting the tokens of this parts (this will move forward the position) // for parts that contains expressions and have return lines. @@ -318,7 +333,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod if (newLines.Length > 0) { this.NextToken.DoesReturnLine = true; - this.CurrentToken.TokenOverride = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); } token.Accept(this); @@ -332,7 +346,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod this.tokensMetadata[startIdx + j].DoesReturnLine ??= false; } } - MultiIndent(newLineCount); if (this.CurrentToken.DoesReturnLine?.Value ?? false) { var previousId = this.PreviousNonNewLineToken(); @@ -340,20 +353,6 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod } this.CurrentToken.DoesReturnLine = true; node.CloseQuotes.Accept(this); - - - void MultiIndent(int newLineCount) - { - if (newLineCount > 0) - { - this.CurrentToken.DoesReturnLine = true; - if (newLineCount > 1) - { - var previousId = this.PreviousNonNewLineToken(); - this.tokensMetadata[previousId].TokenOverride += string.Concat(Enumerable.Repeat(this.Settings.Newline, newLineCount - 1)); - } - } - } } private int PreviousNonNewLineToken() diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 662d276f0..4f1e84669 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -45,17 +45,16 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings) private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings) { - if (metadata.LeadingComments.Count > 0) + if (metadata.LeadingComments.Count <= 0) return; + + foreach (var comment in metadata.LeadingComments) { - foreach (var comment in metadata.LeadingComments) + this.sb.Append(comment); + if (metadata.Token.Kind != Api.Syntax.TokenKind.EndOfInput) { - this.sb.Append(comment); - if (metadata.Token.Kind != Api.Syntax.TokenKind.EndOfInput) - { - this.sb.Append(settings.Newline); - this.sb.Append(this.indentation); - this.LineWidth = this.indentation.Length; - } + this.sb.Append(settings.Newline); + this.sb.Append(this.indentation); + this.LineWidth = this.indentation.Length; } } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs index 7b4365a0a..db825096b 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs @@ -115,6 +115,10 @@ public IEnumerable Parents } } + /// + /// Try to fold a scope by materializing a scope. + /// + /// The scope that have been fold, else if no scope can be fold. public Scope? Fold() { foreach (var item in this.ThisAndParents.Reverse()) From 1a88e83fa21dfb6669b3d726c593e26b20cdd229 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 3 May 2024 20:52:42 +0200 Subject: [PATCH 35/76] DRY FTW. --- .../Internal/Syntax/Formatting/Formatter.cs | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index f7a5b999b..df2e59f49 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -409,18 +409,7 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { this.VisitDeclaration(node); - DisposeAction disposable; - if (node.VisibilityModifier != null) - { - node.VisibilityModifier?.Accept(this); - disposable = this.CreateScope(this.Settings.Indentation); - node.FunctionKeyword.Accept(this); - } - else - { - node.FunctionKeyword.Accept(this); - disposable = this.CreateScope(this.Settings.Indentation); - } + var disposable = this.OpenScopeAtFirstToken(node.VisibilityModifier, node.FunctionKeyword); node.Name.Accept(this); if (node.Generics is not null) { @@ -529,18 +518,7 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) { - DisposeAction disposable; - if (node.VisibilityModifier != null) - { - node.VisibilityModifier.Accept(this); - disposable = this.CreateScope(this.Settings.Indentation); - node.Keyword.Accept(this); - } - else - { - node.Keyword.Accept(this); - disposable = this.CreateScope(this.Settings.Indentation); - } + var disposable = this.OpenScopeAtFirstToken(node.VisibilityModifier, node.Keyword); node.Name.Accept(this); disposable.Dispose(); node.Type?.Accept(this); @@ -548,6 +526,18 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt node.Semicolon.Accept(this); } + private DisposeAction OpenScopeAtFirstToken(Api.Syntax.SyntaxToken? optionalToken, Api.Syntax.SyntaxToken token) + { + var disposable = null as DisposeAction; + if (optionalToken != null) + { + optionalToken.Accept(this); + disposable = this.CreateScope(this.Settings.Indentation); + } + token.Accept(this); + return disposable ?? this.CreateScope(this.Settings.Indentation); + } + private DisposeAction CreateScope(string indentation) { this.scope = new Scope(this.scope, this.Settings, FoldPriority.Never, indentation); From 80ec52b8e69e496585a7115b20221e8d865b865d Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 3 May 2024 21:15:35 +0200 Subject: [PATCH 36/76] Stop exposing the token reference in the metadata. --- .../Api/Syntax/StringPartSyntax.cs | 8 ++++ .../Internal/Syntax/Formatting/Formatter.cs | 48 +++++++------------ .../Syntax/Formatting/LineStateMachine.cs | 10 ++-- .../Internal/Syntax/Formatting/Scope.cs | 4 +- .../Syntax/Formatting/TokenMetadata.cs | 3 +- 5 files changed, 33 insertions(+), 40 deletions(-) create mode 100644 src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs diff --git a/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs b/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs new file mode 100644 index 000000000..9dbcad568 --- /dev/null +++ b/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs @@ -0,0 +1,8 @@ +using System; +using System.Linq; + +namespace Draco.Compiler.Api.Syntax; +public partial class StringPartSyntax +{ + public bool IsNewLine => this.Children.Count() == 1 && this.Children.SingleOrDefault() is SyntaxToken and { Kind: TokenKind.StringNewline }; +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index df2e59f49..5ae2f02e1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -29,12 +29,13 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) tree.Root.Accept(formatter); var metadatas = formatter.tokensMetadata; - FoldTooLongLine(formatter, settings); + var tokens = tree.Root.Tokens.ToArray(); + FoldTooLongLine(formatter, settings, tokens); var builder = new StringBuilder(); var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - stateMachine.AddToken(metadatas[0], settings); + stateMachine.AddToken(metadatas[0], settings, tokens[0].Kind == TokenKind.EndOfInput); for (var x = 1; x < metadatas.Length; x++) { @@ -52,14 +53,14 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) builder.Append(settings.Newline); } - stateMachine.AddToken(metadata, settings); + stateMachine.AddToken(metadata, settings, tokens[x].Kind == TokenKind.EndOfInput); } builder.Append(stateMachine); builder.Append(settings.Newline); return builder.ToString(); } - private static void FoldTooLongLine(Formatter formatter, FormatterSettings settings) + private static void FoldTooLongLine(Formatter formatter, FormatterSettings settings, IReadOnlyList tokens) { var metadatas = formatter.tokensMetadata; var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); @@ -76,7 +77,7 @@ private static void FoldTooLongLine(Formatter formatter, FormatterSettings setti foldedScopes.Clear(); } - stateMachine.AddToken(curr, settings); + stateMachine.AddToken(curr, settings, tokens[x].Kind == TokenKind.EndOfInput); if (stateMachine.LineWidth <= settings.LineWidth) continue; @@ -214,16 +215,17 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.CurrentToken.ScopeInfo = this.scope; this.CurrentToken.Kind |= GetFormattingTokenKind(node); - this.CurrentToken.Token = node; - this.HandleTokenComments(); + //this.CurrentToken.Token = node; + this.CurrentToken.Text = node.Text; + this.HandleTokenComments(node); base.VisitSyntaxToken(node); this.currentIdx++; } - private void HandleTokenComments() + private void HandleTokenComments(Api.Syntax.SyntaxToken node) { - var trivia = this.CurrentToken.Token.TrailingTrivia; + var trivia = node.TrailingTrivia; if (trivia.Count > 0) { var comment = trivia @@ -232,11 +234,11 @@ private void HandleTokenComments() .SingleOrDefault(); if (comment != null) { - this.CurrentToken.TokenOverride = this.CurrentToken.Token.Text + " " + comment; + this.CurrentToken.TokenOverride = node.Text + " " + comment; this.NextToken.DoesReturnLine = true; } } - var leadingComments = this.CurrentToken.Token.LeadingTrivia + var leadingComments = node.LeadingTrivia .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) .Select(x => x.Text) .ToArray(); @@ -302,8 +304,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod { var curr = node.Parts[i]; - var isNewLine = curr.Children.Count() == 1 && curr.Children.SingleOrDefault() is Api.Syntax.SyntaxToken and { Kind: TokenKind.StringNewline }; - if (isNewLine) + if (curr.IsNewLine) { shouldIndent = true; curr.Accept(this); @@ -318,7 +319,8 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); this.CurrentToken.TokenOverride = tokenText[blockCurrentIndentCount..]; this.CurrentToken.DoesReturnLine = true; - if (this.PreviousToken.Token.Kind == TokenKind.StringNewline) + + if (i > 0 && node.Parts[i - 1].IsNewLine) { this.PreviousToken.TokenOverride = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. } @@ -346,28 +348,10 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod this.tokensMetadata[startIdx + j].DoesReturnLine ??= false; } } - if (this.CurrentToken.DoesReturnLine?.Value ?? false) - { - var previousId = this.PreviousNonNewLineToken(); - this.tokensMetadata[previousId].TokenOverride += this.Settings.Newline; - } this.CurrentToken.DoesReturnLine = true; node.CloseQuotes.Accept(this); } - private int PreviousNonNewLineToken() - { - var previousId = 0; - for (var i = this.currentIdx - 1; i >= 0; i--) - { - if ((this.tokensMetadata[i].Token?.Kind ?? TokenKind.StringNewline) == TokenKind.StringNewline) continue; - previousId = i; - break; - } - - return previousId; - } - public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) { DisposeAction? closeScope = null; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 4f1e84669..bfa91379a 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -18,9 +18,9 @@ public LineStateMachine(string indentation) public int LineWidth { get; set; } - public void AddToken(TokenMetadata metadata, FormatterSettings settings) + public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) { - this.HandleLeadingComments(metadata, settings); + this.HandleLeadingComments(metadata, settings, endOfInput); if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) { @@ -36,21 +36,21 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings) { this.Append(" "); } - this.Append(metadata.TokenOverride ?? metadata.Token.Text); + this.Append(metadata.TokenOverride ?? metadata.Text); this.forceWhiteSpace = metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken) || metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); } - private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings) + private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) { if (metadata.LeadingComments.Count <= 0) return; foreach (var comment in metadata.LeadingComments) { this.sb.Append(comment); - if (metadata.Token.Kind != Api.Syntax.TokenKind.EndOfInput) + if (!endOfInput) { this.sb.Append(settings.Newline); this.sb.Append(this.indentation); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs index db825096b..45bd45898 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs @@ -90,10 +90,10 @@ int GetStartLineTokenIndex() for (var i = startLine; i <= indexOfLevelingToken; i++) { var curr = this.levelingToken.Value.tokens[i]; - stateMachine.AddToken(curr, this.settings); + stateMachine.AddToken(curr, this.settings, false); } var levelingToken = this.levelingToken.Value.tokens[indexOfLevelingToken]; - return [new string(' ', stateMachine.LineWidth - levelingToken.Token.Text.Length)]; + return [new string(' ', stateMachine.LineWidth - levelingToken.Text.Length)]; } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index 33da1a30e..09cd1781d 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -5,7 +5,8 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal record struct TokenMetadata(WhitespaceBehavior Kind, - Api.Syntax.SyntaxToken Token, + //Api.Syntax.SyntaxToken Token, + string Text, [DisallowNull] string? TokenOverride, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, From 58d9fd6789c1ec49f4c5a92155831d174cd7530b Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 3 May 2024 21:32:34 +0200 Subject: [PATCH 37/76] Removed token override. --- .../Internal/Syntax/Formatting/Formatter.cs | 13 ++++++------- .../Internal/Syntax/Formatting/LineStateMachine.cs | 2 +- .../Internal/Syntax/Formatting/TokenMetadata.cs | 2 -- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs index 5ae2f02e1..08bade5a0 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs @@ -214,9 +214,8 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) }; this.CurrentToken.ScopeInfo = this.scope; - this.CurrentToken.Kind |= GetFormattingTokenKind(node); - //this.CurrentToken.Token = node; - this.CurrentToken.Text = node.Text; + this.CurrentToken.Kind |= GetFormattingTokenKind(node); // may have been set before visiting for convenience. + this.CurrentToken.Text ??= node.Text; // same this.HandleTokenComments(node); base.VisitSyntaxToken(node); @@ -234,7 +233,7 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) .SingleOrDefault(); if (comment != null) { - this.CurrentToken.TokenOverride = node.Text + " " + comment; + this.CurrentToken.Text = node.Text + " " + comment; this.NextToken.DoesReturnLine = true; } } @@ -317,12 +316,12 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.CurrentToken.TokenOverride = tokenText[blockCurrentIndentCount..]; + this.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; this.CurrentToken.DoesReturnLine = true; if (i > 0 && node.Parts[i - 1].IsNewLine) { - this.PreviousToken.TokenOverride = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. + this.PreviousToken.Text = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. } } @@ -335,7 +334,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod if (newLines.Length > 0) { this.NextToken.DoesReturnLine = true; - this.CurrentToken.TokenOverride = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); + this.CurrentToken.Text = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); } token.Accept(this); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index bfa91379a..797a13f61 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -36,7 +36,7 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en { this.Append(" "); } - this.Append(metadata.TokenOverride ?? metadata.Text); + this.Append(metadata.Text); this.forceWhiteSpace = metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index 09cd1781d..d62c0de06 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -5,9 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal record struct TokenMetadata(WhitespaceBehavior Kind, - //Api.Syntax.SyntaxToken Token, string Text, - [DisallowNull] string? TokenOverride, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, IReadOnlyCollection LeadingComments); From 56f75b9ed5186e655617fb70124ddfac77b311f8 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Fri, 3 May 2024 23:57:18 +0200 Subject: [PATCH 38/76] Factorized out formatter. --- src/Draco.Compiler/Api/Syntax/SyntaxTree.cs | 2 +- .../Syntax/Formatting/CodeFormatter.cs | 113 +++++++++++++++ .../{Formatter.cs => DracoFormatter.cs} | 134 ++---------------- .../Syntax/Formatting/TokenMetadata.cs | 2 +- 4 files changed, 130 insertions(+), 121 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs rename src/Draco.Compiler/Internal/Syntax/Formatting/{Formatter.cs => DracoFormatter.cs} (80%) diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs index 8481fa10d..89f122214 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs @@ -157,7 +157,7 @@ public ImmutableArray SyntaxTreeDiff(SyntaxTree other) => /// Syntactically formats this . /// /// The formatted tree. - public string Format(FormatterSettings? settings = null) => Formatter.Format(this, settings); + public string Format(FormatterSettings? settings = null) => DracoFormatter.Format(this, settings); /// /// The internal root of the tree. diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs new file mode 100644 index 000000000..ecb52c161 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs @@ -0,0 +1,113 @@ +using System.Collections.Generic; +using System.Text; +using Draco.Compiler.Api.Syntax; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +public static class CodeFormatter +{ + public static string Format(FormatterSettings settings, TokenMetadata[] metadatas) + { + FoldTooLongLine(metadatas, settings); + var builder = new StringBuilder(); + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + + stateMachine.AddToken(metadatas[0], settings, false); + + for (var x = 1; x < metadatas.Length; x++) + { + var metadata = metadatas[x]; + // we ignore multiline string newline tokens because we handle them in the string expression visitor. + + if (metadata.DoesReturnLine?.Value ?? false) + { + builder.Append(stateMachine); + builder.Append(settings.Newline); + stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); + } + if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline)) + { + builder.Append(settings.Newline); + } + + stateMachine.AddToken(metadata, settings, x != metadatas.Length - 1); + } + builder.Append(stateMachine); + builder.Append(settings.Newline); + return builder.ToString(); + } + + private static void FoldTooLongLine(IReadOnlyList metadatas, FormatterSettings settings) + { + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + var currentLineStart = 0; + List foldedScopes = []; + for (var x = 0; x < metadatas.Count; x++) + { + var curr = metadatas[x]; + if (curr.DoesReturnLine?.Value ?? false) // if it's a new line + { + // we recreate a state machine for the new line. + stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); + currentLineStart = x; + foldedScopes.Clear(); + } + + stateMachine.AddToken(curr, settings, false); + + if (stateMachine.LineWidth <= settings.LineWidth) continue; + + // the line is too long... + + var folded = curr.ScopeInfo.Fold(); // folding can fail if there is nothing else to fold. + if (folded != null) + { + x = currentLineStart - 1; + foldedScopes.Add(folded); + stateMachine.Reset(); + continue; + } + + // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. + // there can be other strategy taken in the future, parametrable through settings. + + // first rewind and fold any "as soon as possible" scopes. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) + { + ResetBacktracking(); + continue; + } + } + // there was no high priority scope to fold, we try to get the low priority then. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) + { + ResetBacktracking(); + continue; + } + } + + // we couldn't fold any scope, we just give up. + + void ResetBacktracking() + { + foreach (var scope in foldedScopes) + { + scope.IsMaterialized.Value = null; + } + foldedScopes.Clear(); + x = currentLineStart - 1; + } + } + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs similarity index 80% rename from src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs rename to src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 08bade5a0..4f469a97e 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Formatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using Draco.Compiler.Api.Syntax; namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class Formatter : Api.Syntax.SyntaxVisitor +internal sealed class DracoFormatter : Api.Syntax.SyntaxVisitor { private TokenMetadata[] tokensMetadata = []; private int currentIdx; @@ -15,6 +13,18 @@ internal sealed class Formatter : Api.Syntax.SyntaxVisitor private ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.currentIdx]; private ref TokenMetadata NextToken => ref this.tokensMetadata[this.currentIdx + 1]; + /// + /// The settings of the formatter. + /// + public FormatterSettings Settings { get; } + + private DracoFormatter(FormatterSettings settings) + { + this.Settings = settings; + this.scope = new(null, settings, FoldPriority.Never, ""); + this.scope.IsMaterialized.Value = true; + } + /// /// Formats the given syntax tree. /// @@ -25,126 +35,12 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) { settings ??= FormatterSettings.Default; - var formatter = new Formatter(settings); + var formatter = new DracoFormatter(settings); tree.Root.Accept(formatter); var metadatas = formatter.tokensMetadata; - var tokens = tree.Root.Tokens.ToArray(); - FoldTooLongLine(formatter, settings, tokens); - - var builder = new StringBuilder(); - var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - - stateMachine.AddToken(metadatas[0], settings, tokens[0].Kind == TokenKind.EndOfInput); - - for (var x = 1; x < metadatas.Length; x++) - { - var metadata = metadatas[x]; - // we ignore multiline string newline tokens because we handle them in the string expression visitor. - - if (metadata.DoesReturnLine?.Value ?? false) - { - builder.Append(stateMachine); - builder.Append(settings.Newline); - stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); - } - if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline)) - { - builder.Append(settings.Newline); - } - - stateMachine.AddToken(metadata, settings, tokens[x].Kind == TokenKind.EndOfInput); - } - builder.Append(stateMachine); - builder.Append(settings.Newline); - return builder.ToString(); - } - - private static void FoldTooLongLine(Formatter formatter, FormatterSettings settings, IReadOnlyList tokens) - { - var metadatas = formatter.tokensMetadata; - var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - var currentLineStart = 0; - List foldedScopes = []; - for (var x = 0; x < metadatas.Length; x++) - { - var curr = metadatas[x]; - if (curr.DoesReturnLine?.Value ?? false) // if it's a new line - { - // we recreate a state machine for the new line. - stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); - currentLineStart = x; - foldedScopes.Clear(); - } - - stateMachine.AddToken(curr, settings, tokens[x].Kind == TokenKind.EndOfInput); - - if (stateMachine.LineWidth <= settings.LineWidth) continue; - - // the line is too long... - var folded = curr.ScopeInfo.Fold(); // folding can fail if there is nothing else to fold. - if (folded != null) - { - x = currentLineStart - 1; - foldedScopes.Add(folded); - stateMachine.Reset(); - continue; - } - - // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. - // there can be other strategy taken in the future, parametrable through settings. - - // first rewind and fold any "as soon as possible" scopes. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) - { - ResetBacktracking(); - continue; - } - } - // there was no high priority scope to fold, we try to get the low priority then. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) - { - ResetBacktracking(); - continue; - } - } - - // we couldn't fold any scope, we just give up. - - void ResetBacktracking() - { - foreach (var scope in foldedScopes) - { - scope.IsMaterialized.Value = null; - } - foldedScopes.Clear(); - x = currentLineStart - 1; - } - } - } - - /// - /// The settings of the formatter. - /// - public FormatterSettings Settings { get; } - - private Formatter(FormatterSettings settings) - { - this.Settings = settings; - this.scope = new(null, settings, FoldPriority.Never, ""); - this.scope.IsMaterialized.Value = true; + return CodeFormatter.Format(settings, metadatas); } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index d62c0de06..329220f96 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -4,7 +4,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal record struct TokenMetadata(WhitespaceBehavior Kind, +public record struct TokenMetadata(WhitespaceBehavior Kind, string Text, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, From 82d72a9f2ea42b4b614c46600ba20cf5dd6e3a2a Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 4 May 2024 00:28:00 +0200 Subject: [PATCH 39/76] Make the formatter core extensible. --- .../Internal/Syntax/Formatting/Box.cs | 18 +----------------- .../Internal/Syntax/Formatting/MutableBox.cs | 10 ++++++++++ .../Internal/Syntax/Formatting/Scope.cs | 4 ++-- .../Syntax/Formatting/TokenMetadata.cs | 3 ++- .../Syntax/Formatting/WhitespaceBehavior.cs | 2 +- 5 files changed, 16 insertions(+), 21 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs index 859e4d1dd..52cef8363 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs @@ -1,24 +1,8 @@ -using System; - namespace Draco.Compiler.Internal.Syntax.Formatting; -internal class Box(T value) +public class Box(T value) { protected T value = value; public T Value => this.value; public static implicit operator Box(T value) => new(value); } - -internal sealed class MutableBox(T value, bool canSetValue) : Box(value) -{ - public bool CanSetValue { get; } = canSetValue; - public new T Value - { - get => base.Value; - set - { - if (!this.CanSetValue) throw new InvalidOperationException("Cannot set value"); - this.value = value; - } - } -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs new file mode 100644 index 000000000..9231a0922 --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs @@ -0,0 +1,10 @@ +namespace Draco.Compiler.Internal.Syntax.Formatting; + +public sealed class MutableBox(T value) : Box(value) +{ + public new T Value + { + get => base.Value; + set => this.value = value; + } +} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs index 45bd45898..de62efaa3 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class Scope +public sealed class Scope { private readonly string? indentation; private readonly (IReadOnlyList tokens, int indexOfLevelingToken)? levelingToken; @@ -52,7 +52,7 @@ public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriorit /// .ToList() /// /// - public MutableBox IsMaterialized { get; } = new MutableBox(null, true); + public MutableBox IsMaterialized { get; } = new MutableBox(null); public IEnumerable CurrentTotalIndent { diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index 329220f96..d898fa587 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -4,7 +4,8 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -public record struct TokenMetadata(WhitespaceBehavior Kind, +public record struct TokenMetadata( + WhitespaceBehavior Kind, string Text, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs index dd5157bd4..59e932a59 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs @@ -3,7 +3,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; [Flags] -internal enum WhitespaceBehavior +public enum WhitespaceBehavior { NoFormatting = 0, PadLeft = 1, From d6cde88c6ef5ba9ee15531fb7bd8d0ec4a5c26fc Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 4 May 2024 02:02:02 +0200 Subject: [PATCH 40/76] Factored out more non-draco specific code. --- .../Syntax/Formatting/CodeFormatter.cs | 6 +- .../Syntax/Formatting/DisposeAction.cs | 2 +- .../Syntax/Formatting/DracoFormatter.cs | 157 +++++++----------- .../Syntax/Formatting/FormatterEngine.cs | 63 +++++++ 4 files changed, 123 insertions(+), 105 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs index ecb52c161..c75320b1b 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs @@ -6,7 +6,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; public static class CodeFormatter { - public static string Format(FormatterSettings settings, TokenMetadata[] metadatas) + public static string Format(FormatterSettings settings, IReadOnlyList metadatas) { FoldTooLongLine(metadatas, settings); var builder = new StringBuilder(); @@ -14,7 +14,7 @@ public static string Format(FormatterSettings settings, TokenMetadata[] metadata stateMachine.AddToken(metadatas[0], settings, false); - for (var x = 1; x < metadatas.Length; x++) + for (var x = 1; x < metadatas.Count; x++) { var metadata = metadatas[x]; // we ignore multiline string newline tokens because we handle them in the string expression visitor. @@ -30,7 +30,7 @@ public static string Format(FormatterSettings settings, TokenMetadata[] metadata builder.Append(settings.Newline); } - stateMachine.AddToken(metadata, settings, x != metadatas.Length - 1); + stateMachine.AddToken(metadata, settings, x == metadatas.Count - 1); } builder.Append(stateMachine); builder.Append(settings.Newline); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs index f44a937a0..971d697e1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs @@ -2,7 +2,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class DisposeAction(Action action) : IDisposable +public sealed class DisposeAction(Action action) : IDisposable { public void Dispose() => action(); } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 4f469a97e..718f123a5 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -6,23 +6,12 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; internal sealed class DracoFormatter : Api.Syntax.SyntaxVisitor { - private TokenMetadata[] tokensMetadata = []; - private int currentIdx; - private Scope scope; - private ref TokenMetadata PreviousToken => ref this.tokensMetadata[this.currentIdx - 1]; - private ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.currentIdx]; - private ref TokenMetadata NextToken => ref this.tokensMetadata[this.currentIdx + 1]; - - /// - /// The settings of the formatter. - /// - public FormatterSettings Settings { get; } + private readonly FormatterSettings settings; + private FormatterEngine formatter = null!; private DracoFormatter(FormatterSettings settings) { - this.Settings = settings; - this.scope = new(null, settings, FoldPriority.Never, ""); - this.scope.IsMaterialized.Value = true; + this.settings = settings; } /// @@ -38,14 +27,14 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var formatter = new DracoFormatter(settings); tree.Root.Accept(formatter); - var metadatas = formatter.tokensMetadata; + var metadatas = formatter.formatter.TokensMetadata; return CodeFormatter.Format(settings, metadatas); } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) { - this.tokensMetadata = new TokenMetadata[node.Tokens.Count()]; + this.formatter = new FormatterEngine(node.Tokens.Count(), this.settings); base.VisitCompilationUnit(node); } @@ -108,14 +97,10 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) _ => WhitespaceBehavior.NoFormatting }; - - this.CurrentToken.ScopeInfo = this.scope; - this.CurrentToken.Kind |= GetFormattingTokenKind(node); // may have been set before visiting for convenience. - this.CurrentToken.Text ??= node.Text; // same this.HandleTokenComments(node); + this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); base.VisitSyntaxToken(node); - this.currentIdx++; } private void HandleTokenComments(Api.Syntax.SyntaxToken node) @@ -129,18 +114,18 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) .SingleOrDefault(); if (comment != null) { - this.CurrentToken.Text = node.Text + " " + comment; - this.NextToken.DoesReturnLine = true; + this.formatter.CurrentToken.Text = node.Text + " " + comment; + this.formatter.NextToken.DoesReturnLine = true; } } var leadingComments = node.LeadingTrivia .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) .Select(x => x.Text) .ToArray(); - this.CurrentToken.LeadingComments = leadingComments; + this.formatter.CurrentToken.LeadingComments = leadingComments; if (leadingComments.Length > 0) { - this.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = true; } } @@ -149,7 +134,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL if (node is Api.Syntax.SeparatedSyntaxList || node is Api.Syntax.SeparatedSyntaxList) { - this.CreateMaterializableScope(this.Settings.Indentation, + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitSeparatedSyntaxList(node) ); @@ -162,7 +147,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL public override void VisitParameter(Api.Syntax.ParameterSyntax node) { - this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; base.VisitParameter(node); } @@ -170,7 +155,7 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) { - this.CurrentToken.DoesReturnLine = this.currentIdx > 0; // don't create empty line on first line in file. + this.formatter.CurrentToken.DoesReturnLine = this.formatter.CurrentIdx > 0; // don't create empty line on first line in file. } base.VisitDeclaration(node); } @@ -182,15 +167,15 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod node.OpenQuotes.Accept(this); foreach (var item in node.Parts.Tokens) { - this.CurrentToken.DoesReturnLine = false; + this.formatter.CurrentToken.DoesReturnLine = false; item.Accept(this); } - this.CurrentToken.DoesReturnLine = false; + this.formatter.CurrentToken.DoesReturnLine = false; node.CloseQuotes.Accept(this); return; } node.OpenQuotes.Accept(this); - using var _ = this.CreateScope(this.Settings.Indentation); + using var _ = this.formatter.CreateScope(this.settings.Indentation); var blockCurrentIndentCount = SyntaxFacts.ComputeCutoff(node).Length; var i = 0; @@ -212,16 +197,16 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; - this.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; + this.formatter.CurrentToken.DoesReturnLine = true; if (i > 0 && node.Parts[i - 1].IsNewLine) { - this.PreviousToken.Text = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. + this.formatter.PreviousToken.Text = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. } } - var startIdx = this.currentIdx; // capture position before visiting the tokens of this parts (this will move forward the position) + var startIdx = this.formatter.CurrentIdx; // capture position before visiting the tokens of this parts (this will move forward the position) // for parts that contains expressions and have return lines. foreach (var token in curr.Tokens) @@ -229,8 +214,8 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var newLines = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).ToArray(); if (newLines.Length > 0) { - this.NextToken.DoesReturnLine = true; - this.CurrentToken.Text = string.Concat(Enumerable.Repeat(this.Settings.Newline, newLines.Length - 1).Prepend(token.Text)); + this.formatter.NextToken.DoesReturnLine = true; + this.formatter.CurrentToken.Text = string.Concat(Enumerable.Repeat(this.settings.Newline, newLines.Length - 1).Prepend(token.Text)); } token.Accept(this); } @@ -240,10 +225,10 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod for (var j = 0; j < tokenCount; j++) { - this.tokensMetadata[startIdx + j].DoesReturnLine ??= false; + this.formatter.TokensMetadata[startIdx + j].DoesReturnLine ??= false; } } - this.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = true; node.CloseQuotes.Accept(this); } @@ -251,16 +236,16 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod { DisposeAction? closeScope = null; var kind = node.Operator.Kind; - if (!(this.scope.Data?.Equals(kind) ?? false)) + if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) { - closeScope = this.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); - this.scope.Data = kind; + closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); + this.formatter.Scope.Data = kind; } node.Left.Accept(this); - if (this.CurrentToken.DoesReturnLine is null) + if (this.formatter.CurrentToken.DoesReturnLine is null) { - this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; } node.Operator.Accept(this); node.Right.Accept(this); @@ -270,17 +255,17 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax node) { node.OpenBrace.Accept(this); - this.CreateScope(this.Settings.Indentation, () => node.Statements.Accept(this)); - this.CurrentToken.DoesReturnLine = true; + this.formatter.CreateScope(this.settings.Indentation, () => node.Statements.Accept(this)); + this.formatter.CurrentToken.DoesReturnLine = true; node.CloseBrace.Accept(this); } public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax node) { - var curr = this.currentIdx; + var curr = this.formatter.CurrentIdx; node.Assign.Accept(this); - using var _ = this.CreateMaterializableScope(curr, FoldPriority.AsSoonAsPossible); + using var _ = this.formatter.CreateMaterializableScope(curr, FoldPriority.AsSoonAsPossible); node.Value.Accept(this); node.Semicolon.Accept(this); } @@ -292,7 +277,7 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt node.Name.Accept(this); if (node.Generics is not null) { - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); } node.OpenParen.Accept(this); disposable.Dispose(); @@ -306,13 +291,13 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) { if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { - this.CurrentToken.DoesReturnLine = true; - this.CurrentToken.Kind = WhitespaceBehavior.RemoveOneIndentation; + this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.Kind = WhitespaceBehavior.RemoveOneIndentation; } else { var shouldBeMultiLine = node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax; - this.CurrentToken.DoesReturnLine = shouldBeMultiLine; + this.formatter.CurrentToken.DoesReturnLine = shouldBeMultiLine; } base.VisitStatement(node); } @@ -321,28 +306,28 @@ public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { node.WhileKeyword.Accept(this); node.OpenParen.Accept(this); - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); node.CloseParen.Accept(this); - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => - this.CreateMaterializableScope("", FoldPriority.AsSoonAsPossible, () => + this.formatter.CreateMaterializableScope("", FoldPriority.AsSoonAsPossible, () => { node.IfKeyword.Accept(this); DisposeAction? disposable = null; node.OpenParen.Accept(this); - if (this.PreviousToken.DoesReturnLine?.Value ?? false) + if (this.formatter.PreviousToken.DoesReturnLine?.Value ?? false) { // there is no reason for an OpenParen to return line except if there is a comment. - disposable = this.CreateScope(this.Settings.Indentation); - this.PreviousToken.ScopeInfo = this.scope; // it's easier to change our mind that compute ahead of time. + disposable = this.formatter.CreateScope(this.settings.Indentation); + this.formatter.PreviousToken.ScopeInfo = this.formatter.Scope; // it's easier to change our mind that compute ahead of time. } - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => { - var firstTokenIdx = this.currentIdx; + var firstTokenIdx = this.formatter.CurrentIdx; node.Condition.Accept(this); - var firstToken = this.tokensMetadata[firstTokenIdx]; + var firstToken = this.formatter.TokensMetadata[firstTokenIdx]; if (firstToken.DoesReturnLine?.Value ?? false) { firstToken.ScopeInfo.IsMaterialized.Value = true; @@ -351,7 +336,7 @@ public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => node.CloseParen.Accept(this); disposable?.Dispose(); - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); node.Else?.Accept(this); }); @@ -360,14 +345,14 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { if (node.IsElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = true; } else { - this.CurrentToken.DoesReturnLine = this.scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; } node.ElseKeyword.Accept(this); - this.CreateMaterializableScope(this.Settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); + this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -378,21 +363,21 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // if (blabla) // an expression; // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - this.scope.IsMaterialized.Value ??= false; + this.formatter.Scope.IsMaterialized.Value ??= false; node.OpenBrace.Accept(this); - this.CreateScope(this.Settings.Indentation, () => + this.formatter.CreateScope(this.settings.Indentation, () => { node.Statements.Accept(this); if (node.Value != null) { - this.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = true; node.Value.Accept(this); } }); node.CloseBrace.Accept(this); - this.PreviousToken.DoesReturnLine = true; + this.formatter.PreviousToken.DoesReturnLine = true; } public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) @@ -404,45 +389,15 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt node.Value?.Accept(this); node.Semicolon.Accept(this); } - private DisposeAction OpenScopeAtFirstToken(Api.Syntax.SyntaxToken? optionalToken, Api.Syntax.SyntaxToken token) { var disposable = null as DisposeAction; if (optionalToken != null) { optionalToken.Accept(this); - disposable = this.CreateScope(this.Settings.Indentation); + disposable = this.formatter.CreateScope(this.settings.Indentation); } token.Accept(this); - return disposable ?? this.CreateScope(this.Settings.Indentation); - } - - private DisposeAction CreateScope(string indentation) - { - this.scope = new Scope(this.scope, this.Settings, FoldPriority.Never, indentation); - this.scope.IsMaterialized.Value = true; - return new DisposeAction(() => this.scope = this.scope.Parent!); - } - - private void CreateScope(string indentation, Action action) - { - using (this.CreateScope(indentation)) action(); - } - - private DisposeAction CreateMaterializableScope(string indentation, FoldPriority foldBehavior) - { - this.scope = new Scope(this.scope, this.Settings, foldBehavior, indentation); - return new DisposeAction(() => this.scope = this.scope.Parent!); - } - - private DisposeAction CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) - { - this.scope = new Scope(this.scope, this.Settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); - return new DisposeAction(() => this.scope = this.scope.Parent!); - } - - private void CreateMaterializableScope(string indentation, FoldPriority foldBehavior, Action action) - { - using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); + return disposable ?? this.formatter.CreateScope(this.settings.Indentation); } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs new file mode 100644 index 000000000..eb84b656e --- /dev/null +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs @@ -0,0 +1,63 @@ +using System; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +public sealed class FormatterEngine +{ + private readonly TokenMetadata[] tokensMetadata; + private readonly FormatterSettings settings; + + public FormatterEngine(int tokenCount, FormatterSettings settings) + { + this.tokensMetadata = new TokenMetadata[tokenCount]; + this.Scope = new(null, settings, FoldPriority.Never, ""); + this.Scope.IsMaterialized.Value = true; + this.settings = settings; + } + + public int CurrentIdx { get; private set; } + public Scope Scope { get; private set; } + + public TokenMetadata[] TokensMetadata => this.tokensMetadata; + + public ref TokenMetadata PreviousToken => ref this.tokensMetadata[this.CurrentIdx - 1]; + public ref TokenMetadata CurrentToken => ref this.tokensMetadata[this.CurrentIdx]; + public ref TokenMetadata NextToken => ref this.tokensMetadata[this.CurrentIdx + 1]; + + public void SetCurrentTokenInfo(WhitespaceBehavior kind, string text) + { + this.CurrentToken.ScopeInfo = this.Scope; + this.CurrentToken.Kind |= kind; // may have been set before visiting for convenience. + this.CurrentToken.Text ??= text; // same + this.CurrentIdx++; + } + + public DisposeAction CreateScope(string indentation) + { + this.Scope = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); + this.Scope.IsMaterialized.Value = true; + return new DisposeAction(() => this.Scope = this.Scope.Parent!); + } + + public void CreateScope(string indentation, Action action) + { + using (this.CreateScope(indentation)) action(); + } + + public DisposeAction CreateMaterializableScope(string indentation, FoldPriority foldBehavior) + { + this.Scope = new Scope(this.Scope, this.settings, foldBehavior, indentation); + return new DisposeAction(() => this.Scope = this.Scope.Parent!); + } + + public DisposeAction CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + { + this.Scope = new Scope(this.Scope, this.settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); + return new DisposeAction(() => this.Scope = this.Scope.Parent!); + } + + public void CreateMaterializableScope(string indentation, FoldPriority foldBehavior, Action action) + { + using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); + } +} From 286ba343889750078b5e69586c439e9b8554637c Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 4 May 2024 02:15:01 +0200 Subject: [PATCH 41/76] nullfix --- .../Internal/Syntax/Formatting/LineStateMachine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 797a13f61..946c38148 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -45,7 +45,7 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) { - if (metadata.LeadingComments.Count <= 0) return; + if (metadata.LeadingComments == null) return; foreach (var comment in metadata.LeadingComments) { From 7f1f1ab4a81880cc691183e080e18e5e9641d35f Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 4 May 2024 16:10:20 +0200 Subject: [PATCH 42/76] More code goes away ~ --- .../Syntax/Formatting/DisposeAction.cs | 8 ---- .../Syntax/Formatting/DracoFormatter.cs | 23 ++++------- .../Syntax/Formatting/FormatterEngine.cs | 41 +++++++++++++++---- 3 files changed, 42 insertions(+), 30 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs deleted file mode 100644 index 971d697e1..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DisposeAction.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Draco.Compiler.Internal.Syntax.Formatting; - -public sealed class DisposeAction(Action action) : IDisposable -{ - public void Dispose() => action(); -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 718f123a5..6b0f79fbd 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -234,7 +234,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) { - DisposeAction? closeScope = null; + IDisposable? closeScope = null; var kind = node.Operator.Kind; if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) { @@ -273,7 +273,9 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { this.VisitDeclaration(node); - var disposable = this.OpenScopeAtFirstToken(node.VisibilityModifier, node.FunctionKeyword); + var disposable = this.formatter.CreateScopeAfterNextToken(this.settings.Indentation); + node.VisibilityModifier?.Accept(this); + node.FunctionKeyword.Accept(this); node.Name.Accept(this); if (node.Generics is not null) { @@ -315,7 +317,7 @@ public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => this.formatter.CreateMaterializableScope("", FoldPriority.AsSoonAsPossible, () => { node.IfKeyword.Accept(this); - DisposeAction? disposable = null; + IDisposable? disposable = null; node.OpenParen.Accept(this); if (this.formatter.PreviousToken.DoesReturnLine?.Value ?? false) { @@ -382,22 +384,13 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) { - var disposable = this.OpenScopeAtFirstToken(node.VisibilityModifier, node.Keyword); + var disposable = this.formatter.CreateScopeAfterNextToken(this.settings.Indentation); + node.VisibilityModifier?.Accept(this); + node.Keyword.Accept(this); node.Name.Accept(this); disposable.Dispose(); node.Type?.Accept(this); node.Value?.Accept(this); node.Semicolon.Accept(this); } - private DisposeAction OpenScopeAtFirstToken(Api.Syntax.SyntaxToken? optionalToken, Api.Syntax.SyntaxToken token) - { - var disposable = null as DisposeAction; - if (optionalToken != null) - { - optionalToken.Accept(this); - disposable = this.formatter.CreateScope(this.settings.Indentation); - } - token.Accept(this); - return disposable ?? this.formatter.CreateScope(this.settings.Indentation); - } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs index eb84b656e..e05dc91d1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs @@ -1,23 +1,32 @@ -using System; +using System; namespace Draco.Compiler.Internal.Syntax.Formatting; public sealed class FormatterEngine { + private readonly Disposable scopePopper; private readonly TokenMetadata[] tokensMetadata; private readonly FormatterSettings settings; public FormatterEngine(int tokenCount, FormatterSettings settings) { + this.scopePopper = new Disposable(this); this.tokensMetadata = new TokenMetadata[tokenCount]; this.Scope = new(null, settings, FoldPriority.Never, ""); this.Scope.IsMaterialized.Value = true; this.settings = settings; } + private class Disposable(FormatterEngine formatter) : IDisposable + { + public void Dispose() => formatter.PopScope(); + } + public int CurrentIdx { get; private set; } public Scope Scope { get; private set; } + private Scope? scopeForNextToken; + public TokenMetadata[] TokensMetadata => this.tokensMetadata; public ref TokenMetadata PreviousToken => ref this.tokensMetadata[this.CurrentIdx - 1]; @@ -30,13 +39,22 @@ public void SetCurrentTokenInfo(WhitespaceBehavior kind, string text) this.CurrentToken.Kind |= kind; // may have been set before visiting for convenience. this.CurrentToken.Text ??= text; // same this.CurrentIdx++; + if (this.scopeForNextToken != null) + { + this.Scope = this.scopeForNextToken; + this.scopeForNextToken = null; + } } - public DisposeAction CreateScope(string indentation) + + + private void PopScope() => this.Scope = this.Scope.Parent!; + + public IDisposable CreateScope(string indentation) { this.Scope = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); this.Scope.IsMaterialized.Value = true; - return new DisposeAction(() => this.Scope = this.Scope.Parent!); + return this.scopePopper; } public void CreateScope(string indentation, Action action) @@ -44,20 +62,29 @@ public void CreateScope(string indentation, Action action) using (this.CreateScope(indentation)) action(); } - public DisposeAction CreateMaterializableScope(string indentation, FoldPriority foldBehavior) + public IDisposable CreateScopeAfterNextToken(string indentation) + { + this.scopeForNextToken = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); + this.scopeForNextToken.IsMaterialized.Value = true; + return this.scopePopper; + } + + + public IDisposable CreateMaterializableScope(string indentation, FoldPriority foldBehavior) { this.Scope = new Scope(this.Scope, this.settings, foldBehavior, indentation); - return new DisposeAction(() => this.Scope = this.Scope.Parent!); + return this.scopePopper; } - public DisposeAction CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + public IDisposable CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { this.Scope = new Scope(this.Scope, this.settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); - return new DisposeAction(() => this.Scope = this.Scope.Parent!); + return this.scopePopper; } public void CreateMaterializableScope(string indentation, FoldPriority foldBehavior, Action action) { using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); } + } From de3ad182b2c223937250c1d61e5206c62a0963c5 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 09:07:43 +0200 Subject: [PATCH 43/76] Some regression. --- .../Syntax/Formatting/CodeFormatter.cs | 113 ------------------ .../Syntax/Formatting/DracoFormatter.cs | 28 +++-- .../Syntax/Formatting/FormatterEngine.cs | 113 +++++++++++++++++- .../Syntax/Formatting/LineStateMachine.cs | 4 +- .../Syntax/Formatting/TokenMetadata.cs | 2 +- .../Syntax/Formatting/WhitespaceBehavior.cs | 1 - 6 files changed, 132 insertions(+), 129 deletions(-) delete mode 100644 src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs deleted file mode 100644 index c75320b1b..000000000 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/CodeFormatter.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Generic; -using System.Text; -using Draco.Compiler.Api.Syntax; - -namespace Draco.Compiler.Internal.Syntax.Formatting; - -public static class CodeFormatter -{ - public static string Format(FormatterSettings settings, IReadOnlyList metadatas) - { - FoldTooLongLine(metadatas, settings); - var builder = new StringBuilder(); - var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - - stateMachine.AddToken(metadatas[0], settings, false); - - for (var x = 1; x < metadatas.Count; x++) - { - var metadata = metadatas[x]; - // we ignore multiline string newline tokens because we handle them in the string expression visitor. - - if (metadata.DoesReturnLine?.Value ?? false) - { - builder.Append(stateMachine); - builder.Append(settings.Newline); - stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); - } - if (metadata.Kind.HasFlag(WhitespaceBehavior.ExtraNewline)) - { - builder.Append(settings.Newline); - } - - stateMachine.AddToken(metadata, settings, x == metadatas.Count - 1); - } - builder.Append(stateMachine); - builder.Append(settings.Newline); - return builder.ToString(); - } - - private static void FoldTooLongLine(IReadOnlyList metadatas, FormatterSettings settings) - { - var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); - var currentLineStart = 0; - List foldedScopes = []; - for (var x = 0; x < metadatas.Count; x++) - { - var curr = metadatas[x]; - if (curr.DoesReturnLine?.Value ?? false) // if it's a new line - { - // we recreate a state machine for the new line. - stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); - currentLineStart = x; - foldedScopes.Clear(); - } - - stateMachine.AddToken(curr, settings, false); - - if (stateMachine.LineWidth <= settings.LineWidth) continue; - - // the line is too long... - - var folded = curr.ScopeInfo.Fold(); // folding can fail if there is nothing else to fold. - if (folded != null) - { - x = currentLineStart - 1; - foldedScopes.Add(folded); - stateMachine.Reset(); - continue; - } - - // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. - // there can be other strategy taken in the future, parametrable through settings. - - // first rewind and fold any "as soon as possible" scopes. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) - { - ResetBacktracking(); - continue; - } - } - // there was no high priority scope to fold, we try to get the low priority then. - for (var i = x - 1; i >= currentLineStart; i--) - { - var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; - var prevFolded = scope.Fold(); - if (prevFolded != null) - { - ResetBacktracking(); - continue; - } - } - - // we couldn't fold any scope, we just give up. - - void ResetBacktracking() - { - foreach (var scope in foldedScopes) - { - scope.IsMaterialized.Value = null; - } - foldedScopes.Clear(); - x = currentLineStart - 1; - } - } - } -} diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 6b0f79fbd..f7fdafab6 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -29,7 +29,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var metadatas = formatter.formatter.TokensMetadata; - return CodeFormatter.Format(settings, metadatas); + return FormatterEngine.Format(settings, metadatas); } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) @@ -63,7 +63,7 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) TokenKind.KeywordMod => WhitespaceBehavior.PadAround, TokenKind.KeywordRem => WhitespaceBehavior.PadAround, - TokenKind.KeywordFunc => WhitespaceBehavior.ExtraNewline, + TokenKind.KeywordFunc => WhitespaceBehavior.PadAround, TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, @@ -122,8 +122,9 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) .Where(x => x.Kind == TriviaKind.LineComment || x.Kind == TriviaKind.DocumentationComment) .Select(x => x.Text) .ToArray(); - this.formatter.CurrentToken.LeadingComments = leadingComments; - if (leadingComments.Length > 0) + this.formatter.CurrentToken.LeadingTrivia ??= []; + this.formatter.CurrentToken.LeadingTrivia.AddRange(leadingComments); + if (this.formatter.CurrentToken.LeadingTrivia.Count > 0) { this.formatter.CurrentToken.DoesReturnLine = true; } @@ -153,10 +154,17 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { - if (node.Parent is not Api.Syntax.DeclarationStatementSyntax) + this.formatter.CurrentToken.DoesReturnLine = true; + var type = node.GetType(); + this.formatter.OnDifferent(node switch { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.CurrentIdx > 0; // don't create empty line on first line in file. - } + Api.Syntax.FunctionDeclarationSyntax _ => node, // always different, that what we want. + _ => type, + }, previous => + { + if (previous == null) return; + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + }); base.VisitDeclaration(node); } @@ -236,11 +244,11 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod { IDisposable? closeScope = null; var kind = node.Operator.Kind; - if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) + this.formatter.OnDifferent(kind, _ => { closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); - this.formatter.Scope.Data = kind; - } + }); + node.Left.Accept(this); if (this.formatter.CurrentToken.DoesReturnLine is null) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs index e05dc91d1..12c6ebf87 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Text; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -46,8 +48,6 @@ public void SetCurrentTokenInfo(WhitespaceBehavior kind, string text) } } - - private void PopScope() => this.Scope = this.Scope.Parent!; public IDisposable CreateScope(string indentation) @@ -87,4 +87,113 @@ public void CreateMaterializableScope(string indentation, FoldPriority foldBehav using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); } + public void OnDifferent(object? newData, Action onChange) + { + if (!(newData?.Equals(this.Scope.Data) ?? (this.Scope.Data == null))) + { + onChange(this.Scope.Data); + this.Scope.Data = newData; + } + } + + public static string Format(FormatterSettings settings, IReadOnlyList metadatas) + { + FoldTooLongLine(metadatas, settings); + var builder = new StringBuilder(); + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + + stateMachine.AddToken(metadatas[0], settings, false); + + for (var x = 1; x < metadatas.Count; x++) + { + var metadata = metadatas[x]; + // we ignore multiline string newline tokens because we handle them in the string expression visitor. + + if (metadata.DoesReturnLine?.Value ?? false) + { + builder.Append(stateMachine); + builder.Append(settings.Newline); + stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); + } + + stateMachine.AddToken(metadata, settings, x == metadatas.Count - 1); + } + builder.Append(stateMachine); + builder.Append(settings.Newline); + return builder.ToString(); + } + + private static void FoldTooLongLine(IReadOnlyList metadatas, FormatterSettings settings) + { + var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + var currentLineStart = 0; + List foldedScopes = []; + for (var x = 0; x < metadatas.Count; x++) + { + var curr = metadatas[x]; + if (curr.DoesReturnLine?.Value ?? false) // if it's a new line + { + // we recreate a state machine for the new line. + stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); + currentLineStart = x; + foldedScopes.Clear(); + } + + stateMachine.AddToken(curr, settings, false); + + if (stateMachine.LineWidth <= settings.LineWidth) continue; + + // the line is too long... + + var folded = curr.ScopeInfo.Fold(); // folding can fail if there is nothing else to fold. + if (folded != null) + { + x = currentLineStart - 1; + foldedScopes.Add(folded); + stateMachine.Reset(); + continue; + } + + // we can't fold the current scope anymore, so we revert our folding, and we fold the previous scopes on the line. + // there can be other strategy taken in the future, parametrable through settings. + + // first rewind and fold any "as soon as possible" scopes. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) + { + ResetBacktracking(); + continue; + } + } + // there was no high priority scope to fold, we try to get the low priority then. + for (var i = x - 1; i >= currentLineStart; i--) + { + var scope = metadatas[i].ScopeInfo; + if (scope.IsMaterialized?.Value ?? false) continue; + var prevFolded = scope.Fold(); + if (prevFolded != null) + { + ResetBacktracking(); + continue; + } + } + + // we couldn't fold any scope, we just give up. + + void ResetBacktracking() + { + foreach (var scope in foldedScopes) + { + scope.IsMaterialized.Value = null; + } + foldedScopes.Clear(); + x = currentLineStart - 1; + } + } + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs index 946c38148..555bc05f5 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs @@ -45,9 +45,9 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) { - if (metadata.LeadingComments == null) return; + if (metadata.LeadingTrivia == null) return; - foreach (var comment in metadata.LeadingComments) + foreach (var comment in metadata.LeadingTrivia) { this.sb.Append(comment); if (!endOfInput) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs index d898fa587..493437aea 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs @@ -9,4 +9,4 @@ public record struct TokenMetadata( string Text, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, - IReadOnlyCollection LeadingComments); + List LeadingTrivia); diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs index 59e932a59..63dd92493 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs @@ -11,7 +11,6 @@ public enum WhitespaceBehavior ForceRightPad = 1 << 2, BehaveAsWhiteSpaceForNextToken = 1 << 3, BehaveAsWhiteSpaceForPreviousToken = 1 << 4, - ExtraNewline = 1 << 5, RemoveOneIndentation = 1 << 6, PadAround = PadLeft | PadRight, Whitespace = BehaveAsWhiteSpaceForNextToken | BehaveAsWhiteSpaceForPreviousToken, From 61262f029585957ce916000ea4f7f68d5213dee0 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 09:11:18 +0200 Subject: [PATCH 44/76] a c# formatter. --- .../Draco.Formatter.CSharp.Tests.csproj | 33 +++ .../FormatterTest.cs | 36 ++++ src/Draco.Formatter.Csharp/CSharpFormatter.cs | 200 ++++++++++++++++++ .../Draco.Formatter.Csharp.csproj | 8 + src/Draco.sln | 18 +- 5 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj create mode 100644 src/Draco.Formatter.CSharp.Tests/FormatterTest.cs create mode 100644 src/Draco.Formatter.Csharp/CSharpFormatter.cs create mode 100644 src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj diff --git a/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj b/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj new file mode 100644 index 000000000..98d6079fb --- /dev/null +++ b/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj @@ -0,0 +1,33 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs new file mode 100644 index 000000000..514a30cd9 --- /dev/null +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -0,0 +1,36 @@ +using Draco.Formatter.Csharp; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using Xunit.Abstractions; +namespace Draco.Formatter.CSharp.Test; +public sealed class FormatterTest(ITestOutputHelper logger) +{ + [Fact] + public void SomeCodeSampleShouldBeFormattedCorrectly() + { + var input = """" + class Program + { + public static void Main() + { + Console.WriteLine("Hello, World!"); + } + } + + """"; + var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); + var formatted = CSharpFormatter.Format(tree); + logger.WriteLine(formatted); + Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); + } + + [Fact] + public void ThisFileShouldBeFormattedCorrectly() + { + var input = File.ReadAllText("../../../../Draco.Formatter.Csharp.Tests/FormatterTest.cs"); + var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); + var formatted = CSharpFormatter.Format(tree); + logger.WriteLine(formatted); + Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); + } +} diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs new file mode 100644 index 000000000..dd00a838d --- /dev/null +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -0,0 +1,200 @@ +using System.Linq; +using Draco.Compiler.Internal.Syntax.Formatting; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Draco.Formatter.Csharp; + +public class CSharpFormatter(FormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) +{ + private readonly FormatterSettings settings = settings; + private FormatterEngine formatter = null!; + + public static string Format(SyntaxTree tree, FormatterSettings? settings = null) + { + settings ??= FormatterSettings.Default; + + var formatter = new CSharpFormatter(settings); + formatter.Visit(tree.GetRoot()); + + var metadatas = formatter.formatter.TokensMetadata; + + return FormatterEngine.Format(settings, metadatas); + } + + public override void VisitCompilationUnit(CompilationUnitSyntax node) + { + this.formatter = new FormatterEngine(node.DescendantTokens().Count(), this.settings); + base.VisitCompilationUnit(node); + } + + public override void VisitToken(SyntaxToken node) + { + if(node.IsKind(SyntaxKind.None)) return; + + static WhitespaceBehavior GetFormattingTokenKind(SyntaxToken token) => token.Kind() switch + { + SyntaxKind.AndKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ElseKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ForKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.GotoKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.UsingDirective => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.InKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.InternalKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.OrKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.PublicKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.VarKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.IfKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.WhileKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.StaticKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.SealedKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + + SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, + + SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + SyntaxKind.OpenBraceToken => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + SyntaxKind.OpenParenToken => WhitespaceBehavior.Whitespace, + SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, + SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, + SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, + + SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, + SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, + SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, + SyntaxKind.PlusToken => WhitespaceBehavior.PadLeft, + SyntaxKind.MinusToken => WhitespaceBehavior.PadLeft, + SyntaxKind.AsteriskToken => WhitespaceBehavior.PadLeft, + SyntaxKind.SlashToken => WhitespaceBehavior.PadLeft, + SyntaxKind.PlusEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.MinusEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.SlashEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.GreaterThanToken => WhitespaceBehavior.PadLeft, + SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.LessThanToken => WhitespaceBehavior.PadLeft, + SyntaxKind.NumericLiteralToken => WhitespaceBehavior.PadLeft, + + SyntaxKind.IdentifierToken => WhitespaceBehavior.PadLeft, + + _ => WhitespaceBehavior.NoFormatting + }; + + base.VisitToken(node); + this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); + } + + public override void VisitClassDeclaration(ClassDeclarationSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + foreach (var attribute in node.AttributeLists) + { + attribute.Accept(this); + } + + foreach (var modifier in node.Modifiers) + { + this.VisitToken(modifier); + } + + this.VisitToken(node.Keyword); + this.VisitToken(node.Identifier); + node.TypeParameterList?.Accept(this); + node.ParameterList?.Accept(this); + node.BaseList?.Accept(this); + + foreach (var constraint in node.ConstraintClauses) + { + constraint.Accept(this); + } + + this.formatter.CurrentToken.DoesReturnLine = true; + this.VisitToken(node.OpenBraceToken); + this.formatter.CreateScope(this.settings.Indentation, () => + { + foreach (var member in node.Members) + { + this.formatter.CurrentToken.DoesReturnLine = true; + member.Accept(this); + } + }); + this.formatter.CurrentToken.DoesReturnLine = true; + this.VisitToken(node.CloseBraceToken); + this.VisitToken(node.SemicolonToken); + } + + public override void VisitBlock(BlockSyntax node) + { + foreach (var attribute in node.AttributeLists) + { + attribute.Accept(this); + } + this.formatter.CurrentToken.DoesReturnLine = true; + this.VisitToken(node.OpenBraceToken); + this.formatter.CreateScope(this.settings.Indentation, () => + { + foreach (var statement in node.Statements) + { + this.formatter.CurrentToken.DoesReturnLine = true; + statement.Accept(this); + } + }); + this.formatter.CurrentToken.DoesReturnLine = true; + this.VisitToken(node.CloseBraceToken); + } + + public override void VisitUsingDirective(UsingDirectiveSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + base.VisitUsingDirective(node); + } + + public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + base.VisitNamespaceDeclaration(node); + } + + public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + base.VisitFileScopedNamespaceDeclaration(node); + } + + public override void VisitMethodDeclaration(MethodDeclarationSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + foreach (var attribute in node.AttributeLists) + { + attribute.Accept(this); + } + this.formatter.CurrentToken.DoesReturnLine = true; + foreach (var modifier in node.Modifiers) + { + this.VisitToken(modifier); + } + node.ReturnType.Accept(this); + node.ExplicitInterfaceSpecifier?.Accept(this); + this.VisitToken(node.Identifier); + node.TypeParameterList?.Accept(this); + node.ParameterList.Accept(this); + foreach (var constraint in node.ConstraintClauses) + { + constraint.Accept(this); + } + node.Body?.Accept(this); + // gets index of the current method + var count = node.Parent!.ChildNodes().Count(); + var membersIdx = node.Parent!.ChildNodes() + .Select((n, i) => (n, i)) + .Where(x => x.n == node) + .Select(x => x.i) + .FirstOrDefault(); + this.VisitToken(node.SemicolonToken); + } +} diff --git a/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj b/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj new file mode 100644 index 000000000..0d42339fe --- /dev/null +++ b/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Draco.sln b/src/Draco.sln index f4d9aa43f..3e9288a79 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -47,7 +47,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.JsonRpc", "Draco.Json EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Debugger.Tests", "Draco.Debugger.Tests\Draco.Debugger.Tests.csproj", "{9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.TracingDebugger", "Draco.TracingDebugger\Draco.TracingDebugger.csproj", "{0D07F057-29C0-4BB3-AE17-32B73CDFB077}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.TracingDebugger", "Draco.TracingDebugger\Draco.TracingDebugger.csproj", "{0D07F057-29C0-4BB3-AE17-32B73CDFB077}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.Csharp", "Draco.Formatter.Csharp\Draco.Formatter.Csharp.csproj", "{379559B5-FE13-4685-9F40-E021326CCB53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.CSharp.Tests", "Draco.Formatter.CSharp.Tests\Draco.Formatter.CSharp.Tests.csproj", "{FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -176,6 +180,18 @@ Global {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Nuget|Any CPU.Build.0 = Debug|Any CPU {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.ActiveCfg = Release|Any CPU {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.Build.0 = Release|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {379559B5-FE13-4685-9F40-E021326CCB53}.Release|Any CPU.Build.0 = Release|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From a97eb4f5a670a664383431713e51bfb936ac2328 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 09:53:16 +0200 Subject: [PATCH 45/76] Put formatter engine in a dedicated project. --- src/Draco.Compiler/Draco.Compiler.csproj | 1 + src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj | 2 +- .../Syntax/Formatting => Draco.FormatterEngine}/Box.cs | 0 src/Draco.FormatterEngine/Draco.FormatterEngine.csproj | 9 +++++++++ .../Formatting => Draco.FormatterEngine}/FoldPriority.cs | 0 .../FormatterEngine.cs | 0 .../FormatterSettings.cs | 0 .../LineStateMachine.cs | 0 .../Formatting => Draco.FormatterEngine}/MutableBox.cs | 0 .../Syntax/Formatting => Draco.FormatterEngine}/Scope.cs | 0 .../TokenMetadata.cs | 0 .../WhitespaceBehavior.cs | 0 src/Draco.sln | 8 ++++++++ 13 files changed, 19 insertions(+), 1 deletion(-) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/Box.cs (100%) create mode 100644 src/Draco.FormatterEngine/Draco.FormatterEngine.csproj rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/FoldPriority.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/FormatterEngine.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/FormatterSettings.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/LineStateMachine.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/MutableBox.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/Scope.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/TokenMetadata.cs (100%) rename src/{Draco.Compiler/Internal/Syntax/Formatting => Draco.FormatterEngine}/WhitespaceBehavior.cs (100%) diff --git a/src/Draco.Compiler/Draco.Compiler.csproj b/src/Draco.Compiler/Draco.Compiler.csproj index 18c36e7b5..00b74f414 100644 --- a/src/Draco.Compiler/Draco.Compiler.csproj +++ b/src/Draco.Compiler/Draco.Compiler.csproj @@ -13,6 +13,7 @@ + diff --git a/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj b/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj index 0d42339fe..43c6d82a4 100644 --- a/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj +++ b/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj @@ -3,6 +3,6 @@ - + diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs b/src/Draco.FormatterEngine/Box.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/Box.cs rename to src/Draco.FormatterEngine/Box.cs diff --git a/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj b/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj new file mode 100644 index 000000000..fa71b7ae6 --- /dev/null +++ b/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + enable + enable + + + diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs b/src/Draco.FormatterEngine/FoldPriority.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/FoldPriority.cs rename to src/Draco.FormatterEngine/FoldPriority.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/FormatterEngine.cs rename to src/Draco.FormatterEngine/FormatterEngine.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs b/src/Draco.FormatterEngine/FormatterSettings.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/FormatterSettings.cs rename to src/Draco.FormatterEngine/FormatterSettings.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs b/src/Draco.FormatterEngine/LineStateMachine.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/LineStateMachine.cs rename to src/Draco.FormatterEngine/LineStateMachine.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs b/src/Draco.FormatterEngine/MutableBox.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/MutableBox.cs rename to src/Draco.FormatterEngine/MutableBox.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs b/src/Draco.FormatterEngine/Scope.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/Scope.cs rename to src/Draco.FormatterEngine/Scope.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/TokenMetadata.cs rename to src/Draco.FormatterEngine/TokenMetadata.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs similarity index 100% rename from src/Draco.Compiler/Internal/Syntax/Formatting/WhitespaceBehavior.cs rename to src/Draco.FormatterEngine/WhitespaceBehavior.cs diff --git a/src/Draco.sln b/src/Draco.sln index 3e9288a79..25b70f8c2 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -53,6 +53,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.Csharp", "D EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.CSharp.Tests", "Draco.Formatter.CSharp.Tests\Draco.Formatter.CSharp.Tests.csproj", "{FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.FormatterEngine", "Draco.FormatterEngine\Draco.FormatterEngine.csproj", "{D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -192,6 +194,12 @@ Global {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Nuget|Any CPU.Build.0 = Debug|Any CPU {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.ActiveCfg = Release|Any CPU {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.Build.0 = Release|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Nuget|Any CPU.Build.0 = Debug|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 6d3f5e03d19d31713cc2c2bd3abee9527ed9dbd2 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 20:05:55 +0200 Subject: [PATCH 46/76] Conforming to repo syntax. --- src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index f7fdafab6..5ade442b7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -242,7 +242,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax node) { - IDisposable? closeScope = null; + var closeScope = null as IDisposable; var kind = node.Operator.Kind; this.formatter.OnDifferent(kind, _ => { From 8c1760456f4cb7b12f9c36a997794397661fa549 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 20:06:34 +0200 Subject: [PATCH 47/76] Some debug tooling. --- src/Draco.FormatterEngine/Scope.cs | 2 ++ src/Draco.FormatterEngine/TokenMetadata.cs | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index de62efaa3..3eaa0347f 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -142,4 +142,6 @@ public IEnumerable Parents } return null; } + + public override string ToString() => $"{(this.IsMaterialized.Value.HasValue ? this.IsMaterialized.Value.Value ? "M" : "U" : "?")}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; } diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index 493437aea..723980a6f 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -9,4 +9,15 @@ public record struct TokenMetadata( string Text, [DisallowNull] Box? DoesReturnLine, Scope ScopeInfo, - List LeadingTrivia); + List LeadingTrivia) +{ + public override readonly string ToString() + { + var merged = string.Join( + ',', + this.ScopeInfo.Parents.Select(x => x.ToString()) + ); + var returnLine = this.DoesReturnLine == null ? "?" : !this.DoesReturnLine.Value.HasValue ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; + return $"{merged} {returnLine}"; + } +} From b611f56bdd968ed95f5b0e1dbf0ec815fca2e35f Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 23:41:15 +0200 Subject: [PATCH 48/76] Remove OnDifferent. --- .../Syntax/Formatting/DracoFormatter.cs | 19 ++++++++++--------- src/Draco.FormatterEngine/FormatterEngine.cs | 9 --------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 5ade442b7..8df53b5ce 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -156,15 +156,16 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; var type = node.GetType(); - this.formatter.OnDifferent(node switch + var data = node switch { Api.Syntax.FunctionDeclarationSyntax _ => node, // always different, that what we want. - _ => type, - }, previous => - { - if (previous == null) return; - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - }); + _ => type as object, + }; + //this.formatter.OnDifferent(, previous => + //{ + // if (previous == null) return; + // this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + //}); base.VisitDeclaration(node); } @@ -244,10 +245,10 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod { var closeScope = null as IDisposable; var kind = node.Operator.Kind; - this.formatter.OnDifferent(kind, _ => + if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) { closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); - }); + } node.Left.Accept(this); diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index 12c6ebf87..781a56a1a 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -87,15 +87,6 @@ public void CreateMaterializableScope(string indentation, FoldPriority foldBehav using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); } - public void OnDifferent(object? newData, Action onChange) - { - if (!(newData?.Equals(this.Scope.Data) ?? (this.Scope.Data == null))) - { - onChange(this.Scope.Data); - this.Scope.Data = newData; - } - } - public static string Format(FormatterSettings settings, IReadOnlyList metadatas) { FoldTooLongLine(metadatas, settings); From 5e589d6be3c70fc2b33edbdbfc0103e59567bb5e Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 6 May 2024 23:57:02 +0200 Subject: [PATCH 49/76] bugfix --- src/Draco.FormatterEngine/TokenMetadata.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index 723980a6f..936c5aef7 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -15,7 +15,7 @@ public override readonly string ToString() { var merged = string.Join( ',', - this.ScopeInfo.Parents.Select(x => x.ToString()) + this.ScopeInfo.ThisAndParents.Select(x => x.ToString()) ); var returnLine = this.DoesReturnLine == null ? "?" : !this.DoesReturnLine.Value.HasValue ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; return $"{merged} {returnLine}"; From fb40b8b6856bc03b1204596e7cbc1e3d29582d04 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 00:35:34 +0200 Subject: [PATCH 50/76] Bug fixed. --- .../Syntax/Formatting/DracoFormatter.cs | 16 ++++++++---- src/Draco.FormatterEngine/FormatterEngine.cs | 26 ++++++++++++++----- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 8df53b5ce..c84701c26 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -161,11 +161,16 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) Api.Syntax.FunctionDeclarationSyntax _ => node, // always different, that what we want. _ => type as object, }; - //this.formatter.OnDifferent(, previous => - //{ - // if (previous == null) return; - // this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - //}); + + if (!data.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = data; + } + base.VisitDeclaration(node); } @@ -248,6 +253,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) { closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); + this.formatter.Scope.Data = kind; } node.Left.Accept(this); diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index 781a56a1a..da429bb36 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using System.Text; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -119,6 +120,7 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); var currentLineStart = 0; List foldedScopes = []; + for (var x = 0; x < metadatas.Count; x++) { var curr = metadatas[x]; @@ -127,12 +129,20 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form // we recreate a state machine for the new line. stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); currentLineStart = x; - foldedScopes.Clear(); + // we do not clear this, because we can be in the middle of trying to make the line fit in the width. } stateMachine.AddToken(curr, settings, false); - if (stateMachine.LineWidth <= settings.LineWidth) continue; + if (stateMachine.LineWidth <= settings.LineWidth) + { + // we clear the folded scope, because the line is complete and we won't need it anymore. + if (x != metadatas.Count - 1 && (metadatas[x + 1].DoesReturnLine?.Value ?? false)) + { + foldedScopes.Clear(); + } + continue; + } // the line is too long... @@ -157,8 +167,8 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form var prevFolded = scope.Fold(); if (prevFolded != null) { - ResetBacktracking(); - continue; + Backtrack(); + goto continue2; } } // there was no high priority scope to fold, we try to get the low priority then. @@ -169,14 +179,16 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form var prevFolded = scope.Fold(); if (prevFolded != null) { - ResetBacktracking(); - continue; + Backtrack(); + goto continue2; } } + continue2: + // we couldn't fold any scope, we just give up. - void ResetBacktracking() + void Backtrack() { foreach (var scope in foldedScopes) { From edb4c9720369aa18f14ded4ec56b5951c1ef0705 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 00:39:54 +0200 Subject: [PATCH 51/76] Cleaned using. --- src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs | 1 - src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs | 4 ---- src/Draco.Compiler/Api/Syntax/SeparatedSyntaxList.cs | 1 - src/Draco.Compiler/Api/Syntax/SyntaxList.cs | 1 - src/Draco.Compiler/Api/Syntax/SyntaxTree.cs | 1 - src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs | 1 - .../Internal/Diagnostics/ConcurrentDiagnosticBag.cs | 2 -- src/Draco.Compiler/Internal/Diagnostics/DiagnosticBag.cs | 3 --- src/Draco.Compiler/Internal/Diagnostics/EmptyDiagnosticBag.cs | 3 --- .../Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs | 1 - .../Internal/Symbols/Metadata/MetadataSymbol.cs | 2 -- src/Draco.Debugger/Platform/Win32PlatformMethods.cs | 1 - src/Draco.Formatter.CSharp.Tests/FormatterTest.cs | 1 - src/Draco.FormatterEngine/FormatterEngine.cs | 3 --- src/Draco.FormatterEngine/Scope.cs | 3 --- src/Draco.FormatterEngine/TokenMetadata.cs | 2 -- src/Draco.FormatterEngine/WhitespaceBehavior.cs | 2 -- 17 files changed, 32 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index ea6acb626..b1aa75701 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -1,5 +1,4 @@ using Draco.Compiler.Api.Syntax; -using Microsoft.VisualStudio.TestPlatform.ObjectModel.Client; using Xunit.Abstractions; namespace Draco.Compiler.Tests.Syntax; diff --git a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs index 865de5936..397056a50 100644 --- a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Draco.Compiler.Api.Syntax; public partial class ExpressionSyntax diff --git a/src/Draco.Compiler/Api/Syntax/SeparatedSyntaxList.cs b/src/Draco.Compiler/Api/Syntax/SeparatedSyntaxList.cs index 80bce7f28..b2ce9314e 100644 --- a/src/Draco.Compiler/Api/Syntax/SeparatedSyntaxList.cs +++ b/src/Draco.Compiler/Api/Syntax/SeparatedSyntaxList.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using Draco.Compiler.Internal; namespace Draco.Compiler.Api.Syntax; diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxList.cs b/src/Draco.Compiler/Api/Syntax/SyntaxList.cs index d55ef2f71..cd78e7c44 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxList.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxList.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using Draco.Compiler.Internal; namespace Draco.Compiler.Api.Syntax; diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs index 89f122214..bf2a734d6 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxTree.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Threading; using Draco.Compiler.Api.Diagnostics; -using Draco.Compiler.Internal; using Draco.Compiler.Internal.Syntax; using Draco.Compiler.Internal.Syntax.Formatting; using Draco.Compiler.Internal.Syntax.Rewriting; diff --git a/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs b/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs index 7ffbdc299..6d3bc60a6 100644 --- a/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs +++ b/src/Draco.Compiler/Internal/Binding/Binder_Lookup.cs @@ -3,7 +3,6 @@ using System.Collections.Immutable; using System.Linq; using Draco.Compiler.Api.Syntax; -using Draco.Compiler.Internal.BoundTree; using Draco.Compiler.Internal.Diagnostics; using Draco.Compiler.Internal.Solver; using Draco.Compiler.Internal.Solver.Tasks; diff --git a/src/Draco.Compiler/Internal/Diagnostics/ConcurrentDiagnosticBag.cs b/src/Draco.Compiler/Internal/Diagnostics/ConcurrentDiagnosticBag.cs index affb4423f..0e0ed790e 100644 --- a/src/Draco.Compiler/Internal/Diagnostics/ConcurrentDiagnosticBag.cs +++ b/src/Draco.Compiler/Internal/Diagnostics/ConcurrentDiagnosticBag.cs @@ -1,7 +1,5 @@ -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using Draco.Compiler.Api.Diagnostics; namespace Draco.Compiler.Internal.Diagnostics; diff --git a/src/Draco.Compiler/Internal/Diagnostics/DiagnosticBag.cs b/src/Draco.Compiler/Internal/Diagnostics/DiagnosticBag.cs index d64c9c276..5e91b4fe3 100644 --- a/src/Draco.Compiler/Internal/Diagnostics/DiagnosticBag.cs +++ b/src/Draco.Compiler/Internal/Diagnostics/DiagnosticBag.cs @@ -1,9 +1,6 @@ -using System; using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Draco.Compiler.Api.Diagnostics; namespace Draco.Compiler.Internal.Diagnostics; diff --git a/src/Draco.Compiler/Internal/Diagnostics/EmptyDiagnosticBag.cs b/src/Draco.Compiler/Internal/Diagnostics/EmptyDiagnosticBag.cs index 66f25ed88..bcd2fd655 100644 --- a/src/Draco.Compiler/Internal/Diagnostics/EmptyDiagnosticBag.cs +++ b/src/Draco.Compiler/Internal/Diagnostics/EmptyDiagnosticBag.cs @@ -1,8 +1,5 @@ -using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Draco.Compiler.Api.Diagnostics; namespace Draco.Compiler.Internal.Diagnostics; diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs index 00cd84275..94133db7e 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs @@ -4,7 +4,6 @@ using System.Reflection; using System.Reflection.Metadata; using System.Threading; -using Draco.Compiler.Api.Semantics; using Draco.Compiler.Internal.Documentation; using Draco.Compiler.Internal.Documentation.Extractors; diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs index 6978d7376..260f2c191 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using Draco.Compiler.Api; using Draco.Compiler.Internal.Symbols.Synthetized; -using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Symbols.Metadata; diff --git a/src/Draco.Debugger/Platform/Win32PlatformMethods.cs b/src/Draco.Debugger/Platform/Win32PlatformMethods.cs index 5eec099cf..22f86af5e 100644 --- a/src/Draco.Debugger/Platform/Win32PlatformMethods.cs +++ b/src/Draco.Debugger/Platform/Win32PlatformMethods.cs @@ -1,6 +1,5 @@ using System; using System.Runtime.InteropServices; -using System.Security.Cryptography; using Draco.Debugger.IO; namespace Draco.Debugger.Platform; diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs index 514a30cd9..7015d2cb6 100644 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -23,7 +23,6 @@ public static void Main() logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } - [Fact] public void ThisFileShouldBeFormattedCorrectly() { diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index da429bb36..db682f514 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -1,6 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Text; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index 3eaa0347f..fe4e2f25d 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Linq; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index 936c5aef7..f4a9cc26a 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.FormatterEngine/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs index 63dd92493..de3dc7f73 100644 --- a/src/Draco.FormatterEngine/WhitespaceBehavior.cs +++ b/src/Draco.FormatterEngine/WhitespaceBehavior.cs @@ -1,5 +1,3 @@ -using System; - namespace Draco.Compiler.Internal.Syntax.Formatting; [Flags] From 2216605317397b3d281e9e3e8c42ebcd640a7323 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 00:41:54 +0200 Subject: [PATCH 52/76] Added line return. --- src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs | 1 + src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs | 10 +++++----- src/Draco.FormatterEngine/Box.cs | 1 + 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs index cbde242f8..9cc83c5b6 100644 --- a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs @@ -1,4 +1,5 @@ namespace Draco.Compiler.Api.Syntax; + public partial class ElseClauseSyntax { public bool IsElseIf => this.Expression is StatementExpressionSyntax statementExpression diff --git a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs index 397056a50..223b8be36 100644 --- a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs @@ -1,6 +1,7 @@ using System.Linq; namespace Draco.Compiler.Api.Syntax; + public partial class ExpressionSyntax { public int ArgumentIndex @@ -9,11 +10,10 @@ public int ArgumentIndex { if (this.Parent is not CallExpressionSyntax callExpression) return -1; if (this == callExpression.Function) return -1; - foreach (var (item, i) in callExpression.ArgumentList.Values.Select((s, i) => (s, i))) - { - if (item == this) return i; - } - throw new InvalidOperationException(); + return callExpression.ArgumentList.Values + .Select((a, i) => (Argument: a, Index: i)) + .First(p => p.Argument == this) + .Index; } } } diff --git a/src/Draco.FormatterEngine/Box.cs b/src/Draco.FormatterEngine/Box.cs index 52cef8363..732f47431 100644 --- a/src/Draco.FormatterEngine/Box.cs +++ b/src/Draco.FormatterEngine/Box.cs @@ -1,4 +1,5 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; + public class Box(T value) { protected T value = value; From 4072e3b23e09de3dd4d4eb1341f3add938416764 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 00:56:36 +0200 Subject: [PATCH 53/76] Some PR feedback. --- .../Api/Syntax/ExpressionSyntax.cs | 19 --- .../Api/Syntax/StringPartSyntax.cs | 3 + .../Syntax/Formatting/DracoFormatter.cs | 124 +++++++++--------- src/Draco.Editor.Web/app/build.js | 58 ++++---- .../Draco.Formatter.CSharp.Tests.csproj | 5 - src/Draco.Formatter.Csharp/CSharpFormatter.cs | 106 +++++++-------- .../Draco.FormatterEngine.csproj | 3 +- .../Draco.TracingDebugger.csproj | 14 -- src/Draco.TracingDebugger/Program.cs | 30 ----- 9 files changed, 147 insertions(+), 215 deletions(-) delete mode 100644 src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs delete mode 100644 src/Draco.TracingDebugger/Draco.TracingDebugger.csproj delete mode 100644 src/Draco.TracingDebugger/Program.cs diff --git a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs b/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs deleted file mode 100644 index 223b8be36..000000000 --- a/src/Draco.Compiler/Api/Syntax/ExpressionSyntax.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Linq; - -namespace Draco.Compiler.Api.Syntax; - -public partial class ExpressionSyntax -{ - public int ArgumentIndex - { - get - { - if (this.Parent is not CallExpressionSyntax callExpression) return -1; - if (this == callExpression.Function) return -1; - return callExpression.ArgumentList.Values - .Select((a, i) => (Argument: a, Index: i)) - .First(p => p.Argument == this) - .Index; - } - } -} diff --git a/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs b/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs index 9dbcad568..bd739afa8 100644 --- a/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/StringPartSyntax.cs @@ -4,5 +4,8 @@ namespace Draco.Compiler.Api.Syntax; public partial class StringPartSyntax { + /// + /// when this is a with . + /// public bool IsNewLine => this.Children.Count() == 1 && this.Children.SingleOrDefault() is SyntaxToken and { Kind: TokenKind.StringNewline }; } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index c84701c26..8cc763028 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -38,65 +38,66 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) base.VisitCompilationUnit(node); } + private static WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch + { + TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + + TokenKind.KeywordTrue => WhitespaceBehavior.PadAround, + TokenKind.KeywordFalse => WhitespaceBehavior.PadAround, + TokenKind.KeywordMod => WhitespaceBehavior.PadAround, + TokenKind.KeywordRem => WhitespaceBehavior.PadAround, + + TokenKind.KeywordFunc => WhitespaceBehavior.PadAround, + + + TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + TokenKind.ParenOpen => WhitespaceBehavior.Whitespace, + TokenKind.ParenClose => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + TokenKind.InterpolationStart => WhitespaceBehavior.Whitespace, + TokenKind.Dot => WhitespaceBehavior.Whitespace, + TokenKind.Colon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + + TokenKind.Assign => WhitespaceBehavior.PadAround, + TokenKind.LineStringStart => WhitespaceBehavior.PadLeft, + TokenKind.MultiLineStringStart => WhitespaceBehavior.PadLeft, + TokenKind.Plus => WhitespaceBehavior.PadLeft, + TokenKind.Minus => WhitespaceBehavior.PadLeft, + TokenKind.Star => WhitespaceBehavior.PadLeft, + TokenKind.Slash => WhitespaceBehavior.PadLeft, + TokenKind.PlusAssign => WhitespaceBehavior.PadLeft, + TokenKind.MinusAssign => WhitespaceBehavior.PadLeft, + TokenKind.StarAssign => WhitespaceBehavior.PadLeft, + TokenKind.SlashAssign => WhitespaceBehavior.PadLeft, + TokenKind.GreaterEqual => WhitespaceBehavior.PadLeft, + TokenKind.GreaterThan => WhitespaceBehavior.PadLeft, + TokenKind.LessEqual => WhitespaceBehavior.PadLeft, + TokenKind.LessThan => WhitespaceBehavior.PadLeft, + TokenKind.Equal => WhitespaceBehavior.PadLeft, + TokenKind.LiteralFloat => WhitespaceBehavior.PadLeft, + TokenKind.LiteralInteger => WhitespaceBehavior.PadLeft, + + TokenKind.Identifier => WhitespaceBehavior.PadLeft, + + _ => WhitespaceBehavior.NoFormatting + }; + public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { - static WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch - { - TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - - TokenKind.KeywordTrue => WhitespaceBehavior.PadAround, - TokenKind.KeywordFalse => WhitespaceBehavior.PadAround, - TokenKind.KeywordMod => WhitespaceBehavior.PadAround, - TokenKind.KeywordRem => WhitespaceBehavior.PadAround, - - TokenKind.KeywordFunc => WhitespaceBehavior.PadAround, - - - TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - TokenKind.ParenOpen => WhitespaceBehavior.Whitespace, - TokenKind.ParenClose => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - TokenKind.InterpolationStart => WhitespaceBehavior.Whitespace, - TokenKind.Dot => WhitespaceBehavior.Whitespace, - TokenKind.Colon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - - TokenKind.Assign => WhitespaceBehavior.PadAround, - TokenKind.LineStringStart => WhitespaceBehavior.PadLeft, - TokenKind.MultiLineStringStart => WhitespaceBehavior.PadLeft, - TokenKind.Plus => WhitespaceBehavior.PadLeft, - TokenKind.Minus => WhitespaceBehavior.PadLeft, - TokenKind.Star => WhitespaceBehavior.PadLeft, - TokenKind.Slash => WhitespaceBehavior.PadLeft, - TokenKind.PlusAssign => WhitespaceBehavior.PadLeft, - TokenKind.MinusAssign => WhitespaceBehavior.PadLeft, - TokenKind.StarAssign => WhitespaceBehavior.PadLeft, - TokenKind.SlashAssign => WhitespaceBehavior.PadLeft, - TokenKind.GreaterEqual => WhitespaceBehavior.PadLeft, - TokenKind.GreaterThan => WhitespaceBehavior.PadLeft, - TokenKind.LessEqual => WhitespaceBehavior.PadLeft, - TokenKind.LessThan => WhitespaceBehavior.PadLeft, - TokenKind.Equal => WhitespaceBehavior.PadLeft, - TokenKind.LiteralFloat => WhitespaceBehavior.PadLeft, - TokenKind.LiteralInteger => WhitespaceBehavior.PadLeft, - - TokenKind.Identifier => WhitespaceBehavior.PadLeft, - - _ => WhitespaceBehavior.NoFormatting - }; this.HandleTokenComments(node); this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); @@ -156,19 +157,19 @@ public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; var type = node.GetType(); - var data = node switch + var newData = node switch { Api.Syntax.FunctionDeclarationSyntax _ => node, // always different, that what we want. _ => type as object, }; - if (!data.Equals(this.formatter.Scope.Data)) + if (!newData.Equals(this.formatter.Scope.Data)) { if (this.formatter.Scope.Data != null) { this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. } - this.formatter.Scope.Data = data; + this.formatter.Scope.Data = newData; } base.VisitDeclaration(node); @@ -192,9 +193,8 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod using var _ = this.formatter.CreateScope(this.settings.Indentation); var blockCurrentIndentCount = SyntaxFacts.ComputeCutoff(node).Length; - var i = 0; var shouldIndent = true; - for (; i < node.Parts.Count; i++) + for (var i = 0; i < node.Parts.Count; i++) { var curr = node.Parts[i]; diff --git a/src/Draco.Editor.Web/app/build.js b/src/Draco.Editor.Web/app/build.js index eeab991df..0bf6f7448 100644 --- a/src/Draco.Editor.Web/app/build.js +++ b/src/Draco.Editor.Web/app/build.js @@ -130,33 +130,31 @@ const response = await octokit.repos.getContent({ repo: 'vscode', path: 'extensions/theme-defaults/themes' }); -// const timeout = setTimeout(() => controller.abort(), 100000); -// const themes = await Promise.all(response.data.map(async s => { -// const resp = await fetch(s.download_url, { -// signal: timeout -// }); -// const txt = await resp.text(); -// const parsed = JSON5.parse(txt); -// const converted = convertTheme(parsed); -// return { -// name: parsed.name, -// filename: s.name, -// theme: converted -// }; -// })); -// const themeObj = {}; -// const themePackageJson = await (await fetch('https://raw.githubusercontent.com/microsoft/vscode/main/extensions/theme-defaults/package.json')).json(); -// const themesMetadata = themePackageJson.contributes.themes; - -// themes.forEach(s => { -// themeObj[s.name] = s.theme; -// themeObj[s.name].base = themesMetadata.find(t => path.basename(t.path) == s.filename).uiTheme; -// }); - -// const themeListJson = JSON.stringify(themeObj); -// fs.writeFileSync(path.join(outDir, 'themes.json'), themeListJson); -// const csharpTextmateYml = await (await fetch('https://raw.githubusercontent.com/dotnet/csharp-tmLanguage/main/src/csharp.tmLanguage.yml')).text(); -// const csharpTextmate = JSON.stringify(YAML.parse(csharpTextmateYml)); -// fs.writeFileSync(path.join(outDir, 'csharp.tmLanguage.json'), csharpTextmate); -// const ilTextmate = await (await fetch('https://raw.githubusercontent.com/soltys/vscode-il/master/syntaxes/il.json')).text(); -// fs.writeFileSync(path.join(outDir, 'il.tmLanguage.json'), ilTextmate); + +const themes = await Promise.all(response.data.map(async s => { + const resp = await fetch(s.download_url); + const txt = await resp.text(); + const parsed = JSON5.parse(txt); + const converted = convertTheme(parsed); + return { + name: parsed.name, + filename: s.name, + theme: converted + }; +})); +const themeObj = {}; +const themePackageJson = await (await fetch('https://raw.githubusercontent.com/microsoft/vscode/main/extensions/theme-defaults/package.json')).json(); +const themesMetadata = themePackageJson.contributes.themes; + +themes.forEach(s => { + themeObj[s.name] = s.theme; + themeObj[s.name].base = themesMetadata.find(t => path.basename(t.path) == s.filename).uiTheme; +}); + +const themeListJson = JSON.stringify(themeObj); +fs.writeFileSync(path.join(outDir, 'themes.json'), themeListJson); +const csharpTextmateYml = await (await fetch('https://raw.githubusercontent.com/dotnet/csharp-tmLanguage/main/src/csharp.tmLanguage.yml')).text(); +const csharpTextmate = JSON.stringify(YAML.parse(csharpTextmateYml)); +fs.writeFileSync(path.join(outDir, 'csharp.tmLanguage.json'), csharpTextmate); +const ilTextmate = await (await fetch('https://raw.githubusercontent.com/soltys/vscode-il/master/syntaxes/il.json')).text(); +fs.writeFileSync(path.join(outDir, 'il.tmLanguage.json'), ilTextmate); diff --git a/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj b/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj index 98d6079fb..377f85b72 100644 --- a/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj +++ b/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj @@ -25,9 +25,4 @@ - - - - - diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index dd00a838d..c2f2da757 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -6,7 +6,7 @@ namespace Draco.Formatter.Csharp; -public class CSharpFormatter(FormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) +public sealed class CSharpFormatter(FormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) { private readonly FormatterSettings settings = settings; private FormatterEngine formatter = null!; @@ -29,62 +29,62 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) base.VisitCompilationUnit(node); } + private static WhitespaceBehavior GetFormattingTokenKind(SyntaxToken token) => token.Kind() switch + { + SyntaxKind.AndKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ElseKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ForKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.GotoKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.UsingDirective => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.InKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.InternalKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.OrKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.PublicKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.VarKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.IfKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.WhileKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.StaticKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.SealedKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + + SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, + + SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + SyntaxKind.OpenBraceToken => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + SyntaxKind.OpenParenToken => WhitespaceBehavior.Whitespace, + SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, + SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, + SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, + SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, + + SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, + SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, + SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, + SyntaxKind.PlusToken => WhitespaceBehavior.PadLeft, + SyntaxKind.MinusToken => WhitespaceBehavior.PadLeft, + SyntaxKind.AsteriskToken => WhitespaceBehavior.PadLeft, + SyntaxKind.SlashToken => WhitespaceBehavior.PadLeft, + SyntaxKind.PlusEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.MinusEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.SlashEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.GreaterThanToken => WhitespaceBehavior.PadLeft, + SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.PadLeft, + SyntaxKind.LessThanToken => WhitespaceBehavior.PadLeft, + SyntaxKind.NumericLiteralToken => WhitespaceBehavior.PadLeft, + + SyntaxKind.IdentifierToken => WhitespaceBehavior.PadLeft, + + _ => WhitespaceBehavior.NoFormatting + }; + public override void VisitToken(SyntaxToken node) { if(node.IsKind(SyntaxKind.None)) return; - static WhitespaceBehavior GetFormattingTokenKind(SyntaxToken token) => token.Kind() switch - { - SyntaxKind.AndKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ElseKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ForKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.GotoKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.UsingDirective => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.InKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.InternalKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.OrKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.PublicKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.VarKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.IfKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.WhileKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.StaticKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.SealedKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - - SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, - - SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - SyntaxKind.OpenBraceToken => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - SyntaxKind.OpenParenToken => WhitespaceBehavior.Whitespace, - SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, - SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, - SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, - - SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, - SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, - SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, - SyntaxKind.PlusToken => WhitespaceBehavior.PadLeft, - SyntaxKind.MinusToken => WhitespaceBehavior.PadLeft, - SyntaxKind.AsteriskToken => WhitespaceBehavior.PadLeft, - SyntaxKind.SlashToken => WhitespaceBehavior.PadLeft, - SyntaxKind.PlusEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.MinusEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.SlashEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.GreaterThanToken => WhitespaceBehavior.PadLeft, - SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.LessThanToken => WhitespaceBehavior.PadLeft, - SyntaxKind.NumericLiteralToken => WhitespaceBehavior.PadLeft, - - SyntaxKind.IdentifierToken => WhitespaceBehavior.PadLeft, - - _ => WhitespaceBehavior.NoFormatting - }; - base.VisitToken(node); this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); } diff --git a/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj b/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj index fa71b7ae6..8d2b2325b 100644 --- a/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj +++ b/src/Draco.FormatterEngine/Draco.FormatterEngine.csproj @@ -1,8 +1,7 @@ - + net8.0 - enable enable diff --git a/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj b/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj deleted file mode 100644 index b8fe9c9ab..000000000 --- a/src/Draco.TracingDebugger/Draco.TracingDebugger.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - diff --git a/src/Draco.TracingDebugger/Program.cs b/src/Draco.TracingDebugger/Program.cs deleted file mode 100644 index 1bae90b42..000000000 --- a/src/Draco.TracingDebugger/Program.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Draco.Debugger; - -namespace Draco.TracingDebugger; - -internal class Program -{ - static async Task Main(string[] args) - { - var debuggerHost = DebuggerHost.Create(); - var path = @"C:\dev\ConsoleApp36\ConsoleApp36\bin\Debug\net8.0\ConsoleApp36.exe"; - var debugger = debuggerHost.StartProcess(path); - await debugger.Ready; - while (!debugger.Terminated.IsCompleted) - { - var lastFrame = debugger.MainThread.CallStack[^1]; - var lines = lastFrame.Method.SourceFile?.Lines.AsEnumerable(); - if (lines is null || !lastFrame.Range.HasValue) - { - Console.WriteLine("???"); - continue; - } - var filtered = lines.Skip(lastFrame.Range.Value.Start.Line).Take(lastFrame.Range.Value.End.Line - lastFrame.Range.Value.Start.Line + 1); - - Console.WriteLine(string.Join("\n", filtered.Select(s => s.ToString()) ?? [])); - debugger.MainThread.StepOver(); - System.Threading.Thread.Sleep(5000); - } - - } -} From 70f94bf8fe98eb1e6e1510d60a9567fce62f2990 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 01:05:35 +0200 Subject: [PATCH 54/76] More PR feedback. --- src/Draco.Formatter.CSharp.Tests/FormatterTest.cs | 1 + src/Draco.FormatterEngine/FormatterEngine.cs | 2 ++ src/Draco.FormatterEngine/FormatterSettings.cs | 13 ------------- src/Draco.FormatterEngine/Scope.cs | 2 ++ src/Draco.FormatterEngine/TokenMetadata.cs | 5 +++-- src/Draco.FormatterEngine/WhitespaceBehavior.cs | 2 ++ src/Draco.sln | 10 +--------- 7 files changed, 11 insertions(+), 24 deletions(-) diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs index 7015d2cb6..26352d2a0 100644 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -1,6 +1,7 @@ using Draco.Formatter.Csharp; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; +using Xunit; using Xunit.Abstractions; namespace Draco.Formatter.CSharp.Test; public sealed class FormatterTest(ITestOutputHelper logger) diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index db682f514..d6b2b35db 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -1,3 +1,5 @@ +using System; +using System.Collections.Generic; using System.Text; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.FormatterEngine/FormatterSettings.cs b/src/Draco.FormatterEngine/FormatterSettings.cs index b65d45478..5597553f5 100644 --- a/src/Draco.FormatterEngine/FormatterSettings.cs +++ b/src/Draco.FormatterEngine/FormatterSettings.cs @@ -23,17 +23,4 @@ public sealed class FormatterSettings public string Indentation { get; init; } = " "; public int LineWidth { get; init; } = 160; - - public string IndentationString(int amount = 1) - { - var sb = new StringBuilder(); - for (var i = 0; i < amount; ++i) sb.Append(this.Indentation); - return sb.ToString(); - } - public string PaddingString(int width = 1) - { - var sb = new StringBuilder(); - for (var i = 0; i < width; ++i) sb.Append(' '); - return sb.ToString(); - } } diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index fe4e2f25d..5c7975e58 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -1,4 +1,6 @@ +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; namespace Draco.Compiler.Internal.Syntax.Formatting; diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index f4a9cc26a..c1c18e75e 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Draco.Compiler.Internal.Syntax.Formatting; @@ -9,11 +10,11 @@ public record struct TokenMetadata( Scope ScopeInfo, List LeadingTrivia) { - public override readonly string ToString() + public readonly override string ToString() { var merged = string.Join( ',', - this.ScopeInfo.ThisAndParents.Select(x => x.ToString()) + this.ScopeInfo.ThisAndParents ); var returnLine = this.DoesReturnLine == null ? "?" : !this.DoesReturnLine.Value.HasValue ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; return $"{merged} {returnLine}"; diff --git a/src/Draco.FormatterEngine/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs index de3dc7f73..63dd92493 100644 --- a/src/Draco.FormatterEngine/WhitespaceBehavior.cs +++ b/src/Draco.FormatterEngine/WhitespaceBehavior.cs @@ -1,3 +1,5 @@ +using System; + namespace Draco.Compiler.Internal.Syntax.Formatting; [Flags] diff --git a/src/Draco.sln b/src/Draco.sln index 25b70f8c2..7026b9710 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -47,13 +47,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.JsonRpc", "Draco.Json EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Debugger.Tests", "Draco.Debugger.Tests\Draco.Debugger.Tests.csproj", "{9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.TracingDebugger", "Draco.TracingDebugger\Draco.TracingDebugger.csproj", "{0D07F057-29C0-4BB3-AE17-32B73CDFB077}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.Csharp", "Draco.Formatter.Csharp\Draco.Formatter.Csharp.csproj", "{379559B5-FE13-4685-9F40-E021326CCB53}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.CSharp.Tests", "Draco.Formatter.CSharp.Tests\Draco.Formatter.CSharp.Tests.csproj", "{FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Draco.FormatterEngine", "Draco.FormatterEngine\Draco.FormatterEngine.csproj", "{D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.FormatterEngine", "Draco.FormatterEngine\Draco.FormatterEngine.csproj", "{D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -176,12 +174,6 @@ Global {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Nuget|Any CPU.Build.0 = Debug|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.Build.0 = Release|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Nuget|Any CPU.Build.0 = Debug|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0D07F057-29C0-4BB3-AE17-32B73CDFB077}.Release|Any CPU.Build.0 = Release|Any CPU {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.Build.0 = Debug|Any CPU {379559B5-FE13-4685-9F40-E021326CCB53}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU From 7dc3e8b608a27351585f554532577c99bc2aa836 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 01:12:31 +0200 Subject: [PATCH 55/76] And more. --- src/Draco.FormatterEngine/TokenMetadata.cs | 2 +- src/Draco.FormatterEngine/WhitespaceBehavior.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index c1c18e75e..9084e07f9 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -16,7 +16,7 @@ public readonly override string ToString() ',', this.ScopeInfo.ThisAndParents ); - var returnLine = this.DoesReturnLine == null ? "?" : !this.DoesReturnLine.Value.HasValue ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; + var returnLine = !(this.DoesReturnLine?.Value.HasValue ?? false) ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; return $"{merged} {returnLine}"; } } diff --git a/src/Draco.FormatterEngine/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs index 63dd92493..330e66cfb 100644 --- a/src/Draco.FormatterEngine/WhitespaceBehavior.cs +++ b/src/Draco.FormatterEngine/WhitespaceBehavior.cs @@ -6,7 +6,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; public enum WhitespaceBehavior { NoFormatting = 0, - PadLeft = 1, + PadLeft = 1 << 0, PadRight = 1 << 1, ForceRightPad = 1 << 2, BehaveAsWhiteSpaceForNextToken = 1 << 3, From 46ab6105fa712164077534077059b17c6e6a2f22 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 09:46:01 +0200 Subject: [PATCH 56/76] Remove empty formatter config. --- .../Syntax/ParseTreeFormatterTests.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index b1aa75701..f1ce97de8 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -183,7 +183,7 @@ bla bla } """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + var actual = SyntaxTree.Parse(input).Format(); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -203,7 +203,7 @@ bla bla } """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + var actual = SyntaxTree.Parse(input).Format(); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -229,7 +229,7 @@ func main() { } """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings()); + var actual = SyntaxTree.Parse(input).Format(); Assert.Equal(expected, actual, ignoreLineEndingDifferences: true); } @@ -328,9 +328,7 @@ public void CursedSample() // oops. """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() - { - }); + var actual = SyntaxTree.Parse(input).Format(); logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } @@ -349,9 +347,7 @@ func foo(a: int32) { } """"; - var actual = SyntaxTree.Parse(input).Format(new Internal.Syntax.Formatting.FormatterSettings() - { - }); + var actual = SyntaxTree.Parse(input).Format(); logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } From 4b9256f702567d3b362a388e5f6457f5088c941b Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 10:15:36 +0200 Subject: [PATCH 57/76] PR feedback. --- .../Api/Syntax/ElseClauseSyntax.cs | 3 ++ .../Syntax/Formatting/DracoFormatter.cs | 32 +++++++++---------- .../FormatterSettings.cs | 3 ++ .../WhitespaceBehavior.cs | 28 +++++++++++++++- 4 files changed, 49 insertions(+), 17 deletions(-) diff --git a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs index 9cc83c5b6..bbbf07b8a 100644 --- a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs @@ -2,6 +2,9 @@ namespace Draco.Compiler.Api.Syntax; public partial class ElseClauseSyntax { + /// + /// Returns when the else clause is followed by an if expression. + /// public bool IsElseIf => this.Expression is StatementExpressionSyntax statementExpression && statementExpression.Statement is ExpressionStatementSyntax expressionStatement && expressionStatement.Expression is IfExpressionSyntax; diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 8cc763028..eedfa8a97 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -40,21 +40,21 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) private static WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { - TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, TokenKind.KeywordTrue => WhitespaceBehavior.PadAround, TokenKind.KeywordFalse => WhitespaceBehavior.PadAround, @@ -66,7 +66,7 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - TokenKind.ParenOpen => WhitespaceBehavior.Whitespace, + TokenKind.ParenOpen => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, TokenKind.ParenClose => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, TokenKind.InterpolationStart => WhitespaceBehavior.Whitespace, TokenKind.Dot => WhitespaceBehavior.Whitespace, diff --git a/src/Draco.FormatterEngine/FormatterSettings.cs b/src/Draco.FormatterEngine/FormatterSettings.cs index 5597553f5..8db2f3169 100644 --- a/src/Draco.FormatterEngine/FormatterSettings.cs +++ b/src/Draco.FormatterEngine/FormatterSettings.cs @@ -22,5 +22,8 @@ public sealed class FormatterSettings /// public string Indentation { get; init; } = " "; + /// + /// The max line width the formatter will try to respect. + /// public int LineWidth { get; init; } = 160; } diff --git a/src/Draco.FormatterEngine/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs index 330e66cfb..374e9fbad 100644 --- a/src/Draco.FormatterEngine/WhitespaceBehavior.cs +++ b/src/Draco.FormatterEngine/WhitespaceBehavior.cs @@ -2,16 +2,42 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; +/// +/// Whitespace behavior that will be respected when formatting the file. +/// [Flags] public enum WhitespaceBehavior { + /// + /// No formatting behavior is set. Unspecified behavior. + /// NoFormatting = 0, + /// + /// Add a left whitespace if necessary. + /// PadLeft = 1 << 0, + /// + /// Add a right whitespace if necessary + /// PadRight = 1 << 1, - ForceRightPad = 1 << 2, + /// + /// The next token will think of this token as a whitespace. + /// BehaveAsWhiteSpaceForNextToken = 1 << 3, + /// + /// The previous token will think of this as a whitespace. + /// BehaveAsWhiteSpaceForPreviousToken = 1 << 4, + /// + /// Remove one indentation level. + /// RemoveOneIndentation = 1 << 6, + /// + /// Add a whitespace on the left and right, if necessary. + /// PadAround = PadLeft | PadRight, + /// + /// This token behave as a whitespace. + /// Whitespace = BehaveAsWhiteSpaceForNextToken | BehaveAsWhiteSpaceForPreviousToken, } From 54dfffd6be1f619c3c90e5a4426722c07496b222 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 10:18:55 +0200 Subject: [PATCH 58/76] Remove force right pad. --- .../Syntax/Formatting/DracoFormatter.cs | 30 ++++++++--------- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 32 +++++++++---------- src/Draco.FormatterEngine/LineStateMachine.cs | 6 ++-- 3 files changed, 33 insertions(+), 35 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index eedfa8a97..16acad606 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -40,21 +40,21 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) private static WhitespaceBehavior GetFormattingTokenKind(Api.Syntax.SyntaxToken token) => token.Kind switch { - TokenKind.KeywordAnd => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordElse => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordFor => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordGoto => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordImport => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordIn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordInternal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordModule => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordOr => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordReturn => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordPublic => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordVar => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordVal => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordIf => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, - TokenKind.KeywordWhile => WhitespaceBehavior.PadLeft | WhitespaceBehavior.PadRight, + TokenKind.KeywordAnd => WhitespaceBehavior.PadAround, + TokenKind.KeywordElse => WhitespaceBehavior.PadAround, + TokenKind.KeywordFor => WhitespaceBehavior.PadAround, + TokenKind.KeywordGoto => WhitespaceBehavior.PadAround, + TokenKind.KeywordImport => WhitespaceBehavior.PadAround, + TokenKind.KeywordIn => WhitespaceBehavior.PadAround, + TokenKind.KeywordInternal => WhitespaceBehavior.PadAround, + TokenKind.KeywordModule => WhitespaceBehavior.PadAround, + TokenKind.KeywordOr => WhitespaceBehavior.PadAround, + TokenKind.KeywordReturn => WhitespaceBehavior.PadAround, + TokenKind.KeywordPublic => WhitespaceBehavior.PadAround, + TokenKind.KeywordVar => WhitespaceBehavior.PadAround, + TokenKind.KeywordVal => WhitespaceBehavior.PadAround, + TokenKind.KeywordIf => WhitespaceBehavior.PadAround, + TokenKind.KeywordWhile => WhitespaceBehavior.PadAround, TokenKind.KeywordTrue => WhitespaceBehavior.PadAround, TokenKind.KeywordFalse => WhitespaceBehavior.PadAround, diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index c2f2da757..85abf48ef 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -31,22 +31,22 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) private static WhitespaceBehavior GetFormattingTokenKind(SyntaxToken token) => token.Kind() switch { - SyntaxKind.AndKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ElseKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ForKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.GotoKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.UsingDirective => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.InKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.InternalKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.OrKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.PublicKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.VarKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.IfKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.WhileKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.StaticKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, - SyntaxKind.SealedKeyword => WhitespaceBehavior.PadLeft | WhitespaceBehavior.ForceRightPad, + SyntaxKind.AndKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.ElseKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.ForKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.GotoKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.UsingDirective => WhitespaceBehavior.PadAround, + SyntaxKind.InKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.InternalKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.OrKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.PublicKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.VarKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.IfKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.WhileKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.StaticKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.SealedKeyword => WhitespaceBehavior.PadAround, SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, diff --git a/src/Draco.FormatterEngine/LineStateMachine.cs b/src/Draco.FormatterEngine/LineStateMachine.cs index 555bc05f5..caa4b2568 100644 --- a/src/Draco.FormatterEngine/LineStateMachine.cs +++ b/src/Draco.FormatterEngine/LineStateMachine.cs @@ -8,7 +8,6 @@ internal sealed class LineStateMachine private readonly string indentation; private bool previousIsWhitespace = true; private bool prevTokenNeedRightPad = false; - private bool forceWhiteSpace = false; public LineStateMachine(string indentation) { this.sb.Append(indentation); @@ -30,7 +29,7 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en var requestedLeftPad = this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft); var haveWhitespace = (metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken) || this.previousIsWhitespace); - var shouldLeftPad = (requestedLeftPad && !haveWhitespace) || this.forceWhiteSpace; + var shouldLeftPad = (requestedLeftPad && !haveWhitespace); if (shouldLeftPad) { @@ -38,9 +37,8 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en } this.Append(metadata.Text); - this.forceWhiteSpace = metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); - this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken) || metadata.Kind.HasFlag(WhitespaceBehavior.ForceRightPad); + this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken); } private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) From ed544782616e1842c1688d8da6367a135c346bb1 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 7 May 2024 10:28:05 +0200 Subject: [PATCH 59/76] wip. --- .../Syntax/Formatting/DracoFormatter.cs | 1 - .../FormatterTest.cs | 3 ++ src/Draco.Formatter.Csharp/CSharpFormatter.cs | 36 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 16acad606..f4ff744a7 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -63,7 +63,6 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) TokenKind.KeywordFunc => WhitespaceBehavior.PadAround, - TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, TokenKind.ParenOpen => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs index 26352d2a0..92bfdb08e 100644 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -3,7 +3,9 @@ using Microsoft.CodeAnalysis.Text; using Xunit; using Xunit.Abstractions; + namespace Draco.Formatter.CSharp.Test; + public sealed class FormatterTest(ITestOutputHelper logger) { [Fact] @@ -24,6 +26,7 @@ public static void Main() logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } + [Fact] public void ThisFileShouldBeFormattedCorrectly() { diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 85abf48ef..02dc5b1b1 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -83,7 +83,7 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) public override void VisitToken(SyntaxToken node) { - if(node.IsKind(SyntaxKind.None)) return; + if (node.IsKind(SyntaxKind.None)) return; base.VisitToken(node); this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); @@ -91,6 +91,14 @@ public override void VisitToken(SyntaxToken node) public override void VisitClassDeclaration(ClassDeclarationSyntax node) { + if (!node.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = node; + } this.formatter.CurrentToken.DoesReturnLine = true; foreach (var attribute in node.AttributeLists) { @@ -151,11 +159,29 @@ public override void VisitBlock(BlockSyntax node) public override void VisitUsingDirective(UsingDirectiveSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; + var newData = typeof(UsingStatementSyntax); + if (!newData.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = newData; + } base.VisitUsingDirective(node); } public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) { + var newData = typeof(NamespaceDeclarationSyntax); + if (!newData.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = newData; + } this.formatter.CurrentToken.DoesReturnLine = true; base.VisitNamespaceDeclaration(node); } @@ -168,6 +194,14 @@ public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDecl public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { + if (!node.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = node; + } this.formatter.CurrentToken.DoesReturnLine = true; foreach (var attribute in node.AttributeLists) { From 1dbbe1e7ce21806c782169b252aec89c99790578 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 8 May 2024 00:24:37 +0200 Subject: [PATCH 60/76] wip --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 10 +++++ src/Draco.FormatterEngine/LineStateMachine.cs | 37 +++++++++---------- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 02dc5b1b1..652789e3c 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -186,8 +186,18 @@ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) base.VisitNamespaceDeclaration(node); } + public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) { + var newData = typeof(FileScopedNamespaceDeclarationSyntax); + if (!newData.Equals(this.formatter.Scope.Data)) + { + if (this.formatter.Scope.Data != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + } + this.formatter.Scope.Data = newData; + } this.formatter.CurrentToken.DoesReturnLine = true; base.VisitFileScopedNamespaceDeclaration(node); } diff --git a/src/Draco.FormatterEngine/LineStateMachine.cs b/src/Draco.FormatterEngine/LineStateMachine.cs index caa4b2568..c4ab708e6 100644 --- a/src/Draco.FormatterEngine/LineStateMachine.cs +++ b/src/Draco.FormatterEngine/LineStateMachine.cs @@ -2,29 +2,29 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; -internal sealed class LineStateMachine +internal sealed class LineStateMachine(string indentation) { private readonly StringBuilder sb = new(); - private readonly string indentation; private bool previousIsWhitespace = true; private bool prevTokenNeedRightPad = false; - public LineStateMachine(string indentation) - { - this.sb.Append(indentation); - this.LineWidth = indentation.Length; - this.indentation = indentation; - } + private bool shouldIndent = true; - public int LineWidth { get; set; } + public int LineWidth { get; set; } = indentation.Length; public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool endOfInput) { this.HandleLeadingComments(metadata, settings, endOfInput); - - if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) + if (this.shouldIndent) { - this.sb.Remove(0, settings.Indentation.Length); - this.LineWidth -= settings.Indentation.Length; + this.shouldIndent = false; + if (metadata.Kind.HasFlag(WhitespaceBehavior.RemoveOneIndentation)) + { + this.Append(indentation.Remove(settings.Indentation.Length)); + } + else + { + this.Append(indentation); + } } var requestedLeftPad = this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft); @@ -45,14 +45,13 @@ private void HandleLeadingComments(TokenMetadata metadata, FormatterSettings set { if (metadata.LeadingTrivia == null) return; - foreach (var comment in metadata.LeadingTrivia) + foreach (var trivia in metadata.LeadingTrivia) { - this.sb.Append(comment); + if (!string.IsNullOrWhiteSpace(trivia)) this.sb.Append(indentation); + this.sb.Append(trivia); if (!endOfInput) { this.sb.Append(settings.Newline); - this.sb.Append(this.indentation); - this.LineWidth = this.indentation.Length; } } } @@ -66,8 +65,8 @@ private void Append(string text) public void Reset() { this.sb.Clear(); - this.sb.Append(this.indentation); - this.LineWidth = this.indentation.Length; + this.sb.Append(indentation); + this.LineWidth = indentation.Length; } From f1854a0e4d80d347b497dd9f4ec015fd097df672 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Wed, 8 May 2024 23:20:13 +0200 Subject: [PATCH 61/76] Removed Data property on scope infos. --- .../E2eBenchmarks.cs | 3 +- .../Syntax/Formatting/DracoFormatter.cs | 48 +++++++---- .../FormatterTest.cs | 14 ++++ src/Draco.Formatter.Csharp/CSharpFormatter.cs | 84 +++++++++++-------- src/Draco.FormatterEngine/Scope.cs | 6 -- 5 files changed, 96 insertions(+), 59 deletions(-) diff --git a/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs b/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs index 0b4a1aa88..86ae261b2 100644 --- a/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs +++ b/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs @@ -11,8 +11,7 @@ public class E2eBenchmarks : FolderBenchmarkBase { private MemoryStream peStream = null!; - public E2eBenchmarks() - : base("e2e") + public E2eBenchmarks() : base("e2e") { } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index f4ff744a7..21b15db2f 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using Draco.Compiler.Api.Syntax; @@ -155,22 +156,6 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; - var type = node.GetType(); - var newData = node switch - { - Api.Syntax.FunctionDeclarationSyntax _ => node, // always different, that what we want. - _ => type as object, - }; - - if (!newData.Equals(this.formatter.Scope.Data)) - { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = newData; - } - base.VisitDeclaration(node); } @@ -249,10 +234,9 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod { var closeScope = null as IDisposable; var kind = node.Operator.Kind; - if (!(this.formatter.Scope.Data?.Equals(kind) ?? false)) + if (node.Parent is not Api.Syntax.BinaryExpressionSyntax { Operator.Kind: var previousKind } || previousKind != kind) { closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); - this.formatter.Scope.Data = kind; } node.Left.Accept(this); @@ -287,6 +271,11 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSyntax node) { this.VisitDeclaration(node); + if (GetPrevious(node) != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; + } + var disposable = this.formatter.CreateScopeAfterNextToken(this.settings.Indentation); node.VisibilityModifier?.Accept(this); node.FunctionKeyword.Accept(this); @@ -407,4 +396,27 @@ public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSynt node.Value?.Accept(this); node.Semicolon.Accept(this); } + + private static Api.Syntax.SyntaxNode? GetPrevious(Api.Syntax.SyntaxNode node) + { + var previous = null as Api.Syntax.SyntaxNode; + foreach (var child in node.Parent!.Children) + { + if (child == node) return previous; + + // TODO: temp fix for AST problem. + if (child is IReadOnlyList list) + { + var previous2 = null as Api.Syntax.SyntaxNode; + foreach (var item in list) + { + if (item == node) return previous2; + previous2 = item; + } + } + previous = child; + } + + return null; + } } diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs index 92bfdb08e..81b04790a 100644 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -36,4 +36,18 @@ public void ThisFileShouldBeFormattedCorrectly() logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } + + [Fact] + public void AllFileShouldBeFormattedCorrectly() + { + var allFiles = Directory.GetFiles("../../../../", "*.cs", SearchOption.AllDirectories); + foreach (var file in allFiles) + { + var input = File.ReadAllText(file); + var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); + var formatted = CSharpFormatter.Format(tree); + logger.WriteLine(formatted); + Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); + } + } } diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 652789e3c..ac75af814 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -59,6 +59,7 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, + SyntaxKind.ColonToken => WhitespaceBehavior.PadAround, SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, @@ -91,13 +92,9 @@ public override void VisitToken(SyntaxToken node) public override void VisitClassDeclaration(ClassDeclarationSyntax node) { - if (!node.Equals(this.formatter.Scope.Data)) + if (GetPreviousNode(node) != null) { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = node; + this.formatter.CurrentToken.LeadingTrivia = [""]; } this.formatter.CurrentToken.DoesReturnLine = true; foreach (var attribute in node.AttributeLists) @@ -159,28 +156,18 @@ public override void VisitBlock(BlockSyntax node) public override void VisitUsingDirective(UsingDirectiveSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; - var newData = typeof(UsingStatementSyntax); - if (!newData.Equals(this.formatter.Scope.Data)) + if (GetPreviousNode(node) is not UsingDirectiveSyntax and not null) { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = newData; + this.formatter.CurrentToken.LeadingTrivia = [""]; } base.VisitUsingDirective(node); } public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) { - var newData = typeof(NamespaceDeclarationSyntax); - if (!newData.Equals(this.formatter.Scope.Data)) + if (GetPreviousNode(node) != null) { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = newData; + this.formatter.CurrentToken.LeadingTrivia = [""]; } this.formatter.CurrentToken.DoesReturnLine = true; base.VisitNamespaceDeclaration(node); @@ -189,29 +176,30 @@ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) { - var newData = typeof(FileScopedNamespaceDeclarationSyntax); - if (!newData.Equals(this.formatter.Scope.Data)) + if (GetPreviousNode(node) != null) { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = newData; + this.formatter.CurrentToken.LeadingTrivia = [""]; } + //var newData = typeof(FileScopedNamespaceDeclarationSyntax); + //if (!newData.Equals(this.formatter.Scope.Data)) + //{ + // if (this.formatter.Scope.Data != null) + // { + // this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. + // } + // this.formatter.Scope.Data = newData; + //} this.formatter.CurrentToken.DoesReturnLine = true; base.VisitFileScopedNamespaceDeclaration(node); } public override void VisitMethodDeclaration(MethodDeclarationSyntax node) { - if (!node.Equals(this.formatter.Scope.Data)) + if (GetPreviousNode(node) != null) { - if (this.formatter.Scope.Data != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - } - this.formatter.Scope.Data = node; + this.formatter.CurrentToken.LeadingTrivia = [""]; } + this.formatter.CurrentToken.DoesReturnLine = true; foreach (var attribute in node.AttributeLists) { @@ -241,4 +229,34 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node) .FirstOrDefault(); this.VisitToken(node.SemicolonToken); } + + public override void VisitFieldDeclaration(FieldDeclarationSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = true; + base.VisitFieldDeclaration(node); + } + + public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) + { + if (GetPreviousNode(node) != null) + { + this.formatter.CurrentToken.LeadingTrivia = [""]; + } + this.formatter.CurrentToken.DoesReturnLine = true; + base.VisitConstructorDeclaration(node); + } + + private static SyntaxNode? GetPreviousNode(SyntaxNode node) + { + var parent = node.Parent; + if (parent == null) return null; + var previous = null as SyntaxNode; + foreach (var child in parent.ChildNodes()) + { + if (child == node) return previous; + previous = child; + } + return null; + } + } diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index 5c7975e58..0749d96d5 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -34,12 +34,6 @@ public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriorit public Scope? Parent { get; } - /// - /// Arbitrary data that can be attached to the scope. - /// Currently only used to group similar binary expressions together. - /// - public object? Data { get; set; } - /// /// Represent if the scope is materialized or not. /// An unmaterialized scope is a potential scope, which is not folded yet. From c59fe6af4710823ad86a0e857bcab91c8bb3dc89 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Thu, 9 May 2024 00:36:00 +0200 Subject: [PATCH 62/76] More C# formatter progress. --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index ac75af814..23cf1f60b 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -47,19 +47,20 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) SyntaxKind.WhileKeyword => WhitespaceBehavior.PadAround, SyntaxKind.StaticKeyword => WhitespaceBehavior.PadAround, SyntaxKind.SealedKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.ForEachKeyword => WhitespaceBehavior.PadAround, SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, + SyntaxKind.CommaToken => WhitespaceBehavior.PadRight, SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, SyntaxKind.OpenBraceToken => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - SyntaxKind.OpenParenToken => WhitespaceBehavior.Whitespace, + SyntaxKind.OpenParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, - SyntaxKind.ColonToken => WhitespaceBehavior.PadAround, SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, @@ -133,6 +134,12 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node) this.VisitToken(node.SemicolonToken); } + public override void VisitBaseList(BaseListSyntax node) + { + this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; + base.VisitBaseList(node); + } + public override void VisitBlock(BlockSyntax node) { foreach (var attribute in node.AttributeLists) @@ -243,7 +250,25 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no this.formatter.CurrentToken.LeadingTrivia = [""]; } this.formatter.CurrentToken.DoesReturnLine = true; - base.VisitConstructorDeclaration(node); + foreach (var attribute in node.AttributeLists) + { + attribute.Accept(this); + } + this.formatter.CurrentToken.DoesReturnLine = true; + foreach (var modifier in node.Modifiers) + { + this.VisitToken(modifier); + } + this.VisitToken(node.Identifier); + node.ParameterList.Accept(this); + if (node.Initializer != null) + { + this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; + node.Initializer.Accept(this); + } + node.Body?.Accept(this); + node.ExpressionBody?.Accept(this); + this.VisitToken(node.SemicolonToken); } private static SyntaxNode? GetPreviousNode(SyntaxNode node) @@ -253,6 +278,7 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no var previous = null as SyntaxNode; foreach (var child in parent.ChildNodes()) { + if (child is ParameterListSyntax) continue; if (child == node) return previous; previous = child; } From 229962db995a44622a69e3e37479f4c182931c61 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 11 May 2024 22:36:55 +0200 Subject: [PATCH 63/76] Created CSharpFormatterSettings plus a Draco specific settings. --- .../E2eBenchmarks.cs | 3 ++- .../FormatterTest.cs | 6 +++--- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 14 ++++++++++---- .../CSharpFormatterSettings.cs | 19 +++++++++++++++++++ .../FormatterSettings.cs | 2 +- 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs diff --git a/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs b/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs index 86ae261b2..0b4a1aa88 100644 --- a/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs +++ b/src/Draco.Compiler.Benchmarks/E2eBenchmarks.cs @@ -11,7 +11,8 @@ public class E2eBenchmarks : FolderBenchmarkBase { private MemoryStream peStream = null!; - public E2eBenchmarks() : base("e2e") + public E2eBenchmarks() + : base("e2e") { } diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs index 81b04790a..86b39552b 100644 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs @@ -22,7 +22,7 @@ public static void Main() """"; var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree); + var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } @@ -32,7 +32,7 @@ public void ThisFileShouldBeFormattedCorrectly() { var input = File.ReadAllText("../../../../Draco.Formatter.Csharp.Tests/FormatterTest.cs"); var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree); + var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } @@ -45,7 +45,7 @@ public void AllFileShouldBeFormattedCorrectly() { var input = File.ReadAllText(file); var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree); + var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); logger.WriteLine(formatted); Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); } diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 23cf1f60b..dda642be3 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -6,14 +6,14 @@ namespace Draco.Formatter.Csharp; -public sealed class CSharpFormatter(FormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) +public sealed class CSharpFormatter(CSharpFormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) { - private readonly FormatterSettings settings = settings; + private readonly CSharpFormatterSettings settings = settings; private FormatterEngine formatter = null!; - public static string Format(SyntaxTree tree, FormatterSettings? settings = null) + public static string Format(SyntaxTree tree, CSharpFormatterSettings? settings = null) { - settings ??= FormatterSettings.Default; + settings ??= CSharpFormatterSettings.Default; var formatter = new CSharpFormatter(settings); formatter.Visit(tree.GetRoot()); @@ -263,6 +263,12 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no node.ParameterList.Accept(this); if (node.Initializer != null) { + using var scope = this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible); + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + if (this.settings.NewLineBeforeConstructorInitializer) + { + this.formatter.Scope.IsMaterialized.Value = true; + } this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; node.Initializer.Accept(this); } diff --git a/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs b/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs new file mode 100644 index 000000000..bf9e833c1 --- /dev/null +++ b/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Draco.Compiler.Internal.Syntax.Formatting; + +namespace Draco.Formatter.Csharp; +public class CSharpFormatterSettings : FormatterSettings +{ + public static new CSharpFormatterSettings Default { get; } = new(); + public static CSharpFormatterSettings DracoStyle { get; } = new() + { + NewLineBeforeConstructorInitializer = true, + LineWidth = 120 + }; + + public bool NewLineBeforeConstructorInitializer { get; init; } +} diff --git a/src/Draco.FormatterEngine/FormatterSettings.cs b/src/Draco.FormatterEngine/FormatterSettings.cs index 8db2f3169..743128e34 100644 --- a/src/Draco.FormatterEngine/FormatterSettings.cs +++ b/src/Draco.FormatterEngine/FormatterSettings.cs @@ -5,7 +5,7 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; /// /// The settings of the formatter. /// -public sealed class FormatterSettings +public class FormatterSettings { /// /// The default formatting settings. From 6cd59641af42141e19196ceb92cbb93207e72c5c Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 12 May 2024 14:25:16 +0200 Subject: [PATCH 64/76] Fixed some PR feedback. --- .../Syntax/Formatting/DracoFormatter.cs | 40 +++++----- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 38 +++++----- src/Draco.FormatterEngine/FoldPriority.cs | 12 +++ src/Draco.FormatterEngine/FormatterEngine.cs | 6 +- src/Draco.FormatterEngine/LineStateMachine.cs | 4 +- src/Draco.FormatterEngine/Scope.cs | 73 +++++++++++++++---- src/Draco.FormatterEngine/TokenMetadata.cs | 25 +++++-- .../WhitespaceBehavior.cs | 6 +- 8 files changed, 137 insertions(+), 67 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 21b15db2f..f68937ec9 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -65,7 +65,7 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) TokenKind.KeywordFunc => WhitespaceBehavior.PadAround, TokenKind.Semicolon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - TokenKind.CurlyOpen => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + TokenKind.CurlyOpen => WhitespaceBehavior.SpaceBefore | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, TokenKind.ParenOpen => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, TokenKind.ParenClose => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, TokenKind.InterpolationStart => WhitespaceBehavior.Whitespace, @@ -73,25 +73,25 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) TokenKind.Colon => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, TokenKind.Assign => WhitespaceBehavior.PadAround, - TokenKind.LineStringStart => WhitespaceBehavior.PadLeft, - TokenKind.MultiLineStringStart => WhitespaceBehavior.PadLeft, - TokenKind.Plus => WhitespaceBehavior.PadLeft, - TokenKind.Minus => WhitespaceBehavior.PadLeft, - TokenKind.Star => WhitespaceBehavior.PadLeft, - TokenKind.Slash => WhitespaceBehavior.PadLeft, - TokenKind.PlusAssign => WhitespaceBehavior.PadLeft, - TokenKind.MinusAssign => WhitespaceBehavior.PadLeft, - TokenKind.StarAssign => WhitespaceBehavior.PadLeft, - TokenKind.SlashAssign => WhitespaceBehavior.PadLeft, - TokenKind.GreaterEqual => WhitespaceBehavior.PadLeft, - TokenKind.GreaterThan => WhitespaceBehavior.PadLeft, - TokenKind.LessEqual => WhitespaceBehavior.PadLeft, - TokenKind.LessThan => WhitespaceBehavior.PadLeft, - TokenKind.Equal => WhitespaceBehavior.PadLeft, - TokenKind.LiteralFloat => WhitespaceBehavior.PadLeft, - TokenKind.LiteralInteger => WhitespaceBehavior.PadLeft, - - TokenKind.Identifier => WhitespaceBehavior.PadLeft, + TokenKind.LineStringStart => WhitespaceBehavior.SpaceBefore, + TokenKind.MultiLineStringStart => WhitespaceBehavior.SpaceBefore, + TokenKind.Plus => WhitespaceBehavior.SpaceBefore, + TokenKind.Minus => WhitespaceBehavior.SpaceBefore, + TokenKind.Star => WhitespaceBehavior.SpaceBefore, + TokenKind.Slash => WhitespaceBehavior.SpaceBefore, + TokenKind.PlusAssign => WhitespaceBehavior.SpaceBefore, + TokenKind.MinusAssign => WhitespaceBehavior.SpaceBefore, + TokenKind.StarAssign => WhitespaceBehavior.SpaceBefore, + TokenKind.SlashAssign => WhitespaceBehavior.SpaceBefore, + TokenKind.GreaterEqual => WhitespaceBehavior.SpaceBefore, + TokenKind.GreaterThan => WhitespaceBehavior.SpaceBefore, + TokenKind.LessEqual => WhitespaceBehavior.SpaceBefore, + TokenKind.LessThan => WhitespaceBehavior.SpaceBefore, + TokenKind.Equal => WhitespaceBehavior.SpaceBefore, + TokenKind.LiteralFloat => WhitespaceBehavior.SpaceBefore, + TokenKind.LiteralInteger => WhitespaceBehavior.SpaceBefore, + + TokenKind.Identifier => WhitespaceBehavior.SpaceBefore, _ => WhitespaceBehavior.NoFormatting }; diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index dda642be3..c4f006767 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -51,10 +51,10 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.CommaToken => WhitespaceBehavior.PadRight, + SyntaxKind.CommaToken => WhitespaceBehavior.SpaceAfter, SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - SyntaxKind.OpenBraceToken => WhitespaceBehavior.PadLeft | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, + SyntaxKind.OpenBraceToken => WhitespaceBehavior.SpaceBefore | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, SyntaxKind.OpenParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, @@ -62,23 +62,23 @@ public override void VisitCompilationUnit(CompilationUnitSyntax node) SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, - SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.PadLeft, - SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.PadLeft, - SyntaxKind.PlusToken => WhitespaceBehavior.PadLeft, - SyntaxKind.MinusToken => WhitespaceBehavior.PadLeft, - SyntaxKind.AsteriskToken => WhitespaceBehavior.PadLeft, - SyntaxKind.SlashToken => WhitespaceBehavior.PadLeft, - SyntaxKind.PlusEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.MinusEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.SlashEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.GreaterThanToken => WhitespaceBehavior.PadLeft, - SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.PadLeft, - SyntaxKind.LessThanToken => WhitespaceBehavior.PadLeft, - SyntaxKind.NumericLiteralToken => WhitespaceBehavior.PadLeft, - - SyntaxKind.IdentifierToken => WhitespaceBehavior.PadLeft, + SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.PlusToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.MinusToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.AsteriskToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.SlashToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.PlusEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.MinusEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.SlashEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.GreaterThanToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.LessThanToken => WhitespaceBehavior.SpaceBefore, + SyntaxKind.NumericLiteralToken => WhitespaceBehavior.SpaceBefore, + + SyntaxKind.IdentifierToken => WhitespaceBehavior.SpaceBefore, _ => WhitespaceBehavior.NoFormatting }; diff --git a/src/Draco.FormatterEngine/FoldPriority.cs b/src/Draco.FormatterEngine/FoldPriority.cs index dc47f1f3c..642883c47 100644 --- a/src/Draco.FormatterEngine/FoldPriority.cs +++ b/src/Draco.FormatterEngine/FoldPriority.cs @@ -1,8 +1,20 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; +/// +/// The priority when folding a scope. +/// public enum FoldPriority { + /// + /// The scope will never be folded. Mostly used for scopes that are already folded. + /// Never, + /// + /// This scope will be folded as late as possible. + /// AsLateAsPossible, + /// + /// This scope will be folded as soon as possible. + /// AsSoonAsPossible } diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index d6b2b35db..211632e05 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -6,20 +6,20 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; public sealed class FormatterEngine { - private readonly Disposable scopePopper; + private readonly ScopeGuard scopePopper; private readonly TokenMetadata[] tokensMetadata; private readonly FormatterSettings settings; public FormatterEngine(int tokenCount, FormatterSettings settings) { - this.scopePopper = new Disposable(this); + this.scopePopper = new ScopeGuard(this); this.tokensMetadata = new TokenMetadata[tokenCount]; this.Scope = new(null, settings, FoldPriority.Never, ""); this.Scope.IsMaterialized.Value = true; this.settings = settings; } - private class Disposable(FormatterEngine formatter) : IDisposable + private class ScopeGuard(FormatterEngine formatter) : IDisposable { public void Dispose() => formatter.PopScope(); } diff --git a/src/Draco.FormatterEngine/LineStateMachine.cs b/src/Draco.FormatterEngine/LineStateMachine.cs index c4ab708e6..f5e6df2a7 100644 --- a/src/Draco.FormatterEngine/LineStateMachine.cs +++ b/src/Draco.FormatterEngine/LineStateMachine.cs @@ -27,7 +27,7 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en } } - var requestedLeftPad = this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.PadLeft); + var requestedLeftPad = this.prevTokenNeedRightPad || metadata.Kind.HasFlag(WhitespaceBehavior.SpaceBefore); var haveWhitespace = (metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken) || this.previousIsWhitespace); var shouldLeftPad = (requestedLeftPad && !haveWhitespace); @@ -37,7 +37,7 @@ public void AddToken(TokenMetadata metadata, FormatterSettings settings, bool en } this.Append(metadata.Text); - this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.PadRight); + this.prevTokenNeedRightPad = metadata.Kind.HasFlag(WhitespaceBehavior.SpaceAfter); this.previousIsWhitespace = metadata.Kind.HasFlag(WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken); } diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index 0749d96d5..c8df9a95f 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -4,6 +4,9 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; +/// +/// Represent an indentation level in the code. +/// public sealed class Scope { private readonly string? indentation; @@ -21,17 +24,34 @@ private Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriori this.FoldPriority = foldPriority; } + /// + /// Create a scope that add a new indentation level. + /// + /// The parent scope. + /// The settings of the formatter. + /// The fold priority of the scope. + /// The indentation this scope will add. public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) { this.indentation = indentation; } + /// + /// Create a scope that override the indentation level and follow the position of a token instead. + /// + /// The parent scope. + /// The settings of the formatter. + /// The fold priority of the scope. + /// The list of the tokens of the formatter and the index of the token to follow the position. public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, (IReadOnlyList tokens, int indexOfLevelingToken) levelingToken) : this(parent, settings, foldPriority) { this.levelingToken = levelingToken; } + /// + /// The parent scope of this scope. + /// public Scope? Parent { get; } /// @@ -47,6 +67,9 @@ public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriorit /// public MutableBox IsMaterialized { get; } = new MutableBox(null); + /// + /// All the indentation parts of the current scope and it's parents. + /// public IEnumerable CurrentTotalIndent { get @@ -91,10 +114,19 @@ int GetStartLineTokenIndex() } } + /// + /// The fold priority of the scope. It's used to determine which scope should be folded first when folding. + /// public FoldPriority FoldPriority { get; } + /// + /// All the parents of this scope, plus this scope. + /// public IEnumerable ThisAndParents => this.Parents.Prepend(this); + /// + /// All the parents of this scope. + /// public IEnumerable Parents { get @@ -114,27 +146,38 @@ public IEnumerable Parents /// The scope that have been fold, else if no scope can be fold. public Scope? Fold() { - foreach (var item in this.ThisAndParents.Reverse()) + var asSoonAsPossible = this.ThisAndParents + .Reverse() + .Where(item => !item.IsMaterialized.Value.HasValue) + .Where(item => item.FoldPriority == FoldPriority.AsSoonAsPossible) + .FirstOrDefault(); + + if (asSoonAsPossible != null) { - if (item.IsMaterialized.Value.HasValue) continue; - if (item.FoldPriority == FoldPriority.AsSoonAsPossible) - { - item.IsMaterialized.Value = true; - return item; - } + asSoonAsPossible.IsMaterialized.Value = true; + return asSoonAsPossible; } - foreach (var item in this.ThisAndParents) + var asLateAsPossible = this.ThisAndParents + .Where(item => !item.IsMaterialized.Value.HasValue) + .Where(item => item.FoldPriority == FoldPriority.AsLateAsPossible) + .FirstOrDefault(); + + if (asLateAsPossible != null) { - if (item.IsMaterialized.Value.HasValue) continue; - if (item.FoldPriority == FoldPriority.AsLateAsPossible) - { - item.IsMaterialized.Value = true; - return item; - } + asLateAsPossible.IsMaterialized.Value = true; + return asLateAsPossible; } + return null; } - public override string ToString() => $"{(this.IsMaterialized.Value.HasValue ? this.IsMaterialized.Value.Value ? "M" : "U" : "?")}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; + /// + /// A debug string. + /// + public override string ToString() + { + var materialized = (this.IsMaterialized.Value.HasValue ? this.IsMaterialized.Value.Value ? "M" : "U" : "?"); + return $"{materialized}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; + } } diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index 9084e07f9..e521c8fb6 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -3,6 +3,24 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; +/// +/// Represent the formatting metadata of a token. +/// +/// The whitespace behavior of this token. +/// The text of the token. +/// +/// +/// An optional reference to a nullable boolean, indicate whether the line returns or not. +/// +/// +/// If the field is null, it means there was no decision was made on this token, we default to not returning a line. +/// +/// +/// If the field is set to a reference, but the value of the Box null, it's because this is a pending +/// +/// +/// The deepest scope this token belong to. +/// Lines of the leading trivia. Each line have an newline apppended to it when outputing the text. public record struct TokenMetadata( WhitespaceBehavior Kind, string Text, @@ -12,11 +30,8 @@ public record struct TokenMetadata( { public readonly override string ToString() { - var merged = string.Join( - ',', - this.ScopeInfo.ThisAndParents - ); - var returnLine = !(this.DoesReturnLine?.Value.HasValue ?? false) ? "?" : this.DoesReturnLine.Value.Value ? "Y" : "N"; + var merged = string.Join(',', this.ScopeInfo.ThisAndParents); + var returnLine = this.DoesReturnLine?.Value.HasValue == true ? this.DoesReturnLine.Value.Value ? "Y" : "N" : "?"; return $"{merged} {returnLine}"; } } diff --git a/src/Draco.FormatterEngine/WhitespaceBehavior.cs b/src/Draco.FormatterEngine/WhitespaceBehavior.cs index 374e9fbad..dbee2d938 100644 --- a/src/Draco.FormatterEngine/WhitespaceBehavior.cs +++ b/src/Draco.FormatterEngine/WhitespaceBehavior.cs @@ -15,11 +15,11 @@ public enum WhitespaceBehavior /// /// Add a left whitespace if necessary. /// - PadLeft = 1 << 0, + SpaceBefore = 1 << 0, /// /// Add a right whitespace if necessary /// - PadRight = 1 << 1, + SpaceAfter = 1 << 1, /// /// The next token will think of this token as a whitespace. /// @@ -35,7 +35,7 @@ public enum WhitespaceBehavior /// /// Add a whitespace on the left and right, if necessary. /// - PadAround = PadLeft | PadRight, + PadAround = SpaceBefore | SpaceAfter, /// /// This token behave as a whitespace. /// From 175d0a89d775a42005112f0b8db779813d2e7b47 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 14 May 2024 00:53:14 +0200 Subject: [PATCH 65/76] Don't merge tokens that can fuse together. --- .../Syntax/ParseTreeFormatterTests.cs | 21 +++++++++++++++++++ .../Syntax/Formatting/DracoFormatter.cs | 20 ++++++++++++++++-- .../FormatterSettings.cs | 5 +++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs index f1ce97de8..b19d9c275 100644 --- a/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs +++ b/src/Draco.Compiler.Tests/Syntax/ParseTreeFormatterTests.cs @@ -1,4 +1,5 @@ using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Syntax.Formatting; using Xunit.Abstractions; namespace Draco.Compiler.Tests.Syntax; @@ -351,4 +352,24 @@ func foo(a: int32) { logger.WriteLine(actual); Assert.Equal(input, actual, ignoreLineEndingDifferences: true); } + + [Fact] + public void DontMergeTokens() + { + var input = """" + func foo() { + var x + + = + 1; + } + + """"; + + var actual = SyntaxTree.Parse(input).Format(new FormatterSettings() + { + SpaceAroundBinaryOperators = false + }); + logger.WriteLine(actual); + Assert.Equal(input, actual, ignoreLineEndingDifferences: true); + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index f68937ec9..257e530ba 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -9,7 +9,6 @@ internal sealed class DracoFormatter : Api.Syntax.SyntaxVisitor { private readonly FormatterSettings settings; private FormatterEngine formatter = null!; - private DracoFormatter(FormatterSettings settings) { this.settings = settings; @@ -99,7 +98,24 @@ public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) { this.HandleTokenComments(node); - this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); + var formattingKind = GetFormattingTokenKind(node); + + var notFirstToken = this.formatter.CurrentIdx > 0; + var doesntInsertSpace = !formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); + var insertNewline = this.formatter.CurrentToken.DoesReturnLine?.Value == true; + var notWhitespaceNode = node.Kind != TokenKind.StringNewline && node.Kind != TokenKind.EndOfInput; + if (doesntInsertSpace && notFirstToken && !insertNewline && notWhitespaceNode) + { + var lexer = new Lexer(SourceReader.From(this.formatter.PreviousToken.Text + node.Text), default); + lexer.Lex(); + var secondToken = lexer.Lex(); + if (secondToken.Kind == TokenKind.EndOfInput) // this means the 2 tokens merged in a single one, we want to avoid that. + { + this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; + } + } + + this.formatter.SetCurrentTokenInfo(formattingKind, node.Text); base.VisitSyntaxToken(node); } diff --git a/src/Draco.FormatterEngine/FormatterSettings.cs b/src/Draco.FormatterEngine/FormatterSettings.cs index 743128e34..3d2766e6b 100644 --- a/src/Draco.FormatterEngine/FormatterSettings.cs +++ b/src/Draco.FormatterEngine/FormatterSettings.cs @@ -26,4 +26,9 @@ public class FormatterSettings /// The max line width the formatter will try to respect. /// public int LineWidth { get; init; } = 160; + + /// + /// Insert a whitespace around binary operators. + /// + public bool SpaceAroundBinaryOperators { get; init; } = true; } From e3a33cebeae1e4d42bed9928bebc886395508d9d Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 14 May 2024 20:55:02 +0200 Subject: [PATCH 66/76] Improved C# formatter. --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index c4f006767..48b67e920 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -1,4 +1,6 @@ +using System.Diagnostics.Metrics; using System.Linq; +using System.Resources; using Draco.Compiler.Internal.Syntax.Formatting; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -88,7 +90,23 @@ public override void VisitToken(SyntaxToken node) if (node.IsKind(SyntaxKind.None)) return; base.VisitToken(node); - this.formatter.SetCurrentTokenInfo(GetFormattingTokenKind(node), node.Text); + var formattingKind = GetFormattingTokenKind(node); + + var notFirstToken = this.formatter.CurrentIdx > 0; + var doesntInsertSpace = !formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); + var insertNewline = this.formatter.CurrentToken.DoesReturnLine?.Value == true; + var notWhitespaceNode = !node.IsKind(SyntaxKind.EndOfFileToken); + if (doesntInsertSpace && notFirstToken && !insertNewline && notWhitespaceNode) + { + var tokens = SyntaxFactory.ParseTokens(this.formatter.PreviousToken.Text + node.Text); + var secondToken = tokens.Skip(1).First(); + if (secondToken.IsKind(SyntaxKind.EndOfFileToken)) // this means the 2 tokens merged in a single one, we want to avoid that. + { + this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; + } + } + + this.formatter.SetCurrentTokenInfo(formattingKind, node.Text); } public override void VisitClassDeclaration(ClassDeclarationSyntax node) @@ -277,6 +295,18 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no this.VisitToken(node.SemicolonToken); } + public override void VisitArgumentList(ArgumentListSyntax node) => + this.formatter.CreateMaterializableScope(this.settings.Indentation, + FoldPriority.AsSoonAsPossible, + () => base.VisitArgumentList(node) + ); + + public override void VisitArgument(ArgumentSyntax node) + { + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + base.VisitArgument(node); + } + private static SyntaxNode? GetPreviousNode(SyntaxNode node) { var parent = node.Parent; From 20dc27b7b1e9244c1199e9db24c181e26113bfe4 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Tue, 14 May 2024 23:49:31 +0200 Subject: [PATCH 67/76] ohno --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 48b67e920..89f193c7d 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -205,15 +205,7 @@ public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDecl { this.formatter.CurrentToken.LeadingTrivia = [""]; } - //var newData = typeof(FileScopedNamespaceDeclarationSyntax); - //if (!newData.Equals(this.formatter.Scope.Data)) - //{ - // if (this.formatter.Scope.Data != null) - // { - // this.formatter.CurrentToken.LeadingTrivia = [""]; // a newline is created between each leading trivia. - // } - // this.formatter.Scope.Data = newData; - //} + this.formatter.CurrentToken.DoesReturnLine = true; base.VisitFileScopedNamespaceDeclaration(node); } @@ -255,6 +247,22 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node) this.VisitToken(node.SemicolonToken); } + public override void VisitInvocationExpression(InvocationExpressionSyntax node) => base.VisitInvocationExpression(node); + + public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) + { + node.Expression.Accept(this); + + if (node.Parent is InvocationExpressionSyntax invocation) + { + if(invocation.Parent is MemberAccessExpressionSyntax parent) + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + } + + this.VisitToken(node.OperatorToken); + node.Name.Accept(this); + } + public override void VisitFieldDeclaration(FieldDeclarationSyntax node) { this.formatter.CurrentToken.DoesReturnLine = true; From 4b30c73b3b8b1291e407cd00eaa90fdfec2fa29e Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 2 Jun 2024 22:16:25 +0200 Subject: [PATCH 68/76] Update src/Draco.FormatterEngine/Scope.cs Co-authored-by: LPeter1997 --- src/Draco.FormatterEngine/Scope.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index c8df9a95f..3b26a8a50 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -31,7 +31,8 @@ private Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriori /// The settings of the formatter. /// The fold priority of the scope. /// The indentation this scope will add. - public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) : this(parent, settings, foldPriority) + public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriority, string indentation) + : this(parent, settings, foldPriority) { this.indentation = indentation; } From 57c6da8b868f0da6f7e510a3cf27aece6c915eba Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 2 Jun 2024 22:17:09 +0200 Subject: [PATCH 69/76] Update src/Draco.Formatter.Csharp/CSharpFormatter.cs Co-authored-by: LPeter1997 --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 89f193c7d..f25f928d7 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -328,5 +328,4 @@ public override void VisitArgument(ArgumentSyntax node) } return null; } - } From 694eeb1a180e26e729b06e9256d1cff1321d3704 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 3 Jun 2024 01:36:00 +0200 Subject: [PATCH 70/76] Dropped box to use a future instead. --- .../Syntax/Formatting/DracoFormatter.cs | 38 +++++----- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 36 +++++----- src/Draco.FormatterEngine/Box.cs | 9 --- src/Draco.FormatterEngine/FormatterEngine.cs | 22 +++--- src/Draco.FormatterEngine/Future.cs | 69 +++++++++++++++++++ src/Draco.FormatterEngine/MutableBox.cs | 10 --- src/Draco.FormatterEngine/Scope.cs | 14 ++-- src/Draco.FormatterEngine/TokenMetadata.cs | 4 +- 8 files changed, 129 insertions(+), 73 deletions(-) delete mode 100644 src/Draco.FormatterEngine/Box.cs create mode 100644 src/Draco.FormatterEngine/Future.cs delete mode 100644 src/Draco.FormatterEngine/MutableBox.cs diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 257e530ba..239bb52f1 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -102,7 +102,8 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) var notFirstToken = this.formatter.CurrentIdx > 0; var doesntInsertSpace = !formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); - var insertNewline = this.formatter.CurrentToken.DoesReturnLine?.Value == true; + var doesReturnLine = this.formatter.CurrentToken.DoesReturnLine; + var insertNewline = doesReturnLine is not null && doesReturnLine.IsCompleted && doesReturnLine.Value; var notWhitespaceNode = node.Kind != TokenKind.StringNewline && node.Kind != TokenKind.EndOfInput; if (doesntInsertSpace && notFirstToken && !insertNewline && notWhitespaceNode) { @@ -132,7 +133,7 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) if (comment != null) { this.formatter.CurrentToken.Text = node.Text + " " + comment; - this.formatter.NextToken.DoesReturnLine = true; + this.formatter.NextToken.DoesReturnLine = new Future(true); } } var leadingComments = node.LeadingTrivia @@ -143,7 +144,7 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) this.formatter.CurrentToken.LeadingTrivia.AddRange(leadingComments); if (this.formatter.CurrentToken.LeadingTrivia.Count > 0) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); } } @@ -171,7 +172,7 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); base.VisitDeclaration(node); } @@ -182,10 +183,10 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod node.OpenQuotes.Accept(this); foreach (var item in node.Parts.Tokens) { - this.formatter.CurrentToken.DoesReturnLine = false; + this.formatter.CurrentToken.DoesReturnLine = new Future(false); item.Accept(this); } - this.formatter.CurrentToken.DoesReturnLine = false; + this.formatter.CurrentToken.DoesReturnLine = new Future(false); node.CloseQuotes.Accept(this); return; } @@ -212,7 +213,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var tokenText = curr.Tokens.First().ValueText!; if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); this.formatter.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); if (i > 0 && node.Parts[i - 1].IsNewLine) { @@ -228,7 +229,7 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod var newLines = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).ToArray(); if (newLines.Length > 0) { - this.formatter.NextToken.DoesReturnLine = true; + this.formatter.NextToken.DoesReturnLine = new Future(true); this.formatter.CurrentToken.Text = string.Concat(Enumerable.Repeat(this.settings.Newline, newLines.Length - 1).Prepend(token.Text)); } token.Accept(this); @@ -239,10 +240,10 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod for (var j = 0; j < tokenCount; j++) { - this.formatter.TokensMetadata[startIdx + j].DoesReturnLine ??= false; + this.formatter.TokensMetadata[startIdx + j].DoesReturnLine ??= new Future(false); } } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); node.CloseQuotes.Accept(this); } @@ -270,7 +271,7 @@ public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax n { node.OpenBrace.Accept(this); this.formatter.CreateScope(this.settings.Indentation, () => node.Statements.Accept(this)); - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); node.CloseBrace.Accept(this); } @@ -312,13 +313,13 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) { if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); this.formatter.CurrentToken.Kind = WhitespaceBehavior.RemoveOneIndentation; } else { var shouldBeMultiLine = node.Parent is Api.Syntax.BlockExpressionSyntax || node.Parent is Api.Syntax.BlockFunctionBodySyntax; - this.formatter.CurrentToken.DoesReturnLine = shouldBeMultiLine; + this.formatter.CurrentToken.DoesReturnLine = new Future(shouldBeMultiLine); } base.VisitStatement(node); } @@ -351,7 +352,7 @@ public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => var firstToken = this.formatter.TokensMetadata[firstTokenIdx]; if (firstToken.DoesReturnLine?.Value ?? false) { - firstToken.ScopeInfo.IsMaterialized.Value = true; + firstToken.ScopeInfo.IsMaterialized.SetValue(true); } }); node.CloseParen.Accept(this); @@ -366,7 +367,7 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { if (node.IsElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); } else { @@ -384,7 +385,8 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // if (blabla) // an expression; // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - this.formatter.Scope.IsMaterialized.Value ??= false; + var isMaterialized = this.formatter.Scope.IsMaterialized; + if (!isMaterialized.IsCompleted) isMaterialized.SetValue(false); node.OpenBrace.Accept(this); @@ -393,12 +395,12 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) node.Statements.Accept(this); if (node.Value != null) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); node.Value.Accept(this); } }); node.CloseBrace.Accept(this); - this.formatter.PreviousToken.DoesReturnLine = true; + this.formatter.PreviousToken.DoesReturnLine = new Future(true); } public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index f25f928d7..2b6d062b2 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -115,7 +115,7 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node) { this.formatter.CurrentToken.LeadingTrivia = [""]; } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); foreach (var attribute in node.AttributeLists) { attribute.Accept(this); @@ -137,17 +137,17 @@ public override void VisitClassDeclaration(ClassDeclarationSyntax node) constraint.Accept(this); } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); this.VisitToken(node.OpenBraceToken); this.formatter.CreateScope(this.settings.Indentation, () => { foreach (var member in node.Members) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); member.Accept(this); } }); - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); this.VisitToken(node.CloseBraceToken); this.VisitToken(node.SemicolonToken); } @@ -164,23 +164,23 @@ public override void VisitBlock(BlockSyntax node) { attribute.Accept(this); } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); this.VisitToken(node.OpenBraceToken); this.formatter.CreateScope(this.settings.Indentation, () => { foreach (var statement in node.Statements) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); statement.Accept(this); } }); - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); this.VisitToken(node.CloseBraceToken); } public override void VisitUsingDirective(UsingDirectiveSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); if (GetPreviousNode(node) is not UsingDirectiveSyntax and not null) { this.formatter.CurrentToken.LeadingTrivia = [""]; @@ -194,7 +194,7 @@ public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) { this.formatter.CurrentToken.LeadingTrivia = [""]; } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); base.VisitNamespaceDeclaration(node); } @@ -206,7 +206,7 @@ public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDecl this.formatter.CurrentToken.LeadingTrivia = [""]; } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); base.VisitFileScopedNamespaceDeclaration(node); } @@ -217,12 +217,12 @@ public override void VisitMethodDeclaration(MethodDeclarationSyntax node) this.formatter.CurrentToken.LeadingTrivia = [""]; } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); foreach (var attribute in node.AttributeLists) { attribute.Accept(this); } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); foreach (var modifier in node.Modifiers) { this.VisitToken(modifier); @@ -255,8 +255,8 @@ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax no if (node.Parent is InvocationExpressionSyntax invocation) { - if(invocation.Parent is MemberAccessExpressionSyntax parent) - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + if (invocation.Parent is MemberAccessExpressionSyntax parent) + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; } this.VisitToken(node.OperatorToken); @@ -265,7 +265,7 @@ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax no public override void VisitFieldDeclaration(FieldDeclarationSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); base.VisitFieldDeclaration(node); } @@ -275,12 +275,12 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no { this.formatter.CurrentToken.LeadingTrivia = [""]; } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); foreach (var attribute in node.AttributeLists) { attribute.Accept(this); } - this.formatter.CurrentToken.DoesReturnLine = true; + this.formatter.CurrentToken.DoesReturnLine = new Future(true); foreach (var modifier in node.Modifiers) { this.VisitToken(modifier); @@ -293,7 +293,7 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; if (this.settings.NewLineBeforeConstructorInitializer) { - this.formatter.Scope.IsMaterialized.Value = true; + this.formatter.Scope.IsMaterialized.SetValue(true); } this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; node.Initializer.Accept(this); diff --git a/src/Draco.FormatterEngine/Box.cs b/src/Draco.FormatterEngine/Box.cs deleted file mode 100644 index 732f47431..000000000 --- a/src/Draco.FormatterEngine/Box.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Draco.Compiler.Internal.Syntax.Formatting; - -public class Box(T value) -{ - protected T value = value; - public T Value => this.value; - - public static implicit operator Box(T value) => new(value); -} diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index 211632e05..de79a463f 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -15,7 +15,7 @@ public FormatterEngine(int tokenCount, FormatterSettings settings) this.scopePopper = new ScopeGuard(this); this.tokensMetadata = new TokenMetadata[tokenCount]; this.Scope = new(null, settings, FoldPriority.Never, ""); - this.Scope.IsMaterialized.Value = true; + this.Scope.IsMaterialized.SetValue(true); this.settings = settings; } @@ -53,7 +53,7 @@ public void SetCurrentTokenInfo(WhitespaceBehavior kind, string text) public IDisposable CreateScope(string indentation) { this.Scope = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); - this.Scope.IsMaterialized.Value = true; + this.Scope.IsMaterialized.SetValue(true); return this.scopePopper; } @@ -65,7 +65,7 @@ public void CreateScope(string indentation, Action action) public IDisposable CreateScopeAfterNextToken(string indentation) { this.scopeForNextToken = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); - this.scopeForNextToken.IsMaterialized.Value = true; + this.scopeForNextToken.IsMaterialized.SetValue(true); return this.scopePopper; } @@ -100,7 +100,7 @@ public static string Format(FormatterSettings settings, IReadOnlyList metadatas, Form for (var x = 0; x < metadatas.Count; x++) { var curr = metadatas[x]; - if (curr.DoesReturnLine?.Value ?? false) // if it's a new line + if (curr.DoesReturnLine?.IsCompleted == true && curr.DoesReturnLine.Value) // if it's a new line { // we recreate a state machine for the new line. stateMachine = new LineStateMachine(string.Concat(curr.ScopeInfo.CurrentTotalIndent)); @@ -136,9 +136,13 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form if (stateMachine.LineWidth <= settings.LineWidth) { // we clear the folded scope, because the line is complete and we won't need it anymore. - if (x != metadatas.Count - 1 && (metadatas[x + 1].DoesReturnLine?.Value ?? false)) + if (x != metadatas.Count - 1) { - foldedScopes.Clear(); + var doesReturnLine = metadatas[x + 1].DoesReturnLine; + if (doesReturnLine?.IsCompleted == true && doesReturnLine.Value) + { + foldedScopes.Clear(); + } } continue; } @@ -161,7 +165,7 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form for (var i = x - 1; i >= currentLineStart; i--) { var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.IsMaterialized?.IsCompleted == true && scope.IsMaterialized.Value) continue; if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; var prevFolded = scope.Fold(); if (prevFolded != null) @@ -191,7 +195,7 @@ void Backtrack() { foreach (var scope in foldedScopes) { - scope.IsMaterialized.Value = null; + scope.IsMaterialized.Reset(); } foldedScopes.Clear(); x = currentLineStart - 1; diff --git a/src/Draco.FormatterEngine/Future.cs b/src/Draco.FormatterEngine/Future.cs new file mode 100644 index 000000000..f98632baf --- /dev/null +++ b/src/Draco.FormatterEngine/Future.cs @@ -0,0 +1,69 @@ +using System; + +namespace Draco.Compiler.Internal.Syntax.Formatting; + +/// +/// Represent a value that may be set later. +/// +/// The type of the value +public class Future +{ + /// + /// A future that may be set later. + /// + public Future() + { + } + + /// + /// An already completed future. + /// + /// The value of the future. + public Future(T value) + { + this.IsCompleted = true; + this.value = value; + } + + private T? value; + + /// + /// Whether the future is completed or not. + /// + public bool IsCompleted { get; private set; } + + /// + /// The value of the future. + /// Will throw when accessed if the future is not completed. + /// + public T Value + { + get + { + if (!this.IsCompleted) + { + throw new InvalidOperationException("Cannot access value when not completed."); + } + return this.value!; + } + } + + /// + /// Sets the value of the future. + /// + /// + public void SetValue(T value) + { + this.IsCompleted = true; + this.value = value; + } + + /// + /// Reset the future to be not completed. + /// + public void Reset() + { + this.IsCompleted = false; + this.value = default; + } +} diff --git a/src/Draco.FormatterEngine/MutableBox.cs b/src/Draco.FormatterEngine/MutableBox.cs deleted file mode 100644 index 9231a0922..000000000 --- a/src/Draco.FormatterEngine/MutableBox.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Draco.Compiler.Internal.Syntax.Formatting; - -public sealed class MutableBox(T value) : Box(value) -{ - public new T Value - { - get => base.Value; - set => this.value = value; - } -} diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index 3b26a8a50..52a6e79ec 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -66,7 +66,7 @@ public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriorit /// .ToList() /// /// - public MutableBox IsMaterialized { get; } = new MutableBox(null); + public Future IsMaterialized { get; } = new Future(); /// /// All the indentation parts of the current scope and it's parents. @@ -75,7 +75,7 @@ public IEnumerable CurrentTotalIndent { get { - if (!(this.IsMaterialized.Value ?? false)) + if (!this.IsMaterialized.IsCompleted || !this.IsMaterialized.Value) { if (this.Parent is null) return []; return this.Parent.CurrentTotalIndent; @@ -149,24 +149,24 @@ public IEnumerable Parents { var asSoonAsPossible = this.ThisAndParents .Reverse() - .Where(item => !item.IsMaterialized.Value.HasValue) + .Where(item => !item.IsMaterialized.IsCompleted) .Where(item => item.FoldPriority == FoldPriority.AsSoonAsPossible) .FirstOrDefault(); if (asSoonAsPossible != null) { - asSoonAsPossible.IsMaterialized.Value = true; + asSoonAsPossible.IsMaterialized.SetValue(true); return asSoonAsPossible; } var asLateAsPossible = this.ThisAndParents - .Where(item => !item.IsMaterialized.Value.HasValue) + .Where(item => !item.IsMaterialized.IsCompleted) .Where(item => item.FoldPriority == FoldPriority.AsLateAsPossible) .FirstOrDefault(); if (asLateAsPossible != null) { - asLateAsPossible.IsMaterialized.Value = true; + asLateAsPossible.IsMaterialized.SetValue(true); return asLateAsPossible; } @@ -178,7 +178,7 @@ public IEnumerable Parents /// public override string ToString() { - var materialized = (this.IsMaterialized.Value.HasValue ? this.IsMaterialized.Value.Value ? "M" : "U" : "?"); + var materialized = (this.IsMaterialized.IsCompleted ? this.IsMaterialized.Value ? "M" : "U" : "?"); return $"{materialized}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; } } diff --git a/src/Draco.FormatterEngine/TokenMetadata.cs b/src/Draco.FormatterEngine/TokenMetadata.cs index e521c8fb6..02e3ecbe1 100644 --- a/src/Draco.FormatterEngine/TokenMetadata.cs +++ b/src/Draco.FormatterEngine/TokenMetadata.cs @@ -24,14 +24,14 @@ namespace Draco.Compiler.Internal.Syntax.Formatting; public record struct TokenMetadata( WhitespaceBehavior Kind, string Text, - [DisallowNull] Box? DoesReturnLine, + [DisallowNull] Future? DoesReturnLine, Scope ScopeInfo, List LeadingTrivia) { public readonly override string ToString() { var merged = string.Join(',', this.ScopeInfo.ThisAndParents); - var returnLine = this.DoesReturnLine?.Value.HasValue == true ? this.DoesReturnLine.Value.Value ? "Y" : "N" : "?"; + var returnLine = this.DoesReturnLine?.IsCompleted == true ? this.DoesReturnLine.Value ? "Y" : "N" : "?"; return $"{merged} {returnLine}"; } } From 4199b122af67fa7d5ac495d456422dbfc251ea7f Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 21 Jul 2024 19:02:52 +0200 Subject: [PATCH 71/76] Some improvements & pr feedback. --- .../Syntax/SyntaxHelpersTests.cs | 20 +++++++ .../Api/Syntax/ElseClauseSyntax.cs | 4 +- src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs | 10 ++++ .../Syntax/Formatting/DracoFormatter.cs | 57 +++++++++---------- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 12 ++-- src/Draco.FormatterEngine/FormatterEngine.cs | 38 ++++++------- src/Draco.FormatterEngine/Scope.cs | 24 ++++---- 7 files changed, 96 insertions(+), 69 deletions(-) create mode 100644 src/Draco.Compiler.Tests/Syntax/SyntaxHelpersTests.cs diff --git a/src/Draco.Compiler.Tests/Syntax/SyntaxHelpersTests.cs b/src/Draco.Compiler.Tests/Syntax/SyntaxHelpersTests.cs new file mode 100644 index 000000000..79e96805b --- /dev/null +++ b/src/Draco.Compiler.Tests/Syntax/SyntaxHelpersTests.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Draco.Compiler.Api.Syntax; + +namespace Draco.Compiler.Tests.Syntax; +public class SyntaxHelpersTests +{ + + [Fact] + public void TestIfElseIfExpression() + { + var res = SyntaxTree.Parse("func main() { var a = if (true) 1 else if (false) 2 else 3 }"); + var statement = (((res.Root as CompilationUnitSyntax)!.Declarations.Single() as FunctionDeclarationSyntax)!.Body as BlockFunctionBodySyntax)!.Statements.Single() as DeclarationStatementSyntax; + var variableDeclaration = statement!.Declaration as VariableDeclarationSyntax; + var ifExpression = variableDeclaration!.Value!.Value as IfExpressionSyntax; + Assert.True(ifExpression!.Else!.IsElseIf); + } +} diff --git a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs index bbbf07b8a..673eed85e 100644 --- a/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs +++ b/src/Draco.Compiler/Api/Syntax/ElseClauseSyntax.cs @@ -5,7 +5,9 @@ public partial class ElseClauseSyntax /// /// Returns when the else clause is followed by an if expression. /// - public bool IsElseIf => this.Expression is StatementExpressionSyntax statementExpression + public bool IsElseIf => + this.Expression is IfExpressionSyntax + || this.Expression is StatementExpressionSyntax statementExpression && statementExpression.Statement is ExpressionStatementSyntax expressionStatement && expressionStatement.Expression is IfExpressionSyntax; } diff --git a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs index d51a7bde5..087128aec 100644 --- a/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs +++ b/src/Draco.Compiler/Api/Syntax/SyntaxFacts.cs @@ -1,4 +1,6 @@ using System.Diagnostics; +using Draco.Compiler.Internal.Syntax.Formatting; +using Draco.Compiler.Internal.Syntax; namespace Draco.Compiler.Api.Syntax; @@ -161,4 +163,12 @@ internal static string ComputeCutoff(Internal.Syntax.StringExpressionSyntax str) Debug.Assert(str.CloseQuotes.LeadingTrivia[1].Kind == TriviaKind.Whitespace); return str.CloseQuotes.LeadingTrivia[1].Text; } + + public static bool WillTokenMerges(string leftToken, string rightToken) + { + var lexer = new Lexer(SourceReader.From(leftToken + rightToken), default); + lexer.Lex(); + var secondToken = lexer.Lex(); + return secondToken.Kind == TokenKind.EndOfInput; + } } diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 239bb52f1..f909a24ba 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -26,10 +26,7 @@ public static string Format(SyntaxTree tree, FormatterSettings? settings = null) var formatter = new DracoFormatter(settings); tree.Root.Accept(formatter); - - var metadatas = formatter.formatter.TokensMetadata; - - return FormatterEngine.Format(settings, metadatas); + return formatter.formatter.Format(); } public override void VisitCompilationUnit(Api.Syntax.CompilationUnitSyntax node) @@ -100,20 +97,18 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) this.HandleTokenComments(node); var formattingKind = GetFormattingTokenKind(node); - var notFirstToken = this.formatter.CurrentIdx > 0; - var doesntInsertSpace = !formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); + var firstToken = this.formatter.CurrentIdx == 0; + var insertSpace = formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); var doesReturnLine = this.formatter.CurrentToken.DoesReturnLine; var insertNewline = doesReturnLine is not null && doesReturnLine.IsCompleted && doesReturnLine.Value; - var notWhitespaceNode = node.Kind != TokenKind.StringNewline && node.Kind != TokenKind.EndOfInput; - if (doesntInsertSpace && notFirstToken && !insertNewline && notWhitespaceNode) + var whitespaceNode = node.Kind == TokenKind.StringNewline || node.Kind == TokenKind.EndOfInput; + if (!insertSpace + && !firstToken + && !insertNewline + && !whitespaceNode + && SyntaxFacts.WillTokenMerges(this.formatter.PreviousToken.Text, node.Text)) { - var lexer = new Lexer(SourceReader.From(this.formatter.PreviousToken.Text + node.Text), default); - lexer.Lex(); - var secondToken = lexer.Lex(); - if (secondToken.Kind == TokenKind.EndOfInput) // this means the 2 tokens merged in a single one, we want to avoid that. - { - this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; - } + this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; } this.formatter.SetCurrentTokenInfo(formattingKind, node.Text); @@ -153,7 +148,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL if (node is Api.Syntax.SeparatedSyntaxList || node is Api.Syntax.SeparatedSyntaxList) { - this.formatter.CreateMaterializableScope(this.settings.Indentation, + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitSeparatedSyntaxList(node) ); @@ -166,7 +161,7 @@ public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxL public override void VisitParameter(Api.Syntax.ParameterSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; base.VisitParameter(node); } @@ -253,14 +248,14 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod var kind = node.Operator.Kind; if (node.Parent is not Api.Syntax.BinaryExpressionSyntax { Operator.Kind: var previousKind } || previousKind != kind) { - closeScope = this.formatter.CreateMaterializableScope("", FoldPriority.AsLateAsPossible); + closeScope = this.formatter.CreateFoldableScope("", FoldPriority.AsLateAsPossible); } node.Left.Accept(this); if (this.formatter.CurrentToken.DoesReturnLine is null) { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; } node.Operator.Accept(this); node.Right.Accept(this); @@ -280,7 +275,7 @@ public override void VisitInlineFunctionBody(Api.Syntax.InlineFunctionBodySyntax var curr = this.formatter.CurrentIdx; node.Assign.Accept(this); - using var _ = this.formatter.CreateMaterializableScope(curr, FoldPriority.AsSoonAsPossible); + using var _ = this.formatter.CreateFoldableScope(curr, FoldPriority.AsSoonAsPossible); node.Value.Accept(this); node.Semicolon.Accept(this); } @@ -299,7 +294,7 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt node.Name.Accept(this); if (node.Generics is not null) { - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); } node.OpenParen.Accept(this); disposable.Dispose(); @@ -328,13 +323,13 @@ public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) { node.WhileKeyword.Accept(this); node.OpenParen.Accept(this); - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Condition.Accept(this)); node.CloseParen.Accept(this); - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); } public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => - this.formatter.CreateMaterializableScope("", FoldPriority.AsSoonAsPossible, () => + this.formatter.CreateFoldableScope("", FoldPriority.AsSoonAsPossible, () => { node.IfKeyword.Accept(this); IDisposable? disposable = null; @@ -345,20 +340,20 @@ public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => disposable = this.formatter.CreateScope(this.settings.Indentation); this.formatter.PreviousToken.ScopeInfo = this.formatter.Scope; // it's easier to change our mind that compute ahead of time. } - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => { var firstTokenIdx = this.formatter.CurrentIdx; node.Condition.Accept(this); var firstToken = this.formatter.TokensMetadata[firstTokenIdx]; if (firstToken.DoesReturnLine?.Value ?? false) { - firstToken.ScopeInfo.IsMaterialized.SetValue(true); + firstToken.ScopeInfo.Folded.SetValue(true); } }); node.CloseParen.Accept(this); disposable?.Dispose(); - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); node.Else?.Accept(this); }); @@ -371,10 +366,10 @@ public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) } else { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; } node.ElseKeyword.Accept(this); - this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Expression.Accept(this)); } public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) @@ -385,8 +380,8 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) // if (blabla) // an expression; // but since we are in a block we create our own scope and the if/while/else will never create it's own scope. - var isMaterialized = this.formatter.Scope.IsMaterialized; - if (!isMaterialized.IsCompleted) isMaterialized.SetValue(false); + var folded = this.formatter.Scope.Folded; + if (!folded.IsCompleted) folded.SetValue(false); node.OpenBrace.Accept(this); diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 2b6d062b2..81bf36f02 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -256,7 +256,7 @@ public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax no if (node.Parent is InvocationExpressionSyntax invocation) { if (invocation.Parent is MemberAccessExpressionSyntax parent) - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; } this.VisitToken(node.OperatorToken); @@ -289,11 +289,11 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no node.ParameterList.Accept(this); if (node.Initializer != null) { - using var scope = this.formatter.CreateMaterializableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible); - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + using var scope = this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible); + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; if (this.settings.NewLineBeforeConstructorInitializer) { - this.formatter.Scope.IsMaterialized.SetValue(true); + this.formatter.Scope.Folded.SetValue(true); } this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; node.Initializer.Accept(this); @@ -304,14 +304,14 @@ public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax no } public override void VisitArgumentList(ArgumentListSyntax node) => - this.formatter.CreateMaterializableScope(this.settings.Indentation, + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => base.VisitArgumentList(node) ); public override void VisitArgument(ArgumentSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.IsMaterialized; + this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; base.VisitArgument(node); } diff --git a/src/Draco.FormatterEngine/FormatterEngine.cs b/src/Draco.FormatterEngine/FormatterEngine.cs index de79a463f..9a65bfc4d 100644 --- a/src/Draco.FormatterEngine/FormatterEngine.cs +++ b/src/Draco.FormatterEngine/FormatterEngine.cs @@ -15,7 +15,7 @@ public FormatterEngine(int tokenCount, FormatterSettings settings) this.scopePopper = new ScopeGuard(this); this.tokensMetadata = new TokenMetadata[tokenCount]; this.Scope = new(null, settings, FoldPriority.Never, ""); - this.Scope.IsMaterialized.SetValue(true); + this.Scope.Folded.SetValue(true); this.settings = settings; } @@ -53,7 +53,7 @@ public void SetCurrentTokenInfo(WhitespaceBehavior kind, string text) public IDisposable CreateScope(string indentation) { this.Scope = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); - this.Scope.IsMaterialized.SetValue(true); + this.Scope.Folded.SetValue(true); return this.scopePopper; } @@ -65,52 +65,52 @@ public void CreateScope(string indentation, Action action) public IDisposable CreateScopeAfterNextToken(string indentation) { this.scopeForNextToken = new Scope(this.Scope, this.settings, FoldPriority.Never, indentation); - this.scopeForNextToken.IsMaterialized.SetValue(true); + this.scopeForNextToken.Folded.SetValue(true); return this.scopePopper; } - public IDisposable CreateMaterializableScope(string indentation, FoldPriority foldBehavior) + public IDisposable CreateFoldableScope(string indentation, FoldPriority foldBehavior) { this.Scope = new Scope(this.Scope, this.settings, foldBehavior, indentation); return this.scopePopper; } - public IDisposable CreateMaterializableScope(int indexOfLevelingToken, FoldPriority foldBehavior) + public IDisposable CreateFoldableScope(int indexOfLevelingToken, FoldPriority foldBehavior) { this.Scope = new Scope(this.Scope, this.settings, foldBehavior, (this.tokensMetadata, indexOfLevelingToken)); return this.scopePopper; } - public void CreateMaterializableScope(string indentation, FoldPriority foldBehavior, Action action) + public void CreateFoldableScope(string indentation, FoldPriority foldBehavior, Action action) { - using (this.CreateMaterializableScope(indentation, foldBehavior)) action(); + using (this.CreateFoldableScope(indentation, foldBehavior)) action(); } - public static string Format(FormatterSettings settings, IReadOnlyList metadatas) + public string Format() { - FoldTooLongLine(metadatas, settings); + FoldTooLongLine(this.tokensMetadata, this.settings); var builder = new StringBuilder(); - var stateMachine = new LineStateMachine(string.Concat(metadatas[0].ScopeInfo.CurrentTotalIndent)); + var stateMachine = new LineStateMachine(string.Concat(this.tokensMetadata[0].ScopeInfo.CurrentTotalIndent)); - stateMachine.AddToken(metadatas[0], settings, false); + stateMachine.AddToken(this.tokensMetadata[0], this.settings, false); - for (var x = 1; x < metadatas.Count; x++) + for (var x = 1; x < this.tokensMetadata.Length; x++) { - var metadata = metadatas[x]; + var metadata = this.tokensMetadata[x]; // we ignore multiline string newline tokens because we handle them in the string expression visitor. if (metadata.DoesReturnLine?.IsCompleted == true && metadata.DoesReturnLine.Value) { builder.Append(stateMachine); - builder.Append(settings.Newline); + builder.Append(this.settings.Newline); stateMachine = new LineStateMachine(string.Concat(metadata.ScopeInfo.CurrentTotalIndent)); } - stateMachine.AddToken(metadata, settings, x == metadatas.Count - 1); + stateMachine.AddToken(metadata, this.settings, x == this.tokensMetadata.Length - 1); } builder.Append(stateMachine); - builder.Append(settings.Newline); + builder.Append(this.settings.Newline); return builder.ToString(); } @@ -165,7 +165,7 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form for (var i = x - 1; i >= currentLineStart; i--) { var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.IsCompleted == true && scope.IsMaterialized.Value) continue; + if (scope.Folded?.IsCompleted == true && scope.Folded.Value) continue; if (scope.FoldPriority != FoldPriority.AsSoonAsPossible) continue; var prevFolded = scope.Fold(); if (prevFolded != null) @@ -178,7 +178,7 @@ private static void FoldTooLongLine(IReadOnlyList metadatas, Form for (var i = x - 1; i >= currentLineStart; i--) { var scope = metadatas[i].ScopeInfo; - if (scope.IsMaterialized?.Value ?? false) continue; + if (scope.Folded?.Value ?? false) continue; var prevFolded = scope.Fold(); if (prevFolded != null) { @@ -195,7 +195,7 @@ void Backtrack() { foreach (var scope in foldedScopes) { - scope.IsMaterialized.Reset(); + scope.Folded.Reset(); } foldedScopes.Clear(); x = currentLineStart - 1; diff --git a/src/Draco.FormatterEngine/Scope.cs b/src/Draco.FormatterEngine/Scope.cs index 52a6e79ec..e45b28cf3 100644 --- a/src/Draco.FormatterEngine/Scope.cs +++ b/src/Draco.FormatterEngine/Scope.cs @@ -56,17 +56,17 @@ public Scope(Scope? parent, FormatterSettings settings, FoldPriority foldPriorit public Scope? Parent { get; } /// - /// Represent if the scope is materialized or not. - /// An unmaterialized scope is a potential scope, which is not folded yet. - /// items.Select(x => x).ToList() have an unmaterialized scope. - /// It can be materialized like: + /// Represent if the scope is folded or not. + /// An unfolded scope is a potential scope, which is not folded yet. + /// items.Select(x => x).ToList() have an unfolded scope. + /// It can be folded like: /// /// items /// .Select(x => x) /// .ToList() /// /// - public Future IsMaterialized { get; } = new Future(); + public Future Folded { get; } = new Future(); /// /// All the indentation parts of the current scope and it's parents. @@ -75,7 +75,7 @@ public IEnumerable CurrentTotalIndent { get { - if (!this.IsMaterialized.IsCompleted || !this.IsMaterialized.Value) + if (!this.Folded.IsCompleted || !this.Folded.Value) { if (this.Parent is null) return []; return this.Parent.CurrentTotalIndent; @@ -149,24 +149,24 @@ public IEnumerable Parents { var asSoonAsPossible = this.ThisAndParents .Reverse() - .Where(item => !item.IsMaterialized.IsCompleted) + .Where(item => !item.Folded.IsCompleted) .Where(item => item.FoldPriority == FoldPriority.AsSoonAsPossible) .FirstOrDefault(); if (asSoonAsPossible != null) { - asSoonAsPossible.IsMaterialized.SetValue(true); + asSoonAsPossible.Folded.SetValue(true); return asSoonAsPossible; } var asLateAsPossible = this.ThisAndParents - .Where(item => !item.IsMaterialized.IsCompleted) + .Where(item => !item.Folded.IsCompleted) .Where(item => item.FoldPriority == FoldPriority.AsLateAsPossible) .FirstOrDefault(); if (asLateAsPossible != null) { - asLateAsPossible.IsMaterialized.SetValue(true); + asLateAsPossible.Folded.SetValue(true); return asLateAsPossible; } @@ -178,7 +178,7 @@ public IEnumerable Parents /// public override string ToString() { - var materialized = (this.IsMaterialized.IsCompleted ? this.IsMaterialized.Value ? "M" : "U" : "?"); - return $"{materialized}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; + var folded = (this.Folded.IsCompleted ? this.Folded.Value ? "M" : "U" : "?"); + return $"{folded}{this.FoldPriority}{this.indentation?.Length.ToString() ?? "L"}"; } } From d0f5c95e46b38ea6ddb772c5070895b84ae0eebe Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sun, 21 Jul 2024 19:51:48 +0200 Subject: [PATCH 72/76] Forgot to commit this file. --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs index 81bf36f02..e08629182 100644 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ b/src/Draco.Formatter.Csharp/CSharpFormatter.cs @@ -20,9 +20,7 @@ public static string Format(SyntaxTree tree, CSharpFormatterSettings? settings = var formatter = new CSharpFormatter(settings); formatter.Visit(tree.GetRoot()); - var metadatas = formatter.formatter.TokensMetadata; - - return FormatterEngine.Format(settings, metadatas); + return formatter.formatter.Format(); } public override void VisitCompilationUnit(CompilationUnitSyntax node) From e6d51005bce2ae336f51572f42b276dcc1cbc425 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 29 Jul 2024 17:35:27 +0200 Subject: [PATCH 73/76] Deleted csharp formatter. --- .../Draco.Formatter.CSharp.Tests.csproj | 28 -- .../FormatterTest.cs | 53 --- src/Draco.Formatter.Csharp/CSharpFormatter.cs | 329 ------------------ .../CSharpFormatterSettings.cs | 19 - .../Draco.Formatter.Csharp.csproj | 8 - src/Draco.sln | 16 - 6 files changed, 453 deletions(-) delete mode 100644 src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj delete mode 100644 src/Draco.Formatter.CSharp.Tests/FormatterTest.cs delete mode 100644 src/Draco.Formatter.Csharp/CSharpFormatter.cs delete mode 100644 src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs delete mode 100644 src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj diff --git a/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj b/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj deleted file mode 100644 index 377f85b72..000000000 --- a/src/Draco.Formatter.CSharp.Tests/Draco.Formatter.CSharp.Tests.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net8.0 - enable - enable - - false - true - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - - - - - diff --git a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs b/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs deleted file mode 100644 index 86b39552b..000000000 --- a/src/Draco.Formatter.CSharp.Tests/FormatterTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Draco.Formatter.Csharp; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Text; -using Xunit; -using Xunit.Abstractions; - -namespace Draco.Formatter.CSharp.Test; - -public sealed class FormatterTest(ITestOutputHelper logger) -{ - [Fact] - public void SomeCodeSampleShouldBeFormattedCorrectly() - { - var input = """" - class Program - { - public static void Main() - { - Console.WriteLine("Hello, World!"); - } - } - - """"; - var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); - logger.WriteLine(formatted); - Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); - } - - [Fact] - public void ThisFileShouldBeFormattedCorrectly() - { - var input = File.ReadAllText("../../../../Draco.Formatter.Csharp.Tests/FormatterTest.cs"); - var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); - logger.WriteLine(formatted); - Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); - } - - [Fact] - public void AllFileShouldBeFormattedCorrectly() - { - var allFiles = Directory.GetFiles("../../../../", "*.cs", SearchOption.AllDirectories); - foreach (var file in allFiles) - { - var input = File.ReadAllText(file); - var tree = SyntaxFactory.ParseSyntaxTree(SourceText.From(input)); - var formatted = CSharpFormatter.Format(tree, CSharpFormatterSettings.DracoStyle); - logger.WriteLine(formatted); - Assert.Equal(input, formatted, ignoreLineEndingDifferences: true); - } - } -} diff --git a/src/Draco.Formatter.Csharp/CSharpFormatter.cs b/src/Draco.Formatter.Csharp/CSharpFormatter.cs deleted file mode 100644 index e08629182..000000000 --- a/src/Draco.Formatter.Csharp/CSharpFormatter.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System.Diagnostics.Metrics; -using System.Linq; -using System.Resources; -using Draco.Compiler.Internal.Syntax.Formatting; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; - -namespace Draco.Formatter.Csharp; - -public sealed class CSharpFormatter(CSharpFormatterSettings settings) : CSharpSyntaxWalker(SyntaxWalkerDepth.Token) -{ - private readonly CSharpFormatterSettings settings = settings; - private FormatterEngine formatter = null!; - - public static string Format(SyntaxTree tree, CSharpFormatterSettings? settings = null) - { - settings ??= CSharpFormatterSettings.Default; - - var formatter = new CSharpFormatter(settings); - formatter.Visit(tree.GetRoot()); - - return formatter.formatter.Format(); - } - - public override void VisitCompilationUnit(CompilationUnitSyntax node) - { - this.formatter = new FormatterEngine(node.DescendantTokens().Count(), this.settings); - base.VisitCompilationUnit(node); - } - - private static WhitespaceBehavior GetFormattingTokenKind(SyntaxToken token) => token.Kind() switch - { - SyntaxKind.AndKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.ElseKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.ForKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.GotoKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.UsingDirective => WhitespaceBehavior.PadAround, - SyntaxKind.InKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.InternalKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.ModuleKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.OrKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.ReturnKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.PublicKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.VarKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.IfKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.WhileKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.StaticKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.SealedKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.ForEachKeyword => WhitespaceBehavior.PadAround, - - SyntaxKind.TrueKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.FalseKeyword => WhitespaceBehavior.PadAround, - SyntaxKind.CommaToken => WhitespaceBehavior.SpaceAfter, - - SyntaxKind.SemicolonToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - SyntaxKind.OpenBraceToken => WhitespaceBehavior.SpaceBefore | WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - SyntaxKind.OpenParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForNextToken, - SyntaxKind.OpenBracketToken => WhitespaceBehavior.Whitespace, - SyntaxKind.CloseParenToken => WhitespaceBehavior.BehaveAsWhiteSpaceForPreviousToken, - SyntaxKind.InterpolatedStringStartToken => WhitespaceBehavior.Whitespace, - SyntaxKind.DotToken => WhitespaceBehavior.Whitespace, - - SyntaxKind.EqualsToken => WhitespaceBehavior.PadAround, - SyntaxKind.InterpolatedSingleLineRawStringStartToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.InterpolatedMultiLineRawStringStartToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.PlusToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.MinusToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.AsteriskToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.SlashToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.PlusEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.MinusEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.AsteriskEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.SlashEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.GreaterThanEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.GreaterThanToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.LessThanEqualsToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.LessThanToken => WhitespaceBehavior.SpaceBefore, - SyntaxKind.NumericLiteralToken => WhitespaceBehavior.SpaceBefore, - - SyntaxKind.IdentifierToken => WhitespaceBehavior.SpaceBefore, - - _ => WhitespaceBehavior.NoFormatting - }; - - public override void VisitToken(SyntaxToken node) - { - if (node.IsKind(SyntaxKind.None)) return; - - base.VisitToken(node); - var formattingKind = GetFormattingTokenKind(node); - - var notFirstToken = this.formatter.CurrentIdx > 0; - var doesntInsertSpace = !formattingKind.HasFlag(WhitespaceBehavior.SpaceBefore); - var insertNewline = this.formatter.CurrentToken.DoesReturnLine?.Value == true; - var notWhitespaceNode = !node.IsKind(SyntaxKind.EndOfFileToken); - if (doesntInsertSpace && notFirstToken && !insertNewline && notWhitespaceNode) - { - var tokens = SyntaxFactory.ParseTokens(this.formatter.PreviousToken.Text + node.Text); - var secondToken = tokens.Skip(1).First(); - if (secondToken.IsKind(SyntaxKind.EndOfFileToken)) // this means the 2 tokens merged in a single one, we want to avoid that. - { - this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; - } - } - - this.formatter.SetCurrentTokenInfo(formattingKind, node.Text); - } - - public override void VisitClassDeclaration(ClassDeclarationSyntax node) - { - if (GetPreviousNode(node) != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - foreach (var attribute in node.AttributeLists) - { - attribute.Accept(this); - } - - foreach (var modifier in node.Modifiers) - { - this.VisitToken(modifier); - } - - this.VisitToken(node.Keyword); - this.VisitToken(node.Identifier); - node.TypeParameterList?.Accept(this); - node.ParameterList?.Accept(this); - node.BaseList?.Accept(this); - - foreach (var constraint in node.ConstraintClauses) - { - constraint.Accept(this); - } - - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - this.VisitToken(node.OpenBraceToken); - this.formatter.CreateScope(this.settings.Indentation, () => - { - foreach (var member in node.Members) - { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - member.Accept(this); - } - }); - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - this.VisitToken(node.CloseBraceToken); - this.VisitToken(node.SemicolonToken); - } - - public override void VisitBaseList(BaseListSyntax node) - { - this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; - base.VisitBaseList(node); - } - - public override void VisitBlock(BlockSyntax node) - { - foreach (var attribute in node.AttributeLists) - { - attribute.Accept(this); - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - this.VisitToken(node.OpenBraceToken); - this.formatter.CreateScope(this.settings.Indentation, () => - { - foreach (var statement in node.Statements) - { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - statement.Accept(this); - } - }); - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - this.VisitToken(node.CloseBraceToken); - } - - public override void VisitUsingDirective(UsingDirectiveSyntax node) - { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - if (GetPreviousNode(node) is not UsingDirectiveSyntax and not null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - base.VisitUsingDirective(node); - } - - public override void VisitNamespaceDeclaration(NamespaceDeclarationSyntax node) - { - if (GetPreviousNode(node) != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - base.VisitNamespaceDeclaration(node); - } - - - public override void VisitFileScopedNamespaceDeclaration(FileScopedNamespaceDeclarationSyntax node) - { - if (GetPreviousNode(node) != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - base.VisitFileScopedNamespaceDeclaration(node); - } - - public override void VisitMethodDeclaration(MethodDeclarationSyntax node) - { - if (GetPreviousNode(node) != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - foreach (var attribute in node.AttributeLists) - { - attribute.Accept(this); - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - foreach (var modifier in node.Modifiers) - { - this.VisitToken(modifier); - } - node.ReturnType.Accept(this); - node.ExplicitInterfaceSpecifier?.Accept(this); - this.VisitToken(node.Identifier); - node.TypeParameterList?.Accept(this); - node.ParameterList.Accept(this); - foreach (var constraint in node.ConstraintClauses) - { - constraint.Accept(this); - } - node.Body?.Accept(this); - // gets index of the current method - var count = node.Parent!.ChildNodes().Count(); - var membersIdx = node.Parent!.ChildNodes() - .Select((n, i) => (n, i)) - .Where(x => x.n == node) - .Select(x => x.i) - .FirstOrDefault(); - this.VisitToken(node.SemicolonToken); - } - - public override void VisitInvocationExpression(InvocationExpressionSyntax node) => base.VisitInvocationExpression(node); - - public override void VisitMemberAccessExpression(MemberAccessExpressionSyntax node) - { - node.Expression.Accept(this); - - if (node.Parent is InvocationExpressionSyntax invocation) - { - if (invocation.Parent is MemberAccessExpressionSyntax parent) - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; - } - - this.VisitToken(node.OperatorToken); - node.Name.Accept(this); - } - - public override void VisitFieldDeclaration(FieldDeclarationSyntax node) - { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - base.VisitFieldDeclaration(node); - } - - public override void VisitConstructorDeclaration(ConstructorDeclarationSyntax node) - { - if (GetPreviousNode(node) != null) - { - this.formatter.CurrentToken.LeadingTrivia = [""]; - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - foreach (var attribute in node.AttributeLists) - { - attribute.Accept(this); - } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - foreach (var modifier in node.Modifiers) - { - this.VisitToken(modifier); - } - this.VisitToken(node.Identifier); - node.ParameterList.Accept(this); - if (node.Initializer != null) - { - using var scope = this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible); - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; - if (this.settings.NewLineBeforeConstructorInitializer) - { - this.formatter.Scope.Folded.SetValue(true); - } - this.formatter.CurrentToken.Kind = WhitespaceBehavior.PadAround; - node.Initializer.Accept(this); - } - node.Body?.Accept(this); - node.ExpressionBody?.Accept(this); - this.VisitToken(node.SemicolonToken); - } - - public override void VisitArgumentList(ArgumentListSyntax node) => - this.formatter.CreateFoldableScope(this.settings.Indentation, - FoldPriority.AsSoonAsPossible, - () => base.VisitArgumentList(node) - ); - - public override void VisitArgument(ArgumentSyntax node) - { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; - base.VisitArgument(node); - } - - private static SyntaxNode? GetPreviousNode(SyntaxNode node) - { - var parent = node.Parent; - if (parent == null) return null; - var previous = null as SyntaxNode; - foreach (var child in parent.ChildNodes()) - { - if (child is ParameterListSyntax) continue; - if (child == node) return previous; - previous = child; - } - return null; - } -} diff --git a/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs b/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs deleted file mode 100644 index bf9e833c1..000000000 --- a/src/Draco.Formatter.Csharp/CSharpFormatterSettings.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Draco.Compiler.Internal.Syntax.Formatting; - -namespace Draco.Formatter.Csharp; -public class CSharpFormatterSettings : FormatterSettings -{ - public static new CSharpFormatterSettings Default { get; } = new(); - public static CSharpFormatterSettings DracoStyle { get; } = new() - { - NewLineBeforeConstructorInitializer = true, - LineWidth = 120 - }; - - public bool NewLineBeforeConstructorInitializer { get; init; } -} diff --git a/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj b/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj deleted file mode 100644 index 43c6d82a4..000000000 --- a/src/Draco.Formatter.Csharp/Draco.Formatter.Csharp.csproj +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/src/Draco.sln b/src/Draco.sln index 7026b9710..7b106f63e 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -47,10 +47,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.JsonRpc", "Draco.Json EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Debugger.Tests", "Draco.Debugger.Tests\Draco.Debugger.Tests.csproj", "{9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.Csharp", "Draco.Formatter.Csharp\Draco.Formatter.Csharp.csproj", "{379559B5-FE13-4685-9F40-E021326CCB53}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.Formatter.CSharp.Tests", "Draco.Formatter.CSharp.Tests\Draco.Formatter.CSharp.Tests.csproj", "{FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Draco.FormatterEngine", "Draco.FormatterEngine\Draco.FormatterEngine.csproj", "{D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}" EndProject Global @@ -174,18 +170,6 @@ Global {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Nuget|Any CPU.Build.0 = Debug|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.Build.0 = Release|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Debug|Any CPU.Build.0 = Debug|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Nuget|Any CPU.Build.0 = Debug|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Release|Any CPU.ActiveCfg = Release|Any CPU - {379559B5-FE13-4685-9F40-E021326CCB53}.Release|Any CPU.Build.0 = Release|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Nuget|Any CPU.Build.0 = Debug|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFCA2651-FA2D-4D0A-AA4A-B7DF1F19647C}.Release|Any CPU.Build.0 = Release|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU From 8143fffd328c0f74da73044d18cbf09f4bb1e832 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Mon, 29 Jul 2024 18:37:17 +0200 Subject: [PATCH 74/76] Some code simplifications. --- .../Syntax/Formatting/DracoFormatter.cs | 165 +++++++++--------- src/Draco.FormatterEngine/Future.cs | 2 + 2 files changed, 89 insertions(+), 78 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index f909a24ba..96c582678 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -102,11 +102,12 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) var doesReturnLine = this.formatter.CurrentToken.DoesReturnLine; var insertNewline = doesReturnLine is not null && doesReturnLine.IsCompleted && doesReturnLine.Value; var whitespaceNode = node.Kind == TokenKind.StringNewline || node.Kind == TokenKind.EndOfInput; + var willTokenMerges = SyntaxFacts.WillTokenMerges(this.formatter.PreviousToken.Text, node.Text); if (!insertSpace && !firstToken && !insertNewline && !whitespaceNode - && SyntaxFacts.WillTokenMerges(this.formatter.PreviousToken.Text, node.Text)) + && willTokenMerges) { this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; } @@ -128,7 +129,7 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) if (comment != null) { this.formatter.CurrentToken.Text = node.Text + " " + comment; - this.formatter.NextToken.DoesReturnLine = new Future(true); + this.formatter.NextToken.DoesReturnLine = true; } } var leadingComments = node.LeadingTrivia @@ -139,19 +140,17 @@ private void HandleTokenComments(Api.Syntax.SyntaxToken node) this.formatter.CurrentToken.LeadingTrivia.AddRange(leadingComments); if (this.formatter.CurrentToken.LeadingTrivia.Count > 0) { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; } } public override void VisitSeparatedSyntaxList(Api.Syntax.SeparatedSyntaxList node) { if (node is Api.Syntax.SeparatedSyntaxList - || node is Api.Syntax.SeparatedSyntaxList) + or Api.Syntax.SeparatedSyntaxList) { - this.formatter.CreateFoldableScope(this.settings.Indentation, - FoldPriority.AsSoonAsPossible, - () => base.VisitSeparatedSyntaxList(node) - ); + using var _ = this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible); + base.VisitSeparatedSyntaxList(node); } else { @@ -167,7 +166,7 @@ public override void VisitParameter(Api.Syntax.ParameterSyntax node) public override void VisitDeclaration(Api.Syntax.DeclarationSyntax node) { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; base.VisitDeclaration(node); } @@ -175,70 +174,82 @@ public override void VisitStringExpression(Api.Syntax.StringExpressionSyntax nod { if (node.OpenQuotes.Kind != TokenKind.MultiLineStringStart) { - node.OpenQuotes.Accept(this); - foreach (var item in node.Parts.Tokens) - { - this.formatter.CurrentToken.DoesReturnLine = new Future(false); - item.Accept(this); - } - this.formatter.CurrentToken.DoesReturnLine = new Future(false); - node.CloseQuotes.Accept(this); + this.HandleSingleLineString(node); return; } + + this.HandleMultiLineString(node); + } + + private void HandleMultiLineString(Api.Syntax.StringExpressionSyntax node) + { node.OpenQuotes.Accept(this); using var _ = this.formatter.CreateScope(this.settings.Indentation); var blockCurrentIndentCount = SyntaxFacts.ComputeCutoff(node).Length; - var shouldIndent = true; for (var i = 0; i < node.Parts.Count; i++) { var curr = node.Parts[i]; if (curr.IsNewLine) { - shouldIndent = true; curr.Accept(this); + if (i == node.Parts.Count - 1) break; + HandleNewLine(node, blockCurrentIndentCount, i, curr); continue; } - if (shouldIndent) - { - shouldIndent = false; - - var tokenText = curr.Tokens.First().ValueText!; - if (!tokenText.Take(blockCurrentIndentCount).All(char.IsWhiteSpace)) throw new InvalidOperationException(); - this.formatter.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; - this.formatter.CurrentToken.DoesReturnLine = new Future(true); - - if (i > 0 && node.Parts[i - 1].IsNewLine) - { - this.formatter.PreviousToken.Text = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. - } - } - var startIdx = this.formatter.CurrentIdx; // capture position before visiting the tokens of this parts (this will move forward the position) // for parts that contains expressions and have return lines. foreach (var token in curr.Tokens) { - var newLines = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).ToArray(); - if (newLines.Length > 0) + var newLineCount = token.TrailingTrivia.Where(t => t.Kind == TriviaKind.Newline).Count(); + if (newLineCount > 0) { - this.formatter.NextToken.DoesReturnLine = new Future(true); - this.formatter.CurrentToken.Text = string.Concat(Enumerable.Repeat(this.settings.Newline, newLines.Length - 1).Prepend(token.Text)); + this.formatter.NextToken.DoesReturnLine = true; + this.formatter.CurrentToken.Text = token.Text + string.Concat(Enumerable.Repeat(this.settings.Newline, newLineCount - 1)); } token.Accept(this); } - // default all tokens to never return. - var tokenCount = curr.Tokens.Count(); + // sets all unset tokens to not return a line. + var j = 0; + foreach (var token in curr.Tokens) + { + this.formatter.TokensMetadata[startIdx + j].DoesReturnLine ??= false; + j++; + } + } + this.formatter.CurrentToken.DoesReturnLine = true; + node.CloseQuotes.Accept(this); - for (var j = 0; j < tokenCount; j++) + void HandleNewLine(Api.Syntax.StringExpressionSyntax node, int blockCurrentIndentCount, int i, Api.Syntax.StringPartSyntax curr) + { + var next = node.Parts[i + 1]; + var tokenText = next.Tokens.First().ValueText!; + this.formatter.CurrentToken.Text = tokenText[blockCurrentIndentCount..]; + this.formatter.CurrentToken.DoesReturnLine = true; + + if (i > 0 && node.Parts[i - 1].IsNewLine) { - this.formatter.TokensMetadata[startIdx + j].DoesReturnLine ??= new Future(false); + this.formatter.PreviousToken.Text = ""; // PreviousToken is a newline, CurrentToken.DoesReturnLine will produce the newline. } } - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + } + + private void HandleSingleLineString(Api.Syntax.StringExpressionSyntax node) + { + // this is a single line string + node.OpenQuotes.Accept(this); + // we just sets all tokens in this string to not return a line. + foreach (var item in node.Parts.Tokens) + { + this.formatter.CurrentToken.DoesReturnLine = false; + item.Accept(this); + } + // including the close quote. + this.formatter.CurrentToken.DoesReturnLine = false; node.CloseQuotes.Accept(this); } @@ -253,10 +264,7 @@ public override void VisitBinaryExpression(Api.Syntax.BinaryExpressionSyntax nod node.Left.Accept(this); - if (this.formatter.CurrentToken.DoesReturnLine is null) - { - this.formatter.CurrentToken.DoesReturnLine = this.formatter.Scope.Folded; - } + this.formatter.CurrentToken.DoesReturnLine ??= this.formatter.Scope.Folded; node.Operator.Accept(this); node.Right.Accept(this); closeScope?.Dispose(); @@ -266,7 +274,7 @@ public override void VisitBlockFunctionBody(Api.Syntax.BlockFunctionBodySyntax n { node.OpenBrace.Accept(this); this.formatter.CreateScope(this.settings.Indentation, () => node.Statements.Accept(this)); - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; node.CloseBrace.Accept(this); } @@ -294,7 +302,8 @@ public override void VisitFunctionDeclaration(Api.Syntax.FunctionDeclarationSynt node.Name.Accept(this); if (node.Generics is not null) { - this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible, () => node.Generics?.Accept(this)); + using var _ = this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsLateAsPossible); + node.Generics?.Accept(this); } node.OpenParen.Accept(this); disposable.Dispose(); @@ -308,7 +317,7 @@ public override void VisitStatement(Api.Syntax.StatementSyntax node) { if (node is Api.Syntax.DeclarationStatementSyntax { Declaration: Api.Syntax.LabelDeclarationSyntax }) { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; this.formatter.CurrentToken.Kind = WhitespaceBehavior.RemoveOneIndentation; } else @@ -328,41 +337,41 @@ public override void VisitWhileExpression(Api.Syntax.WhileExpressionSyntax node) this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); } - public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) => - this.formatter.CreateFoldableScope("", FoldPriority.AsSoonAsPossible, () => + public override void VisitIfExpression(Api.Syntax.IfExpressionSyntax node) + { + using var _ = this.formatter.CreateFoldableScope("", FoldPriority.AsSoonAsPossible); + + node.IfKeyword.Accept(this); + IDisposable? disposable = null; + node.OpenParen.Accept(this); + if (this.formatter.PreviousToken.DoesReturnLine?.Value ?? false) { - node.IfKeyword.Accept(this); - IDisposable? disposable = null; - node.OpenParen.Accept(this); - if (this.formatter.PreviousToken.DoesReturnLine?.Value ?? false) + // there is no reason for an OpenParen to return line except if there is a comment. + disposable = this.formatter.CreateScope(this.settings.Indentation); + this.formatter.PreviousToken.ScopeInfo = this.formatter.Scope; // it's easier to change our mind that compute ahead of time. + } + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => + { + var firstTokenIdx = this.formatter.CurrentIdx; + node.Condition.Accept(this); + var firstToken = this.formatter.TokensMetadata[firstTokenIdx]; + if (firstToken.DoesReturnLine?.Value ?? false) { - // there is no reason for an OpenParen to return line except if there is a comment. - disposable = this.formatter.CreateScope(this.settings.Indentation); - this.formatter.PreviousToken.ScopeInfo = this.formatter.Scope; // it's easier to change our mind that compute ahead of time. + firstToken.ScopeInfo.Folded.SetValue(true); } - this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => - { - var firstTokenIdx = this.formatter.CurrentIdx; - node.Condition.Accept(this); - var firstToken = this.formatter.TokensMetadata[firstTokenIdx]; - if (firstToken.DoesReturnLine?.Value ?? false) - { - firstToken.ScopeInfo.Folded.SetValue(true); - } - }); - node.CloseParen.Accept(this); - disposable?.Dispose(); - - this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); - - node.Else?.Accept(this); }); + node.CloseParen.Accept(this); + disposable?.Dispose(); + this.formatter.CreateFoldableScope(this.settings.Indentation, FoldPriority.AsSoonAsPossible, () => node.Then.Accept(this)); + + node.Else?.Accept(this); + } public override void VisitElseClause(Api.Syntax.ElseClauseSyntax node) { if (node.IsElseIf || node.Parent!.Parent is Api.Syntax.ExpressionStatementSyntax) { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; } else { @@ -390,12 +399,12 @@ public override void VisitBlockExpression(Api.Syntax.BlockExpressionSyntax node) node.Statements.Accept(this); if (node.Value != null) { - this.formatter.CurrentToken.DoesReturnLine = new Future(true); + this.formatter.CurrentToken.DoesReturnLine = true; node.Value.Accept(this); } }); node.CloseBrace.Accept(this); - this.formatter.PreviousToken.DoesReturnLine = new Future(true); + this.formatter.PreviousToken.DoesReturnLine = true; } public override void VisitVariableDeclaration(Api.Syntax.VariableDeclarationSyntax node) diff --git a/src/Draco.FormatterEngine/Future.cs b/src/Draco.FormatterEngine/Future.cs index f98632baf..71a074319 100644 --- a/src/Draco.FormatterEngine/Future.cs +++ b/src/Draco.FormatterEngine/Future.cs @@ -66,4 +66,6 @@ public void Reset() this.IsCompleted = false; this.value = default; } + + public static implicit operator Future(T value) => new(value); } From 14aab9551b2420438f41b91af609f4c04249ba81 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 3 Aug 2024 03:35:32 +0200 Subject: [PATCH 75/76] VS did open it fine... --- src/Draco.sln | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Draco.sln b/src/Draco.sln index 75f7a4129..4ad4c75c7 100644 --- a/src/Draco.sln +++ b/src/Draco.sln @@ -170,14 +170,12 @@ Global {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Nuget|Any CPU.Build.0 = Debug|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.ActiveCfg = Release|Any CPU {9E5A4FEB-109B-4BA7-AEDE-0802CF3E765E}.Release|Any CPU.Build.0 = Release|Any CPU -<<<<<<< HEAD {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Debug|Any CPU.Build.0 = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Nuget|Any CPU.Build.0 = Debug|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Release|Any CPU.ActiveCfg = Release|Any CPU {D5DC7780-B933-400C-9AA8-4AFF1FF45DF7}.Release|Any CPU.Build.0 = Release|Any CPU -======= {E8DF7730-54AE-45F1-9D36-809B444A5F7A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E8DF7730-54AE-45F1-9D36-809B444A5F7A}.Debug|Any CPU.Build.0 = Debug|Any CPU {E8DF7730-54AE-45F1-9D36-809B444A5F7A}.Nuget|Any CPU.ActiveCfg = Debug|Any CPU @@ -202,7 +200,6 @@ Global {5A813218-38B9-4B7D-A352-47978EBB0554}.Nuget|Any CPU.Build.0 = Debug|Any CPU {5A813218-38B9-4B7D-A352-47978EBB0554}.Release|Any CPU.ActiveCfg = Release|Any CPU {5A813218-38B9-4B7D-A352-47978EBB0554}.Release|Any CPU.Build.0 = Release|Any CPU ->>>>>>> origin/main EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From b702b49dfbab8afd8897beb1066875eb90df7d40 Mon Sep 17 00:00:00 2001 From: Kuinox Date: Sat, 3 Aug 2024 04:03:44 +0200 Subject: [PATCH 76/76] Some fixes of the code simplification. --- .../Internal/Syntax/Formatting/DracoFormatter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs index 96c582678..5532a1abc 100644 --- a/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs +++ b/src/Draco.Compiler/Internal/Syntax/Formatting/DracoFormatter.cs @@ -102,14 +102,15 @@ public override void VisitSyntaxToken(Api.Syntax.SyntaxToken node) var doesReturnLine = this.formatter.CurrentToken.DoesReturnLine; var insertNewline = doesReturnLine is not null && doesReturnLine.IsCompleted && doesReturnLine.Value; var whitespaceNode = node.Kind == TokenKind.StringNewline || node.Kind == TokenKind.EndOfInput; - var willTokenMerges = SyntaxFacts.WillTokenMerges(this.formatter.PreviousToken.Text, node.Text); if (!insertSpace && !firstToken && !insertNewline - && !whitespaceNode - && willTokenMerges) + && !whitespaceNode) { - this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; + if (SyntaxFacts.WillTokenMerges(this.formatter.PreviousToken.Text, node.Text)) + { + this.formatter.CurrentToken.Kind = WhitespaceBehavior.SpaceBefore; + } } this.formatter.SetCurrentTokenInfo(formattingKind, node.Text); @@ -191,7 +192,7 @@ private void HandleMultiLineString(Api.Syntax.StringExpressionSyntax node) { var curr = node.Parts[i]; - if (curr.IsNewLine) + if (curr.IsNewLine || i == 0) { curr.Accept(this); if (i == node.Parts.Count - 1) break;