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}