1
use std::fs;
2
use std::io::{self, BufRead, IsTerminal, Read, Write};
3
use std::path::Path;
4

            
5
use clap::Parser;
6
use scripting::nomiscript::{Reader, Value};
7

            
8
use nms::interpreter;
9
mod repl;
10

            
11
#[derive(Parser)]
12
#[command(name = "nms")]
13
#[command(about = "Nomiscript interpreter", long_about = None)]
14
struct Cli {
15
    /// File to evaluate (use - for stdin)
16
    #[arg(value_name = "FILE")]
17
    file: Option<String>,
18

            
19
    /// Evaluate a string expression
20
    #[arg(short, long, value_name = "EXPR")]
21
    eval: Option<String>,
22

            
23
    /// Compile a source file to WASM bytecode
24
    #[arg(long, value_name = "FILE")]
25
    compile: Option<String>,
26

            
27
    /// Load and run a pre-compiled WASM file
28
    #[arg(long, value_name = "FILE")]
29
    load: Option<String>,
30

            
31
    /// Use TUI mode for the REPL
32
    #[arg(short, long)]
33
    tui: bool,
34

            
35
    /// Enable debug-level tracing output
36
    #[arg(long)]
37
    debug: bool,
38
}
39

            
40
fn main() -> anyhow::Result<()> {
41
    let cli = Cli::parse();
42

            
43
    if let Some(source) = cli.compile {
44
        compile_file(&source, cli.debug)?;
45
    } else if let Some(wasm_path) = cli.load {
46
        load_wasm(&wasm_path, cli.debug)?;
47
    } else if let Some(expr_str) = cli.eval {
48
        eval_string(&expr_str, cli.debug)?;
49
    } else if let Some(file) = cli.file {
50
        if file == "-" {
51
            eval_stdin(cli.debug)?;
52
        } else {
53
            eval_file(&file, cli.debug)?;
54
        }
55
    } else if cli.tui {
56
        repl::run()?;
57
    } else {
58
        plain_repl(cli.debug)?;
59
    }
60

            
61
    Ok(())
62
}
63

            
64
fn plain_repl(debug: bool) -> anyhow::Result<()> {
65
    let stdin = io::stdin();
66
    let mut stdout = io::stdout();
67
    let use_color = stdout.is_terminal();
68
    let mut interp = interpreter::Interpreter::new(debug)?;
69
    let mut buffer = String::new();
70

            
71
    loop {
72
        if buffer.is_empty() {
73
            print!("\n> ");
74
        } else {
75
            print!("  ");
76
        }
77
        stdout.flush()?;
78

            
79
        let mut line = String::new();
80
        if stdin.lock().read_line(&mut line)? == 0 {
81
            break;
82
        }
83

            
84
        if buffer.is_empty() && line.trim().is_empty() {
85
            continue;
86
        }
87

            
88
        buffer.push_str(&line);
89

            
90
        if Reader::is_incomplete(&buffer) {
91
            continue;
92
        }
93

            
94
        let input = buffer.trim();
95
        if input.is_empty() {
96
            buffer.clear();
97
            continue;
98
        }
99

            
100
        match interp.eval(input) {
101
            Ok(values) => {
102
                for value in values {
103
                    println!("{}", format_value(&value, use_color));
104
                }
105
            }
106
            Err(e) => eprint!("{}", e.render(use_color)),
107
        }
108
        buffer.clear();
109
    }
110

            
111
    Ok(())
112
}
113

            
114
fn compile_file(path: &str, debug: bool) -> anyhow::Result<()> {
115
    let content = fs::read_to_string(path)?;
116
    let mut interp = interpreter::Interpreter::new(debug)?;
117
    let use_color = io::stderr().is_terminal();
118
    match interp.compile_to_wasm(&content) {
119
        Ok(wasm) => {
120
            let out_path = Path::new(path).with_extension("wasm");
121
            fs::write(&out_path, &wasm)?;
122
            eprintln!("wrote {}", out_path.display());
123
            Ok(())
124
        }
125
        Err(e) => {
126
            eprint!("{}", e.render(use_color));
127
            std::process::exit(1);
128
        }
129
    }
130
}
131

            
132
fn load_wasm(path: &str, debug: bool) -> anyhow::Result<()> {
133
    let wasm = fs::read(path)?;
134
    let interp = interpreter::Interpreter::new(debug)?;
135
    let use_color = io::stderr().is_terminal();
136
    match interp.run_wasm(&wasm) {
137
        Ok(value) => {
138
            let use_color = io::stdout().is_terminal();
139
            println!("{}", format_value(&value, use_color));
140
            Ok(())
141
        }
142
        Err(e) => {
143
            eprint!("{}", e.render(use_color));
144
            std::process::exit(1);
145
        }
146
    }
147
}
148

            
149
fn eval_string(input: &str, debug: bool) -> anyhow::Result<()> {
150
    let mut interp = interpreter::Interpreter::new(debug)?;
151
    print_results(&mut interp, input)
152
}
153

            
154
fn eval_file(path: &str, debug: bool) -> anyhow::Result<()> {
155
    let content = fs::read_to_string(path)?;
156
    let mut interp = interpreter::Interpreter::new(debug)?;
157
    print_results(&mut interp, &content)
158
}
159

            
160
fn eval_stdin(debug: bool) -> anyhow::Result<()> {
161
    let mut content = String::new();
162
    io::stdin().read_to_string(&mut content)?;
163
    let mut interp = interpreter::Interpreter::new(debug)?;
164
    print_results(&mut interp, &content)
165
}
166

            
167
fn print_results(interp: &mut interpreter::Interpreter, input: &str) -> anyhow::Result<()> {
168
    let use_color = io::stderr().is_terminal();
169
    match interp.eval(input) {
170
        Ok(values) => {
171
            let use_color = io::stdout().is_terminal();
172
            for value in values {
173
                println!("{}", format_value(&value, use_color));
174
            }
175
        }
176
        Err(e) => {
177
            eprint!("{}", e.render(use_color));
178
            std::process::exit(1);
179
        }
180
    }
181
    Ok(())
182
}
183

            
184
fn format_value(value: &Value, use_color: bool) -> String {
185
    match value {
186
        Value::Nil | Value::Bool(false) => {
187
            if use_color {
188
                "\x1b[90mNIL\x1b[0m".to_string()
189
            } else {
190
                "NIL".to_string()
191
            }
192
        }
193
        Value::Bool(true) => {
194
            if use_color {
195
                "\x1b[32m#T\x1b[0m".to_string()
196
            } else {
197
                "#T".to_string()
198
            }
199
        }
200
        Value::Number(n) => {
201
            let text = if *n.denom() == 1 {
202
                n.numer().to_string()
203
            } else {
204
                format!("{}/{}", n.numer(), n.denom())
205
            };
206
            if use_color {
207
                format!("\x1b[36m{text}\x1b[0m")
208
            } else {
209
                text
210
            }
211
        }
212
        Value::String(s) => {
213
            if use_color {
214
                format!("\x1b[33m{s}\x1b[0m")
215
            } else {
216
                s.clone()
217
            }
218
        }
219
        Value::Symbol(s) => {
220
            if use_color {
221
                format!("\x1b[35m{s}\x1b[0m")
222
            } else {
223
                s.clone()
224
            }
225
        }
226
        Value::Pair(_) => "<pair>".to_string(),
227
        Value::Vector(_) => "<vector>".to_string(),
228
        Value::Closure(_) => "<closure>".to_string(),
229
        Value::Struct { name, fields } => {
230
            if use_color {
231
                format!("\x1b[94m#{name}({} fields)\x1b[0m", fields.len())
232
            } else {
233
                format!("#{name}({} fields)", fields.len())
234
            }
235
        }
236
    }
237
}