driver_lang/pipeline.rs
1//! Typed composition of stages into an end-to-end driver.
2
3use core::marker::PhantomData;
4
5use crate::error::DriverError;
6use crate::session::Session;
7use crate::stage::Stage;
8
9/// Two stages run back to back — the output of [`Then`] is the composition of its
10/// parts.
11///
12/// `Then<A, B>` is itself a [`Stage`]: it feeds an input through `A`, then feeds
13/// `A`'s output through `B`, and its own `Input`/`Output` are `A::Input` and
14/// `B::Output`. It is the node [`Pipeline::then`] builds, and you rarely name it
15/// directly — a three-stage pipeline has type `Pipeline<Then<Then<A, B>, C>>`,
16/// assembled for you by chaining `.then(...)`. It is `pub` only because it appears
17/// in those inferred types.
18///
19/// Composition is monomorphized: dispatch to each stage is a direct, inlinable
20/// call with no boxing and no virtual dispatch, so a pipeline of any length is as
21/// cheap as calling the stages by hand.
22#[derive(Debug, Clone)]
23pub struct Then<A, B> {
24 first: A,
25 second: B,
26}
27
28impl<C, A, B> Stage<C> for Then<A, B>
29where
30 A: Stage<C>,
31 B: Stage<C, Input = A::Output>,
32{
33 type Input = A::Input;
34 type Output = B::Output;
35
36 /// The name of the final stage in the composition — the stage whose output the
37 /// pipeline ultimately produces.
38 fn name(&self) -> &'static str {
39 self.second.name()
40 }
41
42 fn run(
43 &mut self,
44 input: Self::Input,
45 session: &mut Session<C>,
46 ) -> Result<Self::Output, DriverError> {
47 let mid = self
48 .first
49 .run(input, session)
50 .map_err(|e| e.in_stage(self.first.name()))?;
51 self.second
52 .run(mid, session)
53 .map_err(|e| e.in_stage(self.second.name()))
54 }
55}
56
57/// An end-to-end driver: a chain of [`Stage`]s whose types line up, run against a
58/// [`Session`].
59///
60/// A `Pipeline` is how stages become a compiler. You start from one stage with
61/// [`Pipeline::new`] and extend it with [`then`](Pipeline::then), which appends a
62/// stage whose [`Input`](Stage::Input) matches the current
63/// [`Output`](Stage::Output). That match is checked *at compile time*: a pipeline
64/// that tries to feed tokens into a stage expecting an AST does not build. The
65/// finished pipeline is driven with [`run`](Pipeline::run), which threads an input
66/// through every stage in order and returns the final artifact.
67///
68/// On the first stage that returns a [`DriverError`], the pipeline stops — later
69/// stages do not run — and the error names the stage that failed. Diagnostics a
70/// stage emitted before failing remain in the session.
71///
72/// The whole pipeline is a single monomorphized type, so composing stages adds no
73/// runtime indirection over calling them directly.
74///
75/// # Examples
76///
77/// A two-stage calculator driver — tokenize, then sum:
78///
79/// ```
80/// use driver_lang::{DriverError, Pipeline, Session, Stage};
81///
82/// struct Lex;
83/// impl Stage<()> for Lex {
84/// type Input = &'static str;
85/// type Output = Vec<i64>;
86/// fn name(&self) -> &'static str { "lex" }
87/// fn run(&mut self, input: &'static str, _s: &mut Session<()>)
88/// -> Result<Vec<i64>, DriverError>
89/// {
90/// input.split_whitespace()
91/// .map(|w| w.parse::<i64>().map_err(|_| DriverError::new("not an integer")))
92/// .collect()
93/// }
94/// }
95///
96/// struct Sum;
97/// impl Stage<()> for Sum {
98/// type Input = Vec<i64>;
99/// type Output = i64;
100/// fn name(&self) -> &'static str { "sum" }
101/// fn run(&mut self, input: Vec<i64>, _s: &mut Session<()>)
102/// -> Result<i64, DriverError>
103/// {
104/// Ok(input.iter().sum())
105/// }
106/// }
107///
108/// let mut driver = Pipeline::new(Lex).then(Sum);
109/// let mut session = Session::new(());
110///
111/// assert_eq!(driver.run("1 2 3 4", &mut session).unwrap(), 10);
112/// ```
113///
114/// The configuration type `C` is a phantom parameter: a pipeline threads a
115/// `&mut Session<C>` through its stages but stores no `C` of its own. It is
116/// inferred from the stages, so you write `Pipeline::new(stage)` and never name
117/// `C` explicitly.
118pub struct Pipeline<C, S> {
119 stage: S,
120 _config: PhantomData<fn() -> C>,
121}
122
123impl<C, S> Pipeline<C, S> {
124 /// Start a pipeline from a single stage.
125 ///
126 /// # Examples
127 ///
128 /// ```
129 /// use driver_lang::{DriverError, Pipeline, Session, Stage};
130 ///
131 /// struct Identity;
132 /// impl Stage<()> for Identity {
133 /// type Input = i64;
134 /// type Output = i64;
135 /// fn name(&self) -> &'static str { "identity" }
136 /// fn run(&mut self, input: i64, _s: &mut Session<()>) -> Result<i64, DriverError> {
137 /// Ok(input)
138 /// }
139 /// }
140 ///
141 /// let driver = Pipeline::new(Identity);
142 /// assert_eq!(driver.name(), "identity");
143 /// ```
144 #[must_use]
145 pub fn new(stage: S) -> Self {
146 Self {
147 stage,
148 _config: PhantomData,
149 }
150 }
151}
152
153impl<C, S: Stage<C>> Pipeline<C, S> {
154 /// Append a stage whose [`Input`](Stage::Input) is this pipeline's current
155 /// [`Output`](Stage::Output).
156 ///
157 /// The type bound `N: Stage<C, Input = S::Output>` is the compile-time check
158 /// that the phases connect: the appended stage must accept exactly what the
159 /// pipeline currently produces. The result is a longer pipeline that produces
160 /// `N`'s output.
161 ///
162 /// # Examples
163 ///
164 /// ```
165 /// use driver_lang::{DriverError, Pipeline, Session, Stage};
166 ///
167 /// struct Parse;
168 /// impl Stage<()> for Parse {
169 /// type Input = &'static str;
170 /// type Output = usize;
171 /// fn name(&self) -> &'static str { "parse" }
172 /// fn run(&mut self, input: &'static str, _s: &mut Session<()>)
173 /// -> Result<usize, DriverError>
174 /// { Ok(input.len()) }
175 /// }
176 ///
177 /// struct Halve;
178 /// impl Stage<()> for Halve {
179 /// type Input = usize;
180 /// type Output = usize;
181 /// fn name(&self) -> &'static str { "halve" }
182 /// fn run(&mut self, input: usize, _s: &mut Session<()>)
183 /// -> Result<usize, DriverError>
184 /// { Ok(input / 2) }
185 /// }
186 ///
187 /// let mut driver = Pipeline::new(Parse).then(Halve);
188 /// let mut session = Session::new(());
189 /// assert_eq!(driver.run("abcdef", &mut session).unwrap(), 3);
190 /// ```
191 #[must_use]
192 pub fn then<N>(self, next: N) -> Pipeline<C, Then<S, N>>
193 where
194 N: Stage<C, Input = S::Output>,
195 {
196 Pipeline {
197 stage: Then {
198 first: self.stage,
199 second: next,
200 },
201 _config: PhantomData,
202 }
203 }
204
205 /// Run the pipeline: thread `input` through every stage in order and return the
206 /// final output.
207 ///
208 /// Stages run left to right, each receiving the previous stage's output and the
209 /// shared [`Session`]. The run stops at the first stage that returns a
210 /// [`DriverError`]; that error names the failing stage.
211 ///
212 /// # Errors
213 ///
214 /// Returns the [`DriverError`] of the first stage that fails, with the stage's
215 /// name stamped in.
216 ///
217 /// # Examples
218 ///
219 /// ```
220 /// use driver_lang::{DriverError, Pipeline, Session, Stage};
221 ///
222 /// struct Fail;
223 /// impl Stage<()> for Fail {
224 /// type Input = ();
225 /// type Output = ();
226 /// fn name(&self) -> &'static str { "fail" }
227 /// fn run(&mut self, _input: (), _s: &mut Session<()>) -> Result<(), DriverError> {
228 /// Err(DriverError::new("nope"))
229 /// }
230 /// }
231 ///
232 /// let mut driver = Pipeline::new(Fail);
233 /// let mut session = Session::new(());
234 /// let err = driver.run((), &mut session).unwrap_err();
235 /// assert_eq!(err.stage(), "fail");
236 /// assert_eq!(err.message(), "nope");
237 /// ```
238 pub fn run(
239 &mut self,
240 input: S::Input,
241 session: &mut Session<C>,
242 ) -> Result<S::Output, DriverError> {
243 let name = self.stage.name();
244 self.stage.run(input, session).map_err(|e| e.in_stage(name))
245 }
246
247 /// The name of the final stage — the stage whose output this pipeline produces.
248 #[must_use]
249 #[inline]
250 pub fn name(&self) -> &'static str {
251 self.stage.name()
252 }
253}
254
255#[cfg(test)]
256#[allow(clippy::unwrap_used, clippy::expect_used)]
257mod tests {
258 use super::*;
259 use alloc::vec;
260 use alloc::vec::Vec;
261
262 struct Lex;
263 impl Stage<()> for Lex {
264 type Input = &'static str;
265 type Output = Vec<i64>;
266 fn name(&self) -> &'static str {
267 "lex"
268 }
269 fn run(
270 &mut self,
271 input: &'static str,
272 _s: &mut Session<()>,
273 ) -> Result<Vec<i64>, DriverError> {
274 input
275 .split_whitespace()
276 .map(|w| {
277 w.parse::<i64>()
278 .map_err(|_| DriverError::new("not an integer"))
279 })
280 .collect()
281 }
282 }
283
284 struct Sum;
285 impl Stage<()> for Sum {
286 type Input = Vec<i64>;
287 type Output = i64;
288 fn name(&self) -> &'static str {
289 "sum"
290 }
291 fn run(&mut self, input: Vec<i64>, _s: &mut Session<()>) -> Result<i64, DriverError> {
292 Ok(input.iter().sum())
293 }
294 }
295
296 struct Negate;
297 impl Stage<()> for Negate {
298 type Input = i64;
299 type Output = i64;
300 fn name(&self) -> &'static str {
301 "negate"
302 }
303 fn run(&mut self, input: i64, _s: &mut Session<()>) -> Result<i64, DriverError> {
304 Ok(-input)
305 }
306 }
307
308 /// A stage that emits an error then aborts, to prove a mid-pipeline abort is
309 /// attributed to the stage that made it.
310 struct EmitThenAbort;
311 impl Stage<()> for EmitThenAbort {
312 type Input = i64;
313 type Output = i64;
314 fn name(&self) -> &'static str {
315 "emit-then-abort"
316 }
317 fn run(&mut self, _input: i64, session: &mut Session<()>) -> Result<i64, DriverError> {
318 session.error("boom");
319 session.abort_if_errors()?;
320 Ok(0)
321 }
322 }
323
324 #[test]
325 fn test_single_stage_pipeline_runs() {
326 let mut driver = Pipeline::new(Sum);
327 let mut s = Session::new(());
328 assert_eq!(driver.run(vec![1, 2, 3], &mut s).unwrap(), 6);
329 }
330
331 #[test]
332 fn test_two_stage_pipeline_threads_output() {
333 let mut driver = Pipeline::new(Lex).then(Sum);
334 let mut s = Session::new(());
335 assert_eq!(driver.run("1 2 3 4", &mut s).unwrap(), 10);
336 }
337
338 #[test]
339 fn test_three_stage_pipeline_threads_output() {
340 let mut driver = Pipeline::new(Lex).then(Sum).then(Negate);
341 let mut s = Session::new(());
342 assert_eq!(driver.run("1 2 3", &mut s).unwrap(), -6);
343 }
344
345 #[test]
346 fn test_pipeline_name_is_final_stage() {
347 let driver = Pipeline::new(Lex).then(Sum).then(Negate);
348 assert_eq!(driver.name(), "negate");
349 }
350
351 #[test]
352 fn test_pipeline_stops_at_first_failing_stage() {
353 let mut driver = Pipeline::new(Lex).then(Sum);
354 let mut s = Session::new(());
355 let err = driver.run("1 nope 3", &mut s).unwrap_err();
356 assert_eq!(err.stage(), "lex");
357 assert_eq!(err.message(), "not an integer");
358 }
359
360 #[test]
361 fn test_failing_stage_in_the_middle_is_attributed() {
362 // Lex succeeds, Sum succeeds, EmitThenAbort aborts — the error names the
363 // stage that actually failed, not an outer wrapper.
364 let mut driver = Pipeline::new(Lex).then(Sum).then(EmitThenAbort);
365 let mut s = Session::new(());
366 let err = driver.run("1 2 3", &mut s).unwrap_err();
367 assert_eq!(err.stage(), "emit-then-abort");
368 assert_eq!(err.message(), "aborting due to 1 previous error");
369 // The diagnostic the failing stage emitted survives in the session.
370 assert_eq!(s.diagnostics().len(), 1);
371 assert_eq!(s.diagnostics()[0].message(), "boom");
372 }
373
374 /// A stage generic over a shared string config, to prove every stage in a
375 /// pipeline reads and writes the same `Session<C>`.
376 struct TagFromConfig;
377 impl Stage<&'static str> for TagFromConfig {
378 type Input = i64;
379 type Output = i64;
380 fn name(&self) -> &'static str {
381 "tag-from-config"
382 }
383 fn run(
384 &mut self,
385 input: i64,
386 session: &mut Session<&'static str>,
387 ) -> Result<i64, DriverError> {
388 session.note(*session.config());
389 Ok(input)
390 }
391 }
392
393 #[test]
394 fn test_stages_share_the_session_config() {
395 let mut driver = Pipeline::new(TagFromConfig).then(TagFromConfig);
396 let mut s = Session::new("shared");
397 let out = driver.run(7, &mut s).unwrap();
398 assert_eq!(out, 7);
399 // Both stages saw the same config and emitted from it.
400 let notes: Vec<_> = s.diagnostics().iter().map(|d| d.message()).collect();
401 assert_eq!(notes, ["shared", "shared"]);
402 }
403
404 #[test]
405 fn test_diagnostics_from_earlier_stages_survive_a_later_failure() {
406 struct WarnThenPass;
407 impl Stage<()> for WarnThenPass {
408 type Input = &'static str;
409 type Output = &'static str;
410 fn name(&self) -> &'static str {
411 "warn-then-pass"
412 }
413 fn run(
414 &mut self,
415 input: &'static str,
416 session: &mut Session<()>,
417 ) -> Result<&'static str, DriverError> {
418 session.warn("heads up");
419 Ok(input)
420 }
421 }
422 struct AlwaysFail;
423 impl Stage<()> for AlwaysFail {
424 type Input = &'static str;
425 type Output = ();
426 fn name(&self) -> &'static str {
427 "always-fail"
428 }
429 fn run(
430 &mut self,
431 _input: &'static str,
432 _s: &mut Session<()>,
433 ) -> Result<(), DriverError> {
434 Err(DriverError::new("stop"))
435 }
436 }
437
438 let mut driver = Pipeline::new(WarnThenPass).then(AlwaysFail);
439 let mut s = Session::new(());
440 let err = driver.run("x", &mut s).unwrap_err();
441 assert_eq!(err.stage(), "always-fail");
442 assert_eq!(s.diagnostics().len(), 1);
443 assert_eq!(s.diagnostics()[0].message(), "heads up");
444 }
445}