burn / docs

Tools

Projects and packages

A Burn project is a folder with a burn.toml. Projects share code as packages: any git repository with a burn.toml at the top is one, named after where it lives.

github.com/owner/project
└──┬─────┘ └─┬─┘ └──┬──┘
 domain    owner  project

There is nothing to register. Push the code, tag a version, and everyone can install it with ash, the package manager, which comes with the toolchain.

Creating a project

burn init github.com/you/hello                 # an app that builds to a native executable
burn init github.com/you/hello --target bvm    # an app that builds to bvm bytecode
burn init github.com/you/hello --target bar    # an app that builds to a runnable archive
burn init github.com/you/hello --target js     # an app that builds to JavaScript for Node.js
burn init github.com/you/colors --lib          # a library

ash init <name> does the same. ash init without a name asks step by step instead, with a suggestion for every answer: the package name (from your git user and the folder), whether to create an app, a library or a workspace, what an app builds to, the directory and whether to create a git repository. It shows what it will create and asks before creating it:

$ ash init
Create a new Burn project
? Package name (github.com/you/my-project)
? What do you want to create?
  › 1 app       a program you run
    2 lib       a package others import
    3 workspace a shared library and one app per target
? What should `ash build` make?
  › 1 native    an executable for this computer
    2 js        a JavaScript file for Node.js
    3 bvm       portable bytecode (.bvmc)
    4 bar       a runnable archive (.bar)
? Directory (my-project)
? Create a git repository? (Y/n)

An app gets src/main.bn, a library gets src/lib.bn and a test in tests/main.bn. Both get a burn.toml, a .gitignore, a README.md and a git repository (skip it with --no-git). Pass a directory after the name to create the project somewhere else, or . for the current folder.

Project layout

Every project has the same shape, so tools and people always know where things are:

hello/
├── burn.toml          the manifest
├── burn.lock          exact versions of the dependencies (written by ash)
├── src/               the sources; folders in it are packages
│   ├── main.bn        the program (an app), or lib.bn (a library)
│   ├── net/
│   │   ├── mod.bn     the module `net` (optional)
│   │   └── http.bn    the module `net/http`
│   └── bin/           more programs in the same project
│       ├── tool.bn    `burn run --bin tool`
│       └── server/
│           └── main.bn  `burn run --bin server`
├── tests/             every file is a test program: `burn test`
├── examples/          `burn run --example <name>`
└── build/             what `burn build` writes (ignored by git)

Sources import each other through the project's source root, wherever they are, with @/. Other projects use the package name, the same path written out in full:

import "@/net/http"                 // inside the project
import "github.com/you/hello/net/http"  // from anywhere

src/ is the folder of the main file, so a project whose main is somewhere else has its source root there.

Inside a project, burn commands work without a file name:

Command What it does
burn run [args] run main on bvm
burn run --bin <name> run src/bin/<name>.bn (or src/bin/<name>/main.bn)
burn run --example <name> run examples/<name>.bn
burn build build main and every program in src/bin for the project's target into build/; --target overrides it, --bin and --example build one program
burn check type-check every source, program, test and example of the project
burn test [filter] run each file in tests/; a test fails when it exits with an error, such as a failed assert

In a workspace, each of these takes -p <member>.

Workspaces

A workspace is one repository with several projects that are built together, like a Gradle multi-project build or a Cargo workspace. The usual shape keeps the shared code in one library and adds one app per target, so the same program ships as a native executable, a JavaScript file and bvm bytecode:

burn init github.com/you/game --workspace                    # common + native, js and bvm apps
burn init github.com/you/game --workspace --targets native,bar
game/
├── burn.toml            [workspace] with the members
├── burn.lock            one lock file for every member
├── common/              github.com/you/game/common, a library
│   └── src/lib.bn
├── native/              github.com/you/game/native, target = "native"
│   └── src/main.bn      import "github.com/you/game/common"
├── js/                  github.com/you/game/js, target = "js"
└── bvm/                 github.com/you/game/bvm, target = "bvm"
[workspace]
name = "github.com/you/game"
members = ["common", "native", "js", "bvm"]
  • Members are ordinary projects named under the workspace (github.com/you/game/common). They import each other by name without listing each other as dependencies; a path dependency works too.
  • All members share the burn.lock at the root, so they always use the same versions of their packages.
  • At the root, burn build, burn check and burn test work on every member, and -p <member> picks one (burn run -p native, burn build -p js). Inside a member's folder, commands work on that member alone.
  • burn init <name> inside a workspace creates a new member and adds it to members; a plain name such as burn init tools --lib is named after the workspace (github.com/you/game/tools).
  • Other projects can install a member like any package: ash install github.com/you/game/common downloads the repository github.com/you/game and uses its common folder. A member that others install should list the members it uses as path dependencies; ash pins them to the same commit.

burn.toml

[package]
name = "github.com/you/hello"
version = "0.1.0"
kind = "app"
target = "native"
main = "src/main.bn"
description = "Says hello"

[dependencies]
"github.com/you/colors" = "^1.2"
"github.com/you/local" = { path = "../local" }
"example.org/team/tool" = { version = "^0.3", git = "https://example.org/team/tool.git" }

[scripts]
dev = "burni src/main.bn"
release = "burnc src/main.bn -o build/hello.bvmc"
start = "burn build/hello.bvmc"
Key Meaning
name where the code lives: <domain>.<tld>/<owner>/<project>
version the version, tagged in git as v<version> when it is published
kind "app" (a program) or "lib" (a library that others import)
target what burn build makes: "native" (an executable, the default), "js" (JavaScript for Node.js), "bvm" (bytecode) or "bar" (a runnable archive)
main the entry file (default src/main.bn, or src/lib.bn for a library)
output where burn build writes, relative to the project (default build/<project>)
std false builds without the standard library (default true)
burn the Burn version the project uses, installed and selected by burnup
[dependencies] packages, with a version, or a table with version, git and path
[scripts] shell commands for ash run <name>, or just ash <name>

Installing packages

ash install github.com/you/colors          # newest version, saved as "^x.y.z"
ash install github.com/you/colors@1.2      # a version range
ash install github.com/you/colors@main     # a branch
ash install ../local                       # a folder on this machine
ash install                                # everything in burn.toml, as burn.lock pins it
ash update                                 # newest versions burn.toml allows
ash remove github.com/you/colors
ash list                                   # the installed packages as a tree

Versions are git tags such as v1.2.0:

Written Allows
"^1.2" 1.2.0 up to, not including, 2.0.0
"~1.2" 1.2.x
"=1.2.3" exactly 1.2.3
">=1.2" 1.2.0 and anything newer
"*" the newest version, or the newest commit when there are no tags
"main" the newest commit on a branch
"#1a2b3c" one exact commit

burn.lock

burn.lock is the index of what is installed: the exact commit of every package, including the packages your packages need. ash writes it; commit it so everyone builds the same code.

version = 1

[[package]]
name = "github.com/you/colors"
version = "v1.2.0"
rev = "4f1c2b0e9a7d3c5b8e6f1a2d3c4b5a6f7e8d9c0b"
source = "git+https://github.com/you/colors"
dependencies = []

Downloads are kept in ~/.burn/packages/<name>@<commit>, so each version is fetched once and shared by every project. Local packages (source = "path+../local") are used where they are.

Importing a package

import "github.com/you/colors"
import "github.com/you/colors/extra"

print(paint("hello"))

The package name loads the package's main file; a longer path loads a module from its src/ folder (extra is src/extra.bn, and the older form with src/ written out still works). The compiler finds packages through burn.lock, so imports never touch the network. A project can import its own files by its name too, which is how a library's tests use it: import "github.com/you/colors".

Apps as packages, and mixins

Apps can be packages too. Importing an app's name loads its main file like a library; the importing program's own main is the one that runs. To change an app without touching its source, import it as bytecode and use mixins:

import "github.com/you/game.bvmc"

@Inject(target: "score", at: "return")
fun doubled(points: int, result: int): int {
    return result * 2
}

.bvmc after a package name compiles the package to a bytecode library (into its build/ folder, again only when its sources change) and links it in. Its top-level code and main run first, so running this program runs the game with the change applied, on bvm and in .bar archives alike.

Scripts and commands

ash run release      # run a script from burn.toml
ash release          # the same, when no ash command has that name
ash build            # the build script, or burn build
ash start            # the start script, or burn run
ash test             # the test script, or every file in tests/
ash install -g github.com/you/tool    # build an app and install it as a command in ~/.burn/bin

Publishing

ash publish --tag

checks that the project type-checks, that git's origin matches the package name and that nothing is uncommitted, then tags v<version> and pushes the tag. From then on ash install <name> works for everyone. To be found by ash search, open an Add a package issue on the ash index with the git URL. A check clones the repository, confirms that it is a Burn project named after where it is hosted, and the burnt bot adds it and closes the issue; if something is missing, the bot says what in the issue.

The standard library for tools

std/process and std/fs give Burn programs what tools like ash need: running programs (Process.run, Process.output, Process.shell), the working directory, and managing files and folders (Files.makeDir, Files.list, Files.remove, ...). See the standard library.