mittens_engine/scripting/repl/
frontend.rs1use std::io::{BufRead, Write};
2use std::sync::mpsc;
3
4pub struct MeowMeowReplFrontend {
5 rx: mpsc::Receiver<String>,
6}
7
8impl MeowMeowReplFrontend {
9 pub fn new() -> Result<Self, &'static str> {
10 crate::engine::repl::claim_stdin()?;
11 let (tx, rx) = mpsc::channel();
12 std::thread::spawn(move || {
13 let stdin = std::io::stdin();
14 let mut buffered = String::new();
15 loop {
16 print!("{}", if buffered.is_empty() { "mms> " } else { "... " });
17 let _ = std::io::stdout().flush();
18 let mut line = String::new();
19 match stdin.lock().read_line(&mut line) {
20 Ok(0) | Err(_) => break,
21 Ok(_) => {}
22 }
23 buffered.push_str(&line);
24 if input_complete(&buffered) {
25 if tx.send(std::mem::take(&mut buffered)).is_err() {
26 break;
27 }
28 }
29 }
30 crate::engine::repl::release_stdin();
31 });
32 Ok(Self { rx })
33 }
34
35 pub fn try_recv_all(&self) -> Vec<String> {
36 let mut out = Vec::new();
37 while let Ok(source) = self.rx.try_recv() {
38 out.push(source);
39 }
40 out
41 }
42}
43
44pub(crate) fn input_complete(source: &str) -> bool {
45 let mut stack = Vec::new();
46 let mut quote = None;
47 let mut escaped = false;
48 let mut line_comment = false;
49 let chars = source.chars().collect::<Vec<_>>();
50 let mut index = 0;
51 while index < chars.len() {
52 let ch = chars[index];
53 if line_comment {
54 if ch == '\n' {
55 line_comment = false;
56 }
57 index += 1;
58 continue;
59 }
60 if let Some(q) = quote {
61 if escaped {
62 escaped = false;
63 } else if ch == '\\' {
64 escaped = true;
65 } else if ch == q {
66 quote = None;
67 }
68 index += 1;
69 continue;
70 }
71 if ch == '/' && chars.get(index + 1) == Some(&'/') {
72 line_comment = true;
73 index += 2;
74 continue;
75 }
76 match ch {
77 '"' | '\'' => quote = Some(ch),
78 '(' | '[' | '{' => stack.push(ch),
79 ')' => {
80 if stack.last() == Some(&'(') {
81 stack.pop();
82 }
83 }
84 ']' => {
85 if stack.last() == Some(&'[') {
86 stack.pop();
87 }
88 }
89 '}' => {
90 if stack.last() == Some(&'{') {
91 stack.pop();
92 }
93 }
94 _ => {}
95 }
96 index += 1;
97 }
98 quote.is_none() && stack.is_empty()
99}
100
101#[cfg(test)]
102mod tests {
103 use super::input_complete;
104 #[test]
105 fn balances_multiline_and_quoted_delimiters() {
106 assert!(!input_complete("T {\n Text {"));
107 assert!(input_complete("T {\n Text { \"}\" }\n}"));
108 assert!(!input_complete("\"escaped \\\" quote"));
109 assert!(input_complete("query(world, \"#x > Text\")"));
110 assert!(input_complete("// an unmatched { in a comment\n42"));
111 }
112}