Skip to main content

repl_lang/
input.rs

1//! The view of pending input a session hands to the pipeline.
2
3use diag_lang::{Diagnostic, Severity};
4use lexer_lang::Cursor;
5use source_lang::{BytePos, Span};
6
7/// The pending text of one REPL entry, as the pipeline sees it.
8///
9/// Each time a line is fed to a [`Session`](crate::Session), the session calls
10/// the pipeline with an `Input` holding *every* line of the entry so far —
11/// the first line, then the first two, and so on until the pipeline answers
12/// [`Status::Complete`](crate::Status::Complete). Lines are joined with `'\n'`
13/// and the text always ends with one.
14///
15/// The input also carries its position in the session's
16/// [`SourceMap`](source_lang::SourceMap): [`base`](Input::base) is the global
17/// offset its first byte will occupy once the entry is committed. Lex with
18/// [`cursor`](Input::cursor), or offset spans by `base` yourself, and every span
19/// the pipeline produces — in tokens, in syntax trees, in diagnostics — is
20/// already valid against [`Session::sources`](crate::Session::sources). There
21/// is no second pass to rebase spans after the entry is accepted.
22///
23/// `Input` is a borrowed, `Copy` view; it lives only for the duration of the
24/// pipeline call.
25///
26/// # Examples
27///
28/// ```
29/// use repl_lang::{Feed, Session, Status};
30///
31/// let mut session = Session::new();
32/// let feed = session.feed("1 + 2", |input| {
33///     assert_eq!(input.text(), "1 + 2\n");
34///     assert_eq!(input.number(), 1);
35///     Status::Complete(input.text().len())
36/// })?;
37/// assert!(matches!(feed, Feed::Complete { value: 6, .. }));
38/// # Ok::<(), repl_lang::SessionError>(())
39/// ```
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
41pub struct Input<'a> {
42    text: &'a str,
43    base: u32,
44    number: u32,
45}
46
47impl<'a> Input<'a> {
48    /// Wraps pending text that the session has checked will fit at `base`.
49    #[inline]
50    pub(crate) const fn new(text: &'a str, base: u32, number: u32) -> Self {
51        Self { text, base, number }
52    }
53
54    /// Every line of the entry so far, joined with `'\n'` and ending with one.
55    ///
56    /// # Examples
57    ///
58    /// ```
59    /// use repl_lang::{Feed, Session, Status};
60    ///
61    /// let mut session = Session::new();
62    /// let mut seen = Vec::new();
63    /// for line in ["(1 +", " 2)"] {
64    ///     session.feed(line, |input| {
65    ///         seen.push(input.text().to_owned());
66    ///         if input.text().matches('(').count() > input.text().matches(')').count() {
67    ///             Status::Incomplete
68    ///         } else {
69    ///             Status::Complete(())
70    ///         }
71    ///     })?;
72    /// }
73    /// assert_eq!(seen, ["(1 +\n", "(1 +\n 2)\n"]);
74    /// # Ok::<(), repl_lang::SessionError>(())
75    /// ```
76    #[inline]
77    #[must_use]
78    pub const fn text(&self) -> &'a str {
79        self.text
80    }
81
82    /// The global position the first byte of this entry occupies in the
83    /// session's source map.
84    ///
85    /// Entries are laid out end to end, so the base of entry *n* is the total
86    /// length of entries 1 through *n − 1*.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use repl_lang::{Session, Status};
92    ///
93    /// let mut session = Session::new();
94    /// session.feed("abc", |_| Status::Complete(()))?; // occupies 0..4 ("abc\n")
95    /// session.feed("x", |input| {
96    ///     assert_eq!(input.base().to_u32(), 4);
97    ///     Status::Complete(())
98    /// })?;
99    /// # Ok::<(), repl_lang::SessionError>(())
100    /// ```
101    #[inline]
102    #[must_use]
103    pub const fn base(&self) -> BytePos {
104        BytePos::new(self.base)
105    }
106
107    /// The global span the whole entry will occupy: from
108    /// [`base`](Input::base) to the end of [`text`](Input::text).
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use repl_lang::{Session, Span, Status};
114    ///
115    /// let mut session = Session::new();
116    /// session.feed("let x = 1", |input| {
117    ///     assert_eq!(input.span(), Span::new(0, 10));
118    ///     Status::Complete(())
119    /// })?;
120    /// # Ok::<(), repl_lang::SessionError>(())
121    /// ```
122    #[inline]
123    #[must_use]
124    pub const fn span(&self) -> Span {
125        // The session admits text only if `base + len` fits in `u32`.
126        Span::new(self.base, self.base + self.text.len() as u32)
127    }
128
129    /// The entry number this input will be committed as, counting from `1`.
130    ///
131    /// Useful for prompts (`In [3]:`) and for names the evaluator binds per
132    /// entry (`_3`). The number does not advance while an entry is incomplete,
133    /// and blank lines never consume one.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use repl_lang::{Session, Status};
139    ///
140    /// let mut session = Session::new();
141    /// session.feed("a", |input| { assert_eq!(input.number(), 1); Status::Complete(()) })?;
142    /// session.feed("b", |input| { assert_eq!(input.number(), 2); Status::Complete(()) })?;
143    /// # Ok::<(), repl_lang::SessionError>(())
144    /// ```
145    #[inline]
146    #[must_use]
147    pub const fn number(&self) -> u32 {
148        self.number
149    }
150
151    /// A [`lexer_lang::Cursor`] over [`text`](Input::text), positioned at
152    /// [`base`](Input::base).
153    ///
154    /// Every token span the cursor produces is a global span in the session's
155    /// source map, so tokens, the syntax built from them, and any diagnostic
156    /// pointing at them render correctly against
157    /// [`Session::sources`](crate::Session::sources) once the entry is
158    /// committed.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use repl_lang::{Session, Status};
164    ///
165    /// let mut session = Session::new();
166    /// session.feed("first", |_| Status::Complete(()))?; // 0..6
167    /// session.feed("ab", |input| {
168    ///     let mut cursor = input.cursor();
169    ///     cursor.eat_while(|c| c.is_alphabetic());
170    ///     assert_eq!(cursor.lexeme(), "ab");
171    ///     assert_eq!(cursor.token_span().start().to_u32(), 6); // global, not 0
172    ///     Status::Complete(())
173    /// })?;
174    /// # Ok::<(), repl_lang::SessionError>(())
175    /// ```
176    #[inline]
177    #[must_use]
178    pub fn cursor(&self) -> Cursor<'a> {
179        Cursor::with_base(self.text, self.base)
180    }
181
182    /// Returns `true` if `errors` say the input ended too soon rather than that
183    /// it is wrong — the signal to ask for another line.
184    ///
185    /// That is the case when there is at least one error-severity diagnostic,
186    /// and every one of them points at the end of the input: its primary span
187    /// starts at or after the last non-whitespace byte. A parser that reaches
188    /// the end of `1 + (2 *` reports "expected expression" *at the end*, so the
189    /// user simply has not finished typing; a parser that trips over `1 + )`
190    /// reports the error at the `)`, and more lines will never fix that.
191    /// Warnings, notes, and help messages are ignored.
192    ///
193    /// The rule matches how [`parser_lang`] reports a missing token at the end
194    /// of input — an empty span just past the last token — and works with any
195    /// parser that does the same. Errors a lexer reports where a construct
196    /// *starts*, such as an unterminated string pointing at its opening quote,
197    /// are not recognised; return [`Status::Incomplete`](crate::Status::Incomplete)
198    /// for those directly.
199    ///
200    /// [`parser_lang`]: https://docs.rs/parser-lang
201    ///
202    /// # Examples
203    ///
204    /// ```
205    /// use diag_lang::{Diagnostic, Label, Severity};
206    /// use repl_lang::{Session, Span, Status};
207    ///
208    /// let error_at = |at: u32| {
209    ///     Diagnostic::new(Severity::Error, "expected expression", Label::new(Span::empty(at), "here"))
210    /// };
211    ///
212    /// let mut session = Session::new();
213    /// session.feed("1 + (2 *", |input| {
214    ///     // An error at the end (byte 8, before the trailing newline): incomplete.
215    ///     assert!(input.is_incomplete(&[error_at(8)]));
216    ///     // An error in the middle: the input is wrong, not unfinished.
217    ///     assert!(!input.is_incomplete(&[error_at(2), error_at(8)]));
218    ///     // No errors at all: complete.
219    ///     assert!(!input.is_incomplete(&[]));
220    ///     Status::Complete(())
221    /// })?;
222    /// # Ok::<(), repl_lang::SessionError>(())
223    /// ```
224    #[must_use]
225    pub fn is_incomplete(&self, errors: &[Diagnostic]) -> bool {
226        // Fits: `base + text.len()` fits in `u32`, and this is no longer.
227        let end = self.base + self.text.trim_end().len() as u32;
228        let mut any = false;
229        for error in errors.iter().filter(|d| d.severity() == Severity::Error) {
230            if error.primary().span().start().to_u32() < end {
231                return false;
232            }
233            any = true;
234        }
235        any
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use diag_lang::Label;
242
243    use super::*;
244
245    fn diag(severity: Severity, at: u32) -> Diagnostic {
246        Diagnostic::new(severity, "m", Label::new(Span::empty(at), "l"))
247    }
248
249    #[test]
250    fn test_span_covers_text_from_base() {
251        let input = Input::new("ab\n", 10, 1);
252        assert_eq!(input.span(), Span::new(10, 13));
253        assert_eq!(input.base(), BytePos::new(10));
254    }
255
256    #[test]
257    fn test_cursor_positions_are_global() {
258        let input = Input::new("xy\n", 40, 1);
259        let mut cursor = input.cursor();
260        assert_eq!(cursor.pos(), BytePos::new(40));
261        assert_eq!(cursor.bump(), Some('x'));
262        assert_eq!(cursor.pos(), BytePos::new(41));
263    }
264
265    #[test]
266    fn test_is_incomplete_no_errors_is_false() {
267        assert!(!Input::new("x\n", 0, 1).is_incomplete(&[]));
268    }
269
270    #[test]
271    fn test_is_incomplete_error_at_trimmed_end_is_true() {
272        // "1 +   \n" — the meaningful text ends at 3.
273        let input = Input::new("1 +   \n", 0, 1);
274        assert!(input.is_incomplete(&[diag(Severity::Error, 3)]));
275        assert!(input.is_incomplete(&[diag(Severity::Error, 7)]));
276        assert!(!input.is_incomplete(&[diag(Severity::Error, 2)]));
277    }
278
279    #[test]
280    fn test_is_incomplete_respects_base() {
281        let input = Input::new("1 +\n", 100, 1);
282        assert!(input.is_incomplete(&[diag(Severity::Error, 103)]));
283        assert!(!input.is_incomplete(&[diag(Severity::Error, 3)]));
284    }
285
286    #[test]
287    fn test_is_incomplete_ignores_non_errors() {
288        let input = Input::new("1 +\n", 0, 1);
289        assert!(!input.is_incomplete(&[diag(Severity::Warning, 3)]));
290        assert!(input.is_incomplete(&[diag(Severity::Warning, 0), diag(Severity::Error, 3)]));
291        assert!(!input.is_incomplete(&[diag(Severity::Note, 0), diag(Severity::Help, 1)]));
292    }
293
294    #[test]
295    fn test_is_incomplete_any_early_error_wins() {
296        let input = Input::new("a b c\n", 0, 1);
297        let errors = [diag(Severity::Error, 5), diag(Severity::Error, 1)];
298        assert!(!input.is_incomplete(&errors));
299    }
300}