Inside F#

Brian's thoughts on F# and .NET

Monadic parser combinators, part three

Posted by Brian on December 4, 2007

Last time we learned a bit about monadic parser combinators.  Today we are going to build on that foundation and create some new combinators for the parser library.  We’ll use those new combinators to create a few interesting parsers, including a mathematical expression evaluator.  Along the way we’ll learn a little more about lists and lambdas in C#, too.

Review

Let’s review the parser combinator library we have accumulated so far:

// Fail() is a parser that always fails without consuming any input
P<T> Fail<T>()
// Return(x) is a parser that always succeeds with result x, without consuming any input
P<T> Return<T>(T x)
// p1.Then_(p2) runs p1, discards the result, then runs p2 on the remaining input
P<U> Then_<T, U>(this P<T> p1, P<U> p2)
// p1.Then(v => p2) runs p1, binds the result to variable v, then runs p2 on the remaining input
P<U> Then<T, U>(this P<T> p1, Func<T, P<U>> f)
// p1.Or(p2) tries p1, but if it fails, runs p2 on the original input instead
P<T> Or<T>(this P<T> p1, P<T> p2)
// Item() consumes the first character of the input string and returns it as a result
P<char> Item()
// Sat(pred) succeeds parsing a character only if the character matches the predicate
P<char> Sat(Predicate<char> pred)
// Letter parses a letter
P<char> Letter
// Digit parses a digit character
P<char> Digit
// DigitVal parses a digit character and returns the corresponding numeric value
P<int> DigitVal
// Literal(c) parses a particular character
P<char> Literal(char c)
// Literal(s, x) parses a particular string, and returns a fixed result
T Literal<T>(string toParse, T result)

Not a bad start!  Still, all the parsers we have built thus far could only parse a finite amount of input.  Most parser need to be able to parse an arbitrary number of things.  For example, a mathematical expression can have an arbitrary number of terms (e.g. "3+4+5+…"), and a C# function body is composed of some arbitrary number of statements.  To make realistic parsers, we need to be able to parse lists of things.  So let’s get to it.

Lists in C#

What data type should we use to represent a list of things?  The .NET framework offers a number of different kinds of concrete data structures, including arrays, a List class, and LinkedList class, and some others.  When considering lists from a functional programming perspective, however, there are a few common idioms regarding how lists are typically uses.  Common scenarios include applying a function to each value in a list to compute a new list of results, and accumulating all of the values from a list into a single result using some accumulator function.

It turns out that these common idioms appear as extension methods on the IEnumerable<T> interface.  Query comprehensions in LINQ are extremely similar to list comprehensions in a functional programming language like Haskell.  So we’ll use IEnumerable<T> as our list representation when it comes to parsing lists of things.  (In a future blog, IEnumerable<T> might also be valuable to us because of how it can enable lazy evaluation.)

There are all kinds of cool functions for manipulating IEnumerable<T>s in LINQ – I could spend an entire blog entry discussing them.  That would be a needless aside, though, check out the docs yourself to learn more.  Here is just a quick taste:

IEnumerable<int> nums = new int[] { 1, 2, 3, 4 };
IEnumerable<int> squares = nums.Select(x => x * x);  // [1, 4, 9, 16]
int sumOfSquares = squares.Aggregate((x,y) => x+y);  // 30
Cons(0, squares);                                    // [0, 1, 4, 9, 16]

Select applies a given function to each element of a list and returns a list of results.  Aggregate combines the values in a list with a given function.  Cons is not in the .NET library, but rather is a useful little function I defined like this:

public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> rest)
{
    yield return x;
    foreach (T t in rest)
        yield return t;
}

Cons just appends a single value onto the front of a list.  (If you’re not familiar with iterator blocks and the yield statement, don’t sweat it.)  By the way, I am using the term list loosely, simply because repeatedly using the word "IEnumerable" makes for unwieldy prose.

Ok, now that we have a way to represent lists, let’s start authoring some parsers that can consume arbitrary amounts of input.

The Many() combinator

Let’s make a parser for words.  A word is a sequence of one or more letters.  So the goal is to be able to author a parser that will work like so:

word("hello world")  // yields { "hello", " world" }

(Recall that I am using the shorthand "{ Result, RemainingInput }" to describe the ParseResult; see the previous blog for details, if you’ve forgotten.)

To write word, we need a way to parse an arbitrary number of items.  So we invent these combinators:

// Many(p) parses with p zero or more times, returning the sequence of results
public static P<IEnumerable<T>> Many<T>(P<T> p)
{
    return Many1<T>(p).Or(Return(Enumerable.Empty<T>()));
}
// Many1(p) parses with p one or more times, returning the sequence of results
public static P<IEnumerable<T>> Many1<T>(P<T> p)
{
    return p.Then(x =>
           Many(p).Then(xs =>
           Return(Cons(x, xs))));
}

Many and Many1 are mutually recursive; the former parser zero or more occurrences, the latter one or more.  The definition of Many is easy; we try to parse one or more times with p, but if that fails (because there wasn’t even a single instance of the thing that p parses at the start of the input), then we just return the empty list without consuming any input.  Many1 is only a little more complicated.  It parses a single instance of p, and names the result value x.  Then it calls Many to parse the remaining occurrences, and names those results xs.  Now that we’ve parsed all that we can, we return a list containing the first result (x) tacked on the front of the list of the rest of the results (xs).

Now we can define word:

P<string> word = Many1(Letter).Then(chars => Return(new string(chars.ToArray())));
word("arbitrary number of characters") // yields { "arbitrary", " number of characters" }

Many1(Letter) does all the work.  The result type is IEnumerable<char>, though, and we would prefer that word result in a string, so after parsing the letters, we convert the resulting list to a string.  Easy!  This is our first parser that can parse an arbitrarily long input string.

Before moving on, I should note that names of these functions demonstrate a naming convention we’ll use throughout the rest of the library.  "Foo" and "Foo1" are similar combinators, except the former parses zero or more things whereas the latter parses one or more things.

Parsing numbers

Now let’s try to parse numbers.  A natural number is just a fancy term for a non-negative integer.  Let’s try to write a parser for natural numbers.  Suppose we are parsing the string "153".  We already have the parser DigitVal, so we can get the numeric value of each individual character.  We need to accumulate a value as we parse along – each time we see another digit, we multiply the previously-accumulated value by 10 and add the value of the new digit.  Here is one way we could write such a parser:

public static P<int> Natural = DigitVal.Then(x => NaturalHelper(x));
static P<int> NaturalHelper(int x)
{
    return DigitVal.Then(y =>
           NaturalHelper(10 * x + y))
    .Or(Return(x));
}
Natural("153 happy bunnies") // yields { 153, " happy bunnies" }

The logic is clear.  First we parse the initial digit value to seed the accumulator.  Then we start repeatedly calling a helper, which either sees another digit and continues with an accumulated value of 10*x+y, or when it fails to see another digit, just returns the accumulated value.

Simple expressions, and the Chainl1 combinator

Let’s write a very tiny expression parser.  The expressions we want to parse are just natural numbers and the addition and subtraction operators.  Here’s an illustrative example that tests the limits of the desired parser:

simpleExpr("10+8-13-4")  // yields { 1, "" }

Now, it just so happens that left-associative binary operators are a very common thing to want to parse – practically every computer language has expressions like these.  And as it turns out, there’s a very handy combinator that makes parsing these a breeze.  The combinator is called Chainl1 – "Chain" because it chains together values with operators, "l" because it works left-associatively, and "1" because it parses one or more values.  Here’s the code for it, as well as the code for our simple expression parser:

// p.Chainl1(op) parses a grammar like "p (op p)*"
// the result is the p results combined by the op results, left-associatively
public static P<T> Chainl1<T>(this P<T> p, P<Func<T, T, T>> op)
{
    return p.Then(x => Chainl1Helper(x, p, op));
}
static P<T> Chainl1Helper<T>(T x, P<T> p, P<Func<T, T, T>> op)
{
    return op.Then(f =>
           p.Then(y =>
           Chainl1Helper<T>(f(x, y), p, op)))
    .Or(Return(x));
}
P<Func<int,int,int>> addOp = Literal('+').Then_(Return((x, y) => x + y))
                         .Or(Literal('-').Then_(Return((x, y) => x - y)));
P<int> simpleExpr = Natural.Chainl1(addOp);

Yikes!  Let’s break it down.  Chainl1 works kind of like Natural, only it is more general.  Like Natural, it parses an initial seed value, and then calls a recursive helper to continually accumulate values.  But the values it parses are separated by operators, and the operators define the way the values are combined.  We’ll trace through it in a moment.  First have a look at addOp.  It recognizes operators, and depending on what operator it sees, it returns a corresponding C# function.  Our expression parser-evaluator is then just defined as Natural.Chainl1(addOp).

Let’s trace what happens when we try to parse "10+8-13-4".  Chainl1 is called with p bound to Natural and op bound to addOp.  First we run p.  This will parse the initial Natural 10, and store that value in x.  Then we call the recursive helper.  It runs op, which will parse the + character and bind the name f to the C# lambda function that adds two integers.  Then we run p again, parsing 8 and storing the value in y.  Next we compute f(x,y), which is +(10,8), or 18.  And so having parsed the prefix "10+8" we recurse with 18 as the accumulated value x.  This time we parse a and bind it to f, parse the 13 as y, and recurse with 18-13=5.  This time we’ll parse the remaining "-4" and recurse with 1.  And now when we try to parse another operator, we fail (because we’ve reached the end of the input), so control continues to the right-hand-side of the Or call, and we return the accumulated value 1, which is the answer.  Whew!

Sometimes a picture is worth a thousand words.  Here’s a diagram that explains the parse:

simpleExpr

The first call to Chainl1 is circled in the bottom left, and then the subsequent recursive calls to Chainl1Helper are the larger circles (the whole diagram is the last "circle").  Compare the diagram to the code until you grok what’s going on.

A short C# aside

I fudged things a tiny bit in the previous code snippet.  The code included expressions like

Return((x, y) => x + y)

and it turns out that doesn’t compile.  The compiler complains that it can’t infer the type argument of the Return<T> function.  You might think the problem is due to the fact that + can work on doubles as well as on ints, and so you might try changing it to

Return((int x, int y) => x + y)

but you still get the same error.  The problem is that lambdas do not have types (they are merely convertible to delegate types with compatible signatures).  We can fix this by saying

Return<Func<int,int,int>>((x, y) => x + y)

that is, explicitly calling out the T type of the generic Return function.  However this is pretty verbose, and as you’ll see, we’ll be Returning lambdas somewhat frequently.  We can make things more bearable by adding a new Return overload to our library:

public static P<Func<T, T, T>> Return<T>(Func<T, T, T> f)
{
    return Return<Func<T, T, T>>(f);
}

Now there is a named delegate type (Func) to participate in the overload/coercion rules the compiler uses, and as a result, now the code

Return((int x, int y) => x + y)

will compile and work as desired.

Parsing numbers with combinators

I mentioned that the Chainl1 combinator was very generally applicable.  To drive home that point, let’s rewrite the function to parse natural numbers.  Ready?  Ok, here it is!

P<int> Nat = DigitVal.Chainl1(Return((int x, int y) => 10 * x + y));

That’s it!  We chain the DigitVal parser with a Return call – that Return call parses no characters of the input, but returns a useful function for combining the DigitVal results.  Here’s another diagram of how this particular parser works on the string "153":

Nat

In this case, the "operator" between the digit characters is just the empty string.  Neat, huh?  See, I told you Chainl1 was versatile.

The full expression parser

Now let’s revisit the expression parser, but add the ability to do multiplication, division, exponentiation, and parentheses.  The usual precedence rules will apply, and exponentiation will associate to the right.  To deal with right-associative operators, we need Chainr1:

// p.Chainr1(op) is just like p.Chainl1(op), except it's right-associative
public static P<T> Chainr1<T>(this P<T> p, P<Func<T, T, T>> op) { ... }

We won’t look at the code now; you can find it (and all the code from today) at the end of this blog entry.  With this new combinator in hand, we can write a full expression parser:

P<Func<int,int,int>> addOp = Literal('+').Then_(Return((int x, int y) => x + y))
                         .Or(Literal('-').Then_(Return((int x, int y) => x - y)));
P<Func<int,int,int>> mulOp = Literal('*').Then_(Return((int x, int y) => x * y))
                         .Or(Literal('/').Then_(Return((int x, int y) => x / y)));
P<Func<int,int,int>> expOp = Literal('^').Then_(Return((int x, int y) => (int)Math.Pow(x,y)));
P<int> expr = null, term = null, factor = null, part = null, paren = null;
paren  = Literal('(').Then(dummy =>   
         expr.Then(e =>                // (1)
         Literal(')').Then_(
         Return<int>(e))));
part   = Nat.Or(paren);
factor = part.Chainr1(expOp);
term   = factor.Chainl1(mulOp);
expr   = term.Chainl1(addOp);          // (2)
expr("(611-2^3^2+1)/10-5*2")   // yields { 0, "" }

First we define parsers that recognize the strings representing operators and return the corresponding C# function as a result.  We’ve seen this before, with addOp.  Next, we define a bunch of integer parsers for each portion of the language grammar.  Since these guys are mutually recursive, I declare them all up front to avoid a forward-declaration problem.  Then we create parsers for each portion of the grammar, starting with highest precedence.  The paren parser recognizes strings of the form "(exp)", and returns the value that’s the result of parsing the expression inside the parentheses.  The part parser recognizes natural numbers or parenthesized expressions.  factor chains together parts with the exponentiation operator, which has the highest precedence.  And so on for terms and expressions.  The end result is a nice expression parser that can parse and evaluate complicated expressions like the example at the end of the code snippet.

There is one cute aspect worth pointing out.  These guys are mutually recursive, and so there’s no order to define these to make everything immediately work out.  Note especially that paren references expr (on the line marked (1)), but at that moment in time, expr is still null.  expr doesn’t get assigned the right value until the line marked (2).  So why does it work?  The key is the way C# lambdas capture values from the external environment.  Note that in the line above (1), we call "Then(dummy=>" rather than just "Then_(".  This way, expr is referenced inside a lambda, and since it’s a name from outside the lambda, C# captures a reference to this mutable local variable.  A few statements later, when we mutate the value of the local variable expr, this also affects the lambda inside paren.  So by the time we actually use expr to parse a string, everything works.  This is a cute trick, and it saves us from having to define paren/part/factor/term/expr as functions instead of just as variables inside a function.  It’s also a nice opportunity to point out the subtleties of lambda capture in C#; to learn more, check out section 7.14.4.2 of the C# 3.0 spec.

Parsing lists with SepBy

There’s one more handy combinator I want to show off today.  SepBy makes it easy to parse lists of things that are separated by other things.  Here’s an example that shows off parsing a bracket-delimited list of numbers-separated-by-commas:

// p.SepBy(sep) parses zero or more p, each separated by a sep, returning a list of the p results
public static P<IEnumerable<T>> SepBy<T, U>(this P<T> p, P<U> sep) { ... }
P<IEnumerable<int>> listOfNums = Literal('[').Then_(
                                 Nat.SepBy(Literal(',')).Then(nats =>
                                 Literal(']').Then_(
                                 Return(nats))));
listOfNums("[1,10,100]")
listOfNums("[1]")
listOfNums("[]")            

You can see the code for SepBy at the end of this blog entry.

Summary

We’ve covered a lot today!  We introduced a number of combinators for parsing lists of things, including SepBy and Many.  We also learned about the Chainl1 and Chainr1 combinators, which can be used to build expression parsers and even simple parsers for individual numbers.  We built a few cool little parsers that parse things like words, expressions, and comma-separated lists.  And we learned a little more about new C# features, like extension methods for IEnumerable<T>s and lambda capture semantics.  I hope you enjoyed it!

Looking ahead

There’s still a lot of ground we can cover.  A number of potential future topics spring to mind:

  • walking through the plumbing of functions like Then and Or
  • adding better error handling (so that when a parse fails, we know where and why)
  • generalizing the parser library to parse arbitrary token types (rather than just characters)
  • enabling laziness, so that we can stream text through a parser and deliver initial results without parsing the entire string beforehand

We’ll see what I’m next in the mood to write about.

Full source code

As always, here’s today’s code.

using System;
using System.Linq;
using System.Collections.Generic;

namespace MPC_Part3
{
    public class ParseResult<T>
    {
        public readonly T Result;
        public readonly string RemainingInput;
        public ParseResult(T r, string ri) { this.Result = r; this.RemainingInput = ri; }
        public override bool Equals(object obj)
        {
            ParseResult<T> other = obj as ParseResult<T>;
            if (other == null)
                return false;
            if (!object.Equals(other.Result, this.Result))
                return false;
            if (!object.Equals(other.RemainingInput, this.RemainingInput))
                return false;
            return true;
        }
        public override int GetHashCode()
        {
            return this.RemainingInput.GetHashCode();
        }
    }

    // representation type for parsers
    public delegate ParseResult<T> P<T>(string input);

    public static class Parser
    {
        // core functions that know the underlying parser representation
        // Fail() is a parser that always fails without consuming any input
        public static P<T> Fail<T>()
        {
            return input => null;
        }
        // Return(x) is a parser that always succeeds with result x, without consuming any input
        public static P<T> Return<T>(T x)
        {
            return input => new ParseResult<T>(x, input);
        }
        public static P<Func<T, T, T>> Return<T>(Func<T, T, T> f)
        {
            return Return<Func<T, T, T>>(f);
        }
        // p1.Then_(p2) runs p1, discards the result, then runs p2 on the remaining input
        public static P<U> Then_<T, U>(this P<T> p1, P<U> p2)
        {
            return p1.Then(dummy => p2);
        }
        // p1.Then(v => p2) runs p1, binds the result to variable v, then runs p2 on the remaining input
        public static P<U> Then<T, U>(this P<T> p1, Func<T, P<U>> f)
        {
            return input =>
            {
                ParseResult<T> result1 = p1(input);
                if (result1 == null)
                    return null;
                else
                    return f(result1.Result)(result1.RemainingInput);
            };
        }
        // p1.Or(p2) tries p1, but if it fails, runs p2 on the original input instead
        public static P<T> Or<T>(this P<T> p1, P<T> p2)
        {
            return input =>
            {
                ParseResult<T> result1 = p1(input);
                if (result1 == null)
                    return p2(input);
                else
                    return result1;
            };
        }
        // Item() consumes the first character of the input string and returns it as a result
        public static P<char> Item()
        {
            return input =>
            {
                if (string.IsNullOrEmpty(input))
                    return null;
                else
                    return new ParseResult<char>(input[0], input.Substring(1));
            };
        }
        // other handy parser functions
        // Sat(pred) succeeds parsing a character only if the character matches the predicate
        public static P<char> Sat(Predicate<char> pred)
        {
            return Item().Then(c => pred(c) ? Parser.Return(c) : Parser.Fail<char>());
        }
        // Letter parses a letter
        public static P<char> Letter = Sat(char.IsLetter);
        // Digit parses a digit character
        public static P<char> Digit = Sat(char.IsDigit);
        // DigitVal parses a digit character and returns the corresponding numeric value
        public static P<int> DigitVal = Sat(char.IsDigit).Then(c => Return((int)(c - '0')));
        // Literal(c) parses a particular character
        public static P<char> Literal(char c) { return Sat(x => x == c); }
        // Literal(s, x) parses a particular string, and returns a fixed result
        public static P<T> Literal<T>(string toParse, T result)
        {
            if (toParse == "")
                return Parser.Return(result);
            else
                return Literal(toParse[0]).Then_(Literal(toParse.Substring(1), result));
        }
        // Cons(x, rest) returns a new list like rest but with x appended to the front
        public static IEnumerable<T> Cons<T>(T x, IEnumerable<T> rest)
        {
            yield return x;
            foreach (T t in rest)
                yield return t;
        }
        // Many(p) parses with p zero or more times, returning the sequence of results
        public static P<IEnumerable<T>> Many<T>(P<T> p)
        {
            return Many1<T>(p).Or(Return(Enumerable.Empty<T>()));
        }
        // Many1(p) parses with p one or more times, returning the sequence of results
        public static P<IEnumerable<T>> Many1<T>(P<T> p)
        {
            return p.Then(x =>
                   Many(p).Then(xs =>
                   Return(Cons(x, xs))));
        }
        // one way to parse natural numbers
        public static P<int> Natural = DigitVal.Then(x => NaturalHelper(x));
        static P<int> NaturalHelper(int x)
        {
            return DigitVal.Then(y =>
                   NaturalHelper(10*x + y))
            .Or(Return(x));
        }
        // p.Chainl1(op) parses a grammar like "p (op p)*"
        // the result is the p results combined by the op results, left-associatively
        public static P<T> Chainl1<T>(this P<T> p, P<Func<T, T, T>> op)
        {
            return p.Then(x =>
                Chainl1Helper(x, p, op));
        }
        static P<T> Chainl1Helper<T>(T x, P<T> p, P<Func<T, T, T>> op)
        {
            return op.Then(f =>
                   p.Then(y =>
                   Chainl1Helper<T>(f(x, y), p, op)))
            .Or(Return(x));
        }
        // another way to parse naturals
        public static P<int> Nat = DigitVal.Chainl1(Return((int x, int y) => 10 * x + y));
        // p.Chainr1(op) is just like p.Chainl1(op), except it's right-associative
        public static P<T> Chainr1<T>(this P<T> p, P<Func<T, T, T>> op)
        {
            return p.Then(x =>
                   (
                   op.Then(f =>
                   Chainr1(p, op).Then(y =>
                   Return(f(x, y))))
                   )
                   .Or(Return(x)));
        }
        // p.SepBy(sep) parses zero or more p, each separated by a sep, returning a list of the p results
        public static P<IEnumerable<T>> SepBy<T, U>(this P<T> p, P<U> sep)
        {
            return SepBy1(p, sep).Or(Return(Enumerable.Empty<T>()));
        }
        // p.SepBy(sep) parses one or more p, each separated by a sep, returning a list of the p results
        public static P<IEnumerable<T>> SepBy1<T, U>(this P<T> p, P<U> sep)
        {
            return p.Then(x =>
                SepByHelper(p, sep).Then(xs =>
                Return(Cons(x, xs))));
        }
        static P<IEnumerable<T>> SepByHelper<T, U>(P<T> p, P<U> sep)
        {
            return sep.Then_(SepBy1(p, sep))
                .Or(Return(Enumerable.Empty<T>()));
        }

        // various running examples from the blog
        static void AssertEqual<T>(T x, T y)
        {
            if (!object.Equals(x, y))
                throw new Exception("not equal");
        }
        static void AssertEqualEnumerable<T>(ParseResult<IEnumerable<T>> x, ParseResult<IEnumerable<T>> y)
        {
            AssertEqual(x.RemainingInput, y.RemainingInput);
            List<T> xl = x.Result.ToList();
            List<T> yl = y.Result.ToList();
            AssertEqual(xl.Count, yl.Count);
            for (int i = 0; i < xl.Count; ++i)
                AssertEqual(xl[i], yl[i]);
        }

        static void Main(string[] args)
        {
            IEnumerable<int> nums = new int[] { 1, 2, 3, 4 };
            IEnumerable<int> squares = nums.Select(x => x * x);  // [1, 4, 9, 16]
            int sumOfSquares = squares.Aggregate((x,y) => x+y);  // 30
            foreach (int x in Cons(0, squares))                  // [0, 1, 4, 9, 16]
                Console.WriteLine(x);

            P<string> word = Many1(Letter).Then(chars => Return(new string(chars.ToArray())));
            AssertEqual(word("hello world"), new ParseResult<string>("hello", " world"));

            AssertEqual(Natural("256K"), new ParseResult<int>(256, "K"));
            AssertEqual(Nat("256K"), new ParseResult<int>(256, "K"));

            P<Func<int,int,int>> addOp = Literal('+').Then_(Return((int x, int y) => x + y))
                                     .Or(Literal('-').Then_(Return((int x, int y) => x - y)));
            P<int> simpleExpr = Natural.Chainl1(addOp);
            AssertEqual(simpleExpr("10+2+3-14"), new ParseResult<int>(1, ""));

            P<Func<int,int,int>> mulOp = Literal('*').Then_(Return((int x, int y) => x * y))
                                     .Or(Literal('/').Then_(Return((int x, int y) => x / y)));
            P<Func<int,int,int>> expOp = Literal('^').Then_(Return((int x, int y) => (int)Math.Pow(x,y)));
            P<int> expr = null, term = null, factor = null, part = null, paren = null;
            paren = Literal('(').Then(dummy => // use dummy lambda to capture reference to mutable "expr"
                    expr.Then(e =>
                    Literal(')').Then_(
                    Return<int>(e))));
            part = Nat.Or(paren);
            factor = part.Chainr1(expOp);
            term = factor.Chainl1(mulOp);
            expr = term.Chainl1(addOp);
            AssertEqual(expr("(611-2^3^2+1)/10-5*2"), new ParseResult<int>(0, ""));

            P<IEnumerable<int>> listOfNums = Literal('[').Then_(
                                             Nat.SepBy(Literal(',')).Then(nats =>
                                             Literal(']').Then_(
                                             Return(nats))));
            AssertEqualEnumerable(listOfNums("[1,10,100]"), new ParseResult<IEnumerable<int>>(new int[] { 1, 10, 100 }, ""));
            AssertEqualEnumerable(listOfNums("[1]"), new ParseResult<IEnumerable<int>>(new int[] { 1 }, ""));
            AssertEqualEnumerable(listOfNums("[]"), new ParseResult<IEnumerable<int>>(new int[] { }, ""));
            
            Console.WriteLine("done, press a key");
            Console.ReadKey();
        }
    }
}
Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
%d bloggers like this: