Upload from upload_mods.ps1

This commit is contained in:
Nathaniel Cosford
2025-06-04 16:13:32 +09:30
commit 7345f42201
470 changed files with 51966 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
namespace Sprache
{
/// <summary>
/// Constructs customizable comment parsers.
/// </summary>
public class CommentParser : IComment
{
///<summary>
///Single-line comment header.
///</summary>
public string Single { get; set; }
///<summary>
///Newline character preference.
///</summary>
public string NewLine { get; set; }
///<summary>
///Multi-line comment opener.
///</summary>
public string MultiOpen { get; set; }
///<summary>
///Multi-line comment closer.
///</summary>
public string MultiClose { get; set; }
/// <summary>
/// Initializes a Comment with C-style headers and Windows newlines.
/// </summary>
public CommentParser()
{
Single = "//";
MultiOpen = "/*";
MultiClose = "*/";
NewLine = "\n";
}
/// <summary>
/// Initializes a Comment with custom multi-line headers and newline characters.
/// Single-line headers are made null, it is assumed they would not be used.
/// </summary>
/// <param name="multiOpen"></param>
/// <param name="multiClose"></param>
/// <param name="newLine"></param>
public CommentParser(string multiOpen, string multiClose, string newLine)
{
Single = null;
MultiOpen = multiOpen;
MultiClose = multiClose;
NewLine = newLine;
}
/// <summary>
/// Initializes a Comment with custom headers and newline characters.
/// </summary>
/// <param name="single"></param>
/// <param name="multiOpen"></param>
/// <param name="multiClose"></param>
/// <param name="newLine"></param>
public CommentParser(string single, string multiOpen, string multiClose, string newLine)
{
Single = single;
MultiOpen = multiOpen;
MultiClose = multiClose;
NewLine = newLine;
}
///<summary>
///Parse a single-line comment.
///</summary>
public Parser<string> SingleLineComment
{
get
{
if (Single == null)
throw new ParseException("Field 'Single' is null; single-line comments not allowed.");
return from first in Parse.String(Single)
from rest in Parse.CharExcept(NewLine).Many().Text()
select rest;
}
private set { }
}
///<summary>
///Parse a multi-line comment.
///</summary>
public Parser<string> MultiLineComment
{
get
{
if (MultiOpen == null)
throw new ParseException("Field 'MultiOpen' is null; multi-line comments not allowed.");
else if (MultiClose == null)
throw new ParseException("Field 'MultiClose' is null; multi-line comments not allowed.");
return from first in Parse.String(MultiOpen)
from rest in Parse.AnyChar
.Until(Parse.String(MultiClose)).Text()
select rest;
}
private set { }
}
///<summary>
///Parse a comment.
///</summary>
public Parser<string> AnyComment
{
get
{
if (Single != null && MultiOpen != null && MultiClose != null)
return SingleLineComment.Or(MultiLineComment);
else if (Single != null && (MultiOpen == null || MultiClose == null))
return SingleLineComment;
else if (Single == null && (MultiOpen != null && MultiClose != null))
return MultiLineComment;
else throw new ParseException("Unable to parse comment; check values of fields 'MultiOpen' and 'MultiClose'.");
}
private set { }
}
}
}

View File

@@ -0,0 +1,43 @@
namespace Sprache
{
/// <summary>
/// Represents a customizable comment parser.
/// </summary>
public interface IComment
{
///<summary>
/// Single-line comment header.
///</summary>
string Single { get; set; }
///<summary>
/// Newline character preference.
///</summary>
string NewLine { get; set; }
///<summary>
/// Multi-line comment opener.
///</summary>
string MultiOpen { get; set; }
///<summary>
/// Multi-line comment closer.
///</summary>
string MultiClose { get; set; }
///<summary>
/// Parse a single-line comment.
///</summary>
Parser<string> SingleLineComment { get; }
///<summary>
/// Parse a multi-line comment.
///</summary>
Parser<string> MultiLineComment { get; }
///<summary>
/// Parse a comment.
///</summary>
Parser<string> AnyComment { get; }
}
}

View File

@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents a commented result with its leading and trailing comments.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface ICommented<T>
{
/// <summary>
/// Gets the leading comments.
/// </summary>
IEnumerable<string> LeadingComments { get; }
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets the trailing comments.
/// </summary>
IEnumerable<string> TrailingComments { get; }
}
}

View File

@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents an input for parsing.
/// </summary>
public interface IInput : IEquatable<IInput>
{
/// <summary>
/// Advances the input.
/// </summary>
/// <returns>A new <see cref="IInput" /> that is advanced.</returns>
/// <exception cref="System.InvalidOperationException">The input is already at the end of the source.</exception>
IInput Advance();
/// <summary>
/// Gets the whole source.
/// </summary>
string Source { get; }
/// <summary>
/// Gets the current <see cref="System.Char" />.
/// </summary>
char Current { get; }
/// <summary>
/// Gets a value indicating whether the end of the source is reached.
/// </summary>
bool AtEnd { get; }
/// <summary>
/// Gets the current positon.
/// </summary>
int Position { get; }
/// <summary>
/// Gets the current line number.
/// </summary>
int Line { get; }
/// <summary>
/// Gets the current column.
/// </summary>
int Column { get; }
/// <summary>
/// Memos used by this input
/// </summary>
IDictionary<object, object> Memos { get; }
}
}

View File

@@ -0,0 +1,18 @@

namespace Sprache
{
/// <summary>
/// An interface for objects that have a source <see cref="Position"/>.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface IPositionAware<out T>
{
/// <summary>
/// Set the start <see cref="Position"/> and the matched length.
/// </summary>
/// <param name="startPos">The start position</param>
/// <param name="length">The matched length.</param>
/// <returns>The matched result.</returns>
T SetPos(Position startPos, int length);
}
}

View File

@@ -0,0 +1,36 @@
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents a parsing result.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
public interface IResult<out T>
{
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets a value indicating whether wether parsing was successful.
/// </summary>
bool WasSuccessful { get; }
/// <summary>
/// Gets the error message.
/// </summary>
string Message { get; }
/// <summary>
/// Gets the parser expectations in case of error.
/// </summary>
IEnumerable<string> Expectations { get; }
/// <summary>
/// Gets the remainder of the input.
/// </summary>
IInput Remainder { get; }
}
}

View File

@@ -0,0 +1,29 @@
namespace Sprache
{
/// <summary>
/// Represents a text span of the matched result.
/// </summary>
/// <typeparam name="T">Type of the matched result.</typeparam>
public interface ITextSpan<T>
{
/// <summary>
/// Gets the resulting value.
/// </summary>
T Value { get; }
/// <summary>
/// Gets the starting <see cref="Position"/>.
/// </summary>
Position Start { get; }
/// <summary>
/// Gets the ending <see cref="Position"/>.
/// </summary>
Position End { get; }
/// <summary>
/// Gets the length of the text span.
/// </summary>
int Length { get; }
}
}

View File

@@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
namespace Sprache
{
/// <summary>
/// Represents an input for parsing.
/// </summary>
public class Input : IInput
{
private readonly string _source;
private readonly int _position;
private readonly int _line;
private readonly int _column;
/// <summary>
/// Gets the list of memos assigned to the <see cref="Input" /> instance.
/// </summary>
public IDictionary<object, object> Memos { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="Input" /> class.
/// </summary>
/// <param name="source">The source.</param>
public Input(string source)
: this(source, 0)
{
}
internal Input(string source, int position, int line = 1, int column = 1)
{
_source = source;
_position = position;
_line = line;
_column = column;
Memos = new Dictionary<object, object>();
}
/// <summary>
/// Advances the input.
/// </summary>
/// <returns>A new <see cref="IInput" /> that is advanced.</returns>
/// <exception cref="System.InvalidOperationException">The input is already at the end of the source.</exception>
public IInput Advance()
{
if (AtEnd)
throw new InvalidOperationException("The input is already at the end of the source.");
return new Input(_source, _position + 1, Current == '\n' ? _line + 1 : _line, Current == '\n' ? 1 : _column + 1);
}
/// <summary>
/// Gets the whole source.
/// </summary>
public string Source { get { return _source; } }
/// <summary>
/// Gets the current <see cref="System.Char" />.
/// </summary>
public char Current { get { return _source[_position]; } }
/// <summary>
/// Gets a value indicating whether the end of the source is reached.
/// </summary>
public bool AtEnd { get { return _position == _source.Length; } }
/// <summary>
/// Gets the current positon.
/// </summary>
public int Position { get { return _position; } }
/// <summary>
/// Gets the current line number.
/// </summary>
public int Line { get { return _line; } }
/// <summary>
/// Gets the current column.
/// </summary>
public int Column { get { return _column; } }
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Line {0}, Column {1}", _line, _column);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="Input" />.
/// </returns>
public override int GetHashCode()
{
unchecked
{
return ((_source != null ? _source.GetHashCode() : 0) * 397) ^ _position;
}
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="Input" />.
/// </summary>
/// <returns>
/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="Input" />; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return Equals(obj as IInput);
}
/// <summary>
/// Indicates whether the current <see cref="Input" /> is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(IInput other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return string.Equals(_source, other.Source) && _position == other.Position;
}
/// <summary>
/// Indicates whether the left <see cref="Input" /> is equal to the right <see cref="Input" />.
/// </summary>
/// <param name="left">The left <see cref="Input" />.</param>
/// <param name="right">The right <see cref="Input" />.</param>
/// <returns>true if both objects are equal.</returns>
public static bool operator ==(Input left, Input right)
{
return Equals(left, right);
}
/// <summary>
/// Indicates whether the left <see cref="Input" /> is not equal to the right <see cref="Input" />.
/// </summary>
/// <param name="left">The left <see cref="Input" />.</param>
/// <param name="right">The right <see cref="Input" />.</param>
/// <returns>true if the objects are not equal.</returns>
public static bool operator !=(Input left, Input right)
{
return !Equals(left, right);
}
}
}

View File

@@ -0,0 +1,145 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents an optional result.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
public interface IOption<out T>
{
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Gets a value indicating whether this instance is defined.
/// </summary>
bool IsDefined { get; }
/// <summary>
/// Gets the matched result or a default value.
/// </summary>
/// <returns></returns>
T GetOrDefault();
/// <summary>
/// Gets the matched result.
/// </summary>
T Get();
}
/// <summary>
/// Extensions for <see cref="IOption&lt;T&gt;"/>.
/// </summary>
public static class OptionExtensions
{
/// <summary>
/// Gets the value or else returns a default value.
/// </summary>
/// <typeparam name="T">The result type.</typeparam>
/// <param name="option"></param>
/// <param name="defaultValue">The default value.</param>
/// <returns></returns>
public static T GetOrElse<T>(this IOption<T> option, T defaultValue)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsEmpty ? defaultValue : option.Get();
}
/// <summary>
/// Maps a function over the value or else returns an empty option.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type.</typeparam>
/// <param name="option">The option containing the value to apply <paramref name="map" /> to.</param>
/// <param name="map">The function to apply to the value of <paramref name="option" />.</param>
/// <returns>An options result containing the result if there was an input value.</returns>
public static IOption<U> Select<T, U>(this IOption<T> option, Func<T,U> map)
{
if (option == null) throw new ArgumentNullException(nameof(option));
return option.IsDefined ? (IOption<U>) new Some<U>(map(option.Get())) : new None<U>();
}
/// <summary>
/// Binds the value to a function with optional result and flattens the result to a single optional.
/// A result projection is applied aftherwards.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type of <paramref name="bind" />.</typeparam>
/// <typeparam name="V">The final output type.</typeparam>
/// <param name="option">The option containing the value to bind to.</param>
/// <param name="bind">The function that receives the input values and returns an optional value.</param>
/// <param name="project">The function that is projects the result of <paramref name="bind" />.</param>
/// <returns>An option result containing the result if there were was an input value and bind result.</returns>
public static IOption<V> SelectMany<T,U,V>(this IOption<T> option, Func<T,IOption<U>> bind, Func<T,U,V> project)
{
if (option == null) throw new ArgumentNullException(nameof(option));
if (option.IsEmpty) return new None<V>();
var t = option.Get();
return bind(t).Select(u => project(t,u));
}
/// <summary>
/// Binds the value to a function with optional result and flattens the result to a single optional.
/// </summary>
/// <typeparam name="T">The input type.</typeparam>
/// <typeparam name="U">The output type.</typeparam>
/// <param name="option">The option containing the value to bind to.</param>
/// <param name="bind">The function that receives the input values and returns an optional value.</param>
/// <returns>An option result containing the result if there were was an input value and bind result.</returns>
public static IOption<U> SelectMany<T,U>(this IOption<T> option, Func<T,IOption<U>> bind) => option.SelectMany(bind, (_,x) => x);
}
internal abstract class AbstractOption<T> : IOption<T>
{
public abstract bool IsEmpty { get; }
public bool IsDefined
{
get { return !IsEmpty; }
}
public T GetOrDefault()
{
return IsEmpty ? default(T) : Get();
}
public abstract T Get();
}
internal sealed class Some<T> : AbstractOption<T>
{
private readonly T _value;
public Some(T value)
{
_value = value;
}
public override bool IsEmpty
{
get { return false; }
}
public override T Get()
{
return _value;
}
}
internal sealed class None<T> : AbstractOption<T>
{
public override bool IsEmpty
{
get { return true; }
}
public override T Get()
{
throw new InvalidOperationException("Cannot get value from None.");
}
}
}

View File

@@ -0,0 +1,126 @@
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));
}
}
}

View File

@@ -0,0 +1,80 @@
using System;
namespace Sprache
{
partial class Parse
{
/// <summary>
/// Construct a parser that indicates that the given parser
/// is optional. The returned parser will succeed on
/// any input no matter whether the given parser
/// succeeds or not.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <returns>An optional version of the given parser.</returns>
public static Parser<IOption<T>> Optional<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var pr = parser(i);
if (pr.WasSuccessful)
return Result.Success(new Some<T>(pr.Value), pr.Remainder);
return Result.Success(new None<T>(), i);
};
}
/// <summary>
/// Constructs the eXclusive version of the Optional{T} parser.
/// </summary>
/// <typeparam name="T">The result type of the given parser</typeparam>
/// <param name="parser">The parser to wrap</param>
/// <returns>An eXclusive optional version of the given parser.</returns>
/// <seealso cref="XOr"/>
public static Parser<IOption<T>> XOptional<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
return Result.Success(new Some<T>(result.Value), result.Remainder);
if (result.Remainder.Equals(i))
return Result.Success(new None<T>(), i);
return Result.Failure<IOption<T>>(result.Remainder, result.Message, result.Expectations);
};
}
/// <summary>
/// Construct a parser that indicates that the given parser is optional
/// and non-consuming. The returned parser will succeed on
/// any input no matter whether the given parser succeeds or not.
/// In any case, it won't consume any input, like a positive look-ahead in regex.
/// </summary>
/// <typeparam name="T">The result type of the given parser.</typeparam>
/// <param name="parser">The parser to wrap.</param>
/// <returns>A non-consuming version of the given parser.</returns>
public static Parser<IOption<T>> Preview<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
return Result.Success(new Some<T>(result.Value), i);
return Result.Success(new None<T>(), i);
};
}
}
}

View File

@@ -0,0 +1,27 @@

namespace Sprache
{
partial class Parse
{
/// <summary>
/// Construct a parser that will set the position to the position-aware
/// T on succsessful match.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> Positioned<T>(this Parser<T> parser) where T : IPositionAware<T>
{
return i =>
{
var r = parser(i);
if (r.WasSuccessful)
{
return Result.Success(r.Value.SetPos(Position.FromInput(i), r.Remainder.Position - i.Position), r.Remainder);
}
return r;
};
}
}
}

View File

@@ -0,0 +1,34 @@
namespace Sprache
{
partial class Parse
{
/// <summary>
/// \n or \r\n
/// </summary>
public static Parser<string> LineEnd =
(from r in Char('\r').Optional()
from n in Char('\n')
select r.IsDefined ? r.Get().ToString() + n : n.ToString())
.Named("LineEnd");
/// <summary>
/// line ending or end of input
/// </summary>
public static Parser<string> LineTerminator =
Return("").End()
.Or(LineEnd.End())
.Or(LineEnd)
.Named("LineTerminator");
/// <summary>
/// Parser for identifier starting with <paramref name="firstLetterParser"/> and continuing with <paramref name="tailLetterParser"/>
/// </summary>
public static Parser<string> Identifier(Parser<char> firstLetterParser, Parser<char> tailLetterParser)
{
return
from firstLetter in firstLetterParser
from tail in tailLetterParser.Many().Text()
select firstLetter + tail;
}
}
}

View File

@@ -0,0 +1,158 @@
namespace Sprache
{
using System;
using System.Collections.Generic;
using UniLinq;
partial class Parse
{
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="delimiter"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> DelimitedBy<T, U>(this Parser<T> parser, Parser<U> delimiter)
{
return DelimitedBy(parser, delimiter, null, null);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="delimiter"></param>
/// <param name="minimumCount"></param>
/// <param name="maximumCount"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> DelimitedBy<T, U>(this Parser<T> parser, Parser<U> delimiter, int? minimumCount, int? maximumCount)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (delimiter == null) throw new ArgumentNullException(nameof(delimiter));
return from head in parser.Once()
from tail in
(from separator in delimiter
from item in parser
select item).Repeat(minimumCount - 1, maximumCount - 1)
select head.Concat(tail);
}
/// <summary>
/// Fails on the first itemParser failure, if it reads at least one character.
/// </summary>
/// <param name="itemParser"></param>
/// <param name="delimiter"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> XDelimitedBy<T, U>(this Parser<T> itemParser, Parser<U> delimiter)
{
if (itemParser == null) throw new ArgumentNullException(nameof(itemParser));
if (delimiter == null) throw new ArgumentNullException(nameof(delimiter));
return from head in itemParser.Once()
from tail in
(from separator in delimiter
from item in itemParser
select item).XMany()
select head.Concat(tail);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="count"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int count)
{
return Repeat(parser, count, count);
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="minimumCount"></param>
/// <param name="maximumCount"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<IEnumerable<T>> Repeat<T>(this Parser<T> parser, int? minimumCount, int? maximumCount)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var remainder = i;
var result = new List<T>();
var count = 0;
var r = parser(remainder);
while (r.WasSuccessful && (maximumCount == null || count < maximumCount.Value))
{
count++;
result.Add(r.Value);
remainder = r.Remainder;
r = parser(remainder);
}
if (minimumCount.HasValue && count < minimumCount.Value)
{
var what = r.Remainder.AtEnd
? "end of input"
: r.Remainder.Current.ToString();
var msg = $"Unexpected '{what}'";
string exp;
if (minimumCount == maximumCount)
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' {minimumCount.Value} times, but found {count}";
else if (maximumCount == null)
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' minimum {minimumCount.Value} times, but found {count}";
else
exp = $"'{StringExtensions.Join(", ", r.Expectations)}' between {minimumCount.Value} and {maximumCount.Value} times, but found {count}";
return Result.Failure<IEnumerable<T>>(i, msg, new[] { exp });
}
return Result.Success<IEnumerable<T>>(result, remainder);
};
}
/// <summary>
///
/// </summary>
/// <param name="parser"></param>
/// <param name="open"></param>
/// <param name="close"></param>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <typeparam name="V"></typeparam>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public static Parser<T> Contained<T, U, V>(this Parser<T> parser, Parser<U> open, Parser<V> close)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (open == null) throw new ArgumentNullException(nameof(open));
if (close == null) throw new ArgumentNullException(nameof(close));
return from o in open
from item in parser
from c in close
select item;
}
}
}

View File

@@ -0,0 +1,782 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using UniLinq;
namespace Sprache
{
/// <summary>
/// Parsers and combinators.
/// </summary>
public static partial class Parse
{
/// <summary>
/// TryParse a single character matching 'predicate'
/// </summary>
/// <param name="predicate"></param>
/// <param name="description"></param>
/// <returns></returns>
public static Parser<char> Char(Predicate<char> predicate, string description)
{
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
if (description == null) throw new ArgumentNullException(nameof(description));
return i =>
{
if (!i.AtEnd)
{
if (predicate(i.Current))
return Result.Success(i.Current, i.Advance());
return Result.Failure<char>(i,
$"unexpected '{i.Current}'",
new[] { description });
}
return Result.Failure<char>(i,
"Unexpected end of input reached",
new[] { description });
};
}
/// <summary>
/// Parse a single character except those matching <paramref name="predicate"/>.
/// </summary>
/// <param name="predicate">Characters not to match.</param>
/// <param name="description">Description of characters that don't match.</param>
/// <returns>A parser for characters except those matching <paramref name="predicate"/>.</returns>
public static Parser<char> CharExcept(Predicate<char> predicate, string description)
{
return Char(c => !predicate(c), "any character except " + description);
}
/// <summary>
/// Parse a single character c.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Char(char c)
{
return Char(ch => c == ch, char.ToString(c));
}
/// <summary>
/// Parse a single character of any in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Chars(params char[] c)
{
return Char(c.Contains, StringExtensions.Join("|", c));
}
/// <summary>
/// Parse a single character of any in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> Chars(string c)
{
return Char(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable()));
}
/// <summary>
/// Parse a single character except c.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(char c)
{
return CharExcept(ch => c == ch, char.ToString(c));
}
/// <summary>
/// Parses a single character except for those in the given parameters
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(IEnumerable<char> c)
{
var chars = c as char[] ?? c.ToArray();
return CharExcept(chars.Contains, StringExtensions.Join("|", chars));
}
/// <summary>
/// Parses a single character except for those in c
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> CharExcept(string c)
{
return CharExcept(c.ToEnumerable().Contains, StringExtensions.Join("|", c.ToEnumerable()));
}
/// <summary>
/// Parse a single character in a case-insensitive fashion.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static Parser<char> IgnoreCase(char c)
{
return Char(ch => char.ToLower(c) == char.ToLower(ch), char.ToString(c));
}
/// <summary>
/// Parse a string in a case-insensitive fashion.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static Parser<IEnumerable<char>> IgnoreCase(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
return s
.ToEnumerable()
.Select(IgnoreCase)
.Aggregate(Return(Enumerable.Empty<char>()),
(a, p) => a.Concat(p.Once()))
.Named(s);
}
/// <summary>
/// Parse any character.
/// </summary>
public static readonly Parser<char> AnyChar = Char(c => true, "any character");
/// <summary>
/// Parse a whitespace.
/// </summary>
public static readonly Parser<char> WhiteSpace = Char(char.IsWhiteSpace, "whitespace");
/// <summary>
/// Parse a digit.
/// </summary>
public static readonly Parser<char> Digit = Char(char.IsDigit, "digit");
/// <summary>
/// Parse a letter.
/// </summary>
public static readonly Parser<char> Letter = Char(char.IsLetter, "letter");
/// <summary>
/// Parse a letter or digit.
/// </summary>
public static readonly Parser<char> LetterOrDigit = Char(char.IsLetterOrDigit, "letter or digit");
/// <summary>
/// Parse a lowercase letter.
/// </summary>
public static readonly Parser<char> Lower = Char(char.IsLower, "lowercase letter");
/// <summary>
/// Parse an uppercase letter.
/// </summary>
public static readonly Parser<char> Upper = Char(char.IsUpper, "uppercase letter");
/// <summary>
/// Parse a numeric character.
/// </summary>
public static readonly Parser<char> Numeric = Char(char.IsNumber, "numeric character");
/// <summary>
/// Parse a string of characters.
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public static Parser<IEnumerable<char>> String(string s)
{
if (s == null) throw new ArgumentNullException(nameof(s));
return s
.ToEnumerable()
.Select(Char)
.Aggregate(Return(Enumerable.Empty<char>()),
(a, p) => a.Concat(p.Once()))
.Named(s);
}
/// <summary>
/// Constructs a parser that will fail if the given parser succeeds,
/// and will succeed if the given parser fails. In any case, it won't
/// consume any input. It's like a negative look-ahead in regex.
/// </summary>
/// <typeparam name="T">The result type of the given parser</typeparam>
/// <param name="parser">The parser to wrap</param>
/// <returns>A parser that is the opposite of the given parser.</returns>
public static Parser<object> Not<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var result = parser(i);
if (result.WasSuccessful)
{
var msg = $"`{StringExtensions.Join(", ", result.Expectations)}' was not expected";
return Result.Failure<object>(i, msg, new string[0]);
}
return Result.Success<object>(null, i);
};
}
/// <summary>
/// Parse first, and if successful, then parse second.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<U> Then<T, U>(this Parser<T> first, Func<T, Parser<U>> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i => first(i).IfSuccess(s => second(s.Value)(s.Remainder));
}
/// <summary>
/// Parse a stream of elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
/// <remarks>Implemented imperatively to decrease stack usage.</remarks>
public static Parser<IEnumerable<T>> Many<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i =>
{
var remainder = i;
var result = new List<T>();
var r = parser(i);
while (r.WasSuccessful)
{
if (remainder.Equals(r.Remainder))
break;
result.Add(r.Value);
remainder = r.Remainder;
r = parser(remainder);
}
return Result.Success<IEnumerable<T>>(result, remainder);
};
}
/// <summary>
/// Parse a stream of elements, failing if any element is only partially parsed.
/// </summary>
/// <typeparam name="T">The type of element to parse.</typeparam>
/// <param name="parser">A parser that matches a single element.</param>
/// <returns>A <see cref="Parser{T}"/> that matches the sequence.</returns>
/// <remarks>
/// <para>
/// Using <seealso cref="XMany{T}(Parser{T})"/> may be preferable to <seealso cref="Many{T}(Parser{T})"/>
/// where the first character of each match identified by <paramref name="parser"/>
/// is sufficient to determine whether the entire match should succeed. The X*
/// methods typically give more helpful errors and are easier to debug than their
/// unqualified counterparts.
/// </para>
/// </remarks>
/// <seealso cref="XOr"/>
public static Parser<IEnumerable<T>> XMany<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Many().Then(m => parser.Once().XOr(Return(m)));
}
/// <summary>
/// TryParse a stream of elements with at least one item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> AtLeastOnce<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Once().Then(t1 => parser.Many().Select(ts => t1.Concat(ts)));
}
/// <summary>
/// TryParse a stream of elements with at least one item. Except the first
/// item, all other items will be matched with the <code>XMany</code> operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> XAtLeastOnce<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Once().Then(t1 => parser.XMany().Select(ts => t1.Concat(ts)));
}
/// <summary>
/// Parse end-of-input.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> End<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return i => parser(i).IfSuccess(s =>
s.Remainder.AtEnd
? s
: Result.Failure<T>(
s.Remainder,
string.Format("unexpected '{0}'", s.Remainder.Current),
new[] { "end of input" }));
}
/// <summary>
/// Take the result of parsing, and project it onto a different domain.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="convert"></param>
/// <returns></returns>
public static Parser<U> Select<T, U>(this Parser<T> parser, Func<T, U> convert)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (convert == null) throw new ArgumentNullException(nameof(convert));
return parser.Then(t => Return(convert(t)));
}
/// <summary>
/// Parse the token, embedded in any amount of whitespace characters.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<T> Token<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return from leading in WhiteSpace.Many()
from item in parser
from trailing in WhiteSpace.Many()
select item;
}
/// <summary>
/// Refer to another parser indirectly. This allows circular compile-time dependency between parsers.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="reference"></param>
/// <returns></returns>
public static Parser<T> Ref<T>(Func<Parser<T>> reference)
{
if (reference == null) throw new ArgumentNullException(nameof(reference));
Parser<T> p = null;
return i =>
{
if (p == null)
p = reference();
if (i.Memos.ContainsKey(p))
{
var pResult = i.Memos[p] as IResult<T>;
if (pResult.WasSuccessful)
return pResult;
throw new ParseException(pResult.ToString());
}
i.Memos[p] = Result.Failure<T>(i,
"Left recursion in the grammar.",
new string[0]);
var result = p(i);
i.Memos[p] = result;
return result;
};
}
/// <summary>
/// Convert a stream of characters to a string.
/// </summary>
/// <param name="characters"></param>
/// <returns></returns>
public static Parser<string> Text(this Parser<IEnumerable<char>> characters)
{
return characters.Select(chs => new string(chs.ToArray()));
}
/// <summary>
/// Parse first, if it succeeds, return first, otherwise try second.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<T> Or<T>(this Parser<T> first, Parser<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i =>
{
var fr = first(i);
if (!fr.WasSuccessful)
{
return second(i).IfFailure(sf => DetermineBestError(fr, sf));
}
if (fr.Remainder.Equals(i))
return second(i).IfFailure(sf => fr);
return fr;
};
}
/// <summary>
/// Names part of the grammar for help with error messages.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <param name="name"></param>
/// <returns></returns>
public static Parser<T> Named<T>(this Parser<T> parser, string name)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (name == null) throw new ArgumentNullException(nameof(name));
return i => parser(i).IfFailure(f => f.Remainder.Equals(i) ?
Result.Failure<T>(f.Remainder, f.Message, new[] { name }) :
f);
}
/// <summary>
/// Parse first, if it succeeds, return first, otherwise try second.
/// Assumes that the first parsed character will determine the parser chosen (see Try).
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<T> XOr<T>(this Parser<T> first, Parser<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return i => {
var fr = first(i);
if (!fr.WasSuccessful)
{
// The 'X' part
if (!fr.Remainder.Equals(i))
return fr;
return second(i).IfFailure(sf => DetermineBestError(fr, sf));
}
// This handles a zero-length successful application of first.
if (fr.Remainder.Equals(i))
return second(i).IfFailure(sf => fr);
return fr;
};
}
// Examines two results presumably obtained at an "Or" junction; returns the result with
// the most information, or if they apply at the same input position, a union of the results.
static IResult<T> DetermineBestError<T>(IResult<T> firstFailure, IResult<T> secondFailure)
{
if (secondFailure.Remainder.Position > firstFailure.Remainder.Position)
return secondFailure;
if (secondFailure.Remainder.Position == firstFailure.Remainder.Position)
return Result.Failure<T>(
firstFailure.Remainder,
firstFailure.Message,
firstFailure.Expectations.Union(secondFailure.Expectations));
return firstFailure;
}
/// <summary>
/// Parse a stream of elements containing only one item.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Once<T>(this Parser<T> parser)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Select(r => (IEnumerable<T>)new[] { r });
}
/// <summary>
/// Concatenate two streams of elements.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="first"></param>
/// <param name="second"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Concat<T>(this Parser<IEnumerable<T>> first, Parser<IEnumerable<T>> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
return first.Then(f => second.Select(f.Concat));
}
/// <summary>
/// Succeed immediately and return value.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="value"></param>
/// <returns></returns>
public static Parser<T> Return<T>(T value)
{
return i => Result.Success(value, i);
}
/// <summary>
/// Version of Return with simpler inline syntax.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="value"></param>
/// <returns></returns>
public static Parser<U> Return<T, U>(this Parser<T> parser, U value)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
return parser.Select(t => value);
}
/// <summary>
/// Attempt parsing only if the <paramref name="except"/> parser fails.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="except"></param>
/// <returns></returns>
public static Parser<T> Except<T, U>(this Parser<T> parser, Parser<U> except)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (except == null) throw new ArgumentNullException(nameof(except));
// Could be more like: except.Then(s => s.Fail("..")).XOr(parser)
return i =>
{
var r = except(i);
if (r.WasSuccessful)
return Result.Failure<T>(i, "Excepted parser succeeded.", new[] { "other than the excepted input" });
return parser(i);
};
}
/// <summary>
/// Parse a sequence of items until a terminator is reached.
/// Returns the sequence, discarding the terminator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <param name="parser"></param>
/// <param name="until"></param>
/// <returns></returns>
public static Parser<IEnumerable<T>> Until<T, U>(this Parser<T> parser, Parser<U> until)
{
return parser.Except(until).Many().Then(r => until.Return(r));
}
/// <summary>
/// Succeed if the parsed value matches predicate.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parser"></param>
/// <param name="predicate"></param>
/// <returns></returns>
public static Parser<T> Where<T>(this Parser<T> parser, Func<T, bool> predicate)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (predicate == null) throw new ArgumentNullException(nameof(predicate));
return i => parser(i).IfSuccess(s =>
predicate(s.Value) ? s : Result.Failure<T>(i,
string.Format("Unexpected {0}.", s.Value),
new string[0]));
}
/// <summary>
/// Monadic combinator Then, adapted for Linq comprehension syntax.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="U"></typeparam>
/// <typeparam name="V"></typeparam>
/// <param name="parser"></param>
/// <param name="selector"></param>
/// <param name="projector"></param>
/// <returns></returns>
public static Parser<V> SelectMany<T, U, V>(
this Parser<T> parser,
Func<T, Parser<U>> selector,
Func<T, U, V> projector)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (selector == null) throw new ArgumentNullException(nameof(selector));
if (projector == null) throw new ArgumentNullException(nameof(projector));
return parser.Then(t => selector(t).Select(u => projector(t, u)));
}
/// <summary>
/// Chain a left-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> ChainOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainOperatorRest(first, op, operand, apply, Or));
}
/// <summary>
/// Chain a left-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> XChainOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainOperatorRest(first, op, operand, apply, XOr));
}
static Parser<T> ChainOperatorRest<T, TOp>(
T firstOperand,
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply,
Func<Parser<T>, Parser<T>, Parser<T>> or)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return or(op.Then(opvalue =>
operand.Then(operandValue =>
ChainOperatorRest(apply(opvalue, firstOperand, operandValue), op, operand, apply, or))),
Return(firstOperand));
}
/// <summary>
/// Chain a right-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> ChainRightOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, Or));
}
/// <summary>
/// Chain a right-associative operator.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TOp"></typeparam>
/// <param name="op"></param>
/// <param name="operand"></param>
/// <param name="apply"></param>
/// <returns></returns>
public static Parser<T> XChainRightOperator<T, TOp>(
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return operand.Then(first => ChainRightOperatorRest(first, op, operand, apply, XOr));
}
static Parser<T> ChainRightOperatorRest<T, TOp>(
T lastOperand,
Parser<TOp> op,
Parser<T> operand,
Func<TOp, T, T, T> apply,
Func<Parser<T>, Parser<T>, Parser<T>> or)
{
if (op == null) throw new ArgumentNullException(nameof(op));
if (operand == null) throw new ArgumentNullException(nameof(operand));
if (apply == null) throw new ArgumentNullException(nameof(apply));
return or(op.Then(opvalue =>
operand.Then(operandValue =>
ChainRightOperatorRest(operandValue, op, operand, apply, or)).Then(r =>
Return(apply(opvalue, lastOperand, r)))),
Return(lastOperand));
}
/// <summary>
/// Parse a number.
/// </summary>
public static readonly Parser<string> Number = Numeric.AtLeastOnce().Text();
static Parser<string> DecimalWithoutLeadingDigits(CultureInfo ci = null)
{
return from nothing in Return("")
// dummy so that CultureInfo.CurrentCulture is evaluated later
from dot in String((ci ?? CultureInfo.CurrentCulture).NumberFormat.NumberDecimalSeparator).Text()
from fraction in Number
select dot + fraction;
}
static Parser<string> DecimalWithLeadingDigits(CultureInfo ci = null)
{
return Number.Then(n => DecimalWithoutLeadingDigits(ci).XOr(Return("")).Select(f => n + f));
}
/// <summary>
/// Parse a decimal number using the current culture's separator character.
/// </summary>
public static readonly Parser<string> Decimal = DecimalWithLeadingDigits().XOr(DecimalWithoutLeadingDigits());
/// <summary>
/// Parse a decimal number with separator '.'.
/// </summary>
public static readonly Parser<string> DecimalInvariant = DecimalWithLeadingDigits(CultureInfo.InvariantCulture)
.XOr(DecimalWithoutLeadingDigits(CultureInfo.InvariantCulture));
}
}

View File

@@ -0,0 +1,50 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents an error that occurs during parsing.
/// </summary>
public class ParseException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class.
/// </summary>
public ParseException() { }
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public ParseException(string message) : base(message) { }
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message
/// and the position where the error occured.
/// </summary>
/// <param name="message">The message that describes the error.</param>
/// <param name="position">The position where the error occured.</param>
public ParseException(string message, Position position) : base(message)
{
if (position == null) throw new ArgumentNullException(nameof(position));
Position = position;
}
/// <summary>
/// Initializes a new instance of the <see cref="ParseException" /> class with a specified error message
/// and a reference to the inner exception that is the cause of this exception.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception,
/// or a null reference (Nothing in Visual Basic) if no inner exception is specified.</param>
public ParseException(string message, Exception innerException) : base(message, innerException) { }
/// <summary>
/// Gets the position of the parsing failure if one is available; otherwise, null.
/// </summary>
public Position Position {
get;
}
}
}

View File

@@ -0,0 +1,54 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents a parser.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="input">The input to parse.</param>
/// <returns>The result of the parser.</returns>
public delegate IResult<T> Parser<out T>(IInput input);
/// <summary>
/// Contains some extension methods for <see cref="Parser&lt;T&gt;" />.
/// </summary>
public static class ParserExtensions
{
/// <summary>
/// Tries to parse the input without throwing an exception.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="input">The input.</param>
/// <returns>The result of the parser</returns>
public static IResult<T> TryParse<T>(this Parser<T> parser, string input)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (input == null) throw new ArgumentNullException(nameof(input));
return parser(new Input(input));
}
/// <summary>
/// Parses the specified input string.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="parser">The parser.</param>
/// <param name="input">The input.</param>
/// <returns>The result of the parser.</returns>
/// <exception cref="Sprache.ParseException">It contains the details of the parsing error.</exception>
public static T Parse<T>(this Parser<T> parser, string input)
{
if (parser == null) throw new ArgumentNullException(nameof(parser));
if (input == null) throw new ArgumentNullException(nameof(input));
var result = parser.TryParse(input);
if(result.WasSuccessful)
return result.Value;
throw new ParseException(result.ToString(), Position.FromInput(result.Remainder));
}
}
}

View File

@@ -0,0 +1,136 @@
using System;
namespace Sprache
{
/// <summary>
/// Represents a position in the input.
/// </summary>
public class Position : IEquatable<Position>
{
/// <summary>
/// Initializes a new instance of the <see cref="Position" /> class.
/// </summary>
/// <param name="pos">The position.</param>
/// <param name="line">The line number.</param>
/// <param name="column">The column.</param>
public Position(int pos, int line, int column)
{
Pos = pos;
Line = line;
Column = column;
}
/// <summary>
/// Creates an new <see cref="Position"/> instance from a given <see cref="IInput"/> object.
/// </summary>
/// <param name="input">The current input.</param>
/// <returns>A new <see cref="Position"/> instance.</returns>
public static Position FromInput(IInput input)
{
return new Position(input.Position, input.Line, input.Column);
}
/// <summary>
/// Gets the current positon.
/// </summary>
public int Pos
{
get;
private set;
}
/// <summary>
/// Gets the current line number.
/// </summary>
public int Line
{
get;
private set;
}
/// <summary>
/// Gets the current column.
/// </summary>
public int Column
{
get;
private set;
}
/// <summary>
/// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="Position" />.
/// </summary>
/// <returns>
/// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="Position" />; otherwise, false.
/// </returns>
/// <param name="obj">The object to compare with the current object. </param>
public override bool Equals(object obj)
{
return Equals(obj as Position);
}
/// <summary>
/// Indicates whether the current <see cref="Position" /> is equal to another object of the same type.
/// </summary>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
/// <param name="other">An object to compare with this object.</param>
public bool Equals(Position other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Pos == other.Pos
&& Line == other.Line
&& Column == other.Column;
}
/// <summary>
/// Indicates whether the left <see cref="Position" /> is equal to the right <see cref="Position" />.
/// </summary>
/// <param name="left">The left <see cref="Position" />.</param>
/// <param name="right">The right <see cref="Position" />.</param>
/// <returns>true if both objects are equal.</returns>
public static bool operator ==(Position left, Position right)
{
return Equals(left, right);
}
/// <summary>
/// Indicates whether the left <see cref="Position" /> is not equal to the right <see cref="Position" />.
/// </summary>
/// <param name="left">The left <see cref="Position" />.</param>
/// <param name="right">The right <see cref="Position" />.</param>
/// <returns>true if the objects are not equal.</returns>
public static bool operator !=(Position left, Position right)
{
return !Equals(left, right);
}
/// <summary>
/// Serves as a hash function for a particular type.
/// </summary>
/// <returns>
/// A hash code for the current <see cref="Position" />.
/// </returns>
public override int GetHashCode()
{
var h = 31;
h = h * 13 + Pos;
h = h * 13 + Line;
h = h * 13 + Column;
return h;
}
/// <summary>
/// Returns a string that represents the current object.
/// </summary>
/// <returns>
/// A string that represents the current object.
/// </returns>
public override string ToString()
{
return string.Format("Line {0}, Column {1}", Line, Column);
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
/// <summary>
/// Contains helper functions to create <see cref="IResult&lt;T&gt;"/> instances.
/// </summary>
public static class Result
{
/// <summary>
/// Creates a success result.
/// </summary>
/// <typeparam name="T">The type of the result (value).</typeparam>
/// <param name="value">The sucessfully parsed value.</param>
/// <param name="remainder">The remainder of the input.</param>
/// <returns>The new <see cref="IResult&lt;T&gt;"/>.</returns>
public static IResult<T> Success<T>(T value, IInput remainder)
{
return new Result<T>(value, remainder);
}
/// <summary>
/// Creates a failure result.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="remainder">The remainder of the input.</param>
/// <param name="message">The error message.</param>
/// <param name="expectations">The parser expectations.</param>
/// <returns>The new <see cref="IResult&lt;T&gt;"/>.</returns>
public static IResult<T> Failure<T>(IInput remainder, string message, IEnumerable<string> expectations)
{
return new Result<T>(remainder, message, expectations);
}
}
internal class Result<T> : IResult<T>
{
private readonly T _value;
private readonly IInput _remainder;
private readonly bool _wasSuccessful;
private readonly string _message;
private readonly IEnumerable<string> _expectations;
public Result(T value, IInput remainder)
{
_value = value;
_remainder = remainder;
_wasSuccessful = true;
_message = null;
_expectations = Enumerable.Empty<string>();
}
public Result(IInput remainder, string message, IEnumerable<string> expectations)
{
_value = default(T);
_remainder = remainder;
_wasSuccessful = false;
_message = message;
_expectations = expectations;
}
public T Value
{
get
{
if (!WasSuccessful)
throw new InvalidOperationException("No value can be computed.");
return _value;
}
}
public bool WasSuccessful { get { return _wasSuccessful; } }
public string Message { get { return _message; } }
public IEnumerable<string> Expectations { get { return _expectations; } }
public IInput Remainder { get { return _remainder; } }
public override string ToString()
{
if (WasSuccessful)
return string.Format("Successful parsing of {0}.", Value);
var expMsg = "";
if (Expectations.Any())
expMsg = " expected " + Expectations.Aggregate((e1, e2) => e1 + " or " + e2);
var recentlyConsumed = CalculateRecentlyConsumed();
return string.Format("Parsing failure: {0};{1} ({2}); recently consumed: {3}", Message, expMsg, Remainder, recentlyConsumed);
}
private string CalculateRecentlyConsumed()
{
const int windowSize = 10;
var totalConsumedChars = Remainder.Position;
var windowStart = totalConsumedChars - windowSize;
windowStart = windowStart < 0 ? 0 : windowStart;
var numberOfRecentlyConsumedChars = totalConsumedChars - windowStart;
return Remainder.Source.Substring(windowStart, numberOfRecentlyConsumedChars);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
namespace Sprache
{
internal static class ResultHelper
{
public static IResult<U> IfSuccess<T, U>(this IResult<T> result, Func<IResult<T>, IResult<U>> next)
{
if(result == null) throw new ArgumentNullException(nameof(result));
if (result.WasSuccessful)
return next(result);
return Result.Failure<U>(result.Remainder, result.Message, result.Expectations);
}
public static IResult<T> IfFailure<T>(this IResult<T> result, Func<IResult<T>, IResult<T>> next)
{
if (result == null) throw new ArgumentNullException(nameof(result));
return result.WasSuccessful
? result
: next(result);
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using UniLinq;
namespace Sprache
{
internal static class StringExtensions
{
public static IEnumerable<char> ToEnumerable(this string @this)
{
#if STRING_IS_ENUMERABLE
return @this;
#else
if (@this == null) throw new ArgumentNullException(nameof(@this));
for (var i = 0; i < @this.Length; ++i)
{
yield return @this[i];
}
#endif
}
public static string Join<T>(string separator, IEnumerable<T> values)
{
#if STRING_JOIN_ENUMERABLE
return string.Join(separator, values);
#else
return string.Join(separator, values.Select(v => v.ToString()).ToArray());
#endif
}
}
}

View File

@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011 Nicholas Blumhardt
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.