Skip to content

plotnik-lang/plotnik

Repository files navigation


The logo: a curled wood shaving on a workbench

Plotnik


A type-safe query language for Tree-sitter.
Powered by the arborium grammar collection.


stable nightly Apache-2.0 License


⚠️ BETA: NOT FOR PRODUCTION USE ⚠️


Tree-sitter gives you the syntax tree. Extracting structured data from it still means writing imperative navigation code, null checks, and maintaining type definitions by hand. Plotnik makes extraction declarative: write a pattern, get typed data. The query is the type definition.

Features

  • Static type inference from query structure
  • Named expressions for composition and reuse
  • Recursion for nested structures
  • Tagged unions (discriminated unions)
  • TypeScript type generation
  • CLI: exec for matches, infer for types, ast/trace/dump for debug
  • Full validation against grammar (reject queries that can never match)
  • Compile-time queries via proc-macro
  • WASM
  • LSP, editor extensions

Installation

cargo install plotnik

By default, 15 common languages are included. To add specific languages:

cargo install plotnik --features lang-ruby,lang-elixir

Or with all 80+ languages:

cargo install plotnik --features all-languages

Example

Extract function signatures from Rust. Type references itself to handle nested generics like Option<Vec<String>>.

query.ptk:

Type = [
  Simple: [(type_identifier) (primitive_type)] @name :: string
  Generic: (generic_type
    type: (type_identifier) @name :: string
    type_arguments: (type_arguments (Type)* @args))
]

Func = (function_item
  name: (identifier) @name :: string
  parameters: (parameters
    (parameter
      pattern: (identifier) @param :: string
      type: (Type) @type
    )* @params))

Funcs = (source_file (Func)* @funcs)

lib.rs:

fn get(key: Option<Vec<String>>) {}

fn set(key: String, val: i32) {}

Plotnik infers TypeScript types from the query structure. Type is recursive: args: Type[].

❯ plotnik infer query.ptk --lang rust
export type Type =
  | { $tag: "Simple"; $data: { name: string } }
  | { $tag: "Generic"; $data: { name: string; args: Type[] } };

export interface Func {
  name: string;
  params: { param: string; type: Type }[];
}

export interface Funcs {
  funcs: Func[];
}

Run the query against lib.rs to extract structured JSON:

❯ plotnik exec query.ptk lib.rs
{
  "funcs": [
    {
      "name": "get",
      "params": [{
        "param": "key",
        "type": {
          "$tag": "Generic",
          "$data": {
            "name": "Option",
            "args": [{
              "$tag": "Generic",
              "$data": {
                "name": "Vec",
                "args": [{ "$tag": "Simple", "$data": { "name": "String" } }]
              }
            }]
          }
        }
      }]
    },
    {
      "name": "set",
      "params": [
        { "param": "key", "type": { "$tag": "Simple", "$data": { "name": "String" } } },
        { "param": "val", "type": { "$tag": "Simple", "$data": { "name": "i32" } } }
      ]
    }
  ]
}

Why

Pattern matching over syntax trees is powerful, but tree-sitter queries produce flat capture lists. You still need to assemble the results, handle missing captures, and define types by hand. Plotnik closes this gap: the query describes structure, the engine guarantees it.

Documentation

Acknowledgments

Max Brunsfeld created Tree-sitter; Amaan Qureshi and other contributors maintain the parser ecosystem that makes this project possible.

License

This project is licensed under the Apache License (Version 2.0).

About

Typed queries for Tree-sitter

Resources

License

Contributing

Stars

Watchers

Forks

Contributors 2

  •  
  •