interpreter

package
v1.74.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 3, 2026 License: MIT Imports: 58 Imported by: 0

Documentation

Overview

apikey.go — service-to-service auth helper.

Pattern:

middleware require_api_key {
  if (!api_key_auth(request, env("API_KEYS"))) {
    return status(401, { error: "invalid api key" })
  }
}

group /api {
  use require_api_key
  get /health { return json({ ok: true }) }
}

Reads `X-API-Key` first, falls back to `Authorization: Bearer <key>`. `allowed_keys` is a comma-separated string of valid keys, typically from env("API_KEYS"). Compare is constant-time so timing leaks can't enumerate keys one byte at a time.

auth_passwordless.go — magic-link tokens (signed, time-limited) and RFC 6238 TOTP (Google Authenticator–compatible). Together they cover the two most common passwordless auth flows. Magic links are great for "send me a sign-in email", TOTP is the second factor.

body_validate.go — single-call body validation for POST/PUT handlers.

The pattern handlers used to write:

post /users {
  let r = validate(request.body, schema)
  if (!r.valid) {
    return problem(400, "Validation failed", "", { errors: r.errors, trace_id: request.id })
  }
  let body = request.body
  // ... actual logic
}

collapses to:

post /users {
  let r = body_validate(request, schema)
  if (!r.ok) { return r.response }
  // r.body is the validated body
}

`r.response` is a fully-formed problem+json 400 with the right trace_id from request.id. Same shape as if you'd written it by hand.

builtins.go installs the MX Script standard library into the global environment. Every native function is registered here so they're available in every .mx program without an import.

config.go — convenience config loader with format auto-detection and env-var interpolation. Common SaaS pattern:

# config.yaml
db:
  dsn: ${DATABASE_URL}
  pool: 10
stripe:
  secret: ${STRIPE_SECRET_KEY}
  price:  ${STRIPE_PRICE_ID}

# app.mx
let cfg = config.load("./config.yaml")
let db  = sql.open(cfg.db.dsn)

Supports .yaml / .yml / .json / .toml — picked by extension. `${NAME}` placeholders expand to env(NAME) before parsing, so a committed config file can carry secret references without the secrets themselves.

cron.go — minimal but correct Vixie-cron parser + scheduler used by the cron(spec, fn) builtin. Supports the standard 5-field expression (minute hour day-of-month month day-of-week) with `*`, lists, ranges, and `*/step`. Day-of-month and day-of-week combine with OR when both are restricted, matching cron's documented behavior.

debug.go — small `debug.*` namespace for assertions, tracing, and quick inspection. Together with `pp()` and `log.*` this rounds out the dev / panic-driven-development surface so MX programs can short-circuit cleanly when invariants break.

debug.assert(user.subscribed, "user must be subscribed here")
debug.unreachable("expected match arm to handle this")
let result = debug.trace("expensive_query", fn() { ... })

All three throw on failure (so try/catch semantics work the same), rather than calling os.Exit — programs that want hard-stop behavior can wrap them in their own handler.

etag.go — HTTP caching primitives for read-heavy endpoints.

The pattern:

get /users/:id {
  let user = sql.first(db, "SELECT * FROM users WHERE id = ?", request.params.id)
  let tag  = etag(user)
  if (request.headers["if-none-match"] == tag) { return not_modified() }
  return json(user, { headers: { "ETag": tag, "Cache-Control": "private, max-age=60" } })
}

One round-trip cost on the cold path; zero body bytes on the warm path. Big win for list/detail endpoints that serve the same shape to the same client over and over.

export.go — high-level response helpers for the most common export shapes APIs serve: CSV (for spreadsheets) and NDJSON (for streaming pipelines / log shippers).

Pattern:

get /export/users.csv {
  return csv(sql.find(db, "users", {}), { filename: "users.csv" })
}

get /export/events.ndjson {
  return ndjson(sql.find(db, "events", { active: true }))
}

`filename` opts triggers `Content-Disposition: attachment` so a browser downloads instead of trying to render.

extra_crypto_config.go — bcrypt-grade password hashing (Argon2id, scrypt) plus YAML/TOML parsers. These need external deps that the stdlib doesn't ship:

golang.org/x/crypto/argon2 + scrypt
gopkg.in/yaml.v3
github.com/BurntSushi/toml

All three are widely-deployed, well-maintained, and pure Go.

form.go — application/x-www-form-urlencoded helpers. The HTTP router auto-decodes form bodies into request.body for incoming requests, so mostly these are for outbound calls + tests.

form.parse("a=1&b=2&c=hello%20world")
// -> { a: "1", b: "2", c: "hello world" }

form.encode({ user: "alice", count: 3 })
// -> "count=3&user=alice"

// Building a POST body:
fetch(url, { method: "POST", body: form.encode({ ... }) })

graphql.go — minimal GraphQL handler. Parses a useful subset of the query language (queries, mutations, fields with arguments, nested selection sets, variables, aliases) and dispatches to a user- supplied resolver tree. Skips: fragments, interfaces, unions, directives, introspection, subscriptions. Apollo / urql / Relay clients all work for the common case where you write a backend from scratch.

The schema is implicit in the resolver shape:

graphql.handler({
  Query: {
    user: fn(args, ctx) {
      return sql.query_one(db, "SELECT * FROM users WHERE id = ?", args.id)
    },
    users: fn(args, ctx) {
      return sql.query(db, "SELECT * FROM users LIMIT ?", args.limit ?? 50)
    },
  },
  Mutation: {
    create_user: fn(args, ctx) { ... }
  },
  User: {
    posts: fn(parent, args, ctx) {
      return sql.query(db, "SELECT * FROM posts WHERE user_id = ?", parent.id)
    }
  }
})

Routes mount the handler with `route POST /graphql { return graphql.handler(...)(request) }`.

health.go — Kubernetes-flavoured liveness / readiness probes.

route GET /healthz { return health.live() }
route GET /readyz {
  return health.ready({
    database: fn() { sql.query_one(db, "SELECT 1") != null },
    redis:    fn() { redis.get(r, "ping") != null }
  })
}

The conventions are deliberate: healthz checks "is this process alive enough to keep" — nothing else, returns 200 once the binary is responding to HTTP. readyz checks "is this process ready to serve traffic" — runs every check fn, returns 200 if all pass and 503 with a per-check JSON body otherwise.

http_session.go — stateful HTTP client with a cookie jar. Useful for client SDKs that consume legacy APIs (form login → session cookie → subsequent requests share the auth state). The standard `fetch()` builtin is stateless; `http.session()` returns an object with methods that share a cookie jar and base configuration.

let s = http.session({ base_url: "https://api.example.com" })
s.post("/login", { email: "x", password: "y" })
let user = s.get("/me")        // cookies from /login auto-attach
s.close()                       // optional, clears the jar

ids_objects.go — ID generators (uuid/ulid/nanoid/short) and a small suite of object helpers (pick/omit/merge/deep_merge). All are expression-friendly and copy-rather-than-mutate so chained transformations don't surprise callers.

Package interpreter is the heart of MX Script. It walks the parsed AST, evaluates expressions, drives the standard library, and (when route declarations are present) starts an HTTP server that dispatches incoming requests to user-defined route bodies.

jobs.go — durable background job queue backed by SQLite.

Two-line worker:

let q = jobs.create({ db: "./jobs.db", queue: "emails" })
q.enqueue({ to: "alice@example.com", subject: "Hi" })
q.process(2, fn(job) { email.send(...job) })   # 2 workers

Each job carries: id, queue, payload (JSON), status (pending | running | done | failed), attempts, last_error, run_at, created_at. Failed jobs retry with exponential backoff up to `max_attempts` (default 3). After that they stay marked `failed` in the table for inspection.

metrics.go — minimal Prometheus-compatible metrics. Three primitives:

metrics.counter(name, value?, labels?)    monotonically increasing
metrics.gauge(name, value, labels?)       point-in-time number
metrics.histogram(name, value, labels?)   distribution of observations

Plus a built-in /metrics route handler (`metrics.handler()`) that emits the standard Prometheus text exposition format. Drop-in for any scrape-based monitoring stack — Prometheus, Grafana Cloud, VictoriaMetrics, Datadog Agent's openmetrics check, Honeycomb's OTel collector, etc.

Storage is in-process and lives on a single global registry so any goroutine (route handler, spawn block, cron job) can record without passing a context object around. We use sync.Mutex on writes, atomic for counter increments under a lock so reads see consistent state.

notify.go — outbound notification primitives. Each function is a thin one-shot HTTP call to a well-known service so route handlers don't have to ship copy-pasted curl recipes.

Today we cover the three channels real SaaS apps wire up first:

notify.slack(webhook_url, msg)            // incoming-webhook post
notify.discord(webhook_url, msg)          // Discord webhook
notify.email(to, subject, body, opts?)    // Resend (recommended) or any RESEND_API_KEY-compatible API

Each returns a small response object: { ok: bool, status: number, error: string|null }. Callers stay declarative — `if (!r.ok) ...` reads better than wrapping every call in try/catch.

paginate.go — request → page params, then page params + items → envelope.

Most list endpoints reinvent the same five lines:

let page     = num(request.query?.page) || 1
let per_page = math.min(100, num(request.query?.per_page) || 20)
let offset   = (page - 1) * per_page
let total    = sql.first(db, "SELECT count(*) AS n FROM ...").n
return json({ items: ..., page, per_page, total, total_pages: ceil(...), ... })

paginate() collapses the input parsing, page_response() collapses the envelope. Together they turn a list endpoint into:

get /users {
  let p = paginate(request)
  let total = sql.first(db, "SELECT count(*) AS n FROM users").n
  let items = sql.query(db, "SELECT * FROM users LIMIT ? OFFSET ?", p.limit, p.offset)
  return json(page_response(items, p, total))
}

parse_helper.go wires the interpreter's import statement to the lexer and parser without forcing the consumer of this package to do it themselves.

pretty.go — pp(value) pretty-printer. Indented, color-aware, cycle-safe (well, cycles aren't possible in the value model since arrays + objects copy, but we still cap recursion depth).

The REPL uses prettyValue() automatically when displaying results; scripts can call pp() explicitly for debugging output.

problem.go — RFC 7807 application/problem+json responses.

The standard machine-readable error shape:

{ "type": "...", "title": "...", "status": 400, "detail": "...", ... }

Pattern in handlers:

post /users {
  let r = validate(request.body, schema)
  if (!r.valid) {
    return problem(400, "Validation failed", { errors: r.errors })
  }
}

get /users/:id {
  let u = sql.first(db, "SELECT * FROM users WHERE id = ?", request.params.id)
  if (u == null) { return problem(404, "User not found") }
  return json(u)
}

Anything beyond status/title/detail goes into the `ext` object and is merged into the top-level response — RFC 7807 §3.2 explicitly allows arbitrary extension members.

proxy.go — server-side reverse-proxy helper. Lets MX route handlers forward an incoming request to an upstream service. Useful for dev workflows where MX is the backend serving the API and a Vite / Next dev server is the frontend at a different port:

get /api/*  { return json(handle_api(...)) }
get /*      { return proxy("http://localhost:5173", request) }

Streams body bytes through verbatim. Per-hop headers (Connection, Hop-by-Hop) are stripped per RFC 2616 §13.5.1.

rate_limit.go — application-level rate limiting via the `rate_limit(key, max, window_seconds)` builtin. Same token-bucket algorithm as the existing server-level rate_limit config, but keyed by an arbitrary string so callers can rate-limit per user, per tenant, per IP, per endpoint — whatever makes sense for the route.

route POST /signup {
  if (!rate_limit("signup:" + request.ip, 5, 60)) {
    return status(429, { error: "too many requests" })
  }
  // ... actual signup
}

Buckets live in-process; for distributed rate limiting back this onto Redis with `redis.incr(...)` instead.

redis.go — thin wrapper around go-redis/redis/v9. Exposed as the `redis` namespace:

let r = redis.connect("redis://localhost:6379/0")
redis.set(r, "user:1", "Jassim", { ttl_seconds: 3600 })
let name = redis.get(r, "user:1")
redis.del(r, "user:1")
redis.publish(r, "events", json_stringify({ kind: "ping" }))

Connection objects are dbHandle-style opaque handles. Pipelining and streams aren't surfaced yet; callers who need them can drop into Go or open an issue.

route_trie.go — segment-based trie for HTTP route lookup. Each path segment is a node; static children are stored in a map (O(1) hit), `:param` children are a single slot per node, and a `*` wildcard child catches the remainder.

Built lazily by Interpreter.dispatch on first request, then reused for every subsequent dispatch. Compared to the previous O(n) linear scan over registeredRoute, lookup is now O(segments) with branching limited to method × path-shape.

s3.go — S3-compatible object storage. Implements AWS Signature V4 directly (no external SDK) so the same builtins work against AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, MinIO, Wasabi, and any other S3-compatible store. The API surface is intentionally small — five calls cover every common need:

s3.put(bucket, key, body, opts?)         upload
s3.get(bucket, key, opts?)               download (returns body string)
s3.delete(bucket, key, opts?)            delete
s3.list(bucket, prefix?, opts?)          array of keys
s3.presign(bucket, key, opts?)           presigned GET URL with expiry

All read AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_REGION from the environment by default. opts.endpoint overrides the host for non-AWS providers; opts.region overrides the region for requests outside us-east-1.

search.go — SQLite FTS5 full-text search wrapper. The engine (FTS5) ships in the pure-Go modernc.org/sqlite driver we already depend on, so this namespace is zero-extra-deps. The four calls covered are enough for any "search bar" on top of a SQL table:

search.create(db, "posts_fts", ["title", "body"])
search.index(db, "posts_fts", post.id, post)
let hits = search.query(db, "posts_fts", "lang AND fast", { limit: 20 })
search.delete(db, "posts_fts", post.id)

FTS5 supports BM25 ranking, NEAR proximity search, prefix queries, boolean operators (AND/OR/NOT), and column-scoped queries (`title: lang`). The wrapper passes user queries through unchanged so all of that works out of the box — see the FTS5 docs: https://www.sqlite.org/fts5.html

shell_helpers.go — convenience wrappers around the existing `shell()` builtin. Three patterns:

shell.run(cmd, args?, opts?)    -> exit_code  (just the exit code)
shell.output(cmd, args?, opts?) -> string     (stdout, fail on non-zero)
shell.bash(script, opts?)       -> result     (run a multi-line script)
shell.which(name)               -> string|null  (locate on PATH)

All four delegate to the existing builtinShell so they share env / dir / timeout / stdin opts.

snapshot.go — golden-file testing primitive.

test "rendered email looks right" {
  let body = render_email(user, plan)
  assert_snapshot("welcome_email", body)
}

First run writes the value's pretty repr to __snapshots__/<file>.snap.json under the same directory as the source file, then passes. Subsequent runs read the file back and fail if the rendered value has changed. `mx test -u` (or --update-snapshots) flips the interpreter into update mode, which always overwrites.

Snapshots are committed to source control. They're the test — review changes the same way you'd review a code change.

sql.go — SQLite via the pure-Go modernc.org/sqlite driver. Exposed through the `sql` namespace:

let db = sql.open("./data.db")           # opens / creates a database
sql.exec(db, "CREATE TABLE users (...)")
sql.exec(db, "INSERT ... VALUES (?, ?)", "Jassim", 30)
let rows = sql.query(db, "SELECT * FROM users WHERE name = ?", "Jassim")
loop rows as u { print(u.name) }
let one = sql.query_one(db, "SELECT * FROM users WHERE id = ?", 1)
sql.close(db)

Disabled under js/wasm — the sqlite driver depends on cgo-style libc shims that don't build for the browser. sql_wasm.go provides stubs that return clear errors at runtime.

static.go — single-file static asset helper for routes that have already done their own routing/auth and just want to hand back bytes from disk with a sensible Content-Type.

Pairs naturally with proxy() for SPA setups:

get /* {
  if (env("MX_DEV") == "1") { return proxy("http://localhost:5173", request) }
  let f = static_file("./web/dist" + request.path)
  if (f != null) { return f }
  return html(read_file("./web/dist/index.html"))   // SPA fallback
}

Returns null when the path is missing or points at a directory so the caller can do its own 404 / fallthrough handling.

stripe.go — thin one-shot wrappers around the Stripe API. The four calls covered here (checkout session, customer, portal, subscription) are what 90% of SaaS apps actually need. Anything fancier — disputes, refunds, payouts — should drop straight to fetch() with the `STRIPE_SECRET_KEY` env var; the surface here intentionally stays small.

Combined with v0.56's webhooks.verify_stripe, the full Stripe loop (create checkout → user pays → webhook updates DB) is about 30 lines of MX. See examples/stripe.mx.

time_path.go — time, path, and fs.glob builtins. The shapes match what users coming from Python/JS/Ruby expect:

time.parse("2026-05-03T12:00:00Z") -> unix seconds
time.format(unix, "2006-01-02")    -> "2026-05-03"
time.add(unix, "24h")              -> unix + 86400
time.diff(a, b)                    -> seconds (b - a)
time.weekday(unix)                 -> "Sunday"..."Saturday"
time.iso(unix)                     -> "2026-05-03T12:00:00Z"
time.unix(iso)                     -> unix seconds (alias of parse)

path.join("/a", "b", "c.txt")      -> "/a/b/c.txt"
path.dir("/a/b/c.txt")             -> "/a/b"
path.base("/a/b/c.txt")            -> "c.txt"
path.ext("/a/b/c.txt")             -> ".txt"

fs.glob("*.mx")                    -> ["app.mx", "auth.mx"]
fs.glob("**/*.mx")                 -> recursive (max-depth 16)

upload.go — convenience helpers for the multipart upload story.

`request.files` already gives you parsed file objects with name / size / content_type / content / ext. The friction was the second step: writing them to disk safely. Without save_upload(), users wrote:

let dir = "./uploads"
write_file(dir + "/" + uuid() + img.ext, img.content)

— which has to mkdir the parent themselves, and the call is verbose in the hot path of every endpoint that takes uploads. save_upload() collapses that.

vault.go — tiny encrypted secrets store. Lets users keep per-environment secrets out of the binary + out of plaintext .env files. Secrets are AES-256-GCM encrypted at rest with a single master key (read from env), so you can commit the encrypted vault to source control without leaking the values.

$ MX_VAULT_KEY=$(openssl rand -hex 32) mx run app.mx

// app.mx
let stripe = vault.get("stripe_key")  // decrypted on read

vault.set / vault.export are the write side: typically you run these once interactively to bootstrap the file, then commit the resulting .vault.json (encrypted, safe to share).

vm.go — experimental bytecode VM for the hot paths of MX Script. Compiles a useful subset of the AST (arithmetic, comparison, unary, `let`, `=`, `if`, `while`) into a flat slice of stack-machine instructions, then runs them on a tight loop.

Why a subset? Programs that mix routes / spawn / channels / SSE / tool-calling AI / SQLite still want full interpreter semantics because those paths fan out into Go-side IO. The compiler refuses to lower anything it doesn't fully understand, and the runtime falls back to the tree-walker for those programs. The VM is purely an optimisation for tight numeric / data-shuffling code.

Today this ships behind `mx run --bytecode` and `mx bench --bytecode` (off by default) so users opt in. Once we have full coverage and equivalent semantics for every node, --bytecode flips to default.

websocket.go — minimal RFC 6455 WebSocket server in pure stdlib. No extensions, no permessage-deflate, no fragmented messages over 16 MB. Good enough for chat / live updates / collaborative editors. If you need the heavyweight features, drop in gorilla/websocket and call Interpreter.Handler() to keep the rest of the framework.

xml.go — minimal XML parser and stringifier. Useful for the legacy APIs SaaS apps still need to integrate with: SOAP-style HTTP, RSS / Atom feeds, sitemaps, podcast feeds, Google Search Console responses, etc.

xml.parse("<root><a>1</a><b>2</b></root>")
// → { tag: "root", attrs: {}, children: [
//      { tag: "a", attrs: {}, text: "1", children: [] },
//      { tag: "b", attrs: {}, text: "2", children: [] },
//    ], text: "" }

xml.stringify({ tag: "rss", attrs: { version: "2.0" }, children: [...] })
// → '<rss version="2.0"><channel>...</channel></rss>'

The shape mirrors what callers actually want: each node carries `tag`, `attrs`, `text` (the immediate text content), and `children`. Mixed-content elements lose ordering between text and children — that's an acceptable trade for the simpler API; users who need full fidelity should drop to `encoding/xml` directly.

Index

Constants

This section is empty.

Variables

View Source
var PackageResolver func(importPath string) string

PackageResolver lets the host (the `mx` CLI) plug in a function that turns a package-style import path like `github.com/foo/bar` into an on-disk file. Returning "" means "not a package, fall through to relative-path resolution". Wired up in main.go so the interpreter package itself doesn't need to depend on pkg.

Functions

func DisplayValue added in v0.2.0

func DisplayValue(v Value) string

DisplayValue formats a value for human-readable output, used by the REPL.

func IsBuiltin added in v0.2.0

func IsBuiltin(name string) bool

func MagicLinkCreate added in v0.58.0

func MagicLinkCreate(email, secret string, minutes int) string

MagicLinkCreate builds a signed token for an email that expires in `minutes` minutes. Stateless: pass to user, they click, you verify.

func MagicLinkVerify added in v0.58.0

func MagicLinkVerify(token, secret string) (string, error)

MagicLinkVerify returns the email if `token` is a well-formed, untampered, unexpired link signed with `secret`. Otherwise empty.

func ParseSource

func ParseSource(src string) (*parser.Program, error)

ParseSource lexes and parses a string of MX Script source code.

func PrettyDisplay added in v0.78.0

func PrettyDisplay(v Value, colors bool) string

PrettyDisplay returns a colored, indented representation of v suitable for terminal output. Used by the REPL to display results so objects + arrays don't render as one-line JSON. Pass colors=false for plain output (e.g. when piping to a file).

func TOTPGenerate added in v0.58.0

func TOTPGenerate(secret string) (string, error)

TOTPGenerate returns the current 6-digit TOTP code for `secret`, which must be a base32-encoded string (with or without padding).

func TOTPProvisioningURI added in v0.58.0

func TOTPProvisioningURI(account, secret, issuer string) string

TOTPProvisioningURI returns the otpauth:// URI suitable for encoding in a QR code so authenticator apps can scan and add the account.

otpauth://totp/MX%20Script:user@host?secret=BASE32&issuer=MX%20Script

func TOTPVerify added in v0.58.0

func TOTPVerify(code, secret string, drift int) (bool, error)

TOTPVerify accepts the user-supplied `code` if it matches the current counter, or any counter within ±drift slots (each slot is 30 seconds). Drift defaults to 1 (90-second total window) so a slow user / clock skew doesn't lock them out.

Types

type Channel added in v0.16.0

type Channel struct {
	C chan Value
	// contains filtered or unexported fields
}

Channel wraps a Go channel of MX values. Allocated by chan(); operated on with send(ch, v), recv(ch), close_chan(ch).

func (*Channel) Close added in v0.16.0

func (c *Channel) Close()

type Compiled added in v0.52.0

type Compiled struct {
	Code     []Instr
	Consts   []Value
	VarNames []string
	// contains filtered or unexported fields
}

Compiled is the output of the bytecode compiler. It owns the instruction stream plus the constant pool and the variable name table the instructions index into.

func CompileBlock added in v0.53.0

func CompileBlock(stmts []parser.Stmt) (*Compiled, bool)

CompileBlock lowers a list of statements. Used for the body of `if`, `while`, and (eventually) functions. Returns (nil, false) if any statement uses something the compiler doesn't understand yet.

func CompileExpr added in v0.52.0

func CompileExpr(e parser.Expr) (*Compiled, bool)

CompileExpr lowers a single expression node into a self-contained program that pushes the value and returns. Used for ad-hoc expression statements at the top level.

func (*Compiled) Run added in v0.52.0

func (c *Compiled) Run(interp *Interpreter, env *Env) (Value, error)

Run executes the compiled program against the given environment. `interp` is needed for OpCall (function dispatch); pass nil for programs that don't include calls (the runtime errors clearly if a call is hit without an interpreter).

Variables are read from / written to the env via name lookup, so the VM shares state with the tree-walker for free.

type Coverage added in v0.31.0

type Coverage struct {
	// contains filtered or unexported fields
}

Coverage tracks which source lines were executed during a run. Filled in by the interpreter when EnableCoverage is called and queried by the CLI's `mx test --cover` reporter.

func (*Coverage) ExecutedLines added in v0.31.0

func (c *Coverage) ExecutedLines() []int

ExecutedLines returns a sorted snapshot of the line numbers that ran.

func (*Coverage) Hit added in v0.31.0

func (c *Coverage) Hit(line int)

Hit publicly records that `line` ran. Used by the test runner to merge per-test coverage into a file-level aggregate.

type CronSchedule added in v0.57.0

type CronSchedule struct {
	Minute uint64 // bits 0..59
	Hour   uint64 // bits 0..23
	Dom    uint64 // bits 1..31  (bit 0 unused)
	Month  uint64 // bits 1..12  (bit 0 unused)
	Dow    uint64 // bits 0..6   (Sunday = 0)
	// contains filtered or unexported fields
}

CronSchedule is a parsed cron expression. Each field is a 64-bit bitmask of allowed values: bit `i` set means value `i` is in the schedule. We use bitmasks so nextFiring's match check is a single AND operation per field per minute.

func ParseCron added in v0.57.0

func ParseCron(spec string) (*CronSchedule, error)

ParseCron parses a 5-field cron expression and returns a schedule.

"0 9 * * 1-5"      every weekday at 09:00
"*/5 * * * *"      every 5 minutes
"0 0,12 * * *"     midnight and noon
"15 14 1 * *"      14:15 on the 1st of each month

func (*CronSchedule) Match added in v0.57.0

func (s *CronSchedule) Match(t time.Time) bool

Match reports whether the schedule fires at minute `t`. Seconds are ignored; callers should align `t` to the minute before checking.

func (*CronSchedule) Next added in v0.57.0

func (s *CronSchedule) Next(from time.Time) time.Time

Next returns the earliest time at or after `from` (rounded to the next minute) at which the schedule fires. We bound the search at four years to handle pathological specs like "* * 29 2 *" that only fire on a leap-year February 29.

type Env

type Env struct {
	// contains filtered or unexported fields
}

Env is the lexical scope. RWMutex protects the vars map so spawned goroutines can read/write the closure safely. (Programs that share MUTABLE state across spawns still race on the values themselves — use channels for coordination.)

func NewEnv

func NewEnv(parent *Env) *Env

func (*Env) Assign

func (e *Env) Assign(name string, v Value)

Assign walks up parents until the variable is found, then replaces it. If not found, defines it in the current scope.

func (*Env) Get

func (e *Env) Get(name string) (Value, bool)

func (*Env) Keys added in v0.2.0

func (e *Env) Keys() []string

Keys returns the names defined directly in this scope (not parents). Used by the REPL to show what the user has bound.

func (*Env) Set

func (e *Env) Set(name string, v Value)

type Function

type Function struct {
	Name    string
	Params  []string
	Body    []parser.Stmt
	Closure *Env
	Native  func(interp *Interpreter, args []Value) (Value, error)
	// contains filtered or unexported fields
}

type Instr added in v0.52.0

type Instr struct {
	Op  Op
	Arg int32
}

type Interpreter

type Interpreter struct {

	// Out / Err are the destinations for print/println/write and eprint
	// respectively. Default to os.Stdout / os.Stderr; embedders (notably
	// the eval() builtin and the playground) can swap in a buffer to
	// capture output.
	Out io.Writer
	Err io.Writer
	// contains filtered or unexported fields
}

func New

func New() *Interpreter

New constructs an interpreter pre-populated with all built-ins.

func (*Interpreter) Benches added in v1.32.0

func (i *Interpreter) Benches() []TestInfo

Benches returns the inline `bench "name" { ... }` declarations the loaded program registered, in source order. `mx bench` calls this after Load to discover what to time.

func (*Interpreter) BytecodeEnabled added in v0.52.0

func (i *Interpreter) BytecodeEnabled() bool

BytecodeEnabled reports whether the VM is on. Used by the bench reporter so users can see which engine ran the numbers.

func (*Interpreter) CallByName added in v0.6.0

func (i *Interpreter) CallByName(name string, args []Value) (Value, error)

CallByName invokes a user-defined function in the global scope by name. Used by the test runner to call discovered `test_*` functions.

func (*Interpreter) Coverage added in v0.31.0

func (i *Interpreter) Coverage() *Coverage

Coverage returns the active coverage tracker (nil if disabled).

func (*Interpreter) EnableCoverage added in v0.31.0

func (i *Interpreter) EnableCoverage() *Coverage

EnableCoverage turns on per-statement line tracking. Called by `mx test --cover`; off by default for normal runs (zero overhead).

func (*Interpreter) Exec added in v0.2.0

func (i *Interpreter) Exec(prog *parser.Program) (Value, error)

Exec runs every statement in the program against the global scope and returns the value of the last expression statement (if any). Unlike Run, it does NOT start an HTTP server even if the program defined routes. This is intended for the REPL, where we want immediate feedback.

func (*Interpreter) File added in v1.34.0

func (i *Interpreter) File() string

File returns the current source file path. Builtins that need to resolve relative paths (snapshots next to the test file, fixture loaders, etc.) call this to anchor their lookups.

func (*Interpreter) Globals added in v0.2.0

func (i *Interpreter) Globals() *Env

Globals returns the interpreter's top-level environment. It's exposed so embedders (notably the REPL) can evaluate statements that read or write the same scope across multiple calls.

func (*Interpreter) Handler added in v0.13.0

func (i *Interpreter) Handler() http.Handler

Handler returns the fully-wrapped HTTP handler for the loaded program: the route mux composed with the configured CORS, logging, and request body size middleware.

Call Load before Handler so the program's routes, middleware, and server config are registered. Handler is safe to call multiple times; each call returns a freshly-composed handler chain over the same underlying state.

This is the entry point for embedders (e.g. the Vercel adapter) that want to mount an MX Script app inside their own HTTP server.

func (*Interpreter) HasRoutes added in v0.13.0

func (i *Interpreter) HasRoutes() bool

HasRoutes reports whether the loaded program defined any HTTP routes or static mounts. Useful for embedders deciding whether to start a server.

func (*Interpreter) Load added in v0.13.0

func (i *Interpreter) Load(prog *parser.Program) error

Load evaluates every top-level statement in the program against the global scope. It registers any `server { ... }` config, route, middleware and `static` mount, but does NOT start an HTTP server.

Embedders that want to mount the resulting routes in their own server (for example, the Vercel adapter generated by `mx build --vercel`) should call Load followed by Handler.

func (*Interpreter) RouteSummary added in v0.57.0

func (i *Interpreter) RouteSummary() []RouteInfo

RouteSummary returns one entry per registered route, in registration order. Static mounts and middleware declarations are not included — only `route VERB /path { ... }` and shorthand `get /x { ... }`.

func (*Interpreter) Run

func (i *Interpreter) Run(prog *parser.Program) error

Run executes a parsed program. If the program declared any routes, it boots an HTTP server and blocks; otherwise it returns once the top-level statements have all been evaluated.

func (*Interpreter) RunBench added in v1.32.0

func (i *Interpreter) RunBench(idx int) error

RunBench evaluates the body of the inline benchmark at the given index once. The CLI's `mx bench` calls this in a tight loop to time it; we don't add timing here because the calibration logic belongs in the runner, not the language.

func (*Interpreter) RunTest added in v1.29.0

func (i *Interpreter) RunTest(idx int) error

RunTest evaluates the body of the inline test at the given index. Index matches the order returned by Tests(). The body runs in a fresh child scope so `let` bindings inside the test don't leak. Any error (assertion failure, runtime error) propagates back so the caller can format it with file/line context.

func (*Interpreter) SetBytecode added in v0.52.0

func (i *Interpreter) SetBytecode(on bool)

SetBytecode toggles the experimental stack VM. When on, expression statements that compile cleanly are lowered to bytecode and run on the VM; nodes the compiler doesn't understand fall back to the tree-walker transparently. Off by default.

func (*Interpreter) SetFile

func (i *Interpreter) SetFile(path string)

SetFile records the source file path for error messages.

func (*Interpreter) SetPort

func (i *Interpreter) SetPort(p int)

SetPort marks the CLI-provided port. It overrides any port set by the program's `server { port: ... }` block so `mx run --port 3000` always wins.

func (*Interpreter) SetSnapshotMode added in v1.34.0

func (i *Interpreter) SetSnapshotMode(mode string)

snapshotMode controls how assert_snapshot resolves a missing or changed snapshot. "" / "compare" (default): fail on miss/diff. "update": overwrite the snapshot from disk and pass.

func (*Interpreter) SnapshotMode added in v1.34.0

func (i *Interpreter) SnapshotMode() string

func (*Interpreter) Tests added in v1.29.0

func (i *Interpreter) Tests() []TestInfo

Tests returns the inline `test "name" { ... }` declarations the loaded program registered, in source order. `mx test` calls this after Load to discover what to run.

type MXError

type MXError struct {
	Message string
	Line    int
	Col     int
	File    string
	Stack   []StackFrame
}

MXError is a structured runtime error with file/line context and the active call stack at the moment of failure.

func (*MXError) Error

func (e *MXError) Error() string

type Op added in v0.52.0

type Op uint8
const (
	OpConst          Op = iota // push consts[arg]
	OpLoadVar                  // push env.Get(varNames[arg])
	OpStoreVar                 // env.Set(varNames[arg], pop()) — for `let`
	OpAssignVar                // env.Assign(varNames[arg], pop()) — for `=`
	OpAdd                      // a + b (numeric or string concat)
	OpSub                      // a - b
	OpMul                      // a * b
	OpDiv                      // a / b
	OpMod                      // a % b
	OpEq                       // a == b
	OpNeq                      // a != b
	OpLt                       // a < b
	OpGt                       // a > b
	OpLte                      // a <= b
	OpGte                      // a >= b
	OpNot                      // !a
	OpNeg                      // -a
	OpJump                     // pc = arg (absolute)
	OpJumpIfFalse              // pop; if !truthy: pc = arg
	OpPop                      // discard top
	OpReturn                   // halt; return top of stack (or null)
	OpCall                     // pop arg values + callee; push call result
	OpGetField                 // pop obj; push obj.<consts[arg].String>
	OpMakeArray                // pop arg values; push as KindArray
	OpMakeObject               // pop arg*2 values (key, value pairs); push as KindObject
	OpGetIndex                 // pop index; pop target; push target[index]
	OpLength                   // pop value; push its length (array len, string len, object key count)
	OpAndJump                  // peek; if falsy: pc = arg (leaves value); else pop + continue
	OpOrJump                   // peek; if truthy: pc = arg (leaves value); else pop + continue
	OpNullishJump              // peek; if non-null: pc = arg (leaves value); else pop + continue
	OpJumpIfNullKeep           // peek; if null: pc = arg (leaves null); else continue with value still on stack
)

type OrderedMap

type OrderedMap struct {
	Keys   []string
	Values map[string]Value
}

OrderedMap preserves insertion order of object keys for predictable JSON output.

func NewOrderedMap

func NewOrderedMap() *OrderedMap

func (*OrderedMap) Get

func (o *OrderedMap) Get(k string) (Value, bool)

func (*OrderedMap) Set

func (o *OrderedMap) Set(k string, v Value)

type Response

type Response struct {
	Status      int
	ContentType string
	Body        Value
	Headers     map[string]string
	Cookies     []*http.Cookie
}

type RouteInfo added in v0.57.0

type RouteInfo struct {
	Method      string
	Path        string
	Middlewares []string
}

RouteInfo is a public summary of one route — the shape returned by the `mx routes` CLI and any embedder that wants to introspect routes without poking at unexported fields.

type StackFrame added in v0.8.0

type StackFrame struct {
	Name string
	Line int
	Col  int
}

StackFrame describes one active call when an error fired.

type TestInfo added in v1.29.0

type TestInfo struct {
	Name string
	Line int
}

TestInfo is the public summary returned by Tests() — name + source line so cmdTest can print nice headers without poking at internals.

type Value

type Value struct {
	Kind     ValueKind
	Bool     bool
	Number   float64
	String   string
	Array    []Value
	Object   *OrderedMap
	Function *Function
	Response *Response
	Channel  *Channel
	// Handle is a generic opaque pointer for resources that don't fit
	// the standard value shapes — DB connections, future file handles,
	// etc. Treated as a black box by the interpreter.
	Handle any
}

func ArrayValue

func ArrayValue(a []Value) Value

func BoolValue

func BoolValue(b bool) Value

func ChannelValue added in v0.16.0

func ChannelValue(c *Channel) Value

func FunctionValue

func FunctionValue(f *Function) Value

func HandleValue added in v0.22.0

func HandleValue(h any) Value

func NullValue

func NullValue() Value

Helpers to construct values.

func NumberValue

func NumberValue(n float64) Value

func ObjectValue

func ObjectValue(o *OrderedMap) Value

func ResponseValue

func ResponseValue(r *Response) Value

func StringValue

func StringValue(s string) Value

func (Value) Display

func (v Value) Display() string

String produces a human-readable representation, used by print().

func (Value) IsTruthy

func (v Value) IsTruthy() bool

IsTruthy follows the rules: null/false/0/""/[]/{} are falsy; anything else is truthy.

type ValueKind

type ValueKind int
const (
	KindNull ValueKind = iota
	KindBool
	KindNumber
	KindString
	KindArray
	KindObject
	KindFunction
	KindResponse
	KindChannel
	KindHandle
)

type WSConn added in v0.21.0

type WSConn struct {
	// contains filtered or unexported fields
}

WSConn is the per-connection handle exposed to .mx code. Methods are safe for concurrent use; the underlying conn is guarded by writeMu.

`clientSide` is set when the connection was opened via ws.connect: per RFC 6455 §5.3 client frames MUST be masked while server frames MUST NOT, so writeFrame branches on this flag.

func DialWebSocket added in v0.87.0

func DialWebSocket(rawURL string, headers map[string]string) (*WSConn, error)

DialWebSocket opens an outbound WebSocket connection. Supports both `ws://host:port/path` and `wss://host:port/path`. Returns a WSConn that supports the same WriteText / ReadFrame / WriteClose surface as server-side connections — except writes are masked per the spec.

func (*WSConn) ReadMessage added in v0.21.0

func (c *WSConn) ReadMessage() (string, bool, error)

ReadMessage reads one application-level message from the peer. It transparently handles continuation frames, ping/pong, and close frames. Returns the raw payload as a string and a boolean indicating whether the message is text (true) or binary (false). On normal close it returns io.EOF; on protocol error it returns a descriptive error.

func (*WSConn) WriteBinary added in v0.21.0

func (c *WSConn) WriteBinary(b []byte) error

WriteBinary sends a binary frame.

func (*WSConn) WriteClose added in v0.21.0

func (c *WSConn) WriteClose(code int, reason string) error

WriteClose sends a close frame with the given code and reason and then closes the underlying TCP connection. Subsequent calls are no-ops.

func (*WSConn) WriteText added in v0.21.0

func (c *WSConn) WriteText(s string) error

WriteText sends a text frame containing the given UTF-8 string.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL