User Guide

Complete guide to CalcMark features, editor shortcuts, and workflows.

Contents #


Editor Shortcuts #

The CalcMark editor provides keyboard shortcuts for common actions. Press F1 for full help inside the editor.

File #

ShortcutAction
Ctrl+NNew empty document
Ctrl+SSave document
Ctrl+OOpen file
Ctrl+TExport to format
Ctrl+QQuit editor

Edit #

ShortcutAction
Ctrl+ZUndo last change
Ctrl+YRedo last change
Ctrl+KDelete current line
Ctrl+FAdd YAML frontmatter
Ctrl+ASelect all
Ctrl+CCopy selection
Ctrl+XCut selection
Ctrl+VPaste
Ctrl+BackspaceDelete word

View #

ShortcutAction
Ctrl+PCycle preview mode
Ctrl+H / F1Open command menu

Navigation #

ShortcutAction
Opt+Left / Opt+BMove to previous word
Opt+Right / Opt+FMove to next word
Ctrl+HomeJump to document start
Ctrl+EndJump to document end
Ctrl+Left / Ctrl+RightMove word left/right
Ctrl+DScroll down half page
Ctrl+UScroll up half page
Shift+ArrowExtend selection

Locale Formatting #

CalcMark can format output numbers using locale-specific decimal and thousands separators. This affects how results are displayed in all output modes (editor, REPL, eval, convert).

Setting Your Locale #

Per-command with the --locale flag:

cm eval --locale=de-DE budget.cm
cm convert doc.cm --to=html --locale=fr-FR

Permanently in your config file:

# ~/.config/calcmark/config.toml
locale = "de-DE"

The --locale flag overrides the config file for that invocation.

Example: Same Document, Different Locales #

Given a CalcMark document:

price = $1500
pi = 3.14159
users = 1500000
weight = 50.5 kg
Results
price = $1500$1,500.00
pi = 3.141593.14159
users = 15000001.5M
weight = 50.5 kg50.5 kg
Valueen-US (default)de-DEfr-FR
price$1,500.00$1.500,00$1 500,00
pi3.141593,141593,14159
users1.5M1,5M1,5M
weight50.5 kg50,5 kg50,5 kg

What Stays the Same #

Regardless of locale, these never change:

  • Input syntax – always use . for decimals and , or _ for thousands in your source
  • K/M/B/T suffixes – always English letters
  • Currency symbols – always prefix position ($, , etc.)
  • CalcMark formatcm convert --to=cm is always locale-independent

JSON Output #

When exporting to JSON, each result includes a locale-formatted display value and structured type information:

cm convert budget.cm --to=json --locale=de-DE
{
  "source": "price = $1500",
  "value": "$1.500,00",
  "type": "currency",
  "numeric_value": 1500,
  "unit": "USD",
  "variable": "price"
}

Use type for dispatch, numeric_value + unit for computation, and value for display. The type, numeric_value, and unit fields are always locale-independent. See Configuration: JSON Output for details.

Exporting Results #

Convert CalcMark files to other formats using cm convert:

cm convert doc.cm --to=html              # HTML to stdout
cm convert doc.cm --to=md -o doc.md      # Markdown file
cm convert doc.cm --to=json              # JSON to stdout
cm convert doc.cm --to=html -T tpl.html  # Custom HTML template

Use cm eval for quick evaluation, or pipe directly into cm:

cm eval budget.cm            # Print final results
cm eval -v budget.cm         # Show all intermediate values
echo "1 + 2" | cm           # Evaluate piped input (auto-detects pipe)
echo "1 + 2" | cm --format json  # JSON output for scripting/agents
cm eval --locale=de-DE budget.cm  # German number formatting

All export formats respect the --locale flag (or config setting) except cm format, which stays locale-independent.

Sharing with GitHub Gist #

Share CalcMark documents as GitHub Gists directly from the editor. This feature requires the GitHub CLI (gh) to be installed and authenticated.

Prerequisites #

Install and authenticate the GitHub CLI:

# Install
brew install gh          # macOS
sudo apt install gh      # Debian/Ubuntu

# Authenticate
gh auth login

Share To Gist #

Open the command menu (Ctrl+H or F1), select Share To Gist, then choose visibility (public or secret) and add an optional description. Press Enter to share. The Gist URL is copied to your clipboard.

If you are not authenticated, CalcMark will launch gh auth login interactively and retry after you sign in.

Open From Gist #

Open the command menu, select Open From Gist, then paste a Gist URL or ID. CalcMark fetches the Gist content and loads it into the editor. If the Gist contains multiple files, .cm files are preferred.

Note: Sharing with GitHub Gist is not available in the browser (WASM) build.

Language Features #

Supported Units #

CalcMark supports a wide range of units across categories:

  • Area: cm², m², km², ha, in², ft², yd², mi², acre
  • DataSize: byte, KB, MB, GB, TB, PB (and binary: KiB, MiB, GiB, TiB)
  • Energy: J, kJ, cal, kcal, kWh
  • Length: m, cm, mm, km, in, ft, yd, mi, nmi (nautical mile)
  • Mass: mg, g, kg, metric ton (t), oz, lb
  • Power: W, kW, MW, hp
  • Speed: m/s, km/h, mph, knot
  • Temperature: C, F, K
  • Volume: mL, L, tsp, tbsp, cup, pt, qt, gal

Time units (second, minute, hour, day, week, month, year) are used in durations and rates but are not a conversion category.

Run cm help constants for the complete list with aliases and descriptions.

Unit Conversion #

Convert between compatible units using in or as:

distance = 5 miles
distance_km = distance in km

temp_c = 20 celsius
temp_f = temp_c in fahrenheit

file_size = 1.5 GB
file_size_mb = file_size in MB
Results
distance = 5 miles5 mi
distance_km = distance in km8.05 km
temp_c = 20 celsius20 celsius
temp_f = temp_c in fahrenheit68 fahrenheit
file_size = 1.5 GB1.5 GB
file_size_mb = file_size in MB1,536 MB

Currency Conversion #

Convert between currencies using in with exchange rates defined in YAML frontmatter:

---
exchange:
  USD_EUR: 0.92
  EUR_GBP: 0.86
---
price_usd = $100
price_eur = price_usd in EUR

salary = 50000 EUR
salary_gbp = salary in GBP
Error
line 2: no exchange rate defined for USD → EUR; add to frontmatter: exchange:
  USD_EUR: <rate>

Exchange rates use the format FROM_TO: rate where 1 unit of FROM equals rate units of TO.

Global Variables #

Define reusable values in the frontmatter that can be referenced throughout your document:

---
globals:
  base_date: Jan 15 2025
  tax_rate: 0.32
  base_price: $100
  sprint_length: 2 weeks
  bandwidth: 100 MB/s
---

Reference globals in expressions with the @globals. prefix:

net_price = @globals.base_price * (1 - @globals.tax_rate)
project_end = @globals.base_date + @globals.sprint_length * 6
monthly_transfer = @globals.bandwidth over 30 days

Globals support all CalcMark literal types:

  • Numbers: 42, 3.14, 1.5K, 25%
  • Quantities: 10 meters, 5 kg, 100 MB
  • Currencies: $100, 50 EUR
  • Dates: Jan 15 2025
  • Durations: 5 days, 2 weeks
  • Rates: 100 MB/s, $50/hour
  • Booleans: true, false

Globals must be literal values. Expressions like 1 + 1 are not allowed.

@Directive References #

Use @scale and @globals.name to reference frontmatter values in expressions:

per_unit = total_cost / @scale
tax = income * @globals.tax_rate

@scale resolves to the numeric scale factor from frontmatter. @globals.name resolves to the typed value of a named global. These are the only supported directives — @exchange, @convert_to, and other names produce errors.

@globals without a field name (e.g., bare @globals) is a parser error. Use @globals.name to reference a specific global.

Template Variables #

Use {{variable_name}} in prose to embed calculated values. Tags are resolved after all calculations run, so forward references work — put a summary at the top and calculations below:

## Summary

Total cost: {{total_cost}}
Per person: {{per_person}}


headcount = 12
rate = $150000
total_cost = headcount * rate
per_person = total_cost / headcount

The summary renders with $1.8M and $150K substituted in. If a variable is not defined, the {{tag}} is left as-is in the output.

Inline Patterns #

Interpolated values render bold in markdown output and are wrapped in <span class="cm-interpolated"> in HTML. You can combine them with other markdown formatting:

The grand total is {{total_cost}}.

_Headcount: {{team_size}}_

## Budget: {{annual_budget}}

| Metric | Value |
|--------|-------|
| Revenue | {{revenue}} |
| Margin | {{margin}} |

Backticks around {{var}} are automatically stripped — `{{cost}}` renders as $1.8M, not $1.8M. This prevents interpolated values from appearing as inline code in HTML.

Tips #

  • Forward references are the killer feature. Write your executive summary first, add calculations below, and the summary stays current when assumptions change.
  • Whitespace is tolerated. {{ total_cost }} works the same as {{total_cost}}.
  • Undefined variables are safe. {{unknown}} passes through unchanged — you won’t get errors for tags that don’t match a variable.
  • Scale and convert_to apply. Interpolated values match what CalcBlock results display, including any frontmatter transforms.

See the Language Reference: Template Interpolation for the complete specification, the Services P&L for a production dashboard with interpolated summary tables, and the Household Budget for inline results in prose.

Scale and Convert #

Use scale and convert_to in the frontmatter to transform results across an entire document. This is useful for recipes, engineering estimates, and any document where you want to change units or multiply quantities globally.

Scaling #

Scaling is explicit — you specify which categories to scale in unit_categories. A bare scale: 2 sets the factor (accessible via @scale) but scales nothing on its own.

---
scale:
  factor: 2
  unit_categories: [Mass, Volume]
---
flour = 1.5 cups       → 3 cups (doubled — Volume)
eggs = 3 eggs           → 3 eggs (unchanged — custom unit, not in categories)
oven = 350 fahrenheit   → 350 fahrenheit (unchanged — not in categories)
price = $5.00           → $5.00 (unchanged — not in categories)

flour displays as 3 cups because Volume is in the category list. eggs is unchanged because eggs is a custom unit — add Custom to unit_categories to scale it. Temperature and currency are unaffected unless you add Temperature or Currency to unit_categories.

In the editor, scaled values show a × (multiply) suffix in the results pane. Converted values show a (bullet) suffix. When both scaling and conversion apply, the result shows ו. The context footer shows @scale = N when you’re on a scaled line.

Converting #

Convert all quantities to a target measurement system:

---
convert_to: si
---
distance = 5 miles       -> 8.05 km
weight = 10 pounds       -> 4.54 kg
temp = 72 fahrenheit     -> 22.2222 celsius

distance displays as 8.05 km, weight as 4.54 kg, and temp as 22.2222 celsius. Converted values show a suffix in the editor results pane. If you use the in keyword explicitly (e.g., distance in feet), that conversion takes priority over convert_to.

Both Together #

When both are present, scale applies first, then convert:

---
scale:
  factor: 2
  unit_categories: [Mass]
convert_to: si
---
butter = 4 ounces       -> 227 g (doubled, then converted)

butter is first doubled to 8 ounces (Mass is in unit_categories), then converted to 227 g. In the editor, this result shows ו — both the scale and convert indicators.

Filtering by Category #

Limit scale or convert to specific unit categories:

---
scale:
  factor: 4
  unit_categories: [Mass, Volume]
convert_to:
  system: si
  unit_categories: [Length]
---

Valid categories: Area, Currency, Custom, DataSize, Energy, Length, Mass, Number, Power, Speed, Temperature, Volume. Custom matches units not in the standard library (e.g., bananas, eggs). These are derived from the unit definitions — run cm help frontmatter for the current list. Use unit_categories: [All] to scale every category.

See the Recipe Scaling example for a complete walkthrough, and the Language Reference — Frontmatter for the full specification.

Transform Indicators #

The editor results pane shows symbols next to values that have been transformed:

IndicatorMeaningExample
×Scaled4 buns× — factor applied
Converted4.54 kg• — unit system changed
וBoth227 gו — scaled then converted
(none)Untransformed10 m — no transforms applied

Built-in Functions #

accumulate

Calculate total from a rate over time

Syntax
accumulate(rate, time)
Example
accumulate(100 req/s, 1 hour) → 360000 req

avg

Calculate the average of values

Syntax
avg(a, b, c, ...)
Aliases
average, mean, average of (input syntax)
Example
avg(10, 20, 30) → 20

capacity

Calculate how many units needed for a given load

Syntax
capacity(demand, capacity_per_unit, unit) or capacity(demand, capacity_per_unit, unit, buffer)
Aliases
requires
Example
capacity(10000 req/s, 500 req/s, server) → 20 servers

compound

Calculate compound growth over time periods

Syntax
compound(principal, rate, periods, modifier?)
Aliases
compound...by...over (input syntax), compound...by...monthly...over (input syntax)
Example
compound(1000, 5%, 10 years, monthly) → 1647.01

compress

Estimate compressed data size

Syntax
compress(size, algorithm)
Aliases
compress...using (input syntax)
Example
compress 1 GB using gzip

convert_rate

Convert a rate to a different time unit

Syntax
convert_rate(rate, unit)
Example
convert_rate(1000 req/s, minute) → 60000 req/min

depreciate

Calculate declining balance depreciation over time

Syntax
depreciate(value, rate, periods, salvage?)
Aliases
depreciate...by...over...to (input syntax)
Example
depreciate(10000, 20%, 5) → 3276.80

downtime

Calculate downtime from availability percentage

Syntax
downtime(availability, period)
Example
downtime(99.9%, month) → 43.2 minutes

grow

Calculate linear growth by adding a fixed amount each period

Syntax
grow(amount, increment, periods)
Aliases
grow...by...over (input syntax)
Example
grow(100, 20 GB, 5) → 200 GB

number

Extract the numeric value from any type

Syntax
number(value)
Example
number(10 kg) → 10

read

Time to read data from storage

Syntax
read(size, storage_type)
Aliases
read...from (input syntax)
Example
read 100 MB from ssd

rtt

Network round-trip time for a scope

Syntax
rtt(scope)
Aliases
round trip time
Example
rtt(regional) → 10 ms

seek

Access latency for storage type

Syntax
seek(storage_type)
Example
seek(hdd) → 10 ms

sqrt

Calculate the square root

Syntax
sqrt(n)
Aliases
square root of (input syntax)
Example
sqrt(16) → 4

sum

Calculate the sum of values

Syntax
sum(a, b, c, ...)
Aliases
sum of (input syntax), total
Example
sum($100, $200, $300) → $600

throughput

Network bandwidth for a connection type

Syntax
throughput(network_type)
Example
throughput(gigabit) → 125 MB/s

transfer_time

Time to transfer data over a network

Syntax
transfer_time(size, scope, network)
Aliases
transfer...across (input syntax)
Example
transfer 1 GB across regional gigabit

See the Function Reference below for detailed examples of every function, including natural language forms.

Function Reference #

Detailed examples for every built-in function, showing both function-call and natural language syntax.

avg / average of #

avg(10, 20, 30)                    -> 20
avg(1, 2, 3, 4, 5)                -> 3
average of 100, 200, 300           -> 200  (NL form)
avg($100, $200, $300)              -> $200.00  (preserves currency)

sum / sum of #

sum($100, $200, $300)              -> $600.00
sum(10, 20, 30)                    -> 60
sum of total_a, total_b, total_c   -> (NL form, sums variables)

number #

number(10 kg)                      -> 10
number($250)                       -> 250
number(3.5 hours)                  -> 3.5

Strips the unit or currency from any value, returning a plain number. Useful when you need the raw numeric value for a calculation that doesn’t preserve units.

sqrt / square root of #

sqrt(16)                           -> 4
sqrt(2)                            -> 1.4142...
square root of 144                 -> 12  (NL form)
sqrt($100)                         -> $10.00  (preserves currency)

accumulate / over #

accumulate(100 MB/s, 1 hour)       -> 360000 MB
accumulate($75/hour, 8 hours)      -> $600
100 MB/s over 1 day                -> ~8.64 TB  (keyword form)
$120000/year over 1 month          -> $10000

convert_rate #

convert_rate(1000 req/s, minute)   -> 60000 req/min
convert_rate($120000/year, month)  -> $10000/month

capacity / at...per #

capacity(10 TB, 2 TB, disk)              -> 5 disks
capacity(10000 req/s, 500 req/s, server) -> 20 servers
10 TB at 2 TB per disk                   -> 5 disks  (NL form)
10000 req/s at 450 req/s per server with 20% buffer -> 27 servers

downtime #

downtime(0.999, year)              -> 8.76 hours
downtime(0.999, month)             -> 43.2 minutes
downtime(0.9999, month)            -> 4.32 minutes

rtt #

rtt(local)                         -> 0.5 ms
rtt(regional)                      -> 10 ms
rtt(continental)                   -> 50 ms
rtt(global)                        -> 150 ms

throughput #

throughput(gigabit)                 -> 125 MB/s
throughput(ten_gig)                -> 1250 MB/s
throughput(wifi)                   -> 12.5 MB/s
throughput(five_g)                 -> 50 MB/s

transfer_time / transfer...across #

transfer_time(1 GB, regional, gigabit)   -> ~8 seconds
transfer_time(500 MB, continental, gigabit)
transfer 1 GB across regional gigabit    -> (NL form)
transfer 100 MB across local ten_gig

data = 500 KB
transfer data across continental ten_gig -> (variable reference)
Results
transfer_time(500 MB, continental, gigabit)4.05 second
transfer 100 MB across local ten_gig0.0805 second
data = 500 KB500 KB

read / read...from #

read(100 MB, ssd)                  -> ~0.18 seconds
read(1 GB, nvme)                   -> ~0.29 seconds
read 100 MB from ssd               -> (NL form)
read 10 GB from pcie_ssd

data = 5 MB
read data from nvme                -> (variable reference)
Results
read 10 GB from pcie_ssd1.46 second
data = 5 MB5 MB

seek #

seek(hdd)                          -> 10 ms
seek(ssd)                          -> 0.1 ms
seek(nvme)                         -> 0.01 ms
db_query_hdd = seek(hdd) + read(5 MB, hdd)
Results
db_query_hdd = seek(hdd) + read(5 MB, hdd)0.0433 second

compress / compress...using #

compress(1 GB, gzip)               -> ~333 MB
compress(500 MB, lz4)              -> ~250 MB
compress(2 GB, zstd)               -> ~571 MB
compress 1 GB using gzip            -> (NL form)
compress 500 MB using lz4

data = 1 GB
compress data using zstd            -> (variable reference)
Results
compress 500 MB using lz4250 MB
data = 1 GB1 GB

compound / compound...by...over #

compound($1000, 5%, 10)                              -> $1628.89
compound(500 customers, 20%, 12)                     -> 4458.05 customers
compound($1000, 5%, 10 years, monthly)               -> $1647.01
compound($1000, 5%, 10 years, quarterly)             -> $1643.62
compound $1000 by 5% over 10 years                   -> $1628.89  (NL form)
compound $1000 by 5% monthly over 10 years
compound $1000 by 5% per month over 12 months
Results
compound $1000 by 5% monthly over 10 years$1,647.01
compound $1000 by 5% per month over 12 months$1,795.86

grow / grow...by...over #

grow(100, 20, 5)                   -> 200
grow($500, $100, 36)               -> $4100.00
grow 100 by 20 over 5 months       -> 200  (NL form)

depreciate / depreciate...by...over #

depreciate($50000, 15%, 5)                -> $22185.27
depreciate($50000, 15%, 20, $5000)        -> $5000.00  (salvage floor)
depreciate $50000 by 15% over 5 years     -> $22185.27  (NL form)
depreciate $50000 by 15% over 5 years to $5000
Results
depreciate $50000 by 15% over 5 years to $5000$22.19K

Unit Handling in Functions #

Same units are preserved:

avg($100, $200, $300) -> $200.00
sqrt($100) -> $10.00

Mixed units are dropped:

avg($100, €200) -> 150  (no units)
average of $50, €100, £150 -> 100  (no units)

Natural Language Syntax #

CalcMark supports natural language forms for many functions. These are equivalent to the function-call syntax. Arguments can be literal values (100 MB) or variable references (data).

Function Aliases #

Natural LanguageEquivalentExample
average of X, Y, Zavg(X, Y, Z)average of 10, 20, 30
square root of Xsqrt(X)square root of 144
read X from Yread(X, Y)read 100 MB from ssd
compress X using Ycompress(X, Y)compress 1 GB using gzip
transfer X across Y Ztransfer_time(X, Y, Z)transfer 1 GB across regional gigabit
compound X by Y% over Zcompound(X, Y%, Z)compound $1000 by 5% over 10 years
compound X by Y% monthly over Zcompound(X, Y%, Z, monthly)compound $1000 by 5% monthly over 10 years
compound X by Y% per P over Zcompound(X, Y%, Z, P)compound $1000 by 5% per month over 12 months
compound X by Y% compounded F over Zcompound(X, Y%, Z, compounded F)compound $1000 by 12% compounded monthly over 10 years
grow X by Y over Zgrow(X, Y, Z)grow 100 by 20 over 5 months
depreciate X by Y% over Zdepreciate(X, Y%, Z)depreciate $50000 by 15% over 5 years
depreciate X by Y% over Z to Wdepreciate(X, Y%, Z, W)depreciate $50000 by 15% over 5 years to $5000

Capacity Planning Syntax #

The at...per syntax is a natural language form for the capacity() function:

demand at capacity per unit
demand at capacity per unit with N% buffer

Examples:

10 TB at 2 TB per disk                         -> 5 disks
10000 req/s at 450 req/s per server             -> 23 servers
10000 req/s at 450 req/s per server with 20%    -> 27 servers
100 apples at 30 per crate                      -> 4 crates

The slash syntax also works: 10 TB at 2 TB/disk.

Rate Accumulation with over #

The over keyword accumulates a rate over a time duration:

rate over duration

This is equivalent to accumulate(rate, duration):

100 MB/s over 1 day         -> total data transferred
$75/hour over 8 hours       -> daily earnings
1000 req/s over 1 hour      -> total requests

Rate Conversion with per #

The per keyword in a rate context creates a rate literal:

1000 requests per second    -> 1000 req/s
$50 per hour                -> $50/hour

Network Functions #

Round-Trip Time #

rtt(local)          -> 0.5 ms   (same datacenter)
rtt(regional)       -> 10 ms    (same region)
rtt(continental)    -> 50 ms    (cross-continent)
rtt(global)         -> 150 ms   (worldwide)

Throughput #

throughput(gigabit)      -> 125 MB/s
throughput(ten_gig)      -> 1.22 GB/s
throughput(hundred_gig)  -> 12500 MB/s
throughput(wifi)         -> 12.5 MB/s
throughput(four_g)       -> 2.5 MB/s
throughput(five_g)       -> 50 MB/s

Transfer Time #

Calculate data transfer time across a network:

transfer_time(1 GB, regional, gigabit)
transfer 1 GB across regional gigabit       (NL form)
Results
transfer_time(1 GB, regional, gigabit)8.2 second

Downtime from Availability #

downtime(99.9%, year)     -> ~8.76 hours
downtime(99.99%, month)   -> ~4.32 minutes

Storage Functions #

Read Time #

read(1 GB, ssd)       read from SATA SSD (~550 MB/s)
read(1 GB, nvme)      read from NVMe SSD (~3.5 GB/s)
read(1 GB, pcie_ssd)  read from PCIe Gen4 SSD (~7 GB/s)
read(1 GB, hdd)       read from 7200 RPM HDD (~150 MB/s)

read 100 MB from ssd  (NL form)

Seek Latency #

seek(ssd)       -> 0.1 ms
seek(nvme)      -> 0.01 ms
seek(pcie_ssd)  -> 0.01 ms
seek(hdd)       -> 10 ms

Compression #

compress(1 GB, gzip)     -> 333 MB   (3:1 ratio)
compress(1 GB, lz4)      -> 512 MB   (2:1 ratio)
compress(1 GB, zstd)     -> 293 MB   (3.5:1 ratio)
compress(1 GB, bzip2)    -> 250 MB   (4:1 ratio)
compress(1 GB, snappy)   -> 400 MB   (2.5:1 ratio)
compress(1 GB, none)     -> 1 GB     (1:1, no compression)

compress 1 GB using gzip (NL form)

Growth Functions #

Compound Growth #

The 4th argument controls which formula compound() uses. This matters – monthly and month give different answers:

Without a frequency modifier – simple compound growth, once per year:

A = P × (1 + r)^t
compound($1000, 5%, 10)                              -> $1628.89
compound $1000 by 5% over 10 years                   -> $1628.89

5% annual rate applied once per year for 10 years. A plain number like 10 means 10 years.

With a frequency adverb (monthly, quarterly, weekly, daily, yearly) – financial compounding, multiple times per year:

A = P × (1 + r/n)^(n × t)

where r is the annual rate, n is compounding frequency per year, and t is years.

compound($1000, 5%, 10, monthly)                     -> $1647.01
compound($1000, 5%, 10, quarterly)                    -> $1643.62
compound($1000, 5%, 24 months, monthly)              -> $1103.43
compound $1000 by 5% monthly over 10 years            -> $1647.01

The 5% annual rate is split into 12 monthly applications (5%/12 per month), compounded 120 times over 10 years. More frequent compounding yields higher returns: monthly ($1,647) > quarterly ($1,643) > yearly ($1,629).

Arguments:

#NameDescription
1principalStarting amount (number, currency, or quantity)
2rateAnnual growth rate as percentage
3durationTime in years. A plain number like 10 means 10 years. Durations like 24 months are converted to years
4modifierOptional: monthly, quarterly, daily, weekly, yearly. Compounds multiple times per year instead of once
month vs monthly: The noun month is a period label – compound($1000, 5%, 12, month) means “5% per month, applied 12 times” (the rate is per-month, not annual). The adverb monthly means “5% annual rate, compounded 12 times per year” – a different formula with a different result.

Linear Growth #

Calculate linear (additive) growth over time:

grow($500, $100, 36)               -> $4100.00
grow(100, 20, 5)                   -> 200

grow 100 by 20 over 5 months       (NL form)

Arguments:

#NameDescription
1initialStarting amount
2incrementAmount added each period
3periodsNumber of periods (number or duration)

Depreciation #

Calculate declining-balance depreciation with optional salvage floor:

depreciate($50000, 15%, 5)                -> $22185.27
depreciate($50000, 15%, 20, $5000)        -> $5000.00  (salvage floor)

depreciate $50000 by 15% over 5 years     (NL form)
depreciate $50000 by 15% over 5 years to $5000
Results
depreciate $50000 by 15% over 5 years to $5000$22.19K

Arguments:

#NameDescription
1valueStarting value
2rateDepreciation rate as percentage
3periodsNumber of periods (number or duration)
4salvageOptional: minimum floor value

Rates and the over keyword #

Rate Literals #

Create rates using the slash syntax:

net_bandwidth = 100 MB/s
salary = $120000/year
load = 1000 req/s
Results
net_bandwidth = 100 MB/s100 MB/s
salary = $120000/year120K $/year
load = 1000 req/s1K req/s

Rate Accumulation with over #

Use over to calculate the total from a rate over time:

link_speed = 100 MB/s
daily_transfer = link_speed over 1 day

hourly_rate = $75/hour
daily_earnings = hourly_rate over 8 hours
Results
link_speed = 100 MB/s100 MB/s
daily_transfer = link_speed over 1 day8.24 TB
hourly_rate = $75/hour75 $/h
daily_earnings = hourly_rate over 8 hours$600.00

Rate Conversion #

Convert rates to different time units using convert_rate():

convert_rate(1000 req/s, minute)    -> 60000 req/min
convert_rate($120000/year, month)   -> $10000/month

Rate Arithmetic Widening #

When you multiply or divide by a rate (rate on the right), CalcMark drops the time denominator and uses the rate’s amount. This is called widening — the rate widens into a plain quantity.

When the rate is on the left side, it stays a rate. This lets you scale rates naturally with rate * number.

posts_rate = 2 posts/week
scaled = posts_rate * 3           -> 6 posts/week  (rate * number → rate)
total  = 3 * posts_rate           -> 6 posts       (number * rate → quantity)

The rule is simple: left operand wins. If you start with a rate, you get a rate. If you start with a number or quantity and multiply by a rate, you get a number or quantity.

See the Language Reference: Rate Arithmetic Widening for the full type dispatch table.

Date Arithmetic #

Date Literals #

project_start = Jan 15 2025
christmas = Dec 25 2025
now = today
Results
project_start = Jan 15 2025Wednesday, January 15, 2025
christmas = Dec 25 2025Thursday, December 25, 2025
now = todaySaturday, March 14, 2026

CalcMark recognizes today, tomorrow, and yesterday as date keywords.

Duration Arithmetic #

project_start = Jan 15 2025
duration = 12 weeks
project_end = project_start + duration

deadline = Jun 1 2025
launch = deadline - 2 weeks
Results
project_start = Jan 15 2025Wednesday, January 15, 2025
duration = 12 weeks12 week
project_end = project_start + durationWednesday, April 9, 2025
deadline = Jun 1 2025Sunday, June 1, 2025
launch = deadline - 2 weeksSunday, May 18, 2025

The from Keyword #

7 days from Jan 1 2025   -> Wednesday, January 8, 2025
2 weeks from today       -> (today + 14 days)

Napkin Math #

The as napkin modifier rounds results to 2 significant figures and normalizes units. It adds a ~ prefix to signal the result is an approximation.

Syntax: expression as napkin

Works with: Number, Quantity, Currency, Duration, Rate

432000 MB as napkin                 -> ~420 GB
100 MB/s over 30 days as napkin    -> ~248 TB
$1234567 as napkin                  -> ~$1.2M

This is useful for quick back-of-the-envelope calculations where exact precision is not needed.

Precise Display #

The as precise modifier is the opposite of as napkin. It shows full float precision, skipping all display rounding. This is useful when you need exact values from unit conversions.

Syntax: expression as precise

10 meters as feet                  -> 32.8 feet
10 meters as feet as precise       -> 32.808399 feet

Explicit unit conversions are rounded by default for readability. Use as precise when you need the exact value.

Capacity Planning #

Use the at...per syntax to calculate how many units you need:

disks = 10 TB at 2 TB per disk
servers = 10000 req/s at 450 req/s per server
servers_buffered = 10000 req/s at 450 req/s per server with 20% buffer
Results
disks = 10 TB at 2 TB per disk5 disk
servers = 10000 req/s at 450 req/s per server23 server
servers_buffered = 10000 req/s at 450 req/s per server with 20% buffer27 server

Multiplier Suffixes #

Use K, M, B for large numbers:

users = 10M
revenue = $5B
requests = 100K
Results
users = 10M10M
revenue = $5B$5B
requests = 100K100K

Percentages #

Percentages work naturally in calculations:

price = $100
discount = 20%
sale_price = price * (1 - discount)

tax_rate = 8.25%
tax = price * tax_rate
Results
price = $100$100.00
discount = 20%20%
sale_price = price * (1 - discount)$80.00
tax_rate = 8.25%8.25%
tax = price * tax_rate$8.25

Worked Examples #

Basic Calculations #

# Simple Math

5 + 3
10 - 2
4 * 5
20 / 4
2 ^ 3
10 % 3
Results
5 + 38
10 - 28
4 * 520
20 / 45
2 ^ 38
10 % 31

Variables #

# Budget

salary = $5000
bonus = $500
expenses = $3000

net = salary + bonus - expenses
Results
salary = $5000$5,000.00
bonus = $500$500.00
expenses = $3000$3,000.00
net = salary + bonus - expenses$2,500.00

Comparisons #

# Checks

salary = $50000
threshold = $60000

is_high_earner = salary > threshold
needs_raise = salary < $40000
meets_target = salary >= $50000
Results
salary = $50000$50K
threshold = $60000$60K
is_high_earner = salary > thresholdfalse
needs_raise = salary < $40000false
meets_target = salary >= $50000true

Complex Expressions #

# Mortgage

principal = $200000
rate = 0.04
years = 30
months = years * 12

monthly_rate = rate / 12
payment = principal * (monthly_rate * (1 + monthly_rate) ^ months) / ((1 + monthly_rate) ^ months - 1)
Results
principal = $200000$200K
rate = 0.040.04
years = 3030
months = years * 12360
monthly_rate = rate / 120.003333
payment = principal * (monthly_rate * (1 + monthly_rate) ^ months) / ((1 + monthly_rate) ^ months - 1)$954.83

System Sizing #

# Server Capacity Planning

peak_load = 50000 req/s
server_capacity = 2000 req/s
servers = peak_load at server_capacity per server with 25% buffer

# Storage
daily_data = 100 MB/s over 1 day
monthly_storage = daily_data * 30
disks = monthly_storage at 2 TB per disk
Results
peak_load = 50000 req/s50K req/s
server_capacity = 2000 req/s2K req/s
servers = peak_load at server_capacity per server with 25% buffer32 server
daily_data = 100 MB/s over 1 day8.24 TB
monthly_storage = daily_data * 30247 TB
disks = monthly_storage at 2 TB per disk124 disk

Mixed Markdown #

# My Monthly Budget

I earn a salary and get bonuses.

## Income

monthly_salary = $5000
annual_bonus = $3000
monthly_bonus = annual_bonus / 12

Total monthly income:
total_income = monthly_salary + monthly_bonus

## Expenses

- Rent: $1500
- Food: $600
- Utilities: $200

rent = $1500
food = $600
utilities = $200
total_expenses = rent + food + utilities

## Summary

Monthly surplus:
surplus = total_income - total_expenses

Can I save 20%?
savings_goal = total_income * 0.20
can_save = surplus >= savings_goal
Results
monthly_salary = $5000$5,000.00
annual_bonus = $3000$3,000.00
monthly_bonus = annual_bonus / 12$250.00
total_income = monthly_salary + monthly_bonus$5,250.00
rent = $1500$1,500.00
food = $600$600.00
utilities = $200$200.00
total_expenses = rent + food + utilities$2,300.00
surplus = total_income - total_expenses$2,950.00
savings_goal = total_income * 0.20$1,050.00
can_save = surplus >= savings_goaltrue

Tips #

Organize with Markdown #

Use headers and prose to structure your thinking:

# Q1 Budget

## Revenue Assumptions
monthly_revenue = $50000
q1_months = 3
total_revenue = monthly_revenue * q1_months

## Cost Breakdown
fixed_costs = $20000
variable_pct = 30%
variable_costs = total_revenue * variable_pct
Results
monthly_revenue = $50000$50K
q1_months = 33
total_revenue = monthly_revenue * q1_months$150K
fixed_costs = $20000$20K
variable_pct = 30%30%
variable_costs = total_revenue * variable_pct$45K

Use the Preview Pane #

Press Ctrl+P in the editor to toggle the preview pane, which shows evaluated results alongside your source.

Get Help on Functions #

Run cm help functions to see all available functions with descriptions and usage patterns. Run cm help constants for unit constants, or cm help frontmatter for frontmatter directives.

Troubleshooting #

“Undefined variable” #

Variables must be defined before use. Check that:

  1. The variable is spelled correctly
  2. It’s defined on an earlier line
  3. No typos in the name

“Incompatible units” #

You can’t add meters to kilograms. Check that operations make physical sense.

“Parse error” #

The line isn’t valid CalcMark syntax. Common issues:

  • Missing operator between values
  • Unclosed parentheses
  • Invalid characters