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 spans are **global** positions in the session's source map, as a
194    /// parser fed by [`cursor`](Input::cursor) produces them. For a parser
195    /// that runs on [`text`](Input::text) alone and reports positions from
196    /// `0` (a lang-forge language, for one), use
197    /// [`is_incomplete_relative`](Input::is_incomplete_relative) instead.
198    ///
199    /// The rule matches how [`parser_lang`] reports a missing token at the end
200    /// of input — an empty span just past the last token — and works with any
201    /// parser that does the same. Errors a lexer reports where a construct
202    /// *starts*, such as an unterminated string pointing at its opening quote,
203    /// are not recognised; return [`Status::Incomplete`](crate::Status::Incomplete)
204    /// for those directly.
205    ///
206    /// [`parser_lang`]: https://docs.rs/parser-lang
207    ///
208    /// # Examples
209    ///
210    /// ```
211    /// use diag_lang::{Diagnostic, Label, Severity};
212    /// use repl_lang::{Session, Span, Status};
213    ///
214    /// let error_at = |at: u32| {
215    ///     Diagnostic::new(Severity::Error, "expected expression", Label::new(Span::empty(at), "here"))
216    /// };
217    ///
218    /// let mut session = Session::new();
219    /// session.feed("1 + (2 *", |input| {
220    ///     // An error at the end (byte 8, before the trailing newline): incomplete.
221    ///     assert!(input.is_incomplete(&[error_at(8)]));
222    ///     // An error in the middle: the input is wrong, not unfinished.
223    ///     assert!(!input.is_incomplete(&[error_at(2), error_at(8)]));
224    ///     // No errors at all: complete.
225    ///     assert!(!input.is_incomplete(&[]));
226    ///     Status::Complete(())
227    /// })?;
228    /// # Ok::<(), repl_lang::SessionError>(())
229    /// ```
230    #[must_use]
231    pub fn is_incomplete(&self, errors: &[Diagnostic]) -> bool {
232        // Fits: `base + text.len()` fits in `u32`, and this is no longer.
233        errors_at_end(errors, self.base + self.meaningful_len())
234    }
235
236    /// [`is_incomplete`](Input::is_incomplete) for a parser whose spans are
237    /// **relative to [`text`](Input::text)**: byte `0` is the entry's first
238    /// byte, whatever its [`base`](Input::base).
239    ///
240    /// Use it when the pipeline parses `input.text()` on its own, without
241    /// [`cursor`](Input::cursor) — a language forged with lang-forge, for
242    /// example, whose `Language::parse(input.text())` reports positions from
243    /// `0`. The rule is the same as `is_incomplete`'s: at least one
244    /// error-severity diagnostic, and every one starting at or after the last
245    /// non-whitespace byte of the text. Passing relative spans to
246    /// `is_incomplete` instead would judge every entry after the first
247    /// complete, because their end-of-input errors fall before the entry's
248    /// global end.
249    ///
250    /// Which method suits which parser:
251    ///
252    /// | The parser… | Spans | Method |
253    /// |---|---|---|
254    /// | lexes with [`Input::cursor`] (or adds [`Input::base`] itself) | global | [`is_incomplete`](Input::is_incomplete) |
255    /// | parses [`Input::text`] as a standalone string | relative | `is_incomplete_relative` |
256    ///
257    /// In the first entry the two agree, since `base` is `0`; from the second
258    /// on, only the matching one is right.
259    ///
260    /// # Examples
261    ///
262    /// ```
263    /// use diag_lang::{Diagnostic, Label, Severity};
264    /// use repl_lang::{Session, Span, Status};
265    ///
266    /// let error_at = |at: u32| {
267    ///     Diagnostic::new(Severity::Error, "expected expression", Label::new(Span::empty(at), "here"))
268    /// };
269    ///
270    /// let mut session = Session::new();
271    /// session.feed("let a = 1;", |_| Status::Complete(()))?; // occupies 0..11
272    /// session.feed("(2 *", |input| {
273    ///     assert_eq!(input.base().to_u32(), 11);
274    ///     // A parser of `input.text()` alone reports the end of `(2 *` at 4.
275    ///     assert!(input.is_incomplete_relative(&[error_at(4)]));
276    ///     assert!(!input.is_incomplete_relative(&[error_at(1)])); // mid-text
277    ///     // The same spans read as global would point into entry 1.
278    ///     assert!(!input.is_incomplete(&[error_at(4)]));
279    ///     Status::Complete(())
280    /// })?;
281    /// # Ok::<(), repl_lang::SessionError>(())
282    /// ```
283    #[must_use]
284    pub fn is_incomplete_relative(&self, errors: &[Diagnostic]) -> bool {
285        errors_at_end(errors, self.meaningful_len())
286    }
287
288    /// Length of the text without its trailing whitespace: where the last
289    /// thing the user typed ends.
290    #[inline]
291    fn meaningful_len(&self) -> u32 {
292        // Fits: the session admits text only if its length fits in `u32`.
293        self.text.trim_end().len() as u32
294    }
295}
296
297/// `true` if there is at least one error-severity diagnostic and every one
298/// starts at or after `end`.
299fn errors_at_end(errors: &[Diagnostic], end: u32) -> bool {
300    let mut any = false;
301    for error in errors.iter().filter(|d| d.severity() == Severity::Error) {
302        if error.primary().span().start().to_u32() < end {
303            return false;
304        }
305        any = true;
306    }
307    any
308}
309
310#[cfg(test)]
311mod tests {
312    use diag_lang::Label;
313
314    use super::*;
315
316    fn diag(severity: Severity, at: u32) -> Diagnostic {
317        Diagnostic::new(severity, "m", Label::new(Span::empty(at), "l"))
318    }
319
320    #[test]
321    fn test_span_covers_text_from_base() {
322        let input = Input::new("ab\n", 10, 1);
323        assert_eq!(input.span(), Span::new(10, 13));
324        assert_eq!(input.base(), BytePos::new(10));
325    }
326
327    #[test]
328    fn test_cursor_positions_are_global() {
329        let input = Input::new("xy\n", 40, 1);
330        let mut cursor = input.cursor();
331        assert_eq!(cursor.pos(), BytePos::new(40));
332        assert_eq!(cursor.bump(), Some('x'));
333        assert_eq!(cursor.pos(), BytePos::new(41));
334    }
335
336    #[test]
337    fn test_is_incomplete_no_errors_is_false() {
338        assert!(!Input::new("x\n", 0, 1).is_incomplete(&[]));
339    }
340
341    #[test]
342    fn test_is_incomplete_error_at_trimmed_end_is_true() {
343        // "1 +   \n" — the meaningful text ends at 3.
344        let input = Input::new("1 +   \n", 0, 1);
345        assert!(input.is_incomplete(&[diag(Severity::Error, 3)]));
346        assert!(input.is_incomplete(&[diag(Severity::Error, 7)]));
347        assert!(!input.is_incomplete(&[diag(Severity::Error, 2)]));
348    }
349
350    #[test]
351    fn test_is_incomplete_respects_base() {
352        let input = Input::new("1 +\n", 100, 1);
353        assert!(input.is_incomplete(&[diag(Severity::Error, 103)]));
354        assert!(!input.is_incomplete(&[diag(Severity::Error, 3)]));
355    }
356
357    #[test]
358    fn test_is_incomplete_ignores_non_errors() {
359        let input = Input::new("1 +\n", 0, 1);
360        assert!(!input.is_incomplete(&[diag(Severity::Warning, 3)]));
361        assert!(input.is_incomplete(&[diag(Severity::Warning, 0), diag(Severity::Error, 3)]));
362        assert!(!input.is_incomplete(&[diag(Severity::Note, 0), diag(Severity::Help, 1)]));
363    }
364
365    #[test]
366    fn test_is_incomplete_any_early_error_wins() {
367        let input = Input::new("a b c\n", 0, 1);
368        let errors = [diag(Severity::Error, 5), diag(Severity::Error, 1)];
369        assert!(!input.is_incomplete(&errors));
370    }
371
372    #[test]
373    fn test_is_incomplete_relative_ignores_base() {
374        // "1 +   \n" at base 100: the meaningful text ends at relative 3.
375        let input = Input::new("1 +   \n", 100, 2);
376        assert!(input.is_incomplete_relative(&[diag(Severity::Error, 3)]));
377        assert!(input.is_incomplete_relative(&[diag(Severity::Error, 7)]));
378        assert!(!input.is_incomplete_relative(&[diag(Severity::Error, 2)]));
379        // Global positions are past the text, so they read as "at the end";
380        // the global method is the one for them.
381        assert!(input.is_incomplete(&[diag(Severity::Error, 103)]));
382        assert!(!input.is_incomplete(&[diag(Severity::Error, 3)]));
383    }
384
385    #[test]
386    fn test_is_incomplete_relative_same_rules_as_global() {
387        let input = Input::new("a b c\n", 40, 3);
388        assert!(!input.is_incomplete_relative(&[]));
389        assert!(!input.is_incomplete_relative(&[diag(Severity::Warning, 5)]));
390        let errors = [diag(Severity::Error, 5), diag(Severity::Error, 1)];
391        assert!(!input.is_incomplete_relative(&errors));
392        assert!(input.is_incomplete_relative(&[diag(Severity::Note, 0), diag(Severity::Error, 5)]));
393    }
394
395    #[test]
396    fn test_both_methods_agree_at_base_zero() {
397        let input = Input::new("x *\n", 0, 1);
398        for at in 0..6 {
399            let errors = [diag(Severity::Error, at)];
400            assert_eq!(
401                input.is_incomplete(&errors),
402                input.is_incomplete_relative(&errors)
403            );
404        }
405    }
406}