Skip to content

Commit fc2e77b

Browse files
committed
Merge remote-tracking branch 'origin/main' into sebros/sourcegen
# Conflicts: # src/Parlot/Fluent/LeftAssociative.cs # src/Parlot/Fluent/Unary.cs
2 parents dd6abe2 + c268ebf commit fc2e77b

12 files changed

Lines changed: 768 additions & 36 deletions

File tree

.github/dependabot.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ updates:
99
directory: "/" # Location of package manifests
1010
schedule:
1111
interval: "weekly"
12+
# Ignore Abstractions packages - these should not be updated
13+
ignore:
14+
- dependency-name: "*Abstractions"
1215
groups:
1316
# Grouped version updates configuration
1417
all-dependencies:
1518
patterns:
1619
- "*"
17-
exclude-patterns:
18-
- "*Abstractions" # Don't update abstractions packages

Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
</PropertyGroup>
55
<ItemGroup>
66
<PackageVersion Include="System.Memory" Version="4.6.3" />
7+
<PackageVersion Include="System.Collections.Immutable" Version="10.0.1" />
78
<PackageVersion Include="FastExpressionCompiler.Internal.src" Version="5.3.3" />
89
<!-- Benchmarks -->
910
<PackageVersion Include="BenchmarkDotNet" Version="0.15.8" />

src/Parlot/CharMap.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Collections.Frozen;
34
using System.Linq;
4-
using System.Reflection;
55
using System.Runtime.CompilerServices;
66

77
namespace Parlot;
@@ -12,10 +12,8 @@ namespace Parlot;
1212
/// </summary>
1313
internal sealed class CharMap<T> where T : class
1414
{
15-
public static MethodInfo IndexerMethodInfo = typeof(CharMap<T>).GetMethod("get_Item", BindingFlags.Public | BindingFlags.Instance)!;
16-
1715
private readonly T[] _asciiMap = new T[128];
18-
private Dictionary<uint, T>? _nonAsciiMap;
16+
private FrozenDictionary<uint, T>? _nonAsciiMap;
1917

2018
public CharMap()
2119
{
@@ -34,6 +32,8 @@ public CharMap(IEnumerable<KeyValuePair<char, T>> map)
3432
ExpectedChars = [.. charSet];
3533
Array.Sort(ExpectedChars);
3634

35+
Dictionary<uint, T>? nonAsciiMap = null;
36+
3737
foreach (var item in map)
3838
{
3939
var c = item.Key;
@@ -43,14 +43,19 @@ public CharMap(IEnumerable<KeyValuePair<char, T>> map)
4343
}
4444
else
4545
{
46-
_nonAsciiMap ??= [];
46+
nonAsciiMap ??= [];
4747

48-
if (!_nonAsciiMap.ContainsKey(c))
48+
if (!nonAsciiMap.ContainsKey(c))
4949
{
50-
_nonAsciiMap[c] = item.Value;
50+
nonAsciiMap[c] = item.Value;
5151
}
5252
}
5353
}
54+
55+
if (nonAsciiMap != null)
56+
{
57+
_nonAsciiMap = nonAsciiMap.ToFrozenDictionary();
58+
}
5459
}
5560

5661
public void Set(char c, T value)
@@ -64,11 +69,12 @@ public void Set(char c, T value)
6469
}
6570
else
6671
{
67-
_nonAsciiMap ??= [];
72+
Dictionary<uint, T> dic = _nonAsciiMap == null ? [] : new(_nonAsciiMap);
6873

69-
if (!_nonAsciiMap.ContainsKey(c))
74+
if (!dic.ContainsKey(c))
7075
{
71-
_nonAsciiMap[c] = value;
76+
dic[c] = value;
77+
_nonAsciiMap = dic.ToFrozenDictionary();
7278
}
7379
}
7480
}

src/Parlot/Fluent/LeftAssociative.cs

Lines changed: 304 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,3 +353,307 @@ static Type GetParserValueType(object parser)
353353

354354
public override string ToString() => Name ?? $"LeftAssociative({_parser})";
355355
}
356+
357+
358+
public sealed class LeftAssociativeWithContext<T, TInput> : Parser<T>, ICompilable, ISourceable
359+
{
360+
private readonly Parser<T> _parser;
361+
private readonly (Parser<TInput> Op, Func<ParseContext, T, T, T> Factory)[] _operators;
362+
363+
public LeftAssociativeWithContext(Parser<T> parser, (Parser<TInput> op, Func<ParseContext, T, T, T> factory)[] operators)
364+
{
365+
_parser = parser ?? throw new ArgumentNullException(nameof(parser));
366+
_operators = operators ?? throw new ArgumentNullException(nameof(operators));
367+
368+
if (_operators.Length == 0)
369+
{
370+
throw new ArgumentException("At least one operator must be provided.", nameof(operators));
371+
}
372+
}
373+
374+
public override bool Parse(ParseContext context, ref ParseResult<T> result)
375+
{
376+
context.EnterParser(this);
377+
378+
if (!_parser.Parse(context, ref result))
379+
{
380+
context.ExitParser(this);
381+
return false;
382+
}
383+
384+
var value = result.Value;
385+
var end = result.End;
386+
387+
while (true)
388+
{
389+
var operatorPosition = context.Scanner.Cursor.Position;
390+
var operatorResult = new ParseResult<TInput>();
391+
Func<ParseContext, T, T, T>? matchedFactory = null;
392+
393+
foreach (var (op, factory) in _operators)
394+
{
395+
if (op.Parse(context, ref operatorResult))
396+
{
397+
matchedFactory = factory;
398+
break;
399+
}
400+
}
401+
402+
if (matchedFactory == null)
403+
{
404+
break;
405+
}
406+
407+
var rightResult = new ParseResult<T>();
408+
if (!_parser.Parse(context, ref rightResult))
409+
{
410+
context.Scanner.Cursor.ResetPosition(operatorPosition);
411+
break;
412+
}
413+
414+
value = matchedFactory(context, value, rightResult.Value);
415+
end = rightResult.End;
416+
}
417+
418+
result = new ParseResult<T>(result.Start, end, value);
419+
420+
context.ExitParser(this);
421+
return true;
422+
}
423+
424+
public CompilationResult Compile(CompilationContext context)
425+
{
426+
var result = context.CreateCompilationResult<T>();
427+
428+
var nextNum = context.NextNumber;
429+
var currentValue = result.DeclareVariable<T>($"leftAssocCtxValue{nextNum}");
430+
var matchedFactory = result.DeclareVariable<Func<ParseContext, T, T, T>>($"matchedFactoryCtx{nextNum}");
431+
var operatorPosition = result.DeclareVariable<TextPosition>($"leftAssocCtxPos{nextNum}");
432+
433+
var breakLabel = Expression.Label($"leftAssocCtxBreak{nextNum}");
434+
435+
var firstParserResult = _parser.Build(context);
436+
var rightParserResult = _parser.Build(context);
437+
438+
var operatorChecks = new List<Expression>();
439+
var allOperatorVariables = new List<ParameterExpression>();
440+
441+
for (int i = 0; i < _operators.Length; i++)
442+
{
443+
var (op, factory) = _operators[i];
444+
var opCompileResult = op.Build(context);
445+
446+
foreach (var variable in opCompileResult.Variables)
447+
{
448+
allOperatorVariables.Add(variable);
449+
}
450+
451+
var factoryConst = Expression.Constant(factory);
452+
453+
if (i > 0)
454+
{
455+
operatorChecks.Add(
456+
Expression.IfThen(
457+
Expression.Equal(matchedFactory, Expression.Constant(null, typeof(Func<ParseContext, T, T, T>))),
458+
Expression.Block(
459+
opCompileResult.Body.Concat([
460+
Expression.IfThen(
461+
opCompileResult.Success,
462+
Expression.Assign(matchedFactory, factoryConst)
463+
)
464+
])
465+
)
466+
)
467+
);
468+
}
469+
else
470+
{
471+
operatorChecks.AddRange(opCompileResult.Body);
472+
operatorChecks.Add(
473+
Expression.IfThen(
474+
opCompileResult.Success,
475+
Expression.Assign(matchedFactory, factoryConst)
476+
)
477+
);
478+
}
479+
}
480+
481+
var scanner = Expression.Field(context.ParseContext, nameof(ParseContext.Scanner));
482+
var cursor = Expression.Field(scanner, nameof(Scanner.Cursor));
483+
var cursorPosition = Expression.Property(cursor, nameof(Cursor.Position));
484+
var resetPosition = typeof(Cursor).GetMethod(nameof(Cursor.ResetPosition), [typeof(TextPosition).MakeByRefType()])!;
485+
486+
var loopBody = Expression.Block(
487+
allOperatorVariables.Concat(rightParserResult.Variables),
488+
new Expression[]
489+
{
490+
Expression.Assign(matchedFactory, Expression.Constant(null, typeof(Func<ParseContext, T, T, T>))),
491+
Expression.Assign(operatorPosition, cursorPosition)
492+
}
493+
.Concat(operatorChecks)
494+
.Concat([
495+
Expression.IfThen(
496+
Expression.Equal(matchedFactory, Expression.Constant(null, typeof(Func<ParseContext, T, T, T>))),
497+
Expression.Break(breakLabel)
498+
)
499+
])
500+
.Concat(rightParserResult.Body)
501+
.Concat([
502+
Expression.IfThen(
503+
Expression.Not(rightParserResult.Success),
504+
Expression.Block(
505+
Expression.Call(cursor, resetPosition, operatorPosition),
506+
Expression.Break(breakLabel)
507+
)
508+
),
509+
Expression.Assign(currentValue,
510+
Expression.Invoke(matchedFactory, context.ParseContext, currentValue, rightParserResult.Value))
511+
])
512+
);
513+
514+
var loopExpr = Expression.Loop(loopBody, breakLabel);
515+
516+
result.Body.Add(
517+
Expression.Block(
518+
firstParserResult.Variables,
519+
new Expression[] { Expression.Block(firstParserResult.Body) }.Concat([
520+
Expression.IfThenElse(
521+
firstParserResult.Success,
522+
Expression.Block(
523+
Expression.Assign(currentValue, firstParserResult.Value),
524+
loopExpr,
525+
Expression.Assign(result.Success, Expression.Constant(true)),
526+
context.DiscardResult ? Expression.Empty() : Expression.Assign(result.Value, currentValue)
527+
),
528+
Expression.Assign(result.Success, Expression.Constant(false))
529+
)
530+
])
531+
)
532+
);
533+
534+
return result;
535+
}
536+
537+
public SourceResult GenerateSource(SourceGenerationContext context)
538+
{
539+
ThrowHelper.ThrowIfNull(context, nameof(context));
540+
541+
if (_parser is not ISourceable parserSourceable)
542+
{
543+
throw new NotSupportedException("LeftAssociative requires the base parser to be source-generatable.");
544+
}
545+
546+
var result = context.CreateResult(typeof(T));
547+
var ctx = context.ParseContextName;
548+
var cursorName = context.CursorName;
549+
var valueTypeName = SourceGenerationContext.GetTypeName(typeof(T));
550+
551+
var uniqueId = context.NextNumber();
552+
var operatorMatchedName = $"opMatched{context.NextNumber()}";
553+
554+
result.Body.Add($"bool {operatorMatchedName} = false;");
555+
556+
static Type GetParserValueType(object parser)
557+
{
558+
var type = parser.GetType();
559+
while (type != null)
560+
{
561+
if (type.IsGenericType && type.GetGenericTypeDefinition().FullName == "Parlot.Fluent.Parser`1")
562+
{
563+
return type.GetGenericArguments()[0];
564+
}
565+
type = type.BaseType!;
566+
}
567+
throw new InvalidOperationException("Unable to determine parser value type.");
568+
}
569+
570+
var baseHelperName = context.Helpers
571+
.GetOrCreate(parserSourceable, $"{context.MethodNamePrefix}_LeftAssocCtx{uniqueId}", valueTypeName, () => parserSourceable.GenerateSource(context))
572+
.MethodName;
573+
574+
if (context.DiscardResult)
575+
{
576+
result.Body.Add($"if ({baseHelperName}({ctx}, out _))");
577+
}
578+
else
579+
{
580+
result.Body.Add($"if ({baseHelperName}({ctx}, out {result.ValueVariable}))");
581+
}
582+
583+
result.Body.Add("{");
584+
result.Body.Add(" while (true)");
585+
result.Body.Add(" {");
586+
result.Body.Add($" {operatorMatchedName} = false;");
587+
588+
var operatorPositionName = $"opPos{context.NextNumber()}";
589+
result.Body.Add($" var {operatorPositionName} = {cursorName}.Position;");
590+
591+
for (int i = 0; i < _operators.Length; i++)
592+
{
593+
var (op, factory) = _operators[i];
594+
595+
if (op is not ISourceable opSourceable)
596+
{
597+
throw new NotSupportedException("LeftAssociative requires all operator parsers to be source-generatable.");
598+
}
599+
600+
var factoryFieldName = context.RegisterLambda(factory);
601+
602+
var opValueTypeName = SourceGenerationContext.GetTypeName(GetParserValueType(opSourceable));
603+
var opHelperName = context.Helpers
604+
.GetOrCreate(opSourceable, $"{context.MethodNamePrefix}_LeftAssocCtx{uniqueId}", opValueTypeName, () => opSourceable.GenerateSource(context))
605+
.MethodName;
606+
607+
var opResultName = $"opResult{context.NextNumber()}";
608+
609+
var indent = " ";
610+
if (i == 0)
611+
{
612+
result.Body.Add($"{indent}if ({opHelperName}({ctx}, out _))");
613+
}
614+
else
615+
{
616+
result.Body.Add($"{indent}if (!{operatorMatchedName})");
617+
result.Body.Add($"{indent}{{");
618+
result.Body.Add($"{indent} if ({opHelperName}({ctx}, out _))");
619+
}
620+
621+
var innerIndent = i == 0 ? indent : $"{indent} ";
622+
result.Body.Add($"{innerIndent}{{");
623+
result.Body.Add($"{innerIndent} if ({baseHelperName}({ctx}, out var {opResultName}RightValue))");
624+
result.Body.Add($"{innerIndent} {{");
625+
result.Body.Add($"{innerIndent} {operatorMatchedName} = true;");
626+
627+
if (!context.DiscardResult)
628+
{
629+
result.Body.Add($"{innerIndent} {result.ValueVariable} = {factoryFieldName}({ctx}, {result.ValueVariable}, {opResultName}RightValue);");
630+
}
631+
else
632+
{
633+
result.Body.Add($"{innerIndent} {factoryFieldName}({ctx}, {result.ValueVariable}, {opResultName}RightValue);");
634+
}
635+
636+
result.Body.Add($"{innerIndent} }}");
637+
result.Body.Add($"{innerIndent} else");
638+
result.Body.Add($"{innerIndent} {{");
639+
result.Body.Add($"{innerIndent} {cursorName}.ResetPosition({operatorPositionName});");
640+
result.Body.Add($"{innerIndent} break;");
641+
result.Body.Add($"{innerIndent} }}");
642+
result.Body.Add($"{innerIndent}}}");
643+
644+
if (i > 0)
645+
{
646+
result.Body.Add($"{indent}}}");
647+
}
648+
}
649+
650+
result.Body.Add($" if (!{operatorMatchedName}) break;");
651+
result.Body.Add(" }");
652+
result.Body.Add($" {result.SuccessVariable} = true;");
653+
result.Body.Add("}");
654+
655+
return result;
656+
}
657+
658+
public override string ToString() => Name ?? $"LeftAssociative({_parser})";
659+
}

0 commit comments

Comments
 (0)