Tooling & IDE
Maggie ships with a complete development toolchain built into the mag
command. Source formatting, documentation generation, docstring testing,
image persistence, an LSP server, an interactive REPL, and a graphical
IDE are all available out of the box -- no external tools required.
This chapter walks through each tool and shows how they fit into a typical development workflow.
Source Formatting
The mag fmt command formats Maggie source files to a canonical style.
It parses each .mag file, walks the AST, and emits consistently
formatted output -- canonical indentation, consistent spacing around
operators, and normalized block layout. This is similar to gofmt for
Go or rustfmt for Rust.
By default, mag fmt formats all .mag files in the current directory
recursively. You can also pass specific files or directories as arguments.
# Format all .mag files in the current directory
mag fmt
# Format specific files or directories
mag fmt src/ lib/MyClass.mag
# Format a single file
mag fmt lib/models/User.mag
The --check flag verifies formatting without modifying any files.
It exits with code 1 if any file would be changed -- useful in CI
pipelines to enforce consistent style.
# Check formatting without modifying files
mag fmt --check
# Use in CI: fail the build if formatting is off
mag fmt --check src/ || echo "Run 'mag fmt' to fix formatting"
The formatter normalizes several aspects of the source:
- Indentation: two spaces per level
- Method signatures: canonical keyword/parameter layout
- Block formatting: single-line for short blocks, multi-line for long ones
- Docstrings: consistent triple-quote placement
- Trailing whitespace: removed
- File ending: exactly one trailing newline
The formatter never changes the semantics of your code -- it only adjusts whitespace and layout. If a file is already canonical, it is left untouched (no spurious modification timestamps).
Documentation Generation
The mag doc command generates HTML documentation from your classes,
methods, and their docstrings. It reads all classes loaded in the VM
and produces a browsable website with an API reference and guide pages.
# Generate HTML docs to the default directory (docs/api/)
mag doc
# Generate to a specific directory
mag doc --output ./site
# Set a custom title
mag doc --title "My Project API"
The generated site includes:
- An index page listing all classes grouped by namespace
- Individual pages for each class with method documentation
- Guide pages for any
Guide*classes (like this one) - Syntax-highlighted code blocks from docstrings
- A sidebar with navigation
Docstrings support two kinds of fenced code blocks. A test block
contains assertions (expressions with >>> expected values) that
can be verified by mag doctest. An example block contains
illustrative code that appears in the docs with a "Run" button when
served interactively.
The --serve flag generates the docs and then starts a local HTTP
server so you can browse them immediately. The server also provides
an /api/eval endpoint that lets you run example code interactively
from the browser.
# Generate docs and serve on the default port (8080)
mag doc --serve
# Serve on a custom port
mag doc --serve --port 3000
The documentation generator detects guide chapters automatically.
Any class whose name matches the pattern Guide\d{2}* (for example,
Guide01GettingStarted or Guide14Tooling) is treated as a guide
chapter rather than an API class. Guide chapters are rendered with
their methods sorted by selector name (s01_, s02_, etc.) to
preserve reading order.
Docstring Testing
The mag doctest command extracts test blocks from docstrings and
runs them as assertions. This ensures that the examples in your
documentation actually work and stay in sync with the code.
# Run all docstring tests
mag doctest
# Run with verbose output (shows each assertion)
mag doctest --verbose
# Run tests for a single class only
mag doctest --class Array
# Run tests for a namespaced class
mag doctest --class "Widgets::Button"
Inside a method docstring, a fenced test block contains lines with
>>> assertions. The expression before >>> is evaluated, the
expression after >>> is evaluated, and their printString results
are compared. Lines without >>> are setup lines -- they run for
side effects (like variable assignments) but do not count as assertions.
For example, a docstring might contain a test block where the first line assigns a variable (setup) and the second line checks the result (assertion). Setup lines that fail are reported as errors.
Important rules for doctest blocks:
- Use := for variable assignment -- | var | temporary declarations
are not supported inside test blocks
- Each assertion is independent in terms of what counts as pass/fail,
but they share a global environment (earlier assignments persist)
- Setup lines that cause a runtime error are reported as failures
- The --class filter matches both short names and fully-qualified names
The output shows results grouped by class and method, with green checkmarks for passing assertions and red crosses for failures. A summary line at the end reports total passed, failed, and elapsed time.
Image Persistence
Maggie can save and restore the entire VM state -- all classes, methods, globals, and docstrings -- as a binary image file. This is much faster than recompiling from source on every startup.
From Maggie code, use Compiler saveImage: to write the current state.
The Compiler class is always available in the global namespace:
Compiler evaluate: '1 + 1' >>> 2
From the command line, use the --save-image flag to compile sources
and then save the resulting state:
# Compile all source files and save an image
mag ./src/... --save-image my-app.image
# Load from a project manifest and save
mag --save-image my-app.image
To run from a previously saved image instead of recompiling from
source, use the --image flag:
# Run from a custom image
mag --image my-app.image -m Main.start
# Start a REPL with a custom image
mag --image my-app.image -i
The default image (maggie.image) is embedded in the mag binary
itself. It contains all the core library classes. When you run mag
without --image, this embedded image is loaded automatically.
Image files use a versioned binary format. The current format (v4) includes content hashes for each method, enabling the distribution protocol to verify code integrity. Images are forward-compatible: a v4 reader can load v3 images (methods will have zero hashes).
Typical workflow:
1. Develop with source files (mag ./src -m Main.start)
2. When ready to deploy, save an image (mag ./src --save-image app.image)
3. Distribute the image file (much smaller than source + compiler)
4. Run in production from the image (mag --image app.image -m Main.start)
LSP Server
Maggie includes a Language Server Protocol (LSP) server that provides editor integration for any LSP-compatible editor -- VS Code, Neovim, Emacs, Helix, Zed, and others.
# Start the LSP server on stdio
mag --lsp
# Subcommand form (equivalent)
mag lsp
The LSP server provides:
- Completion: triggered by . and : characters, suggests method
names based on the classes loaded in the VM
- Hover: shows docstrings and method signatures when you hover
over a selector or class name
- Go to Definition: jumps to the source location of a class or method
- Find References: finds all uses of a selector across loaded classes
To configure your editor, point it at mag --lsp as the language
server command for .mag files. For example, in VS Code's
settings.json:
{
"maggie.server.command": "mag",
"maggie.server.args": ["--lsp"]
}
The LSP server loads the default image on startup, so it knows about
all core library classes. If you pass source paths before --lsp,
those are compiled first, giving the server knowledge of your project
classes as well:
# Start LSP with project sources loaded
mag ./src/... --lsp
There is also a --serve flag (distinct from LSP) that starts a
gRPC + Connect language server for programmatic access:
# Start language server on default port 4567
mag --serve
# Start on a custom port
mag --serve --port 8080
REPL
The Maggie REPL (Read-Eval-Print Loop) is an interactive environment
for exploring the language. Start it with mag -i, or just run mag
with no arguments and no maggie.toml present.
# Start the REPL
mag -i
# Start the REPL with source files preloaded
mag ./src -i
Inside the REPL you can type any Maggie expression and see its result immediately. Multi-line input is supported -- the REPL detects incomplete expressions (unclosed brackets) and prompts for continuation lines.
The REPL supports several meta-commands, all prefixed with :.
:help (also :h or :?) shows the list of available commands:
mag> :help
REPL Commands:
:help, :h, :? Show this help
:help ClassName Show class documentation
:help Class>>method Show method documentation
:compiler Show current compiler
:use-go Switch to Go compiler (default)
:use-maggie Switch to Maggie compiler (experimental)
exit, quit Exit REPL
:help ClassName displays the docstring and method list for any
loaded class. :help ClassName>>methodName shows the docstring for
a specific method. This is the same information that mag doc
generates, but accessible interactively.
mag> :help Array
Array (subclass of Object)
Instance variables: none
...methods listed...
mag> :help Array>>at:
Array>>at:
Returns the element at the given index (0-based).
:use-go and :use-maggie switch between the Go-based compiler
(the default, production-quality backend) and the experimental
self-hosting Maggie compiler. The :compiler command shows which
backend is currently active.
The REPL also loads ~/.maggierc on startup (unless --no-rc is
passed). This file can contain any Maggie expressions to customize
your REPL environment -- defining helper methods, loading libraries,
or setting up variables.
Variables assigned in the REPL persist across evaluations:
Compiler evaluate: 'x := 42'.
Compiler evaluate: 'x + 1' >>> 43
Yutani IDE
Yutani is Maggie's graphical IDE, built as a TUI (terminal user interface) application. It connects to a Yutani server process and provides a desktop-like environment inside your terminal.
# Start the Yutani IDE launcher (connects to localhost:7755)
mag --yutani
# Connect to a specific server address
mag --yutani --yutani-addr host:port
# Start a specific IDE tool directly
mag --yutani --ide-tool inspector
mag --yutani --ide-tool repl
Before starting the IDE, make sure the Yutani server is running:
# Start the Yutani server (in a separate terminal)
yutani server
The available IDE tools are:
- launcher (default) -- the MaggieDesktop, a desktop-like environment with a taskbar and window management - inspector -- an object inspector for exploring live objects, their instance variables, and class hierarchies - repl -- a graphical REPL with syntax highlighting and output history
The Yutani IDE is written entirely in Maggie (the source is in
lib/yutani/). It uses Yutani's widget toolkit for layout and
event handling, communicating with the Yutani server over a
network session. This means you can connect to a remote Yutani
server and develop on a remote machine from your local terminal.
The --ide-tool flag selects which tool to launch. Some tools
(ClassBrowser, CodeEditor) are currently shelved and will return
an error with a message pointing to their shelved location.
Debugging Tools
Maggie provides several debugging facilities at different levels.
REPL-Based Debugging
The REPL itself is a powerful debugging tool. You can load your project sources and then interactively evaluate expressions to inspect state, call methods, and verify behavior:
# Load project and start REPL
mag ./src -i
# Then in the REPL:
mag> myObject inspect
mag> MyClass new doSomething
mag> :help MyClass>>suspiciousMethod
Docstring Tests as Regression Guards
Writing test blocks in your docstrings serves double duty: they
document expected behavior and catch regressions. Run mag doctest
after changes to verify nothing is broken:
# Quick regression check
mag doctest --class MyClass
Stack Overflow Protection
The VM enforces a maximum call frame depth (default 4096). When
exceeded, a StackOverflow exception is raised. This exception
is catchable via on:do:, so runaway recursion does not crash
the process. See the Blocks chapter for details on exception handling.
Yutani Debug Service
When debugging Yutani TUI applications, the Yutani server exposes a debug service that can dump screen state, inspect widget properties, and show event flow. This is especially useful when debugging layout or event handling issues in graphical applications.
# See what is on screen (ASCII dump with widget markers)
yutani debug screen -s <session-id> --bounds --legend
# Inspect a specific widget
yutani debug widget -s <session-id> -w <widget-id>
# List all widgets with positions
yutani debug bounds -s <session-id>
# JSON output for programmatic analysis
yutani debug screen -s <session-id> --format json
The session ID is printed when a Yutani application starts. Look for a line like:
YutaniSession: session created with ID: f1e6eb39-...
Process-Level Isolation
For sandboxing untrusted code, Maggie supports process-level
restriction. A forked process can be denied access to specific
globals (classes or modules), which simply resolve to nil in that
process. This is useful for safely evaluating user-provided code.
Restricted processes also get a copy-on-write overlay for globals,
so any assignments they make do not affect the parent process.
See the Concurrency chapter for full details on forkRestricted:
and Process forkWithout:do:.
Compiler Evaluate for Ad-Hoc Inspection
Compiler evaluate: can be used at any point to dynamically compile
and run expressions. Combined with Compiler getGlobal: and
Compiler setGlobal:to:, this gives you full programmatic access
to the VM's global state:
Compiler evaluate: '3 + 4' >>> 7
Compiler setGlobal: #debugFlag to: true.
Compiler getGlobal: #debugFlag >>> true
Profiling
Maggie includes a wall-clock sampling profiler that periodically snapshots interpreter call stacks and produces flamegraph-compatible output. This helps identify performance bottlenecks across the Maggie, gowrap, and Go boundary.
CLI Usage
The simplest way to profile is with the --profile flag:
# Profile at 1000 Hz (default), write to profile.folded
mag --profile -m Main.start
# Profile at 500 Hz, write to a custom file
mag --profile --profile-rate 500 --profile-output my.folded -m Main.start
# Also enable Go pprof CPU profiler (writes cpu.pprof)
mag --profile --pprof -m Main.start
The output file (profile.folded) uses the folded-stack format:
ClassName>>method1;ClassName>>method2;[primitive sleep:] 42
Each line is a semicolon-separated call stack followed by a sample count. This format is understood by:
- [flamegraph.pl](https://github.com/brendangregg/FlameGraph)
- [speedscope](https://www.speedscope.app/) (drag and drop the file)
- [inferno](https://github.com/jonhoo/inferno)
Maggie API
You can also control the profiler from Maggie code:
Compiler isProfiling >>> false
"Start profiling at 1000 Hz"
Compiler startProfiling.
"Start profiling at a custom rate (Hz)"
Compiler startProfiling: 500.
"Check if profiling is active"
Compiler isProfiling. "=> true"
"Stop and get folded-stack output as a String"
result := Compiler stopProfiling.
The stopProfiling method returns the folded-stack data as a
Maggie String, which you can write to a file or inspect in the REPL.
Building Custom Binaries
The mag build command compiles your project into a standalone
binary. There are two modes:
Entry-point-only (default): The binary loads the embedded image and runs the entry point. Smallest output, no CLI features.
mag build -o myapp
./myapp # runs Main.start
Full system (--full): The binary is a complete Maggie system
with your project's classes pre-loaded. All mag subcommands work.
When invoked with no arguments, it runs your entry point.
mag build --full -o myapp
./myapp # runs Main.start
./myapp -i # REPL with your classes loaded
./myapp fmt src/ # format your source files
./myapp doctest # run your docstring tests
./myapp help MyClass # show help for your classes
./myapp --lsp # LSP server with your classes
Multi-target builds: When [[target]] sections are defined
in maggie.toml, use -t to build a specific target or --all
to build all of them:
mag build -t server # build the 'server' target
mag build --all # build all targets
mag build -t cli -o /usr/local/bin/mycli
Full-system builds are useful for distributing a self-contained
Maggie environment. The recipient gets a single binary that can
run your application, explore it interactively, and use all the
standard development tools -- no separate mag install needed.
Both modes support Go interop via [go-wrap] in maggie.toml.
See the Go Interop chapter for details.
Workflow Summary
Here is a typical development workflow using Maggie's tooling:
1. Write code in .mag files organized by directory-as-namespace
2. Format with mag fmt (or mag fmt --check in CI)
3. Add docstrings with test and example blocks
4. Run doctests with mag doctest to verify examples
5. Run tests with mag test (uses [test] config from manifest)
6. Browse docs with mag doc --serve
7. Debug interactively in the REPL with mag -i
8. Use your editor with LSP support via mag --lsp
9. Run your app with mag run or mag run -t <target>
10. Save images with --save-image for fast deployment
11. Build binaries with mag build, mag build -t <target>, or mag build --all
12. Inspect graphically with mag --yutani when needed
All of these tools work together. Docstrings drive both the
documentation site and the test suite. The REPL shares its
evaluation pipeline with doctest and the doc server's playground.
The LSP server uses the same VM introspection that powers :help
in the REPL. Images capture the full state so that any tool
can start from a pre-compiled baseline. And mag build --full
packages the entire toolchain into a single distributable binary.