Skip to content

Commit 48295e4

Browse files
A Rust crate to handle the eccentricities of BASIC input statements
1 parent 5301155 commit 48295e4

File tree

8 files changed

+658
-0
lines changed

8 files changed

+658
-0
lines changed

00_Common/rust/Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

00_Common/rust/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#
2+
# Although there is currently only the `basic_input` crate here, I am anticipating
3+
# more utility crates for rust ports, as well as proc macro crates.
4+
#
5+
[workspace]
6+
resolver = "2"
7+
members = [ "crates/basic_input" ]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "basic_input"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
Lines changed: 354 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,354 @@
1+
mod input_state;
2+
mod io_layer;
3+
4+
use input_state::InputState;
5+
use io_layer::IoLayer;
6+
7+
/// Main entrypoint for this module. Display a prompt and read one or more values from the user.
8+
/// See [the crate docs](module@crate)
9+
pub fn get_input<BI: BasicInput, S: AsRef<str>>(prompt: S) -> BI {
10+
let mut io = IoLayer::get_io();
11+
get_input_from_io(prompt, &mut io)
12+
}
13+
14+
// This is the trait that we return to the user, which allows us to have
15+
// a different function for each return type.
16+
// let one_input : f32 = get_input("...");
17+
// let (x, y) : (f32, f32) = get_input("...");
18+
// let (a, b, c) : (f32, f32, f32) = get_input("...");
19+
pub trait BasicInput {
20+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError>
21+
where
22+
Self: Sized;
23+
}
24+
25+
// --------------------------------------------------------------------------------
26+
27+
impl BasicInput for f32 {
28+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
29+
state.input_f32s(s, 1)?;
30+
Ok(state.data[0].as_f32())
31+
}
32+
}
33+
34+
impl BasicInput for (f32, f32) {
35+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
36+
state.input_f32s(s, 2)?;
37+
38+
Ok((state.data[0].as_f32(), state.data[1].as_f32()))
39+
}
40+
}
41+
42+
impl BasicInput for (f32, f32, f32) {
43+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
44+
state.input_f32s(s, 3)?;
45+
46+
Ok((
47+
state.data[0].as_f32(),
48+
state.data[1].as_f32(),
49+
state.data[2].as_f32(),
50+
))
51+
}
52+
}
53+
54+
impl BasicInput for (f32, f32, f32, f32) {
55+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
56+
state.input_f32s(s, 4)?;
57+
58+
Ok((
59+
state.data[0].as_f32(),
60+
state.data[1].as_f32(),
61+
state.data[2].as_f32(),
62+
state.data[3].as_f32(),
63+
))
64+
}
65+
}
66+
67+
impl BasicInput for (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32) {
68+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
69+
state.input_f32s(s, 10)?;
70+
71+
Ok((
72+
state.data[0].as_f32(),
73+
state.data[1].as_f32(),
74+
state.data[2].as_f32(),
75+
state.data[3].as_f32(),
76+
state.data[4].as_f32(),
77+
state.data[5].as_f32(),
78+
state.data[6].as_f32(),
79+
state.data[7].as_f32(),
80+
state.data[8].as_f32(),
81+
state.data[9].as_f32(),
82+
))
83+
}
84+
}
85+
86+
impl BasicInput for String {
87+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
88+
state.input_strings(s, 1)?;
89+
Ok(state.data[0].as_string())
90+
}
91+
}
92+
93+
impl BasicInput for (String, String) {
94+
fn from_string<S: AsRef<str>>(s: S, state: &mut InputState) -> Result<Self, BasicInputError> {
95+
state.input_strings(s, 2)?;
96+
Ok((state.data[0].as_string(), state.data[1].as_string()))
97+
}
98+
}
99+
100+
// --------------------------------------------------------------------------------
101+
102+
fn get_input_from_io<BI: BasicInput, S: AsRef<str>>(prompt: S, io: &mut IoLayer) -> BI {
103+
let mut state = InputState {
104+
io,
105+
data: Vec::new(),
106+
};
107+
108+
state.io.raw_output_string(format!("{}? ", prompt.as_ref()));
109+
110+
loop {
111+
let buffer = state.io.raw_input_string();
112+
match BI::from_string(buffer, &mut state) {
113+
Ok(result) => return result,
114+
Err(e) => match e {
115+
BasicInputError::ParseFailed => {
116+
state.io.raw_output_string("?REENTER\n");
117+
state.io.raw_output_string(format!("{}? ", prompt.as_ref()));
118+
state.data.clear();
119+
}
120+
BasicInputError::MissingInput => {
121+
state.io.raw_output_string("??? ");
122+
}
123+
},
124+
}
125+
}
126+
}
127+
128+
#[derive(Debug)]
129+
pub enum BasicInputError {
130+
ParseFailed,
131+
MissingInput,
132+
}
133+
134+
#[cfg(test)]
135+
mod test {
136+
use crate::input::get_input_from_io;
137+
use crate::input::io_layer::{IoLayer, TextInput, TextOutput};
138+
use std::collections::VecDeque;
139+
140+
fn getio() -> IoLayer {
141+
IoLayer {
142+
output: Box::new(WriteTestLayer::new()),
143+
input: Box::new(ReadTestLayer::new()),
144+
}
145+
}
146+
147+
#[test]
148+
fn test_trait_single_input() {
149+
let mut testio = getio();
150+
testio.input.set_input_text("2");
151+
let result: f32 = get_input_from_io("prompt", &mut testio);
152+
assert_eq!(2.0, result);
153+
}
154+
155+
#[test]
156+
fn test_trait_dual_input() {
157+
let mut testio = getio();
158+
testio.input.set_input_text("2,3");
159+
let result: (f32, f32) = get_input_from_io("prompt", &mut testio);
160+
assert_eq!(
161+
"prompt? ".to_string(),
162+
testio.output.get_last_line().unwrap()
163+
);
164+
assert_eq!(2.0, result.0);
165+
assert_eq!(3.0, result.1);
166+
}
167+
168+
#[test]
169+
fn test_invalid_input() {
170+
let mut testio = getio();
171+
testio.input.set_input_text("abc");
172+
testio.input.set_input_text("2");
173+
let result: f32 = get_input_from_io("prompt", &mut testio);
174+
assert_eq!(
175+
"prompt? ".to_string(),
176+
testio.output.get_last_line().unwrap()
177+
);
178+
assert_eq!(
179+
"?REENTER\n".to_string(),
180+
testio.output.get_last_line().unwrap()
181+
);
182+
assert_eq!(2.0, result);
183+
}
184+
185+
#[test]
186+
fn test_multiple_invalid_input() {
187+
let mut testio = getio();
188+
testio.input.set_input_text("abc");
189+
testio.input.set_input_text("2,a");
190+
testio.input.set_input_text("2,3");
191+
let result: (f32, f32) = get_input_from_io("prompt", &mut testio);
192+
assert_eq!(
193+
"prompt? ".to_string(),
194+
testio.output.get_last_line().unwrap()
195+
);
196+
assert_eq!(
197+
"?REENTER\n".to_string(),
198+
testio.output.get_last_line().unwrap()
199+
);
200+
assert_eq!(
201+
"prompt? ".to_string(),
202+
testio.output.get_last_line().unwrap()
203+
);
204+
assert_eq!(
205+
"?REENTER\n".to_string(),
206+
testio.output.get_last_line().unwrap()
207+
);
208+
assert_eq!(
209+
"prompt? ".to_string(),
210+
testio.output.get_last_line().unwrap()
211+
);
212+
assert_eq!(2.0, result.0);
213+
assert_eq!(3.0, result.1);
214+
}
215+
216+
#[test]
217+
fn two_inputs_over_multiple_lines() {
218+
let mut testio = getio();
219+
testio.input.set_input_text("2");
220+
testio.input.set_input_text("3");
221+
let result: (f32, f32) = get_input_from_io("prompt", &mut testio);
222+
assert_eq!(
223+
"prompt? ".to_string(),
224+
testio.output.get_last_line().unwrap()
225+
);
226+
assert_eq!("??? ".to_string(), testio.output.get_last_line().unwrap());
227+
assert_eq!(2.0, result.0);
228+
assert_eq!(3.0, result.1);
229+
}
230+
231+
#[test]
232+
fn test_triple_f32() {
233+
let mut testio = getio();
234+
testio.input.set_input_text("1, 2, 3");
235+
let result: (f32, f32, f32) = get_input_from_io("enter 3 numbers", &mut testio);
236+
assert_eq!(1.0, result.0);
237+
assert_eq!(2.0, result.1);
238+
assert_eq!(3.0, result.2);
239+
}
240+
241+
#[test]
242+
fn test_input_string() {
243+
let mut testio = getio();
244+
testio.input.set_input_text("hello");
245+
let result: String = get_input_from_io("String prompt", &mut testio);
246+
assert_eq!("hello".to_string(), result);
247+
}
248+
249+
#[test]
250+
fn test_two_input_strings() {
251+
let mut testio = getio();
252+
testio.input.set_input_text("hello, world");
253+
let result: (String, String) = get_input_from_io("Double string prompt", &mut testio);
254+
assert_eq!("hello".to_string(), result.0);
255+
assert_eq!("world".to_string(), result.1);
256+
}
257+
258+
#[test]
259+
fn test_four_nums() {
260+
let mut testio = getio();
261+
testio.input.set_input_text("1, 2, 3, 4");
262+
let result: (f32, f32, f32, f32) = get_input_from_io("whatever", &mut testio);
263+
assert_eq!(4.0, result.3);
264+
}
265+
266+
#[test]
267+
fn test_extra_ignored() {
268+
let mut testio = getio();
269+
testio.input.set_input_text("1, 2, 3, 4");
270+
let result: (f32, f32) = get_input_from_io("whatever", &mut testio);
271+
assert_eq!(Some("whatever? "), testio.output.get_last_line().as_deref());
272+
assert_eq!(
273+
Some("?EXTRA IGNORED\n"),
274+
testio.output.get_last_line().as_deref()
275+
);
276+
assert_eq!(2.0, result.1);
277+
}
278+
279+
#[test]
280+
fn test_extra_ignored_bad_parse() {
281+
let mut testio = getio();
282+
testio.input.set_input_text("1, 2, hello");
283+
let result: (f32, f32) = get_input_from_io("whatever", &mut testio);
284+
assert_eq!(Some("whatever? "), testio.output.get_last_line().as_deref());
285+
assert_eq!(
286+
Some("?EXTRA IGNORED\n"),
287+
testio.output.get_last_line().as_deref()
288+
);
289+
assert_eq!(2.0, result.1);
290+
}
291+
292+
#[test]
293+
fn test_ten_nums() {
294+
let mut testio = getio();
295+
testio.input.set_input_text("1, 2, 3, 4, 5, 6, 7, 8, 9, 10");
296+
let result: (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32) =
297+
get_input_from_io("whatever", &mut testio);
298+
assert_eq!(Some("whatever? "), testio.output.get_last_line().as_deref());
299+
assert_eq!(None, testio.output.get_last_line());
300+
assert_eq!(10.0, result.9);
301+
}
302+
303+
//
304+
// Helpers for test i/o
305+
//
306+
pub struct ReadTestLayer {
307+
next_line: VecDeque<String>,
308+
}
309+
310+
impl ReadTestLayer {
311+
pub fn new() -> Self {
312+
Self {
313+
next_line: VecDeque::new(),
314+
}
315+
}
316+
}
317+
318+
impl TextInput for ReadTestLayer {
319+
fn read(&mut self) -> String {
320+
if let Some(line) = self.next_line.pop_front() {
321+
println!(">> returning {line}");
322+
line
323+
} else {
324+
panic!("Test tried to read too much input");
325+
}
326+
}
327+
328+
fn set_input_text(&mut self, text: &str) {
329+
self.next_line.push_back(text.to_string());
330+
}
331+
}
332+
333+
pub struct WriteTestLayer {
334+
last_items_written: VecDeque<String>,
335+
}
336+
337+
impl WriteTestLayer {
338+
pub fn new() -> Self {
339+
Self {
340+
last_items_written: VecDeque::new(),
341+
}
342+
}
343+
}
344+
345+
impl TextOutput for WriteTestLayer {
346+
fn write(&mut self, to_write: &str) {
347+
self.last_items_written.push_back(to_write.into());
348+
}
349+
350+
fn get_last_line(&mut self) -> Option<String> {
351+
self.last_items_written.pop_front()
352+
}
353+
}
354+
}

0 commit comments

Comments
 (0)