# Instacalc — full syntax and URL reference for AI agents Instacalc is a plain-text calculator language designed for readability and inspection—just like Markdown. Unlike HTML/JS, which is too difficult and verbose for most humans to audit, Instacalc looks like what you would write on the back of a napkin. Each line ("row") is a variable assignment or expression; later rows can reference earlier ones by name. A calc is a list of rows (.ic file). Calcs are stateless — the entire calc can be encoded directly into a URL, so an agent can construct a working, shareable calculator without any API call or account. Calcs can also load live external data or formulas from GitHub or any HTTPS URL via `import(url)`. This document has two parts: (1) how to write rows, (2) how to turn rows into a URL. ## 1. Row syntax ### Assignment and references ``` principal = 10000 interest_rate = 0.05 time_in_years = 5 total_interest = principal * interest_rate * time_in_years ``` Variable names can contain spaces ("bill amount", "hours per day") but NOT symbols: no `/ ( ) - + *` or other punctuation in a name. Words like "per", "for", "every", "each", "until" are fine inside a name — "miles per gallon" parses as one identifier. ### Units Attach a unit to a value directly; the engine understands unit math and converts/cancels units automatically: ``` distance = 100 km time = 2 hours speed = distance / time // unit computed automatically (km/hour) ``` For RATES, prefer slash-unit syntax so units cancel correctly: ``` wage = 25 dollars / hour hours = 40 pay = hours * wage // dollars, unit cancels correctly ``` Convert explicitly with `->`, `to`, `in`, or `as`: ``` length_in_inches = 12 inches length_in_cm = length_in_inches -> cm 100 km in miles 32 fahrenheit in celsius ``` ### Conditionals If/else MUST be a single line — this is a real parser constraint, not a style preference: ``` score = 85 if (score >= 60) { grade = "Pass" } else { grade = "Fail" } ``` For multi-branch logic, prefer intermediate boolean rows over a long else-if chain: ``` is_a_grade = score >= 90 is_b_grade = score >= 80 if (is_a_grade) { grade = "A" } else if (is_b_grade) { grade = "B" } else { grade = "C" } ``` No loops exist (no `for`, `while`, `do`). Use closed-form math or `map` / `filter` / `reduce` over an array instead. ### Common functions Trig (`sin`, `cos`, `tan`, ... — degrees by default, not radians), rounding (`round`, `ceil`, `floor`, `trunc`), `sqrt`, `pow`, `log`, `ln`, `exp`, aggregate functions over ranges or lists (`sum`, `avg`, `min`, `max`, `median`, `stddev`), and financial functions (`PMT`, `FV`, `PV`, `NPER`, `IRR`, `NPV`). Excel-compatible names work: `PMT(rate, nper, pv)`. ### Verbs — one-line advanced operations **Goal-seek** — find the input that produces a target output. Build it as exactly three rows: an independent input, a named formula that uses it, and a `solve` row referencing both names (never inline the formula into the solve row). Multi-word names work fine here too, same as everywhere else in a calc: ``` wage = 25 dollars / hour hours = 10 // independent input, any default pay = hours * wage // named formula solve pay = 1000 using hours // finds hours where pay = 1000 ``` **Solve an equation:** ``` solve 2x + 3 = 7 for x // x = 2 solve x^2 - 5x + 6 = 0 // x = 2 or x = 3 ``` **Graph / plot:** ``` graph sin(x) plot sin(x), cos(x) // multiple curves, comma-separated graph x^2 from -5 to 5 // explicit x-range ``` **Calculus:** ``` derive x^2 // derivative as a function d/dx x^3 at 2 // derivative at a point -> 12 integral x^2 from 0 to 1 // definite integral -> 0.3333 limit sin(x)/x as x -> 0 // -> 1 taylor e^x to 4 // Taylor series ``` These verbs work bare, or with a name prefix (single- or multi-word) if you want to label the row: `deriv = d/dx x^3 at 2`, or `slope at that point = d/dx x^3 at 2`. ### Presentation decorators Trailing `// ` comments can carry decorators that control how a row is displayed. Use them sparingly: ``` total_cost = base + tax // @hero @prefix($) interest_rate = 5 // slider(0, 15, 0.25) @suffix(%) co2_per_mile = 0.404 // @label(CO₂ per mile) @suffix(kg) ``` - `@hero` — marks the row as the headline answer (put on an output, never an input). - `slider(min, max, step)` — turns an input into a draggable slider; the slider's range must bracket the row's default value. - `@label(text)` / `@prefix(...)` / `@suffix(...)` — display formatting. - `# Section Title` on its own line groups following rows under a heading. ## 2. Encoding rows into a URL A calc's rows become a URL path (or hash) so the calc is fully shareable with no server storage. ### Encoding rules Get these wrong and the link still *opens* — it just silently drops rows, truncates, or errors before the app loads. There is no error message. If you can run code, use the encoder at /claude/ic-link.mjs instead of doing this by hand; it implements exactly the rules below and can decode a link back so you can diff it against your source. - Rows are separated by `;` (path form) or `|` (fragment form). - Spaces become `_`. A literal underscore becomes `__`. A space directly ADJACENT to a literal underscore becomes `%20` instead of `_` — otherwise the run of underscores is ambiguous and decodes wrong. - Runs of two or more spaces collapse to one (they're display-equivalent). - **`%` becomes `%25`.** A raw `%` starts a percent-escape, so `18% of 54` produces an invalid escape and the request fails in the router BEFORE the app loads. Percent math is not an edge case in a calculator — this is the single most common way a hand-built link dies. - **`#` becomes `%23`.** A raw `#` starts the URL fragment, so everything after it — every remaining row — never reaches the app. `# Section` rows and `# Title` rows are ordinary content, so this comes up constantly. - **`?` becomes `%3F`.** A raw `?` starts the query string and truncates the calc there. Ternaries (`x > 5 ? a : b`) and questions in prose both hit it. - **`"` `<` `>` `\` `` ` `` `{` `}` become `%22 %3C %3E %5C %60 %7B %7D`.** RFC 3986 excludes these from URLs entirely, so messengers and mail clients cut the pasted link at the first one — the recipient opens half a calc. - **A literal `;` or `|` INSIDE a row becomes `%3B` / `%7C`**, so it isn't read as a row separator. Prose with a semicolon is the usual case. - Left alone, deliberately: `= + * / ^ ( ) [ ] $ @ , . : ' -` and all letters and digits. A row's own `=` (`price=40`) is never encoded, and `/` inside a row — division, a rate unit (`dollars / hour`), a `d/dx` verb, or the `//` that starts a decorator comment — is never encoded either; the router separates row content from a `/present`-style view tail correctly. - Decoding is the exact inverse, in this order: `__` → a literal `_` (greedy, left to right), then `_` → a space, then `%XX` → the byte. - A leading `=` after the domain means "no title — treat the first segment as a row." Without it, a bare first segment is read as the calc's title. (This one leading `=` — right after the domain, before any row content — is the one place `=` has special meaning; it's a marker, not row content.) ### Shapes | Shape | Example | Use it when | |---|---|---| | Readable path | `/bill=50;tip=bill*0.20` | Default — human-readable URL | | No-title | `/=x=10;y=20;z=x+y` | First row shouldn't become the title | | Title-prefix | `/My_Trip;x=10;y=20` | Want a custom title ("My Trip") | | Fragment | `/#bill=50\|tip=bill*0.20` | Long calc, or values you don't want in server logs (the fragment is never sent to the server) | ### Worked example Rows: ``` bill amount = 50 tip percentage = 18 tip = bill_amount * (tip_percentage / 100) total = bill_amount + tip ``` Encoded (spaces -> `_`, joined with `;`, `/` left as-is since it's inside row content, not a path boundary): ``` /=bill_amount=50;tip_percentage=18;tip=bill_amount_*_(tip_percentage_/_100);total=bill_amount_+_tip ``` ### Worked example with a decorator and a view mode Rows, with the result marked as the hero and formatted as currency: ``` total cost = 100 + 20 // @hero @prefix($) ``` **Plain calc-view URL** — one underscore for the space before `//`, same as any other space; `@` and `$` need no encoding: ``` /=total_cost=100_+_20_//_@hero_@prefix($) ``` **Same calc, opened directly in Present mode** — append `/present` as a path tail. The row content is UNCHANGED — still one underscore before `//`, and `/present` is still correctly recognized as the view tail rather than part of the row: ``` /=total_cost=100_+_20_//_@hero_@prefix($)/present ``` ### View mode Append the view as a path tail: `//present` (renders a clean, presentation-style layout). `?view=present` also works as a legacy query param. ### Compressed form (large calcs) `/#data=` is a real, supported format, decoded on every route. The JSON is `{"v": 2, "rows": [{"text": ""}, …]}` — a leading `# Title` row carries the title, exactly as in the readable form. Use it when the readable link runs past ~2000 characters (messengers and mail clients truncate long URLs) or when rows contain a literal `;` or `|` — the gzip payload has no separator grammar, so nothing can re-split it. Trade-off: the fragment is never sent to the server, so the link cannot unfurl with a title or preview image, and it can't be used as an `/embed/` URL. Prefer the readable form when it fits. `#v1:lz:` and other prefixes are reserved but NOT implemented — don't emit them. ## Fetching an existing calc Append `.md` to any calc URL, or send `Accept: text/markdown`, to receive that calc's title and rows as plain Markdown (its exact source, ready to copy into a new calc or modify). ## More examples See [/gallery](/gallery) for hundreds of complete, working calcs across finance, health, engineering, and everyday math.