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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
//! Interactive Read-Eval-Print-Loop

#[cfg(feature = "readline")]
use rustyline::{Editor, error::ReadlineError};
#[cfg(feature = "readline")]
use std::{process, fs};
use crate::Spaik;
use crate::r8vm::OutStream;
use crate::nkgc::PV;
use crate::fmt::LispFmt;
use std::path::Path;
use crate::stylize::Stylize;

#[allow(unused)]
fn make_intro() -> String {
    format!("{read} {arrow} {eval} {arrow} {print} {arrow} {loop}
 ┗━━━━━━━━━━━━━━━━━━━━━━┛\n",
            read="read".style_ret(),
            eval="eval".style_ret(),
            print="print".style_ret(),
            loop="loop".style_ret(),
            arrow="➟"
    )
}

macro_rules! vmprint {
    ($vm:expr, $($fmt:expr),+) => {
        $vm.print_fmt(format_args!($($fmt),+)).unwrap()
    };
}

macro_rules! vmprintln {
    ($vm:expr, $($fmt:expr),+) => {
        $vm.print_fmt(format_args!($($fmt),+)).unwrap();
        $vm.println(&"").unwrap();
    };
}

type EBResult<T> = Result<T, Box<dyn std::error::Error>>;

pub trait LineInput {
    fn readline();
    fn save_history<P: AsRef<Path> + ?Sized>(&self, path: &P) -> EBResult<()>;
    fn load_history<P: AsRef<Path> + ?Sized>(&mut self, path: &P) -> EBResult<()>;
    fn add_history_entry<S: AsRef<str> + Into<String>>(&mut self, line: S) -> bool;
}

pub struct REPL {
    vm: Spaik,
    exit_status: Option<i32>,
}

impl REPL {
    pub fn new(out_override: Option<Box<dyn OutStream>>) -> REPL {
        let mut vm = Spaik::new();
        if let Some(out) = out_override {
            vm.vm.set_stdout(out);
        }
        REPL {
            vm,
            exit_status: None,
        }
    }

    pub fn eval(&mut self, code: impl AsRef<str>) -> Result<Option<String>, String> {
        let res = self.vm.vm.eval(code.as_ref());
        self.vm.vm.flush_output().map_err(|e| e.to_string())?;
        match res {
            Ok(PV::Nil) => Ok(None),
            Ok(res) => Ok(Some(res.lisp_to_string())),
            Err(e) => Err(e.to_string()),
        }
    }

    pub fn exit_status(&self) -> Option<i32> {
        self.exit_status
    }

    pub fn print_intro(&mut self) {
        vmprint!(self.vm.vm, "{}", make_intro());
    }

    #[cfg(not(feature = "readline"))]
    pub fn readline_repl(&mut self) -> ! {
        unimplemented!("readline not available")
    }

    #[cfg(feature = "readline")]
    pub fn readline_repl(&mut self) -> ! {
        let mut spaik_dir = dirs::data_local_dir().unwrap();
        spaik_dir.push("spaik");
        if let Err(e) = fs::create_dir_all(spaik_dir) {
            vmprintln!(self.vm.vm, "Error: {}", e);
            process::exit(1);
        }
        let mut hist_path = dirs::data_local_dir().unwrap();
        hist_path.push("spaik");
        hist_path.push("history");
        self.vm.add_load_path(".");
        self.vm.add_load_path("lisp");
        let mut config_dir = dirs::config_dir().unwrap();
        config_dir.push("spaik");
        self.vm.add_load_path(config_dir.to_str().unwrap());
        match self.vm.load("init") {
            Ok(_) => (),
            Err(e) => {
                vmprintln!(self.vm.vm, "{}", e);
            }
        }
        let mut rl = Editor::<()>::new();
        if rl.load_history(&hist_path).is_err() {
            vmprintln!(self.vm.vm, "{} {}",
                       "Warning: No history log, will be created in".style_warning(),
                       hist_path.to_string_lossy().style_info());
        }
        self.print_intro();
        let prompt = "λ> ".style_prompt().to_string();
        while self.exit_status.is_none() {
            let readline = rl.readline(&prompt);
            match readline {
                Ok(line) => {
                    rl.add_history_entry(line.as_str());
                    match self.eval(line.as_str()) {
                        Ok(Some(s)) => { vmprintln!(self.vm.vm, "{s}"); },
                        Err(e) => { vmprintln!(self.vm.vm, "{e}"); },
                        _ => ()
                    }
                },
                Err(ReadlineError::Interrupted) | Err(ReadlineError::Eof) => {
                    self.exit_status = Some(0)
                },
                Err(err) => {
                    vmprintln!(self.vm.vm, "Read Error: {:?}", err);
                    self.exit_status = Some(1)
                }
            }
        }
        if let Err(e) = rl.save_history(&hist_path) {
            vmprintln!(self.vm.vm, "Error: {}", e);
        }
        process::exit(self.exit_status.unwrap_or_default())
    }
}