Search code examples
c#parsingenumsdslsprache

Using Sprache to parse Enums from identifiers?


I am starting to use Sprache to parse a domain specific language for math expressions. I know I can parse an identifier using something like this:

    static readonly Parser<string> Identifier = 
        from leading in Parse.WhiteSpace.Many()
        from first in Parse.Letter.Once()
        from rest in Parse.LetterOrDigit.Many()
        from trailing in Parse.WhiteSpace.Many()
        select new string(first.Concat(rest).ToArray());

From this I want to build a parser that only succeeds if the Identifier token is one of the text values of an Enum. So say I have an Enum called Dimension, with values Dimension.Location and Dimension.Time. I want to make

    static readonly Parser<Dimension> DimensionIdentifier = ...

that only succeeds if what is being parsed is an Identifier and if the token string of the identifier is one of the enum names ("Location" or "Time"), and that returns the enum value, Dimension.Location or Dimension.Time respectively. Can someone help with what is probably a simple question? Thanks!


Solution

  • Very nice solution stolen from here... http://www.codewise-llc.com/blog/2015/8/13/parsing-enum-values-with-sprache

    Build a typed helper class to build the parser for a given enum...

    public static class EnumParser<T>
    {
        public static Parser<T> Create()
        {
            var names = Enum.GetNames(typeof(T));
    
            var parser = Parse.IgnoreCase(names.First()).Token()
                .Return((T)Enum.Parse(typeof(T), names.First()));
    
            foreach (var name in names.Skip(1))
            {
                parser = parser.Or(Parse.IgnoreCase(name).Token().Return((T)Enum.Parse(typeof(T), name)));
            }
    
            return parser;
        }
    }
    

    Then your parser is simply this...

    public static Parser<Dimension> Dimension = EnumParser<Dimension>.Create();
    

    And some unit tests (change the class name to whatever you are using, I was using the Sprache tutorial to get started)...

     [Test]
            [TestCase("Time", Dimension.Time)]
            [TestCase("Location", Dimension.Location)]
            public void ShouldGetProperEnumValue(string enumValueName, Dimension expected)
            {
                var eValue = QuestionnaireGrammar.Dimension.Parse(enumValueName);
                Assert.AreEqual(expected, eValue);
            }
    
            [Test]
            [ExpectedException]
            [TestCase("Fredo")]
            public void ShouldFailIfNotInList(string enumValueName)
            {
                var eValue = QuestionnaireGrammar.Dimension.Parse(enumValueName);
            }
    

    Interesting library, happy to learn about it.

    OK, fairly easy to chain parsers...

    Created a copy of your identity parser, and called it Identifier2 to keepit clear...

      public static readonly Parser<string> Identifier2 =
                from leading in Parse.WhiteSpace.Many()
                from first in Parse.Letter.Once()
                from rest in Parse.LetterOrDigit.Many()
                from trailing in Parse.WhiteSpace.Many()
                select new string(first.Concat(rest).ToArray());
    

    Then added a compound parser that takes the results of the Identifier2 parser and uses the Dimension parser...

     public  static readonly Parser<Dimension> IdentityDimension =
                from result in Identifier2
                select Dimension.Parse(result);
    

    Though not sure what you are buying -- enum parser already seems to do everything the identifier parser does.