127 lines
4.9 KiB
C#
127 lines
4.9 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using UniLinq;
|
|
|
|
namespace Sprache
|
|
{
|
|
partial class Parse
|
|
{
|
|
/// <summary>
|
|
/// Represents a text span of the matched result.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of the matched result.</typeparam>
|
|
private class TextSpan<T> : ITextSpan<T>
|
|
{
|
|
public T Value { get; set; }
|
|
|
|
public Position Start { get; set; }
|
|
|
|
public Position End { get; set; }
|
|
|
|
public int Length { get; set; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs a parser that returns the <see cref="ITextSpan{T}"/> of the parsed value.
|
|
/// </summary>
|
|
/// <typeparam name="T">The result type of the given parser.</typeparam>
|
|
/// <param name="parser">The parser to wrap.</param>
|
|
/// <returns>A parser for the text span of the given parser.</returns>
|
|
public static Parser<ITextSpan<T>> Span<T>(this Parser<T> parser)
|
|
{
|
|
if (parser == null) throw new ArgumentNullException(nameof(parser));
|
|
|
|
return i =>
|
|
{
|
|
var r = parser(i);
|
|
if (r.WasSuccessful)
|
|
{
|
|
var span = new TextSpan<T>
|
|
{
|
|
Value = r.Value,
|
|
Start = Position.FromInput(i),
|
|
End = Position.FromInput(r.Remainder),
|
|
Length = r.Remainder.Position - i.Position,
|
|
};
|
|
|
|
return Result.Success(span, r.Remainder);
|
|
}
|
|
|
|
return Result.Failure<ITextSpan<T>>(r.Remainder, r.Message, r.Expectations);
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Represents a commented result with its leading and trailing comments.
|
|
/// </summary>
|
|
/// <typeparam name="T">Type of the matched result.</typeparam>
|
|
private class CommentedValue<T> : ICommented<T>
|
|
{
|
|
public CommentedValue(T value)
|
|
{
|
|
LeadingComments = TrailingComments = EmptyStringList;
|
|
Value = value;
|
|
}
|
|
|
|
public CommentedValue(IEnumerable<string> leading, T value, IEnumerable<string> trailing)
|
|
{
|
|
LeadingComments = leading ?? EmptyStringList;
|
|
Value = value;
|
|
TrailingComments = trailing ?? EmptyStringList;
|
|
}
|
|
|
|
public T Value { get; }
|
|
|
|
public IEnumerable<string> LeadingComments { get; }
|
|
|
|
public IEnumerable<string> TrailingComments { get; }
|
|
}
|
|
|
|
private static readonly string[] EmptyStringList = new string[0];
|
|
|
|
private static readonly IComment DefaultCommentParser = new CommentParser();
|
|
|
|
/// <summary>
|
|
/// Constructs a parser that consumes a whitespace and all comments
|
|
/// parsed by the commentParser.AnyComment parser, but parses only one trailing
|
|
/// comment that starts exactly on the last line of the parsed value.
|
|
/// </summary>
|
|
/// <typeparam name="T">The result type of the given parser.</typeparam>
|
|
/// <param name="parser">The parser to wrap.</param>
|
|
/// <param name="commentParser">The comment parser.</param>
|
|
/// <returns>An extended Token() version of the given parser.</returns>
|
|
public static Parser<ICommented<T>> Commented<T>(this Parser<T> parser, IComment commentParser = null)
|
|
{
|
|
if (parser == null) throw new ArgumentNullException(nameof(parser));
|
|
|
|
// consume any comment supported by the comment parser
|
|
var comment = (commentParser ?? DefaultCommentParser).AnyComment;
|
|
|
|
// parses any whitespace except for the new lines
|
|
var whiteSpaceExceptForNewLine = WhiteSpace.Except(Chars("\r\n")).Many().Text();
|
|
|
|
// returns true if the second span starts on the first span's last line
|
|
bool IsSameLine(ITextSpan<T> first, ITextSpan<string> second) =>
|
|
first.End.Line == second.Start.Line;
|
|
|
|
// single comment span followed by a whitespace
|
|
var commentSpan =
|
|
from cs in comment.Span()
|
|
from ws in whiteSpaceExceptForNewLine
|
|
select cs;
|
|
|
|
// add leading and trailing comments to the parser
|
|
return
|
|
from leadingWhiteSpace in WhiteSpace.Many()
|
|
from leadingComments in comment.Token().Many()
|
|
from valueSpan in parser.Span()
|
|
from trailingWhiteSpace in whiteSpaceExceptForNewLine
|
|
from trailingPreview in commentSpan.Many().Preview()
|
|
let trailingCount = trailingPreview.GetOrElse(Enumerable.Empty<ITextSpan<string>>())
|
|
.Where(c => IsSameLine(valueSpan, c)).Count()
|
|
from trailingComments in commentSpan.Repeat(trailingCount)
|
|
select new CommentedValue<T>(leadingComments, valueSpan.Value, trailingComments.Select(c => c.Value));
|
|
}
|
|
}
|
|
}
|