mirror of
https://codeberg.org/scip/rpnc.git
synced 2025-12-17 04:21:01 +01:00
Compare commits
92 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 328d08cf95 | |||
| 70b455a19f | |||
| 326b45b838 | |||
|
|
3e490a9fb5 | ||
| 047920b665 | |||
|
|
60df7086a6 | ||
|
|
1f0e4626a9 | ||
|
|
1a07ccc812 | ||
|
|
ee97c05443 | ||
| cc11f923b4 | |||
| d449b4bd1f | |||
| 0688d6b213 | |||
|
|
06aad0649b | ||
|
|
ed69fbeeaa | ||
|
|
0bc23be919 | ||
| 92cbc0f8dc | |||
| fa93b16d02 | |||
| 15c40583a2 | |||
| d7368374b6 | |||
| 6094f480f1 | |||
| fd17211a53 | |||
| 1f96e99da2 | |||
| f977b56815 | |||
| d430a45384 | |||
| 433c5ede91 | |||
|
|
b77ef061e6 | ||
|
|
2a5e70279e | ||
|
|
6c56ed9508 | ||
| ff76137986 | |||
| e5dfad1e35 | |||
| 43fcf43d1f | |||
| 3a9d753720 | |||
| 5afe1275bc | |||
|
|
41b38191a5 | ||
| 8f2b6955ff | |||
| 9b244fc170 | |||
| e4b2a4d6ea | |||
| 3ee4d4181a | |||
| 1a1670076a | |||
| 7ccb05558f | |||
|
|
b38b431d29 | ||
| 62188dda0c | |||
| 6a2a501e48 | |||
|
|
e81be12b19 | ||
| 222dc3a734 | |||
| 49e01565b9 | |||
| e4a8af9b5b | |||
| ac9d08d6fc | |||
|
|
cb774b3b80 | ||
|
|
846b3e63fc | ||
|
|
5557ad5f99 | ||
|
|
d2db420837 | ||
|
|
b4f53d2dd6 | ||
| ec4d86f727 | |||
| 4c6caa7114 | |||
|
|
252e7eb8d9 | ||
|
|
416c163d94 | ||
| d93fbe33dc | |||
|
|
59241932e0 | ||
|
|
127483eea1 | ||
| 4846691c46 | |||
|
|
2f56761bf1 | ||
|
|
0782b0920b | ||
| 40c4cf0e45 | |||
|
|
b13fbc63e3 | ||
| bacbfcc517 | |||
| b91e024569 | |||
| a6f8a0fdbe | |||
| 7b656c492a | |||
| 7d0443ce4b | |||
|
|
a964a99f3d | ||
|
|
31a0ddd547 | ||
|
|
fa5f8dcb3b | ||
|
|
64e66e9d7b | ||
|
|
23a4d87514 | ||
|
|
2ce8cc7a7e | ||
|
|
e963a770a7 | ||
|
|
ad2d9d98d6 | ||
|
|
bb49cb7626 | ||
|
|
9441be35ef | ||
|
|
dac5c0967a | ||
|
|
b5430403fd | ||
| 2b79f3f9ca | |||
| 11753fc984 | |||
| 5adc3b30ab | |||
| 56a4000b67 | |||
| c4c60651d1 | |||
| 5189d351c6 | |||
| 3649408d17 | |||
| a0c0a27a35 | |||
| 052a9ae4d0 | |||
| 48154ce6b1 |
10
.github/dependabot.yml
vendored
Normal file
10
.github/dependabot.yml
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
|
- package-ecosystem: "github-actions"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "monthly"
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
releases
|
releases
|
||||||
rpn
|
rpn
|
||||||
rpn.1
|
rpn.1
|
||||||
|
coverage.out
|
||||||
|
|||||||
79
Makefile
79
Makefile
@@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
# Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
#
|
|
||||||
# no need to modify anything below
|
|
||||||
tool = rpn
|
|
||||||
VERSION = $(shell grep VERSION main.go | head -1 | cut -d '"' -f2)
|
|
||||||
archs = darwin freebsd linux windows
|
|
||||||
PREFIX = /usr/local
|
|
||||||
UID = root
|
|
||||||
GID = 0
|
|
||||||
HAVE_POD := $(shell pod2text -h 2>/dev/null)
|
|
||||||
|
|
||||||
all: $(tool).1 $(tool).go buildlocal
|
|
||||||
|
|
||||||
%.1: %.pod
|
|
||||||
ifdef HAVE_POD
|
|
||||||
pod2man -c "User Commands" -r 1 -s 1 $*.pod > $*.1
|
|
||||||
endif
|
|
||||||
|
|
||||||
%.go: %.pod
|
|
||||||
ifdef HAVE_POD
|
|
||||||
echo "package main" > $*.go
|
|
||||||
echo >> $*.go
|
|
||||||
echo "var manpage = \`" >> $*.go
|
|
||||||
pod2text $*.pod >> $*.go
|
|
||||||
echo "\`" >> $*.go
|
|
||||||
|
|
||||||
echo "var usage = \`" >> $*.go
|
|
||||||
awk '/SYNOPS/{f=1;next} /DESCR/{f=0} f' $*.pod | sed 's/^ //' >> $*.go
|
|
||||||
echo "\`" >> $*.go
|
|
||||||
endif
|
|
||||||
|
|
||||||
buildlocal:
|
|
||||||
CGO_LDFLAGS='-static' go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o $(tool)
|
|
||||||
|
|
||||||
install: buildlocal
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
|
|
||||||
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
|
|
||||||
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm -rf $(tool) coverage.out
|
|
||||||
|
|
||||||
test:
|
|
||||||
go test -v ./...
|
|
||||||
bash t/test.sh
|
|
||||||
|
|
||||||
singletest:
|
|
||||||
@echo "Call like this: ''make singletest TEST=TestPrepareColumns MOD=lib"
|
|
||||||
go test -run $(TEST) github.com/tlinden/rpn/$(MOD)
|
|
||||||
|
|
||||||
cover-report:
|
|
||||||
go test ./... -cover -coverprofile=coverage.out
|
|
||||||
go tool cover -html=coverage.out
|
|
||||||
|
|
||||||
goupdate:
|
|
||||||
go get -t -u=patch ./...
|
|
||||||
|
|
||||||
buildall:
|
|
||||||
./mkrel.sh $(tool) $(VERSION)
|
|
||||||
|
|
||||||
release: buildall
|
|
||||||
gh release create $(VERSION) --generate-notes releases/*
|
|
||||||
50
README.md
50
README.md
@@ -1,4 +1,12 @@
|
|||||||
## Reverse Polish Notation Calculator for the commandline
|
[](https://ci.codeberg.org/repos/15511)
|
||||||
|
[](https://codeberg.org/scip/rpnc/raw/branch/master/LICENSE)
|
||||||
|
[](https://goreportcard.com/report/codeberg.org/scip/rpnc)
|
||||||
|
|
||||||
|
## Programmable command-line calculator using reverse polish notation
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> This app is now being maintained on [Codeberg](https://codeberg.org/scip/rpnc/).
|
||||||
|
|
||||||
|
|
||||||
This is a small commandline calculator which takes its input in
|
This is a small commandline calculator which takes its input in
|
||||||
[reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)
|
[reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)
|
||||||
@@ -6,15 +14,25 @@ form.
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
|
|
||||||
- unlimited stack
|
- unlimited stack
|
||||||
- undo
|
- undo
|
||||||
- various stack manipulation commands
|
- various stack manipulation commands
|
||||||
- basic math operators
|
- basic math operators
|
||||||
- advanced math functions (not yet complete)
|
- advanced math functions (not yet complete)
|
||||||
- provides interactive repl
|
|
||||||
- can be used on the commandline
|
- can be used on the commandline
|
||||||
- can calculate data in batch mode (also from STDIN)
|
- can calculate data in batch mode (also from STDIN)
|
||||||
- extensible with custom LUA functions
|
- extensible with custom LUA functions
|
||||||
|
- provides interactive repl
|
||||||
|
- completion
|
||||||
|
- history
|
||||||
|
- comments (comment character is `#`)
|
||||||
|
- variables
|
||||||
|
- help screen uses comfortable internal pager
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## Working principle
|
## Working principle
|
||||||
|
|
||||||
@@ -65,6 +83,8 @@ DEBUG(012): push to stack: 200.00
|
|||||||
= 200
|
= 200
|
||||||
```
|
```
|
||||||
|
|
||||||
|
For a very good explanation how reverse polish notation and the stack works [watch this video by Prof. Brailsford](https://youtu.be/7ha78yWRDlE?si=9MCp59jAAG8fXavP)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Basically you enter numbers followed by an operator or a
|
Basically you enter numbers followed by an operator or a
|
||||||
@@ -211,10 +231,15 @@ the `register()` function to register your functions to the
|
|||||||
calculator. This function takes these parameters:
|
calculator. This function takes these parameters:
|
||||||
|
|
||||||
- function name
|
- function name
|
||||||
- number of arguments expected (1,2 or -1 allowed), -1 means batch
|
- number of arguments expected (see below)
|
||||||
mode
|
|
||||||
- help text
|
- help text
|
||||||
|
|
||||||
|
Number of expected arguments can be:
|
||||||
|
|
||||||
|
- 0: expect 1 argument but do NOT modify the stack
|
||||||
|
- 1-n: do a singular calculation
|
||||||
|
- -1: batch mode work with all numbers on the stack
|
||||||
|
|
||||||
Please [refer to the lua language
|
Please [refer to the lua language
|
||||||
reference](https://www.lua.org/manual/5.4/) for more details about
|
reference](https://www.lua.org/manual/5.4/) for more details about
|
||||||
LUA.
|
LUA.
|
||||||
@@ -227,7 +252,12 @@ connection to the outside!**
|
|||||||
|
|
||||||
There are multiple ways to install **rpn**:
|
There are multiple ways to install **rpn**:
|
||||||
|
|
||||||
- Go to the [latest release page](https://github.com/tlinden/rpn/releases/latest),
|
- You can use [stew](https://github.com/marwanhawari/stew) to install rpnc:
|
||||||
|
```default
|
||||||
|
stew install tlinden/rpnc
|
||||||
|
```
|
||||||
|
|
||||||
|
- Go to the [latest release page](https://codeberg.org/scip/rpn/releases/),
|
||||||
locate the binary for your operating system and platform.
|
locate the binary for your operating system and platform.
|
||||||
|
|
||||||
Download it and put it into some directory within your `$PATH` variable.
|
Download it and put it into some directory within your `$PATH` variable.
|
||||||
@@ -240,7 +270,7 @@ There are multiple ways to install **rpn**:
|
|||||||
|
|
||||||
- You can also install from source. Issue the following commands in your shell:
|
- You can also install from source. Issue the following commands in your shell:
|
||||||
```
|
```
|
||||||
git clone https://github.com/TLINDEN/rpn.git
|
git clone https://codeberg.org/scip/rpn.git
|
||||||
cd rpn
|
cd rpn
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
@@ -254,9 +284,7 @@ hesitate to ask me about it, I'll add it.
|
|||||||
|
|
||||||
The documentation is provided as a unix man-page. It will be
|
The documentation is provided as a unix man-page. It will be
|
||||||
automatically installed if you install from source. However, you can
|
automatically installed if you install from source. However, you can
|
||||||
read the man-page online:
|
[read the man-page online](https://codeberg.org/scip/rpnc/raw/branch/master/rpn.pod)
|
||||||
|
|
||||||
https://github.com/TLINDEN/rpnc/blob/main/rpn.pod
|
|
||||||
|
|
||||||
Or if you cloned the repository you can read it this way (perl needs
|
Or if you cloned the repository you can read it this way (perl needs
|
||||||
to be installed though): `perldoc rpn.pod`.
|
to be installed though): `perldoc rpn.pod`.
|
||||||
@@ -273,7 +301,7 @@ best way for me to forget to do something.
|
|||||||
|
|
||||||
In order to report a bug, unexpected behavior, feature requests or to
|
In order to report a bug, unexpected behavior, feature requests or to
|
||||||
submit a patch, please open an issue on github:
|
submit a patch, please open an issue on github:
|
||||||
https://github.com/TLINDEN/rpnc/issues.
|
https://codeberg.org/scip/rpnc/issues.
|
||||||
|
|
||||||
## Copyright and license
|
## Copyright and license
|
||||||
|
|
||||||
@@ -285,4 +313,4 @@ T.v.Dein <tom AT vondein DOT org>
|
|||||||
|
|
||||||
## Project homepage
|
## Project homepage
|
||||||
|
|
||||||
https://github.com/TLINDEN/rpnc
|
https://codeberg.org/scip/rpnc
|
||||||
|
|||||||
@@ -1,264 +0,0 @@
|
|||||||
## Reverse Polish Notation Calculator for the commandline
|
|
||||||
|
|
||||||
This is a small commandline calculator which takes its input in
|
|
||||||
[reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation)
|
|
||||||
form.
|
|
||||||
|
|
||||||
It has an unlimited stack, supports various stack manipulation
|
|
||||||
commands, can be used interactively or via a pipe and has a collector
|
|
||||||
mode. It doesn't have any other dependencies than Perl.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Calculate the summary resistance of parallel resistors with 220, 330
|
|
||||||
and 440 Ohm using the following formula:
|
|
||||||
|
|
||||||
1 / (1/R1 + 1/R2 + 1/R3)
|
|
||||||
|
|
||||||
Here's the sample session:
|
|
||||||
|
|
||||||
0 % 1
|
|
||||||
stack 1: 1
|
|
||||||
|
|
||||||
1 % 1
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 1
|
|
||||||
|
|
||||||
2 % 220
|
|
||||||
stack 3: 1
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 220
|
|
||||||
|
|
||||||
3 % /
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 0.00454545454545455
|
|
||||||
|
|
||||||
=> 0.00454545454545455
|
|
||||||
|
|
||||||
2 % 1
|
|
||||||
stack 3: 1
|
|
||||||
stack 2: 0.00454545454545455
|
|
||||||
stack 1: 1
|
|
||||||
|
|
||||||
3 % 330
|
|
||||||
stack 4: 1
|
|
||||||
stack 3: 0.00454545454545455
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 330
|
|
||||||
|
|
||||||
4 % /
|
|
||||||
stack 3: 1
|
|
||||||
stack 2: 0.00454545454545455
|
|
||||||
stack 1: 0.00303030303030303
|
|
||||||
|
|
||||||
=> 0.00303030303030303
|
|
||||||
|
|
||||||
3 % 1
|
|
||||||
stack 4: 1
|
|
||||||
stack 3: 0.00454545454545455
|
|
||||||
stack 2: 0.00303030303030303
|
|
||||||
stack 1: 1
|
|
||||||
|
|
||||||
4 % 440
|
|
||||||
stack 5: 1
|
|
||||||
stack 4: 0.00454545454545455
|
|
||||||
stack 3: 0.00303030303030303
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 440
|
|
||||||
|
|
||||||
5 % /
|
|
||||||
stack 4: 1
|
|
||||||
stack 3: 0.00454545454545455
|
|
||||||
stack 2: 0.00303030303030303
|
|
||||||
stack 1: 0.00227272727272727
|
|
||||||
|
|
||||||
=> 0.00227272727272727
|
|
||||||
|
|
||||||
4 % +
|
|
||||||
stack 3: 1
|
|
||||||
stack 2: 0.00454545454545455
|
|
||||||
stack 1: 0.0053030303030303
|
|
||||||
|
|
||||||
=> 0.0053030303030303
|
|
||||||
|
|
||||||
3 % +
|
|
||||||
stack 2: 1
|
|
||||||
stack 1: 0.00984848484848485
|
|
||||||
|
|
||||||
=> 0.00984848484848485
|
|
||||||
|
|
||||||
2 % /
|
|
||||||
stack 1: 101.538461538462
|
|
||||||
|
|
||||||
=> 101.538461538462
|
|
||||||
|
|
||||||
The *%* character denotes the interactive prompt. What we basically entered was:
|
|
||||||
|
|
||||||
1 1 220 / 1 330 / 1 440 / + + /
|
|
||||||
|
|
||||||
Which translates to:
|
|
||||||
|
|
||||||
1 ((1 / 220) + (1 / 330) + (1 / 440))
|
|
||||||
|
|
||||||
So, you're entering the numbers and operators as you would do on
|
|
||||||
paper. To learn more, refer to the Wikipedia page linked above.
|
|
||||||
|
|
||||||
## Collector mode
|
|
||||||
|
|
||||||
Beside traditional RPN you can also enter a special mode, called
|
|
||||||
*collector mode* by entering the <kbd>(</kbd> command. The collector
|
|
||||||
mode has its own stack (a sub stack) which is independed of the
|
|
||||||
primary stack. Inside this mode you can use all operators, however
|
|
||||||
they work on *ALL* items on the sub stack.
|
|
||||||
|
|
||||||
So, let's compare. If you had in normal RPN mode the following stack:
|
|
||||||
|
|
||||||
3
|
|
||||||
5
|
|
||||||
6
|
|
||||||
|
|
||||||
and then entering the <kbd>+</kbd> operator, the calculator would pop
|
|
||||||
5 and 6 from the stack, add them and push the result 11 back to the
|
|
||||||
stack.
|
|
||||||
|
|
||||||
However, if you are in collector mode with this stack, then all the
|
|
||||||
items would be added, the sub stack would be cleared and the result 14
|
|
||||||
would be added to the primary stack.
|
|
||||||
|
|
||||||
You will leave the collector mode after an operator has been
|
|
||||||
executed. But you can also just leave the collector mode with the
|
|
||||||
command <kbd>)</kbd> leaving the sub stack intact. That is, upon
|
|
||||||
re-entering collector mode at a later time, you'll find the unaltered
|
|
||||||
sub stack of before.
|
|
||||||
|
|
||||||
## Undo
|
|
||||||
|
|
||||||
Every operation which modifies the stack can be reversed by entering
|
|
||||||
the <kbd>u</kbd> command. There's only one level of undo and no redo.
|
|
||||||
|
|
||||||
## Functions
|
|
||||||
|
|
||||||
You can define functions anytime directly on the cli or in a file called
|
|
||||||
`~/.rpnc`. A function has a name (which must not collide with existing
|
|
||||||
functions and commands) and a body of commands.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
f res2vcc 1.22 R1 R2 + R2 / 1 + *
|
|
||||||
|
|
||||||
Which calculates:
|
|
||||||
|
|
||||||
(((R1 + R2) / R2) + 1) * 1.22 = ??
|
|
||||||
|
|
||||||
To use it later, just enter the variables into the stack followed by the
|
|
||||||
function name:
|
|
||||||
|
|
||||||
470
|
|
||||||
220
|
|
||||||
res2vcc
|
|
||||||
=> 2.79
|
|
||||||
|
|
||||||
You can also put the function definition in the config file
|
|
||||||
`~/.rpnc`. Empty lines and lines beginning with `#` will be ignored.
|
|
||||||
|
|
||||||
Another way to define a function is to use perl code directly. The
|
|
||||||
perl code must be a closure string and surrounded by braces. You can
|
|
||||||
access the stack via `@_`. Here's an example:
|
|
||||||
|
|
||||||
f pr { return "1.0 / (" . join(' + ', map { "1.0 / $_"} @_) . ")" }
|
|
||||||
|
|
||||||
This function calculates the parallel resistance of a number of
|
|
||||||
resistors. It adds up all values from the stack. Usage:
|
|
||||||
|
|
||||||
22
|
|
||||||
47
|
|
||||||
330
|
|
||||||
pr
|
|
||||||
=> 41.14
|
|
||||||
|
|
||||||
|
|
||||||
## Using STDIN via a PIPE
|
|
||||||
|
|
||||||
If the commandline includes any operator, commands will be read from
|
|
||||||
STDIN, the result will be printed to STDOUT wihout any decoration and
|
|
||||||
the program will exit. Commands can be separated by whitespace or
|
|
||||||
newline.
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
|
|
||||||
echo "2 2" | rpnc +
|
|
||||||
(echo 2; echo 2) | rpnc +
|
|
||||||
|
|
||||||
Both commands will print 4 to STDOUT.
|
|
||||||
|
|
||||||
|
|
||||||
## Complete list of all supported commands:
|
|
||||||
|
|
||||||
### Stack Management
|
|
||||||
|
|
||||||
* <kbd>s</kbd> show the stack
|
|
||||||
* <kbd>ss</kbd> show the whole stack
|
|
||||||
* <kbd>sc</kbd> clear stack
|
|
||||||
* <kbd>scx</kbd> clear last stack element
|
|
||||||
* <kbd>sr</kbd> reverse the stack
|
|
||||||
* <kbd>srt</kbd> rotate the stack
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
* <kbd>td</kbd> toggle debugging (-d)
|
|
||||||
* <kbd>ts</kbd> toggle display of the stack (-n)
|
|
||||||
|
|
||||||
## Supported mathematical operators:
|
|
||||||
|
|
||||||
* <kbd>+</kbd> add
|
|
||||||
* <kbd>-</kbd> substract
|
|
||||||
* <kbd>/</kbd> divide
|
|
||||||
* <kbd>*</kbd> multiply
|
|
||||||
* <kbd>^</kbd> expotentiate
|
|
||||||
* <kbd>%</kbd> percent
|
|
||||||
* <kbd>%+</kbd> add percent
|
|
||||||
* <kbd>%-</kbd> substract percent
|
|
||||||
* <kbd>%d</kbd> percentual difference
|
|
||||||
* <kbd>&</kbd> bitwise AND
|
|
||||||
* <kbd>|</kbd> bitwise OR
|
|
||||||
* <kbd>x</kbd> bitwise XOR
|
|
||||||
* <kbd>m</kbd> median
|
|
||||||
* <kbd>a</kbd> average
|
|
||||||
* <kbd>v</kbd> pull root (2nd if stack==1)
|
|
||||||
* <kbd>(</kbd> enter collect mode
|
|
||||||
* <kbd>)</kbd> leave collect mode
|
|
||||||
|
|
||||||
## Register Commands
|
|
||||||
|
|
||||||
* <kbd>r</kbd> put element into register
|
|
||||||
* <kbd>rc</kbd> clear register
|
|
||||||
* <kbd>rcx</kbd> clear last register element
|
|
||||||
|
|
||||||
## Various Commands
|
|
||||||
|
|
||||||
* <kbd>u</kbd> undo last operation
|
|
||||||
* <kbd>q</kbd> finish (<kbd>C-d</kbd> works as well)
|
|
||||||
* <kbd>h</kbd> show history of past operations
|
|
||||||
* <kbd>?</kbd> print help
|
|
||||||
|
|
||||||
## Converters
|
|
||||||
|
|
||||||
* <kbd>tl</kbd> gallons to liters
|
|
||||||
* <kbd>tk</kbd> miles to kilometers
|
|
||||||
* <kbd>tm</kbd> yards to meters
|
|
||||||
* <kbd>tc</kbd> inches to centimeters
|
|
||||||
* <kbd>tkb</kbd> bytes to kilobytes
|
|
||||||
* <kbd>tmb</kbd> bytes to megabytes
|
|
||||||
* <kbd>tgb</kbd> bytes to gigabytes
|
|
||||||
* <kbd>ttb</kbd> bytes to terabytes
|
|
||||||
|
|
||||||
## Function Comands
|
|
||||||
|
|
||||||
* <kbd>f NAME CODE</kbd> define a functions (see ab above)
|
|
||||||
* <kbd>fs</kbd> show list of defined functions
|
|
||||||
|
|
||||||
|
|
||||||
## Copyleft
|
|
||||||
|
|
||||||
Copyleft (L) 2019 - Thomas von Dein.
|
|
||||||
Licensed under the terms of the GPL 3.0.
|
|
||||||
679
archived/rpnc
679
archived/rpnc
@@ -1,679 +0,0 @@
|
|||||||
#!/usr/bin/perl
|
|
||||||
|
|
||||||
use Term::ReadLine;
|
|
||||||
use Data::Dumper;
|
|
||||||
use Getopt::Long;
|
|
||||||
use Data::Dumper;
|
|
||||||
|
|
||||||
use strict;
|
|
||||||
use warnings;
|
|
||||||
|
|
||||||
my (@stack, @substack, @backup, @subbackup, @hist, @register);
|
|
||||||
my $term = Term::ReadLine->new('rpn calc');
|
|
||||||
my $debug = 0;
|
|
||||||
my $showstack = 1;
|
|
||||||
my $mgt = 0;
|
|
||||||
my $tty = 1;
|
|
||||||
my $VERSION = '1.10';
|
|
||||||
my $sub = 0;
|
|
||||||
my $maxstack = 10;
|
|
||||||
my $maxreg = 5;
|
|
||||||
my $silent = 1;
|
|
||||||
my $op;
|
|
||||||
|
|
||||||
# management commands, always lower case letters or words
|
|
||||||
my %commands = (
|
|
||||||
# stack commands
|
|
||||||
s => sub { $mgt = 1; dumpstack(); $mgt = 0; },
|
|
||||||
sa => sub { $mgt = 1; dumpstack(1); $mgt = 0;},
|
|
||||||
sc => sub { clearstack(); },
|
|
||||||
scx => sub { clearstack(1); dumpstack(); },
|
|
||||||
sr => sub { reversestack(); },
|
|
||||||
srt => sub { rotatestack(); },
|
|
||||||
# collector
|
|
||||||
'(' => sub { $sub = 1 },
|
|
||||||
')' => sub { stack2sub(); },
|
|
||||||
# register stuff
|
|
||||||
r => sub { last_to_reg(); dumpstack(); },
|
|
||||||
rcx => sub { clearreg(1); dumpstack(); },
|
|
||||||
rc => sub { clearreg(); },
|
|
||||||
# main
|
|
||||||
'?' => sub { help(); },
|
|
||||||
u => sub { undo(); dumpstack(); },
|
|
||||||
h => sub { showhist(); },
|
|
||||||
q => sub { exit; },
|
|
||||||
# toggles
|
|
||||||
td => sub { $debug ^= 1; },
|
|
||||||
ts => sub { $showstack ^= 1; },
|
|
||||||
# functions
|
|
||||||
fs => sub { showfuncs(); },
|
|
||||||
);
|
|
||||||
|
|
||||||
# executed 1:1, or aliased
|
|
||||||
my %alias = qw(^ ** x ^ < << > >> + + - - / / * * & & | |);
|
|
||||||
|
|
||||||
# holds user functions
|
|
||||||
my %custom;
|
|
||||||
|
|
||||||
# hand coded functions
|
|
||||||
my %func = (
|
|
||||||
'%' => sub {
|
|
||||||
# X % of Y
|
|
||||||
my ($a, $b) = getlast(2);
|
|
||||||
if (defined $b) {
|
|
||||||
return "($a / 100) * $b";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'%d' => sub {
|
|
||||||
# percentual difference
|
|
||||||
my ($a, $b) = getlast(2);
|
|
||||||
if (defined $b) {
|
|
||||||
return "(($a - $b) / $b) * 100"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'%+' => sub {
|
|
||||||
# Y + (X $ of Y)
|
|
||||||
my ($a, $b) = getlast(2);
|
|
||||||
if (defined $b) {
|
|
||||||
return "$a + (($a / 100) * $b)";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'%-' => sub {
|
|
||||||
# Y - (X $ of Y)
|
|
||||||
my ($a, $b) = getlast(2);
|
|
||||||
if (defined $b) {
|
|
||||||
return "$a - (($a / 100) * $b)";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'v' => sub {
|
|
||||||
# square root
|
|
||||||
my ($a) = getlast(1);
|
|
||||||
if (defined $a) {
|
|
||||||
return "$a ** (1 / 2)";
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'm' => sub {
|
|
||||||
# median
|
|
||||||
my @values = getlast(2); # we need 2 or all in sub mode
|
|
||||||
if (scalar @values >= 2) {
|
|
||||||
my $c = $#values;
|
|
||||||
if (scalar @values % 2 == 0) {
|
|
||||||
# even
|
|
||||||
return "((sort qw(@values))[$c / 2] + (sort qw(@values))[($c / 2) + 1]) / 2";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# uneven
|
|
||||||
return "(sort qw(@values))[$c / 2]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "median only possible with 2 or more values\n";
|
|
||||||
undo();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
'a' => sub {
|
|
||||||
# average
|
|
||||||
my @values = getlast(2); # we need 2 or all in sub mode
|
|
||||||
if (scalar @values > 1) {
|
|
||||||
return "(" . join(' + ', @values) . ") / " . scalar @values;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "average only possible with 2 or more values\n";
|
|
||||||
undo();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
# converters:
|
|
||||||
# gallons to liters
|
|
||||||
'tl' => sub { return convert("* 3.785") },
|
|
||||||
# yards to meters
|
|
||||||
'tm' => sub { return convert("* 91.44") },
|
|
||||||
# miles to kilometers
|
|
||||||
'tk' => sub { return convert("* 1.609") },
|
|
||||||
# inches to cm
|
|
||||||
'tc' => sub { return convert("* 2.54") },
|
|
||||||
# to 'bytes
|
|
||||||
'tkb' => sub { return convert("/ 1000") },
|
|
||||||
'tmb' => sub { return convert("/ 1000 / 1000") },
|
|
||||||
'tgb' => sub { return convert("/ 1000 / 1000 / 1000") },
|
|
||||||
'ttb' => sub { return convert("/ 1000 / 1000 / 1000 / 1000") },
|
|
||||||
);
|
|
||||||
|
|
||||||
# math constants, always upper case letters, usable via eval{}
|
|
||||||
use constant PI => 3.141592653589793;
|
|
||||||
use constant V2 => 1.414213562373095;
|
|
||||||
use constant V3 => 1.732050807568877;
|
|
||||||
|
|
||||||
# handle command line
|
|
||||||
my ($o_h, $o_v, $o_s);
|
|
||||||
Getopt::Long::Configure( qw(no_ignore_case));
|
|
||||||
if (! GetOptions (
|
|
||||||
"version|v" => \$o_v,
|
|
||||||
"help|h" => \$o_h,
|
|
||||||
"debug|d" => \$debug,
|
|
||||||
"nostack|n" => \$o_s
|
|
||||||
) ) {
|
|
||||||
help();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($o_v) {
|
|
||||||
print "$0 version $VERSION\n";
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($o_h) {
|
|
||||||
help();
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($o_s) {
|
|
||||||
$showstack = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
# load config, if any
|
|
||||||
if (-s "$ENV{HOME}/.rpnc") {
|
|
||||||
if (open RC, "< $ENV{HOME}/.rpnc") {
|
|
||||||
while (<RC>) {
|
|
||||||
chomp();
|
|
||||||
next if (/^\s*#/ || /^\s*$/);
|
|
||||||
looptokenize($_);
|
|
||||||
}
|
|
||||||
close RC;
|
|
||||||
$silent = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# run in commandline mode?
|
|
||||||
$op = shift;
|
|
||||||
if ($op) {
|
|
||||||
$tty = 0;
|
|
||||||
while (<STDIN>) {
|
|
||||||
chomp;
|
|
||||||
push @stack, split /\s\s*/;
|
|
||||||
}
|
|
||||||
print calc($op);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
# else: run interactively
|
|
||||||
# main
|
|
||||||
my $OUT = $term->OUT || \*STDOUT;
|
|
||||||
while ( defined ($_ = $term->readline(prompt())) ) {
|
|
||||||
looptokenize($_);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
1;
|
|
||||||
|
|
||||||
|
|
||||||
# converter helper
|
|
||||||
sub convert {
|
|
||||||
my $code = shift;
|
|
||||||
my ($a) = getlast(1);
|
|
||||||
if (defined $a) {
|
|
||||||
return "$a $code";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub looptokenize {
|
|
||||||
# disassemble user input into tokens
|
|
||||||
my $tokens = shift;
|
|
||||||
|
|
||||||
if ($tokens =~ /^f\s/) {
|
|
||||||
# function definition
|
|
||||||
defun($tokens);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
foreach my $tok (split /\s+/, $tokens) {
|
|
||||||
if ($tok =~ /^-?[A-Z\.\d]+?$/) {
|
|
||||||
# number or register fetch
|
|
||||||
if ($tok =~ /^R(\d+?)/) {
|
|
||||||
# fetch number from register $1 and put it to stack
|
|
||||||
my $r = getreg($1);
|
|
||||||
if ($r) {
|
|
||||||
pushstack($r);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "invalid register index!\n";
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# put number to stsack
|
|
||||||
pushstack($tok);
|
|
||||||
}
|
|
||||||
dumpstack();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# operator or command, execute
|
|
||||||
if (exists $commands{$tok}) {
|
|
||||||
cmd($tok);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print calc($tok);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub cmd {
|
|
||||||
my $c = shift;
|
|
||||||
|
|
||||||
if (exists $commands{$c}) {
|
|
||||||
my $sub = $commands{$c};
|
|
||||||
&$sub;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "unknown command '$c'!\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub showhist {
|
|
||||||
foreach my $entry (@hist) {
|
|
||||||
printf "History: %10s = %s\n", $entry->[0], $entry->[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub clearstack {
|
|
||||||
my $one = shift;
|
|
||||||
|
|
||||||
backup();
|
|
||||||
|
|
||||||
if ($sub) {
|
|
||||||
if ($one) {
|
|
||||||
pop @substack;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@substack = ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ($one) {
|
|
||||||
pop @stack;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@stack = ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub reversestack {
|
|
||||||
backup();
|
|
||||||
if ($sub) {
|
|
||||||
@substack = reverse @substack;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@stack = reverse @stack;
|
|
||||||
}
|
|
||||||
dumpstack();
|
|
||||||
}
|
|
||||||
|
|
||||||
sub rotatestack {
|
|
||||||
backup();
|
|
||||||
if ($sub) {
|
|
||||||
my $f = shift @substack;
|
|
||||||
@substack = (@substack, $f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
my $f = shift @stack;
|
|
||||||
@stack = (@stack, $f);
|
|
||||||
}
|
|
||||||
dumpstack();
|
|
||||||
}
|
|
||||||
|
|
||||||
sub pushstack {
|
|
||||||
my $num = shift;
|
|
||||||
if ($num) {
|
|
||||||
if ($num =~ /^\./) {
|
|
||||||
$num = '0' . $num;
|
|
||||||
}
|
|
||||||
if ($sub) {
|
|
||||||
push @substack, $num;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
push @stack, $num;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub dumpstack {
|
|
||||||
if (! $showstack && !$mgt) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $max = shift;
|
|
||||||
my $x = ' ';
|
|
||||||
my $prefix = 'stack';
|
|
||||||
my @all;
|
|
||||||
|
|
||||||
if ($sub) {
|
|
||||||
@all = @substack;
|
|
||||||
$prefix = 'collectorstack';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@all = @stack;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $abs = scalar @all;
|
|
||||||
if (! $max && $abs > $maxstack) {
|
|
||||||
my $min = $max - ($max * 2);
|
|
||||||
@all = @all[$min .. -1];
|
|
||||||
printf "%s [..]\n", $prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (@register) {
|
|
||||||
my $p = 1;
|
|
||||||
foreach my $n (@register) {
|
|
||||||
printf "register R%d: %s\n", $p++, $n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
print "\n";
|
|
||||||
|
|
||||||
my $p = scalar @all;
|
|
||||||
foreach my $n (@all) {
|
|
||||||
$x = 'X' if($p == 1);
|
|
||||||
printf "%s %s %4d: %s\n", $prefix, $x, $p--, $n;
|
|
||||||
}
|
|
||||||
print "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
sub undo {
|
|
||||||
if ($sub) {
|
|
||||||
@substack = @subbackup;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@stack = @backup;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub backup {
|
|
||||||
if ($sub) {
|
|
||||||
@subbackup = @substack;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@backup = @stack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub getlast {
|
|
||||||
# return and remove last 1, 2 or all elements of current stack
|
|
||||||
my $request = shift;
|
|
||||||
|
|
||||||
my @all = ();
|
|
||||||
|
|
||||||
backup();
|
|
||||||
|
|
||||||
if ($sub) {
|
|
||||||
# ignore request count
|
|
||||||
@all = @substack;
|
|
||||||
@substack = ();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (@stack) {
|
|
||||||
if (scalar @stack == 1) {
|
|
||||||
if ($request > 1) {
|
|
||||||
print "At least $request variables must be on the stack!\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@all = pop @stack;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elsif (scalar @stack >= 2) {
|
|
||||||
@all = splice(@stack, -1 * $request, $request);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "Please enter one or more numbers to operate on!\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return @all;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub getreg {
|
|
||||||
# fetch $n'th element from register
|
|
||||||
my $n = shift;
|
|
||||||
if ($n <= scalar @register) {
|
|
||||||
return $register[$n-1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub last_to_reg {
|
|
||||||
# put last stack element to register
|
|
||||||
my $n;
|
|
||||||
if ($sub) {
|
|
||||||
if (@substack) {
|
|
||||||
$n = $substack[-1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (@stack) {
|
|
||||||
$n = $stack[-1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($n) {
|
|
||||||
if (scalar @register == $maxreg) {
|
|
||||||
shift @register;
|
|
||||||
}
|
|
||||||
push @register, $n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub clearreg {
|
|
||||||
my $one = shift;
|
|
||||||
|
|
||||||
if ($one) {
|
|
||||||
pop @register;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
@register = ();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub stack2sub {
|
|
||||||
if (! $sub && scalar @substack == 0 && scalar @stack > 1) {
|
|
||||||
# not in collector mode, empty substack, move stack to substack, enter collect
|
|
||||||
backup();
|
|
||||||
@substack = @stack;
|
|
||||||
@stack = ();
|
|
||||||
$sub = 1;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# leave collector mode
|
|
||||||
$sub = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub prompt {
|
|
||||||
my $count;
|
|
||||||
my $prompt;
|
|
||||||
|
|
||||||
if ($sub) {
|
|
||||||
$count = scalar @substack;
|
|
||||||
$prompt = '%--(';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$count = scalar @stack;
|
|
||||||
$prompt = '%';
|
|
||||||
}
|
|
||||||
|
|
||||||
return sprintf "%3d %s ", $count, $prompt;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub calc {
|
|
||||||
my $op = shift;
|
|
||||||
my $res;
|
|
||||||
my $code;
|
|
||||||
|
|
||||||
if (exists $alias{$op}) {
|
|
||||||
my @last = getlast(2);
|
|
||||||
$op = $alias{$op};
|
|
||||||
$code = join(" $op ", @last);
|
|
||||||
}
|
|
||||||
elsif (exists $func{$op}) {
|
|
||||||
my $sub = $func{$op};
|
|
||||||
$code = &$sub();
|
|
||||||
return unless $code;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "syntax error or unknown command ($op)!\n";
|
|
||||||
undo();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
# execute
|
|
||||||
eval "\$res = $code";
|
|
||||||
|
|
||||||
if ($@) {
|
|
||||||
# error, reset stack
|
|
||||||
print "Syntax error: $@, resetting stack\n";
|
|
||||||
undo();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
push @stack, $res;
|
|
||||||
$sub = 0;
|
|
||||||
|
|
||||||
if ($debug) {
|
|
||||||
print "DEBUG: $code = $res\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($tty) {
|
|
||||||
dumpstack();
|
|
||||||
push @hist, [$res, $code];
|
|
||||||
return "=> $res\n\n";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return "$res\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sub defun {
|
|
||||||
# define a function, use N1 .. NN as function arguments
|
|
||||||
my $code = shift;
|
|
||||||
my ($op, $name, @tokens) = split /\s\s*/, $code;
|
|
||||||
|
|
||||||
if ($name !~ /^[a-zA-Z0-9_]+$/) {
|
|
||||||
print "invalid function name (a-z0-9_)!\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! exists $custom{$name}) {
|
|
||||||
# no need to check twice and overwriting of custom function must be legal
|
|
||||||
if (grep {$name eq $_} keys %commands) {
|
|
||||||
print "reserved function name (command)!\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (grep {$name eq $_} keys %func) {
|
|
||||||
print "reserved function name (function)!\n";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$custom{$name} = "@tokens";
|
|
||||||
|
|
||||||
if ($custom{$name} =~ /^\{.*\}$/) {
|
|
||||||
# perl code
|
|
||||||
$func{$name} = sub { return eval "@tokens" };
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# rpnc code
|
|
||||||
$func{$name} = sub {
|
|
||||||
my $max = scalar @_;
|
|
||||||
my @args = reverse(@_);
|
|
||||||
|
|
||||||
# replace N1..NN with actual stack items
|
|
||||||
my @body;
|
|
||||||
foreach my $item (@tokens) {
|
|
||||||
if ($item =~ /^([A-Z])(\d+)$/) {
|
|
||||||
my $letter = $1;
|
|
||||||
my $i = $2;
|
|
||||||
if ($i <= $max) {
|
|
||||||
push @body, $args[$i-1];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
print "undefined variable ${letter}${i}!\n";
|
|
||||||
push @body, 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
push @body, $item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# execute @body
|
|
||||||
looptokenize("@body");
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
print "function $name() defined.\n" unless $silent;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub showfuncs {
|
|
||||||
foreach my $f (sort keys %custom) {
|
|
||||||
print "Function $f():\n $custom{$f}\n\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sub help {
|
|
||||||
print qq~
|
|
||||||
Reverse Polish Notation Calculator, version $VERSION.
|
|
||||||
Copyleft (L) 2019-2020 - Thomas von Dein.
|
|
||||||
Licensed under the terms of the GPL 3.0.
|
|
||||||
|
|
||||||
Commandline: rpn [-d] [<operator>]
|
|
||||||
|
|
||||||
If <operator> is provided, read numbers from STDIN,
|
|
||||||
otherwise runs interactively.
|
|
||||||
|
|
||||||
Configure: Available math operators:
|
|
||||||
td toggle debugging (-d) ( enter collect mode
|
|
||||||
ts toggle display of stack (-n) ) leave collect || stack => collect
|
|
||||||
+ add
|
|
||||||
Stack Management: - substract
|
|
||||||
s show the stack / divide
|
|
||||||
sa show the whole stack * multiply
|
|
||||||
scx clear X (last stack element) ^ expotentiate
|
|
||||||
sc clear stack % percent (%+ add %- substract)
|
|
||||||
sr reverse the stack %d percentual difference
|
|
||||||
srt rotate the stack & bitwise AND
|
|
||||||
| bitwise OR
|
|
||||||
Register Management: x bitwise XOR
|
|
||||||
r put X to register < > bitwise shift left or right
|
|
||||||
R1-9 push value of register to stack v square root
|
|
||||||
rcx clear X (last register element) m median
|
|
||||||
rc clear register a average
|
|
||||||
|
|
||||||
Converters:
|
|
||||||
tl gallons => liters tkb bytes => kb
|
|
||||||
tk miles => kilometers tmb bytes => mb
|
|
||||||
tm yards => meters tgb bytes => gb
|
|
||||||
tc inches => centimeters ttb bytes => tb
|
|
||||||
|
|
||||||
Various Commands: Functions:
|
|
||||||
u undo last operation f <name> op op... (use N1..NN for stack)
|
|
||||||
h show history of past operations fs show list of defined functions
|
|
||||||
q finish (C-d works as well) Using register: enter R + index, e.g. R1
|
|
||||||
? print help Constants: PI V2 V3
|
|
||||||
|
|
||||||
~;
|
|
||||||
}
|
|
||||||
489
calc.go
489
calc.go
@@ -1,489 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Calc struct {
|
|
||||||
debug bool
|
|
||||||
batch bool
|
|
||||||
stdin bool
|
|
||||||
stack *Stack
|
|
||||||
history []string
|
|
||||||
completer readline.AutoCompleter
|
|
||||||
interpreter *Interpreter
|
|
||||||
Operators *regexp.Regexp
|
|
||||||
Space *regexp.Regexp
|
|
||||||
Constants []string
|
|
||||||
MathFunctions []string
|
|
||||||
BatchFunctions []string
|
|
||||||
LuaFunctions []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// help for lua functions will be added dynamically
|
|
||||||
const Help string = `Available commands:
|
|
||||||
batch toggle batch mode
|
|
||||||
debug toggle debug output
|
|
||||||
dump display the stack contents
|
|
||||||
clear clear the whole stack
|
|
||||||
shift remove the last element of the stack
|
|
||||||
history display calculation history
|
|
||||||
help|? show this message
|
|
||||||
quit|exit|c-d|c-c exit program
|
|
||||||
|
|
||||||
Available operators:
|
|
||||||
basic operators: + - x /
|
|
||||||
|
|
||||||
Available math functions:
|
|
||||||
sqrt square root
|
|
||||||
mod remainder of division (alias: remainder)
|
|
||||||
max batch mode only: max of all values
|
|
||||||
min batch mode only: min of all values
|
|
||||||
mean batch mode only: mean of all values (alias: avg)
|
|
||||||
median batch mode only: median of all values
|
|
||||||
% percent
|
|
||||||
%- substract percent
|
|
||||||
%+ add percent
|
|
||||||
|
|
||||||
Math operators:
|
|
||||||
^ power`
|
|
||||||
|
|
||||||
// commands, constants and operators, defined here to feed completion
|
|
||||||
// and our mode switch in Eval() dynamically
|
|
||||||
const (
|
|
||||||
Commands string = `dump reverse debug undebug clear batch shift undo help history manual exit quit`
|
|
||||||
Operators string = `+ - * x / ^ % %- %+`
|
|
||||||
MathFunctions string = `sqrt remainder`
|
|
||||||
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
|
|
||||||
BatchFunctions string = `median avg mean max min`
|
|
||||||
)
|
|
||||||
|
|
||||||
// That way we can add custom functions to completion
|
|
||||||
func GetCompleteCustomFunctions() func(string) []string {
|
|
||||||
return func(line string) []string {
|
|
||||||
completions := []string{}
|
|
||||||
|
|
||||||
for luafunc := range LuaFuncs {
|
|
||||||
completions = append(completions, luafunc)
|
|
||||||
}
|
|
||||||
|
|
||||||
completions = append(completions, strings.Split(Commands, " ")...)
|
|
||||||
completions = append(completions, strings.Split(Operators, " ")...)
|
|
||||||
completions = append(completions, strings.Split(MathFunctions, " ")...)
|
|
||||||
completions = append(completions, strings.Split(Constants, " ")...)
|
|
||||||
|
|
||||||
return completions
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCalc() *Calc {
|
|
||||||
c := Calc{stack: NewStack(), debug: false}
|
|
||||||
|
|
||||||
c.completer = readline.NewPrefixCompleter(
|
|
||||||
// custom lua functions
|
|
||||||
readline.PcItemDynamic(GetCompleteCustomFunctions()),
|
|
||||||
)
|
|
||||||
|
|
||||||
// pre-calculate mode switching regexes
|
|
||||||
reg := `^[`
|
|
||||||
for _, op := range strings.Split(Operators, " ") {
|
|
||||||
switch op {
|
|
||||||
case "x":
|
|
||||||
reg += op
|
|
||||||
default:
|
|
||||||
reg += `\` + op
|
|
||||||
}
|
|
||||||
}
|
|
||||||
reg += `]$`
|
|
||||||
c.Operators = regexp.MustCompile(reg)
|
|
||||||
|
|
||||||
c.Space = regexp.MustCompile(`\s+`)
|
|
||||||
|
|
||||||
// pre-calculate mode switching arrays
|
|
||||||
c.Constants = strings.Split(Constants, " ")
|
|
||||||
c.MathFunctions = strings.Split(MathFunctions, " ")
|
|
||||||
c.BatchFunctions = strings.Split(BatchFunctions, " ")
|
|
||||||
|
|
||||||
for name := range LuaFuncs {
|
|
||||||
c.LuaFunctions = append(c.LuaFunctions, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup the interpreter, called from main()
|
|
||||||
func (c *Calc) SetInt(I *Interpreter) {
|
|
||||||
c.interpreter = I
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) ToggleDebug() {
|
|
||||||
c.debug = !c.debug
|
|
||||||
c.stack.ToggleDebug()
|
|
||||||
fmt.Printf("debugging set to %t\n", c.debug)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) ToggleBatch() {
|
|
||||||
c.batch = !c.batch
|
|
||||||
fmt.Printf("batchmode set to %t\n", c.batch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) ToggleStdin() {
|
|
||||||
c.stdin = !c.stdin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) Prompt() string {
|
|
||||||
p := "\033[31m»\033[0m "
|
|
||||||
b := ""
|
|
||||||
if c.batch {
|
|
||||||
b = "->batch"
|
|
||||||
}
|
|
||||||
d := ""
|
|
||||||
v := ""
|
|
||||||
if c.debug {
|
|
||||||
d = "->debug"
|
|
||||||
v = fmt.Sprintf("/rev%d", c.stack.rev)
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf("rpn%s%s [%d%s]%s", b, d, c.stack.Len(), v, p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the actual work horse, evaluate a line of calc command[s]
|
|
||||||
func (c *Calc) Eval(line string) {
|
|
||||||
line = strings.TrimSpace(line)
|
|
||||||
|
|
||||||
if line == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range c.Space.Split(line, -1) {
|
|
||||||
num, err := strconv.ParseFloat(item, 64)
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
c.stack.Backup()
|
|
||||||
c.stack.Push(num)
|
|
||||||
} else {
|
|
||||||
if c.Operators.MatchString(item) {
|
|
||||||
// simple ops like + or x
|
|
||||||
c.simple(item[0])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if contains(c.Constants, item) {
|
|
||||||
// put the constant onto the stack
|
|
||||||
c.stack.Backup()
|
|
||||||
c.stack.Push(const2num(item))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if contains(c.MathFunctions, item) {
|
|
||||||
// go builtin math function, if implemented
|
|
||||||
c.mathfunc(item)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if contains(c.BatchFunctions, item) {
|
|
||||||
// math functions only supported in batch mode like max or mean
|
|
||||||
c.batchfunc(item)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if contains(c.LuaFunctions, item) {
|
|
||||||
// user provided custom lua functions
|
|
||||||
c.luafunc(item)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// management commands
|
|
||||||
switch item {
|
|
||||||
case "?":
|
|
||||||
fallthrough
|
|
||||||
case "help":
|
|
||||||
fmt.Println(Help)
|
|
||||||
fmt.Println("Lua functions:")
|
|
||||||
for name, function := range LuaFuncs {
|
|
||||||
fmt.Printf("%-20s %s\n", name, function.help)
|
|
||||||
}
|
|
||||||
case "dump":
|
|
||||||
c.stack.Dump()
|
|
||||||
case "debug":
|
|
||||||
c.ToggleDebug()
|
|
||||||
case "undebug":
|
|
||||||
c.debug = false
|
|
||||||
case "batch":
|
|
||||||
c.ToggleBatch()
|
|
||||||
case "clear":
|
|
||||||
c.stack.Backup()
|
|
||||||
c.stack.Clear()
|
|
||||||
case "shift":
|
|
||||||
c.stack.Backup()
|
|
||||||
c.stack.Shift()
|
|
||||||
case "reverse":
|
|
||||||
c.stack.Backup()
|
|
||||||
c.stack.Reverse()
|
|
||||||
case "undo":
|
|
||||||
c.stack.Restore()
|
|
||||||
case "history":
|
|
||||||
for _, entry := range c.history {
|
|
||||||
fmt.Println(entry)
|
|
||||||
}
|
|
||||||
case "exit":
|
|
||||||
fallthrough
|
|
||||||
case "quit":
|
|
||||||
os.Exit(0)
|
|
||||||
case "manual":
|
|
||||||
man()
|
|
||||||
default:
|
|
||||||
fmt.Println("unknown command or operator!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// just a textual representation of math operations, viewable with the
|
|
||||||
// history command
|
|
||||||
func (c *Calc) History(format string, args ...any) {
|
|
||||||
c.history = append(c.history, fmt.Sprintf(format, args...))
|
|
||||||
}
|
|
||||||
|
|
||||||
// print the result
|
|
||||||
func (c *Calc) Result() float64 {
|
|
||||||
if !c.stdin {
|
|
||||||
fmt.Print("= ")
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(c.stack.Last())
|
|
||||||
|
|
||||||
return c.stack.Last()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) Debug(msg string) {
|
|
||||||
if c.debug {
|
|
||||||
fmt.Printf("DEBUG(calc): %s\n", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// do simple calculations
|
|
||||||
func (c *Calc) simple(op byte) {
|
|
||||||
c.stack.Backup()
|
|
||||||
|
|
||||||
for c.stack.Len() > 1 {
|
|
||||||
b := c.stack.Pop()
|
|
||||||
a := c.stack.Pop()
|
|
||||||
var x float64
|
|
||||||
|
|
||||||
c.Debug(fmt.Sprintf("evaluating: %.2f %c %.2f", a, op, b))
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case '+':
|
|
||||||
x = a + b
|
|
||||||
case '-':
|
|
||||||
x = a - b
|
|
||||||
case 'x':
|
|
||||||
fallthrough // alias for *
|
|
||||||
case '*':
|
|
||||||
x = a * b
|
|
||||||
case '/':
|
|
||||||
if b == 0 {
|
|
||||||
fmt.Println("error: division by null!")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x = a / b
|
|
||||||
case '^':
|
|
||||||
x = math.Pow(a, b)
|
|
||||||
default:
|
|
||||||
panic("invalid operator!")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Push(x)
|
|
||||||
|
|
||||||
c.History("%f %c %f = %f", a, op, b, x)
|
|
||||||
|
|
||||||
if !c.batch {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Result()
|
|
||||||
}
|
|
||||||
|
|
||||||
// calc using go math lib functions
|
|
||||||
func (c *Calc) mathfunc(funcname string) {
|
|
||||||
c.stack.Backup()
|
|
||||||
|
|
||||||
for c.stack.Len() > 0 {
|
|
||||||
var x float64
|
|
||||||
|
|
||||||
switch funcname {
|
|
||||||
case "sqrt":
|
|
||||||
a := c.stack.Pop()
|
|
||||||
x = math.Sqrt(a)
|
|
||||||
c.History("sqrt(%f) = %f", a, x)
|
|
||||||
|
|
||||||
case "mod":
|
|
||||||
fallthrough // alias
|
|
||||||
case "remainder":
|
|
||||||
b := c.stack.Pop()
|
|
||||||
a := c.stack.Pop()
|
|
||||||
x = math.Remainder(a, b)
|
|
||||||
c.History("remainderf(%f / %f) = %f", a, b, x)
|
|
||||||
|
|
||||||
case "%":
|
|
||||||
b := c.stack.Pop()
|
|
||||||
a := c.stack.Pop()
|
|
||||||
|
|
||||||
x = (a / 100) * b
|
|
||||||
c.History("%f percent of %f = %f", b, a, x)
|
|
||||||
|
|
||||||
case "%-":
|
|
||||||
b := c.stack.Pop()
|
|
||||||
a := c.stack.Pop()
|
|
||||||
|
|
||||||
x = a - ((a / 100) * b)
|
|
||||||
c.History("%f minus %f percent of %f = %f", a, b, a, x)
|
|
||||||
|
|
||||||
case "%+":
|
|
||||||
b := c.stack.Pop()
|
|
||||||
a := c.stack.Pop()
|
|
||||||
|
|
||||||
x = a + ((a / 100) * b)
|
|
||||||
c.History("%f plus %f percent of %f = %f", a, b, a, x)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Push(x)
|
|
||||||
|
|
||||||
if !c.batch {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Result()
|
|
||||||
}
|
|
||||||
|
|
||||||
// execute pure batch functions, operating on the whole stack
|
|
||||||
func (c *Calc) batchfunc(funcname string) {
|
|
||||||
if !c.batch {
|
|
||||||
fmt.Println("error: only available in batch mode")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Backup()
|
|
||||||
var x float64
|
|
||||||
count := c.stack.Len()
|
|
||||||
|
|
||||||
switch funcname {
|
|
||||||
case "median":
|
|
||||||
all := []float64{}
|
|
||||||
|
|
||||||
for c.stack.Len() > 0 {
|
|
||||||
all = append(all, c.stack.Pop())
|
|
||||||
}
|
|
||||||
|
|
||||||
middle := count / 2
|
|
||||||
|
|
||||||
x = all[middle]
|
|
||||||
c.History("median(all)")
|
|
||||||
|
|
||||||
case "mean":
|
|
||||||
fallthrough // alias
|
|
||||||
case "avg":
|
|
||||||
var sum float64
|
|
||||||
|
|
||||||
for c.stack.Len() > 0 {
|
|
||||||
sum += c.stack.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
x = sum / float64(count)
|
|
||||||
c.History("avg(all)")
|
|
||||||
case "min":
|
|
||||||
x = c.stack.Pop() // initialize with the last one
|
|
||||||
|
|
||||||
for c.stack.Len() > 0 {
|
|
||||||
val := c.stack.Pop()
|
|
||||||
if val < x {
|
|
||||||
x = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.History("min(all)")
|
|
||||||
case "max":
|
|
||||||
x = c.stack.Pop() // initialize with the last one
|
|
||||||
|
|
||||||
for c.stack.Len() > 0 {
|
|
||||||
val := c.stack.Pop()
|
|
||||||
if val > x {
|
|
||||||
x = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.History("max(all)")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Push(x)
|
|
||||||
_ = c.Result()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Calc) luafunc(funcname string) {
|
|
||||||
// called from calc loop
|
|
||||||
var x float64
|
|
||||||
var err error
|
|
||||||
|
|
||||||
switch c.interpreter.FuncNumArgs(funcname) {
|
|
||||||
case 1:
|
|
||||||
x, err = c.interpreter.CallLuaFunc(funcname, []float64{c.stack.Last()})
|
|
||||||
case 2:
|
|
||||||
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.LastTwo())
|
|
||||||
case -1:
|
|
||||||
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.All())
|
|
||||||
default:
|
|
||||||
x, err = 0, errors.New("invalid number of argument requested")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Backup()
|
|
||||||
|
|
||||||
switch c.interpreter.FuncNumArgs(funcname) {
|
|
||||||
case 1:
|
|
||||||
a := c.stack.Pop()
|
|
||||||
c.History("%s(%f) = %f", funcname, a, x)
|
|
||||||
case 2:
|
|
||||||
a := c.stack.Pop()
|
|
||||||
b := c.stack.Pop()
|
|
||||||
c.History("%s(%f,%f) = %f", funcname, a, b, x)
|
|
||||||
case -1:
|
|
||||||
c.stack.Clear()
|
|
||||||
c.History("%s(*) = %f", funcname, x)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.stack.Push(x)
|
|
||||||
|
|
||||||
c.Result()
|
|
||||||
}
|
|
||||||
7
demo/Makefile
Normal file
7
demo/Makefile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
all:
|
||||||
|
asciinema rec --cols 80 --row 25 -c "env - PS1='> ' PATH=..:$PATH /bin/bash --norc --noprofile" --overwrite demo.cast
|
||||||
|
agg demo.cast demo.gif
|
||||||
|
|
||||||
3
demo/demo.cast
Normal file
3
demo/demo.cast
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{"version": 2, "width": 80, "height": 25, "timestamp": 1699358215, "env": {"SHELL": "/bin/bash", "TERM": "tmux-256color"}}
|
||||||
|
[0.005628, "o", "> "]
|
||||||
|
[0.941195, "o", "exit\r\n"]
|
||||||
BIN
demo/demo.gif
Normal file
BIN
demo/demo.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 613 KiB |
18
demo/sessions.txt
Normal file
18
demo/sessions.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# demo sessions, used to create an interactive rpn recording
|
||||||
|
|
||||||
|
# part 1: interactive
|
||||||
|
|
||||||
|
# put some numbers onto the stack
|
||||||
|
2 4 6
|
||||||
|
8
|
||||||
|
10
|
||||||
|
|
||||||
|
# take a look at the stack
|
||||||
|
dump
|
||||||
|
|
||||||
|
# add the last 2
|
||||||
|
+
|
||||||
|
|
||||||
|
# multiply the result and the previous
|
||||||
|
*
|
||||||
|
|
||||||
10
go.mod
10
go.mod
@@ -1,10 +0,0 @@
|
|||||||
module rpn
|
|
||||||
|
|
||||||
go 1.20
|
|
||||||
|
|
||||||
require (
|
|
||||||
github.com/chzyer/readline v1.5.1 // indirect
|
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
|
||||||
github.com/yuin/gopher-lua v1.1.0 // indirect
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 // indirect
|
|
||||||
)
|
|
||||||
10
go.sum
10
go.sum
@@ -1,10 +0,0 @@
|
|||||||
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
|
|
||||||
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
|
|
||||||
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
|
||||||
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
|
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/yuin/gopher-lua v1.1.0 h1:BojcDhfyDWgU2f2TOzYK/g5p2gxMrku8oupLDqlnSqE=
|
|
||||||
github.com/yuin/gopher-lua v1.1.0/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5 h1:y/woIyUBFbpQGKS0u1aHF/40WUDnek3fPOyD08H5Vng=
|
|
||||||
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
177
interpreter.go
177
interpreter.go
@@ -1,177 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Interpreter struct {
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// LUA interpreter, instanciated in main()
|
|
||||||
var L *lua.LState
|
|
||||||
|
|
||||||
// holds a user provided lua function
|
|
||||||
type LuaFunction struct {
|
|
||||||
name string
|
|
||||||
help string
|
|
||||||
numargs int
|
|
||||||
}
|
|
||||||
|
|
||||||
// must be global since init() is being called from lua which doesn't
|
|
||||||
// have access to the interpreter instance
|
|
||||||
var LuaFuncs map[string]LuaFunction
|
|
||||||
|
|
||||||
// initialize the lua environment properly
|
|
||||||
func InitLua(config string, debug bool) *Interpreter {
|
|
||||||
// we only load a subset of lua Open modules and don't allow
|
|
||||||
// net, system or io stuff
|
|
||||||
for _, pair := range []struct {
|
|
||||||
n string
|
|
||||||
f lua.LGFunction
|
|
||||||
}{
|
|
||||||
{lua.LoadLibName, lua.OpenPackage},
|
|
||||||
{lua.BaseLibName, lua.OpenBase},
|
|
||||||
{lua.TabLibName, lua.OpenTable},
|
|
||||||
{lua.DebugLibName, lua.OpenDebug},
|
|
||||||
{lua.MathLibName, lua.OpenMath},
|
|
||||||
} {
|
|
||||||
if err := L.CallByParam(lua.P{
|
|
||||||
Fn: L.NewFunction(pair.f),
|
|
||||||
NRet: 0,
|
|
||||||
Protect: true,
|
|
||||||
}, lua.LString(pair.n)); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// load the lua config (which we expect to contain init() and math functions)
|
|
||||||
if err := L.DoFile(config); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// instanciate
|
|
||||||
LuaFuncs = map[string]LuaFunction{}
|
|
||||||
|
|
||||||
// that way the user can call register(...) from lua inside init()
|
|
||||||
L.SetGlobal("register", L.NewFunction(register))
|
|
||||||
|
|
||||||
// actually call init()
|
|
||||||
if err := L.CallByParam(lua.P{
|
|
||||||
Fn: L.GetGlobal("init"),
|
|
||||||
NRet: 0,
|
|
||||||
Protect: true,
|
|
||||||
}); err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Interpreter{debug: debug}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Interpreter) Debug(msg string) {
|
|
||||||
if i.debug {
|
|
||||||
fmt.Printf("DEBUG(lua): %s\n", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Interpreter) FuncNumArgs(name string) int {
|
|
||||||
return LuaFuncs[name].numargs
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call a user provided math function registered with register().
|
|
||||||
//
|
|
||||||
// Each function has to tell us how many args it expects, the actual
|
|
||||||
// function call from here is different depending on the number of
|
|
||||||
// arguments. 1 uses the last item of the stack, 2 the last two and -1
|
|
||||||
// all items (which translates to batch mode)
|
|
||||||
//
|
|
||||||
// The items array will be provded by calc.Eval(), these are
|
|
||||||
// non-popped stack items. So the items will only removed from the
|
|
||||||
// stack when the lua function execution is successfull.
|
|
||||||
func (i *Interpreter) CallLuaFunc(funcname string, items []float64) (float64, error) {
|
|
||||||
i.Debug(fmt.Sprintf("calling lua func %s() with %d args",
|
|
||||||
funcname, LuaFuncs[funcname].numargs))
|
|
||||||
|
|
||||||
switch LuaFuncs[funcname].numargs {
|
|
||||||
case 1:
|
|
||||||
// 1 arg variant
|
|
||||||
if err := L.CallByParam(lua.P{
|
|
||||||
Fn: L.GetGlobal(funcname),
|
|
||||||
NRet: 1,
|
|
||||||
Protect: true,
|
|
||||||
}, lua.LNumber(items[0])); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
// 2 arg variant
|
|
||||||
if err := L.CallByParam(lua.P{
|
|
||||||
Fn: L.GetGlobal(funcname),
|
|
||||||
NRet: 1,
|
|
||||||
Protect: true,
|
|
||||||
}, lua.LNumber(items[0]), lua.LNumber(items[1])); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
case -1:
|
|
||||||
// batch variant, use lua table as array
|
|
||||||
tb := L.NewTable()
|
|
||||||
|
|
||||||
// put the whole stack into it
|
|
||||||
for _, item := range items {
|
|
||||||
tb.Append(lua.LNumber(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := L.CallByParam(lua.P{
|
|
||||||
Fn: L.GetGlobal(funcname),
|
|
||||||
NRet: 1,
|
|
||||||
Protect: true,
|
|
||||||
}, tb); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// get result and cast to float64
|
|
||||||
if res, ok := L.Get(-1).(lua.LNumber); ok {
|
|
||||||
L.Pop(1)
|
|
||||||
return float64(res), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, errors.New("function did not return a float64")
|
|
||||||
}
|
|
||||||
|
|
||||||
// called from lua to register a math function numargs may be 1, 2 or
|
|
||||||
// -1, it denotes the number of items from the stack requested by the
|
|
||||||
// lua function. -1 means batch mode, that is all items
|
|
||||||
func register(L *lua.LState) int {
|
|
||||||
function := L.ToString(1)
|
|
||||||
numargs := L.ToInt(2)
|
|
||||||
help := L.ToString(3)
|
|
||||||
|
|
||||||
LuaFuncs[function] = LuaFunction{
|
|
||||||
name: function,
|
|
||||||
numargs: numargs,
|
|
||||||
help: help,
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
171
main.go
171
main.go
@@ -1,171 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/chzyer/readline"
|
|
||||||
flag "github.com/spf13/pflag"
|
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
|
||||||
|
|
||||||
const VERSION string = "2.0.0"
|
|
||||||
|
|
||||||
const Usage string = `This is rpn, a reverse polish notation calculator cli.
|
|
||||||
|
|
||||||
Usage: rpn [-bdvh] [<operator>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-b, --batchmode enable batch mode
|
|
||||||
-d, --debug enable debug mode
|
|
||||||
-m, --manual show manual
|
|
||||||
-v, --version show version
|
|
||||||
-h, --help show help
|
|
||||||
|
|
||||||
When <operator> is given, batch mode ist automatically enabled. Use
|
|
||||||
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
|
|
||||||
|
|
||||||
Copyright (c) 2023 T.v.Dein`
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
calc := NewCalc()
|
|
||||||
|
|
||||||
showversion := false
|
|
||||||
showhelp := false
|
|
||||||
showmanual := false
|
|
||||||
enabledebug := false
|
|
||||||
configfile := ""
|
|
||||||
|
|
||||||
flag.BoolVarP(&calc.batch, "batchmode", "b", false, "batch mode")
|
|
||||||
flag.BoolVarP(&enabledebug, "debug", "d", false, "debug mode")
|
|
||||||
flag.BoolVarP(&showversion, "version", "v", false, "show version")
|
|
||||||
flag.BoolVarP(&showhelp, "help", "h", false, "show usage")
|
|
||||||
flag.BoolVarP(&showmanual, "manual", "m", false, "show manual")
|
|
||||||
flag.StringVarP(&configfile, "config", "c",
|
|
||||||
os.Getenv("HOME")+"/.rpn.lua", "config file (lua format)")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if showversion {
|
|
||||||
fmt.Printf("This is rpn version %s\n", VERSION)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if showhelp {
|
|
||||||
fmt.Println(Usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if enabledebug {
|
|
||||||
calc.ToggleDebug()
|
|
||||||
}
|
|
||||||
|
|
||||||
if showmanual {
|
|
||||||
man()
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// the lua state object is global, instanciate it early
|
|
||||||
L = lua.NewState(lua.Options{SkipOpenLibs: true})
|
|
||||||
defer L.Close()
|
|
||||||
|
|
||||||
// our config file is interpreted as lua code, only functions can
|
|
||||||
// be defined, init() will be called by InitLua().
|
|
||||||
if _, err := os.Stat(configfile); err == nil {
|
|
||||||
I := InitLua(configfile, enabledebug)
|
|
||||||
calc.SetInt(I)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(flag.Args()) > 1 {
|
|
||||||
// commandline calc operation, no readline etc needed
|
|
||||||
// called like rpn 2 2 +
|
|
||||||
calc.stdin = true
|
|
||||||
calc.Eval(strings.Join(flag.Args(), " "))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// interactive mode, need readline
|
|
||||||
rl, err := readline.NewEx(&readline.Config{
|
|
||||||
Prompt: calc.Prompt(),
|
|
||||||
HistoryFile: os.Getenv("HOME") + "/.rpn-history",
|
|
||||||
HistoryLimit: 500,
|
|
||||||
AutoComplete: calc.completer,
|
|
||||||
InterruptPrompt: "^C",
|
|
||||||
EOFPrompt: "exit",
|
|
||||||
HistorySearchFold: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
defer rl.Close()
|
|
||||||
rl.CaptureExitSignal()
|
|
||||||
|
|
||||||
if inputIsStdin() {
|
|
||||||
// commands are coming on stdin, however we will still enter
|
|
||||||
// the same loop since readline just reads fine from stdin
|
|
||||||
calc.ToggleStdin()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// primary program repl
|
|
||||||
line, err := rl.Readline()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
calc.Eval(line)
|
|
||||||
rl.SetPrompt(calc.Prompt())
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(flag.Args()) > 0 {
|
|
||||||
// called like this:
|
|
||||||
// echo 1 2 3 4 | rpn +
|
|
||||||
// batch mode enabled automatically
|
|
||||||
calc.batch = true
|
|
||||||
calc.Eval(flag.Args()[0])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inputIsStdin() bool {
|
|
||||||
stat, _ := os.Stdin.Stat()
|
|
||||||
return (stat.Mode() & os.ModeCharDevice) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func man() {
|
|
||||||
man := exec.Command("less", "-")
|
|
||||||
|
|
||||||
var b bytes.Buffer
|
|
||||||
b.Write([]byte(manpage))
|
|
||||||
|
|
||||||
man.Stdout = os.Stdout
|
|
||||||
man.Stdin = &b
|
|
||||||
man.Stderr = os.Stderr
|
|
||||||
|
|
||||||
err := man.Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
65
mkrel.sh
65
mkrel.sh
@@ -1,65 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published by
|
|
||||||
# the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
|
|
||||||
# This program is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
|
|
||||||
# get list with: go tool dist list
|
|
||||||
DIST="darwin/amd64
|
|
||||||
freebsd/amd64
|
|
||||||
linux/amd64
|
|
||||||
netbsd/amd64
|
|
||||||
openbsd/amd64
|
|
||||||
windows/amd64"
|
|
||||||
|
|
||||||
tool="$1"
|
|
||||||
version="$2"
|
|
||||||
|
|
||||||
if test -z "$version"; then
|
|
||||||
echo "Usage: $0 <tool name> <release version>"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf releases
|
|
||||||
mkdir -p releases
|
|
||||||
|
|
||||||
|
|
||||||
for D in $DIST; do
|
|
||||||
os=${D/\/*/}
|
|
||||||
arch=${D/*\//}
|
|
||||||
binfile="releases/${tool}-${os}-${arch}-${version}"
|
|
||||||
tardir="${tool}-${os}-${arch}-${version}"
|
|
||||||
tarfile="releases/${tool}-${os}-${arch}-${version}.tar.gz"
|
|
||||||
set -x
|
|
||||||
GOOS=${os} GOARCH=${arch} go build -tags osusergo,netgo -ldflags "-extldflags=-static" -o ${binfile}
|
|
||||||
mkdir -p ${tardir}
|
|
||||||
cp ${binfile} README.md LICENSE ${tardir}/
|
|
||||||
echo 'tool = rpn
|
|
||||||
PREFIX = /usr/local
|
|
||||||
UID = root
|
|
||||||
GID = 0
|
|
||||||
|
|
||||||
install:
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/bin
|
|
||||||
install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1
|
|
||||||
install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/
|
|
||||||
install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/' > ${tardir}/Makefile
|
|
||||||
tar cpzf ${tarfile} ${tardir}
|
|
||||||
sha256sum ${binfile} | cut -d' ' -f1 > ${binfile}.sha256
|
|
||||||
sha256sum ${tarfile} | cut -d' ' -f1 > ${tarfile}.sha256
|
|
||||||
rm -rf ${tardir}
|
|
||||||
set +x
|
|
||||||
done
|
|
||||||
|
|
||||||
222
rpn.go
222
rpn.go
@@ -1,222 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
var manpage = `
|
|
||||||
NAME
|
|
||||||
rpn - Reverse Polish Notation Calculator for the commandline
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
Usage: rpn [-bdvh] [<operator>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-b, --batchmode enable batch mode
|
|
||||||
-d, --debug enable debug mode
|
|
||||||
-v, --version show version
|
|
||||||
-h, --help show help
|
|
||||||
|
|
||||||
When <operator> is given, batch mode ist automatically enabled. Use
|
|
||||||
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
|
|
||||||
|
|
||||||
DESCRIPTION
|
|
||||||
rpn is a command line calculator using reverse polish notation.
|
|
||||||
|
|
||||||
Working principle
|
|
||||||
Reverse Polish Notation (short: RPN) requires to have a stack where
|
|
||||||
numbers and results are being put. So, you put numbers onto the stack
|
|
||||||
and each math operation uses these for calculation, removes them and
|
|
||||||
puts the result back.
|
|
||||||
|
|
||||||
To visualize it, let's look at a calculation:
|
|
||||||
|
|
||||||
((80 + 20) / 2) * 4
|
|
||||||
|
|
||||||
This is how you enter the formula int an RPN calculator and how the
|
|
||||||
stack evolves during the operation:
|
|
||||||
|
|
||||||
| rpn commands | stack contents | calculation |
|
|
||||||
|--------------|----------------|---------------|
|
|
||||||
| 80 | 80 | |
|
|
||||||
| 20 | 80 20 | |
|
|
||||||
| + | 100 | 80 + 20 = 100 |
|
|
||||||
| 2 | 100 2 | |
|
|
||||||
| / | 50 | 100 / 2 = 50 |
|
|
||||||
| 4 | 50 4 | |
|
|
||||||
| x | 200 | 50 * 4 = 200 |
|
|
||||||
|
|
||||||
The last stack element 200 is the calculation result.
|
|
||||||
|
|
||||||
USAGE
|
|
||||||
The default mode of operation is the interactive mode. You'll get a
|
|
||||||
prompt which shows you the current size of the stack. At the prompt you
|
|
||||||
enter numbers followed by operators or mathematical functions. You can
|
|
||||||
use completion for the functions. You can either enter each number or
|
|
||||||
operator on its own line or separated by whitespace, that doesn't
|
|
||||||
matter. After a calculation the result will be immediately displayed
|
|
||||||
(and added to the stack). You can quit interactive mode using the
|
|
||||||
commands quit or exit or hit one of the "ctrl-d" or "ctrl-c" key
|
|
||||||
combinations.
|
|
||||||
|
|
||||||
If you feed data to standard input (STDIN), rpn just does the
|
|
||||||
calculation denoted in the contet fed in via stdin, prints the result
|
|
||||||
and exits. You can also specify a calculation on the commandline.
|
|
||||||
|
|
||||||
Here are the three variants ($ is the shell prompt):
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> 2
|
|
||||||
rpn> 2
|
|
||||||
rpn> +
|
|
||||||
= 4
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> 2 2 +
|
|
||||||
= 4
|
|
||||||
|
|
||||||
$ echo 2 2 + | rpn
|
|
||||||
4
|
|
||||||
|
|
||||||
$ rpn 2 2 +
|
|
||||||
4
|
|
||||||
|
|
||||||
The rpn calculator provides a batch mode which you can use to do math
|
|
||||||
operations on many numbers. Batch mode can be enabled using the
|
|
||||||
commandline option "-b" or toggled using the interactive command batch.
|
|
||||||
Not all math operations and functions work in batch mode though.
|
|
||||||
|
|
||||||
Example of batch mode usage:
|
|
||||||
|
|
||||||
$ rpn -b
|
|
||||||
rpn->batch > 2 2 2 2 +
|
|
||||||
= 8
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> batch
|
|
||||||
rpn->batch> 2 2 2 2 +
|
|
||||||
8
|
|
||||||
|
|
||||||
$ echo 2 2 2 2 + | rpn -b
|
|
||||||
8
|
|
||||||
|
|
||||||
$ echo 2 2 2 2 | rpn +
|
|
||||||
8
|
|
||||||
|
|
||||||
If the first parameter to rpn is a math operator or function, batch mode
|
|
||||||
is enabled automatically, see last example.
|
|
||||||
|
|
||||||
STACK MANIPULATION
|
|
||||||
There are lots of stack manipulation commands provided. The most
|
|
||||||
important one is undo which goes back to the stack before the last math
|
|
||||||
operation.
|
|
||||||
|
|
||||||
You can use dump to display the stack. If debugging is enabled ("-d"
|
|
||||||
switch or debug toggle command), then the backup stack is also being
|
|
||||||
displayed.
|
|
||||||
|
|
||||||
The stack can be reversed using the reverse command.
|
|
||||||
|
|
||||||
You can use the shift command to remove the last number from the stack.
|
|
||||||
|
|
||||||
BUILTIN OPERATORS AND FUNCTIONS
|
|
||||||
Basic operators: + - x /
|
|
||||||
|
|
||||||
Math functions:
|
|
||||||
|
|
||||||
sqrt square root
|
|
||||||
mod remainder of division (alias: remainder)
|
|
||||||
max batch mode only: max of all values
|
|
||||||
min batch mode only: min of all values
|
|
||||||
mean batch mode only: mean of all values (alias: avg)
|
|
||||||
median batch mode only: median of all values
|
|
||||||
% percent
|
|
||||||
%- substract percent
|
|
||||||
%+ add percent
|
|
||||||
^ power
|
|
||||||
|
|
||||||
EXTENDING RPN USING LUA
|
|
||||||
You can use a lua script with lua functions to extend the calculator. By
|
|
||||||
default the tool looks for "~/.rpn.lua". You can also specify a script
|
|
||||||
using the <kbd>-c</kbd> flag.
|
|
||||||
|
|
||||||
Here's an example of such a script:
|
|
||||||
|
|
||||||
function add(a,b)
|
|
||||||
return a + b
|
|
||||||
end
|
|
||||||
|
|
||||||
function init()
|
|
||||||
register("add", 2, "addition")
|
|
||||||
end
|
|
||||||
|
|
||||||
Here we created a function "add()" which adds two parameters. All
|
|
||||||
parameters are "FLOAT64" numbers. You don't have to worry about stack
|
|
||||||
management, this is taken care of automatically.
|
|
||||||
|
|
||||||
The function "init()" MUST be defined, it will be called on startup. You
|
|
||||||
can do anything you like in there, but you need to call the "register()"
|
|
||||||
function to register your functions to the calculator. This function
|
|
||||||
takes these parameters:
|
|
||||||
|
|
||||||
* function name
|
|
||||||
|
|
||||||
* number of arguments expected (1,2 or -1 allowed), -1 means batch
|
|
||||||
mode.
|
|
||||||
|
|
||||||
* help text
|
|
||||||
|
|
||||||
Please refer to the lua language reference:
|
|
||||||
<https://www.lua.org/manual/5.4/> for more details about LUA.
|
|
||||||
|
|
||||||
Please note, that io, networking and system stuff is not allowed though.
|
|
||||||
So you can't open files, execute other programs or open a connection to
|
|
||||||
the outside!
|
|
||||||
|
|
||||||
GETTING HELP
|
|
||||||
In interactive mode you can enter the help command (or ?) to get a short
|
|
||||||
help along with a list of all supported operators and functions.
|
|
||||||
|
|
||||||
To read the manual you can use the manual command in interactive mode.
|
|
||||||
The commandline option "-m" does the same thing.
|
|
||||||
|
|
||||||
If you have installed rpn as a package or using the distributed tarball,
|
|
||||||
there will also be a manual page you can read using "man rpn".
|
|
||||||
|
|
||||||
BUGS
|
|
||||||
In order to report a bug, unexpected behavior, feature requests or to
|
|
||||||
submit a patch, please open an issue on github:
|
|
||||||
<https://github.com/TLINDEN/rpnc/issues>.
|
|
||||||
|
|
||||||
LICENSE
|
|
||||||
This software is licensed under the GNU GENERAL PUBLIC LICENSE version
|
|
||||||
3.
|
|
||||||
|
|
||||||
Copyright (c) 2023 by Thomas von Dein
|
|
||||||
|
|
||||||
This software uses the following GO modules:
|
|
||||||
|
|
||||||
readline (github.com/chzyer/readline)
|
|
||||||
Released under the MIT License, Copyright (c) 2016-2023 ChenYe
|
|
||||||
|
|
||||||
pflag (https://github.com/spf13/pflag)
|
|
||||||
Released under the BSD 3 license, Copyright 2013-2023 Steve Francia
|
|
||||||
|
|
||||||
gopher-lua (github.com/yuin/gopher-lua)
|
|
||||||
Released under the MIT License, Copyright (c) 2015-2023 Yusuke
|
|
||||||
Inuzuka
|
|
||||||
|
|
||||||
AUTHORS
|
|
||||||
Thomas von Dein tom AT vondein DOT org
|
|
||||||
|
|
||||||
`
|
|
||||||
var usage = `
|
|
||||||
|
|
||||||
Usage: rpn [-bdvh] [<operator>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-b, --batchmode enable batch mode
|
|
||||||
-d, --debug enable debug mode
|
|
||||||
-v, --version show version
|
|
||||||
-h, --help show help
|
|
||||||
|
|
||||||
When <operator> is given, batch mode ist automatically enabled. Use
|
|
||||||
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
|
|
||||||
|
|
||||||
`
|
|
||||||
236
rpn.pod
236
rpn.pod
@@ -1,236 +0,0 @@
|
|||||||
=head1 NAME
|
|
||||||
|
|
||||||
rpn - Reverse Polish Notation Calculator for the commandline
|
|
||||||
|
|
||||||
=head1 SYNOPSIS
|
|
||||||
|
|
||||||
Usage: rpn [-bdvh] [<operator>]
|
|
||||||
|
|
||||||
Options:
|
|
||||||
-b, --batchmode enable batch mode
|
|
||||||
-d, --debug enable debug mode
|
|
||||||
-v, --version show version
|
|
||||||
-h, --help show help
|
|
||||||
|
|
||||||
When <operator> is given, batch mode ist automatically enabled. Use
|
|
||||||
this only when working with stdin. E.g.: echo "2 3 4 5" | rpn +
|
|
||||||
|
|
||||||
=head1 DESCRIPTION
|
|
||||||
|
|
||||||
rpn is a command line calculator using reverse polish notation.
|
|
||||||
|
|
||||||
=head2 Working principle
|
|
||||||
|
|
||||||
Reverse Polish Notation (short: RPN) requires to have a stack where
|
|
||||||
numbers and results are being put. So, you put numbers onto the stack
|
|
||||||
and each math operation uses these for calculation, removes them and
|
|
||||||
puts the result back.
|
|
||||||
|
|
||||||
To visualize it, let's look at a calculation:
|
|
||||||
|
|
||||||
((80 + 20) / 2) * 4
|
|
||||||
|
|
||||||
This is how you enter the formula int an RPN calculator and how the
|
|
||||||
stack evolves during the operation:
|
|
||||||
|
|
||||||
| rpn commands | stack contents | calculation |
|
|
||||||
|--------------|----------------|---------------|
|
|
||||||
| 80 | 80 | |
|
|
||||||
| 20 | 80 20 | |
|
|
||||||
| + | 100 | 80 + 20 = 100 |
|
|
||||||
| 2 | 100 2 | |
|
|
||||||
| / | 50 | 100 / 2 = 50 |
|
|
||||||
| 4 | 50 4 | |
|
|
||||||
| x | 200 | 50 * 4 = 200 |
|
|
||||||
|
|
||||||
The last stack element 200 is the calculation result.
|
|
||||||
|
|
||||||
=head2 USAGE
|
|
||||||
|
|
||||||
The default mode of operation is the interactive mode. You'll get a
|
|
||||||
prompt which shows you the current size of the stack. At the prompt
|
|
||||||
you enter numbers followed by operators or mathematical functions. You
|
|
||||||
can use completion for the functions. You can either enter each number
|
|
||||||
or operator on its own line or separated by whitespace, that doesn't
|
|
||||||
matter. After a calculation the result will be immediately displayed
|
|
||||||
(and added to the stack). You can quit interactive mode using the
|
|
||||||
commands B<quit> or B<exit> or hit one of the C<ctrl-d> or C<ctrl-c>
|
|
||||||
key combinations.
|
|
||||||
|
|
||||||
If you feed data to standard input (STDIN), rpn just does the
|
|
||||||
calculation denoted in the contet fed in via stdin, prints the result
|
|
||||||
and exits. You can also specify a calculation on the commandline.
|
|
||||||
|
|
||||||
Here are the three variants ($ is the shell prompt):
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> 2
|
|
||||||
rpn> 2
|
|
||||||
rpn> +
|
|
||||||
= 4
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> 2 2 +
|
|
||||||
= 4
|
|
||||||
|
|
||||||
$ echo 2 2 + | rpn
|
|
||||||
4
|
|
||||||
|
|
||||||
$ rpn 2 2 +
|
|
||||||
4
|
|
||||||
|
|
||||||
The rpn calculator provides a batch mode which you can use to do math
|
|
||||||
operations on many numbers. Batch mode can be enabled using the
|
|
||||||
commandline option C<-b> or toggled using the interactive command
|
|
||||||
B<batch>. Not all math operations and functions work in batch mode
|
|
||||||
though.
|
|
||||||
|
|
||||||
Example of batch mode usage:
|
|
||||||
|
|
||||||
$ rpn -b
|
|
||||||
rpn->batch > 2 2 2 2 +
|
|
||||||
= 8
|
|
||||||
|
|
||||||
$ rpn
|
|
||||||
rpn> batch
|
|
||||||
rpn->batch> 2 2 2 2 +
|
|
||||||
8
|
|
||||||
|
|
||||||
$ echo 2 2 2 2 + | rpn -b
|
|
||||||
8
|
|
||||||
|
|
||||||
$ echo 2 2 2 2 | rpn +
|
|
||||||
8
|
|
||||||
|
|
||||||
|
|
||||||
If the first parameter to rpn is a math operator or function, batch
|
|
||||||
mode is enabled automatically, see last example.
|
|
||||||
|
|
||||||
=head2 STACK MANIPULATION
|
|
||||||
|
|
||||||
There are lots of stack manipulation commands provided. The most
|
|
||||||
important one is B<undo> which goes back to the stack before the last
|
|
||||||
math operation.
|
|
||||||
|
|
||||||
You can use B<dump> to display the stack. If debugging
|
|
||||||
is enabled (C<-d> switch or B<debug> toggle command), then the backup
|
|
||||||
stack is also being displayed.
|
|
||||||
|
|
||||||
The stack can be reversed using the B<reverse> command.
|
|
||||||
|
|
||||||
You can use the B<shift> command to remove the last number from the
|
|
||||||
stack.
|
|
||||||
|
|
||||||
=head2 BUILTIN OPERATORS AND FUNCTIONS
|
|
||||||
|
|
||||||
Basic operators: + - x /
|
|
||||||
|
|
||||||
Math functions:
|
|
||||||
|
|
||||||
sqrt square root
|
|
||||||
mod remainder of division (alias: remainder)
|
|
||||||
max batch mode only: max of all values
|
|
||||||
min batch mode only: min of all values
|
|
||||||
mean batch mode only: mean of all values (alias: avg)
|
|
||||||
median batch mode only: median of all values
|
|
||||||
% percent
|
|
||||||
%- substract percent
|
|
||||||
%+ add percent
|
|
||||||
^ power
|
|
||||||
|
|
||||||
=head1 EXTENDING RPN USING LUA
|
|
||||||
|
|
||||||
You can use a lua script with lua functions to extend the
|
|
||||||
calculator. By default the tool looks for C<~/.rpn.lua>. You can also
|
|
||||||
specify a script using the <kbd>-c</kbd> flag.
|
|
||||||
|
|
||||||
Here's an example of such a script:
|
|
||||||
|
|
||||||
function add(a,b)
|
|
||||||
return a + b
|
|
||||||
end
|
|
||||||
|
|
||||||
function init()
|
|
||||||
register("add", 2, "addition")
|
|
||||||
end
|
|
||||||
|
|
||||||
Here we created a function C<add()> which adds two parameters. All
|
|
||||||
parameters are C<FLOAT64> numbers. You don't have to worry about stack
|
|
||||||
management, this is taken care of automatically.
|
|
||||||
|
|
||||||
The function C<init()> B<MUST> be defined, it will be called on
|
|
||||||
startup. You can do anything you like in there, but you need to call
|
|
||||||
the C<register()> function to register your functions to the
|
|
||||||
calculator. This function takes these parameters:
|
|
||||||
|
|
||||||
=over
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
function name
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
number of arguments expected (1,2 or -1 allowed), -1 means batch
|
|
||||||
mode.
|
|
||||||
|
|
||||||
=item *
|
|
||||||
|
|
||||||
help text
|
|
||||||
|
|
||||||
=back
|
|
||||||
|
|
||||||
Please refer to the lua language reference:
|
|
||||||
L<https://www.lua.org/manual/5.4/> for more details about LUA.
|
|
||||||
|
|
||||||
B<Please note, that io, networking and system stuff is not allowed
|
|
||||||
though. So you can't open files, execute other programs or open a
|
|
||||||
connection to the outside!>
|
|
||||||
|
|
||||||
=head1 GETTING HELP
|
|
||||||
|
|
||||||
In interactive mode you can enter the B<help> command (or B<?>) to get
|
|
||||||
a short help along with a list of all supported operators and
|
|
||||||
functions.
|
|
||||||
|
|
||||||
To read the manual you can use the B<manual> command in interactive
|
|
||||||
mode. The commandline option C<-m> does the same thing.
|
|
||||||
|
|
||||||
If you have installed rpn as a package or using the distributed
|
|
||||||
tarball, there will also be a manual page you can read using C<man rpn>.
|
|
||||||
|
|
||||||
=head1 BUGS
|
|
||||||
|
|
||||||
In order to report a bug, unexpected behavior, feature requests
|
|
||||||
or to submit a patch, please open an issue on github:
|
|
||||||
L<https://github.com/TLINDEN/rpnc/issues>.
|
|
||||||
|
|
||||||
=head1 LICENSE
|
|
||||||
|
|
||||||
This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3.
|
|
||||||
|
|
||||||
Copyright (c) 2023 by Thomas von Dein
|
|
||||||
|
|
||||||
This software uses the following GO modules:
|
|
||||||
|
|
||||||
=over 4
|
|
||||||
|
|
||||||
=item readline (github.com/chzyer/readline)
|
|
||||||
|
|
||||||
Released under the MIT License, Copyright (c) 2016-2023 ChenYe
|
|
||||||
|
|
||||||
=item pflag (https://github.com/spf13/pflag)
|
|
||||||
|
|
||||||
Released under the BSD 3 license, Copyright 2013-2023 Steve Francia
|
|
||||||
|
|
||||||
=item gopher-lua (github.com/yuin/gopher-lua)
|
|
||||||
|
|
||||||
Released under the MIT License, Copyright (c) 2015-2023 Yusuke Inuzuka
|
|
||||||
|
|
||||||
=back
|
|
||||||
|
|
||||||
=head1 AUTHORS
|
|
||||||
|
|
||||||
Thomas von Dein B<tom AT vondein DOT org>
|
|
||||||
|
|
||||||
=cut
|
|
||||||
208
stack.go
208
stack.go
@@ -1,208 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"container/list"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The stack uses a linked list provided by container/list as storage
|
|
||||||
// and works after the LIFO principle (last in first out). Most of the
|
|
||||||
// work is being done in the linked list, but we add a couple of
|
|
||||||
// cenvenient functions, so that the user doesn't have to cope with
|
|
||||||
// list directly.
|
|
||||||
|
|
||||||
type Stack struct {
|
|
||||||
linklist list.List
|
|
||||||
backup list.List
|
|
||||||
debug bool
|
|
||||||
rev int
|
|
||||||
backuprev int
|
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: maybe use a separate stack object for backup so that it has
|
|
||||||
// its own revision etc
|
|
||||||
func NewStack() *Stack {
|
|
||||||
return &Stack{
|
|
||||||
linklist: list.List{},
|
|
||||||
backup: list.List{},
|
|
||||||
rev: 0,
|
|
||||||
backuprev: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Debug(msg string) {
|
|
||||||
if s.debug {
|
|
||||||
fmt.Printf("DEBUG(%03d): %s\n", s.rev, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) ToggleDebug() {
|
|
||||||
s.debug = !s.debug
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Bump() {
|
|
||||||
s.rev++
|
|
||||||
}
|
|
||||||
|
|
||||||
// append an item to the stack
|
|
||||||
func (s *Stack) Push(x float64) {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
s.Debug(fmt.Sprintf(" push to stack: %.2f", x))
|
|
||||||
|
|
||||||
s.Bump()
|
|
||||||
s.linklist.PushBack(x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove and return an item from the stack
|
|
||||||
func (s *Stack) Pop() float64 {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
if s.linklist.Len() == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
tail := s.linklist.Back()
|
|
||||||
val := tail.Value
|
|
||||||
s.linklist.Remove(tail)
|
|
||||||
|
|
||||||
s.Debug(fmt.Sprintf(" remove from stack: %.2f", val))
|
|
||||||
|
|
||||||
s.Bump()
|
|
||||||
return val.(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// just remove the last item, do not return it
|
|
||||||
func (s *Stack) Shift() {
|
|
||||||
s.mutex.Lock()
|
|
||||||
defer s.mutex.Unlock()
|
|
||||||
|
|
||||||
if s.linklist.Len() == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tail := s.linklist.Back()
|
|
||||||
s.linklist.Remove(tail)
|
|
||||||
|
|
||||||
s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value))
|
|
||||||
}
|
|
||||||
|
|
||||||
// just return the last item, do not remove it
|
|
||||||
func (s *Stack) Last() float64 {
|
|
||||||
if s.linklist.Back() == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.linklist.Back().Value.(float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the last 2 elements of the stack without modifying it.
|
|
||||||
//
|
|
||||||
// We need to return the last 2 elements of the stack, however
|
|
||||||
// container/list only supports access to 1 last element. So, we
|
|
||||||
// pop the last, retrieve the second last and push the popped one
|
|
||||||
// back.
|
|
||||||
func (s *Stack) LastTwo() []float64 {
|
|
||||||
items := []float64{}
|
|
||||||
if s.linklist.Back() == nil {
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
last := s.Pop()
|
|
||||||
items = append(items, last)
|
|
||||||
items = append(items, s.linklist.Back().Value.(float64))
|
|
||||||
|
|
||||||
s.Push(last)
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return all elements of the stack without modifying it.
|
|
||||||
func (s *Stack) All() []float64 {
|
|
||||||
items := []float64{}
|
|
||||||
|
|
||||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
|
||||||
items = append(items, e.Value.(float64))
|
|
||||||
}
|
|
||||||
|
|
||||||
return items
|
|
||||||
}
|
|
||||||
|
|
||||||
// dump the stack to stdout, including backup if debug is enabled
|
|
||||||
func (s *Stack) Dump() {
|
|
||||||
fmt.Printf("Stack revision %d (%p):\n", s.rev, &s.linklist)
|
|
||||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
|
||||||
fmt.Println(e.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.debug {
|
|
||||||
fmt.Printf("Backup stack revision %d (%p):\n", s.backuprev, &s.backup)
|
|
||||||
for e := s.backup.Front(); e != nil; e = e.Next() {
|
|
||||||
fmt.Println(e.Value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Clear() {
|
|
||||||
s.Debug("DEBUG: clearing stack")
|
|
||||||
|
|
||||||
s.linklist = list.List{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Len() int {
|
|
||||||
return s.linklist.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Backup() {
|
|
||||||
// we need clean the list and restore it from scratch each time we
|
|
||||||
// make a backup, because the elements in list.List{} are pointers
|
|
||||||
// and lead to unexpected results. The methid here works reliably
|
|
||||||
// at least.
|
|
||||||
s.backup = list.List{}
|
|
||||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
|
||||||
s.backup.PushBack(e.Value)
|
|
||||||
}
|
|
||||||
s.backuprev = s.rev
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Restore() {
|
|
||||||
if s.rev == 0 {
|
|
||||||
fmt.Println("error: stack is empty.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s.Debug(fmt.Sprintf("restoring stack to revision %d", s.backuprev))
|
|
||||||
|
|
||||||
s.rev = s.backuprev
|
|
||||||
s.linklist = s.backup
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stack) Reverse() {
|
|
||||||
newstack := list.List{}
|
|
||||||
|
|
||||||
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
|
||||||
newstack.PushFront(e.Value)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.linklist = newstack
|
|
||||||
}
|
|
||||||
17
test.lua
17
test.lua
@@ -1,17 +0,0 @@
|
|||||||
function add(a,b)
|
|
||||||
return a + b
|
|
||||||
end
|
|
||||||
|
|
||||||
function test(a)
|
|
||||||
return a
|
|
||||||
end
|
|
||||||
|
|
||||||
function parallelresistance(a,b)
|
|
||||||
return 1.0 / (a * b)
|
|
||||||
end
|
|
||||||
|
|
||||||
function init()
|
|
||||||
register("add", 2, "addition")
|
|
||||||
register("test", 1, "test")
|
|
||||||
register("parallelresistance", 2, "parallel resistance")
|
|
||||||
end
|
|
||||||
57
util.go
57
util.go
@@ -1,57 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright © 2023 Thomas von Dein
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU General Public License as published by
|
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
|
||||||
(at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU General Public License
|
|
||||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
// find an item in a list
|
|
||||||
func contains(s []string, e string) bool {
|
|
||||||
for _, a := range s {
|
|
||||||
if a == e {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func const2num(name string) float64 {
|
|
||||||
switch name {
|
|
||||||
case "Pi":
|
|
||||||
return math.Pi
|
|
||||||
case "Phi":
|
|
||||||
return math.Phi
|
|
||||||
case "Sqrt2":
|
|
||||||
return math.Sqrt2
|
|
||||||
case "SqrtE":
|
|
||||||
return math.SqrtE
|
|
||||||
case "SqrtPi":
|
|
||||||
return math.SqrtPi
|
|
||||||
case "SqrtPhi":
|
|
||||||
return math.SqrtPhi
|
|
||||||
case "Ln2":
|
|
||||||
return math.Ln2
|
|
||||||
case "Log2E":
|
|
||||||
return math.Log2E
|
|
||||||
case "Ln10":
|
|
||||||
return math.Ln10
|
|
||||||
case "Log10E":
|
|
||||||
return math.Log10E
|
|
||||||
default:
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user