Getting BMX

BMX is a format, so what you install depends on what you want to do with it. Three answers, shortest first.

In your editor — first, because it needs nothing else

Colour, a live preview and inline diagnostics come from an extension that bundles its own renderer, so it needs no compiler and no toolchain:

code --install-extension bmx.vsix

Helix and Neovim get diagnostics from a language server that is a Burxt binary you build once — it needs no runtime at all. That is a change, and it is a trade rather than an improvement: the server was a node script, so anyone with node had it working immediately and now needs a Burxt toolchain to build it. One fewer runtime to have installed, one more step to take. All three are on In your editor.

Everything below is for rendering documents from a program, which is a different job.

In Burxt — a package you pin

BMX used to ship inside the language, as lib/bmx.bx. It does not any more, and the change is worth understanding rather than working around: a module in somebody else’s standard library has that language’s version and no say in its own. BMX is 0.12 and Burxt is 1.3; one number could not honestly carry both.

So it is a dependency, named in your burxt.package:

name        my-app
version     0.1.0
dependency  bmx  https://github.com/andrecorugda/bmx  burxt-0.12.0
burxt fetch

Then in a program:

use "bmx/burxt/bmx.bx";

That gets you the parser, the level-1 renderer, and the generator. See Turning it into a page for which you want.

Why the path has burxt in it: this repository holds more than one implementation, and reference/bmx.js sits beside it. The first segment is your dependency’s name, the rest is where the file lives — so bmx/burxt/bmx.bx reads as the Burxt implementation, from the bmx package.

And what the split buys, since it costs a manifest line: burxt.lock pins a commit, so the BMX your build used is a fact somebody can check, and it stops moving when Burxt releases. The old arrangement had a real virtue — the format and its host could not drift apart — but it bought that by making drift unrepresentable rather than visible, which is only the same thing while nobody needs to upgrade one without the other.

If you reached BMX through another package, you still have to declare it

This is the one thing about installing BMX that will look like your mistake and is not. If you depend on something that depends on BMX — star-burxt, say — you must declare BMX yourself, under the same name that package uses:

dependency  star  https://github.com/andrecorugda/star-burxt  v0.1.0
dependency  bmx   https://github.com/andrecorugda/bmx         burxt-0.12.0  # star needs this

Leave the second line out and you get:

error: cannot read <star-burxt>/bmx/burxt/bmx.bx: No such file or directory
  ...used by <star-burxt>/star.bx
  ...used by app.bx

Read that error carefully, because it names nobody. It looks like a broken install of star-burxt, so the natural next move is to go looking for a mistake in your own program — and there is none. What happened is that use "bmx/…" inside star-burxt was resolved against your dependency list, found nothing there, and fell back to a relative path inside star-burxt’s own directory.

Burxt resolves package imports against the root manifest only: a dependency’s own manifest is never read. That is what keeps this repository from having to be a Burxt package at all — nothing here declares one — and it is the same rule seen from the other side. Whether it should change is Burxt’s to decide; until it does, one extra line is the whole workaround, and the versions have to be kept in step by hand.

Needs Burxt 1.3.0 or later. 1.2.0 was the first release that knew use "std/…", which is how a package reaches the standard library at all — but 1.3.0 is the floor now, because parts of the implementation are declared pure and that rests on a pure string_to_int, which 1.3.0 is the first to mark. On 1.2.0 the compiler says so rather than guessing:

error: `pure function bmx_parse_error` may not call `string_to_int`, which is not declared `pure`

CI here builds against 1.4.0, and that is not the floor. The pin is what this project tests against; the floor is what your host needs. They moved apart when 1.4.0 shipped burxt fmt, which this repository uses to check its own indentation — a tool, not a language feature. The formatted implementation still compiles on 1.3.0 and passes the whole suite there — 92 cases, and both of this repository’s own Burxt runners build and pass on it too. Measured against the published 1.3.0 asset, downloaded and checked against that release’s SHA256SUMS — because a version string does not identify a compiler, and for one tag on the machine this was written on there were three different binaries all reporting the same version. A pin quoted as a requirement tells every host to upgrade for a reason that is not theirs.

The two command-line tools

Both are ordinary Burxt programs, in this repository beside the implementation:

burxt build burxt/examples/parse.bx    -o bmx-parse      # a document -> its AST, as JSON
burxt build burxt/examples/generate.bx -o bmx-generate   # a document -> a typed Burxt view

Read your document from a file, not a string literal

The first thing most people try is passing a document straight to bmx_parse, and it does not compile:

bmx_parse("Total: {{ order.total }}")
error: in the interpolation `{{ order.total }}`: expected an expression, found `{`

Burxt has its own { … } interpolation inside string literals, and it reaches BMX’s slot first. Nothing is wrong with either language — they simply want the same two characters, and the error names Burxt’s feature rather than the collision, so it reads as a puzzle rather than an answer.

Read the document from a file and it goes away:

match file_read_maybe("doc.bmx") {
    None => { print("no document"); }
    Some(source) => { /* bmx_parse(source) */ }
}

That is what .bmx files are for anyway. It is worth knowing before you write your first test rather than during it.

bmx-generate is the interesting one — it is level 2, where a document becomes a function the compiler checks:

./bmx-generate receipt.bmx receipt_view "order: Order" > receipt_view.bx

If the document declares its own props, the signature argument can be empty:

:props: order: Order
:!props:
./bmx-generate receipt.bmx receipt_view ""

In JavaScript — one file, no dependencies

There is no npm package, deliberately for now: publishing a package is a promise to maintain a release channel, and BMX is 0.12 with two implementations by one author. Copy the file instead — it has zero dependencies and is written to be read.

curl -O https://raw.githubusercontent.com/andrecorugda/bmx/main/reference/bmx.js

Node 14 or newer, and that number is measured rather than chosen: the newest thing the file uses is nullish coalescing. CI runs it on Node 20, which is a pin and not this floor — see the note about Burxt’s two numbers above; the same distinction applies here, one runtime down. tests/portability.bx reads the floor out of this sentence and fails if the file ever uses something newer, because a zero-dependency parser whose runtime requirement is undocumented is not portable, it is untested on everything but one machine.

import { parse, BmxError } from './bmx.js'

try {
  const doc = parse(source)      // the AST
} catch (e) {
  if (e instanceof BmxError) console.error(e.code, e.offset, e.message)
}

Or run it directly over a file:

node bmx.js document.bmx     # prints the AST as JSON, or an error and exit 1

It reads documents; it does not check what is inside {{ … }}. Nothing is missing — BMX leaves the contents of a slot to whatever language is rendering, and JavaScript has nothing to check them against. In Burxt it does get checked, which is chapter 4.

In any other language

Nothing to install, and nothing stopping you — BMX is a format, so a reader for it is a few hundred lines in any language. Writing an implementation is the path: what to build, how to run the conformance suite against it, and the four things every implementation gets wrong.

If instead you are building a layer above BMX — a framework, a component system, a site generator — that is Building on BMX. This paragraph pointed there for both jobs, and the implementer arrived at a page whose first line says it is for the other one.

Which version am I getting?

BMX has its own version, separate from whatever language you are using it from. Yours can be at 3.0 and target BMX 0.12 quite happily.

Upgrading between small versions never changes what your existing documents mean — that is what the numbering is for, and Why documents agree explains how it is kept true rather than merely intended.