pystacker 1.0.3__py3.10.egg

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
EGG-INFO/PKG-INFO ADDED
@@ -0,0 +1,446 @@
1
+ Metadata-Version: 2.1
2
+ Name: pystacker
3
+ Version: 1.0.3
4
+ Summary: Stacker: RPN Calculator in Python
5
+ Home-page: https://github.com/remokasu/stacker
6
+ Author: remokasu
7
+ License: MIT License
8
+
9
+ Copyright (c) 2023 Remokasu
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Keywords: reverse-polish-calculator rpn terminal-app
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+
33
+ # Stacker: RPN Calculator in Python
34
+ ~~~
35
+ _____ _ _
36
+ / ____|| | | |
37
+ | (___ | |_ __ _ ___ | | __ ___ _ __
38
+ \___ \ | __| / _` | / __|| |/ / / _ \| '__|
39
+ ____) || |_ | (_| || (__ | < | __/| |
40
+ |_____/ \__| \__,_| \___||_|\_\ \___||_|
41
+
42
+ ~~~
43
+
44
+
45
+ # Download & Install
46
+
47
+ If you don't have Python 3 installed, please install it beforehand.
48
+ Here are the installation instructions for `stacker`:
49
+
50
+ 1. Download
51
+ ~~~ bash
52
+ > git clone git@github.com:remokasu/stacker.git
53
+ ~~~
54
+
55
+ 2. Install dependencies
56
+ ~~~
57
+ pip install setuptools
58
+ ~~~
59
+
60
+ * For Windows users only
61
+ ~~~ bash
62
+ > pip install pyreadline
63
+ ~~~
64
+
65
+ 3. Run the program without installation
66
+ ~~~ bash
67
+ > cd stacker/stacker
68
+ > python stacker.py
69
+ ~~~
70
+
71
+ 4. (Or) Install and run the program
72
+ ~~~ bash
73
+ cd stacker
74
+ > python setup.py install
75
+ > stacker
76
+ ~~~
77
+
78
+ * When you're done playing with it, uninstall
79
+ ~~~ bash
80
+ > pip uninstall stacker
81
+ ~~~
82
+
83
+
84
+ | Operator | Description | Example | Result |
85
+ |----------|-------------------------------------------------------|----------------------------|--------------------------|
86
+ | + | Add | `3 5 +` | 8 |
87
+ | - | Subtract | `10 3 -` | 7 |
88
+ | * | Multiply | `4 6 *` | 24 |
89
+ | / | Divide | `12 4 /` | 3 |
90
+ | // | Integer divide | `7 2 //` | 3 |
91
+ | % | Modulus | `9 2 %` | 1 |
92
+ | ^ | Power | `3 2 ^` | 9 |
93
+ | neg | Negate | `5 neg` | -5 |
94
+ | abs | Absolute value | `-3 abs` | 3 |
95
+ | exp | Exponential | `3 exp` | math.exp(3) |
96
+ | log | Natural logarithm | `2 log` | math.log(2) |
97
+ | log10 | Common logarithm (base 10) | `4 log10` | math.log10(4) |
98
+ | log2 | Logarithm base 2 | `4 log2` | math.log2(4) |
99
+ | sin | Sine | `30 sin` | math.sin(30) |
100
+ | cos | Cosine | `45 cos` | math.cos(45) |
101
+ | tan | Tangent | `60 tan` | math.tan(60) |
102
+ | asin | Arcsine | `0.5 asin` | math.asin(0.5) |
103
+ | acos | Arccosine | `0.5 acos` | math.acos(0.5) |
104
+ | atan | Arctangent | `1 atan` | math.atan(1) |
105
+ | sinh | Hyperbolic sine | `1 sinh` | math.sinh(1) |
106
+ | cosh | Hyperbolic cosine | `1 cosh` | math.cosh(1) |
107
+ | tanh | Hyperbolic tangent | `1 tanh` | math.tanh(1) |
108
+ | asinh | Inverse hyperbolic sine | `1 asinh` | math.asinh(1) |
109
+ | acosh | Inverse hyperbolic cosine | `2 acosh` | math.acosh(2) |
110
+ | atanh | Inverse hyperbolic tangent | `0.5 atanh` | math.atanh(0.5) |
111
+ | sqrt | Square root | `9 sqrt` | math.sqrt(9) |
112
+ | ceil | Ceiling | `3.2 ceil` | math.ceil(3.2) |
113
+ | floor | Floor | `3.8 floor` | math.floor(3.8) |
114
+ | round | Round | `3.5 round` | round(3.5) |
115
+ | float | Convert to floating-point number | `5 float` | 5.0 |
116
+ | int | Convert to integer | `3.14 int` | 3 |
117
+ | == | Equal | `1 1 ==` | True |
118
+ | != | Not equal | `1 0 !=` | True |
119
+ | < | Less than | `1 2 <` | True |
120
+ | <= | Less than or equal to | `3 3 <=` | True |
121
+ | > | Greater than | `2 1 >` | True |
122
+ | >= | Greater than or equal to | `3 3 >=` | True |
123
+ | and | Logical and | `true false and` | False |
124
+ | or | Logical or | `true false or` | True |
125
+ | not | Logical not | `true not` | False |
126
+ | band | Bitwise and | `3 2 band` | 3 & 2 |
127
+ | bor | Bitwise or | `3 2 bor` | 3 | 2 |
128
+ | bxor | Bitwise xor | `3 2 bxor` | 3 ^ 2 |
129
+ | gcd | Greatest common divisor | `4 2 gcd` | math.gcd(4, 2) |
130
+ | ! | Factorial | `4 !` | math.factorial(4) |
131
+ | radians | Convert degrees to radians | `180 radians` | math.radians(180) |
132
+ | roundn | Round to specified decimal places | `3.51 1 roundn` | round(3.51, 1) |
133
+ | random | Generate a random floating-point number between 0 and 1| `random` | random.random() |
134
+ | randint | Generate a random integer within a specified range | `1 6 randint` | random.randint(1, 6) |
135
+ | uniform | Generate a random floating-point number within a specified range | `1 2 uniform` | random.uniform(1, 2) |
136
+ | d | Roll dice (e.g., 3d6) | `3 6 d` | sum(random.randint(1, 6) for _ in range(3)) |
137
+
138
+
139
+ <hr>
140
+
141
+ ## Custom Functions::
142
+
143
+ ### Example 2: Function to calculate the average of two numbers (average)
144
+ ~~~ bash
145
+ stacker:0> x y average => x y + 2 /
146
+ stacker:1> 2 6 average
147
+ [4.0]
148
+ ~~~
149
+
150
+
151
+ (Note that the function definition syntax is a custom RPN-like syntax, so please don't worry about it)
152
+
153
+ <br>
154
+
155
+ <hr>
156
+
157
+ ## You can also enter input like this. In fact, this is the normal way to use it.
158
+ (Example) 3 4 +
159
+ ~~~ bash
160
+ stacker:0> 3
161
+ [3.0]
162
+ stacker:1> 4
163
+ [3.0, 4.0]
164
+ stacker:2> +
165
+ [7.0]
166
+ ~~~
167
+
168
+ ## clear
169
+ Clear the stack with 'clear'
170
+ ~~~
171
+ stacker:0> clear
172
+ []
173
+ ~~~
174
+
175
+ ## exit
176
+ Exit the program with 'exit'
177
+ ~~~
178
+ stacker:0> exit
179
+ ~~~
180
+
181
+ ## about
182
+ Display Stacker's information with `about` (not particularly meaningful)
183
+ ~~~
184
+ stacker:0> about
185
+ ~~~
186
+
187
+ ## help
188
+ Display usage instructions with `help`
189
+ ~~~
190
+ stacker:0> help
191
+ ~~~
192
+
193
+
194
+ <br>
195
+ <hr>
196
+
197
+ # 概要
198
+
199
+ ある晴れた日、学生Aは数学の試験に挑むため、緊張しながら教室へ向かっていました。しかし、彼はある重要なものを忘れてしまっていたのです。それは、関数電卓でした。
200
+
201
+ <br>
202
+
203
+ `学生A(焦りながら)`
204
+ ~~~
205
+ 先生、電卓を忘れちゃったんですが、お借りできますか?
206
+ ~~~
207
+
208
+ `教授(待ってました!と言いたげな表情で)`
209
+ ~~~
210
+ もちろんだ。特別に君にコレを貸してあげよう。
211
+ ヒューレットパッカードの稀代の名機 「hp 50G」!!
212
+ とっくに生産終了してしまって今では新品で買うことなど不可能なシロモノだ。
213
+ Amaz●nではプレミアがついて10倍の値段で取引されている。
214
+ このでっかい画面にはグラフも描画できちゃうぞ!
215
+ さらにこの小さな端子はなななななんとRS-232!シリアル通信だってできちゃう!
216
+ 電卓のくせに一体何と通信するんだろうねぇ!?
217
+ まあ、何故かコネクタは公式から発売されることは無かったから実質幻の機能だがな...
218
+ どうだ?凄いだろう?
219
+ ~~~
220
+
221
+ `学生A(顔を輝かせつつ)`
222
+ ~~~
223
+ ええっ!なにそれチートアイテムぅ!?(よく分かんないけど凄そう!デカイし!)
224
+ やったあ...これで試験も余裕です!!(泣)
225
+ ~~~
226
+
227
+ `教授(ニヤリと笑いながら)`
228
+ ~~~
229
+ ただし、これは逆ポーランド記法の電卓だぞ
230
+ ~~~
231
+
232
+ `学生A(戸惑いつつ)`
233
+ ~~~
234
+ 逆ポ...?逆ポーランドって何です?よく分かんないけど、なんかカッコいいですね!
235
+ ~~~
236
+
237
+ `教授(クスクス笑い)`
238
+ ~~~
239
+ ちょっと普通の電卓とは扱い方が異なるだけだ。
240
+ なに、心配することはないよ。普通の電卓のようなモードにも切り替えられるからね。
241
+ ~~~
242
+
243
+ `学生A(安心しながら)`
244
+ ~~~
245
+ なるほど!ありがとうございます!!たすかりましたああ!!
246
+ ~~~
247
+
248
+ `教授(声には出さず)`
249
+ ~~~
250
+ (ただし切り替え方は初見では分かり難いだろうねククク...)
251
+ ~~~
252
+
253
+
254
+ <br>
255
+ その試験で学生Aは泣いたという。
256
+
257
+ このプログラムは、A君のトラウマを追体験することができる。
258
+
259
+ <br>
260
+
261
+ <hr>
262
+
263
+ # ダウンロード & インストール
264
+
265
+ python3が無ければ事前にインストールしてください。
266
+ 以下は`stacker`のインストール方法です。
267
+
268
+ 1. ダウンロード & インストール
269
+ ~~~ bash
270
+ > pip install pystacker
271
+ ~~~
272
+
273
+ 2. 起動
274
+ ~~~
275
+ > stacker
276
+ ~~~
277
+
278
+ * windowsの場合の場合、追加で実行
279
+ ~~~ bash
280
+ > pip install pyreadline
281
+ ~~~
282
+
283
+ * 遊び終わったら削除しましょう
284
+ ~~~ bash
285
+ > pip uninstall pystacker
286
+ ~~~
287
+
288
+ # 使い方
289
+
290
+ | 演算子 | 説明 | 使い方 | 結果 |
291
+ |--------|-----------------------------------------------------|--------------------------|--------------------------|
292
+ | + | 加算 | `3 5 +` | 8 |
293
+ | - | 減算 | `10 3 -` | 7 |
294
+ | * | 乗算 | `4 6 *` | 24 |
295
+ | / | 除算 | `12 4 /` | 3 |
296
+ | // | 整数除算 | `7 2 //` | 3 |
297
+ | % | 剰余 | `9 2 %` | 1 |
298
+ | ^ | 累乗 | `3 2 ^` | 9 |
299
+ | neg | 符号反転 | `5 neg` | -5 |
300
+ | abs | 絶対値 | `-3 abs` | 3 |
301
+ | exp | 指数関数 | `3 exp` | math.exp(3) |
302
+ | log | 自然対数 | `2 log` | math.log(2) |
303
+ | log10 | 常用対数 (底10) | `4 log10` | math.log10(4) |
304
+ | log2 | 底2の対数 | `4 log2` | math.log2(4) |
305
+ | sin | 正弦 | `30 sin` | math.sin(30) |
306
+ | cos | 余弦 | `45 cos` | math.cos(45) |
307
+ | tan | 正接 | `60 tan` | math.tan(60) |
308
+ | asin | 逆正弦 | `0.5 asin` | math.asin(0.5) |
309
+ | acos | 逆余弦 | `0.5 acos` | math.acos(0.5) |
310
+ | atan | 逆正接 | `1 atan` | math.atan(1) |
311
+ | sinh | 双曲線正弦 | `1 sinh` | math.sinh(1) |
312
+ | cosh | 双曲線余弦 | `1 cosh` | math.cosh(1) |
313
+ | tanh | 双曲線正接 | `1 tanh` | math.tanh(1) |
314
+ | asinh | 逆双曲線正弦 | `1 asinh` | math.asinh(1) |
315
+ | acosh | 逆双曲線余弦 | `2 acosh` | math.acosh(2) |
316
+ | atanh | 逆双曲線正接 | `0.5 atanh` | math.atanh(0.5) |
317
+ | sqrt | 平方根 | `9 sqrt` | math.sqrt(9) |
318
+ | ceil | 切り上げ | `3.2 ceil` | math.ceil(3.2) |
319
+ | floor | 切り捨て | 3.8 floor | math.floor(3.8) |
320
+ | round | 四捨五入 | 3.5 round | round(3.5) |
321
+ | float | 浮動小数点数に変換 | 5 float | 5.0 |
322
+ | int | 整数に変換 | 3.14 int | 3 |
323
+ | == | 等しい | 1 1 == | True |
324
+ | != | 等しくない | 1 0 != | True |
325
+ | < | より小さい | 1 2 < | True |
326
+ | <= | 以下 | 3 3 <= | True |
327
+ | > | より大きい | 2 1 > | True |
328
+ | >= | 以上 | 3 3 >= | True |
329
+ | and | 論理積 | true false and | False |
330
+ | or | 論理和 | true false or | True |
331
+ | not | 論理否定 | true not | False |
332
+ | band | ビットごとの論理積 | 3 2 band | 3 & 2 |
333
+ | bor | ビットごとの論理和 | 3 2 bor | 3 | 2 |
334
+ | bxor | ビットごとの排他的論理和 | 3 2 bxor | 3 ^ 2 |
335
+ | gcd | 最大公約数 | 4 2 gcd | math.gcd(4, 2) |
336
+ | ! | 階乗 | 4 ! | math.factorial(4) |
337
+ | radians| 度数法から弧度法へ変換 | 180 radians | math.radians(180) |
338
+ | roundn | 指定した小数点以下の桁数で四捨五入 | 3.51 1 roundn | round(3.51, 1) |
339
+ | random | 0と1の間の乱数を生成 | random | random.random() |
340
+ | randint| 指定した範囲内の整数乱数を生成 | 1 6 randint | random.randint(1, 6) |
341
+ | uniform| 指定した範囲内の浮動小数点数乱数を生成 | 1 2 uniform | random.uniform(1, 2) |
342
+ | d | サイコロを振る (例:3d6) | 3 6 d | sum(random.randint(1, 6) for _ in range(3)) |
343
+
344
+
345
+ <br>
346
+ <hr>
347
+
348
+ ## 自作関数:
349
+
350
+ ### 例 1: 二つの数の平均を計算する関数 (average)
351
+ ~~~ bash
352
+ stacker:0> x y average => x y + 2 /
353
+ stacker:1> 2 6 average
354
+ [4.0]
355
+ ~~~
356
+
357
+
358
+ (関数定義の構文はRPN構文っぽい独自定義の構文なので突っ込まないでくれ)
359
+
360
+ <br>
361
+
362
+ <hr>
363
+
364
+ ## こんな入力も出来るよ。というかこっちが普通の使い方
365
+ (例) 3 4 +
366
+ ~~~ bash
367
+ stacker:0> 3
368
+ [3.0]
369
+ stacker:1> 4
370
+ [3.0, 4.0]
371
+ stacker:2> +
372
+ [7.0]
373
+ ~~~
374
+
375
+ ## clear
376
+ `clear` でスタックを初期化
377
+ ~~~
378
+ stacker:0> clear
379
+ []
380
+ ~~~
381
+
382
+ ## exit
383
+ `exit` で終了
384
+ ~~~
385
+ stacker:0> exit
386
+ ~~~
387
+
388
+ ## about
389
+ `about` でStackerの情報を表示(特に意味なし)
390
+ ~~~
391
+ stacker:0> about
392
+ ~~~
393
+
394
+ ## help
395
+ `help` で使い方を表示
396
+ ~~~
397
+ stacker:0> help
398
+ ~~~
399
+
400
+ # おまけ
401
+
402
+ ## eval (逆ポーランドなんてクソ喰らえだ)
403
+ シングルコーテーションで囲った文字列を `eval` で評価できる。
404
+ やはり中置記法こそ正義なのです。
405
+ ところで、なんで君はStackerを使ってるんですか?
406
+ ~~~
407
+ stacker:0> '3 + 5' eval
408
+ [8]
409
+ stacker:1>
410
+ ~~~
411
+
412
+ ## exec (Pythonが使いたい)
413
+ シングルコーテーションでかこった範囲は改行しても一連の文字列として認識できる。
414
+ そこにPythonコードをぶっこんで `exec` で実行できる。
415
+ ただし、execはどんな処理でも結果が`None`なので結果はスタックに入らない。
416
+ 結果を表示したければptint文を式中に埋め込もう。
417
+ 文字列に対して`exec`することでPythonの処理として実行できる。
418
+ この機能、危険な香りがしますね。
419
+ ~~~
420
+ stacker:0> '
421
+ stacker:0> def f(x):
422
+ stacker:0> return x**2
423
+ stacker:0> print(f(4))
424
+ stacker:0> '
425
+ ['\ndef f(x):\n\treturn x**2\nprint(f(4))\n']
426
+ stacker:7> exec
427
+ 16
428
+ []
429
+ ~~~
430
+
431
+ ・面倒くさいので以後はこうしましょうね。
432
+ ~~~
433
+ stacker:10> '
434
+ stacker:10> from subprocess import run
435
+ stacker:10> run("python")
436
+ stacker:10> '
437
+ ['\nfrom subprocess import run\nrun("python")\n']
438
+ stacker:11> exec
439
+ Python 3.10.6 (main, Mar 10 2023, 10:55:28) [GCC 11.3.0] on linux
440
+ Type "help", "copyright", "credits" or "license" for more information.
441
+ >>>
442
+ ~~~
443
+
444
+ <br>
445
+ <hr>
446
+
EGG-INFO/SOURCES.txt ADDED
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ pystacker.egg-info/PKG-INFO
5
+ pystacker.egg-info/SOURCES.txt
6
+ pystacker.egg-info/dependency_links.txt
7
+ pystacker.egg-info/entry_points.txt
8
+ pystacker.egg-info/requires.txt
9
+ pystacker.egg-info/top_level.txt
10
+ stacker/__init__.py
11
+ stacker/stacker.py
12
+ stacker/test.py
13
+ stacker/data/about.txt
14
+ stacker/data/help.txt
15
+ stacker/data/top.txt
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ stacker = stacker.stacker:main
EGG-INFO/not-zip-safe ADDED
@@ -0,0 +1 @@
1
+
EGG-INFO/requires.txt ADDED
@@ -0,0 +1 @@
1
+ prompt-toolkit
EGG-INFO/top_level.txt ADDED
@@ -0,0 +1 @@
1
+ stacker
stacker/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ import os
2
+
3
+ # リソースフォルダへのパスを取得する
4
+ resource_path = os.path.join(os.path.dirname(__file__), 'data')
5
+
6
+ # モジュールにパスを追加する
7
+ __path__.append(resource_path)
Binary file
stacker/data/about.txt ADDED
@@ -0,0 +1,9 @@
1
+ Welcome to Stacker, where we stack things up - but only in the right order! 😉
2
+ This powerful, yet humble, Reverse Polish Notation (RPN) calculator is here to make your life easier.
3
+ We know, it's not every day you encounter a calculator that loves postfix expressions as much as we do. 🤓
4
+
5
+ With Stacker, you'll experience the joy of crunching numbers without the hassle of parentheses. 🥴
6
+ Our calculator is so dedicated to the cause that it even lets you define your own functions - isn't that fantastic? 🚀
7
+
8
+ So, go ahead and give Stacker a spin! Just remember, if you ever feel lost, type "help" and we'll be there for you.
9
+ Happy stacking! 🎉
stacker/data/help.txt ADDED
@@ -0,0 +1,30 @@
1
+ Enter RPN expression, variable assignment, or function definition.
2
+ Type 'exit' to quit.
3
+
4
+ Examples:
5
+ RPN expression: 3 4 +
6
+ Variable assignment: x = 5
7
+ Function definition: x y func => x y *
8
+ Function call: 4 5 func
9
+
10
+ Supported operators and functions:
11
+ Arithmetic: +, -, *, /, //, %, ^, neg, abs
12
+ Comparison: ==, !=, <, <=, >, >=
13
+ Logical: and, or, not
14
+ Bitwise: band, bor, bxor
15
+ Math functions:
16
+ exp, log, log10, log2, sin, cos, tan, asin, acos, atan,
17
+ sinh, cosh, tanh, asinh, acosh, atanh, sqrt, cbrt,
18
+ ceil, floor, round, roundn, random, randint, uniform, d,
19
+ ncr((nCr)), npr(nPr)
20
+
21
+ Type conversion: int2float, float2int
22
+ Factorial: !
23
+ GCD: gcd
24
+
25
+ Usage:
26
+ Input numbers and operators in RPN notation, separated by spaces.
27
+ Press Enter to evaluate the expression and display the result.
28
+ The result will be pushed onto the stack.
29
+ To use the result in a subsequent calculation, input the next expression.
30
+ To clear the stack, type 'clear'.
stacker/data/top.txt ADDED
@@ -0,0 +1,6 @@
1
+ _____ _ _
2
+ / ____|| | | |
3
+ | (___ | |_ __ _ ___ | | __ ___ _ __
4
+ \___ \ | __| / _` | / __|| |/ / / _ \| '__|
5
+ ____) || |_ | (_| || (__ | < | __/| |
6
+ |_____/ \__| \__,_| \___||_|\_\ \___||_|
stacker/stacker.py ADDED
@@ -0,0 +1,501 @@
1
+ from __future__ import annotations
2
+
3
+ import cmath
4
+ import copy
5
+ import math
6
+ import os
7
+ import random
8
+ import re
9
+ import shlex
10
+ from pathlib import Path
11
+ from typing import Optional
12
+
13
+ import pkg_resources
14
+ from prompt_toolkit import prompt
15
+ from prompt_toolkit.completion import WordCompleter
16
+ from prompt_toolkit.history import FileHistory
17
+
18
+ history_file = ".stacker_history"
19
+ history_file_path = Path.home() / history_file
20
+
21
+
22
+ COLORS = {
23
+ "black": "\033[30m",
24
+ "red": "\033[31m",
25
+ "green": "\033[32m",
26
+ "yellow": "\033[33m",
27
+ "blue": "\033[34m",
28
+ "magenta": "\033[35m",
29
+ "cyan": "\033[36m",
30
+ "lightgray": "\033[37m",
31
+ "default": "\033[39m",
32
+ "darkgray": "\033[90m",
33
+ "lightred": "\033[91m",
34
+ "lightgreen": "\033[92m",
35
+ "lightyellow": "\033[93m",
36
+ "lightblue": "\033[94m",
37
+ "lightmagenta": "\033[95m",
38
+ "lightcyan": "\033[96m",
39
+ "white": "\033[97m",
40
+ "reset": "\033[0m",
41
+ }
42
+
43
+
44
+ def colored(text: str, color: Optional[str] = "default", end: str = "\n") -> None:
45
+ """A context manager for setting and resetting the terminal color.
46
+ Args:
47
+ color (str, optional): The desired text color. Defaults to "default".
48
+ Returns:
49
+ None
50
+ """
51
+ ctext = COLORS[color] + text + COLORS["reset"]
52
+ return ctext
53
+
54
+
55
+ def show_top():
56
+ # colors = ["red", "green", "yellow", "cyan", "lightred", "lightyellow"]
57
+ colors = ["red", "green", "yellow", "lightblue", "lightmagenta", "cyan"]
58
+ with pkg_resources.resource_stream(__name__, "data/top.txt") as f:
59
+ messages = f.readlines()
60
+ for i in range(len(messages)):
61
+ print(colored(messages[i].decode('utf-8'), colors[i]), end="")
62
+ print("")
63
+ # with pkg_resources.resource_stream(__name__, "data/top.txt") as f:
64
+ # message = f.read().decode('utf-8')
65
+ # print(message)
66
+
67
+
68
+ def show_about():
69
+ with pkg_resources.resource_stream(__name__, "data/about.txt") as f:
70
+ message = f.read().decode('utf-8')
71
+ print(message)
72
+
73
+
74
+ def show_help():
75
+ with pkg_resources.resource_stream(__name__, "data/help.txt") as f:
76
+ message = f.read().decode('utf-8')
77
+ print(message)
78
+
79
+
80
+ def delete_history():
81
+ if history_file_path.exists():
82
+ history_file_path.unlink()
83
+
84
+
85
+ def input_to_str_or_int_or_float(input_str: str) -> int | float | str:
86
+ try:
87
+ return int(input_str)
88
+ except ValueError:
89
+ pass
90
+ try:
91
+ return float(input_str)
92
+ except ValueError:
93
+ pass
94
+ try:
95
+ return complex(input_str)
96
+ except ValueError:
97
+ pass
98
+ return input_str
99
+
100
+
101
+ # 入力が実数か虚数かで呼び出すモジュールを切り替える
102
+ def wrap(func, cfunc):
103
+ def wrapper(x):
104
+ if isinstance(x, complex):
105
+ return cfunc(x)
106
+ return func(x)
107
+ return wrapper
108
+
109
+
110
+ def math_pow(x1, x2):
111
+ if isinstance(x1, complex) or isinstance(x2, complex):
112
+ return cmath.exp(x2 * cmath.log(x1))
113
+ else:
114
+ return math.pow(x1, x2)
115
+
116
+
117
+ def math_log2(x):
118
+ if isinstance(x, complex):
119
+ return cmath.log(x) / cmath.log(2)
120
+ else:
121
+ return math.log2(x)
122
+
123
+
124
+ math_exp = wrap(math.exp, cmath.exp)
125
+ math_log = wrap(math.log, cmath.log)
126
+ math_log10 = wrap(math.log10, cmath.log10)
127
+ math_sin = wrap(math.sin, cmath.sin)
128
+ math_cos = wrap(math.cos, cmath.cos)
129
+ math_tan = wrap(math.tan, cmath.tan)
130
+ math_asin = wrap(math.asin, cmath.asin)
131
+ math_acos = wrap(math.acos, cmath.acos)
132
+ math_atan = wrap(math.atan, cmath.atan)
133
+ math_sinh = wrap(math.sinh, cmath.sinh)
134
+ math_cosh = wrap(math.cosh, cmath.cosh)
135
+ math_tanh = wrap(math.tanh, cmath.tanh)
136
+ math_asinh = wrap(math.asinh, cmath.asinh)
137
+ math_acosh = wrap(math.acosh, cmath.acosh)
138
+ math_atanh = wrap(math.atanh, cmath.atanh)
139
+ math_sqrt = wrap(math.sqrt, cmath.sqrt)
140
+
141
+
142
+ class Stacker:
143
+ def __init__(self):
144
+ self.stack = [] # スタックを追加
145
+ self.last_pop = None # pop コマンド(ユーザー入力)で取り出した値を一時的に格納。演算でpopする場合は対象外
146
+ self.operator = {
147
+ "==": (lambda x1, x2: x1 == x2), # 等しい
148
+ "!=": (lambda x1, x2: x1 != x2), # 等しくない
149
+ "<": (lambda x1, x2: x1 < x2), # より小さい
150
+ "<=": (lambda x1, x2: x1 <= x2), # 以下
151
+ ">": (lambda x1, x2: x1 > x2), # より大きい
152
+ ">=": (lambda x1, x2: x1 >= x2), # 以上
153
+ "and": (lambda x1, x2: x1 and x2), # 論理積
154
+ "or": (lambda x1, x2: x1 or x2), # 論理和
155
+ "not": (lambda x: not x), # 否定
156
+ "band": (lambda x1, x2: int(x1) & int(x2)), # ビット毎の and
157
+ "bor": (lambda x1, x2: int(x1) | int(x2)), # ビット毎の or
158
+ "bxor": (lambda x1, x2: int(x1) ^ int(x2)), # ビット毎の xor
159
+ "+": (lambda x1, x2: x1 + x2), # 加算
160
+ "-": (lambda x1, x2: x1 - x2), # 減算
161
+ "*": (lambda x1, x2: x1 * x2), # 乗算
162
+ "/": (lambda x1, x2: x1 / x2), # 除算
163
+ "//": (lambda x1, x2: x1 // x2), # 整数除算
164
+ "%": (lambda x1, x2: x1 % x2), # 剰余
165
+ "^": (lambda x1, x2: math_pow(x1, x2)), # べき乗
166
+ "gcd": (lambda x1, x2: math.gcd(int(x1), int(x2))), # 最大公約数
167
+ "lcm": (lambda x1, x2: math.lcm(int(x1), int(x2))), # 最小公倍数
168
+ "neg": (lambda x: -x), # 符号反転
169
+ "abs": (lambda x: abs(x)), # 絶対値
170
+ "exp": (lambda x: math_exp(x)), # 指数関数
171
+ "log": (lambda x: math_log(x)), # 自然対数
172
+ "log10": (lambda x: math_log10(x)), # 常用対数(底が10)
173
+ "log2": (lambda x: math_log2(x)), # 常用対数(底が10)
174
+ "sin": (lambda x: math_sin(x)), # 正弦関数
175
+ "cos": (lambda x: math_cos(x)), # 余弦関数
176
+ "tan": (lambda x: math_tan(x)), # 正接関数
177
+ "asin": (lambda x: math_asin(x)), # アークサイン
178
+ "acos": (lambda x: math_acos(x)), # アークコサイン
179
+ "atan": (lambda x: math_atan(x)), # アークタンジェント
180
+ "sinh": (lambda x: math_sinh(x)), # 双曲線正弦
181
+ "cosh": (lambda x: math_cosh(x)), # 双曲線余弦
182
+ "tanh": (lambda x: math_tanh(x)), # 双曲線正接
183
+ "asinh": (lambda x: math_asinh(x)), # 双曲線正弦の逆関数
184
+ "acosh": (lambda x: math_acosh(x)), # 双曲線余弦の逆関数
185
+ "atanh": (lambda x: math_atanh(x)), # 双曲線正接の逆関数
186
+ "sqrt": (lambda x: math_sqrt(x)), # 平方根
187
+ "radians": (lambda deg: math.radians(deg)), # ディグリー(度、Degree)からラジアン(弧度、Radian)に変換
188
+ "!": (lambda x: math.factorial(int(x))), # 階乗
189
+ "cbrt": (lambda x: pow(x, 1/3)), # 立方根
190
+ "ncr": (lambda n, k: math.comb(int(n), int(k))), # 組み合わせ (nCr)
191
+ "npr": (lambda n, k: math.perm(int(n), int(k))), # 順列 (nPr)
192
+ "float": (lambda x: float(x)), # 整数を浮動小数点数に変換
193
+ "int": (lambda x: int(x)), # 浮動小数点数を整数に変換
194
+ "ceil": (lambda x: math.ceil(x)), # 小数点以下を切り上げた最小の整数
195
+ "floor": (lambda x: math.floor(x)), # 小数点以下を切り捨てた最大の整数
196
+ "round": (lambda x: round(x)), # 最も近い整数に四捨五入
197
+ "roundn": (lambda x1, x2: round(x1, int(x2))), # 任意の桁数で丸める
198
+ "random": (lambda: random.random()), # 0から1までの範囲でランダムな浮動小数点数を生成
199
+ "randint": (lambda x1, x2: random.randint(int(x1), int(x2))), # 指定された範囲でランダムな整数を生成
200
+ "uniform": (lambda x1, x2: random.uniform(x1, x2)), # 指定された範囲でランダムな浮動小数点数を生成
201
+ "d": (lambda num_dice, num_faces: sum(random.randint(1, int(num_faces)) for _ in range(int(num_dice)))), # ダイスロール(例 3d6)
202
+ "delete": (lambda index: self.stack.pop(index)), # 指定のindexを削除
203
+ "pluck": (lambda index: self.stack.pop(index)), # 指定のindexを削除し、スタックのトップに移動
204
+ "pick": (lambda index: self.stack.append((self.stack[index]))), # 指定されたインデックスの要素をスタックのトップにコピー
205
+ "pop": (lambda: self.stack.pop()), # pop
206
+ "exec": (lambda command: exec(command, globals())), # 指定のPythonコードを実行
207
+ "eval": (lambda command: eval(command)), # 指定のPython式を評価
208
+ }
209
+ self.variables = {
210
+ "pi": math.pi,
211
+ "tau": math.tau,
212
+ "e": math.e,
213
+ "true": True,
214
+ "false": False,
215
+ "inf": float("inf"),
216
+ "nan": math.nan,
217
+ }
218
+ self.functions = {}
219
+ self.reserved_word = ["help", "about", "exit", "delete_history"]
220
+
221
+ # def get_operators_with_n_args(self, n: int):
222
+ # # 任意の引数の数に対応する演算子の一覧を取得
223
+ # matching_operators = []
224
+
225
+ # for label, func in self.operator.items():
226
+ # if func.__code__.co_argcount == n:
227
+ # matching_operators.append(label)
228
+
229
+ # return matching_operators
230
+
231
+ def get_n_args_for_operator(self, token):
232
+ # token(演算子)に必要な引数の数
233
+ if token in self.operator:
234
+ return self.operator[token].__code__.co_argcount
235
+ else:
236
+ raise KeyError(f"Invalid token {token}")
237
+
238
+ def highlight_syntax(self, expression):
239
+ """
240
+ Highlights the syntax of the given RPN expression.
241
+ Returns a colored string of the input expression.
242
+ """
243
+ tokens = expression.split()
244
+ highlighted_tokens = []
245
+
246
+ for token in tokens:
247
+ if token in self.operator:
248
+ color = "cyan"
249
+ if token in {"==", "!=", "<", "<=", ">", ">="}:
250
+ color = "yellow"
251
+ elif token in {"and", "or"}:
252
+ color = "green"
253
+ highlighted_tokens.append(colored(token, color))
254
+ else:
255
+ try:
256
+ float(token)
257
+ highlighted_tokens.append(colored(token, "white"))
258
+ except ValueError:
259
+ highlighted_tokens.append(colored(token, "red"))
260
+
261
+ return " ".join(highlighted_tokens)
262
+
263
+ def clear_stack(self):
264
+ self.stack = []
265
+
266
+ def define_function(self, name, arg_labels, expression):
267
+ self.validate_name(name)
268
+ self.functions[name] = (arg_labels, expression)
269
+
270
+ def validate_name(self, name):
271
+ """
272
+ Validates if a given name is not a reserved word or an operator.
273
+ Raises a ValueError if the name is invalid.
274
+ """
275
+ if name in self.operator or name.lower() in self.reserved_word:
276
+ raise ValueError(f"Invalid name '{name}', it is a reserved word.")
277
+ return name
278
+
279
+ def assign_variable(self, name, value):
280
+ """
281
+ Assigns a value to a variable with the given name.
282
+ """
283
+ self.variables[name] = value
284
+
285
+ def evaluate_function(self, name, *args):
286
+ """
287
+ Evaluates a function with the given name and arguments.
288
+ Returns the result of the evaluation.
289
+ """
290
+ arg_labels, expression = self.functions[name] # 引数のラベルと式を取得
291
+ if len(arg_labels) != len(args):
292
+ raise ValueError(f"Function '{name}' requires {len(arg_labels)} arguments, {len(args)} provided")
293
+ for label, arg in zip(arg_labels, args): # 引数の値を割り当てる
294
+ self.assign_variable(label, arg)
295
+ result = self.evaluate(expression)
296
+ return result[-1] # 評価された関数の結果だけを返す
297
+
298
+ def apply_operator(self, token, stack):
299
+ """
300
+ Applies an operator to the top elements on the stack.
301
+ Modifies the stack in-place.
302
+ """
303
+ n_args = self.get_n_args_for_operator(token)
304
+ if n_args is None:
305
+ raise ValueError(f"Unknown operator '{token}'")
306
+
307
+ if len(stack) < n_args:
308
+ raise ValueError(f"Not enough operands for operator '{token}'")
309
+
310
+ args = [stack.pop() for _ in range(n_args)]
311
+ args.reverse() # 引数の順序を逆にする
312
+
313
+ ans = self.operator[token](*args)
314
+ if token in {"exec", "delete", "pick"}: # このコマンド実行時は戻り値NoneをStackしない
315
+ return
316
+ elif token == "pop": # popの場合は戻り値を保存
317
+ self.last_pop = ans
318
+ else:
319
+ stack.append(ans) # stackは参照渡し
320
+
321
+ def evaluate(self, expression, stack=None):
322
+ """
323
+ Evaluates a given RPN expression.
324
+ Returns the result of the evaluation.
325
+ """
326
+ if stack is None:
327
+ stack = []
328
+
329
+ # tokens = expression.split()
330
+ tokens = shlex.split(expression)
331
+ for token in tokens:
332
+ # token: (str)
333
+ if token in self.operator:
334
+ self.apply_operator(token, stack)
335
+ elif token == "=>":
336
+ continue
337
+ elif token == "last_pop": # popコマンドでpopした値
338
+ stack.append(self.last_pop)
339
+ elif token in self.variables:
340
+ stack.append(self.variables[token]) # 定数をスタックにプッシュ
341
+ elif token in self.functions:
342
+ if len(stack) < len(self.functions[token][0]):
343
+ raise ValueError(f"Not enough arguments for function '{token}'")
344
+ args = [stack.pop() for _ in range(len(self.functions[token][0]))][::-1]
345
+ stack.append(self.evaluate_function(token, *args))
346
+ else:
347
+ try:
348
+ # stack.append(float(token))
349
+ stack.append(input_to_str_or_int_or_float(token))
350
+ except ValueError:
351
+ raise ValueError(f"Invalid token '{token}'")
352
+
353
+ return stack
354
+
355
+ def process_function_definition(self, expression):
356
+ tokens = expression.split()
357
+ name = tokens[tokens.index("=>") - 1]
358
+ self.validate_name(name)
359
+ arg_labels = tokens[:tokens.index("=>") - 1] # 引数のラベルを取得
360
+ function_expression = " ".join(tokens[tokens.index("=>") + 1:])
361
+ if name in self.operator:
362
+ raise ValueError(f"Invalid function name '{name}'")
363
+ self.define_function(name, arg_labels, function_expression) # 引数を追加
364
+ # return colored(f"Function '{name}' defined", "magenta")
365
+ highlighted_function = self.highlight_syntax(function_expression)
366
+ return colored(f"Function '{name}' defined: ", "magenta") + highlighted_function
367
+
368
+ def process_variable_assignment(self, expression):
369
+ name, value_expression = [s.strip() for s in expression.split("=", 1)]
370
+ print(name, value_expression)
371
+ if name in self.operator:
372
+ raise ValueError(f"Invalid variable name '{name}'")
373
+ self.validate_name(name)
374
+ value = self.evaluate(value_expression)[0]
375
+ self.assign_variable(name, value)
376
+ # return colored(f"Variable '{name}' assigned with value {value}", "blue")
377
+ highlighted_value_expression = self.highlight_syntax(value_expression)
378
+ return colored(f"Variable '{name}' assigned with value {value}: ", "blue") + highlighted_value_expression
379
+
380
+ def process_expression(self, expression):
381
+ if "=>" in expression: # 関数定義
382
+ return self.process_function_definition(expression)
383
+ elif re.search(r"\b\w+\s*=(?!=)", expression): # 代入処理
384
+ return self.process_variable_assignment(expression)
385
+ else: # RPN式の評価と結果の表示
386
+ ans = self.evaluate(expression, stack=self.stack)
387
+ return colored(f"{ans}", "green")
388
+
389
+
390
+ class ExecutionMode:
391
+ def __init__(self, rpn_calculator: Stacker):
392
+ self.rpn_calculator = rpn_calculator
393
+ self._operator_key = list(self.rpn_calculator.operator.keys())
394
+ self._variable_key = list(self.rpn_calculator.variables.keys())
395
+ self._reserved_word = copy.deepcopy(self.rpn_calculator.reserved_word)
396
+ self._reserved_word = (self._reserved_word + self._operator_key + self._variable_key)
397
+ self.completer = WordCompleter(self._reserved_word)
398
+
399
+ def get_multiline_input(prompt=""):
400
+ lines = []
401
+ while True:
402
+ line = input(prompt)
403
+ if line.endswith("\\"):
404
+ line = line[:-1] # バックスラッシュを取り除く
405
+ lines.append(line)
406
+ prompt = "" # 2行目以降のプロンプトは空にする
407
+ else:
408
+ lines.append(line)
409
+ break
410
+ return "\n".join(lines)
411
+
412
+ def run(self):
413
+ raise NotImplementedError("Subclasses must implement the 'run' method")
414
+
415
+
416
+ class InteractiveMode(ExecutionMode):
417
+ def run(self):
418
+ line_count = 0
419
+ while True:
420
+ try:
421
+ expression = prompt(
422
+ f"stacker:{line_count}> ",
423
+ history=FileHistory(history_file_path),
424
+ completer=self.completer,
425
+ )
426
+
427
+ # ダブルコーテーションまたはシングルコーテーションで始まる入力が閉じられるまで継続する処理
428
+ while (
429
+ (expression.startswith('"') and expression.count('"') % 2 != 0) or
430
+ (expression.startswith("'") and expression.count("'") % 2 != 0)
431
+ ):
432
+ next_line = input(f"stacker:{line_count}> ")
433
+ expression += "\n" + next_line
434
+
435
+ if expression.lower() == "exit":
436
+ break
437
+ if expression.lower() == "help":
438
+ show_help()
439
+ continue
440
+ if expression.lower() == "about":
441
+ show_about()
442
+ continue
443
+ if expression.lower() == "delete_history":
444
+ delete_history()
445
+ continue
446
+ if expression.lower() == "clear":
447
+ self.rpn_calculator.clear_stack()
448
+ continue
449
+
450
+ stack_str = self.rpn_calculator.process_expression(expression)
451
+ print(stack_str) # stackを全表示
452
+
453
+ except Exception as e:
454
+ print(colored(f"[ERROR]: {e}", "red"))
455
+ # print("Please enter a valid RPN expression, variable assignment, or function definition.")
456
+
457
+ line_count += 1
458
+
459
+
460
+ class ScriptMode(ExecutionMode):
461
+ def __init__(self, rpn_calculator, filename):
462
+ super().__init__(rpn_calculator)
463
+ self.filename = filename
464
+
465
+ def run(self):
466
+ with open(self.filename, 'r') as file:
467
+ for line in file:
468
+ line = line.strip()
469
+ if not line or line.startswith("#"):
470
+ continue
471
+
472
+ try:
473
+ result = self.rpn_calculator.process_expression(line)
474
+ print(result)
475
+ except Exception as e:
476
+ print(f"Error: {e}")
477
+
478
+
479
+ def main():
480
+ show_top()
481
+
482
+ rpn_calculator = Stacker()
483
+
484
+ # 機能拡張予定
485
+ # if len(sys.argv) > 1: # 追加
486
+ # script_filename = sys.argv[1]
487
+ # # スクリプトモードの実行
488
+ # script_mode = ScriptMode(rpn_calculator, script_filename)
489
+ # script_mode.run()
490
+ # else:
491
+ # # インタラクティブモードの実行
492
+ # interactive_mode = InteractiveMode(rpn_calculator)
493
+ # interactive_mode.run()
494
+
495
+ # インタラクティブモードの実行
496
+ interactive_mode = InteractiveMode(rpn_calculator)
497
+ interactive_mode.run()
498
+
499
+
500
+ if __name__ == "__main__":
501
+ main()
stacker/test.py ADDED
@@ -0,0 +1,102 @@
1
+ import cmath
2
+ import math
3
+ import unittest
4
+
5
+ from stacker import Stacker
6
+
7
+
8
+ def cpow(x1, x2):
9
+ return cmath.exp(x2 * cmath.log(x1))
10
+
11
+
12
+ class TestStacker(unittest.TestCase):
13
+ def setUp(self):
14
+ self.stacker = Stacker()
15
+
16
+ def test_arithmetic_operations(self):
17
+ test_cases = [
18
+ ("2 3 +", 5),
19
+ ("10 3 -", 7),
20
+ ("4 6 *", 24),
21
+ ("12 4 /", 3),
22
+ ("7 2 //", 3),
23
+ ("9 2 %", 1),
24
+ ("5 neg", -5),
25
+ ("-3 abs", 3),
26
+ ("3 2 ^", 9),
27
+ ("3 exp", math.exp(3)),
28
+ ("2 log", math.log(2)),
29
+ ("30 radians sin", math.sin(math.radians(30))),
30
+ ("45 radians cos", math.cos(math.radians(45))),
31
+ ("60 radians tan", math.tan(math.radians(60))),
32
+ ("5 float", 5.0),
33
+ ("3.14 int", 3),
34
+ ("1 1 ==", True),
35
+ ("1 0 !=", True),
36
+ ("1 2 <", True),
37
+ ("3 3 <=", True),
38
+ ("2 1 >", True),
39
+ ("3 3 >=", True),
40
+ ("true false and", False),
41
+ ("true false or", True),
42
+ ("true not", False),
43
+ ("3 2 band", 3 & 2),
44
+ ("3 2 bor", 3 | 2),
45
+ ("3 2 bxor", 3 ^ 2),
46
+ ("4 2 gcd", math.gcd(4, 2)),
47
+ ("4 log10", math.log10(4)),
48
+ ("4 log2", math.log2(4)),
49
+ ("4 !", math.factorial(4)),
50
+ ("9 sqrt", math.sqrt(9)),
51
+ ("3.2 ceil", math.ceil(3.2)),
52
+ ("3.8 floor", math.floor(3.8)),
53
+ ("3.5 round", round(3.5)),
54
+ ("3.51 1 roundn", round(3.51, 1)),
55
+ # Add complex number test cases
56
+ ("1+2j 2+3j +", complex(1, 2) + complex(2, 3)),
57
+ ("1+2j 2+3j -", complex(1, 2) - complex(2, 3)),
58
+ ("1+2j 2+3j *", complex(1, 2) * complex(2, 3)),
59
+ ("1+2j 2+3j /", complex(1, 2) / complex(2, 3)),
60
+ ("1+2j 2 ^", cpow(complex(1, 2), 2)),
61
+ ("1+2j exp", cmath.exp(complex(1, 2))),
62
+ ("1+2j log", cmath.log(complex(1, 2))),
63
+ ("1+2j sin", cmath.sin(complex(1, 2))),
64
+ ("1+2j cos", cmath.cos(complex(1, 2))),
65
+ ("1+2j tan", cmath.tan(complex(1, 2))),
66
+ ("1+2j sqrt", cmath.sqrt(complex(1, 2))),
67
+ ("1+2j sinh", cmath.sinh(complex(1, 2))),
68
+ ("1+2j cosh", cmath.cosh(complex(1, 2))),
69
+ ("1+2j tanh", cmath.tanh(complex(1, 2))),
70
+ ("1+2j asin", cmath.asin(complex(1, 2))),
71
+ ("1+2j acos", cmath.acos(complex(1, 2))),
72
+ ("1+2j atan", cmath.atan(complex(1, 2))),
73
+ ("1+2j asinh", cmath.asinh(complex(1, 2))),
74
+ ("1+2j acosh", cmath.acosh(complex(1, 2))),
75
+ ("1+2j atanh", cmath.atanh(complex(1, 2))),
76
+ ]
77
+
78
+ for expression, expected in test_cases:
79
+ self.stacker.stack.clear()
80
+ self.stacker.process_expression(expression)
81
+ self.assertEqual(self.stacker.stack[-1], expected)
82
+
83
+ # for expression, expected in test_cases:
84
+ # self.stacker.stack.clear()
85
+ # self.stacker.process_expression(expression)
86
+ # self.assertAlmostEqual(self.stacker.stack[-1], expected)
87
+ print("!")
88
+
89
+ def test_variable_assignment(self):
90
+ self.stacker.stack.clear()
91
+ self.stacker.process_expression("a = 5")
92
+ self.assertEqual(self.stacker.variables["a"], 5)
93
+
94
+ def test_function_definition_and_call(self):
95
+ self.stacker.stack.clear()
96
+ self.stacker.process_expression("x square => x x *")
97
+ self.stacker.process_expression("4 square")
98
+ self.assertEqual(self.stacker.stack[-1], 16)
99
+
100
+
101
+ if __name__ == "__main__":
102
+ unittest.main()