[Blog Map] This blog is inactive. New blog: EricWhite.com/blog
In this post, I complete the recursive descent parser that will parse the simple grammar that I presented previously in this series. In this post, I’ll examine in detail the NospaceExpression class, which needs to implement operator precedence.
Note: I have to apologize for the delay in writing this post. First summer vacation took some time, and then I became involved in a project of writing two papers – one that summarizes SharePoint application development, and another that summarizes Office Client application development. I’ve been literally obsessing about every possible way that you as a developer can extend SharePoint Server 2010, and every way that you can extend the Office 2010 client applications. The SharePoint paper is nearing publication. The Office Client paper will be along in a bit. While obsessing, it has been difficult for me to mentally switch tracks back to this series. But here we go again…
This post is one in a series on using LINQ to write a recursive-descent parser for SpreadsheetML formulas. You can find the complete list of posts here.
The NospaceExpression Class
Here is the entire implementation of the NospaceExpression class. I explained all of the code that has a gray background in the previous post, Building a Simple Recursive Descent Parser (continued).
publicstaticNospaceExpression Produce(IEnumerable<Symbol> symbols)
{
// nospace-expression = open-parenthesis expression close-parenthesis
// / numerical-constant
// / prefix-operator expression
// / expression infix-operator expression
if (!symbols.Any())
returnnull;
if (symbols.First() isOpenParenthesis&& symbols.Last() isCloseParenthesis)
{
Expression e = Expression.Produce(symbols.Skip(1).SkipLast(1));
if (e != null)
returnnewNospaceExpression(newOpenParenthesis(), e, newCloseParenthesis());
}
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
var operatorList = z2
.Where(x => x.Depth == 0 &&
x.SymbolWithIndex.Index != 0 &&
InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)
.ToList();
if (operatorList.Any())
{
int minPrecedence = operatorList
.Select(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()]).Min();
var op = operatorList
.Last(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()] == minPrecedence);
if (op != null)
{
var expressionTokenList1 = symbols.TakeWhile(t => t != op.SymbolWithIndex.Symbol);
Expression e1 = Expression.Produce(expressionTokenList1);
if (e1 == null)
thrownewParserException("Invalid expression");
var expressionTokenList2 = symbols
.SkipWhile(t => t != op.SymbolWithIndex.Symbol).Skip(1);
Expression e2 = Expression.Produce(expressionTokenList2);
if (e2 == null)
thrownewParserException("Invalid expression");
InfixOperator io = newInfixOperator(op.SymbolWithIndex.Symbol);
returnnewNospaceExpression(e1, io, e2);
}
}
NumericalConstant n = NumericalConstant.Produce(symbols);
if (n != null)
returnnewNospaceExpression(n);
PrefixOperator p = PrefixOperator.Produce(symbols.FirstOrDefault());
if (p != null)
{
Expression e = Expression.Produce(symbols.Skip(1));
if (e != null)
returnnewNospaceExpression(p, e);
}
returnnull;
}
Let’s work through the new code. Let’s consider how the new code behaves when parsing the following formula:
"1+((2+3)*4)^5"
When determining if an expression has an infix operator, we specifically need to ignore operators that are in parentheses. Those operators will have their chance to be recognized when NospaceExpression.Produce sees that an expression starts with an open parenthesis and ends with a close parenthesis, as processed by this code:
if (!symbols.Any())
returnnull;
if (symbols.First() isOpenParenthesis&& symbols.Last() isCloseParenthesis)
{
Expression e = Expression.Produce(symbols.Skip(1).SkipLast(1));
if (e != null)
returnnewNospaceExpression(newOpenParenthesis(), e, newCloseParenthesis());
}
So we need to ignore operators that are inside parentheses. The easiest way to determine if an operator is inside of parentheses is to use the Rollup extension method, as follows:
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
This use of the Rollup extension methodreturns a new collection of integers that has the same number of elements in it as the source collection. The integer is the depth of parentheses at that point. In the following snippet, I add a bit of code to print the items in the collection, and then exit:
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
foreach (var item in z)
Console.WriteLine(item);
Environment.Exit(0);
When I run this example for the test formula "1+((2+3)*4)^5", I see:
0
0
1
2
2
2
2
1
1
1
0
0
0
When looking for an infix operator, I can ignore all operators where the depth is not zero.
Later on, I’m going to need the index of the symbol of the infix operator that we find. The next projection uses symbols as its source, and projects an anonymous type that includes the symbol and its index:
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
foreach (var item in symbolsWithIndex)
Console.WriteLine(item);
Environment.Exit(0);
When I run this for the test formula, I see:
{ Symbol = 1, Index = 0 }
{ Symbol = +, Index = 1 }
{ Symbol = (, Index = 2 }
{ Symbol = (, Index = 3 }
{ Symbol = 2, Index = 4 }
{ Symbol = +, Index = 5 }
{ Symbol = 3, Index = 6 }
{ Symbol = ), Index = 7 }
{ Symbol = *, Index = 8 }
{ Symbol = 4, Index = 9 }
{ Symbol = ), Index = 10 }
{ Symbol = ^, Index = 11 }
{ Symbol = 5, Index = 12 }
And next, use the Zip extension method to zip together the last two queries to create a list of items that contains:
· The symbol
· Its index
· Its parenthesis depth
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
foreach (var item in z2)
Console.WriteLine(item);
Environment.Exit(0);
This produces:
{ SymbolWithIndex = { Symbol = 1, Index = 0 }, Depth = 0 }
{ SymbolWithIndex = { Symbol = +, Index = 1 }, Depth = 0 }
{ SymbolWithIndex = { Symbol = (, Index = 2 }, Depth = 1 }
{ SymbolWithIndex = { Symbol = (, Index = 3 }, Depth = 2 }
{ SymbolWithIndex = { Symbol = 2, Index = 4 }, Depth = 2 }
{ SymbolWithIndex = { Symbol = +, Index = 5 }, Depth = 2 }
{ SymbolWithIndex = { Symbol = 3, Index = 6 }, Depth = 2 }
{ SymbolWithIndex = { Symbol = ), Index = 7 }, Depth = 1 }
{ SymbolWithIndex = { Symbol = *, Index = 8 }, Depth = 1 }
{ SymbolWithIndex = { Symbol = 4, Index = 9 }, Depth = 1 }
{ SymbolWithIndex = { Symbol = ), Index = 10 }, Depth = 0 }
{ SymbolWithIndex = { Symbol = ^, Index = 11 }, Depth = 0 }
{ SymbolWithIndex = { Symbol = 5, Index = 12 }, Depth = 0 }
Next, we can look for any infix operators where the Depth == 0. In addition, we filter out operators that are found in the very first position, because that will not be an infix operator, but instead a prefix operator.
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
var operatorList = z2
.Where(x => x.Depth == 0 &&
x.SymbolWithIndex.Index != 0 &&
InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)
.ToList();
foreach (var item in operatorList)
Console.WriteLine(item);
Environment.Exit(0);
This produces a list of the two possible operators that are part of an expression that uses an infix operator:
{ SymbolWithIndex = { Symbol = +, Index = 1 }, Depth = 0 }
{ SymbolWithIndex = { Symbol = ^, Index = 11 }, Depth = 0 }
Next, of the operators that could possibly be the infix operator for this expression, we find the minimum precedence of any operators. When implementing a top-down parser, we process operators with the least precedence before processing operators of higher precedence. When looking at the expression tree, operators with the least precedence are nearer to the root of the tree. Operators with the highest precedence will be closer to the leaves, so that they will be evaluated before the operators with lower precedence. If this isn’t clear, I think that it will be when you see the entire parse tree. In this particular case, we need to find the + operator, not the ^ operator, as + has lower precedence.
To enable processing of precedence of operators, I defined a small dictionary of operators and their precedence:
publicstaticDictionary<string, int> OperatorPrecedence = newDictionary<string, int>()
{
{ "^", 3 },
{ "*", 2 },
{ "/", 2 },
{ "+", 1 },
{ "-", 1 },
};
In the following listing, the highlighted line finds the minimum precedence of the operators that are in play.
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
var operatorList = z2
.Where(x => x.Depth == 0 &&
x.SymbolWithIndex.Index != 0 &&
InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)
.ToList();
if (operatorList.Any())
{
int minPrecedence = operatorList
.Select(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()]).Min();
var op = operatorList
.Last(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()] == minPrecedence);
if (op != null)
{
var expressionTokenList1 = symbols.TakeWhile(t => t != op.SymbolWithIndex.Symbol);
Expression e1 = Expression.Produce(expressionTokenList1);
if (e1 == null)
thrownewParserException("Invalid expression");
var expressionTokenList2 = symbols
.SkipWhile(t => t != op.SymbolWithIndex.Symbol).Skip(1);
Expression e2 = Expression.Produce(expressionTokenList2);
if (e2 == null)
thrownewParserException("Invalid expression");
InfixOperator io = newInfixOperator(op.SymbolWithIndex.Symbol);
returnnewNospaceExpression(e1, io, e2);
}
}
And finally, we find the infix operator:
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
var operatorList = z2
.Where(x => x.Depth == 0 &&
x.SymbolWithIndex.Index != 0 &&
InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)
.ToList();
if (operatorList.Any())
{
int minPrecedence = operatorList
.Select(o2 =>OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()]).Min();
var op = operatorList
.Last(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()] == minPrecedence);
if (op != null)
{
var expressionTokenList1 = symbols.TakeWhile(t => t != op.SymbolWithIndex.Symbol);
Expression e1 = Expression.Produce(expressionTokenList1);
if (e1 == null)
thrownewParserException("Invalid expression");
var expressionTokenList2 = symbols
.SkipWhile(t => t != op.SymbolWithIndex.Symbol).Skip(1);
Expression e2 = Expression.Produce(expressionTokenList2);
if (e2 == null)
thrownewParserException("Invalid expression");
InfixOperator io = newInfixOperator(op.SymbolWithIndex.Symbol);
returnnewNospaceExpression(e1, io, e2);
}
}
If we found an operator, then we can new-up (highlighted in green) an instance of NoSpaceExpression, passing the symbols before and after the infix operator to Expression.Produce (highlighted in cyan).
After parsing the sample expression, “1+((2+3)*4)^5”, the parser produces the following parse tree:
Formula >1+((2+3)*4)^5<
Expression >1+((2+3)*4)^5<
NospaceExpression >1+((2+3)*4)^5<
Expression >1<
NospaceExpression >1<
NumericalConstant >1<
SignificandPart >1<
WholeNumberPart >1<
DigitSequence >1<
DecimalDigit >1<
InfixOperator >+< <= the + operator has lower precedence, so is nearer to the root of the tree, and will be evaluated last
Plus >+<
Expression >((2+3)*4)^5<
NospaceExpression >((2+3)*4)^5<
Expression >((2+3)*4)<
NospaceExpression >((2+3)*4)<
OpenParenthesis >(<
Expression >(2+3)*4<
NospaceExpression >(2+3)*4<
Expression >(2+3)<
NospaceExpression >(2+3)<
OpenParenthesis >(<
Expression >2+3<
NospaceExpression >2+3<
Expression >2<
NospaceExpression >2<
NumericalConstant >2<
SignificandPart >2<
WholeNumberPart >2<
DigitSequence >2<
DecimalDigit >2<
InfixOperator >+<
Plus >+<
Expression >3<
NospaceExpression >3<
NumericalConstant >3<
SignificandPart >3<
WholeNumberPart >3<
DigitSequence >3<
DecimalDigit >3<
CloseParenthesis >)<
InfixOperator >*<
Asterisk >*<
Expression >4<
NospaceExpression >4<
NumericalConstant >4<
SignificandPart >4<
WholeNumberPart >4<
DigitSequence >4<
DecimalDigit >4<
CloseParenthesis >)<
InfixOperator >^< <= the ^ operator has higher precedence, so is closer to leaves of the tree, so will be evaluated first
Caret >^<
Expression >5<
NospaceExpression >5<
NumericalConstant >5<
SignificandPart >5<
WholeNumberPart >5<
DigitSequence >5<
DecimalDigit >5<
Following is the complete listing of the simple parser. While I haven’t discussed every class in the parser, I’ve introduced every technique that I’ve used in writing the Produce methods for each class. To run this example, you can simply copy/paste this example into Visual Studio and run it. There are no dependencies.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SimpleParser
{
publicabstractclassSymbol
{
publicList<Symbol> ConstituentSymbols { get; set; }
publicoverridestring ToString()
{
string s = ConstituentSymbols.Select(ct => ct.ToString()).StringConcatenate();
return s;
}
public Symbol(paramsObject[] symbols)
{
List<Symbol> ls = newList<Symbol>();
foreach (var item in symbols)
{
if (item isSymbol)
ls.Add((Symbol)item);
elseif (item isIEnumerable<Symbol>)
foreach (var item2 in (IEnumerable<Symbol>)item)
ls.Add(item2);
else
// If this error is thrown, the parser is coded incorrectly.
thrownewParserException("Internal error");
}
ConstituentSymbols = ls;
}
public Symbol() { }
}
publicclassFormula : Symbol
{
publicstaticFormula Produce(IEnumerable<Symbol> symbols)
{
// formula = expression
Expression e = Expression.Produce(symbols);
return e == null ? null : newFormula(e);
}
public Formula(paramsObject[] symbols) : base(symbols) { }
}
publicclassExpression : Symbol
{
publicstaticExpression Produce(IEnumerable<Symbol> symbols)
{
// expression = *whitespace nospace-expression *whitespace
int whiteSpaceBefore = symbols.TakeWhile(s => s isWhiteSpace).Count();
int whiteSpaceAfter = symbols.Reverse().TakeWhile(s => s isWhiteSpace).Count();
IEnumerable<Symbol> noSpaceSymbolList = symbols
.Skip(whiteSpaceBefore)
.SkipLast(whiteSpaceAfter)
.ToList();
NospaceExpression n = NospaceExpression.Produce(noSpaceSymbolList);
if (n != null)
returnnewExpression(
Enumerable.Repeat(newWhiteSpace(), whiteSpaceBefore),
n,
Enumerable.Repeat(newWhiteSpace(), whiteSpaceAfter));
returnnull;
}
public Expression (paramsObject[] symbols) : base(symbols) { }
}
publicclassNospaceExpression : Symbol
{
publicstaticDictionary<string, int> OperatorPrecedence = newDictionary<string, int>()
{
{ "^", 3 },
{ "*", 2 },
{ "/", 2 },
{ "+", 1 },
{ "-", 1 },
};
publicstaticNospaceExpression Produce(IEnumerable<Symbol> symbols)
{
// nospace-expression = open-parenthesis expression close-parenthesis
// / numerical-constant
// / prefix-operator expression
// / expression infix-operator expression
if (!symbols.Any())
returnnull;
if (symbols.First() isOpenParenthesis&& symbols.Last() isCloseParenthesis)
{
Expression e = Expression.Produce(symbols.Skip(1).SkipLast(1));
if (e != null)
returnnewNospaceExpression(newOpenParenthesis(), e, newCloseParenthesis());
}
// expression, infix-operator, expression
var z = symbols.Rollup(0, (t, d) =>
{
if (t isOpenParenthesis)
return d + 1;
if (t isCloseParenthesis)
return d - 1;
return d;
});
var symbolsWithIndex = symbols.Select((s, i) => new
{
Symbol = s,
Index = i,
});
var z2 = symbolsWithIndex.Zip(z, (v1, v2) => new
{
SymbolWithIndex = v1,
Depth = v2,
});
var operatorList = z2
.Where(x => x.Depth == 0 &&
x.SymbolWithIndex.Index != 0 &&
InfixOperator.Produce(x.SymbolWithIndex.Symbol) != null)
.ToList();
if (operatorList.Any())
{
int minPrecedence = operatorList
.Select(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()]).Min();
var op = operatorList
.Last(o2 => OperatorPrecedence[o2.SymbolWithIndex.Symbol.ToString()] == minPrecedence);
if (op != null)
{
var expressionTokenList1 = symbols.TakeWhile(t => t != op.SymbolWithIndex.Symbol);
Expression e1 = Expression.Produce(expressionTokenList1);
if (e1 == null)
thrownewParserException("Invalid expression");
var expressionTokenList2 = symbols
.SkipWhile(t => t != op.SymbolWithIndex.Symbol).Skip(1);
Expression e2 = Expression.Produce(expressionTokenList2);
if (e2 == null)
thrownewParserException("Invalid expression");
InfixOperator io = newInfixOperator(op.SymbolWithIndex.Symbol);
returnnewNospaceExpression(e1, io, e2);
}
}
NumericalConstant n = NumericalConstant.Produce(symbols);
if (n != null)
returnnewNospaceExpression(n);
PrefixOperator p = PrefixOperator.Produce(symbols.FirstOrDefault());
if (p != null)
{
Expression e = Expression.Produce(symbols.Skip(1));
if (e != null)
returnnewNospaceExpression(p, e);
}
returnnull;
}
public NospaceExpression(paramsObject[] symbols) : base(symbols) { }
}
publicclassNumericalConstant : Symbol
{
publicstaticNumericalConstant Produce(IEnumerable<Symbol> symbols)
{
// numerical-constant = [neg-sign] significand-part
SignificandPart s = SignificandPart.Produce(symbols);
if (s != null)
returnnewNumericalConstant(s);
NegSign n = NegSign.Produce(symbols.First());
if (n != null)
{
SignificandPart s2 = SignificandPart.Produce(symbols.Skip(1));
if (s2 != null)
returnnewNumericalConstant(n, s2);
}
returnnull;
}
public NumericalConstant(paramsObject[] symbols) : base(symbols) { }
}
publicclassSignificandPart : Symbol
{
publicstaticSignificandPart Produce(IEnumerable<Symbol> symbols)
{
// significand-part = whole-number-part [fractional-part] / fractional-part
FractionalPart f;
f = FractionalPart.Produce(symbols);
if (f != null)
returnnewSignificandPart(f);
IEnumerable<Symbol> s = null;
WholeNumberPart w = WholeNumberPart.Produce(symbols, out s);
if (w != null)
{
if (!s.Any())
returnnewSignificandPart(w);
f = FractionalPart.Produce(s);
if (f != null)
returnnewSignificandPart(w, f);
}
returnnull;
}
public SignificandPart(paramsObject[] symbols) : base(symbols) { }
}
publicclassWholeNumberPart : Symbol
{
publicstaticWholeNumberPart Produce(IEnumerable<Symbol> symbols,
outIEnumerable<Symbol> symbolsToProcess)
{
// whole-number-part = digit-sequence
IEnumerable<Symbol> s = null;
DigitSequence d = DigitSequence.Produce(symbols, out s);
if (d != null)
{
symbolsToProcess = s;
returnnewWholeNumberPart(d);
}
symbolsToProcess = null;
returnnull;
}
public WholeNumberPart(paramsObject[] symbols) : base(symbols) { }
}
publicclassFractionalPart : Symbol
{
publicstaticFractionalPart Produce(IEnumerable<Symbol> symbols)
{
// fractional-part = full-stop digit-sequence
if (!symbols.Any())
returnnull;
if (symbols.First() isFullStop)
{
IEnumerable<Symbol> s = null;
DigitSequence d = DigitSequence.Produce(symbols.Skip(1), out s);
if (d == null || s.Any())
returnnull;
returnnewFractionalPart(newFullStop(), d);
}
returnnull;
}
public FractionalPart(paramsObject[] symbols) : base(symbols) { }
}
publicclassDigitSequence : Symbol
{
publicstaticDigitSequence Produce(IEnumerable<Symbol> symbols,
outIEnumerable<Symbol> symbolsToProcess)
{
// digit-sequence = 1*decimal-digit
IEnumerable<Symbol> digits = symbols.TakeWhile(s => s isDecimalDigit);
if (digits.Any())
{
symbolsToProcess = symbols.Skip(digits.Count());
returnnewDigitSequence(digits);
}
symbolsToProcess = null;
returnnull;
}
public DigitSequence(paramsObject[] symbols) : base(symbols) { }
}
publicclassNegSign : Symbol
{
publicstaticNegSign Produce(Symbol symbol)
{
// neg-sign = minus
if (symbol isMinus)
returnnewNegSign(symbol);
returnnull;
}
public NegSign(paramsObject[] symbols) : base(symbols) { }
}
publicclassPrefixOperator : Symbol
{
publicstaticPrefixOperator Produce(Symbol symbol)
{
// prefix-operator = plus / minus
if (symbol isPlus || symbol isMinus)
returnnewPrefixOperator(symbol);
returnnull;
}
public PrefixOperator(paramsObject[] symbols) : base(symbols) { }
}
publicclassInfixOperator : Symbol
{
publicstaticInfixOperator Produce(Symbol symbol)
{
// infix-operator = caret / asterisk / forward-slash / plus / minus
if (symbol isPlus || symbol isMinus || symbol isAsterisk || symbol isForwardSlash
|| symbol isCaret)
returnnewInfixOperator(symbol);
returnnull;
}
public InfixOperator(paramsObject[] symbols) : base(symbols) { }
}
publicclassDecimalDigit : Symbol
{
privatestring CharacterValue;
publicoverridestring ToString() { return CharacterValue; }
public DecimalDigit(char c) { CharacterValue = c.ToString(); }
}
publicclassWhiteSpace : Symbol
{
publicoverridestring ToString() { return" "; }
public WhiteSpace() { }
}
publicclassPlus : Symbol
{
publicoverridestring ToString() { return"+"; }
public Plus() { }
}
publicclassMinus : Symbol
{
publicoverridestring ToString() { return"-"; }
public Minus() { }
}
publicclassAsterisk : Symbol
{
publicoverridestring ToString() { return"*"; }
public Asterisk() { }
}
publicclassForwardSlash : Symbol
{
publicoverridestring ToString() { return"/"; }
public ForwardSlash() { }
}
publicclassCaret : Symbol
{
publicoverridestring ToString() { return"^"; }
public Caret() { }
}
publicclassFullStop : Symbol
{
publicoverridestring ToString() { return"."; }
public FullStop() { }
}
publicclassOpenParenthesis : Symbol
{
publicoverridestring ToString() { return"("; }
public OpenParenthesis() { }
}
publicclassCloseParenthesis : Symbol
{
publicoverridestring ToString() { return")"; }
public CloseParenthesis() { }
}
publicclassSimpleFormulaParser
{
publicstaticSymbol ParseFormula(string s)
{
IEnumerable<Symbol> symbols = s.Select(c =>
{
switch (c)
{
case'0':
case'1':
case'2':
case'3':
case'4':
case'5':
case'6':
case'7':
case'8':
case'9':
returnnewDecimalDigit(c);
case' ':
returnnewWhiteSpace();
case'+':
returnnewPlus();
case'-':
returnnewMinus();
case'*':
returnnewAsterisk();
case'/':
returnnewForwardSlash();
case'^':
returnnewCaret();
case'.':
returnnewFullStop();
case'(':
returnnewOpenParenthesis();
case')':
returnnewCloseParenthesis();
default:
return (Symbol)null;
}
});
#if false
if (symbols.Any())
{
Console.WriteLine("Terminal Symbols");
Console.WriteLine("================");
foreach (var terminal in symbols)
Console.WriteLine("{0} >{1}<", terminal.GetType().Name.ToString(),
terminal.ToString());
Console.WriteLine();
}
#endif
Formula formula = Formula.Produce(symbols);
if (formula == null)
thrownewParserException("Invalid formula");
return formula;
}
publicstaticvoid DumpSymbolRecursive(StringBuilder sb, Symbol symbol, int depth)
{
sb.Append(string.Format("{0}{1} >{2}<",
"".PadRight(depth * 2),
symbol.GetType().Name.ToString(),
symbol.ToString())).Append(Environment.NewLine);
if (symbol.ConstituentSymbols != null)
foreach (var childSymbol in symbol.ConstituentSymbols)
DumpSymbolRecursive(sb, childSymbol, depth + 1);
}
}
classProgram
{
staticvoid Main(string[] args)
{
string[] sampleValidFormulas = new[] {
"1+((2+3)*4)^5",
"1+2-3*4/5^6",
"(1+2)/3",
" (1+3) ",
"-123",
"1+2*(-3)",
"1+2*( - 3)",
"12.34",
".34",
"-123+456",
" ( 123 + 456 ) ",
"-.34",
"-12.34",
"-(123+456)",
};
string[] sampleInvalidFormulas = new[] {
"-(123+)",
"-(*123)",
"*123",
"*123a",
"1.",
"--1",
};
StringBuilder sb = newStringBuilder();
foreach (var formula in sampleValidFormulas)
{
Symbol f = SimpleFormulaParser.ParseFormula(formula);
SimpleFormulaParser.DumpSymbolRecursive(sb, f, 0);
sb.Append("==================================" + Environment.NewLine);
}
foreach (var formula in sampleInvalidFormulas)
{
bool exceptionThrown = false;
try
{
Symbol f = SimpleFormulaParser.ParseFormula(formula);
}
catch (ParserException e)
{
exceptionThrown = true;
sb.Append(String.Format("Parsing >{0}< Exception: {1}", formula, e.Message) +
Environment.NewLine);
}
if (!exceptionThrown)
sb.Append(String.Format("Parsing >{0}< Should have thrown exception, but did not",
formula) + Environment.NewLine);
}
Console.WriteLine(sb.ToString());
}
}
publicclassParserException : Exception
{
public ParserException(string message) : base(message) { }
}
publicstaticclassMyExtensions
{
publicstaticIEnumerable<T> SkipLast<T>(thisIEnumerable<T> source,
int count)
{
Queue<T> saveList = newQueue<T>();
int saved = 0;
foreach (T item in source)
{
if (saved < count)
{
saveList.Enqueue(item);
++saved;
continue;
}
saveList.Enqueue(item);
yieldreturn saveList.Dequeue();
}
yieldbreak;
}
publicstaticstring StringConcatenate(thisIEnumerable<string> source)
{
StringBuilder sb = newStringBuilder();
foreach (string s in source)
sb.Append(s);
return sb.ToString();
}
publicstaticstring StringConcatenate<T>(
thisIEnumerable<T> source,
Func<T, string> projectionFunc)
{
return source.Aggregate(newStringBuilder(),
(s, i) => s.Append(projectionFunc(i)),
s => s.ToString());
}
publicstaticIEnumerable<TResult> Rollup<TSource, TResult>(
thisIEnumerable<TSource> source,
TResult seed,
Func<TSource, TResult, TResult> projection)
{
TResult nextSeed = seed;
foreach (TSource src in source)
{
TResult projectedValue = projection(src, nextSeed);
nextSeed = projectedValue;
yieldreturn projectedValue;
}
}
}
}