コメントで述べられているように、私は再帰下降構文解析を好みます。これは、リンクされたウィキペディアの記事にあるCの例のC#での非常に迅速な部分的な適応です。
単純な再帰下降は、操車場法よりも読みやすく(再帰下降関数がEBNF非終端記号の定義とどのように密接に一致するかに注意してください)、より拡張性があります。以下は、括弧または「外部」機能を可能にするために簡単に適合させることができます。
より堅牢な実装は、実際にはシンボルクラスをサポートし、無効な文法をより適切に処理します。繰り返しになりますが、このような再帰下降構文解析の設定を追加するのは簡単です。入力のトークン化(読み取り:文字列の分割と数値のdoubleへの変換)は、読者の練習問題として残されています。
class RecDec {
St x; // ugly shared state, it's a quick example
public double eval (params object[] tokens) {
x = new St(tokens);
return expression();
}
double expression() {
double res = term();
string accepted;
while ((accepted = x.acceptOp(new [] {"+", "-"})) != null) {
res = accepted == "+"
? res + term()
: res - term();
}
return res;
}
double term() {
double res = factor();
string accepted;
while ((accepted = x.acceptOp(new [] {"*", "/"})) != null) {
res = accepted == "*"
? res * factor();
: res / factor();
}
return res;
}
double factor() {
var val = x.acceptVal();
if (val == null) {
throw new Exception(x.ToString());
}
return (double)val;
}
}
「状態」/トークンフィーダークラス:
class St {
IEnumerable<object> src;
public St (IEnumerable<object> src) {
this.src = src;
}
public object acceptVal () {
var first = src.FirstOrDefault();
if (first is double) {
src = src.Skip(1);
return first;
} else {
return null;
}
}
public string acceptOp (params string[] syms) {
var first = src.FirstOrDefault();
if (syms.Contains(first)) {
src = src.Skip(1);
return (string)first;
} else {
return null;
}
}
public override string ToString () {
return "[" + string.Join(",", src.ToArray()) + "]";
}
}
そして使用法(LINQPad拡張メソッドです。必要に応じて戻り値を使用Dump
してください):eval
void Main()
{
var rd = new RecDec();
// Use results - i.e. Remove Dump - if not on LINQPad
rd.eval(1d, "+", 2d).Dump();
rd.eval(2d, "*", 1d, "+", 2d, "*", 9d, "/", 4d).Dump();
}