using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
partial class Parse
{
///
/// Represents a text span of the matched result.
///
/// Type of the matched result.
private class TextSpan : ITextSpan
{
public T Value { get; set; }
public Position Start { get; set; }
public Position End { get; set; }
public int Length { get; set; }
}
///
/// Constructs a parser that returns the of the parsed value.
///
/// The result type of the given parser.
/// The parser to wrap.
/// A parser for the text span of the given parser.
public static Parser> Span(this Parser parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var r = parser(i);
if (r.WasSuccessful)
{
var span = new TextSpan
{
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>(r.Remainder, r.Message, r.Expectations);
};
}
///
/// Represents a commented result with its leading and trailing comments.
///
/// Type of the matched result.
private class CommentedValue : ICommented
{
public CommentedValue(T value)
{
LeadingComments = TrailingComments = EmptyStringList;
Value = value;
}
public CommentedValue(IEnumerable leading, T value, IEnumerable trailing)
{
LeadingComments = leading ?? EmptyStringList;
Value = value;
TrailingComments = trailing ?? EmptyStringList;
}
public T Value { get; }
public IEnumerable LeadingComments { get; }
public IEnumerable TrailingComments { get; }
}
private static readonly string[] EmptyStringList = new string[0];
private static readonly IComment DefaultCommentParser = new CommentParser();
///
/// 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.
///
/// The result type of the given parser.
/// The parser to wrap.
/// The comment parser.
/// An extended Token() version of the given parser.
public static Parser> Commented(this Parser 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 first, ITextSpan 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>())
.Where(c => IsSameLine(valueSpan, c)).Count()
from trailingComments in commentSpan.Repeat(trailingCount)
select new CommentedValue(leadingComments, valueSpan.Value, trailingComments.Select(c => c.Value));
}
}
}