The following code was written by me.
It is used for evaluating constant expressions.
I think it is good. Any criticism?
namespace Churunmin.HelloWorld
{
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Reflection;
public class Calculator
{
[AttributeUsage(AttributeTargets.Field)]
public class TokenAttribute: Attribute
{
private string name;
public TokenAttribute(string name)
{
this.name = name;
}
public string Name
{
get
{
return this.name;
}
}
}
[AttributeUsage(AttributeTargets.Field)]
public class KeywordAttribute: TokenAttribute
{
public KeywordAttribute(string name): base(name)
{
}
}
public class Tokener
{
[Token("<none>")]
public const int TK_NONE = -1;
[Token("<number>")]
public const int TK_NUMBER = -2;
[Keyword("pi")]
public const int TK_PI = -3;
[Keyword("e")]
public const int TK_E = -4;
[Keyword("sin")]
public const int TK_SIN = -5;
[Keyword("cos")]
public const int TK_COS = -6;
[Keyword("tan")]
public const int TK_TAN = -7;
[Keyword("lg")]
public const int TK_LG = -8;
[Keyword("ln")]
public const int TK_LN = -9;
public const string SIMPLE_TOKENS = "+-*/%^()";
private static readonly Dictionary<int, string> tokenDictionary =
new Dictionary<int, string>();
public static string TokenToString(int token)
{
if (token < 0)
{
return tokenDictionary[token];
}
else if (SIMPLE_TOKENS.IndexOf((char)token) >= 0)
{
return ((char)token).ToString();
}
throw new Exception("Attempt to convert an invalid token to string:
" + token);
}
private static readonly Dictionary<string, int> keywordDictionary =
new Dictionary<string, int>();
public static int TokenFromKeyword(string keyword)
{
if (keywordDictionary.ContainsKey(keyword))
{
return keywordDictionary[keyword];
}
throw new Exception("Attempt to convert an invalid keyword to
token: " + keyword);
}
public static void Test()
{
Console.WriteLine("keywordDictionary:");
foreach (KeyValuePair<string, int> kv in keywordDictionary)
{
Console.WriteLine("{0} --> {1}", kv.Key, kv.Value);
}
Console.WriteLine("tokenDictionary:");
foreach (KeyValuePair<int, string> kv in tokenDictionary)
{
Console.WriteLine("{0} --> {1}", kv.Key, kv.Value);
}
}
static Tokener()
{
FieldInfo[] fields = typeof(Tokener).GetFields(BindingFlags.Static
| BindingFlags.DeclaredOnly | BindingFlags.Public |
BindingFlags.NonPublic);
foreach (FieldInfo field in fields)
{
TokenAttribute attr = Attribute.GetCustomAttribute(field,
typeof(TokenAttribute), false) as TokenAttribute;
if (attr != null)
{
int v = (int)field.GetRawConstantValue();
tokenDictionary[v] = attr.Name;
if (attr.GetType() == typeof(KeywordAttribute))
{
keywordDictionary[attr.Name] = v;
}
}
}
}
}
public class Scanner: Tokener
{
public double DoubleData;
public int Token;
private StringBuilder cache = new StringBuilder(256);
private string data;
private int dataIndex;
private char c;
private void Read()
{
if (dataIndex < data.Length)
{
this.c = data[dataIndex++];
}
else
{
this.c = '\0';
}
}
private bool Check(string s, char c)
{
return s.IndexOf(c) >= 0;
}
private bool Check(string s)
{
return Check(s, this.c);
}
private void Save(char c)
{
this.cache.Append(c);
}
private void Save()
{
Save(this.c);
}
private void SaveRead()
{
Save();
Read();
}
public Scanner(string data)
{
this.data = data;
Read();
Next();
}
private bool IsDigit(char c)
{
return c >= '0' && c <='9';
}
private bool IsLetter(char c)
{
return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z';
}
private bool ReadNumber()
{
this.cache.Length = 0;
if (!IsDigit(this.c))
{
return false;
}
SaveRead();
while (IsDigit(this.c))
{
SaveRead();
}
if (this.c == '.')
{
SaveRead();
if (!IsDigit(this.c))
{
throw new Exception("The radix point must be followed by
digit.");
}
SaveRead();
while (IsDigit(this.c))
{
SaveRead();
}
}
this.Token = TK_NUMBER;
this.DoubleData = double.Parse(this.cache.ToString());
return true;
}
private bool ReadName()
{
this.cache.Length = 0;
if (!(IsLetter(this.c) || this.c == '_'))
{
return false;
}
SaveRead();
while (IsLetter(this.c) || IsDigit(this.c) || this.c == '_')
{
SaveRead();
}
this.Token = TokenFromKeyword(this.cache.ToString());
return true;
}
private void SkipBlank()
{
while (this.c == ' ')
{
Read();
}
}
public void Next()
{
SkipBlank();
if (this.c == '\0')
{
this.Token = TK_NONE;
return;
}
if (ReadNumber()) return;
if (ReadName()) return;
if (SIMPLE_TOKENS.IndexOf(this.c) >= 0)
{
this.Token = this.c; // +, -, *, /, etc.
Read();
return;
}
throw new Exception("Encounter invalid character when scanning for
token: '" + this.c + "'");
}
public static new void Test()
{
string s = "3*6+2*pi+e^2%66-9000";
Console.WriteLine("Scan \"" + s + "\" for tokens:");
Scanner scanner = new Scanner(s);
while (scanner.Token != Scanner.TK_NONE)
{
Console.WriteLine(Scanner.TokenToString(scanner.Token));
scanner.Next();
}
}
}
public class Parser: Scanner
{
private static readonly Dictionary<int, int> leftPriority = new
Dictionary<int, int>();
private static readonly Dictionary<int, int> rightPriority = new
Dictionary<int, int>();
static Parser()
{
leftPriority.Add('+', 6); rightPriority.Add('+', 6);
leftPriority.Add('-', 6); rightPriority.Add('-', 6);
leftPriority.Add('*', 7); rightPriority.Add('*', 7);
leftPriority.Add('/', 7); rightPriority.Add('/', 7);
leftPriority.Add('%', 7); rightPriority.Add('%', 7);
leftPriority.Add('^', 10); rightPriority.Add('^', 9); // right
associative
}
public Parser(string data): base(data)
{
}
public int ParseExpression(out double v, int limit)
{
ParseSimpleExpression(out v); // the expression must start with a
simple expression
int op = this.Token;
// expand while operators have priorities higher than 'limit'
while (leftPriority.ContainsKey(op) && leftPriority[op] > limit)
{
Next();
double rhs; // right hand side
// read expression with higher priority
int nextop = ParseExpression(out rhs, rightPriority[op]);
switch (op)
{
case '+':
v = v + rhs;
break;
case '-':
v = v - rhs;
break;
case '*':
v = v * rhs;
break;
case '/':
v = v / rhs;
break;
case '%':
v = v % rhs;
break;
case '^':
v = Math.Pow(v, rhs);
break;
default:
throw new Exception("Expected an operator token, but was: '" +
TokenToString(op) + "'");
}
op = nextop;
}
return op;
}
private void ParseSimpleExpression(out double v)
{
if (this.Token == TK_NUMBER)
{
v = this.DoubleData;
Next();
return;
}
if (this.Token == TK_PI)
{
v = Math.PI;
Next();
return;
}
if (this.Token == TK_E)
{
v = Math.E;
Next();
return;
}
if (this.Token == TK_SIN)
{
Next();
ExpectToken('(');
ParseExpression(out v, 0);
ExpectToken(')');
v = Math.Sin(v);
return;
}
if (this.Token == TK_COS)
{
Next();
ExpectToken('(');
ParseExpression(out v, 0);
ExpectToken(')');
v = Math.Cos(v);
return;
}
if (this.Token == TK_TAN)
{
Next();
ExpectToken('(');
ParseExpression(out v, 0);
ExpectToken(')');
v = Math.Tan(v);
return;
}
if (this.Token == TK_LG)
{
Next();
ExpectToken('(');
ParseExpression(out v, 0);
ExpectToken(')');
v = Math.Log10(v);
return;
}
if (this.Token == TK_LN)
{
Next();
ExpectToken('(');
ParseExpression(out v, 0);
ExpectToken(')');
v = Math.Log(v);
return;
}
throw new Exception("Unexpected token: '" +
TokenToString(this.Token) + "'");
}
private void ExpectToken(int t)
{
if (this.Token != t) throw new Exception(String.Format("Expected
token: '{0]', but was '{1}'", TokenToString(t),
TokenToString(this.Token)));
Next();
}
public static new void Test()
{
double v;
string s = "3*6 + 2 - 10 % 99 + lg(8)/lg(2)";
new Parser(s).ParseExpression(out v, 0);
Console.WriteLine("{0} = {1}", s, v);
s = "ln(e^2)";
new Parser(s).ParseExpression(out v, 0);
Console.WriteLine("{0} = {1}", s, v);
s = "sin(pi/2)";
new Parser(s).ParseExpression(out v, 0);
Console.WriteLine("{0} = {1}", s, v);
foreach(EncodingInfo ei in Encoding.GetEncodings())
{
Encoding e = ei.GetEncoding();
Console.Write( "{0,-6} {1,-25} {2,-40}", ei.CodePage, ei.Name,
ei.DisplayName);
Console.WriteLine();
}
}
}
public static double Evaluate(string s)
{
double v;
new Parser(s).ParseExpression(out v, 0);
return v;
}
}
}