An alternative Tree-sitter grammar for Caddyfile

First of all, I’m a big fan of Caddy and all the work the team has been doing. I’ve been using Caddy on my home server for about 6 years and it’s by far the most reliable service on there.

I’ve been working on an alternative to the official Caddyfile grammar for Tree-sitter.

Tree-sitter provides a common parsing framework for efficient incremental re-parsing, used by a broad ecosystem of editor integrations and tooling.

The goal of my grammar is to produce more reliable and granular hierarchical structural information for downstream tooling, and shift its validation burden from verifying or reconstructing relational information to validating individual tokens within a stable context.

To do this, my grammar heavily relies on a custom lexer. The reason for taking this approach is that Caddyfile’s highly expressive nature makes the conventional pattern-based approach difficult to apply broadly without introducing ambiguity into token boundaries and losing or distorting hierarchical information. This isn’t really a big issue for syntax highlighting, but other kinds of tools may need to validate or reconstruct those relationships themselves with additional heuristics or by re-parsing the source. (it’s also partly because I hate writing regex…).

The challenge has been finding the right balance between semantic classification while remaining generic and permissive enough to accommodate the language ambiguity, plugins, and many edge cases.

My grammar repo is published on Github and I’m keen to get some feedback. It has generated bindings for many languages including Go, C, Rust, JavaScript, Zig, and others.

All code is released under the MIT license and I’m happy to receive contributions, or to contribute the code to the Caddy project or others if it’s considered useful.

These syntax highlighting screenshots showcase some of the capture groups:

Words of caution: One trade-off is that the custom scanner is fairly hefty. It is written in C11 and operates outside Tree-sitter’s usual safety checks, so bugs like infinite loops or invalid memory accesses could propagate into downstream tooling. The parser currently passes fuzzing and static analyser tests, but I’d recommend caution when integrating it while the project is still maturing.

LLM disclosure: I have pasted portions of my code into LLM chats to help identify issues when stuck on a problem (often fruitlessly). All code was written by me. I do not use LLM agents or autonomous agentic tooling and no code in this project was generated by an LLM.

1 Like