最終更新:2019/5/30
現在ver.1.2.2
プログラム言語をつくってみたいが、なかなかハードルが高い・・・
最初の一歩として関数電卓(のようなもの)をつくってみよう!
とういわけで作ってみました
ソースコードに関しては、Calculatorクラス, Mainメソッドまで飛ばしますようお願いします
内容
今回の電卓は、 逆ポーランド記法で書く
スペースで区切って入力する
起動時の画面はこんな感じ
(演算子,命令の説明は今後変更予定)
5 2 + 3 4 * 5 - +
は
(5 + 2) + ((3 * 4) - 5)
の意味である
また、変数機能が使用でき、
xvar:変数名
で変数を定義する
(初期値は0.0)
そして、
xin:変数名
で一番後ろの数値を変数に入れる
(変数が変数が定義されていない場合は、自動的に定義される)
xout:変数名
もしくは
変数名
で値を数値Stackに入れる
なお、
xcout
で現在登録されている変数の一覧を確認することができる
処理
Dictionary<string, 演算>に、演算子をkey、演算をvalueに登録する
(以下、演算Dictionary)
またStack<double>に、数値を登録する
(以下、数値Stack)
まず、入力されたstringが演算Dictionaryに登録しているかどうか調べる
登録してあるなら、演算Dictionaryの演算が求めている数の数字を数値Stackから演算に与える
そして、その返り値を数値Stackに追加する
登録してない場合、doubleに変換できるなら数値Stackに追加する
それを最後まで繰り返す
今後
- 中置記法でも書けるようにする
Calculatorクラス
以下が「計算機」のソースコード
using System;
using System.Collections.Generic;
using System.Linq;
namespace Calculator
{
// 計算機
class Calculator
{
// バージョン情報
static public readonly Version version = new Version(1, 2, 2);
// 製作者名
static public readonly string maker = "HitsujiRere";
// 接頭辞と初期値のディクショナリ
static public Dictionary<char, bool> prefixs = new Dictionary<char, bool>()
{
['&'] = true,
['@'] = false,
};
// 演算子と計算のディクショナリ
static public Dictionary<string, Func<double, string[], double>> operatorsOf1 =
new Dictionary<string, Func<double, string[], double>>()
{
["=%"] = (a, _) => a * 0.01,
["-%"] = (a, _) => a * 100.0,
["++"] = (a, _) => a + 1.0,
["--"] = (a, _) => a - 1.0,
["*-"] = (a, _) => a * -1.0,
["sq"] = (a, _) => a * a,
["sqrt"] = (a, _) => Math.Sqrt(a),
["bx"] = (a, _) => a * a * a,
["bxrt"] = (a, _) => Math.Pow(a, 1.0 / 3.0),
};
static public Dictionary<string, Func<double, double, string[], double>> operatorsOf2 =
new Dictionary<string, Func<double, double, string[], double>>()
{
["+"] = (a, b, _) => a + b,
["-"] = (a, b, _) => a - b,
["*"] = (a, b, _) => a * b,
["/"] = (a, b, _) => a / b,
["%"] = (a, b, _) => a % b,
["mod"] = (a, b, _) => a % b,
["div"] = (a, b, _) => (a - (a % b)) / b,
["^"] = (a, b, _) => Math.Pow(a, b),
};
static public Dictionary<string, Func<double[], string[], double>> operatorsAll =
new Dictionary<string, Func<double[], string[], double>>()
{
["cnt"] = (a, _) => a.Count(),
["sum"] = (a, _) => a.Sum(),
["ave"] = (a, _) => a.Average(),
};
// 命令と計算のディクショナリ
static public Dictionary<string, Action<Calculator, string[]>> instructions =
new Dictionary<string, Action<Calculator, string[]>>()
{
["sort"] = (calc, _) =>
{
calc.numStack = new Stack<double>(calc.numStack.OrderBy(x => x));
},
["rev"] = (calc, _) =>
{
calc.numStack = new Stack<double>(calc.numStack);
},
["clr"] = (calc, _) => { calc.numStack.Clear(); },
["help"] = (calc, _) =>
{
Console.WriteLine("特殊な接頭辞");
Prints(prefixs.Select(x => x.Key.ToString()));
Console.WriteLine("1項演算子");
Prints(operatorsOf1.Select(x => x.Key));
Console.WriteLine("2項演算子");
Prints(operatorsOf2.Select(x => x.Key));
Console.WriteLine("全てへの演算子");
Prints(operatorsAll.Select(x => x.Key));
Console.WriteLine("命令");
Prints(instructions.Select(x => x.Key));
void Prints(IEnumerable<string> a)
{
foreach (var item in a)
{
Console.WriteLine("{0,6} = {1}", item,
comments.ContainsKey(item) ? comments[item] : "not found");
}
Console.WriteLine();
}
},
["xvar"] = (calc, names) =>
{
foreach (var name in names)
{
if (!calc.variables.ContainsKey(name))
{
calc.variables[name] = 0.0;
}
}
},
["xin"] = (calc, names) =>
{
foreach (var name in names)
{
if (calc.numStack.Count >= 1)
{
calc.variables[name] = calc.numStack.Pop();
}
}
},
["xout"] = (calc, names) =>
{
foreach (var name in names)
{
if (calc.variables.ContainsKey(name))
{
calc.numStack.Push(calc.variables[name]);
}
}
},
["xcout"] = (calc, _) =>
{
foreach (var item in calc.variables)
{
Console.WriteLine("{0,6} = {1}", item.Key, item.Value);
}
Console.WriteLine();
},
};
// 接頭辞,演算子,命令についての説明
static public Dictionary<string, string> comments =
new Dictionary<string, string>()
{
["&"] = "計算のために取得した数字を削除しない",
["@"] = "全ての数に対して演算を行う(実装中)",
["=%"] = "前の数値を、パーセント数値にする(*100する)",
["-%"] = "前のパーセント数値を、数値にする(*0.01する)",
["++"] = "前の数字を、+1する",
["--"] = "前の数字を、-1する",
["*-"] = "前の数字を、*(-1)する",
["sq"] = "前の数字を、平方する",
["sqrt"] = "前の数字を、平方根にする",
["bx"] = "前の数字を、立方する",
["bxrt"] = "前の数字を、立方根にする",
["+"] = "前の数字2つを、足し算する",
["-"] = "前の数字2つを、引き算する",
["*"] = "前の数字2つを、掛け算する",
["/"] = "前の数字2つを、割り算する",
["%"] = "前の数字2つを、割り算し、その余りをとる",
["mod"] = "前の数字2つを、割り算し、その余りをとる",
["div"] = "前の数字2つを、割り算し、その商をとる",
["^"] = "前の数字2つを、累乗する",
["cnt"] = "前の数字全てを、計数する",
["sum"] = "前の数字全てを、合計する",
["ave"] = "前の数字全てを、平均する",
["sort"] = "数値スタックを、昇順にソートする",
["rev"] = "数値スタックを、順序逆転する",
["clr"] = "数値スタックを、全て消去する",
["help"] = "演算子,命令の一覧,説明を、出力する",
["xvar"] = "変数を登録する",
["xin"] = "前の数字を、変数に入れる",
["xout"] = "変数を数値Stackに入れる",
["xcout"] = "全ての変数とその値を出力する",
};
// 数値を入れておくスタック
public Stack<double> numStack = new Stack<double>();
// 変数のディクショナリ
public Dictionary<string, double> variables =
new Dictionary<string, double>();
// 計算もしくは数字を追加する
public bool PassString(string[] txts)
{
var main = txts[0];
var options = txts.Skip(1).ToArray();
// 演算子が有効かどうか
var havePrefixs = new Dictionary<char, bool>(prefixs);
// 先頭に接頭辞が見つからなくなるまで繰り返す
var isContain = true;
while (isContain)
{
if (havePrefixs.ContainsKey(main[0]))
{
havePrefixs[main[0]] ^= true;
main = main.Substring(1);
isContain = true;
}
else
{
isContain = false;
}
}
if (numStack.Count >= 1 && operatorsOf1.ContainsKey(main))
{
var num = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
numStack.Push(operatorsOf1[main](num, options));
return true;
}
else if (numStack.Count >= 2 && operatorsOf2.ContainsKey(main))
{
var num1 = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
var num2 = havePrefixs['&'] ? numStack.Pop() : numStack.Peek();
numStack.Push(operatorsOf2[main](num2, num1, options));
return true;
}
else if (operatorsAll.ContainsKey(main))
{
var nums = numStack.ToArray();
if (havePrefixs['&'])
numStack.Clear();
numStack.Push(operatorsAll[main](nums, options));
return true;
}
else if (instructions.ContainsKey(txts[0]))
{
instructions[main](this, options);
return true;
}
else if (double.TryParse(main, out var num))
{
numStack.Push(num);
return true;
}
else if (variables.ContainsKey(main))
{
numStack.Push(variables[main]);
return true;
}
return false;
}
public bool PassString(string word)
{
var elements = word.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
return PassString(elements);
}
// txtsを順に計算もしくは数字を追加する
public double[] PassStrings(string[] words, bool debug = false)
{
bool existNotFound = false;
foreach (var word in words)
{
if (PassString(word.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries)))
{
if (debug)
{
Console.Write("= ");
if (numStack.Count > 0)
{
foreach (var num in numStack.Reverse())
{
Console.Write("{0}, ", num);
}
Console.Write('\n');
}
else
{
Console.WriteLine("nothing");
}
}
}
else
{
Console.WriteLine("{0} is not found", word);
existNotFound = true;
}
}
if (debug || existNotFound)
{
Console.WriteLine();
}
return numStack.Reverse()
.ToArray();
}
// txtの命令を実行する
public bool Instruction(string words)
{
var word = words.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
if (instructions.ContainsKey(word[0]))
{
instructions[word[0]](this, word.Skip(1).ToArray());
return true;
}
return false;
}
}
}
Mainメソッド
using System;
namespace Calculator
{
class Program
{
static void Main(string[] args)
{
// 計算機
var calc = new Calculator();
Console.WriteLine("Scientific Calculator");
Console.WriteLine("ver.{0}", Calculator.version);
Console.WriteLine("maked by {0}", Calculator.maker);
Console.WriteLine();
Console.WriteLine("演算子,命令の一覧,説明は、'help'で確認できます");
Console.WriteLine();
//下のwhileを繰り返すかどうか
var isRepeat = true;
while (isRepeat)
{
// コマンドから入力したものを' '(空白)もしくは','で分割する
Console.Write("> ");
var inputs = Console.ReadLine().Split(new[] { ' ', ',' }, StringSplitOptions.RemoveEmptyEntries);
Console.WriteLine();
// 分割した入力配列を渡す
var nums = calc.PassStrings(inputs, debug: false);
Console.Write("= ");
if (nums.Length > 0)
{
foreach (var num in nums)
{
Console.Write("{0}, ", num);
}
Console.Write('\n');
}
else
{
Console.WriteLine("nothing");
}
Console.WriteLine();
}
}
}
}