/// <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("predicate"); } if (description == null) { throw new ArgumentNullException("description"); } return(i => { if (!i.AtEnd) { if (predicate(i.Current)) { return Result.Success(i.Current, i.Advance()); } return Result.Failure <char>(i, Observe.Error(string.Format("unexpected '{0}'", i.Current), description)); } return Result.Failure <char>(i, Observe.Error("Unexpected end of input reached", description)); }); }
/// <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("reference"); } Parser <T> p = null; return(i => { if (p == null) { p = reference(); } if (i.Memos.ContainsKey(p)) { throw new ParseException(i.Memos[p].ToString()); } i.Memos[p] = Result.Failure <T>(i, Observe.Error("Left recursion in the grammar.")); var result = p(i); i.Memos[p] = result; return result; }); }
/// <summary> /// Parse a stream of elements, attemting to skip over invalid 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> > ManyWithPanic <T, U>(this Parser <T> parser, Parser <U> panicUntil, string elementDescription) { if (parser == null) { throw new ArgumentNullException("parser"); } if (panicUntil == null) { throw new ArgumentNullException("panicUntil"); } return(i => { var remainder = i; var result = new List <T>(); var observations = new List <ResultObservation>(); var r = parser(i); while (true) { if (r.WasSuccessful && remainder == r.Remainder) { break; } if (!r.WasSuccessful) { if (remainder == r.Remainder) { break; } observations.Add(Observe.Error("Unexpected {0}.", elementDescription)); remainder = r.Remainder; var panicResult = panicUntil(remainder); while (!panicResult.WasSuccessful && !remainder.AtEnd) { remainder = remainder.Advance(); panicResult = panicUntil(remainder); } remainder = panicResult.Remainder; } else { result.Add(r.Value); remainder = r.Remainder; } r = parser(remainder); } return Result.Success <IEnumerable <T> >(result, remainder, observations); }); }
/// <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("parser"); } return(i => parser(i).IfSuccess(s => s.Remainder.AtEnd ? s : Result.Failure <T>(s.Remainder, Observe.Error(string.Format("unexpected '{0}'", s.Remainder.Current), "end of input")))); }
/// <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("parser"); } return(i => { var result = parser(i); if (result.WasSuccessful) { var msg = string.Format("'{0}' was not expected", string.Join(", ", result.Observations.SelectMany(x => x.Expectations))); return Result.Failure <object>(i, Observe.Error(msg)); } return Result.Success <object>(null, i); }); }
/// <summary> /// Construct a parser from the given regular expression. /// </summary> /// <param name="regex">The regex expression.</param> /// <param name="description">Description of characters that don't match.</param> /// <returns>a parse of string</returns> public static Parser <string> Regex(Regex regex, string description = null) { if (regex == null) { throw new ArgumentNullException("regex"); } var expectations = description == null ? new string[0] : new[] { description }; return(i => { if (!i.AtEnd) { var remainder = i; var input = i.Source.Substring(i.Position); var match = regex.Match(input); if (match.Success && match.Index == 0) { for (int j = 0; j < match.Length; j++) { remainder = remainder.Advance(); } return Result.Success(match.Value, remainder); } var found = match.Index == input.Length ? "end of source" : string.Format("`{0}'", input[match.Index]); return Result.Failure <string>( remainder, Observe.Error("string matching regex `" + regex.ToString() + "' expected but " + found + " found", expectations)); } return Result.Failure <string>(i, Observe.Error("Unexpected end of input", expectations)); }); }
public static Parser <IEnumerable <T> > Repeat <T>(this Parser <T> parser, int count) { if (parser == null) { throw new ArgumentNullException("parser"); } return(i => { var remainder = i; var result = new List <T>(); for (var n = 0; n < count; ++n) { var r = parser(remainder); if (!r.WasSuccessful) { var what = r.Remainder.AtEnd ? "end of input" : r.Remainder.Current.ToString(); var msg = string.Format("Unexpected '{0}'", what); var exp = string.Format("'{0}' {1} times, but was {2}", string.Join(", ", r.Observations.SelectMany(x => x.Expectations)), count, n); return Result.Failure <IEnumerable <T> >(i, Observe.Error(msg, exp)); } if (remainder != r.Remainder) { result.Add(r.Value); } remainder = r.Remainder; } return Result.Success <IEnumerable <T> >(result, remainder); }); }
/// <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("parser"); } if (except == null) { throw new ArgumentNullException("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, Observe.Error("Excepted parser succeeded.", "other than the excepted input")); } return parser(i); }); }
/// <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("parser"); } if (predicate == null) { throw new ArgumentNullException("predicate"); } return(i => parser(i).IfSuccess(s => predicate(s.Value) ? s : Result.Failure <T>(i, Observe.Error(string.Format("Unexpected {0}.", s.Value))))); }
/// <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("parser"); } if (name == null) { throw new ArgumentNullException("name"); } return(i => parser(i).IfFailure(f => f.Remainder == i ? Result.Failure <T>(f.Remainder, f.Observations.Concat(new[] { Observe.Error(string.Format("Rule named '{0}' failed.", name)) })) : f)); }