Skip to content

Commit a8e7457

Browse files
committed
RPN v2.0.0 — symbolic stack with 2D rendering
The stack value is a bounded sum of terms Σ (num/den)·√rad·πⁿ: - Exact fractions, k·√m, rational multiples of π and their sums, products and integer powers stay exact: 8 √ 2 × → 4√2, π 2 ÷ → π/2, √2+√3, (1+√2)² → 3+2√2, (√2+√3)(√2−√3) → -1; like terms are collected. - Nested radicals, division by a sum, transcendental functions, overflow or more than 8 terms fall back to a decimal — never a wrongly-exact result. - 2D rendering on the stack (stacked fractions, radicals with a vinculum, raised exponents) via a backend-agnostic layout engine; every level shows its exact form, level 1 also its decimal (≈). The layout code feeds both the EADK screen and a host PNG harness (make -C tests render / render-screen). - Overflow-checked int64 throughout; π exact; factorial via gamma. - Pure host-tested core (make test, 47 checks); device build via nwlink. - Makefile supports PLATFORM=web (.nwb via emcc); docker/ runs the app in the Epsilon web simulator in a browser with only Docker installed. - R↵ identity across the .nwa, README, site and interactive N0120 key map; slogan, badges; landing page, roadmap and CHANGELOG.
1 parent 9beaf18 commit a8e7457

25 files changed

Lines changed: 1308 additions & 401 deletions

.github/workflows/release.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,21 @@ jobs:
3131
- name: Stage release asset
3232
run: cp output/rpn.nwa rpn-${{ github.ref_name }}.nwa
3333

34-
- name: Create GitHub Release
34+
- name: Publish GitHub Release (idempotent)
3535
env:
3636
GH_TOKEN: ${{ github.token }}
37+
TAG: ${{ github.ref_name }}
3738
run: |
38-
gh release create "${{ github.ref_name }}" \
39-
"rpn-${{ github.ref_name }}.nwa#RPN app (${{ github.ref_name }}) — install via my.numworks.com/apps" \
40-
--title "RPN ${{ github.ref_name }}" \
41-
--notes "RPN calculator for NumWorks.
39+
FILE="rpn-${TAG}.nwa"
40+
if gh release view "$TAG" >/dev/null 2>&1; then
41+
echo "Release $TAG exists — updating asset."
42+
gh release upload "$TAG" "$FILE" --clobber
43+
else
44+
gh release create "$TAG" "${FILE}#RPN app (${TAG}) — install via my.numworks.com/apps" \
45+
--title "RPN ${TAG}" \
46+
--notes "RPN calculator for NumWorks.
4247
43-
**Install:** download \`rpn-${{ github.ref_name }}.nwa\` below, then open <https://my.numworks.com/apps>, plug in your calculator and upload the file.
48+
**Install:** download \`rpn-${TAG}.nwa\` below, then open <https://my.numworks.com/apps>, plug in your calculator and upload the file.
4449
4550
See the [project page](https://1e1.github.io/numworks-RPN/) for the full key map."
51+
fi

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Changelog
2+
3+
All notable changes to this project are documented here. This project follows
4+
[Semantic Versioning](https://semver.org/).
5+
6+
## v2.0.0 — Symbolic stack
7+
8+
- The stack value is now symbolic: exact results are kept as a bounded **sum of
9+
terms** `Σ (num/den)·√rad·πⁿ`.
10+
- `8 √ 2 ×``4√2`, `π 2 ÷``π/2`, `√2·√2``2`, `1/√2``√2/2`.
11+
- Sums stay exact too: `√2 + √3`, `π + 1`, `(1+√2)²``3+2√2`,
12+
`(√2+√3)(√2−√3)``-1`; like terms are collected (`√2+√2``2√2`).
13+
- Anything outside that form (nested radicals, division by a sum, `sin`, `ln`,
14+
overflow, or more than 8 terms) falls back to a decimal approximation — a
15+
result is never *wrongly* exact.
16+
- **2D rendering** on the stack: stacked fractions, radicals with a vinculum and
17+
raised exponents; every level shows its exact form and level 1 also shows its
18+
decimal approximation (``).
19+
- π is an exact constant; `→Dec` (`Ans`) forces the decimal form.
20+
- Factorial now uses `gamma(n+1)` for large or non-integer arguments instead of
21+
an unbounded loop.
22+
- All arithmetic remains overflow-checked `int64` with a decimal fallback.
23+
24+
## v1.0.0 — First release
25+
26+
- Reverse Polish Notation calculator as a NumWorks external app (`.nwa`).
27+
- Exact rational arithmetic for `+ − × ÷` and integer powers; IEEE double for
28+
transcendental functions.
29+
- Stack-oriented key map: operator keys act on the stack, RPN-unused keys become
30+
stack operations, `Toolbox` opens a stack menu; RAD/DEG angle mode.
31+
- Pure, host-tested numeric core (`make test`); device build via `nwlink` and
32+
`arm-none-eabi`.
33+
- GitHub Actions for CI, tagged releases and a GitHub Pages site with an
34+
interactive N0120 key map.

Makefile

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,65 @@
1-
# Self-contained build for the NumWorks RPN external app.
1+
# Build the NumWorks RPN external app.
22
#
3-
# make build the device app -> output/rpn.nwa
4-
# make test build & run the host engine tests
5-
# make install build and upload to a plugged-in calculator
3+
# make device app -> output/rpn.nwa (arm-none-eabi)
4+
# make PLATFORM=web web app -> output/rpn.nwb (emscripten)
5+
# make install upload to a plugged-in calculator
6+
# make test build & run the host engine tests
67
# make clean
78
#
8-
# Requirements: arm-none-eabi-gcc (device build) and node/npx (nwlink).
9+
# Requirements: node/npx (nwlink); arm-none-eabi-gcc for device; emcc for web.
910

1011
APP_NAME = rpn
1112
APP_ICON = src/icon.png
1213
OUTPUT_DIR = output
14+
PLATFORM ?= device
1315

1416
NWLINK = npx --yes -- nwlink
1517

16-
CC = arm-none-eabi-gcc
17-
CXX = arm-none-eabi-g++
18-
1918
SOURCES = $(wildcard src/*.cpp)
20-
OBJS = $(patsubst src/%.cpp,$(OUTPUT_DIR)/%.o,$(SOURCES))
21-
22-
# Flags provided by nwlink for the calculator target (Cortex-M7 + EADK headers).
23-
EADK_CFLAGS := $(shell $(NWLINK) eadk-cflags-device)
24-
EADK_LDFLAGS := $(shell $(NWLINK) eadk-ldflags-device)
25-
26-
CXXFLAGS = $(EADK_CFLAGS) -std=c++11 -fno-exceptions -Wall -Os -MMD -MP
27-
CXXFLAGS += -fdata-sections -ffunction-sections
28-
CXXFLAGS += -flto -fno-fat-lto-objects -fwhole-program -fvisibility=internal
29-
30-
LDFLAGS = $(EADK_LDFLAGS) --specs=nano.specs
31-
LDFLAGS += -Wl,-e,main -Wl,-u,eadk_app_name -Wl,-u,eadk_app_icon -Wl,-u,eadk_api_level
32-
LDFLAGS += -Wl,--gc-sections -flinker-output=nolto-rel
33-
# Pull float support into newlib-nano's printf so decimals render (%g).
34-
LDFLAGS += -Wl,-u,_printf_float
19+
OBJDIR = $(OUTPUT_DIR)/$(PLATFORM)
20+
OBJS = $(patsubst src/%.cpp,$(OBJDIR)/%.o,$(SOURCES))
21+
22+
ifeq ($(PLATFORM),device)
23+
CC = arm-none-eabi-gcc
24+
CXX = arm-none-eabi-g++
25+
EADK_CFLAGS := $(shell $(NWLINK) eadk-cflags-device)
26+
CXXFLAGS = $(EADK_CFLAGS) -std=c++11 -fno-exceptions -Wall -Os -MMD -MP
27+
CXXFLAGS += -fdata-sections -ffunction-sections
28+
CXXFLAGS += -flto -fno-fat-lto-objects -fwhole-program -fvisibility=internal
29+
LDFLAGS = $(shell $(NWLINK) eadk-ldflags-device) --specs=nano.specs
30+
LDFLAGS += -Wl,-e,main -Wl,-u,eadk_app_name -Wl,-u,eadk_app_icon -Wl,-u,eadk_api_level
31+
LDFLAGS += -Wl,--gc-sections -flinker-output=nolto-rel -Wl,-u,_printf_float
32+
APP = $(OUTPUT_DIR)/$(APP_NAME).nwa
33+
else ifeq ($(PLATFORM),web)
34+
CC = emcc
35+
CXX = em++
36+
EADK_CFLAGS := $(shell $(NWLINK) eadk-cflags-web)
37+
CXXFLAGS = $(EADK_CFLAGS) -std=c++11 -fno-exceptions -Wall -O0 -g -MMD -MP
38+
LDFLAGS = -sSIDE_MODULE=2 -sEXPORTED_FUNCTIONS=_main -sASYNCIFY=1
39+
LDFLAGS += -sASYNCIFY_IMPORTS=eadk_event_get,_eadk_keyboard_scan_do_scan,eadk_timing_msleep,eadk_display_wait_for_vblank
40+
LDFLAGS += -lc
41+
APP = $(OUTPUT_DIR)/$(APP_NAME).nwb
42+
else
43+
$(error Unknown PLATFORM '$(PLATFORM)' (use device or web))
44+
endif
3545

3646
.PHONY: all
37-
all: $(OUTPUT_DIR)/$(APP_NAME).nwa
47+
all: $(APP)
3848

39-
$(OUTPUT_DIR)/$(APP_NAME).nwa: $(OBJS) $(OUTPUT_DIR)/icon.o
49+
$(APP): $(OBJS) $(OBJDIR)/icon.o
4050
@echo "LD $@"
4151
$(CC) $(CXXFLAGS) $(LDFLAGS) $^ -o $@
4252

43-
$(OUTPUT_DIR)/%.o: src/%.cpp | $(OUTPUT_DIR)
53+
$(OBJDIR)/%.o: src/%.cpp | $(OBJDIR)
4454
@echo "CXX $<"
4555
$(CXX) $(CXXFLAGS) -c $< -o $@
4656

47-
$(OUTPUT_DIR)/icon.o: $(APP_ICON) | $(OUTPUT_DIR)
57+
$(OBJDIR)/icon.o: $(APP_ICON) | $(OBJDIR)
4858
@echo "ICON $<"
4959
$(NWLINK) png-icon-o $< $@
5060

51-
$(OUTPUT_DIR):
52-
mkdir -p $(OUTPUT_DIR)
61+
$(OBJDIR):
62+
mkdir -p $(OBJDIR)
5363

5464
.PHONY: install
5565
install: $(OUTPUT_DIR)/$(APP_NAME).nwa

README.md

Lines changed: 47 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,37 @@
1+
<div align="center">
2+
3+
<img src="src/icon.png" alt="RPN app icon" width="64" height="64">
4+
15
# RPN for NumWorks
26

7+
[![GitHub release](https://img.shields.io/github/v/release/1e1/numworks-RPN?style=flat-square)](https://github.com/1e1/numworks-RPN/releases)
8+
[![GitHub commit activity](https://img.shields.io/github/commit-activity/m/1e1/numworks-RPN?style=flat-square&color=brightgreen)](https://github.com/1e1/numworks-RPN/commits/main)
9+
[![License](https://img.shields.io/github/license/1e1/numworks-RPN?style=flat-square)](https://github.com/1e1/numworks-RPN/blob/main/LICENSE)
10+
11+
### *No parentheses, no `=`.*
12+
13+
</div>
14+
315
A **Reverse Polish Notation** (RPN) calculator as an external app for the
416
[NumWorks](https://www.numworks.com) graphing calculator.
517

618
No parentheses, no `=`: you push numbers onto a stack and operators act on it.
7-
Arithmetic keeps **exact fractions** (`1/3 + 1/6` stays `1/2`), and functions
8-
fall back to decimal approximations — the familiar NumWorks feel, RPN-style.
19+
Results stay **symbolic and exact** where they can — fractions (`1/3 + 1/6`
20+
`1/2`), radicals (`8 √ 2 ×``4√2`), and rational multiples of π (`π 2 ÷`
21+
`π/2`) — and fall back to a decimal approximation otherwise. The familiar
22+
NumWorks feel, RPN-style.
923

1024
➡️ **Project page & key map:** <https://1e1.github.io/numworks-RPN/>
1125

1226
> **Scope.** NumWorks external apps run in a sandbox and cannot call the
13-
> built-in Poincaré engine, so this app ships its own numeric core: exact
14-
> rational arithmetic for `+ − × ÷` and integer powers, IEEE double for
15-
> transcendental functions. Full symbolic simplification would require a native
16-
> build of Epsilon rather than an external app.
27+
> built-in Poincaré engine, so this app ships its own symbolic core. Values are
28+
> held as a bounded **sum of terms** `Σ (num/den)·√rad·πⁿ`, which keeps exact
29+
> fractions, `k√m`, rational multiples of π **and their sums/products**
30+
> (`√2 + √3`, `(1+√2)²``3+2√2`). What still collapses to a decimal: nested
31+
> radicals, division by a sum, transcendental functions (`sin`, `ln`), overflow
32+
> — so a result is never *wrongly* exact. A full expression-tree CAS with 2D
33+
> layout would require a native build of Epsilon rather than an external app
34+
> (see the roadmap).
1735
1836
## Install
1937

@@ -31,12 +49,18 @@ stack. Subtraction and division are `level2 (op) level1`:
3149

3250
```
3351
3 EXE 6 ÷ → 1/2 exact fraction
34-
2 EXE √ → 1.414214 approximation
52+
2 EXE √ → √2 exact radical
53+
8 EXE √ 2 × → 4√2 simplified
54+
2 √ 3 √ + → √2 + √3 sums stay exact
55+
π EXE 2 ÷ → π/2 rational multiple of π
3556
5 EXE 4 EXE − → 1 (5 − 4)
36-
π EXE 2 × → 6.283185
3757
5 ! → 120 exact factorial
3858
```
3959

60+
On the calculator these render in **2D** — stacked fractions, `` with a
61+
vinculum, raised exponents — and level 1 also shows its decimal (``). Press
62+
`Ans` (→Dec) to force the decimal approximation of the top level.
63+
4064
If you enter operands in the wrong order, press **( = SWAP** to exchange the top
4165
two levels.
4266

@@ -90,6 +114,21 @@ The numeric core (`src/value`, `src/stack`, `src/rpn`, `src/input_field`) is pur
90114
C++ with no calculator dependency, so `make test` compiles and runs it on your
91115
host machine.
92116

117+
## Try it in a web simulator (Docker)
118+
119+
Run the app in a browser via the Epsilon web simulator — only Docker is needed
120+
(the emscripten/node toolchain lives inside the container). You need an
121+
[Epsilon](https://github.com/numworks/epsilon) checkout next to this repo (its
122+
folder must contain Epsilon's `Makefile`).
123+
124+
```shell
125+
docker/run.sh # or: EPSILON_DIR=/path/to/epsilon docker/run.sh
126+
```
127+
128+
Then open <http://localhost:8000/epsilon.html?nwb=/rpn.nwb>. The first run builds
129+
the simulator (long; cached in a Docker volume afterwards). You can also build
130+
the web app alone with `make PLATFORM=web` (needs `emcc`).
131+
93132
## Project layout
94133

95134
```

docker/Dockerfile

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Web-simulator dev environment for the RPN app.
2+
#
3+
# Builds the Epsilon web simulator (from a mounted Epsilon checkout) and the RPN
4+
# app (.nwb), then serves them so you can run the app in a browser — no local
5+
# emscripten/node install needed, just Docker.
6+
#
7+
# The emsdk version matches Epsilon's .emsdk-version (4.0.10).
8+
FROM emscripten/emsdk:4.0.10
9+
10+
# Epsilon's asset pipeline and the app build need these on top of emsdk
11+
# (which already ships python3 and node).
12+
RUN apt-get update && apt-get install -y --no-install-recommends \
13+
git python3 python3-pil imagemagick pkg-config \
14+
&& rm -rf /var/lib/apt/lists/*
15+
16+
COPY serve.sh /usr/local/bin/serve.sh
17+
RUN chmod +x /usr/local/bin/serve.sh
18+
19+
WORKDIR /work
20+
EXPOSE 8000
21+
ENTRYPOINT ["/usr/local/bin/serve.sh"]

docker/run.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Build the image and run the web simulator in a browser via Docker.
3+
#
4+
# docker/run.sh
5+
#
6+
# By default it looks for an Epsilon checkout next to this repo:
7+
# ../epsilon/epsilon (the dir that contains Epsilon's Makefile)
8+
# Override with EPSILON_DIR=/path/to/epsilon docker/run.sh
9+
set -euo pipefail
10+
11+
APP_DIR="$(cd "$(dirname "$0")/.." && pwd)"
12+
EPSILON_DIR="${EPSILON_DIR:-$APP_DIR/../epsilon/epsilon}"
13+
14+
if [ ! -f "$EPSILON_DIR/Makefile" ]; then
15+
echo "Epsilon checkout not found at: $EPSILON_DIR" >&2
16+
echo "Set EPSILON_DIR to the folder containing Epsilon's Makefile." >&2
17+
exit 1
18+
fi
19+
EPSILON_DIR="$(cd "$EPSILON_DIR" && pwd)"
20+
21+
echo ">> App: $APP_DIR"
22+
echo ">> Epsilon: $EPSILON_DIR"
23+
24+
docker build -t numworks-rpn-sim "$APP_DIR/docker"
25+
docker run --rm -it -p 8000:8000 \
26+
-v "$EPSILON_DIR":/epsilon \
27+
-v "$APP_DIR":/app \
28+
-v numworks-rpn-simcache:/serve \
29+
numworks-rpn-sim

docker/serve.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env bash
2+
# Container entrypoint: build the Epsilon web simulator + the RPN .nwb, then
3+
# serve them. Expects two mounts:
4+
# /epsilon an Epsilon source tree (the dir that contains its Makefile)
5+
# /app this repository
6+
# and (optionally) a persistent volume on /serve to cache the simulator build.
7+
set -euo pipefail
8+
9+
EPSILON=/epsilon
10+
APP=/app
11+
SERVE=/serve
12+
mkdir -p "$SERVE"
13+
14+
if [ ! -f "$EPSILON/Makefile" ]; then
15+
echo "!! /epsilon does not look like an Epsilon checkout (no Makefile)." >&2
16+
echo " Mount the Epsilon source dir there (see docker/run.sh)." >&2
17+
exit 1
18+
fi
19+
20+
if [ ! -f "$SERVE/epsilon.html" ]; then
21+
echo ">> Building the Epsilon web simulator — first run is long (cached afterwards)…"
22+
( cd "$EPSILON" && make PLATFORM=web WEB_EXTERNAL_APPS=1 -j"$(nproc)" epsilon.html )
23+
cp "$EPSILON"/output/release/web/epsilon.* "$SERVE"/
24+
else
25+
echo ">> Reusing cached Epsilon web simulator in the /serve volume."
26+
fi
27+
28+
echo ">> Building the RPN app for web (.nwb)…"
29+
( cd "$APP" && make PLATFORM=web )
30+
cp "$APP"/output/rpn.nwb "$SERVE"/rpn.nwb
31+
32+
echo
33+
echo "==============================================================="
34+
echo " Open: http://localhost:8000/epsilon.html?nwb=/rpn.nwb"
35+
echo "==============================================================="
36+
echo
37+
cd "$SERVE" && exec python3 -m http.server 8000

docs/ROADMAP.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
# Roadmap — v2: symbolic stack with Poincaré-style presentation
2-
3-
v1.0.0 is a numeric RPN calculator: the stack holds *numbers* (exact rationals
4-
or doubles) and every operator computes eagerly. v2 changes the nature of the
5-
stack.
1+
# Roadmap — symbolic stack with Poincaré-style presentation
2+
3+
> **Status.** v1.0.0 held plain numbers. **v2.0.0 ships a symbolic layer**:
4+
> values are a bounded **sum of terms** `Σ (num/den)·√rad·πⁿ`, so `8 √ 2 ×`
5+
> `4√2`, `π 2 ÷``π/2`, `√2·√2``2`, and sums stay exact too (`√2 + √3`,
6+
> `(1+√2)²``3+2√2`). It also renders results in **2D** (stacked fractions,
7+
> radicals with a vinculum, raised exponents) with the level-1 decimal shown
8+
> beneath. Still remaining toward a *full* Poincaré-grade CAS, staying portable:
9+
> a **general expression tree** — nested radicals (`√(1+√2)`), division by a
10+
> sum, symbolic variables — the harder, higher-risk part.
611
712
## Vision
813

0 commit comments

Comments
 (0)