Skip to content

Commit 084c1fe

Browse files
committed
Refactor repl as proper read / eval / print / loop (and fixed
multiline errors)
1 parent f1f7ee4 commit 084c1fe

File tree

4 files changed

+106
-72
lines changed

4 files changed

+106
-72
lines changed

src/environment.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::namespace::{Namespace, Namespaces};
22
use crate::repl;
3+
use crate::repl::Repl;
34
use crate::rust_core;
45
use crate::symbol::Symbol;
56
use crate::value::{ToValue, Value};
@@ -121,7 +122,8 @@ impl Environment {
121122
//
122123
// Read in clojure.core
123124
//
124-
let _ = repl::try_eval_file(&environment, "./src/clojure/core.clj");
125+
// @TODO its time for a RT (runtime), which environment seems to be becoming
126+
let _ = Repl::new(Rc::clone(&environment)).try_eval_file("./src/clojure/core.clj");
125127

126128
environment
127129
}

src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ fn main() {
2020
//
2121
// Start repl
2222
//
23-
repl::repl();
23+
let repl = repl::Repl::default();
24+
repl.run();
2425
}

src/reader.rs

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
//! not; since this is about being a 'free-er' Clojure, especially since it can't compete with it in raw
99
//! power, neither speed or ecosystem, it might be worth it to leave in reader macros.
1010
11-
use nom::{branch::alt, bytes::complete::tag, map, sequence::preceded, take_until, terminated, IResult, Needed};
11+
use nom::{
12+
branch::alt, bytes::complete::tag, map, sequence::preceded, take_until, terminated,
13+
Err::Incomplete, IResult, Needed,
14+
};
1215

1316
use crate::maps::MapEntry;
1417
use crate::persistent_list::ToPersistentList;
@@ -18,6 +21,7 @@ use crate::symbol::Symbol;
1821
use crate::value::{ToValue, Value};
1922
use std::rc::Rc;
2023

24+
use std::io::BufRead;
2125
//
2226
// Note; the difference between ours 'parsers'
2327
// identifier_parser
@@ -317,6 +321,37 @@ pub fn debug_try_read(input: &str) -> IResult<&str, Value> {
317321
reading
318322
}
319323

324+
// This is the high level read function that Clojure RS wraps
325+
pub fn read<R: BufRead>(reader: &mut R) -> Value {
326+
// This is a buffer that will accumulate if a read requires more
327+
// text to make sense, such as trying to read (+ 1
328+
let mut input_buffer = String::new();
329+
330+
// Ask for a line from the reader, try to read, and if unable (because we need more text),
331+
// loop over and ask for more lines, accumulating them in input_buffer until we can read
332+
loop {
333+
let maybe_line = reader.by_ref().lines().next();
334+
match maybe_line {
335+
Some(Err(e)) => return Value::Condition(format!("Reader error: {}", e)),
336+
Some(Ok(line)) => input_buffer.push_str(&line),
337+
None => return Value::Condition(format!("Tried to read empty stream; unexpected EOF")),
338+
}
339+
340+
let line_read = try_read(&input_buffer);
341+
match line_read {
342+
Ok((_, value)) => return value,
343+
// Continue accumulating more input
344+
Err(Incomplete(_)) => continue,
345+
Err(err) => {
346+
return Value::Condition(format!(
347+
"Reader Error: could not read next form; {:?}",
348+
err
349+
))
350+
}
351+
}
352+
}
353+
}
354+
320355
/// Consumes any whitespace from input, if there is any.
321356
/// Always succeeds.
322357
///

src/repl.rs

Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::fs::File;
22
use std::io;
33
use std::io::BufRead;
44
use std::io::BufReader;
5+
use std::io::Read;
56
use std::io::Write;
67

78
use crate::environment::Environment;
@@ -11,86 +12,81 @@ use crate::value::Value;
1112
use std::rc::Rc;
1213

1314
use nom::Err::Incomplete;
15+
use nom::IResult;
1416
use nom::Needed::Size;
1517

16-
//
17-
// Will possibly just add this to our environment, or turn this into a parallel of clojure.lang.RT
18-
//
19-
pub fn try_eval_file(environment: &Rc<Environment>, filepath: &str) -> Result<(), io::Error> {
20-
let core = File::open(filepath)?;
21-
let reader = BufReader::new(core);
18+
pub struct Repl {
19+
environment: Rc<Environment>,
20+
}
21+
impl Repl {
22+
pub fn new(environment: Rc<Environment>) -> Repl {
23+
Repl { environment }
24+
}
2225

23-
let mut input_buffer = String::new();
26+
// @TODO reconsider eval's signature; since Value wraps all evaluables, it might make more sense
27+
// to frame eval as "environment.eval(value)", and then likewise define a
28+
// 'repl.eval(value)', rather than 'value.eval(environment)'
29+
pub fn eval(&self, value: &Value) -> Value {
30+
value.eval(Rc::clone(&self.environment))
31+
}
2432

25-
for line in reader.lines() {
26-
let line = line?;
27-
input_buffer.push_str(&line);
28-
let mut remaining_input = input_buffer.as_str();
29-
loop {
30-
let next_read_parse = reader::try_read(remaining_input);
31-
match next_read_parse {
32-
Ok((_remaining_input, value)) => {
33-
//print!("{} ",value.eval(Rc::clone(&environment)).to_string_explicit());
34-
value.eval(Rc::clone(&environment));
35-
remaining_input = _remaining_input;
36-
}
37-
Err(Incomplete(Size(1))) => {
38-
break;
39-
}
40-
err => {
41-
println!(
42-
"Error evaluating file {}; {}",
43-
filepath,
44-
Value::Condition(format!("Reader Error: {:?}", err))
45-
);
46-
input_buffer.clear();
47-
// remaining_input = "";
48-
break;
49-
}
50-
}
51-
}
33+
// Just wraps reader's read
34+
pub fn read<R: BufRead>(reader: &mut R) -> Value {
35+
reader::read(reader)
5236
}
37+
pub fn run(&self) {
38+
let stdin = io::stdin();
39+
let mut stdin_reader = stdin.lock();
5340

54-
Ok(())
55-
}
56-
// @TODO eventually, this will likely be implemented purely in Clojure
57-
/// Starts an entirely new session of Clojure RS
58-
pub fn repl() {
59-
println!("Clojure RS 0.0.1");
41+
loop {
42+
print!("user=> ");
43+
let _ = io::stdout().flush();
6044

61-
let environment = Environment::clojure_core_environment();
62-
let stdin = io::stdin();
45+
// Read
46+
let mut next = Repl::read(&mut stdin_reader);
47+
// Eval
48+
let evaled_next = self.eval(&next);
49+
// Print
50+
println!("{}", evaled_next);
51+
// Loop
52+
}
53+
}
54+
//
55+
// Will possibly just add this to our environment, or turn this into a parallel of clojure.lang.RT
56+
//
57+
/// Reads the code in a file sequentially and evaluates the result
58+
pub fn try_eval_file(&self, filepath: &str) -> Result<Value, std::io::Error> {
59+
let core = File::open(filepath)?;
60+
let mut reader = BufReader::new(core);
6361

64-
print!("user=> ");
65-
let _ = io::stdout().flush();
66-
let mut input_buffer = String::new();
67-
for line in stdin.lock().lines() {
68-
let line = line.unwrap();
69-
input_buffer.push_str(&line);
70-
let mut remaining_input = input_buffer.as_str();
62+
let mut last_val = Repl::read(&mut reader);
7163
loop {
72-
let next_read_parse = reader::try_read(remaining_input);
73-
match next_read_parse {
74-
Ok((_remaining_input, value)) => {
75-
print!(
76-
"{} ",
77-
value.eval(Rc::clone(&environment)).to_string_explicit()
78-
);
79-
remaining_input = _remaining_input;
80-
}
81-
Err(Incomplete(_)) => {
82-
break;
83-
}
84-
err => {
85-
print!("{}", Value::Condition(format!("Reader Error: {:?}", err)));
86-
input_buffer.clear();
87-
break;
64+
// @TODO this is hardcoded until we refactor Conditions to have keys, so that
65+
// we can properly identify them
66+
// @FIXME
67+
if let Value::Condition(cond) = &last_val {
68+
if cond != "Tried to read empty stream; unexpected EOF" {
69+
println!("Error reading file: {}", cond);
8870
}
71+
72+
return Ok(last_val);
73+
}
74+
75+
let evaled_last_val = self.eval(&last_val);
76+
77+
if let Value::Condition(cond) = evaled_last_val {
78+
println!("{}", cond);
8979
}
80+
81+
last_val = Repl::read(&mut reader);
82+
}
83+
}
84+
}
85+
86+
impl Default for Repl {
87+
fn default() -> Repl {
88+
Repl {
89+
environment: Environment::clojure_core_environment(),
9090
}
91-
input_buffer.clear();
92-
println!();
93-
print!("user=> ");
94-
let _ = io::stdout().flush();
9591
}
9692
}

0 commit comments

Comments
 (0)