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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/Parlot/Fluent/HybridList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace Parlot.Fluent;

/// <summary>
/// An internal implementation of IReadOnlyList&lt;T&gt; that stores up to 4 items inline
/// before switching to a List&lt;T&gt; for growth.
/// This provides efficient memory usage for small result sets while maintaining
/// flexibility for larger lists.
/// </summary>
#nullable enable
internal sealed class HybridList<T> : IReadOnlyList<T>
{
private T? _item1;
private T? _item2;
private T? _item3;
private T? _item4;
private List<T>? _list;
private int _count;

public int Count => _count;

public T this[int index]
{
get
{
if (index < 0 || index >= _count)
{
throw new ArgumentOutOfRangeException(nameof(index));
}

if (_list is not null)
{
return _list[index];
}

return index switch
{
0 => _item1!,
1 => _item2!,
2 => _item3!,
3 => _item4!,
_ => throw new ArgumentOutOfRangeException(nameof(index))
};
}
}

public void Add(T item)
{
if (_list is not null)
{
_list.Add(item);
_count++;
}
else
{
switch (_count)
{
case 0:
_item1 = item;
_count++;
break;
case 1:
_item2 = item;
_count++;
break;
case 2:
_item3 = item;
_count++;
break;
case 3:
_item4 = item;
_count++;
break;
case 4:
// Transition to List<T>
_list = new List<T>(8) { _item1!, _item2!, _item3!, _item4!, item };
_item1 = default;
_item2 = default;
_item3 = default;
_item4 = default;
_count++;
break;
default:
throw new InvalidOperationException("Unexpected count value");
}
}
}

public IEnumerator<T> GetEnumerator()
{
if (_list is not null)
{
return _list.GetEnumerator();
}

return GetEnumeratorInternal();
}

private IEnumerator<T> GetEnumeratorInternal()
{
if (_count >= 1)
yield return _item1!;
if (_count >= 2)
yield return _item2!;
if (_count >= 3)
yield return _item3!;
if (_count >= 4)
yield return _item4!;
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
2 changes: 1 addition & 1 deletion src/Parlot/Fluent/OneOrMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public override bool Parse(ParseContext context, ref ParseResult<IReadOnlyList<T
}

var start = parsed.Start;
var results = new List<T>();
var results = new HybridList<T>();

int end;

Expand Down
46 changes: 41 additions & 5 deletions src/Parlot/Fluent/ParserExtensions.WhiteSpace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,20 @@ public static Parser<T> WithWhiteSpaceParser<T>(this Parser<T> parser, Parser<Te
/// <param name="parser">The parser to execute with the custom whitespace parser.</param>
/// <param name="commentsBuilder">The action to configure the comments builder.</param>
/// <returns>A parser that uses white spaces, new lines and comments.</returns>
/// <remarks>
/// Here is an example of usage:
/// <code>
/// var parserWithComments = myParser.WithComments(comments =>
/// {
/// comments.WithWhiteSpaceOrNewLine();
/// comments.WithSingleLine("//");
/// comments.WithMultiLine("/*", "*/");
/// });
/// </code>
/// </remarks>
public static Parser<T> WithComments<T>(this Parser<T> parser, Action<CommentsBuilder> commentsBuilder)
{
var builder = new CommentsBuilder(Literals.WhiteSpace(includeNewLines: true));
var builder = new CommentsBuilder();
commentsBuilder(builder);
return new WithWhiteSpaceParser<T>(parser, builder.Build());
}
Expand All @@ -34,23 +45,48 @@ public class CommentsBuilder
{
private readonly List<Parser<TextSpan>> _parsers = [];

[Obsolete("Use CommentsBuilder().WithParser(parser) instead.")]
public CommentsBuilder(Parser<TextSpan> whiteSpaceParser)
{
_parsers.Add(whiteSpaceParser);
}

public Parser<TextSpan> WithSingleLine(string singleLineStart)
public CommentsBuilder()
{
}

public CommentsBuilder WithWhiteSpace()
{
var parser = Literals.WhiteSpace();
_parsers.Add(parser);
return this;
}

public CommentsBuilder WithWhiteSpaceOrNewLine()
{
var parser = Literals.WhiteSpace(includeNewLines: true);
_parsers.Add(parser);
return this;
}

public CommentsBuilder WithParser(Parser<TextSpan> parser)
{
_parsers.Add(parser);
return this;
}

public CommentsBuilder WithSingleLine(string singleLineStart)
{
var parser = Literals.Comments(singleLineStart);
_parsers.Add(parser);
return parser;
return this;
}

public Parser<TextSpan> WithMultiLine(string multiLineStart, string multiLineEnd)
public CommentsBuilder WithMultiLine(string multiLineStart, string multiLineEnd)
{
var parser = Literals.Comments(multiLineStart, multiLineEnd);
_parsers.Add(parser);
return parser;
return this;
}

public Parser<TextSpan> Build()
Expand Down
4 changes: 2 additions & 2 deletions src/Parlot/Fluent/Separated.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public override bool Parse(ParseContext context, ref ParseResult<IReadOnlyList<T
{
context.EnterParser(this);

List<T>? results = null;
HybridList<T>? results = null;

var start = 0;
var end = context.Scanner.Cursor.Position;
Expand Down Expand Up @@ -84,7 +84,7 @@ public override bool Parse(ParseContext context, ref ParseResult<IReadOnlyList<T
results!.Add(parsed.Value);
}

result.Set(start, end.Offset, results ?? []);
result.Set(start, end.Offset, results ?? (IReadOnlyList<T>)[]);

context.ExitParser(this);
return true;
Expand Down
21 changes: 20 additions & 1 deletion src/Parlot/Fluent/When.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Parlot.Compilation;
using Parlot.Rewriting;
using System;
#if NET
using System.Linq;
Expand All @@ -11,7 +12,7 @@ namespace Parlot.Fluent;
/// Ensure the given parser is valid based on a condition, and backtracks if not.
/// </summary>
/// <typeparam name="T">The output parser type.</typeparam>
public sealed class When<T> : Parser<T>, ICompilable
public sealed class When<T> : Parser<T>, ICompilable, ISeekable
{
private readonly Func<ParseContext, T, bool> _action;
private readonly Parser<T> _parser;
Expand All @@ -21,14 +22,32 @@ public When(Parser<T> parser, Func<T, bool> action)
{
_action = action != null ? (c, t) => action(t) : throw new ArgumentNullException(nameof(action));
_parser = parser ?? throw new ArgumentNullException(nameof(parser));
InitializeSeekable();
}

public When(Parser<T> parser, Func<ParseContext, T, bool> action)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_parser = parser ?? throw new ArgumentNullException(nameof(parser));
InitializeSeekable();
}

private void InitializeSeekable()
{
if (_parser is ISeekable seekable)
{
CanSeek = seekable.CanSeek;
ExpectedChars = seekable.ExpectedChars;
SkipWhitespace = seekable.SkipWhitespace;
}
}

public bool CanSeek { get; private set; }

public char[] ExpectedChars { get; private set; } = [];

public bool SkipWhitespace { get; private set; }

public override bool Parse(ParseContext context, ref ParseResult<T> result)
{
context.EnterParser(this);
Expand Down
8 changes: 4 additions & 4 deletions src/Parlot/Fluent/ZeroOrMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public override bool Parse(ParseContext context, ref ParseResult<IReadOnlyList<T
{
context.EnterParser(this);

List<T>? results = null;
HybridList<T>? results = null;

var start = 0;
var end = 0;
Expand All @@ -36,14 +36,14 @@ public override bool Parse(ParseContext context, ref ParseResult<IReadOnlyList<T
{
if (first)
{
results = [];
first = false;
start = parsed.Start;
}

end = parsed.End;

results ??= [];
results.Add(parsed.Value);

results!.Add(parsed.Value);
}

result.Set(start, end, results ?? (IReadOnlyList<T>)[]);
Expand Down
19 changes: 8 additions & 11 deletions src/Samples/Sql/SqlAst.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable

using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.Design;
using System.Linq;

namespace Parlot.Tests.Sql;
Expand Down Expand Up @@ -529,26 +531,21 @@ public ParameterExpression(Identifier name, Expression? defaultValue = null)
}

// Identifiers
public class Identifier : ISqlNode
public sealed class Identifier : ISqlNode
{
public static readonly Identifier STAR = new (["*"]);

private string _cachedToString = null!;

public IReadOnlyList<string> Parts { get; }

public Identifier(IReadOnlyList<string> parts)
{
Parts = parts;
}

public Identifier(string name) : this(new[] { name })
{
}

public Identifier(params string[] parts)
{
Parts = parts.Where(p => !string.IsNullOrWhiteSpace(p)).ToArray();
}

public override string ToString()
{
return string.Join(".", Parts);
return _cachedToString ??= (Parts.Count == 1 ? Parts[0] : string.Join(".", Parts));
}
}
Loading