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 +446 -0
- EGG-INFO/SOURCES.txt +15 -0
- EGG-INFO/dependency_links.txt +1 -0
- EGG-INFO/entry_points.txt +2 -0
- EGG-INFO/not-zip-safe +1 -0
- EGG-INFO/requires.txt +1 -0
- EGG-INFO/top_level.txt +1 -0
- stacker/__init__.py +7 -0
- stacker/__pycache__/__init__.cpython-310.pyc +0 -0
- stacker/__pycache__/stacker.cpython-310.pyc +0 -0
- stacker/__pycache__/test.cpython-310.pyc +0 -0
- stacker/data/about.txt +9 -0
- stacker/data/help.txt +30 -0
- stacker/data/top.txt +6 -0
- stacker/stacker.py +501 -0
- stacker/test.py +102 -0
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
|
+
|
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
|
Binary file
|
|
Binary file
|
|
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
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()
|