cmakefmt/parser/ast.rs
1// SPDX-FileCopyrightText: Copyright 2026 Puneet Matharu
2//
3// SPDX-License-Identifier: MIT OR Apache-2.0
4
5//! AST types returned by [`crate::parser::parse`].
6//!
7//! A CMake file parses into a [`File`] containing an ordered list of
8//! top-level [`Statement`]s. Commands carry their argument list
9//! ([`Argument`]), recognised comment forms ([`Comment`]), and the
10//! source byte span. The AST preserves blank-line and comment
11//! positions so the formatter can round-trip files with stable
12//! semantics.
13
14/// A parsed CMake source file.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct File {
17 /// Top-level statements in source order.
18 pub statements: Vec<Statement>,
19}
20
21/// A top-level statement in a CMake file.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Statement {
24 /// A command invocation, e.g. `target_link_libraries(foo PUBLIC bar)`.
25 Command(CommandInvocation),
26 /// A top-level configure-file placeholder line such as `@PACKAGE_INIT@`.
27 ///
28 /// These occur in `.cmake.in` templates and must be preserved verbatim.
29 TemplatePlaceholder(String),
30 /// A standalone comment (on its own line).
31 Comment(Comment),
32 /// One or more consecutive blank lines between statements.
33 /// The value is the number of blank lines (>= 1).
34 ///
35 /// Blank lines at the start of the file and at the end of the
36 /// file are also preserved as `BlankLines` statements, so a
37 /// round-tripped AST matches the source's whitespace envelope.
38 BlankLines(usize),
39}
40
41/// A CMake command invocation.
42#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct CommandInvocation {
44 /// The command name, e.g. "target_link_libraries". Case as written in source.
45 pub name: String,
46 /// The argument list, in source order.
47 pub arguments: Vec<Argument>,
48 /// A comment that appears after the closing paren on the same line.
49 pub trailing_comment: Option<Comment>,
50 /// Half-open byte range `[start, end)` into the original source.
51 /// `start` is inclusive, `end` is exclusive.
52 pub span: (usize, usize),
53}
54
55/// A single argument (or inline comment) in an argument list.
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum Argument {
58 /// `[[...]]`, `[=[...]=]`, etc. Content is verbatim.
59 Bracket(BracketArgument),
60 /// `"..."` — includes the surrounding quotes verbatim.
61 Quoted(String),
62 /// Any other token — unquoted argument, variable reference
63 /// (`${VAR}`), environment reference (`$ENV{X}`), cache reference
64 /// (`$CACHE{X}`), generator expression (`$<...>`), legacy
65 /// unquoted arguments containing embedded `"..."` segments, or a
66 /// parenthesised group inside a condition (e.g. `(A OR B)`
67 /// inside `if(...)`).
68 Unquoted(String),
69 /// A comment that appears inline between arguments.
70 InlineComment(Comment),
71}
72
73impl Argument {
74 /// The source text of this argument. For
75 /// [`Argument::InlineComment`] the returned slice includes the
76 /// leading `#` (and, for bracket comments, the enclosing
77 /// `#[[...]]` delimiters).
78 pub fn as_str(&self) -> &str {
79 match self {
80 Argument::Bracket(b) => &b.raw,
81 Argument::Quoted(s) | Argument::Unquoted(s) => s,
82 Argument::InlineComment(c) => c.as_str(),
83 }
84 }
85
86 /// Returns `true` when the argument is an inline comment placeholder rather
87 /// than a normal CMake argument token.
88 pub fn is_comment(&self) -> bool {
89 matches!(self, Argument::InlineComment(_))
90 }
91}
92
93/// A bracket argument with its "=" nesting level.
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct BracketArgument {
96 /// Number of `=` characters between the outer brackets. 0 = `[[...]]`.
97 pub level: usize,
98 /// The raw source text, e.g. `[==[content]==]`.
99 pub raw: String,
100}
101
102/// A CMake comment.
103#[derive(Debug, Clone, PartialEq, Eq)]
104pub enum Comment {
105 /// `# text to end of line` (stored with the leading `#`).
106 Line(String),
107 /// `#[[...]]` or `#[=[...]=]` (stored as the full raw text including `#`).
108 Bracket(String),
109}
110
111impl Comment {
112 /// Return the raw source text of the comment, including the leading `#`.
113 pub fn as_str(&self) -> &str {
114 match self {
115 Comment::Line(s) | Comment::Bracket(s) => s,
116 }
117 }
118}