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}