yash_syntax/
input.rs

1// This file is part of yash, an extended POSIX shell.
2// Copyright (C) 2020 WATANABE Yuki
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <https://www.gnu.org/licenses/>.
16
17//! Methods about passing [source](crate::source) code to the [parser](crate::parser)
18
19use std::ops::DerefMut;
20use std::pin::Pin;
21
22/// Parameter passed to the input function
23///
24/// The context is passed to the [input function](Input::next_line) so that it
25/// can read the input in a context-dependent way.
26#[derive(Debug)]
27#[non_exhaustive]
28pub struct Context {
29    is_first_line: bool,
30}
31
32impl Default for Context {
33    fn default() -> Self {
34        Self {
35            is_first_line: true,
36        }
37    }
38}
39
40impl Context {
41    /// Whether the current line is the first line of the input
42    #[inline]
43    #[must_use]
44    pub fn is_first_line(&self) -> bool {
45        self.is_first_line
46    }
47
48    /// Sets whether the current line is the first line of the input
49    ///
50    /// This method is used by the lexer to set the flag. It can also be used in
51    /// tests to simulate a non-first line. The default value is `true`.
52    #[inline]
53    pub fn set_is_first_line(&mut self, is_first_line: bool) {
54        self.is_first_line = is_first_line;
55    }
56}
57
58/// Error returned by the [Input] function
59pub type Error = std::io::Error;
60
61/// Result of the [Input] function
62pub type Result = std::result::Result<String, Error>;
63
64/// Line-oriented source code reader
65///
66/// An `Input` implementor provides the parser with source code by reading from underlying source.
67///
68/// [`InputObject`] is an object-safe version of this trait.
69#[must_use = "Input instances should be used by a parser"]
70pub trait Input {
71    /// Reads a next line of the source code.
72    ///
73    /// The input function is line-oriented; that is, this function returns a string that is
74    /// terminated by a newline unless the end of input (EOF) is reached, in which case the
75    /// remaining characters up to the EOF must be returned without a trailing newline. If there
76    /// are no more characters at all, the returned line is empty.
77    ///
78    /// Errors returned from this function are considered unrecoverable. Once an error is returned,
79    /// this function should not be called any more.
80    fn next_line(&mut self, context: &Context) -> impl Future<Output = Result>;
81}
82
83impl<T> Input for T
84where
85    T: DerefMut,
86    T::Target: Input,
87{
88    fn next_line(&mut self, context: &Context) -> impl Future<Output = Result> {
89        self.deref_mut().next_line(context)
90    }
91}
92
93/// Object-safe adapter for the [`Input`] trait
94///
95/// `InputObject` is an object-safe version of the [`Input`] trait. It allows
96/// the trait to be used as a trait object, which is necessary for dynamic
97/// dispatch.
98///
99/// The umbrella implementation is provided for all types that implement the
100/// [`Input`] trait.
101pub trait InputObject {
102    fn next_line<'a>(
103        &'a mut self,
104        context: &'a Context,
105    ) -> Pin<Box<dyn Future<Output = Result> + 'a>>;
106}
107
108impl<T: Input> InputObject for T {
109    fn next_line<'a>(
110        &'a mut self,
111        context: &'a Context,
112    ) -> Pin<Box<dyn Future<Output = Result> + 'a>> {
113        Box::pin(Input::next_line(self, context))
114    }
115}
116
117/// Input function that reads from a string in memory
118pub struct Memory<'a> {
119    lines: std::str::SplitInclusive<'a, char>,
120}
121
122impl Memory<'_> {
123    /// Creates a new `Memory` that reads the given string.
124    pub fn new(code: &str) -> Memory<'_> {
125        let lines = code.split_inclusive('\n');
126        Memory { lines }
127    }
128}
129
130impl<'a> From<&'a str> for Memory<'a> {
131    fn from(code: &'a str) -> Memory<'a> {
132        Memory::new(code)
133    }
134}
135
136impl Input for Memory<'_> {
137    async fn next_line(&mut self, _context: &Context) -> Result {
138        Ok(self.lines.next().unwrap_or("").to_owned())
139    }
140}
141
142#[cfg(test)]
143mod tests {
144    use super::{Context, Input, Memory};
145    use futures_util::FutureExt;
146
147    #[test]
148    fn memory_empty_source() {
149        let mut input = Memory::new("");
150        let context = Context::default();
151
152        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
153        assert_eq!(line, "");
154    }
155
156    #[test]
157    fn memory_one_line() {
158        let mut input = Memory::new("one\n");
159        let context = Context::default();
160
161        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
162        assert_eq!(line, "one\n");
163
164        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
165        assert_eq!(line, "");
166    }
167
168    #[test]
169    fn memory_three_lines() {
170        let mut input = Memory::new("one\ntwo\nthree");
171        let context = Context::default();
172
173        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
174        assert_eq!(line, "one\n");
175
176        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
177        assert_eq!(line, "two\n");
178
179        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
180        assert_eq!(line, "three");
181
182        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
183        assert_eq!(line, "");
184    }
185}