Windowsにある電卓は、マウスでの操作を強制されるのでチョット嫌です。
そしたら自前で電卓をじゃあ作るか!となった時にハードルになるのが、
字句解析だったり、構文解析器の作成なのですが、これらを 自分で作成するのって
...辛すぎるのではないでしょうか?字句解析ならともかくも、
構文解析器を自分で作るのって、、、どうでしょう?
多分ですが、メチャクチャ大変で、エンジニア人生の中でも成し遂げるのにはかなりのインパクトがあり、
個人の技術的な実りはあれど、誰もお金をくれないチョット辛い作業です。
ここいら辺、世の中には既に使い方を学べばよしなにやってくれる便利なツールがあります。
そう、それが、lex & yacc...
だったら、ソイツラを使ってなるだけ楽しようぜ?というのが今回の趣旨。
利用する言語とモジュール
以下説明に利用する言語はpythonです。
また、lex & yaccに相当するライブラリにPLY(Python-Lex-Yacc)を利用します。
lexってなんだ?
lexとは、一般的には字句解析器(レキシカルアナライザ)と呼ばれ、
一見難しく思えますが、やってることは実にシンプルで、
正規表現で示されたパターンにマッチする文字列を、トークンとして切り出し、
続く、yaccに引き渡す働きを持ちます...などと言ってもややこしいか。
要は、数値や、文字列といった塊を認識するための規則を正規表現でlexに与えると、
トークンとして認識してくれるよっということです。
PLYを使った場合の
文字列と、整数、浮動小数点、加算、減算、乗算、除算の識別規則を下記に記します。
浮動小数点数の正規表現
'\d+\.?\d*([eE][+-]?\d+|)'
下のような浮動小数点数にマッチします。
3.14159
2.718281828459
2.
1.0E+3 1.0E-3
1.0e+3 1.0e-3
1.E+3 1.E-3
1.e+3 1.e-3
1E+3 1E-3
1e+3 1e-3
整数の正規表現
'\d+'
が一般ですが、先に定義した浮動小数点数の定義にマッチしない(?!で指定)ということも
定義に入れます。
'(?!\d+\.?\d*([eE][+-]?\d+|))\d+'
加算の正規表現
'\+'
減算の正規表現
'-'
乗算の正規表現
'\*'
除算の正規表現
'/'
文字列と整数、浮動小数点、加算・減算・乗算・除算のlex定義
import ply.lex as lex
# List of token names. This is always required
tokens = (
'NAME',
'NUMBER',
'FLOAT',
'PLUS',
'MINUS',
'TIMES',
'DIVIDE'
)
# Regular expression rules for simple tokens
t_NAME = r'[a-zA-Z]+'
t_PLUS = r'\+'
t_MINUS = r'-'
t_TIMES = r'\*'
t_DIVIDE = r'/'
# A regular expression rule with some action code
def t_NUMBER(t):
r'(?!\d+\.?\d*([eE][+-]?\d+|))\d+'
t.value = int(t.value)
return t
def t_FLOAT(t):
r'\d+\.?\d*([eE][+-]?\d+|)'
t.value = float(t.value)
return t
yaccとは?
yaccは、lexで生成されたトークンを、
”定義されたルール=構文規則”に従って構文解析するパーサを生成するツールです。
ここでいう定義されたルールとは、電卓の場合、
整数
浮動小数点数
整数 + 整数
浮動小数点数 + 浮動小数点数
整数 + 整数 * 浮動小数点
などといったトークンがyaccに渡った場合に対応する規則のことです。
この規則を、再帰表現を使って定義し対応するアクションを定義します。
ひとまず、
整数/浮動小数点数/加算/減算/乗算/除算に対応する再帰的な構文規則はこんなカンジです。
構文規則
'''expression : expression PLUS expression
| expression MINUS expression
| expression TIMES expression
| expression DIVIDE expression
| FLOAT
| NUMBER'''
expression(数式)の定義にexpression(数式)を利用しているのが分かります
ただ、無限再帰にならないように、FLOAT、NUMBERで再帰の終了条件が示されています。
ついで、それぞれの構文規則に対応したアクションを記載することで、
整数/浮動小数点数/加算/減算/乗算/除算を処理することが出来ます。
構文規則に対応するアクション
# 足し算
if len(p) == 4 and p[2] == '+':
p[0] = p[1] + p[3]
# 引き算
elif len(p) == 4 and p[2] == '-':
p[0] = p[1] - p[3]
# 乗算
elif len(p) == 4 and p[2] == '*':
p[0] = p[1] * p[3]
# 除算
elif len(p) == 4 and p[2] == '/':
p[0] = p[1] / p[3]
# 浮動小数点数
elif len(p) == 2 and isinstance(p[1], float):
p[0] = p[1]
# 整数
elif len(p) == 2 and isinstance(p[1], int):
p[0] = p[1]
実装したいこと
ひとまず、下記の機能を実装してみたいです。
括弧を使った算術優先順序の決定
4 * (5 + 4)、(3 + 4) / 3、etc
一時期、ネットで話題になった、
9-3/1/3+1なんかも電卓としての結果を返したい(笑
加算・減算と乗算・除算との間の算術優先順序の決定
4 + 5 * 6、3 / 3 + 1、etc
べき算の導入
25 ^ 4、25 ^ (2.0e+2)、etc
変数定義機能と計算利用
a = 2.0
a * 5.0、a * a、etc
tan、cos、sin、exp、log、sqrtの導入
tan(10)、cos(50)、sin(4)、exp(10)、log(2) <-eを底に、sqrt(4)、etc
ポイント
演算の優先順序を与えるには、カンタンに下記のように指定出来ます。
precedence = (
('left', 'PLUS', 'MINUS'),
('left', 'TIMES', 'DIVIDE'),
('left', 'POW' )
)
上記のように設定することで、下記の演算で、
2 - 3 + 2 * 4 / 3 ^ 3
= 2 - 3 + (2 * 4) / (3 ^ 3)
= -0.703703703704
という結果になります。(google電卓や、普通の電卓と同じ結果を得ます。)
実装
本ページの下に、下記ソースのリンクを貼っておきます。
cal_lex.py
cal_yacc.py
実行方法
cal_lex.pyと、cal_yacc.pyを同一フォルダに格納して、
コマンドプロンプトから
python cal_yacc.py
calculation >
下の実行結果に示した演算を入力してどうぞ。
ちなみにPLYモジュールのインストールが必要なので、
pip install ply
などしてインストールしてください。
実行結果
結果1
9-3/1/3+1
=9
結果2
(sqrt(10) ^ 2.0 + tan(10) * 100 + (4 * 100) + 1.0e+2) * 2.0 + (sin(10) / cos(19)) ^ 2.0
=1149.97492541
結果3
a = (sqrt(10) ^ 2.0 + tan(10) * 100 + (4 * 100) + 1.0e+2) * 2.0 + (sin(10) / cos(19)) ^ 2.0
a ^ 2.0
=1322442.32907
結果4
sin(10) ^ 2 + cos(10) ^ 2
=1.0
結果5
sin(sin(sin(19)))
=0.148762489955
結果6
exp(2) / log(10) * tan(10) ^ 2 + 5 * sin(10)
=-1.37112151664
動作環境
Mac OS X EI Captian
python 2.7 + ply.yacc(ver 3.8)
Eclipse Mars
参考文献/情報
lex & yacc
O'REILLY John R.Levine, Tony Mason & Doug Brown
PLY (Python Lex-Yacc)
http://members.jcom.home.ne.jp/jintrick/Personal/ply.ja.html#parsing
Lex and YACC primer/HOWTO
http://archive.linux.or.jp/JF/JFdocs/Lex-YACC-HOWTO.html#toc1