| Crates.io | plotnik-cli |
| lib.rs | plotnik-cli |
| version | 0.1.0 |
| created_at | 2025-12-14 21:59:26.064149+00 |
| updated_at | 2025-12-14 21:59:26.064149+00 |
| description | CLI for plotnik - typed query language for tree-sitter AST |
| homepage | |
| repository | https://github.com/plotnik-lang/plotnik |
| max_upload_size | |
| id | 1985175 |
| size | 98,819 |
Plotnik
Plotnik is a query language for source code.
Queries extract relevant structured data.
Transactions allow granular, atomic edits.
⚠️ EARLY STAGE: THERE IS NOTHING TO INSTALL OR RUN YET ⚠️
Tree-sitter solved parsing. It powers syntax highlighting and code navigation at GitHub, drives the editing experience in Zed, Helix, and Neovim. It gives you a fast, accurate, incremental syntax tree for virtually any language.
The hard problem now is what comes after parsing: extraction of meaning from the tree, and safe transformation back to source:
function extractFunction(node: SyntaxNode): FunctionInfo | null {
if (node.type !== "function_declaration") {
return null;
}
const name = node.childForFieldName("name");
const body = node.childForFieldName("body");
if (!name || !body) {
return null;
}
return {
name: name.text,
body,
};
}
Every extraction requires a new function, each one a potential source of bugs that won't surface until production. And once you've extracted what you need, applying changes back to the source requires careful span tracking, validation, and error handling—another layer of brittle code.
Plotnik extends Tree-sitter queries with type annotations:
(function_declaration
name: (identifier) @name :: string
body: (statement_block) @body
) @func :: FunctionInfo
The query describes structure, and Plotnik infers the output type:
interface FunctionInfo {
name: string;
body: SyntaxNode;
}
This structure is guaranteed by the query engine. No defensive programming needed.
Once extraction is complete, Plotnik will support transactions to apply validated changes back to the source. The same typed nodes used for extraction become targets for transformation—completing the loop from source to structured data and back to source.
Tree-sitter already has queries:
(function_declaration
name: (identifier) @name
body: (statement_block) @body)
The result is a flat capture list:
query.matches(tree.rootNode);
// → [{ captures: [{ name: "name", node }, { name: "body", node }] }, ...]
The assembly layer is up to you:
const name = match.captures.find((c) => c.name === "name")?.node;
const body = match.captures.find((c) => c.name === "body")?.node;
if (!name || !body) throw new Error("Missing capture");
return { name: name.text, body };
This means string-based lookup, null checks, and manual type definitions kept in sync by convention.
Tree-sitter queries are designed for matching. Plotnik adds the typing layer: the query is the type definition.
| Hand-written extraction | Plotnik |
|---|---|
| Manual navigation | Declarative pattern matching |
| Runtime type errors | Compile-time type inference |
| Repetitive extraction code | Single-query extraction |
| Ad-hoc data structures | Generated structs/interfaces |
Plotnik extends Tree-sitter's query syntax with:
Plotnik builds on Tree-sitter's query syntax, extending it with the features needed for typed extraction:
Statement = [
Assign: (assignment_expression
left: (identifier) @target :: string
right: (Expression) @value)
Call: (call_expression
function: (identifier) @func :: string
arguments: (arguments (Expression)* @args))
]
Expression = [
Ident: (identifier) @name :: string
Num: (number) @value :: string
]
TopDefinitions = (program (Statement)+ @statements)
This produces:
type Statement =
| { $tag: "Assign"; $data: { target: string; value: Expression } }
| { $tag: "Call"; $data: { func: string; args: Expression[] } };
type Expression =
| { $tag: "Ident"; $data: { name: string } }
| { $tag: "Num"; $data: { value: string } };
type TopDefinitions = {
statements: [Statement, ...Statement[]];
};
Then process the results:
for (const stmt of result.statements) {
switch (stmt.$tag) {
case "Assign":
console.log(`Assignment to ${stmt.$data.target}`);
break;
case "Call":
console.log(
`Call to ${stmt.$data.func} with ${stmt.$data.args.length} args`,
);
break;
}
}
For the detailed specification, see the Language Reference.
Plotnik is bundled with 26 languages:
Bash, C, C++, C#, CSS, Elixir, Go, Haskell, HCL, HTML, Java, JavaScript, JSON, Kotlin, Lua, Nix, PHP, Python, Ruby, Rust, Scala, Solidity, Swift, TypeScript, TSX, YAML
Additional languages and dynamic loading are planned.
The foundation is complete: a resilient parser that recovers from errors and keeps going.
logos) and parser (rowan) with error recoveryThe schema infrastructure is built. Type inference is next.
node-types.json parsing and schema representation (plotnik-core)plotnik-macros)plotnik-langs)grammar.json (production rules, precedence)The CLI foundation exists. The full developer experience is ahead.
debug, docs, langs commandsMax Brunsfeld created Tree-sitter; Amaan Qureshi and other contributors maintain the parser ecosystem that makes this project possible.
This project is licensed under the Apache License (Version 2.0).