{"id":1797,"date":"2016-01-28T01:06:17","date_gmt":"2016-01-28T01:06:17","guid":{"rendered":"http:\/\/www.ericwhite.com\/home2\/bm8qcmjy\/public_html\/blog\/?page_id=1797"},"modified":"2016-01-28T01:06:17","modified_gmt":"2016-01-28T01:06:17","slug":"recursive-descent-parser-code","status":"publish","type":"page","link":"https:\/\/www.ericwhite.com\/blog\/recursive-descent-parser-code\/","title":{"rendered":"Recursive Descent Parser Code"},"content":{"rendered":"<p>Following is the code of this example:<\/p>\n<pre>\r\nusing System;\r\nusing System.Collections.Generic;\r\nusing System.Linq;\r\nusing System.Text;\r\n\r\nnamespace SimpleParser\r\n{\r\n    public abstract class Symbol\r\n    {\r\n        public List<Symbol> ConstituentSymbols { get; set; }\r\n        public override string ToString()\r\n        {\r\n            string s = ConstituentSymbols.Select(ct => ct.ToString()).StringConcatenate();\r\n            return s; \r\n        }\r\n        public Symbol(params Object[] symbols)\r\n        {\r\n            List<Symbol> ls = new List<Symbol>();\r\n            foreach (var item in symbols)\r\n            {\r\n                if (item is Symbol)\r\n                    ls.Add((Symbol)item);\r\n                else if (item is IEnumerable<Symbol>)\r\n                    foreach (var item2 in (IEnumerable<Symbol>)item)\r\n                        ls.Add(item2);\r\n                else\r\n                    \/\/ If this error is thrown, the parser is coded incorrectly.\r\n                    throw new ParserException(\"Internal error\");\r\n            }\r\n            ConstituentSymbols = ls;\r\n        }\r\n        public Symbol() { }\r\n    }\r\n\r\n    public class Formula : Symbol\r\n    {\r\n        public static Formula Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ formula = expression\r\n\r\n            Expression e = Expression.Produce(symbols);\r\n            return e == null ? null : new Formula(e);\r\n        }\r\n        public Formula(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class Expression : Symbol\r\n    {\r\n        public static Expression Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ expression = *whitespace nospace-expression *whitespace\r\n\r\n            int whiteSpaceBefore = symbols.TakeWhile(s => s is WhiteSpace).Count();\r\n            int whiteSpaceAfter = symbols.Reverse().TakeWhile(s => s is WhiteSpace).Count();\r\n            IEnumerable<Symbol> noSpaceSymbolList = symbols\r\n                .Skip(whiteSpaceBefore)\r\n                .SkipLast(whiteSpaceAfter)\r\n                .ToList();\r\n            NospaceExpression n = NospaceExpression.Produce(noSpaceSymbolList);\r\n            if (n != null)\r\n                return new Expression(\r\n                    Enumerable.Repeat(new WhiteSpace(), whiteSpaceBefore),\r\n                    n,\r\n                    Enumerable.Repeat(new WhiteSpace(), whiteSpaceAfter));\r\n            return null;\r\n        }\r\n\r\n        public Expression(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class NospaceExpression : Symbol\r\n    {\r\n        public static Dictionary<string, int> OperatorPrecedence = new Dictionary<string, int>()\r\n        {\r\n            { \"^\", 3 },\r\n            { \"*\", 2 },\r\n            { \"\/\", 2 },\r\n            { \"+\", 1 },\r\n            { \"-\", 1 },\r\n        };\r\n\r\n        public static NospaceExpression Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ nospace-expression = open-parenthesis expression close-parenthesis\r\n            \/\/         \/ numerical-constant\r\n            \/\/         \/ prefix-operator expression\r\n            \/\/         \/ expression infix-operator expression\r\n\r\n            if (!symbols.Any())\r\n                return null;\r\n\r\n            if (symbols.First() is OpenParenthesis && symbols.Last() is CloseParenthesis)\r\n            {\r\n                Expression e = Expression.Produce(symbols.Skip(1).SkipLast(1));\r\n                if (e != null)\r\n                    return new NospaceExpression(new OpenParenthesis(), e, new CloseParenthesis());\r\n            }\r\n\r\n            \/\/ expression, infix-operator, expression\r\n            var z = symbols.Rollup(0, (t, d) =>\r\n            {\r\n                if (t is OpenParenthesis)\r\n                    return d + 1;\r\n                if (t is CloseParenthesis)\r\n                    return d - 1;\r\n                return d;\r\n            });\r\n            var symbolsWithIndex = symbols.Select((s, i) => new\r\n            {\r\n                Symbol = s,\r\n                Index = i,\r\n            });\r\n            var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new\r\n            {\r\n                SymbolWithIndex = v1,\r\n                Depth = v2,\r\n            });\r\n            var operatorList = z2\r\n                .Where(x => x.Depth == 0 &&\r\n                    x.SymbolWithIndex.Index != 0 &&\r\n                    InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)\r\n                .ToList();\r\n            if (operatorList.Any())\r\n            {\r\n                int minPrecedence = operatorList\r\n                    .Select(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()]).Min();\r\n                var op = operatorList\r\n                    .Last(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()] == minPrecedence);\r\n                if (op != null)\r\n                {\r\n                    var expressionTokenList1 = symbols.TakeWhile(t => t != op.SymbolWithIndex.Symbol);\r\n                    Expression e1 = Expression.Produce(expressionTokenList1);\r\n                    if (e1 == null)\r\n                        throw new ParserException(\"Invalid expression\");\r\n                    var expressionTokenList2 = symbols\r\n                        .SkipWhile(t => t != op.SymbolWithIndex.Symbol).Skip(1);\r\n                    Expression e2 = Expression.Produce(expressionTokenList2);\r\n                    if (e2 == null)\r\n                        throw new ParserException(\"Invalid expression\");\r\n                    InfixOperator io = new InfixOperator(op.SymbolWithIndex.Symbol);\r\n                    return new NospaceExpression(e1, io, e2);\r\n                }\r\n            }\r\n\r\n            NumericalConstant n = NumericalConstant.Produce(symbols);\r\n            if (n != null)\r\n                return new NospaceExpression(n);\r\n\r\n            PrefixOperator p = PrefixOperator.Produce(symbols.FirstOrDefault());\r\n            if (p != null)\r\n            {\r\n                Expression e = Expression.Produce(symbols.Skip(1));\r\n                if (e != null)\r\n                    return new NospaceExpression(p, e);\r\n            }\r\n\r\n            return null;\r\n        }\r\n\r\n        public NospaceExpression(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class NumericalConstant : Symbol\r\n    {\r\n        public static NumericalConstant Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ numerical-constant = [neg-sign] significand-part\r\n\r\n            SignificandPart s = SignificandPart.Produce(symbols);\r\n            if (s != null)\r\n                return new NumericalConstant(s);\r\n            NegSign n = NegSign.Produce(symbols.First());\r\n            if (n != null)\r\n            {\r\n                SignificandPart s2 = SignificandPart.Produce(symbols.Skip(1));\r\n                if (s2 != null)\r\n                    return new NumericalConstant(n, s2);\r\n            }\r\n            return null;\r\n        }\r\n        public NumericalConstant(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class SignificandPart : Symbol\r\n    {\r\n        public static SignificandPart Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ significand-part = whole-number-part [fractional-part] \/ fractional-part\r\n\r\n            FractionalPart f;\r\n            f = FractionalPart.Produce(symbols);\r\n            if (f != null)\r\n                return new SignificandPart(f);\r\n            IEnumerable<Symbol> s = null;\r\n            WholeNumberPart w = WholeNumberPart.Produce(symbols, out s);\r\n            if (w != null)\r\n            {\r\n                if (!s.Any())\r\n                    return new SignificandPart(w);\r\n                f = FractionalPart.Produce(s);\r\n                if (f != null)\r\n                    return new SignificandPart(w, f);\r\n            }\r\n            return null;\r\n        }\r\n        public SignificandPart(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class WholeNumberPart : Symbol\r\n    {\r\n        public static WholeNumberPart Produce(IEnumerable<Symbol> symbols,\r\n            out IEnumerable<Symbol> symbolsToProcess)\r\n        {\r\n            \/\/ whole-number-part = digit-sequence\r\n\r\n            IEnumerable<Symbol> s = null;\r\n            DigitSequence d = DigitSequence.Produce(symbols, out s);\r\n            if (d != null)\r\n            {\r\n                symbolsToProcess = s;\r\n                return new WholeNumberPart(d);\r\n            }\r\n            symbolsToProcess = null;\r\n            return null;\r\n        }\r\n        public WholeNumberPart(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class FractionalPart : Symbol\r\n    {\r\n        public static FractionalPart Produce(IEnumerable<Symbol> symbols)\r\n        {\r\n            \/\/ fractional-part = full-stop digit-sequence\r\n\r\n            if (!symbols.Any())\r\n                return null;\r\n            if (symbols.First() is FullStop)\r\n            {\r\n                IEnumerable<Symbol> s = null;\r\n                DigitSequence d = DigitSequence.Produce(symbols.Skip(1), out s);\r\n                if (d == null || s.Any())\r\n                    return null;\r\n                return new FractionalPart(new FullStop(), d);\r\n            }\r\n            return null;\r\n        }\r\n        public FractionalPart(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class DigitSequence : Symbol\r\n    {\r\n        public static DigitSequence Produce(IEnumerable<Symbol> symbols,\r\n            out IEnumerable<Symbol> symbolsToProcess)\r\n        {\r\n            \/\/ digit-sequence = 1*decimal-digit\r\n\r\n            IEnumerable<Symbol> digits = symbols.TakeWhile(s => s is DecimalDigit);\r\n            if (digits.Any())\r\n            {\r\n                symbolsToProcess = symbols.Skip(digits.Count());\r\n                return new DigitSequence(digits);\r\n            }\r\n            symbolsToProcess = null;\r\n            return null;\r\n        }\r\n        public DigitSequence(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class NegSign : Symbol\r\n    {\r\n        public static NegSign Produce(Symbol symbol)\r\n        {\r\n            \/\/ neg-sign = minus\r\n\r\n            if (symbol is Minus)\r\n                return new NegSign(symbol);\r\n            return null;\r\n        }\r\n        public NegSign(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class PrefixOperator : Symbol\r\n    {\r\n        public static PrefixOperator Produce(Symbol symbol)\r\n        {\r\n            \/\/ prefix-operator = plus \/ minus\r\n\r\n            if (symbol is Plus || symbol is Minus)\r\n                return new PrefixOperator(symbol);\r\n            return null;\r\n        }\r\n        public PrefixOperator(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class InfixOperator : Symbol\r\n    {\r\n        public static InfixOperator Produce(Symbol symbol)\r\n        {\r\n            \/\/ infix-operator = caret \/ asterisk \/ forward-slash \/ plus \/ minus\r\n\r\n            if (symbol is Plus || symbol is Minus || symbol is Asterisk || symbol is ForwardSlash\r\n                || symbol is Caret)\r\n                return new InfixOperator(symbol);\r\n            return null;\r\n        }\r\n        public InfixOperator(params Object[] symbols) : base(symbols) { }\r\n    }\r\n\r\n    public class DecimalDigit : Symbol\r\n    {\r\n        private string CharacterValue;\r\n        public override string ToString() { return CharacterValue; }\r\n        public DecimalDigit(char c) { CharacterValue = c.ToString(); }\r\n    }\r\n\r\n    public class WhiteSpace : Symbol\r\n    {\r\n        public override string ToString() { return \" \"; }\r\n        public WhiteSpace() { }\r\n    }\r\n\r\n    public class Plus : Symbol\r\n    {\r\n        public override string ToString() { return \"+\"; }\r\n        public Plus() { }\r\n    }\r\n\r\n    public class Minus : Symbol\r\n    {\r\n        public override string ToString() { return \"-\"; }\r\n        public Minus() { }\r\n    }\r\n\r\n    public class Asterisk : Symbol\r\n    {\r\n        public override string ToString() { return \"*\"; }\r\n        public Asterisk() { }\r\n    }\r\n\r\n    public class ForwardSlash : Symbol\r\n    {\r\n        public override string ToString() { return \"\/\"; }\r\n        public ForwardSlash() { }\r\n    }\r\n\r\n    public class Caret : Symbol\r\n    {\r\n        public override string ToString() { return \"^\"; }\r\n        public Caret() { }\r\n    }\r\n\r\n    public class FullStop : Symbol\r\n    {\r\n        public override string ToString() { return \".\"; }\r\n        public FullStop() { }\r\n    }\r\n\r\n    public class OpenParenthesis : Symbol\r\n    {\r\n        public override string ToString() { return \"(\"; }\r\n        public OpenParenthesis() { }\r\n    }\r\n\r\n    public class CloseParenthesis : Symbol\r\n    {\r\n        public override string ToString() { return \")\"; }\r\n        public CloseParenthesis() { }\r\n    }\r\n\r\n    public class SimpleFormulaParser\r\n    {\r\n        public static Symbol ParseFormula(string s)\r\n        {\r\n            IEnumerable<Symbol> symbols = s.Select(c =>\r\n            {\r\n                switch (c)\r\n                {\r\n                    case '0':\r\n                    case '1':\r\n                    case '2':\r\n                    case '3':\r\n                    case '4':\r\n                    case '5':\r\n                    case '6':\r\n                    case '7':\r\n                    case '8':\r\n                    case '9':\r\n                        return new DecimalDigit(c);\r\n                    case ' ':\r\n                        return new WhiteSpace();\r\n                    case '+':\r\n                        return new Plus();\r\n                    case '-':\r\n                        return new Minus();\r\n                    case '*':\r\n                        return new Asterisk();\r\n                    case '\/':\r\n                        return new ForwardSlash();\r\n                    case '^':\r\n                        return new Caret();\r\n                    case '.':\r\n                        return new FullStop();\r\n                    case '(':\r\n                        return new OpenParenthesis();\r\n                    case ')':\r\n                        return new CloseParenthesis();\r\n                    default:\r\n                        return (Symbol)null;\r\n                }\r\n            });\r\n#if false\r\n            if (symbols.Any())\r\n            {\r\n                Console.WriteLine(\"Terminal Symbols\");\r\n                Console.WriteLine(\"================\");\r\n                foreach (var terminal in symbols)\r\n                    Console.WriteLine(\"{0} >{1}<\", terminal.GetType().Name.ToString(),\r\n                        terminal.ToString());\r\n                Console.WriteLine();\r\n            }\r\n#endif\r\n            Formula formula = Formula.Produce(symbols);\r\n            if (formula == null)\r\n                throw new ParserException(\"Invalid formula\");\r\n            return formula;\r\n        }\r\n\r\n        public static void DumpSymbolRecursive(StringBuilder sb, Symbol symbol, int depth)\r\n        {\r\n            sb.Append(string.Format(\"{0}{1} >{2}<\",\r\n                \"\".PadRight(depth * 2),\r\n                symbol.GetType().Name.ToString(),\r\n                symbol.ToString())).Append(Environment.NewLine);\r\n            if (symbol.ConstituentSymbols != null)\r\n                foreach (var childSymbol in symbol.ConstituentSymbols)\r\n                    DumpSymbolRecursive(sb, childSymbol, depth + 1);\r\n        }\r\n    }\r\n\r\n    class Program\r\n    {\r\n        static void Main(string[] args)\r\n        {\r\n            string[] sampleValidFormulas = new[] {\r\n                \"1+((2+3)*4)^5\",\r\n                \"1+2-3*4\/5^6\",\r\n                \"(1+2)\/3\",\r\n                \"  (1+3)  \",\r\n                \"-123\",\r\n                \"1+2*(-3)\",\r\n                \"1+2*( - 3)\",\r\n                \"12.34\",\r\n                \".34\",\r\n                \"-123+456\",\r\n                \"  (  123 + 456 )  \",\r\n                \"-.34\",\r\n                \"-12.34\",\r\n                \"-(123+456)\",\r\n            };\r\n\r\n            string[] sampleInvalidFormulas = new[] {\r\n                \"-(123+)\",\r\n                \"-(*123)\",\r\n                \"*123\",\r\n                \"*123a\",\r\n                \"1.\",\r\n                \"--1\",\r\n            };\r\n\r\n            StringBuilder sb = new StringBuilder();\r\n            foreach (var formula in sampleValidFormulas)\r\n            {\r\n                Symbol f = SimpleFormulaParser.ParseFormula(formula);\r\n                SimpleFormulaParser.DumpSymbolRecursive(sb, f, 0);\r\n                sb.Append(\"==================================\" + Environment.NewLine);\r\n            }\r\n            foreach (var formula in sampleInvalidFormulas)\r\n            {\r\n                bool exceptionThrown = false;\r\n                try\r\n                {\r\n                    Symbol f = SimpleFormulaParser.ParseFormula(formula);\r\n                }\r\n                catch (ParserException e)\r\n                {\r\n                    exceptionThrown = true;\r\n                    sb.Append(String.Format(\"Parsing >{0}< Exception: {1}\", formula, e.Message) +\r\n                        Environment.NewLine);\r\n                }\r\n                if (!exceptionThrown)\r\n                    sb.Append(String.Format(\"Parsing >{0}< Should have thrown exception, but did not\",\r\n                        formula) + Environment.NewLine);\r\n            }\r\n            Console.WriteLine(sb.ToString());\r\n        }\r\n    }\r\n\r\n    public class ParserException : Exception\r\n    {\r\n        public ParserException(string message) : base(message) { }\r\n    }\r\n\r\n    public static class MyExtensions\r\n    {\r\n        public static IEnumerable<T> SkipLast<T>(this IEnumerable<T> source,\r\n            int count)\r\n        {\r\n            Queue<T> saveList = new Queue<T>();\r\n            int saved = 0;\r\n            foreach (T item in source)\r\n            {\r\n                if (saved < count)\r\n                {\r\n                    saveList.Enqueue(item);\r\n                    ++saved;\r\n                    continue;\r\n                }\r\n                saveList.Enqueue(item);\r\n                yield return saveList.Dequeue();\r\n            }\r\n            yield break;\r\n        }\r\n\r\n        public static string StringConcatenate(this IEnumerable<string> source)\r\n        {\r\n            StringBuilder sb = new StringBuilder();\r\n            foreach (string s in source)\r\n                sb.Append(s);\r\n            return sb.ToString();\r\n        }\r\n\r\n        public static string StringConcatenate<T>(\r\n            this IEnumerable<T> source,\r\n            Func<T, string> projectionFunc)\r\n        {\r\n            return source.Aggregate(new StringBuilder(),\r\n                (s, i) => s.Append(projectionFunc(i)),\r\n                s => s.ToString());\r\n        }\r\n\r\n        public static IEnumerable<TResult> Rollup<TSource, TResult>(\r\n            this IEnumerable<TSource> source,\r\n            TResult seed,\r\n            Func<TSource, TResult, TResult> projection)\r\n        {\r\n            TResult nextSeed = seed;\r\n            foreach (TSource src in source)\r\n            {\r\n                TResult projectedValue = projection(src, nextSeed);\r\n                nextSeed = projectedValue;\r\n                yield return projectedValue;\r\n            }\r\n        }\r\n    }\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Following is the code of this example: using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace SimpleParser { public abstract class Symbol { public List ConstituentSymbols { get; set; } public override string ToString() { string s = ConstituentSymbols.Select(ct => ct.ToString()).StringConcatenate(); return s; } public Symbol(params Object[] symbols) { List ls = new List(); foreach [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_bbp_topic_count":0,"_bbp_reply_count":0,"_bbp_total_topic_count":0,"_bbp_total_reply_count":0,"_bbp_voice_count":0,"_bbp_anonymous_reply_count":0,"_bbp_topic_count_hidden":0,"_bbp_reply_count_hidden":0,"_bbp_forum_subforum_count":0,"_s2mail":"","footnotes":""},"class_list":["post-1797","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/pages\/1797","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/comments?post=1797"}],"version-history":[{"count":2,"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/pages\/1797\/revisions"}],"predecessor-version":[{"id":1799,"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/pages\/1797\/revisions\/1799"}],"wp:attachment":[{"href":"https:\/\/www.ericwhite.com\/blog\/wp-json\/wp\/v2\/media?parent=1797"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}