// CMake language grammar for pest (PEG).
//
// Reference: https://cmake.org/cmake/help/latest/manual/cmake-language.7.html
//
// Design notes:
//   - WHITESPACE is NOT set globally; CMake whitespace is handled explicitly.
//   - Comments are named rules (not silenced) so the AST builder can see them.
//   - Bracket argument = count matching is validated in Rust after parsing,
//     since PEG cannot express "same number of =" in open and close bracket.
//   - Argument separators (spaces/newlines between args) are silent (_sep).

// ── Top-level ────────────────────────────────────────────────────────────────

bom = _{ "\u{FEFF}" }

file = { SOI ~ bom? ~ file_item* ~ EOI }

file_item = { command_invocation | template_placeholder | bracket_comment | line_comment | newline | space }

// ── Command invocation ────────────────────────────────────────────────────────

command_invocation = {
    identifier ~ space* ~ "(" ~ arguments ~ ")"
}

identifier = @{ (ASCII_ALPHA | "_") ~ (ASCII_ALPHANUMERIC | "_")* }
template_placeholder = @{ "@" ~ (ASCII_ALPHANUMERIC | "_")+ ~ "@" }

arguments = { argument_part* }

argument_part = { argument | bracket_comment | line_ending | space }

argument = { bracket_argument | quoted_argument | mixed_unquoted_argument | unquoted_argument }

argument_boundary = _{ space | newline | ")" | "#" }

// ── Line ending (may carry a comment) ────────────────────────────────────────

line_ending = { line_comment? ~ newline }

newline = @{ "\r\n" | "\n" }

space = @{ (" " | "\t")+ }

// ── Comments ──────────────────────────────────────────────────────────────────

// A bracket comment is "#" immediately followed by a bracket_argument.
// A line comment is "#" NOT followed by a bracket open.
// Order matters: try bracket_comment first.
bracket_comment = { "#" ~ bracket_argument }

line_comment = @{ "#" ~ !bracket_open ~ (!newline ~ ANY)* }

// ── Bracket argument ──────────────────────────────────────────────────────────
//
// Syntax: [=*[ content ]=*]  where the number of "=" must match.
// We parse up to 16 "=" (more than enough in practice) and validate
// the closing bracket length in Rust.
//
// bracket_open captures everything up to and including the inner "[".
// bracket_content is then anything until bracket_close.
// bracket_close must have the same number of "=" — enforced in Rust.

bracket_argument = { bracket_open ~ bracket_content ~ bracket_close }

bracket_open    = @{ "[" ~ "="* ~ "[" }
bracket_content = @{ bracket_leading_newline? ~ (!bracket_close_any ~ ANY)* }
bracket_close   = @{ "]" ~ "="* ~ "]" }

// Matches any possible closing bracket (for the negative lookahead in content).
bracket_close_any = _{ "]" ~ "="* ~ "]" }

// A single leading newline after the opening bracket is discarded (per spec).
bracket_leading_newline = _{ "\n" | "\r\n" }

mixed_unquoted_argument = { legacy_quoted ~ (unquoted_element | legacy_quoted | legacy_make_var | nested_parens)+ }

// ── Quoted argument ───────────────────────────────────────────────────────────

quoted_argument = { "\"" ~ quoted_element* ~ "\"" ~ &argument_boundary }

quoted_element = {
    quoted_continuation
  | escape_sequence
  | variable_ref
  | genex
  | quoted_text
}

// Any character except " and \ (captured verbatim).
quoted_text = @{ (!("\"" | "\\" | "$") ~ ANY)+ | "$" ~ !("{" | "ENV{" | "CACHE{" | "<") }

// Line continuation inside a quoted argument: backslash immediately before newline.
quoted_continuation = @{ "\\" ~ newline }

// ── Unquoted argument ─────────────────────────────────────────────────────────

unquoted_argument = { (unquoted_element | legacy_quoted | legacy_make_var | nested_parens)+ }

unquoted_element = {
    escape_sequence
  | variable_ref
  | genex
  | unquoted_text
}

legacy_quoted = @{ "\"" ~ (( "\\" ~ !newline ~ ANY ) | (!("\"" | newline) ~ ANY))* ~ "\"" }
legacy_make_var = @{ "$(" ~ (!(")" | newline) ~ ANY)* ~ ")" }
nested_parens = @{ "(" ~ nested_parens_inner* ~ ")" }
nested_parens_inner = { nested_parens | legacy_quoted | legacy_make_var | escape_sequence | variable_ref | genex | nested_parens_text | space | line_ending }
nested_parens_text = @{
    (!(" " | "\t" | "\r" | "\n" | "(" | ")" | "#" | "\"" | "\\" | "$") ~ ANY)+
  | "$" ~ !("{" | "ENV{" | "CACHE{" | "<" | "(")
}

// Characters allowed verbatim in an unquoted argument.
// Forbidden: whitespace  ( ) # " \
// Allow "$" only when not starting a variable ref or genex.
unquoted_text = @{
    (!(" " | "\t" | "\r" | "\n" | "(" | ")" | "#" | "\"" | "\\" | "$") ~ ANY)+
  | "$" ~ !("{" | "ENV{" | "CACHE{" | "<" | "(")
}

// ── Escape sequences ──────────────────────────────────────────────────────────

escape_sequence = @{
    escape_encoded
  | escape_semicolon
  | escape_identity
}

escape_encoded   = @{ "\\" ~ ("t" | "r" | "n") }
escape_semicolon = @{ "\\;" }
escape_identity  = @{ "\\" ~ (!ASCII_ALPHANUMERIC ~ !";" ~ ANY) }

// ── Variable references ───────────────────────────────────────────────────────

variable_ref = { normal_var | env_var | cache_var }

// ${...} — may nest
normal_var = { "${" ~ var_name ~ "}" }
env_var    = { "$ENV{" ~ var_name ~ "}" }
cache_var  = { "$CACHE{" ~ var_name ~ "}" }

// Variable name: any chars except "}", may contain nested ${...}.
var_name = @{ (!"}" ~ (normal_var_inner | ANY))* }
normal_var_inner = _{ "${" ~ var_name ~ "}" }

// ── Generator expressions ─────────────────────────────────────────────────────

// $<...>  — treated as an opaque token; content is not inspected.
// May nest arbitrarily.
genex = { "$<" ~ genex_content ~ ">" }

genex_content = @{ (genex_inner | !">" ~ ANY)* }
genex_inner   = _{ "$<" ~ genex_content ~ ">" }
