Butter
A high-performance, indentation-aware DSL compiler that converts clean .butter specifications into structured, production-ready JSON.
Modern application architectures often require declarative schemas to define features, validation rules, application parameters, or workflows. While JSON and YAML are industry standards, they can become verbose, deeply nested, and visually exhausting to write and maintain from scratch.
Butter solves this with an elegant, minimalistic language inspired by Python's significant indentation — stripping away trailing commas, brackets, curly braces, and redundant tags.
# Quick taste of Butter
app OrderProcessor
description "Handles high-throughput retail checkout workflows"
feature ProcessPayment
description "Processes financial transactions"
params
param OrderID
type string
required true
param Amount
type float
required true
actions
action "Validate routing balance metrics"
action "Flag for review" | if "Amount > 10000"
Design Philosophy
Core Objectives
- Zero Dependencies for Compilation — The lexer, parser, and semantic validation engines are entirely hand-written in native Go, ensuring extreme speed, predictable compilation, and zero supply-chain vulnerabilities.
- Significant Indentation — Structural scope is driven entirely by whitespace (spaces or tabs), optimizing readability and eliminating delimiter noise.
- Rich Conditionals — Four native keywords (
if,unless,when,while) naturally capture diverse semantic states. - Developer Ergonomics — Paired with a VS Code extension for syntax highlighting, auto-indentation, and on-save linting.
Why not just use JSON / YAML?
JSON's syntactic overhead (braces, commas, quotes) makes manual authoring tedious. YAML's implicit typing and complex rules (anchors, aliases, multi-document support) can produce surprising results. Butter sits in the middle: explicit enough to be unambiguous, minimal enough to be pleasant to write.
Installation
Prerequisites
From Source
git clone <repository-url> butter
cd butter
go build -o butter main.go
sudo cp butter /usr/local/bin/
Install Script (Linux / macOS)
The install.sh script handles both the compiler binary and the VS Code extension:
chmod +x install.sh
./install.sh # install compiler + VS Code extension
./install.sh update # rebuild and reinstall both
./install.sh binary # compiler only
./install.sh extension # VS Code extension only
The binary is installed to /usr/local/bin/butter (falls back to ~/.local/bin/butter). The script automatically checks if the install directory is in your PATH.
Install Script (Windows PowerShell)
.\install.ps1 # install compiler + VS Code extension
.\install.ps1 -Command update # rebuild and reinstall both
.\install.ps1 -Command binary # compiler only
.\install.ps1 -Command extension # VS Code extension only
VS Code Extension (Manual)
code --install-extension butter-extension.vsix
Or open the butter-extension/ directory in VS Code and press F5.
Verify Installation
butter --version
# butter v1.3.0
Getting Started
Your first .butter file
Create a file called hello.butter:
# My first Butter spec
app HelloWorld
description "A simple demonstration"
version "1.0.0"
feature Greet
params
param Name
type string
required true
default "World"
actions
action "Say hello to the user"
Compile it
butter compile hello.butter
This produces hello.json:
{
"app": "HelloWorld",
"description": "A simple demonstration",
"version": "1.0.0",
"features": [
{
"name": "Greet",
"params": [
{
"name": "Name",
"type": "string",
"required": true,
"default": "World"
}
],
"actions": [
{ "statement": "Say hello to the user" }
]
}
]
}
Check syntax without generating output
butter compile --check hello.butter
# OK
Use --check during development to validate syntax without cluttering your project with .json files. This is how the VS Code extension lints on every save.
Language Guide
Keywords
| Keyword | Context | Purpose |
|---|---|---|
app / product | Top-level | Defines the namespace or structural root of the configuration |
description | Top / Block | Provides context or documentation string metadata |
version | Top / Block | Declares the version identifier for the application or feature |
feature | Block-level | Declares a sub-system module, API endpoint, or discrete capability |
params | Block-level | A container block specifying input parameter definitions |
param | Item-level | Declares a discrete parameter variable name |
actions | Block-level | A container block specifying execution routines |
action | Item-level | Declares a logical execution string or mutation step |
Parameter Fields
| Field | Purpose |
|---|---|
type | Dictates data constraints (string, int, float, bool, enum[...]) |
required | Boolean validation rule (true or false) |
default | Explicit fallback value if the parameter is omitted |
validate | Validation rule for numeric parameters (int, float). E.g. >10, !=5, =<12. Multiple lines allowed. Mutually exclusive with length. |
length | Exact digit/numeric length constraint (e.g. length 13). Only on int/float. Mutually exclusive with validate. |
Structure
A Butter file has a single app declaration at the top level, which contains one or more feature blocks. Each feature can define parameters (params → param) and actions (actions → action).
app MyApp
description "..."
version "..."
feature FeatureA
description "..."
version "..."
params
param ParamName
type string
required true
default "value"
actions
action "Do something"
action "Do something else" | if "condition"
Block Nesting & Indentation
Blocks are defined by indentation. The first level after app has no indent. Each nested block (feature, params, param, actions) increases the indent level. Use 2 or 4 spaces (or tabs — tabs are normalized to 4 spaces).
Important: Indentation must be consistent within a file. Mixing tabs and spaces will produce an error.
Conditionals
Actions can have optional conditions attached via the pipe (|) character:
| Keyword | Meaning |
|---|---|
if | Executes only if the predicate evaluates to true. |
unless | Executes except when the predicate evaluates to true (inversion of if not). |
when | Reactive or event-driven hook; triggers upon an external event or state shift. |
while | Active polling or operational state persistence; the state condition must remain continuously active. |
action "Process payment" | if "Amount > 0"
action "Skip validation" | unless "TrustedSource == true"
action "Notify admin" | when "ThresholdExceeded"
action "Keep alive" | while "ConnectionActive"
Types
| Type | Example | Notes |
|---|---|---|
string | type string | Default type if omitted. |
bool | type bool | Boolean values (true or false). |
int | type int | Integer values. |
float | type float | Floating-point values. |
enum["A", "B"] | type enum["CreditCard", "Crypto"] | Restricted set of string values. |
Implicit Defaults
- A
paramwithout an explicittypedefaults tostring. - A
paramwithout an explicitrequireddefaults tofalse.
Comments
Line comments start with #. They can appear at the start of a line or inline after a statement:
# This is a comment
app MyApp # inline comment also works
CLI Reference
butter compile
Compiles a .butter file to pretty-printed JSON.
butter compile [input file] [flags]
Flags
| Flag | Shorthand | Description |
|---|---|---|
| --output | -o | Custom output path (defaults to <input>.json) |
| --check | Validate syntax without generating output |
Examples
butter compile demo.butter
butter compile demo.butter --output result.json
butter compile demo.butter -o result.json
butter compile --check demo.butter
butter fmt
Formats a .butter file according to standard conventions — removes blank lines after parameter keywords and adds blank lines before params, actions, and between top-level feature blocks.
butter fmt [input file] [flags]
Flags
| Flag | Description |
|---|---|
| --check | Check formatting without modifying |
Examples
butter fmt demo.butter
butter fmt --check demo.butter
butter --version
Prints the compiler version:
butter --version
# butter v1.3.0
Exit Codes
| Code | Meaning |
|---|---|
| 0 | Success (compilation or validation passed) |
| 1 | Error (syntax error, file not found, etc.) |
Error Output Format
When compilation fails, the error includes the line number and a descriptive message:
Error: compilation syntax compilation error:
line 5: expected an application name after 'app'
The VS Code extension parses this format to show red squiggly underlines at the reported line.
Compiler Architecture
Pipeline
[ .butter file ]
│
▼
┌───────────┐
│ Lexer │ ← Tracks Indentation Stack, emits INDENT/DEDENT/NEWLINE
└─────┬─────┘
│ (Stream of Tokens)
▼
┌───────────┐
│ Parser │ ← Stateful Recursive Descent State Machine
└─────┬─────┘
│ (Abstract Syntax Tree)
▼
┌───────────┐
│JSON Engine│ ← Go json.MarshalIndent Serialization
└─────┬─────┘
│
▼
[ .json file ]
Lexer
The lexer reads the input file sequentially while maintaining a LIFO indentation stack tracking the current whitespace depth:
- On each new line, leading whitespace is measured.
- If the indent exceeds the stack top, the new depth is pushed and an
INDENTtoken is emitted. - If the indent is less than the stack top, elements are popped (emitting a
DEDENTtoken each) until a matching level is found. - A mismatch throws an indentation error.
Token types: ERROR, EOF, IDENTIFIER, STRING, INDENT, DEDENT, NEWLINE, PIPE
Parser
Hand-written recursive descent parser that converts the token stream into a typed AST. It handles app, description, version, feature, params, param (with type, required, default), actions, action, and pipe-delimited conditionals.
AST Types
| Type | Fields |
|---|---|
AppSpec | app, description?, version?, features[] |
FeatureSpec | name, description?, version?, params[]?, actions[]? |
ParamSpec | name, type (default "string"), required (default false), default?, validate[]? |
ActionSpec | statement, condition? |
ConditionSpec | type (if/unless/when/while), expression |
JSON Engine
The AST is serialized to pretty-printed JSON via Go's json.MarshalIndent(app, "", " "). Fields tagged with omitempty are excluded when empty.
VS Code Extension
The project includes a VS Code extension (butter-extension/) providing language support for .butter files.
Features
- Syntax highlighting — Full TextMate grammar with named captures for
app,feature, andparamidentifiers. - On-save formatting — Automatically applies
butter fmtevery time you save, no configuration needed. - On-save linting — Runs
butter compile --checkafter formatting, surfacing syntax errors with red squiggly underlines and entries in the Problems panel. - Manual lint command —
Butter: Lint current filein the command palette. - Manual format command —
Butter: Format current filein the command palette. - Auto-indentation — Smart indent for
feature,params,actions, andparamblocks. - Configurable compiler path — Set via
butter.compilerPathin settings (defaults to"butter"). - Comment toggle — Ctrl+/ or Cmd+/ toggles
#line comments. - Auto-closing pairs —
"and[]are auto-closed. - Document icon — Custom SVG icon for
.butterfiles.
Installation
Use the install script: ./install.sh extension
Or manually: code --install-extension butter-extension.vsix
Settings
| Setting | Default | Description |
|---|---|---|
butter.compilerPath | "butter" | Path to the butter compiler binary. Use this if the binary is not on your PATH or to use a different version. |
Troubleshooting
Linting is not working. Make sure the
butterbinary is installed and accessible from thebutter.compilerPathsetting (default:PATH). Open the "Butter" output channel in the VS Code panel to see diagnostic messages.
Error Reference
The compiler produces line-precise error messages. Here are the most common ones and what they mean:
| Error Message | Likely Cause |
|---|---|
input file must have a .butter extension |
You tried to compile a file that doesn't end in .butter. |
expected an application name after 'app' |
The app keyword must be followed by a name (e.g. app MyApp). |
expected a quoted string for description |
description must be followed by a double-quoted string. |
expected a feature name after 'feature' |
feature must be followed by a name (e.g. feature MyFeature). |
expected an indented block under this feature |
The content after a feature declaration must be indented. |
expected a newline after 'params' |
params must be on its own line, followed by an indented block. |
expected 'param' inside this block, got '...' |
Only param declarations are allowed inside a params block. |
expected a parameter name after 'param' |
param must be followed by a name (e.g. param MyParam). |
unexpected '%s' for this parameter — expected 'type', 'required', 'default', 'validate', or 'length' |
Only type, required, default, validate, and length are valid parameter fields. |
invalid validate rule "..." — must be a numeric comparison like ">0", ">=1", "=<100", "!=5" |
The validate string must be a comparison operator followed by a number (e.g. >=1, !=0). |
validate rules require numeric type (int or float), got "..." |
validate is only allowed on parameters with type int or type float. |
length and validate cannot be used together on the same parameter |
A parameter can have length or validate, but not both. |
action statement must be a quoted string |
The action description must be wrapped in double quotes. |
unsupported condition ... after '|' — expected if, unless, when, or while |
The word after | must be one of the four conditionals. |
unexpected '...' at the top level — expected 'app', 'description', 'version', or 'feature' |
Only app, description, version, and feature are valid at the root level. |
unexpected '...' inside feature — expected 'description', 'version', 'params', or 'actions' |
Only description, version, params, and actions are valid inside a feature. |
Indentation mismatch |
The indentation level does not match any parent block level. Check that you're using consistent spacing. |
unexpected character '...' |
The character is not valid Butter syntax. Only letters, digits, underscores, pipes, quotes, and whitespace are allowed. |
Examples
Minimal
The simplest valid Butter file:
app Minimal
This compiles to:
{
"app": "Minimal",
"features": []
}
Todo App (using product)
A complete todo application example using product instead of app:
# TodoApp — A complete task management application
product TodoApp
description "A full-featured todo application with task management capabilities"
version "1.0.0"
feature CreateTask
description "Allows creation of new tasks with title, priority, and due date"
version "1.0.0"
params
param Title
type string
required true
param Description
type string
default ""
param Priority
type enum["low", "medium", "high", "urgent"]
default "medium"
param DueDate
type string
actions
action "Validate title is not empty"
action "Assign unique identifier to the new task"
action "Set creation timestamp to current time"
action "Send notification to assigned user" | if "Priority == urgent"
action "Schedule reminder for due date" | when "DueDate is not empty"
feature ListTasks
description "Retrieves and filters tasks based on various criteria"
version "1.0.0"
params
param StatusFilter
type enum["all", "pending", "completed", "archived"]
default "all"
param SortBy
type enum["created", "priority", "due_date", "title"]
default "created"
param Limit
type int
default 50
validate ">=1"
validate "<=200"
actions
action "Query task repository with specified filters"
action "Sort results according to SortBy parameter"
action "Limit results to specified count"
action "Apply pagination metadata to response"
feature UpdateTask
description "Modifies existing task attributes"
version "1.0.0"
params
param TaskID
type string
required true
param Title
type string
param Status
type enum["pending", "completed", "archived"]
param Priority
type enum["low", "medium", "high", "urgent"]
actions
action "Look up task by TaskID"
action "Merge provided fields into existing task record"
action "Set last-modified timestamp to current time"
action "Reject update if task is archived" | unless "Status != archived"
feature DeleteTask
description "Removes a task from the system permanently"
version "1.0.0"
params
param TaskID
type string
required true
param Confirmation
type string
required true
actions
action "Verify TaskID exists in repository"
action "Verify Confirmation matches task title"
action "Remove task record from persistent storage"
action "Broadcast deletion event to connected clients"
Compile: butter compile todo.butter
See the working app: todo.html — a complete single-page application built from this spec, with each feature's actions running as sequential steps. Update and Delete are accessed via a modal form by clicking Edit on any task in the list.
Using the JSON output with AI
Feed the compiled JSON to an AI model to generate application code. The actions within each feature are sequential execution steps — they must run in order, one after another, not in parallel.
Using this JSON specification, build the complete application. Each feature's
actions are sequential execution steps — they must be implemented strictly one
after the other in the listed order, never in parallel or reordered.
```json
{
"app": "TodoApp",
"description": "A full-featured todo application with task management capabilities",
"features": [
{
"name": "CreateTask",
"actions": [
{ "statement": "Validate title is not empty" },
{ "statement": "Assign unique identifier to the new task" },
{ "statement": "Set creation timestamp to current time" },
{
"statement": "Send notification to assigned user",
"condition": { "type": "if", "expression": "Priority == urgent" }
},
{
"statement": "Schedule reminder for due date",
"condition": { "type": "when", "expression": "DueDate is not empty" }
}
]
}
]
}
```
Changelog
v1.4.0 — 2026-06-25
booltype — Newboolparameter type for boolean values (true/false).lengthfield — Newlengthparameter field for exact length constraints (e.g.length 13). Mutually exclusive withvalidate.- Updated error messages — Param field error lists include
validateandlength; length/validate mutual exclusion enforced.
v1.3.0 — 2026-06-25
productkeyword alias —productcan now be used interchangeably withappat the top level, across parser, formatter, and syntax highlighting.- Numeric literals in defaults — Values like
default 50now parse correctly. The lexer now allows digits as identifier start characters. - Parser robustness —
parseParamloop now guards against infinite loops on unexpected token types. todo.butterexample — New complete todo application example usingproductwith four features, enum types, and integer defaults.- Validate rule format validation —
validaterules are now rejected at parse time if they don't match a valid numeric comparison (operator + number). Supported operators:>,>=,<,<=,=<,==,=,!=. - Validate type restriction —
validaterules are now rejected if the parameter type is notintorfloat.
v1.2.0 — 2026-06-24
- Butter Format (
butter fmt) — New subcommand to automatically format.butterfiles. Uses a two-pass algorithm: removes blank lines after parameter keywords, then inserts blank lines beforeparams,actions, and between top-levelfeatureblocks. - Format on save — The VS Code extension now automatically runs
butter fmton every save, no configuration needed. Butter: Format current filecommand — Available in the VS Code command palette.
v1.1.0 — 2026-06-23
--checkflag —butter compile --checkvalidates syntax without writing a.jsonfile. Returns exit code 0 on success, 1 on error.- On-save linting — The VS Code extension runs
butter compile --checkon every save, surfacing errors with red squiggly underlines. Butter: Lint current filecommand — Available in the VS Code command palette.- Configurable compiler path —
butter.compilerPathsetting lets you specify a custom path to thebutterbinary. butter --version— Prints the compiler version.- Named capture highlighting — TextMate grammar now highlights
app,feature, andparamidentifiers distinctly. versionkeyword — Added to syntax highlighting.- Document icon — Custom SVG icon for
.butterfiles.
v1.0.0 — 2026-06-22
- Initial release of the Butter DSL compiler.
butter compilecommand compiles.butterfiles to pretty-printed JSON.- Lexer with Python-style indentation tracking (INDENT/DEDENT tokens).
- Recursive descent parser supporting
app,description,version,feature,params,param(withtype,required,default),actions,action, and pipe-delimited conditionals (if,unless,when,while). - VS Code extension with syntax highlighting, indentation rules, and comment toggling.