Go Package

Use go-calcmark as a library to evaluate CalcMark expressions and documents in your Go applications.

go-calcmark is a standard Go module. Import it and evaluate CalcMark expressions in a few lines of code.

Full API documentation is on pkg.go.dev.

go get github.com/CalcMark/go-calcmark

Evaluate a Single Expression #

The simplest entry point — pass an expression, get a result:

package main

import (
    "fmt"
    "log"

    calcmark "github.com/CalcMark/go-calcmark"
)

func main() {
    result, err := calcmark.Eval("price = 100 USD * 1.08")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(result.Value) // $108.00
}

Eval creates a fresh session for each call. The returned Result contains:

  • Value — the final computed value (a types.Type)
  • AllValues — all values when the input has multiple lines
  • Diagnostics — any warnings or hints from semantic analysis

Sessions: Persistent Variables #

Use a Session when you need variables to persist across multiple evaluations — like a REPL or an interactive editor:

session := calcmark.NewSession()

session.Eval("base = $85000")
session.Eval("bonus_pct = 15%")
result, _ := session.Eval("total = base + base * bonus_pct")

fmt.Println(result.Value) // $97,750.00

// Look up any variable by name
val, ok := session.GetVariable("total")
if ok {
    fmt.Println(val) // $97,750.00
}

Call session.Reset() to clear all variables and start fresh.

Evaluate a Full Document #

For complete CalcMark documents — with markdown, frontmatter, and multiple calc blocks — use the document-level API:

import (
    "github.com/CalcMark/go-calcmark/spec/document"
    implDoc "github.com/CalcMark/go-calcmark/impl/document"
)

source := `---
exchange:
  USD_EUR: 0.92
---

# Budget

revenue = 500K USD
costs = revenue * 0.6
profit = revenue - costs
profit_eur = profit in EUR
`

doc, err := document.NewDocument(source)
if err != nil {
    log.Fatal(err)
}

eval := implDoc.NewEvaluator()
if err := eval.Evaluate(doc); err != nil {
    log.Fatal(err)
}

// Access all variables through the environment
env := eval.GetEnvironment()
profit, _ := env.Get("profit")
fmt.Println(profit) // $200,000.00

The document evaluator handles block ordering, dependency resolution, frontmatter directives (exchange rates, globals, scale), and variable interpolation in text blocks.

The Type System #

Every CalcMark value is a types.Type. The concrete types are in github.com/CalcMark/go-calcmark/spec/types:

TypeExampleGo Type
*Number42, 3.14, 15%types.Number
*Currency$100, 85 EURtypes.Currency
*Quantity5 kg, 100 Mbpstypes.Quantity
*Duration3 hours, 2 weekstypes.Duration
*Date2026-03-11types.Date
*Rate100 MB/s, $50/hourtypes.Rate
*Booleantrue, falsetypes.Boolean

All numeric values use shopspring/decimal for arbitrary-precision arithmetic — no floating-point surprises.

Extracting Values #

Use types.ToDecimal to get the numeric value from any numeric type:

import "github.com/CalcMark/go-calcmark/spec/types"

result, _ := calcmark.Eval("weight = 5 kg")
d, err := types.ToDecimal(result.Value)
if err == nil {
    fmt.Println(d) // 5
}

For type-specific fields, use a type assertion:

result, _ := calcmark.Eval("price = 100 USD")
if currency, ok := result.Value.(*types.Currency); ok {
    fmt.Println(currency.Value) // 100
    fmt.Println(currency.Code)  // USD
    fmt.Println(currency.Symbol) // $
}

The Environment #

The Environment holds all variable bindings and exchange rates. You can pre-populate it before evaluation:

import (
    "github.com/CalcMark/go-calcmark/impl/interpreter"
    "github.com/CalcMark/go-calcmark/spec/types"
    "github.com/shopspring/decimal"
)

env := interpreter.NewEnvironment()
env.Set("tax_rate", types.NewNumber(decimal.NewFromFloat(0.08)))
env.SetExchangeRate("USD", "EUR", decimal.NewFromFloat(0.92))

interp := interpreter.NewInterpreterWithEnv(env)

Key Environment methods:

MethodDescription
Set(name, value)Store a variable
Get(name)Retrieve a variable (returns value, ok)
Has(name)Check if a variable exists
GetAllVariables()Get all variables as a map
SetExchangeRate(from, to, rate)Set a currency conversion rate
Clone()Shallow copy for isolation

Diagnostics #

Both Eval and the document evaluator return diagnostics — structured messages with severity levels:

result, _ := calcmark.Eval("x = unknown_var + 1")
for _, d := range result.Diagnostics {
    fmt.Printf("[%s] %s\n", d.Severity, d.Message)
}

Severity levels: Error, Warning, Hint.

Package Structure #

The codebase separates specification from implementation:

PackagePurpose
github.com/CalcMark/go-calcmarkTop-level Eval and Session API
spec/typesValue types (Number, Currency, Quantity, etc.)
spec/documentDocument model (blocks, parsing)
spec/unitsCanonical unit definitions and conversions
spec/featuresFeature registry (functions, NL syntax)
impl/interpreterExpression evaluator and Environment
impl/documentDocument-level evaluator

Dependencies flow one way: impl depends on spec, never the reverse.

Further Reading #