Skip to main content

driver_lang/
stage.rs

1//! The compilation-phase trait — the seam a driver is built from.
2
3use crate::error::DriverError;
4use crate::session::Session;
5
6/// One phase of compilation: a transform from an input artifact to an output
7/// artifact, run against a [`Session`].
8///
9/// A stage is the unit a driver is assembled from. Each phase of a compiler — lex,
10/// parse, resolve names, type-check, lower, emit — is a `Stage` that takes the
11/// previous phase's artifact as [`Input`](Self::Input) and produces the next one
12/// as [`Output`](Self::Output): a lexer is `Stage<Input = Source, Output =
13/// Tokens>`, a parser is `Stage<Input = Tokens, Output = Ast>`, and so on. Unlike
14/// a `pass` (which rewrites one type in place), a stage *changes the type* as the
15/// program moves down the pipeline — that type-threading is exactly what a driver
16/// exists to manage.
17///
18/// A stage is generic over the session's configuration type `C`, so every stage in
19/// a pipeline reads and writes the same shared configuration and diagnostics. Wire
20/// stages together with [`Pipeline`](crate::Pipeline), which enforces at compile
21/// time that each stage's `Output` is the next stage's `Input`.
22///
23/// # Contract
24///
25/// - [`name`](Self::name) returns a stable, static identifier, used to attribute a
26///   [`DriverError`] to the stage that produced it. It must not change between
27///   runs.
28/// - [`run`](Self::run) consumes `input`, may read and write the [`Session`]
29///   (emitting diagnostics, reading configuration), and returns the output. A
30///   phase that cannot produce its output returns a [`DriverError`] — never a
31///   panic. Emitting an error *diagnostic* records a problem but does not by itself
32///   stop the pipeline; return `Err`, or call
33///   [`Session::abort_if_errors`](crate::Session::abort_if_errors), to stop.
34///
35/// # Examples
36///
37/// A stage that parses whitespace-separated integers, warning on each token it
38/// cannot read and failing only if nothing parsed:
39///
40/// ```
41/// use driver_lang::{DriverError, Session, Stage};
42///
43/// struct Lex;
44///
45/// impl Stage<()> for Lex {
46///     type Input = &'static str;
47///     type Output = Vec<i64>;
48///
49///     fn name(&self) -> &'static str {
50///         "lex"
51///     }
52///
53///     fn run(&mut self, input: &'static str, session: &mut Session<()>)
54///         -> Result<Vec<i64>, DriverError>
55///     {
56///         let mut out = Vec::new();
57///         for word in input.split_whitespace() {
58///             match word.parse::<i64>() {
59///                 Ok(n) => out.push(n),
60///                 Err(_) => { session.warn("skipping non-integer token"); }
61///             }
62///         }
63///         if out.is_empty() {
64///             return Err(DriverError::new("no integers in input"));
65///         }
66///         Ok(out)
67///     }
68/// }
69///
70/// let mut session = Session::new(());
71/// let tokens = Lex.run("1 two 3", &mut session).unwrap();
72/// assert_eq!(tokens, vec![1, 3]);
73/// assert_eq!(session.diagnostics().len(), 1); // one "skipping" warning
74/// ```
75pub trait Stage<C> {
76    /// The artifact this stage consumes.
77    type Input;
78
79    /// The artifact this stage produces.
80    type Output;
81
82    /// A stable, static name for this stage, used to attribute errors.
83    fn name(&self) -> &'static str;
84
85    /// Run the stage: consume `input`, use the [`Session`] as needed, and produce
86    /// the output.
87    ///
88    /// Return a [`DriverError`] — never a panic — if the phase cannot produce its
89    /// output; the [`Pipeline`](crate::Pipeline) stops there and stamps this
90    /// stage's name into the error.
91    ///
92    /// # Errors
93    ///
94    /// Returns whatever [`DriverError`] the phase produces when it cannot continue.
95    fn run(
96        &mut self,
97        input: Self::Input,
98        session: &mut Session<C>,
99    ) -> Result<Self::Output, DriverError>;
100}
101
102#[cfg(test)]
103#[allow(clippy::unwrap_used, clippy::expect_used)]
104mod tests {
105    use super::*;
106    use alloc::vec;
107    use alloc::vec::Vec;
108
109    /// Doubles every element; changes the length-independent shape but not the type.
110    struct Double;
111    impl Stage<()> for Double {
112        type Input = Vec<i64>;
113        type Output = Vec<i64>;
114        fn name(&self) -> &'static str {
115            "double"
116        }
117        fn run(&mut self, input: Vec<i64>, _s: &mut Session<()>) -> Result<Vec<i64>, DriverError> {
118            Ok(input.into_iter().map(|x| x * 2).collect())
119        }
120    }
121
122    /// A type-changing stage: sums a list into a scalar.
123    struct Sum;
124    impl Stage<()> for Sum {
125        type Input = Vec<i64>;
126        type Output = i64;
127        fn name(&self) -> &'static str {
128            "sum"
129        }
130        fn run(&mut self, input: Vec<i64>, _s: &mut Session<()>) -> Result<i64, DriverError> {
131            Ok(input.iter().sum())
132        }
133    }
134
135    #[test]
136    fn test_stage_transforms_input() {
137        let mut s = Session::new(());
138        let out = Double.run(vec![1, 2, 3], &mut s).unwrap();
139        assert_eq!(out, vec![2, 4, 6]);
140    }
141
142    #[test]
143    fn test_stage_can_change_output_type() {
144        let mut s = Session::new(());
145        let out = Sum.run(vec![1, 2, 3], &mut s).unwrap();
146        assert_eq!(out, 6);
147    }
148
149    #[test]
150    fn test_stage_name_is_stable() {
151        assert_eq!(Double.name(), "double");
152        assert_eq!(Sum.name(), "sum");
153    }
154}