Tooling

March ships with a build tool, an LSP server, and a tree-sitter grammar for editor syntax highlighting.


Installing forge

After cloning and building the repo, install forge to your PATH with:

dune build && dune install forge

Then all forge commands are available directly:

forge run my_program.march
forge test
forge search "map"

forge — Build Tool

forge is the official project manager for March.

Creating a Project

forge new my_app           # application project (default)
forge new my_lib --lib     # library project
forge new my_tool --tool   # CLI tool project
cd my_app

Scaffolded layout:

my_app/
├── forge.toml          # project manifest
├── src/
│   └── my_app.march    # entry point (mod MyApp do ... end)
└── test/
    └── my_app_test.march

forge.toml:

[package]
name = "my_app"
version = "0.1.0"

[dependencies]
# add deps here

To add a forge.toml to an existing directory without scaffolding:

forge init

Building and Running

# Build the project
forge build

# Build in release mode (optimized)
forge build --release

# Build and run
forge run

# Run with arguments
forge run -- --port 8080

# Compile to native binary via LLVM (instead of the interpreter)
forge run --compiled

# Dump compiler IR phases to trace/phases/phases.json
forge build --dump-phases
forge run --dump-phases

# Fail if forge.lock is out of sync with forge.toml
forge build --frozen

In a workspace, build a single member:

forge build -p my_lib

Checking Types

forge check typechecks every .march file in the project without producing a binary. It’s fast — use it for pre-commit checks or continuous editor feedback:

forge check

This catches type errors in every file under lib/ (including orphaned modules that aren’t reachable from the entry point), without paying for codegen or linking.

Testing

# Run all tests
forge test

# Run tests matching a filter
forge test --filter "list operations"

# Show each test name as it runs
forge test --verbose

# Run a specific test file
forge test test/parser_test.march

# Property tests: run with a fixed seed (for reproducibility)
forge test --seed 42

# Skip property-based tests (Check.all)
forge test --skip-properties

# Collect and report coverage
forge test --coverage

# Compile test binary at -O2 (slower build, faster runtime)
forge test --release

Linting

forge lint runs the March coding-standard rule engine across all source files:

forge lint               # report errors and warnings
forge lint --strict      # treat warnings as errors; exit 1 on any finding
forge lint --all         # also report hint-level findings

Rules are configurable via .march-lint.toml at the project root:

[rules]
snake_case_functions = "error"
unused_let           = "warning"
missing_doc          = "off"

Watch Mode

forge watch reruns a command whenever a source file changes. It never exits on failure — it reports and keeps watching. Press Ctrl-C to stop.

forge watch            # rebuild on change (default)
forge watch test       # rerun tests on change
forge watch run        # rerun the app on change

forge watch --clear    # clear the screen before each run
forge watch --interval 500   # poll every 500 ms (default: 300 ms)

Formatting

forge format                # format all .march files
forge format --check        # check without modifying (for CI)
forge format --stdin        # read from stdin, write to stdout (editor integration)

Cleaning Build Artifacts

forge clean           # remove build outputs under .march/build/
forge clean --cas     # also remove the content-addressable cache (.march/cas/)
forge clean --all     # remove the entire .march/ directory

Interactive Mode (REPL)

forge interactive
# alias:
forge i

forge search lets you find functions by name, type signature, or documentation keyword.

Search by Name

forge search "map"
# Finds: List.map, Map.map_values, Option.map, Result.map, ...

forge search "fold"
# Finds: List.fold_left, List.fold_right, Map.fold, Enum.fold, ...

Search by Type Signature

forge search --type "List(a) -> (a -> b) -> List(b)"
# Finds: List.map, Enum.map

forge search --type "Option(a) -> a -> a"
# Finds: Option.unwrap_or

forge search --type "String -> Int"
# Finds: String.length, String.to_int (partial)

Search by Documentation Keyword

forge search --doc "sort"
# Finds functions with "sort" in their docstrings

forge search --doc "hash"

Output Options

forge search "map" --limit 5      # cap results (default: 20)
forge search "map" --json         # JSON output
forge search "map" --pretty       # colored, aligned table

Rebuilding the Search Index

forge search --rebuild

The search index is cached at .march/search-index.json and rebuilt when source changes.


forge cap — Capability and typestate inspection

forge cap query prints a capability and typestate summary across all .march files in your project. It parses (but does not typecheck) each file and reports every needs, always_linear type, transitions, and proof cap declaration.

forge cap query              # scan the whole project
forge cap query --dir lib/   # scan a specific directory

Example output for a project with a typestate database handle:

./lib/db.march
  needs:
    IO.Network
  always_linear:
    Handle
  transitions:
    Handle:
      ConnTag: Closed -> Open  via connect
      ConnTag: Open -> Open    via query
      ConnTag: Open -> Closed  via close
  proof_caps:
    Migrated

./lib/auth.march
  needs:
    IO.Network
    Db.Migrated

This gives you a top-level map of what your codebase touches and what resource lifecycles it manages — useful during code review, security audits, or onboarding a new contributor.


Dependency Management

Adding Dependencies

Use forge add to add a dependency without manually editing forge.toml:

# Git dependency (pinned to a tag)
forge add depot --git https://github.com/march-language/depot --tag v1.2.0

# Git dependency (tracked branch)
forge add depot --git https://github.com/march-language/depot --branch main

# Git dependency (exact commit)
forge add depot --git https://github.com/march-language/depot --rev a3f1c9b

# Local path dependency
forge add my_lib --path ../my_lib

# Dev dependency (available in dev + test builds)
forge add check --git https://github.com/march-language/check --tag v0.3.0 --dev

# Test-only dependency
forge add fixtures --path ../fixtures --test

# Overwrite an existing dependency entry
forge add depot --git https://github.com/march-language/depot --tag v1.3.0 --force

Or edit forge.toml directly:

[dependencies]
depot = { git = "https://github.com/march-language/depot", tag = "v1.2.0" }

Then resolve:

forge deps

Updating Dependencies

forge deps update
forge deps update depot   # update a specific package

Lock File

forge.lock pins exact versions for reproducible builds. Commit it to version control.

Semver Constraints

Syntax Meaning
~> 1.2 >= 1.2.0, < 2.0.0
~> 1.2.3 >= 1.2.3, < 1.3.0
>= 1.0.0 At least 1.0.0
= 1.2.3 Exactly 1.2.3

Dependency Tree

forge tree         # print the full dependency graph
forge why depot    # show all paths that pull in `depot`

Refactoring

forge refactor provides project-wide, parser-based refactorings. All subcommands accept --dry-run / -n to preview changes without writing any files.

Rename a Symbol

# Rename a function, type, constructor, or any symbol
forge refactor rename old_name new_name

# Restrict to a specific kind (fn, type, ctor, module, field, var)
forge refactor rename Parser.parse Parser.run --kind fn

# Regex rename with backreferences
forge refactor rename 'get_(.+)' 'fetch_\1' --pattern

Move a Declaration

# Move a top-level declaration to another file
forge refactor move MyParser lib/parser.march

Structural Find-and-Replace

# Swap argument order at all call sites
forge refactor replace 'f($a, $b)' 'f($b, $a)'

Apply Naming Conventions

# Auto-fix snake_case function names project-wide
forge refactor fix

# Preview without writing
forge refactor fix --dry-run

Bundle Parameters into a Record

# Turn a function's parameters into a generated record type
forge refactor bundle parse_options
forge refactor bundle parse_options --record ParseConfig

Documentation Generation

forge doc generates HTML documentation from March source files. It requires the march_doc archive:

forge install march_doc   # install once

Then:

forge doc                    # generate to doc/ (default)
forge doc -o docs/api        # custom output directory
forge doc --private          # include private (pfn) functions
forge doc --stdlib           # document stdlib only

Notebooks

forge notebook provides a Livebook-style interactive environment for March using .scrollmd files. The live server requires the scroll archive:

forge install scroll   # install once
# Start a fresh notebook in the browser
forge notebook

# Open or create a specific notebook
forge notebook my_notes.scrollmd

# Start the live server explicitly
forge notebook my_notes.scrollmd --serve

# Render a notebook to static HTML
forge notebook my_notes.scrollmd -o output.html

# Use a custom port (default: 4040)
forge notebook --port 8080

# Don't open the browser automatically
forge notebook --no-open

Versioning and Release

Inspect and Bump Versions

forge version                # print current version
forge version patch          # bump patch: 1.2.3 -> 1.2.4
forge version minor          # bump minor: 1.2.3 -> 1.3.0
forge version major          # bump major: 1.2.3 -> 2.0.0
forge version 1.5.0          # set an explicit version

# Commit the bump and create an annotated git tag
forge version patch --tag

Guarded Release Pipeline

forge release requires a clean working tree, then runs build → test → version bump → git tag in sequence. Any failure aborts before the tag is created:

forge release                # patch bump (default)
forge release --bump minor
forge release --bump major

Publishing

forge publish validates the package and optionally checks that the version bump is correct given the API changes:

forge publish
forge publish --dry-run                          # validate only, don't submit
forge publish --old-source ../my_lib-v1.0        # enforce semver against old API surface

When --old-source is provided, forge computes the API surface diff and errors if the declared version bump is too small (e.g. a breaking change requires a major bump).


Archive Management

Archives are globally installed forge extensions — tools, task runners, and generators.

# Install from the registry or a git URL
forge install march_doc
forge install scroll
forge install https://github.com/march-language/my_tool
forge install --force scroll     # reinstall even if already installed

# Remove an archive
forge uninstall march_doc

# List installed archives
forge archives

# Update installed archives
forge update                     # update all
forge update march_doc           # update one

# Verify archive integrity
forge verify                     # verify all
forge verify march_doc           # verify one

Archive tasks are invoked as forge <archive>.<task>:

forge march_doc.build    # run the `build` task of march_doc

Toolchain Management

forge toolchain manages installed March compiler versions.

# List installed toolchains
forge toolchain list

# List available versions from GitHub releases
forge toolchain list --remote

# Install a specific version
forge toolchain install v0.3.0
forge toolchain install nightly          # latest nightly
forge toolchain install nightly-20251201

# Switch the active toolchain
forge toolchain use v0.3.0

# Pin this project to a specific version (writes .march-version)
forge toolchain pin v0.3.0

# Show which toolchain resolves for the current directory
forge toolchain which

# Remove an installed toolchain
forge toolchain uninstall v0.2.0

# Install the latest stable and make it active
forge upgrade

License Audit

forge licenses             # list each dependency and its declared license
forge licenses --json      # JSON output for tooling
forge licenses --strict    # exit non-zero if any dependency has no license

Capability Inspection

forge cap query performs a static analysis pass over the project and summarizes all capability and typestate declarations:

forge cap query                 # scan the project root
forge cap query --dir lib/      # scan a specific directory

Output lists every needs, always_linear type, transitions, and proof cap declaration found in the source, by file.


Shell Completions

Generate a completion script for your shell and source it:

forge completions bash >> ~/.bashrc
forge completions zsh  >> ~/.zshrc
forge completions fish > ~/.config/fish/completions/forge.fish

LSP Server

March ships march-lsp, a Language Server Protocol server built on the compiler’s own parse/typecheck pipeline, so diagnostics, hover types, and completions are always accurate. It provides diagnostics, hover, go-to-definition and find-references (cross-file), completions with auto-import, a large code-action suite, rename, signature help, inlay hints, semantic tokens, call hierarchy, workspace symbols, and per-function performance insights. A Debug Adapter Protocol server (march dap) and a standalone JSON query CLI ship alongside it.

See the dedicated LSP & Editors page for the full feature list, per-editor setup (Neovim, Helix, Zed, Emacs, VS Code), the march-lsp query CLI, and the DAP debugger.


Zed Editor

March ships a tree-sitter grammar for Zed with syntax highlighting and bracket matching.

Installing the Extension

The grammar is at tree-sitter-march/ in the repository. In Zed:

  1. Open the command palette: Cmd+Shift+P
  2. Search for “Install Dev Extension”
  3. Point to tree-sitter-march/

Alternatively, the compiled march.dylib can be installed directly into Zed’s extension directory.

What’s Highlighted

  • Keywords: fn, pfn, let, match, do, end, mod, actor, on, type, etc.
  • String literals and interpolation (${})
  • Comments (-- and {- -})
  • Operators and punctuation
  • Type annotations
  • Constructors (capitalized identifiers)
  • Atoms (:name)

Time-Travel Debugger

Place a dbg() breakpoint anywhere in your code:

fn process(items : List(Int)) : Int do
  let filtered = List.filter(items, fn x -> x > 0)
  dbg()    -- breakpoint: REPL opens here
  let result = List.fold_left(0, filtered, fn acc x -> acc + x)
  result
end

When execution reaches dbg(), the program pauses and enters debug mode:

[debug] Breakpoint hit — :continue to resume, :help for commands

dbg> :where
process  examples/debug.march:5
main     examples/debug.march:12

dbg> filtered
[1, 3, 5] : List(Int)

dbg> :back 2
-- stepped back 2 steps

dbg> :continue
-- resuming...

Debug REPL commands:

:continue           — resume execution
:back N             — step N steps backward in time
:forward N          — step N steps forward
:goto N             — jump to step N
:where              — show current call stack
:diff N [names]     — show what changed at step N
:find               — search for a step matching a condition
:trace N            — show N steps of execution trace
:actors             — list all actors and their state history
:actor ID           — show a specific actor's message history

The debugger captures a full execution trace including all actor message sends and receives.


Compiler Analysis

Dumping IR Phases

Add --dump-phases to any build or run command to serialize each compiler IR stage to trace/phases/phases.json:

forge build --dump-phases
forge run --dump-phases

To compile a single .march file and dump phases (without a forge project):

forge compile my_program.march

This compiles the file, writes the binary to .forge/compile/my_program, and writes phases to trace/phases/phases.json.

Viewing Phases in the Browser

forge phases          # serve phase viewer at http://localhost:7777
forge phases --port 8888

forge phases opens the browser automatically and serves an interactive viewer showing:

  • Per-function TIR dumps at each compiler pass
  • Inline eligibility and reasoning
  • RC density visualization (which values are reference-counted most)

Analyzing GC Traces

MARCH_TRACE_GC=1 forge run my_program.march

With MARCH_TRACE_GC=1 set, the runtime logs all reference-counting operations to trace/gc/gc.jsonl. The analysis command reports:

  • Leaked objects (allocated but never freed)
  • Double frees
  • Negative reference counts (invariant violations)

Open tools/gc-viewer.html after running with MARCH_TRACE_GC=1:

  • Timeline of alloc/free/inc_ref/dec_ref events
  • Live-object count chart
  • Address history for any specific object

Benchmarking

forge bench                   # run all benchmarks under bench/
forge bench list_ops          # run only benchmarks whose name contains "list_ops"
forge bench --json            # emit timings as JSON (for CI tracking)

Each bench/*.march file is a standalone benchmark program with a main() function. Benchmarks are compiled at -O2 and timed.


Environment Variables

Variable Effect
MARCH_LIB_PATH Colon-separated paths for multi-file project discovery
MARCH_TRACE_GC Set to 1 to log GC events to trace/gc/gc.jsonl
MARCH_HISTORY_FILE REPL history file path (default: ~/.march_history)
MARCH_HISTORY_SIZE Max REPL history entries (default: 1000)
MARCH_ENV development / test / production (read by Config.env)

Next Steps