C# 逆ポーランド記法 四則計算機 ソースコード ― 2022年08月12日 18:05
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public struct nameSet // 実行用スタックの要素
{ public string name; // 変数名,評価済みのとき"*VALUE*"
public double value; // 評価済み値
public nameSet(string n, double v) { name = n; value = v;}
}
public struct opeSet // 逆ポーランド変換用スタックの要素
{ public string name; // 演算子
public int value; // 優先順位
public opeSet(string n, int v) { name = n; value = v;}
}
public struct token // 単一トークンの形式
{ public string atr; // トークンの種類"NUM","OPE","VAR"
public string str; // トークンの文字列
public token(string a, string s) { atr = a.ToUpper(); str=s;}
}
public nameSet[] STab=new nameSet[500]; // 記号表
public int numStab; // 記号表のサイズ
public token[] Polish = new token[500]; // 逆ポーランド記法の出力先
public int numPolish; // 逆ポーランド記法のサイズ
public nameSet[] Stack = new nameSet[500]; // 実行用スタック
public int stackP; // スタックポインタ
public opeSet[] StackOpe = new opeSet[500];// 逆ポーランド変換用スタック
public string Alltext; // 入力文字列
public string Delmiter = "()+-*/="; // 区切り記号(演算子)
public string Number = "0123456789"; // 数字
public token[] LAData = new token[500]; // 語彙解析結果
public int numLA; // 語彙解析結果のサイズ
public Form1()
{ InitializeComponent();
}
private void push(nameSet X) // 実行用スタック Push
{ if (stackP >= 500) MessageBox.Show("Stack Over");
else
{ Stack[stackP] = X;
if(checkBox1.Checked)MessageBox.Show("push(" + stackP.ToString() + ")" +
X.name + " Value=" + X.value.ToString());
stackP++;
}
}
private nameSet pop() // 実行用スタック Pop
{ if (stackP <= 0)
{ MessageBox.Show("Stack Empty"); return new nameSet("*Error*", 0);}
else
{ stackP--;nameSet X = Stack[stackP];
if (checkBox1.Checked) MessageBox.Show("pop(" + stackP.ToString() + ")" +
X.name +" Value=" + X.value.ToString());
return X;
}
}
private void pushOpe(opeSet X) // 変換用 Push
{ if (stackP >= 500) MessageBox.Show("Stack Over");
else
{ StackOpe[stackP] = X;
if (checkBox1.Checked) MessageBox.Show("push(" + stackP.ToString() + ")" +
X.name +" Priority=" + X.value.ToString());
stackP++;
}
}
private opeSet popOpe() // 変換用 Pop
{ if (stackP <= 0)
{ MessageBox.Show("Stack Empty"); return new opeSet("*Error*", 0);}
else
{ stackP--; opeSet X=StackOpe[stackP];
if (checkBox1.Checked) MessageBox.Show("pop(" + stackP.ToString() + ")" +
X.name +" Priority=" + X.value.ToString());
return X;
}
}
private nameSet sepValue(string X)// TextBoxの変数名/値分離
{ string[] S = new string[2]; S=X.Split('=');
return new nameSet(S[0].Trim(' '),double.Parse(S[1]));
}
private token setPolish(string X) // TextBoxのトークン設定
{ string[] S = new string[2]; S = X.Split(':');
return new token(S[0].Trim(' '), S[1].Trim(' '));
}
private void setValue(string Name, double V)//変数値の設定
{ STab[numStab].name=Name;
int i=0; while (STab[i].name!=Name) i++;
STab[i].value = V; if (i >= numStab) numStab++;
dspName();
}
private double getValue(string Name) // 与えられた変数の値
{ for (int i = 0; i < STab.Length; i++)
if (STab[i].name == Name) return STab[i].value;
return 0;
}
private void outStab(string s) // "変数名=値"のデータを記号表に登録
{ STab[numStab] = sepValue(s); numStab++;
}
private void outSPolish(string s) // "種類:記号"のデータをトークンとして設定
{ Polish[numPolish] = setPolish(s); numPolish++;
}
private void initialize() // 実行用初期設定
{ numStab = 0; // 名前表
for (int i = 0; i < textBoxSymbol.Lines.Length; i++)
if (textBoxSymbol.Lines[i].Length >= 2) outStab(textBoxSymbol.Lines[i]);
numPolish = 0; // 逆ポーランド記法
for (int i = 0; i < textBoxPolish.Lines.Length; i++)
if (textBoxPolish.Lines[i].Length >= 2) outSPolish(textBoxPolish.Lines[i]);
stackP = 0; // スタックポインタ
}
private void outLAData(string s) // "種類:記号"のデータを語彙解析結果として設定
{ LAData[numLA] = setPolish(s); numLA++;
}
private void initializeSA() // 変換の初期設定
{ numLA = 0; // 語彙解析結果
for (int i = 0; i < textBoxLA.Lines.Length; i++)
if (textBoxLA.Lines[i].Length >= 2) outLAData(textBoxLA.Lines[i]);
stackP = 0;
}
private double evalDT(nameSet X) // 値を求める
{ if (X.name == "*VALUE*") return X.value;
return getValue(X.name);
}
private void exePolish() // 逆ポーランド記法の実行
{ initialize(); //初期設定
for (int i = 0; i < numPolish; i++) //【注】変数Pushの場合、値が必要になった時点で評価。
{ if(Polish[i].atr=="VAR") push(new nameSet(Polish[i].str, 0));//変数Push(ここでは値はダミー)
else if (Polish[i].atr == "OPE")
{ nameSet A1, A2;
if (checkBox1.Checked) MessageBox.Show("演算実行 " + Polish[i].str); //演算実行
A2 = pop(); double V2 = evalDT(A2); // 演算対象Popと評価
if (Polish[i].str == "$+") { } // 単項演算子実行 $+
else if (Polish[i].str == "$-") V2 = -V2; // $-
else
{ A1 = pop(); // 演算対象Pop
if (Polish[i].str == "=") // 代入処理
{ if (A1.name == "*VALUE*") MessageBox.Show("代入に矛盾があります");
else setValue(A1.name, V2);
}
else // 以下2項演算
{ double V1 = evalDT(A1);
if (Polish[i].str == "+") V2 = V1 + V2;
else if (Polish[i].str == "-") V2 = V1 - V2;
else if (Polish[i].str == "*") V2 = V1 * V2;
else if (Polish[i].str == "/") V2 = V1 / V2;
else MessageBox.Show("演算子の例外");
}
}
push(new nameSet("*VALUE*", V2)); // 演算結果のPush
}
else push(new nameSet("*VALUE*", double.Parse(Polish[i].str)));// 数値のPush
}
}
private void dspName() // 記号表をテキストボックスに表示
{ string S = "";
for(int i=0;i<numStab;i++)S=S+STab[i].name+"="+STab[i].value.ToString()+"\r\n";
textBoxSymbol.Text=S;
}
private void button1_Click(object sender, EventArgs e) { exePolish();}
private void startLA() // 語彙解析開始
{ Alltext = textBoxInput.Text.TrimStart();
}
private token numberProc(string ch)// 数字の設定
{
Boolean dot = false;
if (ch == ".") dot = true;
string s = ch;
if (Alltext != "")
{ ch = Alltext.Substring(0, 1);
while (ch=="." || Number.IndexOf(ch) > -1) // 数字のあいだ以下を繰り返す
{ if (ch == ".")
{ if (dot) MessageBox.Show(".の位置の誤り");
dot = true;
}
s += ch; Alltext = Alltext.Substring(1, Alltext.Length - 1);
if (Alltext == "") break;
ch = Alltext.Substring(0, 1);
}
}
return (new token("Num", s));
}
private token nameProc(string ch)// 変数名の設定
{ string s = ch;
if (Alltext != "")
{ ch = Alltext.Substring(0, 1);
while (ch != " " && Delmiter.IndexOf(ch) <= -1)// 空白、区切り記号のいずれ
{ s += ch; // でもないあいだ繰り返す
Alltext = Alltext.Substring(1, Alltext.Length - 1);
if (Alltext == "") break;
ch = Alltext.Substring(0, 1);
}
}
return (new token("Var", s));
}
private token getLAToken() // 語彙解析
{ Alltext=Alltext.TrimStart();
if (Alltext == "") return (new token("$EOF$", "$EOF$"));
string ch = Alltext.Substring(0, 1);
Alltext = Alltext.Substring(1, Alltext.Length - 1);
if (Delmiter.IndexOf(ch) > -1) return (new token("Ope", ch));
else if (ch=="." || Number.IndexOf(ch) > -1) return numberProc(ch);
else return nameProc(ch);
}
private void dspPolish()//逆ポーランド記法をテキストボックスに表示
{ string S = "";
for (int i = 0; i < numPolish; i++)
S += Polish[i].atr + ":" + Polish[i].str + "\r\n";
textBoxPolish.Text = S;
}
private void button2_Click(object sender, EventArgs e)// 連続語彙解析
{ startLA(); string S = ""; token TK = getLAToken();
while (TK.atr != "$EOF$")
{ string X = TK.atr + ":" + TK.str; S = S + X + "\r\n";
if (checkBox1.Checked) MessageBox.Show(X);
TK = getLAToken();
}
textBoxLA.Text = S;
}
private int priority(string s) // 演算子による優先順位の設定
{ if (s == "=") return 10;
else if (s == "+" || s == "-") return 20;
else if (s == "*" || s == "/") return 30;
else if (s == ")") return -1;
else return 0;
}
private void singleOpe(string s) // 単項演算子と左カッコの処理
{ if (s == "+" || s == "-") // 2項演算子の+,-と区別するために
pushOpe(new opeSet("$" + s, 200));// それぞれ$を先頭に付ける。
else if (s == "(") // 左括弧の場合Push
pushOpe(new opeSet("(", 0));
else MessageBox.Show("演算子エラー");
}
private int operandProc(token S) // 演算対象の場合、そのまま変換結果に移動
{ Polish[numPolish] = S;
numPolish++;
return 1; // 次のモードを演算子モードにする。
}
private void rightParPop()// 右カッコの処理
{ if (stackP > 0) // 左カッコになるまでPopして変換結果に移動
{ opeSet X = popOpe();
while (X.name != "(" && stackP >= 0)
{ Polish[numPolish] = new token("OPE", X.name); numPolish++; X = popOpe();
}
}
else MessageBox.Show("左カッコが足りません");
}
private int operationProc(string s, int Opt)// 演算子処理
{ if (stackP > 0) // スタック上に優先順位が高いか、
{ while (stackP>0) // 同じ演算子があればPopして変換結果に移動
{ if (StackOpe[stackP-1].value < Opt) break;
opeSet X = popOpe(); Polish[numPolish] = new token("OPE", X.name); numPolish++;
}
}
pushOpe(new opeSet(s, Opt));// 演算子をPush
return 0; // 次のモードを演算対象モードとする。
}
private void endPop()// スタック上に残った演算子を変換結果に移動。
{ while (stackP > 0)
{ opeSet X = popOpe();
Polish[numPolish] = new token("OPE", X.name); numPolish++;
}
}
private void SA() // 逆ポーランドへの変換
{ initializeSA(); int SMode = 0; numPolish = 0;
for (int i = 0; i < numLA; i++)
{ string ATR = LAData[i].atr.ToUpper();
if (SMode == 0) // 演算対象モード(演算子は、+,-(単項演算子)か左括弧)
{ if (ATR == "OPE") singleOpe(LAData[i].str);// 単項演算子と左括弧の処理
else SMode = operandProc(LAData[i]); //演算対象処理
}
else // 演算子モード(2項演算子か右括弧だけのモード)
{ if (ATR == "OPE")
{ int Opt = priority(LAData[i].str); // 演算子の優先順位設定
if (Opt == 0) MessageBox.Show("演算子エラー"); // (0:エラー,-1:右括弧)
else if (Opt < 0) rightParPop(); // 右括弧の処理
else SMode = operationProc(LAData[i].str, Opt);// 2項演算子処理
}
else MessageBox.Show("演算子がありません");
}
}
endPop(); dspPolish(); // スタックに残った演算子を変換結果に移して結果表示
}
private void button3_Click(object sender, EventArgs e)
{ SA();
}
}
}