summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: dd4d947d0b2d1abca034ee8cf4c60247c8c14c9e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#[cfg(test)]
pub mod tests;

pub mod lib;

use rustyline::error::ReadlineError;
use rustyline::Editor;

use lib::environment::Environment;

const EXIT_STRINGS: &[&str; 3] = &[",q", "exit", "quit"];

fn read<T: rustyline::Helper>(rl: &mut Editor<T>) -> Result<String, String> {
    let mut readline = rl.readline(">> ");
    let mut full_line = String::new();

    fn count_parens(s: &str) -> Result<bool, ()> {
        let mut lcount = 0;
        let mut rcount = 0;
        for c in s.chars() {
            match c {
                '(' => lcount += 1,
                ')' => rcount += 1,
                _ => {}
            }
            if rcount > lcount {
                return Err(())
            }
        }
        Ok(lcount == rcount)
    }

    let mut loop_count = 0;
    loop {
        match readline {
            Ok(line) => {
                full_line.push_str(&line);
                match count_parens(&full_line) {
                    Ok(true) => return Ok(full_line),
                    Ok(false) => readline = rl.readline("... "),
                    Err(_) => return Err("unexpected \")\"".to_string())
                }
            },
            Err(ReadlineError::Interrupted) => {
                return Err("user interrupt".to_string())
            },
            Err(ReadlineError::Eof) => {
                if loop_count > 0 {
                    return Err("end of file".to_string());
                } else {
                    return Ok(EXIT_STRINGS[0].to_string());
                }
            },
            Err(err) => {
                return Err(format!("{:?}", err));
            }
        }
        loop_count += 1
    }
}

fn means_exit(input: &str) -> bool {
    EXIT_STRINGS.iter().any(|&s| s == input)
}

fn eval(env: &mut Environment, input: &str) -> String {
    let sexp = match lib::tokenize::tokenize(input) {
        Ok(x) => x,
        Err(f) => return f
    };

    let res = lib::eval::eval(&sexp, env);
    match res {
        Ok(x) => format!("{}", x),
        Err(f) => format!("Error: {}", f)
    }
}

fn main() {
    let mut env = Environment::new();
    let hist_file = "history.txt";

    // `()` can be used when no completer is required
    let mut rl = Editor::<()>::new();
    if rl.load_history(hist_file).is_err() {
        println!("No previous history.");
    }
    loop {
        let input_line = read(&mut rl);
        match input_line {
            Err(e) => {
                println!("Error: {}", e);
                continue;
            }
            Ok(expr) => {
                if means_exit(&expr) {
                    break;
                }
                rl.add_history_entry(expr.as_str());
                println!("{}", eval(&mut env, &expr));
            }
        };

    }
    rl.save_history(hist_file).unwrap();
}