mirror of
https://codeberg.org/scip/rpnc.git
synced 2025-12-17 12:31:04 +01:00
Compare commits
3 Commits
github
...
feature/ed
| Author | SHA1 | Date | |
|---|---|---|---|
| ed7ab15a1e | |||
| 91fac6d160 | |||
| a33a76bb06 |
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[bug-report]"
|
||||||
|
labels: bug
|
||||||
|
assignees: TLINDEN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describtion**
|
||||||
|
<!-- Please provide a clear and concise description of the issue: -->
|
||||||
|
|
||||||
|
|
||||||
|
**Steps To Reproduce**
|
||||||
|
<!-- Please detail the steps to reproduce the behavior: -->
|
||||||
|
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
<!-- What do you expected to happen instead? -->
|
||||||
|
|
||||||
|
|
||||||
|
**Version information**
|
||||||
|
<!--
|
||||||
|
Please provide as much version information as possible:
|
||||||
|
- if you have just installed a binary, provide the output of: rpn -v
|
||||||
|
- if you installed from source, provide the output of: make show-version
|
||||||
|
- provide additional details: operating system and version and shell environment
|
||||||
|
-->
|
||||||
|
|
||||||
|
|
||||||
|
**Additional informations**
|
||||||
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
23
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest a feature
|
||||||
|
title: "[feature-request]"
|
||||||
|
labels: feature-request
|
||||||
|
assignees: TLINDEN
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describtion**
|
||||||
|
<!-- Please provide a clear and concise description of the feature you desire: -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Version information**
|
||||||
|
<!--
|
||||||
|
Just in case the feature is already present, please provide as
|
||||||
|
much version information as possible:
|
||||||
|
- if you have just installed a binary, provide the output of: rpn -v
|
||||||
|
- if you installed from source, provide the output of: make show-version
|
||||||
|
- provide additional details: operating system and version and shell environment
|
||||||
|
-->
|
||||||
|
|
||||||
10
.github/dependabot.yml
vendored
10
.github/dependabot.yml
vendored
@@ -1,10 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: "gomod"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
- package-ecosystem: "github-actions"
|
|
||||||
directory: "/"
|
|
||||||
schedule:
|
|
||||||
interval: "monthly"
|
|
||||||
36
.github/workflows/ci.yaml
vendored
Normal file
36
.github/workflows/ci.yaml
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
name: build-and-test-rpn
|
||||||
|
on: [push, pull_request]
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
version: [1.21]
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
name: Build
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.version }}
|
||||||
|
id: go
|
||||||
|
|
||||||
|
- name: checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: build
|
||||||
|
run: go build
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
run: make test
|
||||||
|
|
||||||
|
golangci:
|
||||||
|
name: lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/setup-go@v3
|
||||||
|
with:
|
||||||
|
go-version: 1.21
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: golangci-lint
|
||||||
|
uses: golangci/golangci-lint-action@v3
|
||||||
86
Makefile
Normal file
86
Makefile
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
# 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
|
||||||
|
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 ./...
|
||||||
|
|
||||||
|
singletest:
|
||||||
|
@echo "Call like this: ''make singletest TEST=TestPrepareColumns"
|
||||||
|
go test -run $(TEST)
|
||||||
|
|
||||||
|
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 v$(VERSION) --generate-notes releases/*
|
||||||
|
|
||||||
|
show-versions: buildlocal
|
||||||
|
@echo "### rpn version:"
|
||||||
|
@./rpn -v
|
||||||
|
|
||||||
|
@echo
|
||||||
|
@echo "### go module versions:"
|
||||||
|
@go list -m all
|
||||||
|
|
||||||
|
@echo
|
||||||
|
@echo "### go version used for building:"
|
||||||
|
@grep -m 1 go go.mod
|
||||||
27
README.md
27
README.md
@@ -1,12 +1,8 @@
|
|||||||
[](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
|
## Programmable command-line calculator using reverse polish notation
|
||||||
|
|
||||||
> [!IMPORTANT]
|
[](https://github.com/tlinden/rpnc/actions)
|
||||||
> This app is now being maintained on [Codeberg](https://codeberg.org/scip/rpnc/).
|
[](https://github.com/tlinden/rpnc/blob/master/LICENSE)
|
||||||
|
[](https://goreportcard.com/report/github.com/tlinden/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)
|
||||||
@@ -14,7 +10,6 @@ form.
|
|||||||
|
|
||||||
Features:
|
Features:
|
||||||
|
|
||||||
|
|
||||||
- unlimited stack
|
- unlimited stack
|
||||||
- undo
|
- undo
|
||||||
- various stack manipulation commands
|
- various stack manipulation commands
|
||||||
@@ -28,7 +23,6 @@ Features:
|
|||||||
- history
|
- history
|
||||||
- comments (comment character is `#`)
|
- comments (comment character is `#`)
|
||||||
- variables
|
- variables
|
||||||
- help screen uses comfortable internal pager
|
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
@@ -252,12 +246,7 @@ connection to the outside!**
|
|||||||
|
|
||||||
There are multiple ways to install **rpn**:
|
There are multiple ways to install **rpn**:
|
||||||
|
|
||||||
- You can use [stew](https://github.com/marwanhawari/stew) to install rpnc:
|
- Go to the [latest release page](https://github.com/tlinden/rpn/releases/latest),
|
||||||
```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.
|
||||||
@@ -270,7 +259,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://codeberg.org/scip/rpn.git
|
git clone https://github.com/TLINDEN/rpn.git
|
||||||
cd rpn
|
cd rpn
|
||||||
make
|
make
|
||||||
sudo make install
|
sudo make install
|
||||||
@@ -284,7 +273,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](https://codeberg.org/scip/rpnc/raw/branch/master/rpn.pod)
|
[read the man-page online](https://github.com/TLINDEN/rpnc/blob/master/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`.
|
||||||
@@ -301,7 +290,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://codeberg.org/scip/rpnc/issues.
|
https://github.com/TLINDEN/rpnc/issues.
|
||||||
|
|
||||||
## Copyright and license
|
## Copyright and license
|
||||||
|
|
||||||
@@ -313,4 +302,4 @@ T.v.Dein <tom AT vondein DOT org>
|
|||||||
|
|
||||||
## Project homepage
|
## Project homepage
|
||||||
|
|
||||||
https://codeberg.org/scip/rpnc
|
https://github.com/TLINDEN/rpnc
|
||||||
|
|||||||
264
archived/README.md
Normal file
264
archived/README.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
## 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
Executable file
679
archived/rpnc
Executable file
@@ -0,0 +1,679 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
~;
|
||||||
|
}
|
||||||
557
calc.go
Normal file
557
calc.go
Normal file
@@ -0,0 +1,557 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"regexp"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/chzyer/readline"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Calc struct {
|
||||||
|
debug bool
|
||||||
|
batch bool
|
||||||
|
stdin bool
|
||||||
|
showstack bool
|
||||||
|
intermediate bool
|
||||||
|
notdone bool // set to true as long as there are items left in the eval loop
|
||||||
|
stack *Stack
|
||||||
|
history []string
|
||||||
|
completer readline.AutoCompleter
|
||||||
|
interpreter *Interpreter
|
||||||
|
Space *regexp.Regexp
|
||||||
|
Comment *regexp.Regexp
|
||||||
|
Register *regexp.Regexp
|
||||||
|
Constants []string
|
||||||
|
LuaFunctions []string
|
||||||
|
|
||||||
|
Funcalls Funcalls
|
||||||
|
BatchFuncalls Funcalls
|
||||||
|
|
||||||
|
// different kinds of commands, displays nicer in help output
|
||||||
|
StackCommands Commands
|
||||||
|
SettingsCommands Commands
|
||||||
|
ShowCommands Commands
|
||||||
|
Commands Commands
|
||||||
|
|
||||||
|
Vars map[string]float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// help for lua functions will be added dynamically
|
||||||
|
const Help string = `
|
||||||
|
Operators:
|
||||||
|
basic operators: + - x * / ^ (* is an alias of x)
|
||||||
|
|
||||||
|
Bitwise operators: and or xor < (left shift) > (right shift)
|
||||||
|
|
||||||
|
Percent functions:
|
||||||
|
% percent
|
||||||
|
%- substract percent
|
||||||
|
%+ add percent
|
||||||
|
|
||||||
|
Math functions (see https://pkg.go.dev/math):
|
||||||
|
mod sqrt abs acos acosh asin asinh atan atan2 atanh cbrt ceil cos cosh
|
||||||
|
erf erfc erfcinv erfinv exp exp2 expm1 floor gamma ilogb j0 j1 log
|
||||||
|
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
|
||||||
|
y1 copysign dim hypot
|
||||||
|
|
||||||
|
Batch functions:
|
||||||
|
sum sum of all values (alias: +)
|
||||||
|
max max of all values
|
||||||
|
min min of all values
|
||||||
|
mean mean of all values (alias: avg)
|
||||||
|
median median of all values
|
||||||
|
|
||||||
|
Register variables:
|
||||||
|
>NAME Put last stack element into variable NAME
|
||||||
|
<NAME Retrieve variable NAME and put onto stack`
|
||||||
|
|
||||||
|
// commands, constants and operators, defined here to feed completion
|
||||||
|
// and our mode switch in Eval() dynamically
|
||||||
|
const (
|
||||||
|
//Commands string = `dump reverse clear shift undo help history manual exit quit swap debug undebug nodebug batch nobatch showstack noshowstack vars`
|
||||||
|
Constants string = `Pi Phi Sqrt2 SqrtE SqrtPi SqrtPhi Ln2 Log2E Ln10 Log10E`
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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(Constants, " ")...)
|
||||||
|
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) GetCompleteCustomFuncalls() func(string) []string {
|
||||||
|
return func(line string) []string {
|
||||||
|
completions := []string{}
|
||||||
|
|
||||||
|
for function := range c.Funcalls {
|
||||||
|
completions = append(completions, function)
|
||||||
|
}
|
||||||
|
|
||||||
|
for function := range c.BatchFuncalls {
|
||||||
|
completions = append(completions, function)
|
||||||
|
}
|
||||||
|
|
||||||
|
for command := range c.SettingsCommands {
|
||||||
|
completions = append(completions, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
for command := range c.ShowCommands {
|
||||||
|
completions = append(completions, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
for command := range c.StackCommands {
|
||||||
|
completions = append(completions, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
for command := range c.Commands {
|
||||||
|
completions = append(completions, command)
|
||||||
|
}
|
||||||
|
|
||||||
|
return completions
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCalc() *Calc {
|
||||||
|
c := Calc{stack: NewStack(), debug: false}
|
||||||
|
|
||||||
|
c.Funcalls = DefineFunctions()
|
||||||
|
c.BatchFuncalls = DefineBatchFunctions()
|
||||||
|
c.Vars = map[string]float64{}
|
||||||
|
|
||||||
|
c.completer = readline.NewPrefixCompleter(
|
||||||
|
// custom lua functions
|
||||||
|
readline.PcItemDynamic(GetCompleteCustomFunctions()),
|
||||||
|
readline.PcItemDynamic(c.GetCompleteCustomFuncalls()),
|
||||||
|
)
|
||||||
|
|
||||||
|
c.Space = regexp.MustCompile(`\s+`)
|
||||||
|
c.Comment = regexp.MustCompile(`#.*`) // ignore everything after #
|
||||||
|
c.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`)
|
||||||
|
|
||||||
|
// pre-calculate mode switching arrays
|
||||||
|
c.Constants = strings.Split(Constants, " ")
|
||||||
|
|
||||||
|
c.SetCommands()
|
||||||
|
|
||||||
|
return &c
|
||||||
|
}
|
||||||
|
|
||||||
|
// setup the interpreter, called from main(), import lua functions
|
||||||
|
func (c *Calc) SetInt(I *Interpreter) {
|
||||||
|
c.interpreter = I
|
||||||
|
|
||||||
|
for name := range LuaFuncs {
|
||||||
|
c.LuaFunctions = append(c.LuaFunctions, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) ToggleShow() {
|
||||||
|
c.showstack = !c.showstack
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// remove surrounding whitespace and comments, if any
|
||||||
|
line = strings.TrimSpace(c.Comment.ReplaceAllString(line, ""))
|
||||||
|
|
||||||
|
if line == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items := c.Space.Split(line, -1)
|
||||||
|
|
||||||
|
for pos, item := range items {
|
||||||
|
if pos+1 < len(items) {
|
||||||
|
c.notdone = true
|
||||||
|
} else {
|
||||||
|
c.notdone = false
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseFloat(item, 64)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(num)
|
||||||
|
} else {
|
||||||
|
// try hex
|
||||||
|
var i int
|
||||||
|
_, err := fmt.Sscanf(item, "0x%x", &i)
|
||||||
|
if err == nil {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(float64(i))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if contains(c.Constants, item) {
|
||||||
|
// put the constant onto the stack
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(const2num(item))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.Funcalls[item]; ok {
|
||||||
|
if err := c.DoFuncall(item); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else {
|
||||||
|
c.Result()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.batch {
|
||||||
|
if _, ok := c.BatchFuncalls[item]; ok {
|
||||||
|
if err := c.DoFuncall(item); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
} else {
|
||||||
|
c.Result()
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if _, ok := c.BatchFuncalls[item]; ok {
|
||||||
|
fmt.Println("only supported in batch mode")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if contains(c.LuaFunctions, item) {
|
||||||
|
// user provided custom lua functions
|
||||||
|
c.EvalLuaFunction(item)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
regmatches := c.Register.FindStringSubmatch(item)
|
||||||
|
if len(regmatches) == 3 {
|
||||||
|
switch regmatches[1] {
|
||||||
|
case ">":
|
||||||
|
c.PutVar(regmatches[2])
|
||||||
|
case "<":
|
||||||
|
c.GetVar(regmatches[2])
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// internal commands
|
||||||
|
if _, ok := c.Commands[item]; ok {
|
||||||
|
c.Commands[item].Func(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.ShowCommands[item]; ok {
|
||||||
|
c.ShowCommands[item].Func(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.StackCommands[item]; ok {
|
||||||
|
c.StackCommands[item].Func(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := c.SettingsCommands[item]; ok {
|
||||||
|
c.SettingsCommands[item].Func(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch item {
|
||||||
|
case "?":
|
||||||
|
fallthrough
|
||||||
|
case "help":
|
||||||
|
c.PrintHelp()
|
||||||
|
|
||||||
|
default:
|
||||||
|
fmt.Println("unknown command or operator!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.showstack && !c.stdin {
|
||||||
|
dots := ""
|
||||||
|
|
||||||
|
if c.stack.Len() > 5 {
|
||||||
|
dots = "... "
|
||||||
|
}
|
||||||
|
last := c.stack.Last(5)
|
||||||
|
fmt.Printf("stack: %s%s\n", dots, list2str(last))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a math function, check if it is defined just in case
|
||||||
|
func (c *Calc) DoFuncall(funcname string) error {
|
||||||
|
var function *Funcall
|
||||||
|
if c.batch {
|
||||||
|
function = c.BatchFuncalls[funcname]
|
||||||
|
} else {
|
||||||
|
function = c.Funcalls[funcname]
|
||||||
|
}
|
||||||
|
|
||||||
|
if function == nil {
|
||||||
|
panic("function not defined but in completion list")
|
||||||
|
}
|
||||||
|
|
||||||
|
var args Numbers
|
||||||
|
batch := false
|
||||||
|
|
||||||
|
if function.Expectargs == -1 {
|
||||||
|
// batch mode, but always < stack len, so check first
|
||||||
|
args = c.stack.All()
|
||||||
|
batch = true
|
||||||
|
} else {
|
||||||
|
// this is way better behavior than just using 0 in place of
|
||||||
|
// non-existing stack items
|
||||||
|
if c.stack.Len() < function.Expectargs {
|
||||||
|
return errors.New("stack doesn't provide enough arguments")
|
||||||
|
}
|
||||||
|
|
||||||
|
args = c.stack.Last(function.Expectargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Debug(fmt.Sprintf("calling %s with args: %v", funcname, args))
|
||||||
|
|
||||||
|
// the actual lambda call, so to say. We provide a slice of
|
||||||
|
// the requested size, fetched from the stack (but not popped
|
||||||
|
// yet!)
|
||||||
|
R := function.Func(args)
|
||||||
|
|
||||||
|
if R.Err != nil {
|
||||||
|
// leave the stack untouched in case of any error
|
||||||
|
return R.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't forget to backup!
|
||||||
|
c.stack.Backup()
|
||||||
|
|
||||||
|
// "pop"
|
||||||
|
if batch {
|
||||||
|
// get rid of stack
|
||||||
|
c.stack.Clear()
|
||||||
|
} else {
|
||||||
|
// remove operands
|
||||||
|
c.stack.Shift(function.Expectargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save result
|
||||||
|
c.stack.Push(R.Res)
|
||||||
|
|
||||||
|
// thanks a lot
|
||||||
|
c.SetHistory(funcname, args, R.Res)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to add a history entry for each operation
|
||||||
|
func (c *Calc) SetHistory(op string, args Numbers, res float64) {
|
||||||
|
c.History("%s %s -> %f", list2str(args), op, res)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// we only print the result if it's either a final result or
|
||||||
|
// (if it is intermediate) if -i has been given
|
||||||
|
if c.intermediate || !c.notdone {
|
||||||
|
// only needed in repl
|
||||||
|
if !c.stdin {
|
||||||
|
fmt.Print("= ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(c.stack.Last()[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.stack.Last()[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) Debug(msg string) {
|
||||||
|
if c.debug {
|
||||||
|
fmt.Printf("DEBUG(calc): %s\n", msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) EvalLuaFunction(funcname string) {
|
||||||
|
// called from calc loop
|
||||||
|
var x float64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch c.interpreter.FuncNumArgs(funcname) {
|
||||||
|
case 0:
|
||||||
|
fallthrough
|
||||||
|
case 1:
|
||||||
|
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last())
|
||||||
|
case 2:
|
||||||
|
x, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2))
|
||||||
|
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()
|
||||||
|
|
||||||
|
dopush := true
|
||||||
|
|
||||||
|
switch c.interpreter.FuncNumArgs(funcname) {
|
||||||
|
case 0:
|
||||||
|
a := c.stack.Last()
|
||||||
|
if len(a) == 1 {
|
||||||
|
c.History("%s(%f) = %f", funcname, a, x)
|
||||||
|
}
|
||||||
|
dopush = false
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dopush {
|
||||||
|
c.stack.Push(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Result()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) PutVar(name string) {
|
||||||
|
last := c.stack.Last()
|
||||||
|
|
||||||
|
if len(last) == 1 {
|
||||||
|
c.Debug(fmt.Sprintf("register %.2f in %s", last[0], name))
|
||||||
|
c.Vars[name] = last[0]
|
||||||
|
} else {
|
||||||
|
fmt.Println("empty stack")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) GetVar(name string) {
|
||||||
|
if _, ok := c.Vars[name]; ok {
|
||||||
|
c.Debug(fmt.Sprintf("retrieve %.2f from %s", c.Vars[name], name))
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(c.Vars[name])
|
||||||
|
} else {
|
||||||
|
fmt.Println("variable doesn't exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortcommands(hash Commands) []string {
|
||||||
|
keys := make([]string, 0, len(hash))
|
||||||
|
|
||||||
|
for key := range hash {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Calc) PrintHelp() {
|
||||||
|
fmt.Println("Available configuration commands:")
|
||||||
|
for _, name := range sortcommands(c.SettingsCommands) {
|
||||||
|
fmt.Printf("%-20s %s\n", name, c.SettingsCommands[name].Help)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println("Available show commands:")
|
||||||
|
for _, name := range sortcommands(c.ShowCommands) {
|
||||||
|
fmt.Printf("%-20s %s\n", name, c.ShowCommands[name].Help)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println("Available stack manipulation commands:")
|
||||||
|
for _, name := range sortcommands(c.StackCommands) {
|
||||||
|
fmt.Printf("%-20s %s\n", name, c.StackCommands[name].Help)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println("Other commands:")
|
||||||
|
for _, name := range sortcommands(c.Commands) {
|
||||||
|
fmt.Printf("%-20s %s\n", name, c.Commands[name].Help)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
|
||||||
|
fmt.Println(Help)
|
||||||
|
|
||||||
|
// append lua functions, if any
|
||||||
|
if len(LuaFuncs) > 0 {
|
||||||
|
fmt.Println("Lua functions:")
|
||||||
|
for name, function := range LuaFuncs {
|
||||||
|
fmt.Printf("%-20s %s\n", name, function.help)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
352
calc_test.go
Normal file
352
calc_test.go
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCommentsAndWhitespace(t *testing.T) {
|
||||||
|
calc := NewCalc()
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
cmd []string
|
||||||
|
exp float64 // last element of the stack
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "whitespace prefix",
|
||||||
|
cmd: []string{" 5"},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace postfix",
|
||||||
|
cmd: []string{"5 "},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "whitespace both",
|
||||||
|
cmd: []string{" 5 "},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment line w/ spaces",
|
||||||
|
cmd: []string{"5", " # 19"},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "comment line w/o spaces",
|
||||||
|
cmd: []string{"5", `#19`},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inline comment w/ spaces",
|
||||||
|
cmd: []string{"5 # 19"},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "inline comment w/o spaces",
|
||||||
|
cmd: []string{"5#19"},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
testname := fmt.Sprintf("%s .(expect %.2f)",
|
||||||
|
tt.name, tt.exp)
|
||||||
|
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
for _, line := range tt.cmd {
|
||||||
|
calc.Eval(line)
|
||||||
|
}
|
||||||
|
got := calc.stack.Last()
|
||||||
|
|
||||||
|
if len(got) > 0 {
|
||||||
|
if got[0] != tt.exp {
|
||||||
|
t.Errorf("parsing failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
got, tt.exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if calc.stack.Len() != 1 {
|
||||||
|
t.Errorf("invalid stack size:\n+++ got: %d\n--- want: 1",
|
||||||
|
calc.stack.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
calc.stack.Clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalc(t *testing.T) {
|
||||||
|
calc := NewCalc()
|
||||||
|
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
cmd string
|
||||||
|
exp float64
|
||||||
|
batch bool
|
||||||
|
}{
|
||||||
|
// ops
|
||||||
|
{
|
||||||
|
name: "plus",
|
||||||
|
cmd: `15 15 +`,
|
||||||
|
exp: 30,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "power",
|
||||||
|
cmd: `4 2 ^`,
|
||||||
|
exp: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "minus",
|
||||||
|
cmd: `100 50 -`,
|
||||||
|
exp: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi",
|
||||||
|
cmd: `4 4 x`,
|
||||||
|
exp: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "divide",
|
||||||
|
cmd: `10 2 /`,
|
||||||
|
exp: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "percent",
|
||||||
|
cmd: `400 20 %`,
|
||||||
|
exp: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "percent-minus",
|
||||||
|
cmd: `400 20 %-`,
|
||||||
|
exp: 320,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "percent-plus",
|
||||||
|
cmd: `400 20 %+`,
|
||||||
|
exp: 480,
|
||||||
|
},
|
||||||
|
|
||||||
|
// math tests
|
||||||
|
{
|
||||||
|
name: "mod",
|
||||||
|
cmd: `9 2 mod`,
|
||||||
|
exp: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sqrt",
|
||||||
|
cmd: `16 sqrt`,
|
||||||
|
exp: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ceil",
|
||||||
|
cmd: `15.5 ceil`,
|
||||||
|
exp: 16,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dim",
|
||||||
|
cmd: `6 4 dim`,
|
||||||
|
exp: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// constants tests
|
||||||
|
{
|
||||||
|
name: "pitimes2",
|
||||||
|
cmd: `Pi 2 *`,
|
||||||
|
exp: 6.283185307179586,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pi+sqrt2",
|
||||||
|
cmd: `Pi Sqrt2 +`,
|
||||||
|
exp: 4.555806215962888,
|
||||||
|
},
|
||||||
|
|
||||||
|
// batch tests
|
||||||
|
{
|
||||||
|
name: "batch-sum",
|
||||||
|
cmd: `2 2 2 2 sum`,
|
||||||
|
exp: 8,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "batch-median",
|
||||||
|
cmd: `1 2 3 4 5 median`,
|
||||||
|
exp: 3,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "batch-mean",
|
||||||
|
cmd: `2 2 8 2 2 mean`,
|
||||||
|
exp: 3.2,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "batch-min",
|
||||||
|
cmd: `1 2 3 4 5 min`,
|
||||||
|
exp: 1,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "batch-max",
|
||||||
|
cmd: `1 2 3 4 5 max`,
|
||||||
|
exp: 5,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// stack tests
|
||||||
|
{
|
||||||
|
name: "use-vars",
|
||||||
|
cmd: `10 >TEN clear 5 <TEN *`,
|
||||||
|
exp: 50,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reverse",
|
||||||
|
cmd: `100 500 reverse -`,
|
||||||
|
exp: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "swap",
|
||||||
|
cmd: `2 16 swap /`,
|
||||||
|
exp: 8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "clear batch",
|
||||||
|
cmd: "1 1 1 1 1 clear 1 1 sum",
|
||||||
|
exp: 2,
|
||||||
|
batch: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "undo",
|
||||||
|
cmd: `4 4 + undo *`,
|
||||||
|
exp: 16,
|
||||||
|
},
|
||||||
|
|
||||||
|
// bit tests
|
||||||
|
{
|
||||||
|
name: "bit and",
|
||||||
|
cmd: `1 3 and`,
|
||||||
|
exp: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bit or",
|
||||||
|
cmd: `1 3 or`,
|
||||||
|
exp: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bit xor",
|
||||||
|
cmd: `1 3 xor`,
|
||||||
|
exp: 2,
|
||||||
|
},
|
||||||
|
|
||||||
|
// converters
|
||||||
|
{
|
||||||
|
name: "inch-to-cm",
|
||||||
|
cmd: `111 inch-to-cm`,
|
||||||
|
exp: 281.94,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "gallons-to-liters",
|
||||||
|
cmd: `111 gallons-to-liters`,
|
||||||
|
exp: 420.135,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "meters-to-yards",
|
||||||
|
cmd: `111 meters-to-yards`,
|
||||||
|
exp: 1.2139107611548556,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miles-to-kilometers",
|
||||||
|
cmd: `111 miles-to-kilometers`,
|
||||||
|
exp: 178.599,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
testname := fmt.Sprintf("cmd-%s-expect-%.2f",
|
||||||
|
tt.name, tt.exp)
|
||||||
|
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
calc.batch = tt.batch
|
||||||
|
calc.Eval(tt.cmd)
|
||||||
|
got := calc.Result()
|
||||||
|
calc.stack.Clear()
|
||||||
|
if got != tt.exp {
|
||||||
|
t.Errorf("calc failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
got, tt.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCalcLua(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
function string
|
||||||
|
stack []float64
|
||||||
|
exp float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
function: "lower",
|
||||||
|
stack: []float64{5, 6},
|
||||||
|
exp: 5.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
function: "parallelresistance",
|
||||||
|
stack: []float64{100, 200, 300},
|
||||||
|
exp: 54.54545454545455,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
calc := NewCalc()
|
||||||
|
L = lua.NewState(lua.Options{SkipOpenLibs: true})
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
luarunner := NewInterpreter("example.lua", false)
|
||||||
|
luarunner.InitLua()
|
||||||
|
calc.SetInt(luarunner)
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
testname := fmt.Sprintf("lua-%s", tt.function)
|
||||||
|
|
||||||
|
t.Run(testname, func(t *testing.T) {
|
||||||
|
calc.stack.Clear()
|
||||||
|
for _, item := range tt.stack {
|
||||||
|
calc.stack.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
calc.EvalLuaFunction(tt.function)
|
||||||
|
|
||||||
|
got := calc.stack.Last()
|
||||||
|
|
||||||
|
if calc.stack.Len() != 1 {
|
||||||
|
t.Errorf("invalid stack size:\n+++ got: %d\n--- want: 1",
|
||||||
|
calc.stack.Len())
|
||||||
|
}
|
||||||
|
|
||||||
|
if got[0] != tt.exp {
|
||||||
|
t.Errorf("lua function %s failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
tt.function, got, tt.exp)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
310
command.go
Normal file
310
command.go
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommandFunction func(*Calc)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
Help string
|
||||||
|
Func CommandFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commands map[string]*Command
|
||||||
|
|
||||||
|
func NewCommand(help string, function CommandFunction) *Command {
|
||||||
|
return &Command{
|
||||||
|
Help: help,
|
||||||
|
Func: function,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// define all management (that is: non calculation) commands
|
||||||
|
func (c *Calc) SetCommands() {
|
||||||
|
c.SettingsCommands = Commands{
|
||||||
|
// Toggles
|
||||||
|
"debug": NewCommand(
|
||||||
|
"toggle debugging",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.ToggleDebug()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"nodebug": NewCommand(
|
||||||
|
"disable debugging",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.debug = false
|
||||||
|
c.stack.debug = false
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"batch": NewCommand(
|
||||||
|
"toggle batch mode",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.ToggleBatch()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"nobatch": NewCommand(
|
||||||
|
"disable batch mode",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.batch = false
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"showstack": NewCommand(
|
||||||
|
"toggle show last 5 items of the stack",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.ToggleShow()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"noshowstack": NewCommand(
|
||||||
|
"disable display of the stack",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.showstack = false
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.ShowCommands = Commands{
|
||||||
|
// Display commands
|
||||||
|
"dump": NewCommand(
|
||||||
|
"display the stack contents",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.stack.Dump()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"history": NewCommand(
|
||||||
|
"display calculation history",
|
||||||
|
func(c *Calc) {
|
||||||
|
for _, entry := range c.history {
|
||||||
|
fmt.Println(entry)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"vars": NewCommand(
|
||||||
|
"show list of variables",
|
||||||
|
func(c *Calc) {
|
||||||
|
if len(c.Vars) > 0 {
|
||||||
|
fmt.Printf("%-20s %s\n", "VARIABLE", "VALUE")
|
||||||
|
for k, v := range c.Vars {
|
||||||
|
fmt.Printf("%-20s -> %.2f\n", k, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println("no vars registered")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"hex": NewCommand(
|
||||||
|
"show last stack item in hex form (converted to int)",
|
||||||
|
func(c *Calc) {
|
||||||
|
if c.stack.Len() > 0 {
|
||||||
|
fmt.Printf("0x%x\n", int(c.stack.Last()[0]))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
c.StackCommands = Commands{
|
||||||
|
"clear": NewCommand(
|
||||||
|
"clear the whole stack",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Clear()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"shift": NewCommand(
|
||||||
|
"remove the last element of the stack",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Shift()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"reverse": NewCommand(
|
||||||
|
"reverse the stack elements",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Reverse()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"swap": NewCommand(
|
||||||
|
"exchange the last two elements",
|
||||||
|
func(c *Calc) {
|
||||||
|
if c.stack.Len() < 2 {
|
||||||
|
fmt.Println("stack too small, can't swap")
|
||||||
|
} else {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Swap()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"undo": NewCommand(
|
||||||
|
"undo last operation",
|
||||||
|
func(c *Calc) {
|
||||||
|
c.stack.Restore()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"dup": NewCommand(
|
||||||
|
"duplicate last stack item",
|
||||||
|
func(c *Calc) {
|
||||||
|
item := c.stack.Last()
|
||||||
|
if len(item) == 1 {
|
||||||
|
c.stack.Backup()
|
||||||
|
c.stack.Push(item[0])
|
||||||
|
} else {
|
||||||
|
fmt.Println("stack empty")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"edit": NewCommand(
|
||||||
|
"edit the stack interactively",
|
||||||
|
func(c *Calc) {
|
||||||
|
if c.stack.Len() == 0 {
|
||||||
|
fmt.Println("empty stack")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.stack.Backup()
|
||||||
|
|
||||||
|
// put the stack contents into a tmp file
|
||||||
|
tmp, err := os.CreateTemp("", "stack")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer os.Remove(tmp.Name())
|
||||||
|
|
||||||
|
comment := `# add or remove numbers as you wish.
|
||||||
|
# each number must be on its own line.
|
||||||
|
# numbers must be floating point formatted.
|
||||||
|
`
|
||||||
|
_, err = tmp.WriteString(comment)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range c.stack.All() {
|
||||||
|
_, err = fmt.Fprintf(tmp, "%f\n", item)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.Close()
|
||||||
|
|
||||||
|
// determine which editor to use
|
||||||
|
editor := "vi"
|
||||||
|
enveditor, present := os.LookupEnv("EDITOR")
|
||||||
|
if present {
|
||||||
|
if editor != "" {
|
||||||
|
if _, err := os.Stat(editor); err == nil {
|
||||||
|
editor = enveditor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute editor with our tmp file containing current stack
|
||||||
|
cmd := exec.Command(editor, tmp.Name())
|
||||||
|
|
||||||
|
cmd.Stdin = os.Stdin
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
|
||||||
|
err = cmd.Run()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("could not run editor command: ", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the file back in
|
||||||
|
modified, err := os.Open(tmp.Name())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error opening file:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer modified.Close()
|
||||||
|
|
||||||
|
// reset the stack
|
||||||
|
c.stack.Clear()
|
||||||
|
|
||||||
|
// and put the new contents (if legit) back onto the stack
|
||||||
|
scanner := bufio.NewScanner(modified)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(c.Comment.ReplaceAllString(scanner.Text(), ""))
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
num, err := strconv.ParseFloat(line, 64)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%s is not a floating point number!\n", line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
c.stack.Push(num)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
fmt.Println("Error reading from file:", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// general commands
|
||||||
|
c.Commands = Commands{
|
||||||
|
"exit": NewCommand(
|
||||||
|
"exit program",
|
||||||
|
func(c *Calc) {
|
||||||
|
os.Exit(0)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"manual": NewCommand(
|
||||||
|
"show manual",
|
||||||
|
func(c *Calc) {
|
||||||
|
man()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
c.Commands["quit"] = c.Commands["exit"]
|
||||||
|
c.SettingsCommands["undebug"] = c.SettingsCommands["nodebug"]
|
||||||
|
c.SettingsCommands["show"] = c.SettingsCommands["showstack"]
|
||||||
|
}
|
||||||
38
example.lua
Normal file
38
example.lua
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
-- simple function, return the lower number of the two operands
|
||||||
|
function lower(a,b)
|
||||||
|
if a < b then
|
||||||
|
return a
|
||||||
|
else
|
||||||
|
return b
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- calculate parallel resistance. Batch function (registered with -1,
|
||||||
|
-- see below). Takes a table as parameter.
|
||||||
|
--
|
||||||
|
-- Formula: 1/( (1/R1) + (1/R2) + ...)
|
||||||
|
function parallelresistance(list)
|
||||||
|
sumres = 0
|
||||||
|
|
||||||
|
for i, value in ipairs(list) do
|
||||||
|
sumres = sumres + 1 / value
|
||||||
|
end
|
||||||
|
|
||||||
|
return 1 / sumres
|
||||||
|
end
|
||||||
|
|
||||||
|
-- converter example
|
||||||
|
function inch2centimeter(inches)
|
||||||
|
return inches * 2.54
|
||||||
|
end
|
||||||
|
|
||||||
|
function init()
|
||||||
|
-- expects 2 args
|
||||||
|
register("lower", 2, "lower")
|
||||||
|
|
||||||
|
-- expects a list of all numbers on the stack, batch mode
|
||||||
|
register("parallelresistance", -1, "parallel resistance")
|
||||||
|
|
||||||
|
-- expects 1 arg, but doesn't pop()
|
||||||
|
register("inch2centimeter", 0)
|
||||||
|
end
|
||||||
539
funcs.go
Normal file
539
funcs.go
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
type R struct {
|
||||||
|
Res float64
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Numbers []float64
|
||||||
|
|
||||||
|
type Function func(Numbers) R
|
||||||
|
|
||||||
|
// every function we are able to call must be of type Funcall, which
|
||||||
|
// needs to specify how many numbers it expects and the actual go
|
||||||
|
// function to be executed.
|
||||||
|
//
|
||||||
|
// The function has to take a float slice as argument and return a
|
||||||
|
// float and an error object. The float slice is guaranteed to have
|
||||||
|
// the expected number of arguments.
|
||||||
|
//
|
||||||
|
// However, Lua functions are handled differently, see interpreter.go.
|
||||||
|
type Funcall struct {
|
||||||
|
Expectargs int // -1 means batch only mode, you'll get the whole stack as arg
|
||||||
|
Func Function
|
||||||
|
}
|
||||||
|
|
||||||
|
// will hold all hard coded functions and operators
|
||||||
|
type Funcalls map[string]*Funcall
|
||||||
|
|
||||||
|
// convenience function, create a new Funcall object, if expectargs
|
||||||
|
// was not specified, 2 is assumed.
|
||||||
|
func NewFuncall(function Function, expectargs ...int) *Funcall {
|
||||||
|
expect := 2
|
||||||
|
|
||||||
|
if len(expectargs) > 0 {
|
||||||
|
expect = expectargs[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Funcall{
|
||||||
|
Expectargs: expect,
|
||||||
|
Func: function,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience function, create new result
|
||||||
|
func NewR(n float64, e error) R {
|
||||||
|
return R{Res: n, Err: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
// the actual functions, called once during initialization.
|
||||||
|
func DefineFunctions() Funcalls {
|
||||||
|
f := map[string]*Funcall{
|
||||||
|
// simple operators, they all expect 2 args
|
||||||
|
"+": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]+arg[1], nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"-": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]-arg[1], nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"x": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]*arg[1], nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"/": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
if arg[1] == 0 {
|
||||||
|
return NewR(0, errors.New("division by null"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewR(arg[0]/arg[1], nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"^": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Pow(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"%": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR((arg[0]/100)*arg[1], nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"%-": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]-((arg[0]/100)*arg[1]), nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"%+": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]+((arg[0]/100)*arg[1]), nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"mod": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Remainder(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
"sqrt": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Sqrt(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"abs": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Abs(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"acos": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Acos(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"acosh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Acosh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"asin": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Asin(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"asinh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Asinh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"atan": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Atan(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"atan2": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Atan2(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"atanh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Atanh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"cbrt": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Cbrt(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"ceil": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Ceil(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"cos": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Cos(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"cosh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Cosh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"erf": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Erf(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"erfc": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Erfc(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"erfcinv": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Erfcinv(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"erfinv": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Erfinv(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"exp": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Exp(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"exp2": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Exp2(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"expm1": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Expm1(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"floor": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Floor(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"gamma": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Gamma(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"ilogb": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(math.Ilogb(arg[0])), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"j0": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.J0(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"j1": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.J1(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"log": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Log(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"log10": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Log10(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"log1p": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Log1p(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"log2": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Log2(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"logb": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Logb(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"pow": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Pow(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"round": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Round(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"roundtoeven": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.RoundToEven(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"sin": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Sin(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"sinh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Sinh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"tan": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Tan(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"tanh": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Tanh(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"trunc": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Trunc(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"y0": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Y0(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"y1": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Y1(arg[0]), nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"copysign": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Copysign(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"dim": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Dim(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"hypot": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(math.Hypot(arg[0], arg[1]), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
// converters of all kinds
|
||||||
|
"cm-to-inch": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]/2.54, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"inch-to-cm": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]*2.54, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"gallons-to-liters": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]*3.785, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"liters-to-gallons": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]/3.785, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"yards-to-meters": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]*91.44, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"meters-to-yards": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]/91.44, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"miles-to-kilometers": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]*1.609, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"kilometers-to-miles": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(arg[0]/1.609, nil)
|
||||||
|
},
|
||||||
|
1),
|
||||||
|
|
||||||
|
"or": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(int(arg[0])|int(arg[1])), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"and": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(int(arg[0])&int(arg[1])), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"xor": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(int(arg[0])^int(arg[1])), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
"<": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(int(arg[0])<<int(arg[1])), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
|
||||||
|
">": NewFuncall(
|
||||||
|
func(arg Numbers) R {
|
||||||
|
return NewR(float64(int(arg[0])>>int(arg[1])), nil)
|
||||||
|
},
|
||||||
|
2),
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
f["*"] = f["x"]
|
||||||
|
f["remainder"] = f["mod"]
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func DefineBatchFunctions() Funcalls {
|
||||||
|
f := map[string]*Funcall{
|
||||||
|
"median": NewFuncall(
|
||||||
|
func(args Numbers) R {
|
||||||
|
middle := len(args) / 2
|
||||||
|
return NewR(args[middle], nil)
|
||||||
|
},
|
||||||
|
-1),
|
||||||
|
|
||||||
|
"mean": NewFuncall(
|
||||||
|
func(args Numbers) R {
|
||||||
|
var sum float64
|
||||||
|
for _, item := range args {
|
||||||
|
sum += item
|
||||||
|
}
|
||||||
|
return NewR(sum/float64(len(args)), nil)
|
||||||
|
},
|
||||||
|
-1),
|
||||||
|
|
||||||
|
"min": NewFuncall(
|
||||||
|
func(args Numbers) R {
|
||||||
|
var min float64
|
||||||
|
min, args = args[0], args[1:]
|
||||||
|
for _, item := range args {
|
||||||
|
if item < min {
|
||||||
|
min = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NewR(min, nil)
|
||||||
|
},
|
||||||
|
-1),
|
||||||
|
|
||||||
|
"max": NewFuncall(
|
||||||
|
func(args Numbers) R {
|
||||||
|
var max float64
|
||||||
|
max, args = args[0], args[1:]
|
||||||
|
for _, item := range args {
|
||||||
|
if item > max {
|
||||||
|
max = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NewR(max, nil)
|
||||||
|
},
|
||||||
|
-1),
|
||||||
|
|
||||||
|
"sum": NewFuncall(
|
||||||
|
func(args Numbers) R {
|
||||||
|
var sum float64
|
||||||
|
for _, item := range args {
|
||||||
|
sum += item
|
||||||
|
}
|
||||||
|
return NewR(sum, nil)
|
||||||
|
},
|
||||||
|
-1),
|
||||||
|
}
|
||||||
|
|
||||||
|
// aliases
|
||||||
|
f["+"] = f["sum"]
|
||||||
|
f["avg"] = f["mean"]
|
||||||
|
|
||||||
|
return f
|
||||||
|
}
|
||||||
10
go.mod
Normal file
10
go.mod
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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
Normal file
10
go.sum
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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=
|
||||||
182
interpreter.go
Normal file
182
interpreter.go
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
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
|
||||||
|
script string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
func NewInterpreter(script string, debug bool) *Interpreter {
|
||||||
|
return &Interpreter{debug: debug, script: script}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the lua environment properly
|
||||||
|
func (i *Interpreter) InitLua() {
|
||||||
|
// 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(i.script); 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 0:
|
||||||
|
fallthrough
|
||||||
|
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
|
||||||
|
}
|
||||||
177
main.go
Normal file
177
main.go
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
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.11"
|
||||||
|
|
||||||
|
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
|
||||||
|
-s, --stack show last 5 items of the stack (off by default)
|
||||||
|
-i --intermediate print intermediate results
|
||||||
|
-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(&calc.showstack, "show-stack", "s", false, "show stack")
|
||||||
|
flag.BoolVarP(&calc.intermediate, "showin-termediate", "i", false,
|
||||||
|
"show intermediate results")
|
||||||
|
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 {
|
||||||
|
luarunner := NewInterpreter(configfile, enabledebug)
|
||||||
|
luarunner.InitLua()
|
||||||
|
calc.SetInt(luarunner)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
Executable file
65
mkrel.sh
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
327
rpn.go
Normal file
327
rpn.go
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
var manpage = `
|
||||||
|
NAME
|
||||||
|
rpn - Programmable command-line calculator using reverse polish notation
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
Usage: rpn [-bdvh] [<operator>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-b, --batchmode enable batch mode
|
||||||
|
-d, --debug enable debug mode
|
||||||
|
-s, --stack show last 5 items of the stack (off by default)
|
||||||
|
-i --intermediate print intermediate results
|
||||||
|
-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 +
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
You can enter integers, floating point numbers (positive or negative) or
|
||||||
|
hex numbers (prefixed with 0x).
|
||||||
|
|
||||||
|
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. However, sometimes
|
||||||
|
only the last two values are in the wrong order. Use the swap command to
|
||||||
|
exchange them.
|
||||||
|
|
||||||
|
You can use the shift command to remove the last number from the stack.
|
||||||
|
|
||||||
|
BUILTIN OPERATORS AND FUNCTIONS
|
||||||
|
Basic operators:
|
||||||
|
|
||||||
|
+ add
|
||||||
|
- substract
|
||||||
|
/ divide
|
||||||
|
x multiply (alias: *)
|
||||||
|
^ power
|
||||||
|
|
||||||
|
Bitwise operators:
|
||||||
|
|
||||||
|
and bitwise and
|
||||||
|
or bitwise or
|
||||||
|
xor bitwise xor
|
||||||
|
< left shift
|
||||||
|
> right shift
|
||||||
|
|
||||||
|
Percent functions:
|
||||||
|
|
||||||
|
% percent
|
||||||
|
%- substract percent
|
||||||
|
%+ add percent
|
||||||
|
|
||||||
|
Batch functions:
|
||||||
|
|
||||||
|
sum sum of all values (alias: +)
|
||||||
|
max max of all values
|
||||||
|
min min of all values
|
||||||
|
mean mean of all values (alias: avg)
|
||||||
|
median median of all values
|
||||||
|
|
||||||
|
Math functions:
|
||||||
|
|
||||||
|
mod sqrt abs acos acosh asin asinh atan atan2 atanh cbrt ceil cos cosh
|
||||||
|
erf erfc erfcinv erfinv exp exp2 expm1 floor gamma ilogb j0 j1 log
|
||||||
|
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
|
||||||
|
y1 copysign dim hypot
|
||||||
|
|
||||||
|
Conversion functions:
|
||||||
|
|
||||||
|
cm-to-inch
|
||||||
|
inch-to-cm
|
||||||
|
gallons-to-liters
|
||||||
|
liters-to-gallons
|
||||||
|
yards-to-meters
|
||||||
|
meters-to-yards
|
||||||
|
miles-to-kilometers
|
||||||
|
kilometers-to-miles
|
||||||
|
|
||||||
|
Configuration Commands:
|
||||||
|
|
||||||
|
[no]batch toggle batch mode (nobatch turns it off)
|
||||||
|
[no]debug toggle debug output (nodebug turns it off)
|
||||||
|
[no]showstack show the last 5 items of the stack (noshowtack turns it off)
|
||||||
|
|
||||||
|
Show commands: dump display the stack contents hex show last stack item
|
||||||
|
in hex form (converted to int) history display calculation history vars
|
||||||
|
show list of variables
|
||||||
|
|
||||||
|
Stack manipulation commands: clear clear the whole stack shift remove
|
||||||
|
the last element of the stack reverse reverse the stack elements swap
|
||||||
|
exchange the last two stack elements dup duplicate last stack item undo
|
||||||
|
undo last operation edit edit the stack interactively using vi or
|
||||||
|
$EDITOR
|
||||||
|
|
||||||
|
Other commands: help|? show this message manual show manual
|
||||||
|
quit|exit|c-d|c-c exit program
|
||||||
|
|
||||||
|
Register variables:
|
||||||
|
|
||||||
|
>NAME Put last stack element into variable NAME
|
||||||
|
<NAME Retrieve variable NAME and put onto stack
|
||||||
|
|
||||||
|
Refer to https://pkg.go.dev/math for details about those functions.
|
||||||
|
|
||||||
|
INTERACTIVE REPL
|
||||||
|
While you can use rpn in the command-line, the best experience you'll
|
||||||
|
have is the interactive repl (read eval print loop). Just execute "rpn"
|
||||||
|
and you'll be there.
|
||||||
|
|
||||||
|
In interactive mode you can use TAB completion to complete commands,
|
||||||
|
operators and functions. There's also a history, which allows you to
|
||||||
|
repeat complicated calculations (as long as you've entered them in one
|
||||||
|
line).
|
||||||
|
|
||||||
|
There are also a lot of key bindings, here are the most important ones:
|
||||||
|
|
||||||
|
ctrl-c + ctrl-d
|
||||||
|
Exit interactive rpn
|
||||||
|
|
||||||
|
ctrl-z
|
||||||
|
Send rpn to the backgound.
|
||||||
|
|
||||||
|
ctrl-a
|
||||||
|
Beginning of line.
|
||||||
|
|
||||||
|
ctrl-e
|
||||||
|
End of line.
|
||||||
|
|
||||||
|
ctrl-l
|
||||||
|
Clear the screen.
|
||||||
|
|
||||||
|
ctrl-r
|
||||||
|
Search through history.
|
||||||
|
|
||||||
|
COMMENTS
|
||||||
|
Lines starting with "#" are being ignored as comments. You can also
|
||||||
|
append comments to rpn input, e.g.:
|
||||||
|
|
||||||
|
# a comment
|
||||||
|
123 # another comment
|
||||||
|
|
||||||
|
In this case only 123 will be added to the stack.
|
||||||
|
|
||||||
|
VARIABLES
|
||||||
|
You can register the last item of the stack into a variable. Variable
|
||||||
|
names must be all caps. Use the ">NAME" command to put a value into
|
||||||
|
variable "NAME". Use "<NAME" to retrieve the value of variable "NAME"
|
||||||
|
and put it onto the stack.
|
||||||
|
|
||||||
|
The command vars can be used to get a list of all variables.
|
||||||
|
|
||||||
|
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 (see below)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
* 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
|
||||||
|
|
||||||
|
`
|
||||||
377
rpn.pod
Normal file
377
rpn.pod
Normal file
@@ -0,0 +1,377 @@
|
|||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
rpn - Programmable command-line calculator using reverse polish notation
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
Usage: rpn [-bdvh] [<operator>]
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-b, --batchmode enable batch mode
|
||||||
|
-d, --debug enable debug mode
|
||||||
|
-s, --stack show last 5 items of the stack (off by default)
|
||||||
|
-i --intermediate print intermediate results
|
||||||
|
-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 +
|
||||||
|
|
||||||
|
=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.
|
||||||
|
|
||||||
|
You can enter integers, floating point numbers (positive or negative)
|
||||||
|
or hex numbers (prefixed with 0x).
|
||||||
|
|
||||||
|
=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. However,
|
||||||
|
sometimes only the last two values are in the wrong order. Use the
|
||||||
|
B<swap> command to exchange them.
|
||||||
|
|
||||||
|
You can use the B<shift> command to remove the last number from the
|
||||||
|
stack.
|
||||||
|
|
||||||
|
=head2 BUILTIN OPERATORS AND FUNCTIONS
|
||||||
|
|
||||||
|
Basic operators:
|
||||||
|
|
||||||
|
+ add
|
||||||
|
- substract
|
||||||
|
/ divide
|
||||||
|
x multiply (alias: *)
|
||||||
|
^ power
|
||||||
|
|
||||||
|
Bitwise operators:
|
||||||
|
|
||||||
|
and bitwise and
|
||||||
|
or bitwise or
|
||||||
|
xor bitwise xor
|
||||||
|
< left shift
|
||||||
|
> right shift
|
||||||
|
|
||||||
|
Percent functions:
|
||||||
|
|
||||||
|
% percent
|
||||||
|
%- substract percent
|
||||||
|
%+ add percent
|
||||||
|
|
||||||
|
Batch functions:
|
||||||
|
|
||||||
|
sum sum of all values (alias: +)
|
||||||
|
max max of all values
|
||||||
|
min min of all values
|
||||||
|
mean mean of all values (alias: avg)
|
||||||
|
median median of all values
|
||||||
|
|
||||||
|
Math functions:
|
||||||
|
|
||||||
|
mod sqrt abs acos acosh asin asinh atan atan2 atanh cbrt ceil cos cosh
|
||||||
|
erf erfc erfcinv erfinv exp exp2 expm1 floor gamma ilogb j0 j1 log
|
||||||
|
log10 log1p log2 logb pow round roundtoeven sin sinh tan tanh trunc y0
|
||||||
|
y1 copysign dim hypot
|
||||||
|
|
||||||
|
Conversion functions:
|
||||||
|
|
||||||
|
cm-to-inch
|
||||||
|
inch-to-cm
|
||||||
|
gallons-to-liters
|
||||||
|
liters-to-gallons
|
||||||
|
yards-to-meters
|
||||||
|
meters-to-yards
|
||||||
|
miles-to-kilometers
|
||||||
|
kilometers-to-miles
|
||||||
|
|
||||||
|
Configuration Commands:
|
||||||
|
|
||||||
|
[no]batch toggle batch mode (nobatch turns it off)
|
||||||
|
[no]debug toggle debug output (nodebug turns it off)
|
||||||
|
[no]showstack show the last 5 items of the stack (noshowtack turns it off)
|
||||||
|
|
||||||
|
Show commands:
|
||||||
|
dump display the stack contents
|
||||||
|
hex show last stack item in hex form (converted to int)
|
||||||
|
history display calculation history
|
||||||
|
vars show list of variables
|
||||||
|
|
||||||
|
Stack manipulation commands:
|
||||||
|
clear clear the whole stack
|
||||||
|
shift remove the last element of the stack
|
||||||
|
reverse reverse the stack elements
|
||||||
|
swap exchange the last two stack elements
|
||||||
|
dup duplicate last stack item
|
||||||
|
undo undo last operation
|
||||||
|
edit edit the stack interactively using vi or $EDITOR
|
||||||
|
|
||||||
|
Other commands:
|
||||||
|
help|? show this message
|
||||||
|
manual show manual
|
||||||
|
quit|exit|c-d|c-c exit program
|
||||||
|
|
||||||
|
|
||||||
|
Register variables:
|
||||||
|
|
||||||
|
>NAME Put last stack element into variable NAME
|
||||||
|
<NAME Retrieve variable NAME and put onto stack
|
||||||
|
|
||||||
|
Refer to https://pkg.go.dev/math for details about those functions.
|
||||||
|
|
||||||
|
=head1 INTERACTIVE REPL
|
||||||
|
|
||||||
|
While you can use rpn in the command-line, the best experience you'll
|
||||||
|
have is the interactive repl (read eval print loop). Just execute
|
||||||
|
C<rpn> and you'll be there.
|
||||||
|
|
||||||
|
In interactive mode you can use TAB completion to complete commands,
|
||||||
|
operators and functions. There's also a history, which allows you to
|
||||||
|
repeat complicated calculations (as long as you've entered them in one
|
||||||
|
line).
|
||||||
|
|
||||||
|
There are also a lot of key bindings, here are the most important
|
||||||
|
ones:
|
||||||
|
|
||||||
|
=over
|
||||||
|
|
||||||
|
=item ctrl-c + ctrl-d
|
||||||
|
|
||||||
|
Exit interactive rpn
|
||||||
|
|
||||||
|
=item ctrl-z
|
||||||
|
|
||||||
|
Send rpn to the backgound.
|
||||||
|
|
||||||
|
=item ctrl-a
|
||||||
|
|
||||||
|
Beginning of line.
|
||||||
|
|
||||||
|
=item ctrl-e
|
||||||
|
|
||||||
|
End of line.
|
||||||
|
|
||||||
|
=item ctrl-l
|
||||||
|
|
||||||
|
Clear the screen.
|
||||||
|
|
||||||
|
=item ctrl-r
|
||||||
|
|
||||||
|
Search through history.
|
||||||
|
|
||||||
|
=back
|
||||||
|
|
||||||
|
=head1 COMMENTS
|
||||||
|
|
||||||
|
Lines starting with C<#> are being ignored as comments. You can also
|
||||||
|
append comments to rpn input, e.g.:
|
||||||
|
|
||||||
|
# a comment
|
||||||
|
123 # another comment
|
||||||
|
|
||||||
|
In this case only 123 will be added to the stack.
|
||||||
|
|
||||||
|
=head1 VARIABLES
|
||||||
|
|
||||||
|
You can register the last item of the stack into a variable. Variable
|
||||||
|
names must be all caps. Use the ">NAME" command to put a value into
|
||||||
|
variable "NAME". Use "<NAME" to retrieve the value of variable "NAME"
|
||||||
|
and put it onto the stack.
|
||||||
|
|
||||||
|
The command B<vars> can be used to get a list of all variables.
|
||||||
|
|
||||||
|
=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 (see below)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
=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
|
||||||
246
stack.go
Normal file
246
stack.go
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
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(num ...int) {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
count := 1
|
||||||
|
|
||||||
|
if len(num) > 0 {
|
||||||
|
count = num[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.linklist.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < count; i++ {
|
||||||
|
tail := s.linklist.Back()
|
||||||
|
s.linklist.Remove(tail)
|
||||||
|
s.Debug(fmt.Sprintf("remove from stack: %.2f", tail.Value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Swap() {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
if s.linklist.Len() < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
a := s.linklist.Back()
|
||||||
|
s.linklist.Remove(a)
|
||||||
|
|
||||||
|
b := s.linklist.Back()
|
||||||
|
s.linklist.Remove(b)
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf("swapping %.2f with %.2f", b.Value, a.Value))
|
||||||
|
|
||||||
|
s.linklist.PushBack(a.Value)
|
||||||
|
s.linklist.PushBack(b.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the last num items from the stack w/o modifying it.
|
||||||
|
func (s *Stack) Last(num ...int) []float64 {
|
||||||
|
items := []float64{}
|
||||||
|
i := s.Len()
|
||||||
|
count := 1
|
||||||
|
if len(num) > 0 {
|
||||||
|
count = num[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||||
|
if i <= count {
|
||||||
|
items = append(items, e.Value.(float64))
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
|
||||||
|
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("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.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
s.Debug(fmt.Sprintf("backing up %d items from rev %d",
|
||||||
|
s.linklist.Len(), s.rev))
|
||||||
|
|
||||||
|
s.backup = list.List{}
|
||||||
|
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||||
|
s.backup.PushBack(e.Value.(float64))
|
||||||
|
}
|
||||||
|
|
||||||
|
s.backuprev = s.rev
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Restore() {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
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 = list.List{}
|
||||||
|
for e := s.backup.Front(); e != nil; e = e.Next() {
|
||||||
|
s.linklist.PushBack(e.Value.(float64))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Stack) Reverse() {
|
||||||
|
s.mutex.Lock()
|
||||||
|
defer s.mutex.Unlock()
|
||||||
|
|
||||||
|
items := []float64{}
|
||||||
|
|
||||||
|
for e := s.linklist.Front(); e != nil; e = e.Next() {
|
||||||
|
tail := s.linklist.Back()
|
||||||
|
items = append(items, tail.Value.(float64))
|
||||||
|
s.linklist.Remove(tail)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := len(items) - 1; i >= 0; i-- {
|
||||||
|
s.linklist.PushFront(items[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
190
stack_test.go
Normal file
190
stack_test.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPush(t *testing.T) {
|
||||||
|
t.Run("push", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
|
||||||
|
if s.linklist.Back().Value != 5.0 {
|
||||||
|
t.Errorf("push failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
s.linklist.Back().Value, 5.0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPop(t *testing.T) {
|
||||||
|
t.Run("pop", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
got := s.Pop()
|
||||||
|
|
||||||
|
if got != 5.0 {
|
||||||
|
t.Errorf("pop failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
got, 5.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Len() != 0 {
|
||||||
|
t.Errorf("stack not empty after pop()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPops(t *testing.T) {
|
||||||
|
t.Run("pops", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
s.Push(5)
|
||||||
|
s.Push(5)
|
||||||
|
s.Pop()
|
||||||
|
|
||||||
|
if s.Len() != 2 {
|
||||||
|
t.Errorf("stack len not correct after pop:\n+++ got: %d\n--- want: %d",
|
||||||
|
s.Len(), 2)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShift(t *testing.T) {
|
||||||
|
t.Run("shift", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Shift()
|
||||||
|
|
||||||
|
if s.Len() != 0 {
|
||||||
|
t.Errorf("stack not empty after shift()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClear(t *testing.T) {
|
||||||
|
t.Run("clear", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
s.Push(5)
|
||||||
|
s.Push(5)
|
||||||
|
s.Clear()
|
||||||
|
|
||||||
|
if s.Len() != 0 {
|
||||||
|
t.Errorf("stack not empty after clear()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLast(t *testing.T) {
|
||||||
|
t.Run("last", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
got := s.Last()
|
||||||
|
|
||||||
|
if len(got) != 1 {
|
||||||
|
t.Errorf("last failed:\n+++ got: %d elements\n--- want: %d elements",
|
||||||
|
len(got), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got[0] != 5.0 {
|
||||||
|
t.Errorf("last failed:\n+++ got: %f\n--- want: %f",
|
||||||
|
got, 5.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Len() != 1 {
|
||||||
|
t.Errorf("stack modified after last()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
t.Run("all", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
list := []float64{2, 4, 6, 8}
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
s.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
got := s.All()
|
||||||
|
|
||||||
|
if len(got) != len(list) {
|
||||||
|
t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",
|
||||||
|
len(got), len(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < len(list); i++ {
|
||||||
|
if got[i] != list[i] {
|
||||||
|
t.Errorf("all failed (element %d):\n+++ got: %f\n--- want: %f",
|
||||||
|
i, got[i], list[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Len() != len(list) {
|
||||||
|
t.Errorf("stack modified after last()")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBackupRestore(t *testing.T) {
|
||||||
|
t.Run("shift", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
s.Push(5)
|
||||||
|
s.Backup()
|
||||||
|
s.Clear()
|
||||||
|
s.Restore()
|
||||||
|
|
||||||
|
if s.Len() != 1 {
|
||||||
|
t.Errorf("stack not correctly restored()")
|
||||||
|
}
|
||||||
|
|
||||||
|
a := s.Pop()
|
||||||
|
if a != 5.0 {
|
||||||
|
t.Errorf("stack not identical to old revision:\n+++ got: %f\n--- want: %f",
|
||||||
|
a, 5.0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReverse(t *testing.T) {
|
||||||
|
t.Run("reverse", func(t *testing.T) {
|
||||||
|
s := NewStack()
|
||||||
|
list := []float64{2, 4, 6}
|
||||||
|
reverse := []float64{6, 4, 2}
|
||||||
|
|
||||||
|
for _, item := range list {
|
||||||
|
s.Push(item)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.Reverse()
|
||||||
|
|
||||||
|
got := s.All()
|
||||||
|
|
||||||
|
if len(got) != len(list) {
|
||||||
|
t.Errorf("all failed:\n+++ got: %d elements\n--- want: %d elements",
|
||||||
|
len(got), len(list))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 1; i < len(reverse); i++ {
|
||||||
|
if got[i] != reverse[i] {
|
||||||
|
t.Errorf("reverse failed (element %d):\n+++ got: %f\n--- want: %f",
|
||||||
|
i, got[i], list[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
65
util.go
Normal file
65
util.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func list2str(list Numbers) string {
|
||||||
|
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(list)), " "), "[]")
|
||||||
|
}
|
||||||
32
util_test.go
Normal file
32
util_test.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContains(t *testing.T) {
|
||||||
|
list := []string{"a", "b", "c"}
|
||||||
|
|
||||||
|
t.Run("contains", func(t *testing.T) {
|
||||||
|
if !contains(list, "a") {
|
||||||
|
t.Errorf("a in [a,b,c] not found")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user