Butter logo

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

  • Go 1.21+ (for building from source)
  • VS Code (for the extension)

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

KeywordContextPurpose
app / productTop-levelDefines the namespace or structural root of the configuration
descriptionTop / BlockProvides context or documentation string metadata
versionTop / BlockDeclares the version identifier for the application or feature
featureBlock-levelDeclares a sub-system module, API endpoint, or discrete capability
paramsBlock-levelA container block specifying input parameter definitions
paramItem-levelDeclares a discrete parameter variable name
actionsBlock-levelA container block specifying execution routines
actionItem-levelDeclares a logical execution string or mutation step

Parameter Fields

FieldPurpose
typeDictates data constraints (string, int, float, bool, enum[...])
requiredBoolean validation rule (true or false)
defaultExplicit fallback value if the parameter is omitted
validateValidation rule for numeric parameters (int, float). E.g. >10, !=5, =<12. Multiple lines allowed. Mutually exclusive with length.
lengthExact 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 (paramsparam) and actions (actionsaction).

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:

KeywordMeaning
ifExecutes only if the predicate evaluates to true.
unlessExecutes except when the predicate evaluates to true (inversion of if not).
whenReactive or event-driven hook; triggers upon an external event or state shift.
whileActive 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

TypeExampleNotes
stringtype stringDefault type if omitted.
booltype boolBoolean values (true or false).
inttype intInteger values.
floattype floatFloating-point values.
enum["A", "B"]type enum["CreditCard", "Crypto"]Restricted set of string values.

Implicit Defaults

  • A param without an explicit type defaults to string.
  • A param without an explicit required defaults to false.

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

FlagShorthandDescription
--output-oCustom output path (defaults to <input>.json)
--checkValidate 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

FlagDescription
--checkCheck 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

CodeMeaning
0Success (compilation or validation passed)
1Error (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 INDENT token is emitted.
  • If the indent is less than the stack top, elements are popped (emitting a DEDENT token 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

TypeFields
AppSpecapp, description?, version?, features[]
FeatureSpecname, description?, version?, params[]?, actions[]?
ParamSpecname, type (default "string"), required (default false), default?, validate[]?
ActionSpecstatement, condition?
ConditionSpectype (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, and param identifiers.
  • On-save formatting — Automatically applies butter fmt every time you save, no configuration needed.
  • On-save linting — Runs butter compile --check after formatting, surfacing syntax errors with red squiggly underlines and entries in the Problems panel.
  • Manual lint commandButter: Lint current file in the command palette.
  • Manual format commandButter: Format current file in the command palette.
  • Auto-indentation — Smart indent for feature, params, actions, and param blocks.
  • Configurable compiler path — Set via butter.compilerPath in settings (defaults to "butter").
  • Comment toggleCtrl+/ or Cmd+/ toggles # line comments.
  • Auto-closing pairs" and [] are auto-closed.
  • Document icon — Custom SVG icon for .butter files.

Installation

Use the install script: ./install.sh extension

Or manually: code --install-extension butter-extension.vsix

Settings

SettingDefaultDescription
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 butter binary is installed and accessible from the butter.compilerPath setting (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 MessageLikely 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

  • bool type — New bool parameter type for boolean values (true / false).
  • length field — New length parameter field for exact length constraints (e.g. length 13). Mutually exclusive with validate.
  • Updated error messages — Param field error lists include validate and length; length/validate mutual exclusion enforced.

v1.3.0 — 2026-06-25

  • product keyword aliasproduct can now be used interchangeably with app at the top level, across parser, formatter, and syntax highlighting.
  • Numeric literals in defaults — Values like default 50 now parse correctly. The lexer now allows digits as identifier start characters.
  • Parser robustnessparseParam loop now guards against infinite loops on unexpected token types.
  • todo.butter example — New complete todo application example using product with four features, enum types, and integer defaults.
  • Validate rule format validationvalidate rules are now rejected at parse time if they don't match a valid numeric comparison (operator + number). Supported operators: >, >=, <, <=, =<, ==, =, !=.
  • Validate type restrictionvalidate rules are now rejected if the parameter type is not int or float.

v1.2.0 — 2026-06-24

  • Butter Format (butter fmt) — New subcommand to automatically format .butter files. Uses a two-pass algorithm: removes blank lines after parameter keywords, then inserts blank lines before params, actions, and between top-level feature blocks.
  • Format on save — The VS Code extension now automatically runs butter fmt on every save, no configuration needed.
  • Butter: Format current file command — Available in the VS Code command palette.

v1.1.0 — 2026-06-23

  • --check flagbutter compile --check validates syntax without writing a .json file. Returns exit code 0 on success, 1 on error.
  • On-save linting — The VS Code extension runs butter compile --check on every save, surfacing errors with red squiggly underlines.
  • Butter: Lint current file command — Available in the VS Code command palette.
  • Configurable compiler pathbutter.compilerPath setting lets you specify a custom path to the butter binary.
  • butter --version — Prints the compiler version.
  • Named capture highlighting — TextMate grammar now highlights app, feature, and param identifiers distinctly.
  • version keyword — Added to syntax highlighting.
  • Document icon — Custom SVG icon for .butter files.

v1.0.0 — 2026-06-22

  • Initial release of the Butter DSL compiler.
  • butter compile command compiles .butter files to pretty-printed JSON.
  • Lexer with Python-style indentation tracking (INDENT/DEDENT tokens).
  • Recursive descent parser supporting app, description, version, feature, params, param (with type, required, default), actions, action, and pipe-delimited conditionals (if, unless, when, while).
  • VS Code extension with syntax highlighting, indentation rules, and comment toggling.