diff --git a/.gh-dash.yml b/.gh-dash.yml deleted file mode 100644 index abdce81..0000000 --- a/.gh-dash.yml +++ /dev/null @@ -1,96 +0,0 @@ -prSections: - - title: Responsible PRs - filters: repo:tlinden/rpnc is:open NOT dependabot - layout: - repoName: - hidden: true - - - title: Responsible Dependabot PRs - filters: repo:tlinden/rpnc is:open dependabot - layout: - repoName: - hidden: true - -issuesSections: - - title: Responsible Issues - filters: is:open repo:tlinden/rpnc -author:@me - layout: - repoName: - hidden: true - - - title: Note-to-Self Issues - filters: is:open repo:tlinden/rpnc author:@me - layout: - creator: - hidden: true - repoName: - hidden: true - -defaults: - preview: - open: false - width: 100 - -keybindings: - universal: - - key: "shift+down" - builtin: pageDown - - key: "shift+up" - builtin: pageUp - prs: - - key: g - name: gitu - command: > - cd {{.RepoPath}} && /home/scip/bin/gitu - - key: M - name: squash-merge - command: gh pr merge --rebase --squash --admin --repo {{.RepoName}} {{.PrNumber}} - - key: i - name: show ci checks - command: gh pr checks --repo {{.RepoName}} {{.PrNumber}} | glow -p - - key: e - name: edit pr - command: ~/.config/gh-dash/edit-gh-pr {{.RepoName}} {{.PrNumber}} - - key: E - name: open repo in emacs - command: emacsclient {{.RepoPath}} & - issues: - - key: v - name: view - command: gh issue view --repo {{.RepoName}} {{.IssueNumber}} | glow -p - - key: l - name: add label - command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --add-label $(gum choose bug enhancement question dependencies wontfix) - - key: L - name: remove label - command: gh issue --repo {{.RepoName}} edit {{.IssueNumber}} --remove-label $(gum choose bug enhancement question dependencies wontfix) - - key: E - name: open repo in emacs - command: emacsclient {{.RepoPath}} & - -theme: - ui: - sectionsShowCount: true - table: - compact: false - showSeparator: true - colors: - text: - primary: "#E2E1ED" - secondary: "#6770cb" - inverted: "#242347" - faint: "#b0793b" - warning: "#E0AF68" - success: "#3DF294" - background: - selected: "#1B1B33" - border: - primary: "#383B5B" - secondary: "#39386B" - faint: "#8d3e0b" - -repoPaths: - :owner/:repo: ~/dev/:repo - -pager: - diff: delta diff --git a/.goreleaser.yaml b/.goreleaser.yaml deleted file mode 100644 index 00917fe..0000000 --- a/.goreleaser.yaml +++ /dev/null @@ -1,69 +0,0 @@ -# vim: set ts=2 sw=2 tw=0 fo=cnqoj - -version: 2 - -before: - hooks: - - go mod tidy - -gitea_urls: - api: https://codeberg.org/api/v1 - download: https://codeberg.org - -builds: - - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - - freebsd - -archives: - - formats: [tar.gz] - # this name template makes the OS and Arch compatible with the results of `uname`. - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }}_{{ .Tag }} - # use zip for windows archives - format_overrides: - - goos: windows - formats: [zip] - - goos: linux - formats: [tar.gz,binary] - files: - - src: "*.md" - strip_parent: true - - src: "docs/*" - strip_parent: true - - src: Makefile.dist - dst: Makefile - wrap_in_directory: true - -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" - groups: - - title: Improved - regexp: '^.*?(feat|add|new)(\([[:word:]]+\))??!?:.+$' - order: 0 - - title: Fixed - regexp: '^.*?(bug|fix)(\([[:word:]]+\))??!?:.+$' - order: 1 - - title: Changed - order: 999 - -release: - header: "# Release Notes" - footer: >- - - --- - - Full Changelog: [{{ .PreviousTag }}...{{ .Tag }}](https://codeberg.org/scip/rpnc/compare/{{ .PreviousTag }}...{{ .Tag }}) diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml deleted file mode 100644 index d370fc9..0000000 --- a/.woodpecker/build.yaml +++ /dev/null @@ -1,27 +0,0 @@ -matrix: - platform: - - linux/amd64 - goversion: - - 1.24 - -labels: - platform: ${platform} - -steps: - build: - when: - event: [push] - image: golang:${goversion} - commands: - - go get - - go build - - go test - - linter: - when: - event: [push] - image: golang:${goversion} - commands: - - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.5.0 - - golangci-lint --version - - golangci-lint run ./... diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml deleted file mode 100644 index 916c008..0000000 --- a/.woodpecker/release.yaml +++ /dev/null @@ -1,15 +0,0 @@ -# build release - -labels: - platform: linux/amd64 - -steps: - goreleaser: - image: goreleaser/goreleaser - when: - event: [tag] - environment: - GITEA_TOKEN: - from_secret: DEPLOY_TOKEN - commands: - - goreleaser release --clean --verbose diff --git a/Makefile b/Makefile deleted file mode 100644 index 4a8f0ca..0000000 --- a/Makefile +++ /dev/null @@ -1,97 +0,0 @@ - -# Copyright © 2023 Thomas von Dein - -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - - -# -# 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 cmd/$(tool).go buildlocal - -%.1: %.pod -ifdef HAVE_POD - pod2man -c "User Commands" -r 1 -s 1 $*.pod > $*.1 -endif - -cmd/%.go: %.pod -ifdef HAVE_POD - echo "package main" > cmd/$*.go - echo >> cmd/$*.go - echo "var manpage = \`" >> cmd/$*.go - pod2text cmd/$*.pod >> cmd/$*.go - echo "\`" >> cmd/$*.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 testdata - -test: clean - go test ./... $(ARGS) - -testfuzzy: clean - go test -fuzz ./... $(ARGS) - -testlint: test lint - -lint: - golangci-lint run - -lint-full: - golangci-lint run --enable-all --exclude-use-default --disable exhaustivestruct,exhaustruct,depguard,interfacer,deadcode,golint,structcheck,scopelint,varcheck,ifshort,maligned,nosnakecase,godot,funlen,gofumpt,cyclop,noctx,gochecknoglobals,paralleltest,forbidigo,godox,dupword,forcetypeassert,goerr113,gomnd - -singletest: - @echo "Call like this: make singletest TEST=TestPrepareColumns ARGS=-v" - go test -run $(TEST) $(ARGS) - -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: - gh release create v$(VERSION) --generate-notes - -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 diff --git a/Makefile.dist b/Makefile.dist deleted file mode 100644 index cb76bca..0000000 --- a/Makefile.dist +++ /dev/null @@ -1,20 +0,0 @@ -# -*-make-*- - -.PHONY: install all - -tool = rpn -PREFIX = /usr/local -UID = root -GID = 0 - -all: - @echo "Type 'sudo make install' to install the tool." - @echo "To change prefix, type 'sudo make install PREFIX=/opt'" - -install: - install -d -o $(UID) -g $(GID) $(PREFIX)/bin - install -d -o $(UID) -g $(GID) $(PREFIX)/man/man1 - install -d -o $(UID) -g $(GID) $(PREFIX)/share/doc - install -o $(UID) -g $(GID) -m 555 $(tool) $(PREFIX)/sbin/ - install -o $(UID) -g $(GID) -m 444 $(tool).1 $(PREFIX)/man/man1/ - install -o $(UID) -g $(GID) -m 444 *.md $(PREFIX)/share/doc/ diff --git a/README.md b/README.md index d0bc5ef..8acfe1b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ - +[![status-badge](https://ci.codeberg.org/api/badges/15511/status.svg?branch=main)](https://ci.codeberg.org/repos/15511) [![License](https://img.shields.io/badge/license-GPL-blue.svg)](https://codeberg.org/scip/rpnc/raw/branch/master/LICENSE) [![Go Report Card](https://goreportcard.com/badge/codeberg.org/scip/rpnc)](https://goreportcard.com/report/codeberg.org/scip/rpnc) diff --git a/archived/README.md b/archived/README.md deleted file mode 100644 index 920a961..0000000 --- a/archived/README.md +++ /dev/null @@ -1,264 +0,0 @@ -## Reverse Polish Notation Calculator for the commandline - -This is a small commandline calculator which takes its input in -[reverse polish notation](https://en.wikipedia.org/wiki/Reverse_Polish_notation) -form. - -It has an unlimited stack, supports various stack manipulation -commands, can be used interactively or via a pipe and has a collector -mode. It doesn't have any other dependencies than Perl. - -## Usage - -Calculate the summary resistance of parallel resistors with 220, 330 -and 440 Ohm using the following formula: - - 1 / (1/R1 + 1/R2 + 1/R3) - -Here's the sample session: - - 0 % 1 - stack 1: 1 - - 1 % 1 - stack 2: 1 - stack 1: 1 - - 2 % 220 - stack 3: 1 - stack 2: 1 - stack 1: 220 - - 3 % / - stack 2: 1 - stack 1: 0.00454545454545455 - - => 0.00454545454545455 - - 2 % 1 - stack 3: 1 - stack 2: 0.00454545454545455 - stack 1: 1 - - 3 % 330 - stack 4: 1 - stack 3: 0.00454545454545455 - stack 2: 1 - stack 1: 330 - - 4 % / - stack 3: 1 - stack 2: 0.00454545454545455 - stack 1: 0.00303030303030303 - - => 0.00303030303030303 - - 3 % 1 - stack 4: 1 - stack 3: 0.00454545454545455 - stack 2: 0.00303030303030303 - stack 1: 1 - - 4 % 440 - stack 5: 1 - stack 4: 0.00454545454545455 - stack 3: 0.00303030303030303 - stack 2: 1 - stack 1: 440 - - 5 % / - stack 4: 1 - stack 3: 0.00454545454545455 - stack 2: 0.00303030303030303 - stack 1: 0.00227272727272727 - - => 0.00227272727272727 - - 4 % + - stack 3: 1 - stack 2: 0.00454545454545455 - stack 1: 0.0053030303030303 - - => 0.0053030303030303 - - 3 % + - stack 2: 1 - stack 1: 0.00984848484848485 - - => 0.00984848484848485 - - 2 % / - stack 1: 101.538461538462 - - => 101.538461538462 - -The *%* character denotes the interactive prompt. What we basically entered was: - - 1 1 220 / 1 330 / 1 440 / + + / - -Which translates to: - - 1 ((1 / 220) + (1 / 330) + (1 / 440)) - -So, you're entering the numbers and operators as you would do on -paper. To learn more, refer to the Wikipedia page linked above. - -## Collector mode - -Beside traditional RPN you can also enter a special mode, called -*collector mode* by entering the ( 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 + 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 ) 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 u 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 - -* s show the stack -* ss show the whole stack -* sc clear stack -* scx clear last stack element -* sr reverse the stack -* srt rotate the stack - -## Configuration - -* td toggle debugging (-d) -* ts toggle display of the stack (-n) - -## Supported mathematical operators: - -* + add -* - substract -* / divide -* * multiply -* ^ expotentiate -* % percent -* %+ add percent -* %- substract percent -* %d percentual difference -* & bitwise AND -* | bitwise OR -* x bitwise XOR -* m median -* a average -* v pull root (2nd if stack==1) -* ( enter collect mode -* ) leave collect mode - -## Register Commands - -* r put element into register -* rc clear register -* rcx clear last register element - -## Various Commands - -* u undo last operation -* q finish (C-d works as well) -* h show history of past operations -* ? print help - -## Converters - -* tl gallons to liters -* tk miles to kilometers -* tm yards to meters -* tc inches to centimeters -* tkb bytes to kilobytes -* tmb bytes to megabytes -* tgb bytes to gigabytes -* ttb bytes to terabytes - -## Function Comands - -* f NAME CODE define a functions (see ab above) -* fs show list of defined functions - - -## Copyleft - -Copyleft (L) 2019 - Thomas von Dein. -Licensed under the terms of the GPL 3.0. diff --git a/archived/rpnc b/archived/rpnc deleted file mode 100755 index 23bde99..0000000 --- a/archived/rpnc +++ /dev/null @@ -1,679 +0,0 @@ -#!/usr/bin/perl - -use Term::ReadLine; -use Data::Dumper; -use Getopt::Long; -use Data::Dumper; - -use strict; -use warnings; - -my (@stack, @substack, @backup, @subbackup, @hist, @register); -my $term = Term::ReadLine->new('rpn calc'); -my $debug = 0; -my $showstack = 1; -my $mgt = 0; -my $tty = 1; -my $VERSION = '1.10'; -my $sub = 0; -my $maxstack = 10; -my $maxreg = 5; -my $silent = 1; -my $op; - -# management commands, always lower case letters or words -my %commands = ( - # stack commands - s => sub { $mgt = 1; dumpstack(); $mgt = 0; }, - sa => sub { $mgt = 1; dumpstack(1); $mgt = 0;}, - sc => sub { clearstack(); }, - scx => sub { clearstack(1); dumpstack(); }, - sr => sub { reversestack(); }, - srt => sub { rotatestack(); }, - # collector - '(' => sub { $sub = 1 }, - ')' => sub { stack2sub(); }, - # register stuff - r => sub { last_to_reg(); dumpstack(); }, - rcx => sub { clearreg(1); dumpstack(); }, - rc => sub { clearreg(); }, - # main - '?' => sub { help(); }, - u => sub { undo(); dumpstack(); }, - h => sub { showhist(); }, - q => sub { exit; }, - # toggles - td => sub { $debug ^= 1; }, - ts => sub { $showstack ^= 1; }, - # functions - fs => sub { showfuncs(); }, - ); - -# executed 1:1, or aliased -my %alias = qw(^ ** x ^ < << > >> + + - - / / * * & & | |); - -# holds user functions -my %custom; - -# hand coded functions -my %func = ( - '%' => sub { - # X % of Y - my ($a, $b) = getlast(2); - if (defined $b) { - return "($a / 100) * $b"; - } - }, - - '%d' => sub { - # percentual difference - my ($a, $b) = getlast(2); - if (defined $b) { - return "(($a - $b) / $b) * 100" - } - }, - - '%+' => sub { - # Y + (X $ of Y) - my ($a, $b) = getlast(2); - if (defined $b) { - return "$a + (($a / 100) * $b)"; - } - }, - - '%-' => sub { - # Y - (X $ of Y) - my ($a, $b) = getlast(2); - if (defined $b) { - return "$a - (($a / 100) * $b)"; - } - }, - - 'v' => sub { - # square root - my ($a) = getlast(1); - if (defined $a) { - return "$a ** (1 / 2)"; - } - }, - - 'm' => sub { - # median - my @values = getlast(2); # we need 2 or all in sub mode - if (scalar @values >= 2) { - my $c = $#values; - if (scalar @values % 2 == 0) { - # even - return "((sort qw(@values))[$c / 2] + (sort qw(@values))[($c / 2) + 1]) / 2"; - } - else { - # uneven - return "(sort qw(@values))[$c / 2]"; - } - } - else { - print "median only possible with 2 or more values\n"; - undo(); - return 0; - } - }, - - 'a' => sub { - # average - my @values = getlast(2); # we need 2 or all in sub mode - if (scalar @values > 1) { - return "(" . join(' + ', @values) . ") / " . scalar @values; - } - else { - print "average only possible with 2 or more values\n"; - undo(); - return 0; - } - }, - - # converters: - # gallons to liters - 'tl' => sub { return convert("* 3.785") }, - # yards to meters - 'tm' => sub { return convert("* 91.44") }, - # miles to kilometers - 'tk' => sub { return convert("* 1.609") }, - # inches to cm - 'tc' => sub { return convert("* 2.54") }, - # to 'bytes - 'tkb' => sub { return convert("/ 1000") }, - 'tmb' => sub { return convert("/ 1000 / 1000") }, - 'tgb' => sub { return convert("/ 1000 / 1000 / 1000") }, - 'ttb' => sub { return convert("/ 1000 / 1000 / 1000 / 1000") }, - ); - -# math constants, always upper case letters, usable via eval{} -use constant PI => 3.141592653589793; -use constant V2 => 1.414213562373095; -use constant V3 => 1.732050807568877; - -# handle command line -my ($o_h, $o_v, $o_s); -Getopt::Long::Configure( qw(no_ignore_case)); -if (! GetOptions ( - "version|v" => \$o_v, - "help|h" => \$o_h, - "debug|d" => \$debug, - "nostack|n" => \$o_s - ) ) { - help(); - exit; -} - -if ($o_v) { - print "$0 version $VERSION\n"; - exit; -} - -if ($o_h) { - help(); - exit; -} - -if ($o_s) { - $showstack = 0; -} - -# load config, if any -if (-s "$ENV{HOME}/.rpnc") { - if (open RC, "< $ENV{HOME}/.rpnc") { - while () { - chomp(); - next if (/^\s*#/ || /^\s*$/); - looptokenize($_); - } - close RC; - $silent = 0; - } -} - - -# run in commandline mode? -$op = shift; -if ($op) { - $tty = 0; - while () { - 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] [] - -If 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 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 - -~; -} diff --git a/cmd/calc.go b/cmd/calc.go deleted file mode 100644 index e5ab3b7..0000000 --- a/cmd/calc.go +++ /dev/null @@ -1,624 +0,0 @@ -/* -Copyright © 2023-2024 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 . -*/ - -package cmd - -import ( - "errors" - "fmt" - "math" - "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 - precision int - - 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 -%- subtract 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 - -Converter functions: -cm-to-inch yards-to-meters bytes-to-kilobytes -inch-to-cm meters-to-yards bytes-to-megabytes -gallons-to-liters miles-to-kilometers bytes-to-gigabytes -liters-to-gallons kilometers-to-miles bytes-to-terabytes - -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 - 1 { - completions = append(completions, command) - } - } - - for command := range c.ShowCommands { - if len(command) > 1 { - completions = append(completions, command) - } - } - - for command := range c.StackCommands { - if len(command) > 1 { - completions = append(completions, command) - } - } - - for command := range c.Commands { - if len(command) > 1 { - completions = append(completions, command) - } - } - - return completions - } -} - -func NewCalc() *Calc { - calc := Calc{stack: NewStack(), debug: false, precision: Precision} - - calc.Funcalls = DefineFunctions() - calc.BatchFuncalls = DefineBatchFunctions() - calc.Vars = map[string]float64{} - - calc.completer = readline.NewPrefixCompleter( - // custom lua functions - readline.PcItemDynamic(GetCompleteCustomFunctions()), - readline.PcItemDynamic(calc.GetCompleteCustomFuncalls()), - ) - - calc.Space = regexp.MustCompile(`\s+`) - calc.Comment = regexp.MustCompile(`#.*`) // ignore everything after # - calc.Register = regexp.MustCompile(`^([<>])([A-Z][A-Z0-9]*)`) - - // pre-calculate mode switching arrays - calc.Constants = strings.Split(Constants, " ") - - calc.SetCommands() - - return &calc -} - -// setup the interpreter, called from main(), import lua functions -func (c *Calc) SetInt(interpreter *Interpreter) { - c.interpreter = interpreter - - 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 { - prompt := "\033[31m»\033[0m " - batch := "" - - if c.batch { - batch = "->batch" - } - - debug := "" - revision := "" - - if c.debug { - debug = "->debug" - revision = fmt.Sprintf("/rev%d", c.stack.rev) - } - - return fmt.Sprintf("rpn%s%s [%d%s]%s", batch, debug, c.stack.Len(), revision, prompt) -} - -// the actual work horse, evaluate a line of calc command[s] -func (c *Calc) Eval(line string) error { - // remove surrounding whitespace and comments, if any - line = strings.TrimSpace(c.Comment.ReplaceAllString(line, "")) - - if line == "" { - return nil - } - - items := c.Space.Split(line, -1) - - for pos, item := range items { - if pos+1 < len(items) { - c.notdone = true - } else { - c.notdone = false - } - - if err := c.EvalItem(item); err != nil { - return err - } - } - - if c.showstack && !c.stdin { - dots := "" - - if c.stack.Len() > ShowStackLen { - dots = "... " - } - - last := c.stack.Last(ShowStackLen) - - fmt.Printf("stack: %s%s\n", dots, list2str(last)) - } - - return nil -} - -func (c *Calc) EvalItem(item string) error { - num, err := strconv.ParseFloat(item, 64) - - if err == nil { - c.stack.Backup() - c.stack.Push(num) - - return nil - } - - // try time - var hour, min int - _, err = fmt.Sscanf(item, "%d:%d", &hour, &min) - if err == nil { - c.stack.Backup() - c.stack.Push(float64(hour) + float64(min)/60) - - return nil - } - - // try hex - var i int - _, err = fmt.Sscanf(item, "0x%x", &i) - if err == nil { - c.stack.Backup() - c.stack.Push(float64(i)) - - return nil - } - - if contains(c.Constants, item) { - // put the constant onto the stack - c.stack.Backup() - c.stack.Push(const2num(item)) - - return nil - } - - if exists(c.Funcalls, item) { - if err := c.DoFuncall(item); err != nil { - return Error(err.Error()) - } - - c.Result() - - return nil - } - - if exists(c.BatchFuncalls, item) { - if !c.batch { - return Error("only supported in batch mode") - } - - if err := c.DoFuncall(item); err != nil { - return Error(err.Error()) - } - - c.Result() - - return nil - } - - if contains(c.LuaFunctions, item) { - // user provided custom lua functions - c.EvalLuaFunction(item) - - return nil - } - - regmatches := c.Register.FindStringSubmatch(item) - if len(regmatches) == 3 { - switch regmatches[1] { - case ">": - c.PutVar(regmatches[2]) - case "<": - c.GetVar(regmatches[2]) - } - - return nil - } - - // internal commands - // FIXME: propagate errors - if exists(c.Commands, item) { - c.Commands[item].Func(c) - - return nil - } - - if exists(c.ShowCommands, item) { - c.ShowCommands[item].Func(c) - - return nil - } - - if exists(c.StackCommands, item) { - c.StackCommands[item].Func(c) - - return nil - } - - if exists(c.SettingsCommands, item) { - c.SettingsCommands[item].Func(c) - - return nil - } - - switch item { - case "?", "help": - c.PrintHelp() - - default: - return Error("unknown command or operator") - } - - return nil -} - -// 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 { - return Error("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!) - funcresult := function.Func(args) - - if funcresult.Err != nil { - // leave the stack untouched in case of any error - return funcresult.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(funcresult.Res) - - // thanks a lot - c.SetHistory(funcname, args, funcresult.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("= ") - } - - result := c.stack.Last()[0] - truncated := math.Trunc(result) - precision := c.precision - - if result == truncated { - precision = 0 - } - - format := fmt.Sprintf("%%.%df\n", precision) - fmt.Printf(format, result) - } - - 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 luaresult float64 - - var err error - - switch c.interpreter.FuncNumArgs(funcname) { - case 0: - fallthrough - case 1: - luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last()) - case 2: - luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.Last(2)) - case -1: - luaresult, err = c.interpreter.CallLuaFunc(funcname, c.stack.All()) - default: - luaresult, 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, luaresult) - } - - dopush = false - case 1: - a := c.stack.Pop() - c.History("%s(%f) = %f", funcname, a, luaresult) - case 2: - a := c.stack.Pop() - b := c.stack.Pop() - c.History("%s(%f,%f) = %f", funcname, a, b, luaresult) - case -1: - c.stack.Clear() - c.History("%s(*) = %f", funcname, luaresult) - } - - if dopush { - c.stack.Push(luaresult) - } - - 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 exists(c.Vars, name) { - 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 { - if len(key) > 1 { - keys = append(keys, key) - } - } - - sort.Strings(keys) - - return keys -} - -func (c *Calc) PrintHelp() { - output := "Available configuration commands:\n" - - for _, name := range sortcommands(c.SettingsCommands) { - output += fmt.Sprintf("%-20s %s\n", name, c.SettingsCommands[name].Help) - } - - output += "\nAvailable show commands:\n" - - for _, name := range sortcommands(c.ShowCommands) { - output += fmt.Sprintf("%-20s %s\n", name, c.ShowCommands[name].Help) - } - - output += "\nAvailable stack manipulation commands:\n" - - for _, name := range sortcommands(c.StackCommands) { - output += fmt.Sprintf("%-20s %s\n", name, c.StackCommands[name].Help) - } - - output += "\nOther commands:\n" - - for _, name := range sortcommands(c.Commands) { - output += fmt.Sprintf("%-20s %s\n", name, c.Commands[name].Help) - } - - output += "\n" + Help - - // append lua functions, if any - if len(LuaFuncs) > 0 { - output += "\nLua functions:\n" - - for name, function := range LuaFuncs { - output += fmt.Sprintf("%-20s %s\n", name, function.help) - } - } - - Pager("rpn help overview", output) -} diff --git a/cmd/calc_test.go b/cmd/calc_test.go deleted file mode 100644 index 10f079d..0000000 --- a/cmd/calc_test.go +++ /dev/null @@ -1,424 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -import ( - "fmt" - "strconv" - "strings" - "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 _, test := range tests { - testname := fmt.Sprintf("%s .(expect %.2f)", - test.name, test.exp) - - t.Run(testname, func(t *testing.T) { - for _, line := range test.cmd { - if err := calc.Eval(line); err != nil { - t.Error(err.Error()) - } - } - got := calc.stack.Last() - - if len(got) > 0 { - if got[0] != test.exp { - t.Errorf("parsing failed:\n+++ got: %f\n--- want: %f", - got, test.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 \n", calc.stack.All(), line) - switch line { - case "help", "?": - return - } - if err := calc.EvalItem(line); err == nil { - t.Logf("given: <%s>", line) - // not corpus and empty? - if !contains(legal, line) && len(line) > 0 { - item := strings.TrimSpace(calc.Comment.ReplaceAllString(line, "")) - _, hexerr := fmt.Sscanf(item, "0x%x", &hexnum) - _, timeerr := fmt.Sscanf(item, "%d:%d", &hour, &min) - // no comment? - if len(item) > 0 { - // no known command or function? - if _, err := strconv.ParseFloat(item, 64); err != nil { - if !contains(calc.Constants, item) && - !exists(calc.Funcalls, item) && - !exists(calc.BatchFuncalls, item) && - !contains(calc.LuaFunctions, item) && - !exists(calc.Commands, item) && - !exists(calc.ShowCommands, item) && - !exists(calc.SettingsCommands, item) && - !exists(calc.StackCommands, item) && - !calc.Register.MatchString(item) && - item != "?" && item != "help" && - hexerr != nil && - timeerr != nil { - t.Errorf("Fuzzy input accepted: <%s>", line) - } - } - } - } - } - }) -} diff --git a/cmd/command.go b/cmd/command.go deleted file mode 100644 index dbf2bc6..0000000 --- a/cmd/command.go +++ /dev/null @@ -1,361 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -import ( - "bufio" - "fmt" - "log" - "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, - } -} - -func (c *Calc) SetSettingsCommands() Commands { - return 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 - }, - ), - } -} - -func (c *Calc) SetShowCommands() Commands { - return 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])) - } - }, - ), - } -} - -func (c *Calc) SetStackCommands() Commands { - return 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", - CommandSwap, - ), - - "undo": NewCommand( - "undo last operation", - func(c *Calc) { - c.stack.Restore() - }, - ), - - "dup": NewCommand( - "duplicate last stack item", - CommandDup, - ), - - "edit": NewCommand( - "edit the stack interactively", - CommandEdit, - ), - } -} - -// define all management (that is: non calculation) commands -func (c *Calc) SetCommands() { - c.SettingsCommands = c.SetSettingsCommands() - c.ShowCommands = c.SetShowCommands() - c.StackCommands = c.SetStackCommands() - - // 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["d"] = c.SettingsCommands["debug"] - c.SettingsCommands["b"] = c.SettingsCommands["batch"] - c.SettingsCommands["s"] = c.SettingsCommands["showstack"] - - c.SettingsCommands["togglebatch"] = c.SettingsCommands["batch"] - c.SettingsCommands["toggledebug"] = c.SettingsCommands["debug"] - c.SettingsCommands["toggleshowstack"] = c.SettingsCommands["showstack"] - - c.ShowCommands["h"] = c.ShowCommands["history"] - c.ShowCommands["p"] = c.ShowCommands["dump"] - c.ShowCommands["v"] = c.ShowCommands["vars"] - - c.StackCommands["c"] = c.StackCommands["clear"] - c.StackCommands["u"] = c.StackCommands["undo"] -} - -// added to the command map: -func CommandSwap(c *Calc) { - if c.stack.Len() < 2 { - fmt.Println("stack too small, can't swap") - } else { - c.stack.Backup() - c.stack.Swap() - } -} - -func CommandDup(c *Calc) { - item := c.stack.Last() - if len(item) == 1 { - c.stack.Backup() - c.stack.Push(item[0]) - } else { - fmt.Println("stack empty") - } -} - -func CommandEdit(calc *Calc) { - if calc.stack.Len() == 0 { - fmt.Println("empty stack") - - return - } - - calc.stack.Backup() - - // put the stack contents into a tmp file - tmp, err := os.CreateTemp("", "stack") - if err != nil { - fmt.Println(err) - - return - } - - defer func() { - if err := os.Remove(tmp.Name()); err != nil { - log.Fatal(err) - } - }() - - 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 calc.stack.All() { - _, err = fmt.Fprintf(tmp, "%f\n", item) - if err != nil { - fmt.Println(err) - - return - } - } - - if err := tmp.Close(); err != nil { - log.Fatal(err) - } - - // 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 func() { - if err := modified.Close(); err != nil { - log.Fatal(err) - } - }() - - // reset the stack - calc.stack.Clear() - - // and put the new contents (if legit) back onto the stack - scanner := bufio.NewScanner(modified) - for scanner.Scan() { - line := strings.TrimSpace(calc.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 - } - - calc.stack.Push(num) - } - - if err := scanner.Err(); err != nil { - fmt.Println("Error reading from file:", err) - } -} diff --git a/cmd/funcs.go b/cmd/funcs.go deleted file mode 100644 index 1856e13..0000000 --- a/cmd/funcs.go +++ /dev/null @@ -1,578 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -import ( - "errors" - "math" -) - -type Result struct { - Res float64 - Err error -} - -type Numbers []float64 - -type Function func(Numbers) Result - -// 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 NewResult(n float64, e error) Result { - return Result{Res: n, Err: e} -} - -// the actual functions, called once during initialization. -func DefineFunctions() Funcalls { - funcmap := map[string]*Funcall{ - // simple operators, they all expect 2 args - "+": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]+arg[1], nil) - }, - ), - - "-": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]-arg[1], nil) - }, - ), - - "x": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]*arg[1], nil) - }, - ), - - "/": NewFuncall( - func(arg Numbers) Result { - if arg[1] == 0 { - return NewResult(0, errors.New("division by null")) - } - - return NewResult(arg[0]/arg[1], nil) - }, - ), - - "^": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Pow(arg[0], arg[1]), nil) - }, - ), - - "%": NewFuncall( - func(arg Numbers) Result { - return NewResult((arg[0]/100)*arg[1], nil) - }, - ), - - "%-": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]-((arg[0]/100)*arg[1]), nil) - }, - ), - - "%+": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]+((arg[0]/100)*arg[1]), nil) - }, - ), - - "mod": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Remainder(arg[0], arg[1]), nil) - }, - ), - - "sqrt": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Sqrt(arg[0]), nil) - }, - 1), - - "abs": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Abs(arg[0]), nil) - }, - 1), - - "acos": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Acos(arg[0]), nil) - }, - 1), - - "acosh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Acosh(arg[0]), nil) - }, - 1), - - "asin": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Asin(arg[0]), nil) - }, - 1), - - "asinh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Asinh(arg[0]), nil) - }, - 1), - - "atan": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Atan(arg[0]), nil) - }, - 1), - - "atan2": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Atan2(arg[0], arg[1]), nil) - }, - 2), - - "atanh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Atanh(arg[0]), nil) - }, - 1), - - "cbrt": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Cbrt(arg[0]), nil) - }, - 1), - - "ceil": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Ceil(arg[0]), nil) - }, - 1), - - "cos": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Cos(arg[0]), nil) - }, - 1), - - "cosh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Cosh(arg[0]), nil) - }, - 1), - - "erf": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Erf(arg[0]), nil) - }, - 1), - - "erfc": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Erfc(arg[0]), nil) - }, - 1), - - "erfcinv": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Erfcinv(arg[0]), nil) - }, - 1), - - "erfinv": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Erfinv(arg[0]), nil) - }, - 1), - - "exp": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Exp(arg[0]), nil) - }, - 1), - - "exp2": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Exp2(arg[0]), nil) - }, - 1), - - "expm1": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Expm1(arg[0]), nil) - }, - 1), - - "floor": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Floor(arg[0]), nil) - }, - 1), - - "gamma": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Gamma(arg[0]), nil) - }, - 1), - - "ilogb": NewFuncall( - func(arg Numbers) Result { - return NewResult(float64(math.Ilogb(arg[0])), nil) - }, - 1), - - "j0": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.J0(arg[0]), nil) - }, - 1), - - "j1": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.J1(arg[0]), nil) - }, - 1), - - "log": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Log(arg[0]), nil) - }, - 1), - - "log10": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Log10(arg[0]), nil) - }, - 1), - - "log1p": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Log1p(arg[0]), nil) - }, - 1), - - "log2": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Log2(arg[0]), nil) - }, - 1), - - "logb": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Logb(arg[0]), nil) - }, - 1), - - "pow": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Pow(arg[0], arg[1]), nil) - }, - 2), - - "round": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Round(arg[0]), nil) - }, - 1), - - "roundtoeven": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.RoundToEven(arg[0]), nil) - }, - 1), - - "sin": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Sin(arg[0]), nil) - }, - 1), - - "sinh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Sinh(arg[0]), nil) - }, - 1), - - "tan": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Tan(arg[0]), nil) - }, - 1), - - "tanh": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Tanh(arg[0]), nil) - }, - 1), - - "trunc": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Trunc(arg[0]), nil) - }, - 1), - - "y0": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Y0(arg[0]), nil) - }, - 1), - - "y1": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Y1(arg[0]), nil) - }, - 1), - - "copysign": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Copysign(arg[0], arg[1]), nil) - }, - 2), - - "dim": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Dim(arg[0], arg[1]), nil) - }, - 2), - - "hypot": NewFuncall( - func(arg Numbers) Result { - return NewResult(math.Hypot(arg[0], arg[1]), nil) - }, - 2), - - // converters of all kinds - "cm-to-inch": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/2.54, nil) - }, - 1), - - "inch-to-cm": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]*2.54, nil) - }, - 1), - - "gallons-to-liters": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]*3.785, nil) - }, - 1), - - "liters-to-gallons": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/3.785, nil) - }, - 1), - - "yards-to-meters": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]*91.44, nil) - }, - 1), - - "meters-to-yards": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/91.44, nil) - }, - 1), - - "miles-to-kilometers": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]*1.609, nil) - }, - 1), - - "kilometers-to-miles": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/1.609, nil) - }, - 1), - - "bytes-to-kilobytes": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/1024, nil) - }, - 1), - - "bytes-to-megabytes": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/1024/1024, nil) - }, - 1), - - "bytes-to-gigabytes": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/1024/1024/1024, nil) - }, - 1), - - "bytes-to-terabytes": NewFuncall( - func(arg Numbers) Result { - return NewResult(arg[0]/1024/1024/1024/1024, nil) - }, - 1), - - "or": NewFuncall( - func(arg Numbers) Result { - return NewResult(float64(int(arg[0])|int(arg[1])), nil) - }, - 2), - - "and": NewFuncall( - func(arg Numbers) Result { - return NewResult(float64(int(arg[0])&int(arg[1])), nil) - }, - 2), - - "xor": NewFuncall( - func(arg Numbers) Result { - return NewResult(float64(int(arg[0])^int(arg[1])), nil) - }, - 2), - - "<": NewFuncall( - func(arg Numbers) Result { - // Shift by negative number provibited, so check it. - // Note that we check against uint64 overflow as well here - if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 { - return NewResult(0, errors.New("negative shift amount")) - } - - return NewResult(float64(int(arg[0])<": NewFuncall( - func(arg Numbers) Result { - if arg[1] < 0 || uint64(arg[1]) > math.MaxInt64 { - return NewResult(0, errors.New("negative shift amount")) - } - - return NewResult(float64(int(arg[0])>>int(arg[1])), nil) - }, - 2), - } - - // aliases - funcmap["*"] = funcmap["x"] - funcmap["remainder"] = funcmap["mod"] - - return funcmap -} - -func DefineBatchFunctions() Funcalls { - funcmap := map[string]*Funcall{ - "median": NewFuncall( - func(args Numbers) Result { - middle := len(args) / 2 - - return NewResult(args[middle], nil) - }, - -1), - - "mean": NewFuncall( - func(args Numbers) Result { - var sum float64 - for _, item := range args { - sum += item - } - - return NewResult(sum/float64(len(args)), nil) - }, - -1), - - "min": NewFuncall( - func(args Numbers) Result { - var min float64 - min, args = args[0], args[1:] - for _, item := range args { - if item < min { - min = item - } - } - - return NewResult(min, nil) - }, - -1), - - "max": NewFuncall( - func(args Numbers) Result { - var max float64 - max, args = args[0], args[1:] - for _, item := range args { - if item > max { - max = item - } - } - - return NewResult(max, nil) - }, - -1), - - "sum": NewFuncall( - func(args Numbers) Result { - var sum float64 - for _, item := range args { - sum += item - } - - return NewResult(sum, nil) - }, - -1), - } - - // aliases - funcmap["+"] = funcmap["sum"] - funcmap["avg"] = funcmap["mean"] - - return funcmap -} diff --git a/cmd/interpreter.go b/cmd/interpreter.go deleted file mode 100644 index 6f64150..0000000 --- a/cmd/interpreter.go +++ /dev/null @@ -1,180 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -import ( - "errors" - "fmt" - - lua "github.com/yuin/gopher-lua" -) - -type Interpreter struct { - debug bool - script string -} - -// LuaInterpreter is the lua interpreter, instantiated in main() -var LuaInterpreter *lua.LState - -// holds a user provided lua function -type LuaFunction struct { - name string - help string - numargs int -} - -// LuaFuncs 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 := LuaInterpreter.CallByParam(lua.P{ - Fn: LuaInterpreter.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 := LuaInterpreter.DoFile(i.script); err != nil { - panic(err) - } - - // instantiate - LuaFuncs = map[string]LuaFunction{} - - // that way the user can call register(...) from lua inside init() - LuaInterpreter.SetGlobal("register", LuaInterpreter.NewFunction(register)) - - // actually call init() - if err := LuaInterpreter.CallByParam(lua.P{ - Fn: LuaInterpreter.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 provided by calc.Eval(), these are -// non-popped stack items. So the items will only removed from the -// stack when the lua function execution is successful. -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, 1: - // 1 arg variant - if err := LuaInterpreter.CallByParam(lua.P{ - Fn: LuaInterpreter.GetGlobal(funcname), - NRet: 1, - Protect: true, - }, lua.LNumber(items[0])); err != nil { - return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err) - } - case 2: - // 2 arg variant - if err := LuaInterpreter.CallByParam(lua.P{ - Fn: LuaInterpreter.GetGlobal(funcname), - NRet: 1, - Protect: true, - }, lua.LNumber(items[0]), lua.LNumber(items[1])); err != nil { - return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err) - } - case -1: - // batch variant, use lua table as array - table := LuaInterpreter.NewTable() - - // put the whole stack into it - for _, item := range items { - table.Append(lua.LNumber(item)) - } - - if err := LuaInterpreter.CallByParam(lua.P{ - Fn: LuaInterpreter.GetGlobal(funcname), - NRet: 1, - Protect: true, - }, table); err != nil { - return 0, fmt.Errorf("failed to exec lua func %s: %w", funcname, err) - } - } - - // get result and cast to float64 - if res, ok := LuaInterpreter.Get(-1).(lua.LNumber); ok { - LuaInterpreter.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(lstate *lua.LState) int { - function := lstate.ToString(1) - numargs := lstate.ToInt(2) - help := lstate.ToString(3) - - LuaFuncs[function] = LuaFunction{ - name: function, - numargs: numargs, - help: help, - } - - return 1 -} diff --git a/cmd/pager.go b/cmd/pager.go deleted file mode 100644 index 5185d23..0000000 --- a/cmd/pager.go +++ /dev/null @@ -1,120 +0,0 @@ -package cmd - -// pager setup using bubbletea -// file shamlelessly copied from: -// https://github.com/charmbracelet/bubbletea/tree/main/examples/pager - -import ( - "fmt" - "os" - "strings" - - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" -) - -var ( - titleStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Right = "├" - return lipgloss.NewStyle().BorderStyle(b).Padding(0, 1) - }() - - infoStyle = func() lipgloss.Style { - b := lipgloss.RoundedBorder() - b.Left = "┤" - return titleStyle.BorderStyle(b) - }() -) - -type model struct { - content string - title string - ready bool - viewport viewport.Model -} - -func (m model) Init() tea.Cmd { - return nil -} - -func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { - var ( - cmd tea.Cmd - cmds []tea.Cmd - ) - - switch msg := msg.(type) { - case tea.KeyMsg: - if k := msg.String(); k == "ctrl+c" || k == "q" || k == "esc" { - return m, tea.Quit - } - - case tea.WindowSizeMsg: - headerHeight := lipgloss.Height(m.headerView()) - footerHeight := lipgloss.Height(m.footerView()) - verticalMarginHeight := headerHeight + footerHeight - - if !m.ready { - // Since this program is using the full size of the viewport we - // need to wait until we've received the window dimensions before - // we can initialize the viewport. The initial dimensions come in - // quickly, though asynchronously, which is why we wait for them - // here. - m.viewport = viewport.New(msg.Width, msg.Height-verticalMarginHeight) - m.viewport.YPosition = headerHeight - m.viewport.SetContent(m.content) - m.ready = true - } else { - m.viewport.Width = msg.Width - m.viewport.Height = msg.Height - verticalMarginHeight - } - } - - // Handle keyboard and mouse events in the viewport - m.viewport, cmd = m.viewport.Update(msg) - cmds = append(cmds, cmd) - - return m, tea.Batch(cmds...) -} - -func (m model) View() string { - if !m.ready { - return "\n Initializing..." - } - return fmt.Sprintf("%s\n%s\n%s", m.headerView(), m.viewport.View(), m.footerView()) -} - -func (m model) headerView() string { - // title := titleStyle.Render("RPN Help Overview") - title := titleStyle.Render(m.title) - line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(title))) - return lipgloss.JoinHorizontal(lipgloss.Center, title, line) -} - -func (m model) footerView() string { - info := infoStyle.Render(fmt.Sprintf("%3.f%%", m.viewport.ScrollPercent()*100)) - line := strings.Repeat("─", max(0, m.viewport.Width-lipgloss.Width(info))) - return lipgloss.JoinHorizontal(lipgloss.Center, line, info) -} - -func max(a, b int) int { - if a > b { - return a - } - return b -} - -func Pager(title, message string) { - p := tea.NewProgram( - model{content: message, title: title}, - tea.WithAltScreen(), // use the full size of the terminal in its "alternate screen buffer" - tea.WithMouseCellMotion(), // turn on mouse support so we can track the mouse wheel - ) - - if _, err := p.Run(); err != nil { - fmt.Println("could not run pager:", err) - os.Exit(1) - } -} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 66286dd..0000000 --- a/cmd/root.go +++ /dev/null @@ -1,195 +0,0 @@ -/* -Copyright © 2023-2024 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 . -*/ - -package cmd - -import ( - "fmt" - "log" - "os" - "strings" - - "github.com/chzyer/readline" - flag "github.com/spf13/pflag" - lua "github.com/yuin/gopher-lua" -) - -const VERSION string = "2.1.7" - -const Usage string = `This is rpn, a reverse polish notation calculator cli. - -Usage: rpn [-bdvh] [] - -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 - -c, --config load containing LUA code - -p, --precision floating point number precision (default 2) - -v, --version show version - -h, --help show help - -When 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-2025 T.v.Dein` - -func Main() int { - 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.IntVarP(&calc.precision, "precision", "p", Precision, "floating point precision") - - flag.Parse() - - if showversion { - fmt.Printf("This is rpn version %s\n", VERSION) - - return 0 - } - - if showhelp { - fmt.Println(Usage) - - return 0 - } - - if enabledebug { - calc.ToggleDebug() - } - - if showmanual { - man() - - return 0 - } - - // the lua state object is global, instantiate it early - LuaInterpreter = lua.NewState(lua.Options{SkipOpenLibs: true}) - defer LuaInterpreter.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 calc.debug { - fmt.Println("loaded config") - } - } else if calc.debug { - fmt.Println(err) - } - - if len(flag.Args()) > 1 { - // commandline calc operation, no readline etc needed - // called like rpn 2 2 + - calc.stdin = true - if err := calc.Eval(strings.Join(flag.Args(), " ")); err != nil { - fmt.Println(err) - - return 1 - } - - return 0 - } - - // interactive mode, need readline - reader, 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 func() { - if err := reader.Close(); err != nil { - log.Fatal(err) - } - }() - - reader.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 := reader.Readline() - if err != nil { - break - } - - err = calc.Eval(line) - if err != nil { - fmt.Println(err) - } - - reader.SetPrompt(calc.Prompt()) - } - - if len(flag.Args()) > 0 { - // called like this: - // echo 1 2 3 4 | rpn + - // batch mode enabled automatically - calc.batch = true - if err = calc.Eval(flag.Args()[0]); err != nil { - fmt.Println(err) - - return 1 - } - } - - return 0 -} - -func inputIsStdin() bool { - stat, _ := os.Stdin.Stat() - - return (stat.Mode() & os.ModeCharDevice) == 0 -} - -func man() { - Pager("rpn manual page", manpage) -} diff --git a/cmd/rpn.go b/cmd/rpn.go deleted file mode 100644 index f4ab240..0000000 --- a/cmd/rpn.go +++ /dev/null @@ -1,356 +0,0 @@ -package cmd - -var manpage = ` -NAME - rpn - Programmable command-line calculator using reverse polish notation - -SYNOPSIS - Usage: rpn [-bdvh] [] - - 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 - -c, --config load containing LUA code - -p, --precision floating point number precision (default 2) - -v, --version show version - -h, --help show help - - When 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). Time values in hh::mm format are - possible as well. - - 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 - - subtract - / divide - x multiply (alias: *) - ^ power - - Bitwise operators: - - and bitwise and - or bitwise or - xor bitwise xor - < left shift - > right shift - - Percent functions: - - % percent - %- subtract 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 yards-to-meters bytes-to-kilobytes - inch-to-cm meters-to-yards bytes-to-megabytes - gallons-to-liters miles-to-kilometers bytes-to-gigabytes - liters-to-gallons kilometers-to-miles bytes-to-terabytes - - 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" command to put a value into - variable "NAME". Use "-c 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: - 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! - -CONFIGURATION - rpn can be configured via command line flags (see usage above). Most of - the flags are also available as interactive commands, such as "--batch" - has the same effect as the batch command. - - The floating point number precision option "-p, --precision" however is - not available as interactive command, it MUST be configured on the - command line, if needed. The default precision is 2. - -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: - . - -LICENSE - This software is licensed under the GNU GENERAL PUBLIC LICENSE version - 3. - - Copyright (c) 2023-2024 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 - -` diff --git a/cmd/stack.go b/cmd/stack.go deleted file mode 100644 index 83ec620..0000000 --- a/cmd/stack.go +++ /dev/null @@ -1,251 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -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(item float64) { - s.mutex.Lock() - defer s.mutex.Unlock() - - s.Debug(fmt.Sprintf(" push to stack: %.2f", item)) - - s.Bump() - s.linklist.PushBack(item) -} - -// 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 - } - - prevA := s.linklist.Back() - s.linklist.Remove(prevA) - - prevB := s.linklist.Back() - s.linklist.Remove(prevB) - - s.Debug(fmt.Sprintf("swapping %.2f with %.2f", prevB.Value, prevA.Value)) - - s.linklist.PushBack(prevA.Value) - s.linklist.PushBack(prevB.Value) -} - -// Return the last num items from the stack w/o modifying it. -func (s *Stack) Last(num ...int) []float64 { - items := []float64{} - stacklen := s.Len() - count := 1 - - if len(num) > 0 { - count = num[0] - } - - for e := s.linklist.Front(); e != nil; e = e.Next() { - if stacklen <= count { - items = append(items, e.Value.(float64)) - } - stacklen-- - } - - 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]) - } -} diff --git a/cmd/stack_test.go b/cmd/stack_test.go deleted file mode 100644 index f31e418..0000000 --- a/cmd/stack_test.go +++ /dev/null @@ -1,190 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -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) { - stack := NewStack() - stack.Push(5) - got := stack.Pop() - - if got != 5.0 { - t.Errorf("pop failed:\n+++ got: %f\n--- want: %f", - got, 5.0) - } - - if stack.Len() != 0 { - t.Errorf("stack not empty after pop()") - } - }) -} - -func TestPops(t *testing.T) { - t.Run("pops", func(t *testing.T) { - stack := NewStack() - stack.Push(5) - stack.Push(5) - stack.Push(5) - stack.Pop() - - if stack.Len() != 2 { - t.Errorf("stack len not correct after pop:\n+++ got: %d\n--- want: %d", - stack.Len(), 2) - } - }) -} - -func TestShift(t *testing.T) { - t.Run("shift", func(t *testing.T) { - stack := NewStack() - stack.Shift() - - if stack.Len() != 0 { - t.Errorf("stack not empty after shift()") - } - }) -} - -func TestClear(t *testing.T) { - t.Run("clear", func(t *testing.T) { - stack := NewStack() - stack.Push(5) - stack.Push(5) - stack.Push(5) - stack.Clear() - - if stack.Len() != 0 { - t.Errorf("stack not empty after clear()") - } - }) -} - -func TestLast(t *testing.T) { - t.Run("last", func(t *testing.T) { - stack := NewStack() - stack.Push(5) - got := stack.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 stack.Len() != 1 { - t.Errorf("stack modified after last()") - } - }) -} - -func TestAll(t *testing.T) { - t.Run("all", func(t *testing.T) { - stack := NewStack() - list := []float64{2, 4, 6, 8} - - for _, item := range list { - stack.Push(item) - } - - got := stack.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 stack.Len() != len(list) { - t.Errorf("stack modified after last()") - } - }) -} - -func TestBackupRestore(t *testing.T) { - t.Run("shift", func(t *testing.T) { - stack := NewStack() - stack.Push(5) - stack.Backup() - stack.Clear() - stack.Restore() - - if stack.Len() != 1 { - t.Errorf("stack not correctly restored()") - } - - value := stack.Pop() - if value != 5.0 { - t.Errorf("stack not identical to old revision:\n+++ got: %f\n--- want: %f", - value, 5.0) - } - }) -} - -func TestReverse(t *testing.T) { - t.Run("reverse", func(t *testing.T) { - stack := NewStack() - list := []float64{2, 4, 6} - reverse := []float64{6, 4, 2} - - for _, item := range list { - stack.Push(item) - } - - stack.Reverse() - - got := stack.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]) - } - } - }) -} diff --git a/cmd/util.go b/cmd/util.go deleted file mode 100644 index 600598a..0000000 --- a/cmd/util.go +++ /dev/null @@ -1,79 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -import ( - "fmt" - "math" - "strings" -) - -// find an item in a list, generic variant -func contains[E comparable](s []E, v E) bool { - for _, vs := range s { - if v == vs { - return true - } - } - - return false -} - -// look if a key in a map exists, generic variant -func exists[K comparable, V any](m map[K]V, v K) bool { - if _, ok := m[v]; ok { - 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)), " "), "[]") -} - -func Error(m string) error { - return fmt.Errorf("Error: %s", m) -} diff --git a/cmd/util_test.go b/cmd/util_test.go deleted file mode 100644 index ad0c3f2..0000000 --- a/cmd/util_test.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -Copyright © 2023 Thomas von Dein - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see . -*/ - -package cmd - -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") - } - }) -} diff --git a/example.lua b/example.lua deleted file mode 100644 index fa38af7..0000000 --- a/example.lua +++ /dev/null @@ -1,38 +0,0 @@ --- 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 diff --git a/go.mod b/go.mod deleted file mode 100644 index e85dea5..0000000 --- a/go.mod +++ /dev/null @@ -1,34 +0,0 @@ -module rpn - -go 1.24.5 - -require ( - github.com/charmbracelet/bubbles v0.21.0 - github.com/charmbracelet/bubbletea v1.3.10 - github.com/charmbracelet/lipgloss v1.1.0 - github.com/chzyer/readline v1.5.1 - github.com/rogpeppe/go-internal v1.14.1 - github.com/spf13/pflag v1.0.10 - github.com/yuin/gopher-lua v1.1.1 -) - -require ( - github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect - github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect - github.com/charmbracelet/x/ansi v0.10.1 // indirect - github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd // indirect - github.com/charmbracelet/x/term v0.2.1 // indirect - github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/lucasb-eyer/go-colorful v1.2.0 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect - github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect - github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sys v0.36.0 // indirect - golang.org/x/text v0.3.8 // indirect - golang.org/x/tools v0.26.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 870ea95..0000000 --- a/go.sum +++ /dev/null @@ -1,60 +0,0 @@ -github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= -github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= -github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= -github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= -github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= -github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs= -github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk= -github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= -github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= -github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= -github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd h1:vy0GVL4jeHEwG5YOXDmi86oYw2yuYUGqz6a8sLwg0X8= -github.com/charmbracelet/x/cellbuf v0.0.13-0.20250311204145-2c3ea96c31dd/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= -github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= -github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= -github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= -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 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= -github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= -github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= -github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= -github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= -github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= -github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= -github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= -github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= -golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= -golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= diff --git a/main.go b/main.go deleted file mode 100644 index 72c7ae7..0000000 --- a/main.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -Copyright © 2023-2024 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 . -*/ - -package main - -import ( - "os" - "rpn/cmd" -) - -func main() { - os.Exit(cmd.Main()) -} diff --git a/main_test.go b/main_test.go deleted file mode 100644 index 377b2c2..0000000 --- a/main_test.go +++ /dev/null @@ -1,19 +0,0 @@ -package main - -import ( - "testing" - - "github.com/rogpeppe/go-internal/testscript" -) - -func TestMain(m *testing.M) { - testscript.Main(m, map[string]func(){ - "rpn": main, - }) -} - -func TestRpn(t *testing.T) { - testscript.Run(t, testscript.Params{ - Dir: "t", - }) -} diff --git a/rpn.pod b/rpn.pod deleted file mode 100644 index 0bbff7c..0000000 --- a/rpn.pod +++ /dev/null @@ -1,400 +0,0 @@ -=head1 NAME - -rpn - Programmable command-line calculator using reverse polish notation - -=head1 SYNOPSIS - - Usage: rpn [-bdvh] [] - - 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 - -c, --config load containing LUA code - -p, --precision floating point number precision (default 2) - -v, --version show version - -h, --help show help - - When 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 or B or hit one of the C or 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. 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). Time values in hh::mm format are -possible as well. - -=head2 STACK MANIPULATION - -There are lots of stack manipulation commands provided. The most -important one is B which goes back to the stack before the last -math operation. - -You can use B to display the stack. If debugging -is enabled (C<-d> switch or B toggle command), then the backup -stack is also being displayed. - -The stack can be reversed using the B command. However, -sometimes only the last two values are in the wrong order. Use the -B command to exchange them. - -You can use the B command to remove the last number from the -stack. - -=head2 BUILTIN OPERATORS AND FUNCTIONS - -Basic operators: - - + add - - subtract - / divide - x multiply (alias: *) - ^ power - -Bitwise operators: - - and bitwise and - or bitwise or - xor bitwise xor - < left shift - > right shift - -Percent functions: - - % percent - %- subtract 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 yards-to-meters bytes-to-kilobytes - inch-to-cm meters-to-yards bytes-to-megabytes - gallons-to-liters miles-to-kilometers bytes-to-gigabytes - liters-to-gallons kilometers-to-miles bytes-to-terabytes - -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 - 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 " 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 -c 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 which adds two parameters. All -parameters are C numbers. You don't have to worry about stack -management, this is taken care of automatically. - -The function C B be defined, it will be called on -startup. You can do anything you like in there, but you need to call -the C 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 for more details about LUA. - -B - -=head1 CONFIGURATION - -B can be configured via command line flags (see usage -above). Most of the flags are also available as interactive commands, -such as C<--batch> has the same effect as the B command. - -The floating point number precision option C<-p, --precision> however -is not available as interactive command, it MUST be configured on the -command line, if needed. The default precision is 2. - -=head1 GETTING HELP - -In interactive mode you can enter the B 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 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. - -=head1 BUGS - -In order to report a bug, unexpected behavior, feature requests -or to submit a patch, please open an issue on github: -L. - -=head1 LICENSE - -This software is licensed under the GNU GENERAL PUBLIC LICENSE version 3. - -Copyright (c) 2023-2024 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 - -=cut diff --git a/rpnc.mp4 b/rpnc.mp4 deleted file mode 100644 index 63c88c8..0000000 Binary files a/rpnc.mp4 and /dev/null differ diff --git a/t/cmdline-command.txtar b/t/cmdline-command.txtar deleted file mode 100644 index 3c12966..0000000 --- a/t/cmdline-command.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn 1 2 dump -stdout 'Stack revision 2 .0x' diff --git a/t/cmdline-invalidcommand.txtar b/t/cmdline-invalidcommand.txtar deleted file mode 100644 index 826370d..0000000 --- a/t/cmdline-invalidcommand.txtar +++ /dev/null @@ -1,2 +0,0 @@ -! exec rpn 1 2 dumb -stdout 'unknown command or operator' diff --git a/t/cmdline-precision.txtar b/t/cmdline-precision.txtar deleted file mode 100644 index df88c4d..0000000 --- a/t/cmdline-precision.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn -p 4 2 3 / -stdout '0.6667\n' diff --git a/t/cmdline-short-stack.txtar b/t/cmdline-short-stack.txtar deleted file mode 100644 index 193ac8e..0000000 --- a/t/cmdline-short-stack.txtar +++ /dev/null @@ -1,2 +0,0 @@ -! exec rpn 4 + -stdout 'stack doesn''t provide enough arguments' diff --git a/t/cmdlinecalc-debug.txtar b/t/cmdlinecalc-debug.txtar deleted file mode 100644 index 681cde8..0000000 --- a/t/cmdlinecalc-debug.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn -d 44 55 * -stdout 'push to stack: 2420.00\n' diff --git a/t/cmdlinecalc-divzero.txtar b/t/cmdlinecalc-divzero.txtar deleted file mode 100644 index 061b3b1..0000000 --- a/t/cmdlinecalc-divzero.txtar +++ /dev/null @@ -1,2 +0,0 @@ -! exec rpn 100 50 50 - / -stdout 'division by null' diff --git a/t/cmdlinecalc-lua.txtar b/t/cmdlinecalc-lua.txtar deleted file mode 100644 index 229454a..0000000 --- a/t/cmdlinecalc-lua.txtar +++ /dev/null @@ -1,16 +0,0 @@ -exec rpn -d -c test.lua 3 5 lower -stdout '3\n' - --- test.lua -- -function lower(a,b) - if a < b then - return a - else - return b - end -end - -function init() - -- expects 2 args - register("lower", 2, "lower") -end diff --git a/t/cmdlinecalc-time.txtar b/t/cmdlinecalc-time.txtar deleted file mode 100644 index 2660705..0000000 --- a/t/cmdlinecalc-time.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn 09:55 4:15 - -stdout '5.67\n' diff --git a/t/cmdlinecalc.txtar b/t/cmdlinecalc.txtar deleted file mode 100644 index 5b41f71..0000000 --- a/t/cmdlinecalc.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn 44 55 * -stdout '2420\n' diff --git a/t/getusage.txtar b/t/getusage.txtar deleted file mode 100644 index 1ec1b55..0000000 --- a/t/getusage.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn -h -stdout 'This is rpn' diff --git a/t/getversion.txtar b/t/getversion.txtar deleted file mode 100644 index 516b157..0000000 --- a/t/getversion.txtar +++ /dev/null @@ -1,2 +0,0 @@ -exec rpn -v -stdout 'This is rpn version' diff --git a/t/stdin-batch-cmd.txtar b/t/stdin-batch-cmd.txtar deleted file mode 100644 index 115eaa6..0000000 --- a/t/stdin-batch-cmd.txtar +++ /dev/null @@ -1,4 +0,0 @@ -exec echo 1 2 3 4 5 batch median -stdin stdout -exec rpn -[unix] stdout '3\n' diff --git a/t/stdin-batch.txtar b/t/stdin-batch.txtar deleted file mode 100644 index c4db8ee..0000000 --- a/t/stdin-batch.txtar +++ /dev/null @@ -1,4 +0,0 @@ -exec echo 1 2 3 4 5 -stdin stdout -[unix] exec rpn median -[unix] stdout '3\n' diff --git a/t/stdin-calc.txtar b/t/stdin-calc.txtar deleted file mode 100644 index dd8df1a..0000000 --- a/t/stdin-calc.txtar +++ /dev/null @@ -1,4 +0,0 @@ -exec echo 10 10 + -stdin stdout -exec rpn -[unix] stdout '20\n' diff --git a/t/stdin-use-vars.txtar b/t/stdin-use-vars.txtar deleted file mode 100644 index 758befe..0000000 --- a/t/stdin-use-vars.txtar +++ /dev/null @@ -1,13 +0,0 @@ -stdin input.txt -exec rpn -[unix] stdout '28\n' - --- input.txt -- -10 -10 -+ ->SUM -clear -8 -