Skip to main content

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}