Skip to content

Go Runtime

Module: github.com/xpr-lang/xpr-go · GitHub · v0.5.0

Install

bash
go get github.com/xpr-lang/xpr-go

Requires Go 1.21+. Zero runtime dependencies.

API

xpr.New()

Creates a new XPR engine instance.

go
import xpr "github.com/xpr-lang/xpr-go"

engine := xpr.New()

engine.Evaluate(expression string, context map[string]any) (any, error)

Evaluates an XPR expression string against an optional context map. Returns the result value or an error.

go
result, err := engine.Evaluate("1 + 2", nil)
// result → float64(3)

result, err = engine.Evaluate(
    `user["name"] ?? "anonymous"`,
    map[string]any{"user": map[string]any{"name": nil}},
)
// result → "anonymous"

result, err = engine.Evaluate("[1,2,3].map(x => x * 2)", nil)
// result → []any{float64(2), float64(4), float64(6)}

result, err = engine.Evaluate(
    "items.filter(x => x.active).map(x => x.name)",
    map[string]any{
        "items": []any{
            map[string]any{"name": "a", "active": true},
            map[string]any{"name": "b", "active": false},
        },
    },
)
// result → []any{"a"}

engine.AddFunction(name string, fn func(...any) (any, error))

Registers a custom function that can be called from expressions.

go
engine.AddFunction("slugify", func(args ...any) (any, error) {
    s := strings.ToLower(fmt.Sprintf("%v", args[0]))
    return strings.ReplaceAll(s, " ", "-"), nil
})

result, _ := engine.Evaluate(
    "slugify(product.name)",
    map[string]any{"product": map[string]any{"name": "Hello World"}},
)
// result → "hello-world"

Type Mapping

XPR typeGo type
numberfloat64
stringstring
booleanbool
nullnil
array[]interface{}
objectmap[string]interface{}
functioninternal xprFunc

Error Handling

All errors are returned as the second return value. There is no panic.

go
result, err := engine.Evaluate("1 / 0", nil)
if err != nil {
    fmt.Println(err) // "division by zero"
}

v0.2 Features

Let Bindings

go
result, _ := engine.Evaluate("let x = 1; let y = x + 1; y", nil)
// result → float64(2)

result, _ = engine.Evaluate("let f = (x) => x * 2; f(5)", nil)
// result → float64(10)

result, _ = engine.Evaluate(
    "let items = [1,2,3,4,5]; items.filter(x => x > 2).map(x => x * 10)",
    nil,
)
// result → []any{float64(30), float64(40), float64(50)}

Spread Operator

go
result, _ := engine.Evaluate("[...[1,2], ...[3,4]]", nil)
// result → []any{float64(1), float64(2), float64(3), float64(4)}

result, _ = engine.Evaluate("{...defaults, ...overrides}", map[string]any{
    "defaults": map[string]any{"color": "blue", "size": float64(10)},
    "overrides": map[string]any{"color": "red"},
})
// result → map[string]any{"color": "red", "size": float64(10)}

New Array Methods

go
engine.Evaluate("[1,2,3].includes(2)", nil)          // → true
engine.Evaluate("[1,2,3].indexOf(2)", nil)           // → float64(1)
engine.Evaluate("[1,2,3,4,5].slice(1, 3)", nil)      // → []any{float64(2), float64(3)}
engine.Evaluate(`[1,2,3].join(", ")`, nil)           // → "1, 2, 3"
engine.Evaluate("[1,2].concat([3,4])", nil)          // → []any{1,2,3,4}
engine.Evaluate("[[1,2],[3,4]].flat()", nil)         // → []any{1,2,3,4}
engine.Evaluate("[1,2,1,3].unique()", nil)           // → []any{1,2,3}
engine.Evaluate("[1,2,3].zip([4,5,6])", nil)         // → []any{[]any{1,4}, []any{2,5}, []any{3,6}}
engine.Evaluate("[1,2,3,4,5].chunk(2)", nil)         // → []any{[]any{1,2}, []any{3,4}, []any{5}}
engine.Evaluate(`[1,2,3].groupBy(x => x > 1 ? "big" : "small")`, nil)
// → map[string]any{"big": []any{2,3}, "small": []any{1}}

New String Methods

go
engine.Evaluate(`"hello".indexOf("ll")`, nil)        // → float64(2)
engine.Evaluate(`"ab".repeat(3)`, nil)               // → "ababab"
engine.Evaluate(`"  hi  ".trimStart()`, nil)         // → "hi  "
engine.Evaluate(`"  hi  ".trimEnd()`, nil)           // → "  hi"
engine.Evaluate(`"hello".charAt(1)`, nil)            // → "e"
engine.Evaluate(`"42".padStart(5, "0")`, nil)        // → "00042"
engine.Evaluate(`"hi".padEnd(5, ".")`, nil)          // → "hi..."

New Object Methods

go
engine.Evaluate(`{"b": 2, "a": 1}.entries()`, nil)  // → []any{[]any{"a",1}, []any{"b",2}}
engine.Evaluate(`{"a": 1}.has("a")`, nil)            // → true
engine.Evaluate(`{"a": 1}.has("b")`, nil)            // → false

range() Function

go
engine.Evaluate("range(5)", nil)           // → []any{0,1,2,3,4}
engine.Evaluate("range(1, 5)", nil)        // → []any{1,2,3,4}
engine.Evaluate("range(0, 10, 2)", nil)    // → []any{0,2,4,6,8}
engine.Evaluate("range(5, 0, -1)", nil)    // → []any{5,4,3,2,1}

v0.3 Features

Date/Time

Dates are epoch milliseconds (UTC only). Numbers return as float64.

go
result, _ := engine.Evaluate(`formatDate(now(), "yyyy-MM-dd")`, nil)
// → "2026-03-15"

result, _ = engine.Evaluate(`dateDiff(parseDate("2024-01-01T00:00:00Z"), now(), "days")`, nil)
// → float64(439)

result, _ = engine.Evaluate(`dateAdd(parseDate("2024-01-31T00:00:00Z"), 1, "months")`, nil)
// → float64(1709337600000)

Regex Functions

go
result, _ := engine.Evaluate(`matches("hello 42", "\\d+")`, nil)   // → true
result, _ = engine.Evaluate(`match("order-123", "\\d+")`, nil)      // → "123"
result, _ = engine.Evaluate(`matchAll("a1b2c3", "\\d")`, nil)       // → []any{"1","2","3"}
result, _ = engine.Evaluate(`replacePattern("hello world","o","0")`, nil) // → "hell0 w0rld"

Negative Indexing and Spread in Calls

go
result, _ := engine.Evaluate("[1,2,3][-1]", nil)         // → float64(3)
result, _ = engine.Evaluate("max(...[1, 5, 3, 2])", nil) // → float64(5)

v0.4 Features

Destructuring

go
result, _ := engine.Evaluate("let {name, age} = user; name",
    map[string]any{"user": map[string]any{"name": "Alice", "age": 30}})
// → "Alice"

result, _ = engine.Evaluate("let [head, ...tail] = items; tail",
    map[string]any{"items": []any{1, 2, 3}})
// → []any{float64(2), float64(3)}

Regex Literals

go
result, _ := engine.Evaluate(`/\d+/.test("order-123")`, nil)     // → true
result, _ = engine.Evaluate(`"2024-01-15".match(/\d{4}/)`, nil)  // → "2024"
result, _ = engine.Evaluate(`"hello world".replace(/o/, "0")`, nil)  // → "hell0 w0rld"

v0.5 Features

Math Functions and Constants

go
result, _ := engine.Evaluate("sqrt(16)", nil)       // → float64(4)
result, _ = engine.Evaluate("log(E)", nil)           // → float64(1)
result, _ = engine.Evaluate("pow(2, 10)", nil)       // → float64(1024)
result, _ = engine.Evaluate("PI * pow(5, 2)", nil)   // → float64(78.539...)
result, _ = engine.Evaluate("sign(-7)", nil)         // → float64(-1)
result, _ = engine.Evaluate("trunc(3.9)", nil)       // → float64(3)

Type Predicates

go
result, _ := engine.Evaluate("isNumber(42)", nil)    // → true
result, _ = engine.Evaluate("isArray([1,2])", nil)   // → true
result, _ = engine.Evaluate("isObject([1,2])", nil)  // → false
result, _ = engine.Evaluate("isNull(null)", nil)     // → true

New Array Methods

go
result, _ := engine.Evaluate("[3,null,1,null,5].compact().sortBy(x => x)", nil)
// → []any{float64(1), float64(3), float64(5)}

result, _ = engine.Evaluate("[1,2,3,4].sum()", nil)  // → float64(10)
result, _ = engine.Evaluate("[1,2,3,4].avg()", nil)  // → float64(2.5)
result, _ = engine.Evaluate("[3,1,2].first()", nil)  // → float64(3)
result, _ = engine.Evaluate("[1,2,3].count(x => x > 1)", nil)  // → float64(2)

fromEntries, str.split(/regex/), Rest Parameters

go
result, _ := engine.Evaluate(`fromEntries([["a", 1], ["b", 2]])`, nil)
// → map[string]any{"a": float64(1), "b": float64(2)}

result, _ = engine.Evaluate(`"a1b2c3".split(/\d+/)`, nil)
// → []any{"a", "b", "c"}