Skip to main content

acdc_parser/
parsed.rs

1//! Self-referential wrappers returned by the public `parse_*` entry points.
2//!
3//! `ParseResult` pins the preprocessed source, the `bumpalo::Bump` arena
4//! that backs parser-allocated strings, the `Document<'_>` AST that borrows
5//! from both, and any warnings the parser produced — all bound together so
6//! callers can hold the AST past the caller's input slice without any
7//! `into_static()` copy. Drop releases the arena, source, and warnings in
8//! one shot. `ParseInlineResult` plays the same role for the inline-only
9//! entry point used by TCK tests and the HTML converter's quotes-only
10//! fallback.
11//!
12//! Consumers reach the AST via `.document()` (or `.inlines()`); bumpalo
13//! never appears in a public signature.
14//!
15//! `OwnedSource` covers the parse-failure case (we have the text but no
16//! AST) without paying for an empty arena.
17
18use std::{cell::RefCell, rc::Rc};
19
20use bumpalo::Bump;
21
22use crate::{Document, InlineNode, Warning};
23
24/// Owner-side of the self-referential parse cell: holds the preprocessed
25/// source text and the arena that parser-allocated strings live in. The
26/// AST dependent borrows from both fields simultaneously via their shared
27/// owner lifetime.
28#[derive(Debug)]
29pub(crate) struct OwnedInput {
30    pub(crate) source: Box<str>,
31    pub(crate) arena: Bump,
32}
33
34impl OwnedInput {
35    pub(crate) fn new(source: Box<str>) -> Self {
36        // Seed the arena with one chunk sized to the input. Most arena
37        // memory ends up holding interned strings + AST nodes whose total
38        // footprint correlates with source length, so this avoids the
39        // first ~10 chunk-grow round-trips through the global allocator
40        // on documents larger than a few KB.
41        let arena = Bump::with_capacity(source.len());
42        Self { source, arena }
43    }
44}
45
46// `self_cell!`'s `dependent:` slot takes a bare identifier and expands it
47// internally as `$Dependent<'a>`. `Document` already fits, so it goes in
48// directly. `Vec<InlineNode<'a>>` doesn't — hence the `InlineAst` alias.
49type InlineAst<'a> = Vec<InlineNode<'a>>;
50
51self_cell::self_cell! {
52    struct ParsedDocumentCell {
53        owner: OwnedInput,
54        #[covariant]
55        dependent: Document,
56    }
57
58    impl {Debug}
59}
60
61self_cell::self_cell! {
62    struct ParsedInlineCell {
63        owner: OwnedInput,
64        #[covariant]
65        dependent: InlineAst,
66    }
67
68    impl {Debug}
69}
70
71/// Successful document parse output: the AST plus the buffers it borrows
72/// from, plus any non-fatal warnings the parser collected.
73///
74/// Modelled on chumsky's `ParseResult`: the presence of the output and the
75/// presence of warnings are orthogonal, so both are returned side-by-side
76/// rather than encoded into `Result`. `#[must_use]` so warnings don't get
77/// silently dropped.
78#[derive(Debug)]
79#[must_use = "ignoring a ParseResult drops any warnings the parser produced"]
80pub struct ParseResult {
81    cell: ParsedDocumentCell,
82    warnings: Vec<Warning>,
83}
84
85impl ParseResult {
86    /// Internal constructor used by the `parse_*` entry points. Takes the
87    /// owner, a shared warnings handle (the `ParserState` holds its own
88    /// clone of this `Rc`; we recover the collected warnings after the
89    /// builder returns), and a fallible dependent builder.
90    pub(crate) fn try_new<E>(
91        owner: OwnedInput,
92        warnings_handle: Rc<RefCell<Vec<Warning>>>,
93        builder: impl for<'a> FnOnce(&'a OwnedInput) -> Result<Document<'a>, E>,
94    ) -> Result<Self, E> {
95        let cell = ParsedDocumentCell::try_new(owner, builder)?;
96        Ok(Self {
97            cell,
98            warnings: recover_warnings(warnings_handle),
99        })
100    }
101
102    /// Borrow the document AST.
103    #[must_use]
104    pub fn document(&self) -> &Document<'_> {
105        self.cell.borrow_dependent()
106    }
107
108    /// Borrow the preprocessed source the AST was parsed from.
109    ///
110    /// Note: this is the text as seen by the grammar, after include
111    /// resolution and other preprocessor transforms — not the original
112    /// caller input.
113    #[must_use]
114    pub fn source(&self) -> &str {
115        &self.cell.borrow_owner().source
116    }
117
118    /// Borrow the collected warnings.
119    #[must_use]
120    pub fn warnings(&self) -> &[Warning] {
121        &self.warnings
122    }
123
124    /// Take the warnings out of this result, leaving an empty warnings
125    /// slice behind. Useful when the caller wants to route warnings
126    /// independently of the AST (e.g. attach them to an LSP diagnostic
127    /// stream while keeping the document for further borrowing). The
128    /// `ParseResult` keeps its AST intact — only `warnings()` becomes
129    /// empty.
130    pub fn take_warnings(&mut self) -> Vec<Warning> {
131        std::mem::take(&mut self.warnings)
132    }
133}
134
135/// Successful inline-only parse output: the inline-node slice plus the
136/// buffers it borrows from, plus any non-fatal warnings. Counterpart to
137/// [`ParseResult`] for the inline-only entry point.
138#[derive(Debug)]
139#[must_use = "ignoring a ParseInlineResult drops any warnings the parser produced"]
140pub struct ParseInlineResult {
141    cell: ParsedInlineCell,
142    warnings: Vec<Warning>,
143}
144
145impl ParseInlineResult {
146    /// Internal constructor. Counterpart to [`ParseResult::try_new`].
147    pub(crate) fn try_new<E>(
148        owner: OwnedInput,
149        warnings_handle: Rc<RefCell<Vec<Warning>>>,
150        builder: impl for<'a> FnOnce(&'a OwnedInput) -> Result<Vec<InlineNode<'a>>, E>,
151    ) -> Result<Self, E> {
152        let cell = ParsedInlineCell::try_new(owner, builder)?;
153        Ok(Self {
154            cell,
155            warnings: recover_warnings(warnings_handle),
156        })
157    }
158
159    /// Infallible variant for callers that don't need warnings (e.g. the
160    /// HTML converter's quotes-only fallback via `parse_text_for_quotes`).
161    /// `warnings` is always empty.
162    pub(crate) fn from_infallible(
163        owner: OwnedInput,
164        builder: impl for<'a> FnOnce(&'a OwnedInput) -> Vec<InlineNode<'a>>,
165    ) -> Self {
166        let cell = ParsedInlineCell::new(owner, builder);
167        Self {
168            cell,
169            warnings: Vec::new(),
170        }
171    }
172
173    /// Borrow the inline-node slice.
174    #[must_use]
175    pub fn inlines(&self) -> &[InlineNode<'_>] {
176        self.cell.borrow_dependent()
177    }
178
179    /// Borrow the preprocessed source the nodes were parsed from.
180    #[must_use]
181    pub fn source(&self) -> &str {
182        &self.cell.borrow_owner().source
183    }
184
185    /// Borrow the collected warnings.
186    #[must_use]
187    pub fn warnings(&self) -> &[Warning] {
188        &self.warnings
189    }
190
191    /// Take the warnings out of this result, leaving an empty slice
192    /// behind. See [`ParseResult::take_warnings`].
193    pub fn take_warnings(&mut self) -> Vec<Warning> {
194        std::mem::take(&mut self.warnings)
195    }
196}
197
198/// Unwrap the `Rc` the `ParserState` shared with the outer scope. The
199/// state is dropped before `try_new`'s builder returns, so the outer
200/// clone is normally unique and `try_unwrap` succeeds. If any other clone
201/// lingers (e.g. a future code path keeps one alive), we fall back to
202/// draining through the `RefCell` rather than losing the warnings.
203fn recover_warnings(handle: Rc<RefCell<Vec<Warning>>>) -> Vec<Warning> {
204    Rc::try_unwrap(handle).map_or_else(
205        |shared| std::mem::take(&mut *shared.borrow_mut()),
206        RefCell::into_inner,
207    )
208}
209
210/// Source text with no AST — returned by consumers (e.g. the LSP) when the
211/// input is available but parsing failed.
212#[derive(Debug, Clone)]
213pub struct OwnedSource(Box<str>);
214
215impl OwnedSource {
216    /// Wrap owned source text.
217    #[must_use]
218    pub fn new(source: impl Into<Box<str>>) -> Self {
219        Self(source.into())
220    }
221
222    /// Borrow the source text.
223    #[must_use]
224    pub fn source(&self) -> &str {
225        &self.0
226    }
227}