Skip to content

Writing a Language

Table of Contents


This guide covers how to create and generate a grammar directory for a custom language.


Directory Structure

Inside the Galley repository, each bundled language lives under languages/<name>/, where the repository build discovers generated _ll-parser.zig and _lr-parser.zig files. External consumers may place the same grammar and customization files anywhere and pass that directory to addParserModule in their own build.zig.

languages/
└── mylang/
    ├── ll.grm          # LL grammar rules (for top-down parsing)
    ├── lr.grm          # LR grammar rules (for bottom-up parsing)
    ├── config.zig      # Options, configuration, and indentation syntax
    ├── _ll-parser.zig  # Auto-generated by the grammar generator
    ├── _lr-parser.zig  # Auto-generated by the grammar generator
    ├── ll_error_messages.zig # Optional LL message hooks (via --fill-error-messages)
    ├── lr_error_messages.zig # Optional LR message hooks (via --fill-error-messages)
    ├── procedures.zig  # Required: hook functions and runtime payload definition
    └── samples/        # Optional repository benchmark inputs
        └── code-01     # Example benchmark input

Getting Started

Run all commands from the root directory of the repository. A Galley language is a directory containing ll.grm, lr.grm, or both. When you run galley, it generates parser files and creates missing customization files without overwriting existing customization. It does not create or modify application build files.

sh
zig build
./zig-out/bin/galley path/to/language-dir

To add a bundled repository language and its API benchmark harness, use the languages/<name>/ convention:

  1. Create a new directory under languages/:

    sh
    mkdir -p languages/mylang
  2. Define your grammar in languages/mylang/ll.grm (or lr.grm). See Grammar Guidelines for syntax and rules.

  3. Optionally write your languages/mylang/config.zig and languages/mylang/procedures.zig files. If they are missing, galley creates them with documented defaults:

    zig
    pub const ast = true;
    pub const procedures = true;
    // ... one constant per generation-time option, plus:
    pub const error_messages = .{};
    zig
    pub const Payload = struct {};
  4. Optionally create samples/ and add code-* inputs if the language should participate in repository benchmarks. Normal parser generation and module compilation do not require samples.

  5. Generate the parser:

    sh
    zig build
    ./zig-out/bin/galley --parser-type ll languages/mylang

    Parser files start with _ because Galley overwrites them. Support files such as procedures.zig, config.zig, and ll_error_messages.zig do not start with _ because they are user-owned and preserved.


The Grammar File

The grammar file format is documented in detail in Grammar Guidelines. Key points:

  • Entry point: The first rule defined in the file becomes the parser's start symbol.
  • Parser type: Choose --parser-type ll for top-down parsing or --parser-type lr for bottom-up parsing.
  • AST allocation: Variables starting with a capital letter allocate AST nodes; variables starting with _ (CamelCase, e.g. _WhiteSpace) are skipped entirely.
  • Terminals: Exact strings can contain raw UTF-8 (e.g. "سلام") or \u{...} scalar escapes (e.g. "\u{1f600}"). Unquoted lowercase identifiers match named character classes or generative terminals like digit, letter, space, and new_line. UTF-8 byte-class terminals can be composed when a language rule must validate arbitrary Unicode input. Generative terminals can optionally receive exception suffix chains like character^"\n" or digit^"1"^"3"; see Grammar Guidelines for the complete syntax and terminal list.

Example: Simple Arithmetic

languages/mylang/ll.grm:

Definition
| "let" _WhiteSpace Expr _WhiteSpace

Expr
| Number
| "+" Number
| "-" Number

Number
| "4"
| "2"

_WhiteSpace
| space _WhiteSpace
| new_line _WhiteSpace
|

languages/mylang/samples/code-01:

let +4

Hooks in procedures.zig

During parsing, you can execute custom Zig logic (known as reduction procedures or hooks) when grammar rules are matched and reduced. These hooks are exported from your language's procedures.zig file.

To learn how to register explicit/implicit hooks, how to write custom procedures in Zig, and how they map to AST generation options, see the dedicated Reduction Procedures User Guide.

Syntax Error Messages

Generated parsers record structured syntax diagnostics and print Galley's default message unless a matching public hook exists in ll_error_messages.zig or lr_error_messages.zig.

Generate default hook bodies for every current syntax-error site with:

sh
./zig-out/bin/galley --parser-type ll --fill-error-messages languages/mylang

The fill mode appends missing pub fn syntax_error_* hooks, preserves existing hooks, and reports obsolete public hooks that no longer match the generated parser. Non-public helper functions are ignored.

LL hook names are semantic: syntax_error_ll_<parser-symbol>__expected_<semantic-alternatives>. The LL parser resolves hooks at comptime from most specific to broadest: exact semantic hook, syntax_error_ll_<parser-symbol>, syntax_error_ll, syntax_error, then the default renderer.

LR hook names identify exact generated sites, for example syntax_error_lr_state_12_action_19. LR resolution checks that exact hook first, followed by the language-wide syntax_error_lr, the cross-parser syntax_error, and finally the default renderer.

zig
const root = @import("galley");

pub fn syntax_error_ll_Value__expected_String_or_Number(args: root.SyntaxErrorMessageArgs) ![]const u8 {
    return try root.renderParseDiagnostic(args.allocator, args.diagnostic, args.style);
}

Generate and Integrate

Generate LL Parser

sh
zig build
./zig-out/bin/galley --parser-type ll languages/mylang

Generate LR Parser

sh
zig build
./zig-out/bin/galley --parser-type lr languages/mylang

Generation creates _ll-parser.zig or _lr-parser.zig and any missing customization files. It does not create an application. Call addParserModule from Galley's build.zig to assemble the generated source with the runtime — see Using Galley from Another Zig Project. The runnable native consumer of that API is examples/zig. --bootstrap-zig-project writes a stub runner for a new grammar.


Generate from Zig Code

Projects that depend on Galley can call the generator directly. The full galley_generator API, including emitParserFromSource and parseGrammar, is documented in Using Galley from Another Zig Project.


Parsing Verbose Output

Set .verbosity = 1 or 2 in ParseOptions when constructing a session or using a one-shot parse helper. Applications decide how to present ASTs and diagnostics.


Conventions and Tips

  • Keep the intended start rule first. Remaining rules can be organized in any order.
  • Use _-prefixed CamelCase helper rules (like _WhiteSpace or _Tail) for whitespace, trailing symbols, and optional parts when those rules should not allocate AST nodes.
  • If your language needs to handle both LL and LR, write both ll.grm and lr.grm with equivalent grammars in each.
  • Remember that the first rule in the file is the entry point — no explicit @start annotation is needed.
  • Check that all your terminal symbols match what the grammar generator expects (digit, letter, space, new_line, operator, etc.).