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}