Numlex
A notepad calculator: type plain lines, get live, checked results — natural math, variables, percentages, dates, unit and currency conversions, and reusable live answer tokens.
Features
Natural calculations
For the full, exhaustive reference of every construct — line forms, operators,
functions, bases, percentages, money, units, currencies, dates, network
queries, number presentation — see the
docs/SYNTAX_REFERENCE.md canonical reference (documents
current main) and the documentation index for the settings and
appearance guide, docs/EXPORT_AND_PRINT.md for PDF
export and printing, and docs/UPDATES.md for the secure in-app
update model. The published documentation lives at
Numlex reads ordinary notebook text — no formula syntax, no cell references. Each line is evaluated strictly; anything it cannot parse stays quiet instead of guessing.
hotel = 1240 1,240
hotel + 10% 1,364
$240 + 10% tip $264.00
sqrt(144) 12
room = 180 × 4 720
round(room / 3, 2) 240
sum(1, 2, 3) 6
20 km to meter 20,000 m
100 C to F 212 F°
Jan 10 + 12 days Jan 22
Every line above runs through the real engine — these are its exact, deterministic
outputs, with no live rates involved. Declare values with name = expression, use
percentages and money with the same grammar ($240 + 10% tip), and do date arithmetic
on plain month names (Jan 10 + 12 days). Named money reads naturally too:
apple = 5$ prices one apple, and 2 apples + 3 apples totals in dollars.
Currency codes are case-insensitive: 500 usd, 500 UsD and 500 USD all mean
500 US dollars (usd, eur, gbp, … — any of the 166 supported ISO codes). A
line may combine currencies with + and -: the first money operand sets the
result currency and every other operand is converted into it with the current
rates, so with 1 EUR = 1.1 USD 500 usd - 300 eur is $227.27. Symbols and codes
mix freely ($500 - 300 eur, 500 usd - €300), and plain scalars or percentages
keep their meaning (500 usd - 20 = $480.00, 500 usd - 10% = $450.00).
Currency × currency and currency ÷ currency stay errors (never a silent USD²),
money × scalar and money ÷ scalar still work, and physical units still never
convert implicitly. If the needed rate pair is missing, the answer is the explicit
Rates unavailable state — never a guessed number, a partial sum or a stale rate.
The same rules apply to named values and to answer tokens:
balance = 500 usd - 300 eur stores US dollars, and TOKEN - 300 eur converts
into the token's currency. Constants are offline, so a single-currency constant
(Rent = 500 usd) resolves normally while a mixed-currency constant stays inactive.
Built-in math functions
The engine shares one pure function registry across every scalar path — free expressions, assignments, constants and unitless answer tokens. Names are case-insensitive, arguments are full expressions (nesting included), and a function-shaped line is strict: an unknown name, a bad arity, a bad comma or a domain failure is a precise error, never a guess.
sqrt,abs,round(x)/round(x, d)(ties round away from zero)min,max,sum,average— variadic, one or more argumentspow(b, e)— same finite contract as the^operatorln(x),log(x)(base 10),log(x, b),log10(x)sin,cos,tan— radians;asin,acos,atanradians(d)anddegrees(r)— explicit unit helpers
sum(1, 234) is two arguments while
sum(1,234) is one grouped literal — and 1,234,567 outside a call is one number,
exactly as before. Built-ins activate only in call position: a variable or constant
named sum still works in sum + 1, and sum(1, 2) calls the function.
Answer tokens join the same engine when they are unitless: sqrt() and
^ 2 evaluate with the token as a live argument, while unit-bearing and
money tokens passed to a function or to ^ fail safely instead of losing their
unit.
Section totals
A standalone total line sums the unitless calculation answers above it — since
the sheet start or the previous total — and starts a fresh section below.
The answer renders semibold under a gray rule placed exactly between the
neighboring answers, and it behaves like any other answer: Copy, per-answer
rounding and tokens all work, and wrapped logical lines keep their gutter
number centered on the block. Money, unit-bearing quantities, booleans, dates
and error rows never enter a section total.
The window also has a persistent bottom Total panel (turn it off under
Settings → General → Show total bar). It is a separate, dimension-agnostic
contract: it adds the evaluated magnitude of every ordinary scalar answer row
once — unitless numbers, unit-bearing quantities (2 kg), money ($3),
named scalars and exact integers — and shows one plain unitless number with no
unit conversion, no FX normalization and no unit or currency suffix
(2 kg, $3 and 4 EUR total 9). Inline total rows are excluded so the
two totals never double-count, and booleans, dates, locations/DMS, error and
blank rows do not contribute. Per-answer rounding and number-format overrides
never change the bottom Total; it follows the global notation and regional
settings.
Conversions
Write 10 km to meter — in works just as well (10 km in meter).
- Around 290 measurement units across length, area, volume, mass, time, speed,
- 166 fiat currencies with live rates: codes (
10 EUR to USD), symbol forms
$100 in EUR) and English names (10 Indian rupees to Japanese yen).
- Rates are cached locally for an hour; if the network is unavailable, Numlex keeps
Type weather in London for the current 2 m temperature in Celsius degrees
(shown C°). The lookup goes to Open-Meteo with no key and no location
permission — only the city you typed is ever sent. Readings are cached for ten
minutes, and the last good value keeps showing if the next refresh fails, so a
flaky connection never blanks your sheet. The city name is tinted exactly like
any other unit, in Light, Dark and custom styling alike.
Answer tokens
Double-click any answer — or type an operator on a new line — and Numlex inserts a
live token at the caret or selection. A token is a small bubble that always displays
the current value of its source line: change the source and the bubble updates
immediately. Several distinct bubbles may reuse the same earlier line — each keeps its
own identity and follows the live value, as in the screenshot above. If a source line
stops evaluating, its tokens stay in place and show the remembered Line N label
instead of a stale number.
Answer menu
Right-click (or Control-click) any answer for its native menu: Copy Answer puts the exact displayed value on the clipboard; the discrete 0…10 dp slider re-rounds just that answer without touching the source line; Delete Line removes the source line. The menu is fully native, so it follows the system appearance in Light and Dark.
Sheets and folders
Work in named sheets with line numbers, syntax tinting and a live results column. Sheets are grouped by the one-level folder tabs pinned to the bottom of the sidebar: the built-in General tab plus any custom folders you create. Selecting a tab filters the sheet list only — your editor and cursor never jump. The main window resizes down to a 260pt content height for compact desks; the default stays 800×600 and the sidebar and answers keep scrolling safely.
Styling and constants
Choose Auto (follow macOS), Light or Dark for the whole app, pick the
Dock/App Switcher icon (Dark — the signed bundle icon and the default — or
Light), then tune font size, font design and role colors, decimal places, input
helpers, line numbers, interface language, the currency display, and an option
to hide the sidebar button once collapsed —
reopen it any time with ⌃⌘S (Control-Command-S) or View > Toggle Sidebar. Define up to 100 app-wide constants (PI = 3.141592653589793,
Sales Tax = 20%, Side = sqrt(4)) that are available in every sheet and resolved
live through the same strict engine — function arguments included.
See docs/SETTINGS_AND_APPEARANCE.md for the full
settings reference: icon-over-label tiles across the top of the window (General,
Editing, Numbers, Constants & Units, Styling, About) with one focused page each —
language, appearance and the application icon live together in General, and
About carries the app identity plus the update controls.
First launch
A genuinely new install opens on a short, native calculation bloom: ten real calculations in the app's own editor palette stream into two airy columns around the app icon, gather into it and disappear, and the icon answers with a one-shot silver splash (fine monochrome rays, a soft expanding wave and a few droplets). The splash settles into a compact final lockup: the icon grows from its streaming footprint to a large 152 pt frame, the official slogan Think freely. We’ll do the math. fades in beneath it in the same neutral tone, and a single monochrome Get Started button — silver on Dark, graphite on Light, matching the icon rather than the accent colour — appears last. The slogan itself is a two-line monochrome lockup (a rounded clause answered by an italic serif, then a quieter rounded line answered by a compact monospaced one), revealed as one calm block. The whole sequence runs once for roughly two and a half seconds and stops; with **Reduce Motion** the large icon, slogan and button are simply there, immediately usable, with no staged animation at all.
Pressing Get Started records a small versioned marker (welcome-v1) in the
app's data directory (separate from your settings and sheets), mounts the
notebook underneath and then slides the welcome panel up out of the content
bounds like a curtain over 0.75 s — the window itself never moves, resizes or
loses its titlebar. The notebook, its sidebar and TextKit are not created behind
the welcome, and focus lands in the editor only after the curtain has cleared.
Existing installs never see the bloom: any prior artifact — the store (even corrupt or unreadable), the currency-rate cache, the weather cache or the location cache — counts as an existing install, and in that case the completion marker is recorded best-effort without touching those files. Closing the window before pressing Get Started leaves no marker, so the bloom returns next launch. Your Light/Dark/Auto choice and app icon are never changed by any of this.
Files and storage
Sheets persist locally in Application Support. The only network traffic is the
background rates refresh plus Open-Meteo lookups for weather in … lines you
type yourself — never GPS or location data. From the File menu, import or export
any sheet as a .nlx file, export it as a PDF, or print it (⌘P) — export and
print are offline and never touch the live editor. Drag a sheet onto a folder
tab to re-file it. See docs/EXPORT_AND_PRINT.md.
Download
- macOS 26 or later, Apple Silicon (arm64).
- 4.8.2 is the current release. 4.8.0 was the first release with secure in-app
- Download the
.dmgfrom the latest release,
- Numlex is ad-hoc signed and not notarized. On first launch, Control-click (or
- Verify your download with the checksums file from the same release:
shasum -a 256 -c SHA256SUMS
Homebrew
You can also install Numlex from the project's own custom tap:
brew install --cask Qulierm/tap/numlex
This uses the project's custom Qulierm/tap, not the official Homebrew cask repo. Numlex is ad-hoc signed and not notarized, so on first launch you still have to Control-click (or right-click) Numlex.app, choose Open, and confirm the prompt.
Build from source
Requires macOS 26 with the Swift 6.2 toolchain; Xcode Command Line Tools are enough
(xcode-select --install).
swift build # debug
swift build -c release # release
The engine suite covers 1,248 shared cases, runnable two ways:
swift test # Swift Testing suite (full Xcode toolchain)
swift run NumlexTests # standalone runner (Command Line Tools only)
Package a signed app bundle:
Scripts/build-app.sh [debug|release] # produces .build/Numlex.app
Architecture
- Native UI — SwiftUI on top of TextKit (
NSTextView) with line numbers, syntax
Sources/NumlexCore— a pure, deterministic parser: tokenizer,
- Persistence — one JSON store in
~/Library/Application Support/Numlex;.nlx
- Rates — fetched from
open.er-api.com, cached for one hour with an 8-second
License
Numlex is available under the MIT License.



