Tree-sitter grammar for eskip — the routing configuration language used by Skipper.
Eskip is a DSL for defining HTTP routing rules. Each rule is a route definition: a named predicate expression, an optional chain of filters, and a backend target.
// Route with predicates, filters, and a network backend
api: Method("GET") && Host(/^api\.example\.org$/) && Path("/api/:id")
-> setRequestHeader("X-Foo", "bar")
-> ratelimit(10, "1m")
-> "https://backend.example.org";
// Catch-all that shunts (terminates) the request
catchall: * -> inlineContent("not found") -> status(404) -> <shunt>;
// Load-balanced backend with explicit algorithm
lb: Path("/lb")
-> <roundRobin, "https://a.example.org", "https://b.example.org">;
| Construct | Example |
|---|---|
| Route definition | name: predicates -> backend ; |
| Predicate | Path("/foo"), Method("GET") |
| Multiple predicates | Method("GET") && Path("/foo") |
| Wildcard predicate | * |
| Filter chain | -> setRequestHeader("X-A", "b") -> ratelimit(10, "1m") |
| String backend | -> "https://example.org" |
| Shunt backend | -> <shunt> |
| Loopback backend | -> <loopback> |
| Dynamic backend | -> <dynamic> |
| Load-balanced backend | -> <roundRobin, "https://a.org", "https://b.org"> |
| String arguments | "double-quoted", `backtick raw` |
| Regexp arguments | /^pattern$/ |
| Number arguments | 404, 3.14, -1 |
| Comments | // line comment |
Load-balancing algorithms: roundRobin, random, consistentHash, powerOfRandomNChoices
The queries/highlights.scm file maps grammar nodes to standard Tree-sitter capture names:
| Capture | Node |
|---|---|
@function |
route identifier |
@function.method |
predicate / filter name |
@keyword |
lb algorithm |
@constant.builtin |
shunt, loopback, dynamic |
@operator |
->, &&, * |
@string / @string.regexp / @string.escape |
string / regexp / escape |
@number |
number |
@comment |
line comment |
@punctuation.delimiter |
:, ;, , |
@punctuation.bracket |
(, ), <, > |
npm install tree-sitter-eskipimport Parser from "tree-sitter";
import Eskip from "tree-sitter-eskip";
const parser = new Parser();
parser.setLanguage(Eskip);
const tree = parser.parse(`hello: Path("/hello") -> "https://example.org";`);[dependencies]
tree-sitter-eskip = "0.1"let language = tree_sitter_eskip::LANGUAGE;pip install tree-sitter-eskipimport tree_sitter_eskip
from tree_sitter import Language, Parser
lang = Language(tree_sitter_eskip.language())
parser = Parser(lang)go get github.com/carloluis/tree-sitter-eskipimport (
"github.com/tree-sitter/go-tree-sitter"
tree_sitter_eskip "github.com/carloluis/tree-sitter-eskip/bindings/go"
)
language := tree_sitter.NewLanguage(tree_sitter_eskip.Language())#include "tree_sitter/parser.h"
const TSLanguage *tree_sitter_eskip(void);Build with make or cmake.
This repo ships as a Neovim plugin. Install it with your plugin manager, then call setup() to register the parser and enable highlighting.
vim.pack (built-in, no plugin manager required)
-- in your init.lua:
vim.pack.add({ "https://github.com/carloluis/tree-sitter-eskip" })
require("tree-sitter-eskip").setup()vim.pack.add downloads and installs the plugin on first run, then calls :packadd so it is available immediately in the same session.
lazy.nvim
{
"carloluis/tree-sitter-eskip",
ft = "eskip",
config = function()
require("tree-sitter-eskip").setup()
-- then install the parser the first time:
-- :TSInstall eskip
end,
}lazy.nvim / vim-plug / packer
-- in your init.lua, after the plugin is on the runtimepath:
require("tree-sitter-eskip").setup()setup() does three things:
- Registers the parser source with nvim-treesitter so
:TSInstall eskipworks. - Calls
vim.treesitter.language.registerto bind theeskiplanguage to theeskipfiletype. - Creates a
FileType eskipautocommand that starts tree-sitter highlighting for every eskip buffer.
Filetype detection for *.eskip files is handled automatically via ftdetect/eskip.lua, which is sourced by Neovim's runtimepath on startup — no extra configuration needed.
After installing the plugin, run :TSInstall eskip once to compile the parser. Verify with:
:checkhealth nvim-treesitter
:InspectTree " parse tree for the current buffer
:Inspect " highlight captures under cursor# Regenerate parser from grammar.js
tree-sitter generate
# Run corpus tests
tree-sitter test
# Parse example files
tree-sitter parse examples/*.eskip --quiet --stat
# Launch the playground (builds WASM first)
npm startMIT
