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::future::Future;
20use std::ops::DerefMut;
21use std::pin::Pin;
22
23/// Parameter passed to the input function
24///
25/// The context is passed to the [input function](Input::next_line) so that it
26/// can read the input in a context-dependent way.
27#[derive(Debug)]
28#[non_exhaustive]
29pub struct Context {
30    is_first_line: bool,
31}
32
33impl Default for Context {
34    fn default() -> Self {
35        Self {
36            is_first_line: true,
37        }
38    }
39}
40
41impl Context {
42    /// Whether the current line is the first line of the input
43    #[inline]
44    #[must_use]
45    pub fn is_first_line(&self) -> bool {
46        self.is_first_line
47    }
48
49    /// Sets whether the current line is the first line of the input
50    ///
51    /// This method is used by the lexer to set the flag. It can also be used in
52    /// tests to simulate a non-first line. The default value is `true`.
53    #[inline]
54    pub fn set_is_first_line(&mut self, is_first_line: bool) {
55        self.is_first_line = is_first_line;
56    }
57}
58
59/// Error returned by the [Input] function.
60pub type Error = std::io::Error;
61
62/// Result of the [Input] function.
63pub type Result = std::result::Result<String, Error>;
64
65/// Line-oriented source code reader
66///
67/// An `Input` implementor provides the parser with source code by reading from underlying source.
68///
69/// [`InputObject`] is an object-safe version of this trait.
70#[must_use = "Input instances should be used by a parser"]
71pub trait Input {
72    /// Reads a next line of the source code.
73    ///
74    /// The input function is line-oriented; that is, this function returns a string that is
75    /// terminated by a newline unless the end of input (EOF) is reached, in which case the
76    /// remaining characters up to the EOF must be returned without a trailing newline. If there
77    /// are no more characters at all, the returned line is empty.
78    ///
79    /// Errors returned from this function are considered unrecoverable. Once an error is returned,
80    /// this function should not be called any more.
81    fn next_line(&mut self, context: &Context) -> impl Future<Output = Result>;
82}
83
84impl<T> Input for T
85where
86    T: DerefMut,
87    T::Target: Input,
88{
89    fn next_line(&mut self, context: &Context) -> impl Future<Output = Result> {
90        self.deref_mut().next_line(context)
91    }
92}
93
94/// Object-safe adapter for the [`Input`] trait
95///
96/// `InputObject` is an object-safe version of the [`Input`] trait. It allows
97/// the trait to be used as a trait object, which is necessary for dynamic
98/// dispatch.
99///
100/// The umbrella implementation is provided for all types that implement the
101/// [`Input`] trait.
102pub trait InputObject {
103    fn next_line<'a>(
104        &'a mut self,
105        context: &'a Context,
106    ) -> Pin<Box<dyn Future<Output = Result> + 'a>>;
107}
108
109impl<T: Input> InputObject for T {
110    fn next_line<'a>(
111        &'a mut self,
112        context: &'a Context,
113    ) -> Pin<Box<dyn Future<Output = Result> + 'a>> {
114        Box::pin(Input::next_line(self, context))
115    }
116}
117
118/// Input function that reads from a string in memory.
119pub struct Memory<'a> {
120    lines: std::str::SplitInclusive<'a, char>,
121}
122
123impl Memory<'_> {
124    /// Creates a new `Memory` that reads the given string.
125    pub fn new(code: &str) -> Memory<'_> {
126        let lines = code.split_inclusive('\n');
127        Memory { lines }
128    }
129}
130
131impl Input for Memory<'_> {
132    async fn next_line(&mut self, _context: &Context) -> Result {
133        Ok(self.lines.next().unwrap_or("").to_owned())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use super::{Context, Input, Memory};
140    use futures_util::FutureExt;
141
142    #[test]
143    fn memory_empty_source() {
144        let mut input = Memory::new("");
145        let context = Context::default();
146
147        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
148        assert_eq!(line, "");
149    }
150
151    #[test]
152    fn memory_one_line() {
153        let mut input = Memory::new("one\n");
154        let context = Context::default();
155
156        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
157        assert_eq!(line, "one\n");
158
159        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
160        assert_eq!(line, "");
161    }
162
163    #[test]
164    fn memory_three_lines() {
165        let mut input = Memory::new("one\ntwo\nthree");
166        let context = Context::default();
167
168        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
169        assert_eq!(line, "one\n");
170
171        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
172        assert_eq!(line, "two\n");
173
174        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
175        assert_eq!(line, "three");
176
177        let line = input.next_line(&context).now_or_never().unwrap().unwrap();
178        assert_eq!(line, "");
179    }
180}