Skip to main content

repl_lang/
feed.rs

1//! The values exchanged between a session and its pipeline.
2
3use source_lang::{SourceId, Span};
4
5/// The pipeline's verdict on the pending input: finished, or waiting for more.
6///
7/// The pipeline closure passed to [`Session::feed`](crate::Session::feed)
8/// returns a `Status`. `Complete` carries whatever the pipeline produced — a
9/// parsed tree, an evaluated value, a list of diagnostics, or a `Result` of
10/// those — and the session hands it straight back in
11/// [`Feed::Complete`]. Because the check for completeness and the real work
12/// are the same call, the pipeline parses each finished entry exactly once.
13///
14/// Return `Complete` for input that is *wrong* as well as input that is
15/// right: a syntax error in the middle of a line will not be fixed by typing
16/// more lines. Only input that ended too soon should be `Incomplete`; see
17/// [`Input::is_incomplete`](crate::Input::is_incomplete) for the usual test.
18///
19/// # Examples
20///
21/// ```
22/// use repl_lang::{Feed, Session, Status};
23///
24/// // A pipeline that treats a trailing backslash as a line continuation.
25/// let pipeline = |input: repl_lang::Input<'_>| {
26///     if input.text().trim_end().ends_with('\\') {
27///         Status::Incomplete
28///     } else {
29///         Status::Complete(input.text().lines().count())
30///     }
31/// };
32///
33/// let mut session = Session::new();
34/// assert!(matches!(session.feed("echo one \\", pipeline)?, Feed::Incomplete));
35/// assert!(matches!(session.feed("two", pipeline)?, Feed::Complete { value: 2, .. }));
36/// # Ok::<(), repl_lang::SessionError>(())
37/// ```
38#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
39pub enum Status<T> {
40    /// The input is a whole entry. The session commits it and returns the
41    /// value in [`Feed::Complete`].
42    Complete(T),
43    /// The input stops partway through a construct. The session keeps it and
44    /// calls the pipeline again with the next line appended.
45    Incomplete,
46}
47
48/// The outcome of feeding one line to a [`Session`](crate::Session).
49///
50/// A host's read loop matches on it: evaluate and print on `Complete`, show a
51/// continuation prompt on `Incomplete`, and simply prompt again on `Empty`.
52///
53/// # Examples
54///
55/// ```
56/// use repl_lang::{Feed, Session, Status};
57///
58/// let mut session = Session::new();
59/// let pipeline = |input: repl_lang::Input<'_>| {
60///     let open = input.text().matches('{').count();
61///     let close = input.text().matches('}').count();
62///     if open > close { Status::Incomplete } else { Status::Complete(input.text().len()) }
63/// };
64///
65/// let mut transcript = Vec::new();
66/// for line in ["", "f {", "  x", "}"] {
67///     let prompt = if session.is_pending() { "... " } else { ">>> " };
68///     match session.feed(line, pipeline)? {
69///         Feed::Complete { entry, value } => {
70///             transcript.push(format!("{prompt}{line}  -> entry {} ({value} bytes)", entry.number()));
71///         }
72///         Feed::Incomplete => transcript.push(format!("{prompt}{line}")),
73///         Feed::Empty => transcript.push(prompt.trim_end().to_owned()),
74///     }
75/// }
76/// assert_eq!(transcript, [">>>", ">>> f {", "...   x", "... }  -> entry 1 (10 bytes)"]);
77/// # Ok::<(), repl_lang::SessionError>(())
78/// ```
79#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
80pub enum Feed<T> {
81    /// The pipeline accepted the entry. It is now committed to the session's
82    /// source map, so diagnostics carrying its spans render against
83    /// [`Session::sources`](crate::Session::sources).
84    Complete {
85        /// Where the committed entry lives.
86        entry: Entry,
87        /// What the pipeline returned in [`Status::Complete`].
88        value: T,
89    },
90    /// The pipeline needs more input. The line is kept, and the next one is
91    /// appended to it.
92    Incomplete,
93    /// The line was blank and no entry was in progress, so there was nothing
94    /// to evaluate. The pipeline was not called and no entry number was used.
95    Empty,
96}
97
98/// A committed entry: its number and where its text lives in the session's
99/// source map.
100///
101/// Returned in [`Feed::Complete`]. The text itself is stored once, in the
102/// session; an `Entry` is a small `Copy` handle to it.
103///
104/// # Examples
105///
106/// ```
107/// use repl_lang::{Feed, Session, Span, Status};
108///
109/// let mut session = Session::new();
110/// session.feed("first", |_| Status::Complete(()))?;
111/// let Feed::Complete { entry, .. } = session.feed("second", |_| Status::Complete(()))? else {
112///     return Err("the pipeline always completes".into());
113/// };
114///
115/// assert_eq!(entry.number(), 2);
116/// assert_eq!(entry.span(), Span::new(6, 13));
117/// let source = session.sources().source(entry.id()).ok_or("missing")?;
118/// assert_eq!(source.name(), "<repl:2>");
119/// assert_eq!(source.text(), "second\n");
120/// # Ok::<(), Box<dyn std::error::Error>>(())
121/// ```
122#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
123pub struct Entry {
124    number: u32,
125    id: SourceId,
126    span: Span,
127}
128
129impl Entry {
130    #[inline]
131    pub(crate) const fn new(number: u32, id: SourceId, span: Span) -> Self {
132        Self { number, id, span }
133    }
134
135    /// The entry's number, counting from `1` in the order entries were
136    /// committed. It matches [`Input::number`](crate::Input::number) for the
137    /// input that produced it and is the key for
138    /// [`Session::entry`](crate::Session::entry).
139    #[inline]
140    #[must_use]
141    pub const fn number(&self) -> u32 {
142        self.number
143    }
144
145    /// The entry's id in [`Session::sources`](crate::Session::sources).
146    #[inline]
147    #[must_use]
148    pub const fn id(&self) -> SourceId {
149        self.id
150    }
151
152    /// The global span of the entry's text, equal to the
153    /// [`Input::span`](crate::Input::span) the pipeline saw.
154    #[inline]
155    #[must_use]
156    pub const fn span(&self) -> Span {
157        self.span
158    }
159}