plum

#treesitter#compiler#wasm

git clone https://git.pyrossh.dev/plum

A statically typed, imperative programming language inspired by rust, python


plum-tooling/tree-sitter-plum/queries/plum/format.scm
; ============================================================
; Top-level items — always their own line, blank line allowed
; between them (preserved from the source if it was already there)
; ============================================================

(source
  (_) @append_hardline)

(source
  (fn) @allow_blank_line_before)

(source
  (enum) @allow_blank_line_before)

(source
  (trait) @allow_blank_line_before)

(source
  (const) @allow_blank_line_before)

(source
  (import) @allow_blank_line_before)

(source
  (test) @allow_blank_line_before)

; ============================================================
; Binary operators — space before and after. Scoped to the specific
; operator-expression node types they appear in (NOT bare tokens):
; "/" is also the bare separator inside a `url` (`std/os`), which
; must stay glued with no spaces — matching it unscoped would insert
; spaces there too and make the import unparseable on a second pass.
; ============================================================

(binary_operator
  [
    "+"
    "-"
    "*"
    "/"
    "%"
    "^"
    "&"
    "|"
    "<<"
    ">>"
  ] @prepend_space @append_space)

(comparison_operator
  [
    "=="
    "!="
    "<"
    "<="
    ">"
    ">="
    "<>"
  ] @prepend_space @append_space)

(boolean_operator
  [
    "&&"
    "||"
  ] @prepend_space @append_space)

; ============================================================
; Assignment and arrows
; ============================================================

[
  "="
  ":="
] @prepend_space @append_space

"->" @prepend_space @append_space

"=>" @prepend_space @append_space

(ternary_expression
  [
    "?"
    ":"
  ] @prepend_space @append_space)

(elvis_expression "?:" @prepend_space @append_space)

; ============================================================
; Keywords — space after (Topiary inserts NO whitespace at all
; between adjacent leaves unless a query says to; every keyword
; below needs its own rule or it glues to the next token, e.g.
; "module" + "test" -> "moduletest").
; ============================================================

(module "module" @append_space)

(import "import" @append_space)

(trait "trait" @append_space)

(enum "enum" @append_space)

(fn "extern" @append_space)

(fn "fun" @append_space)

(if "if" @append_space)

(else_if "else if" @append_space)

(else "else" @append_space)

; `else_if`/`else` are siblings of `if`'s own body (not wrapped in it), so
; without this they glue right onto the body's last line: `"neg"else if ...`.
(if
  (else_if) @prepend_hardline)

(if
  (else) @prepend_hardline)

(while "while" @append_space)

(for "for" @append_space)

(for "range" @prepend_space @append_space)

(match "match" @append_space)

(test "test" @append_space)

; "assert"/"return" take an OPTIONAL trailing expression — attach the space to
; that expression instead of the keyword, so a bare `return` (no value) never
; gets a dangling trailing space before its line break.
(assert (expression) @prepend_space)

(return (expression) @prepend_space)

; ============================================================
; Comments — preserve whether the ORIGINAL source had a line break
; before it: a standalone comment on its own line stays on its own
; line; a trailing comment (`x = 5 # note`) stays trailing. Using a
; plain @prepend_hardline here would wrongly force even a trailing
; comment onto its own line — and would keep moving on every
; formatting pass, since the next pass's "original" no longer
; matches, which is exactly the idempotence failure this fixes.
; ============================================================

(comment) @prepend_input_softline

; ============================================================
; Separators — no space before, one space after
; ============================================================

"," @append_space

":" @append_space

; ============================================================
; trait/enum bodies — unlike a `fn`'s body, these have no
; wrapping `(body ...)` node of their own (the grammar puts their
; `field`/`fn` children directly under `trait`/`enum`), so
; they need their own hardline + indent rules instead of reusing
; the `(body)` rule below.
; ============================================================

(trait
  (field) @prepend_hardline)

(trait
  "=" @append_indent_start)

(trait
  (_) @append_indent_end
  .)

(enum
  (field) @prepend_hardline)

(enum
  (fn) @prepend_hardline)

; A blank line between two fields/methods (or a field and a method) inside an
; `enum` body is preserved if the source already had one — same as top-level
; items — rather than always being collapsed to a single hardline.
(enum
  (field) @allow_blank_line_before)

(enum
  (fn) @allow_blank_line_before)

(enum
  "=" @append_indent_start)

(enum
  (_) @append_indent_end
  .)

; `enum_field` (a variant like `| Some[T]`) is ALIASED to the same visible
; node kind ("field") as trait_field, but only it has a literal
; "|" — safe to target unscoped by parent.
(field
  "|" @append_space)

; ============================================================
; match — same situation as trait/enum: `case`/`guard_case`
; children sit directly under `match`, no wrapping `(body ...)`.
; ============================================================

(match
  (case) @prepend_hardline)

(match
  (guard_case) @prepend_hardline)

(guard_case
  "|" @append_space)

(match
  "match" @append_indent_start)

(match
  (_) @append_indent_end
  .)

; ============================================================
; Closures — a space between the closing "|" and an inline expression
; body (`|v| v + 1`), same as `assert`/`return`'s optional-expression
; rule above. Targeting the `body` FIELD (not a bare "|" token, which
; appears twice — once opening, once closing) picks out only the
; closing one, regardless of whether the body is a single expression
; or an indented block.
; ============================================================

(closure
  body: (_) @prepend_space)

; ============================================================
; Function body — each statement on its own line
; ============================================================

(body
  (_) @prepend_hardline)

; ============================================================
; Indentation — body blocks
; ============================================================

; Start indent before body content, end indent after the body node
(body) @prepend_indent_start @append_indent_end