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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
|
#[macro_use]
extern crate pest_derive;
#[macro_use]
extern crate pest;
mod ast;
mod builtins;
mod lparser;
mod runtime;
#[cfg(test)]
mod test;
#[macro_export]
macro_rules! extract_enum_value {
($value:expr, $pattern:pat => $extracted_value:expr) => {
match $value {
$pattern => $extracted_value,
_ => panic!("extract_enum_value: Pattern doesn't match!"),
}
};
}
const REPL_HISTORY_FILENAME: &str = "repl_history.txt";
fn main() -> Result<(), String> {
use ast::Program;
use pest::Parser;
use std::env;
use std::fs;
let mut args = env::args();
let command = args.next().unwrap();
let subcommand_result = args.next();
if subcommand_result.is_none() {
println!("usage: {} run FILE", command.as_str());
println!(" {} repl", command.as_str());
return Ok(());
}
let subcommand = subcommand_result.unwrap();
match subcommand.as_str() {
"run" => {
let script_path = args.next().ok_or("[run] no script file specified")?;
let source = fs::read_to_string(script_path).or(Err("[run] cannot read file"))?;
let parse_tree_result = lparser::LParser::parse(lparser::Rule::program, &source);
if parse_tree_result.is_err() {
println!("{}", parse_tree_result.unwrap_err());
return Err("[run] failed to parse".to_owned());
}
let mut parse_tree = parse_tree_result.unwrap();
let syntax_tree: Program = ast::from_parse_tree(&mut parse_tree);
let process_result = runtime::process(&syntax_tree, None);
if process_result.is_err() {
println!("{}", process_result.unwrap_err());
return Err("[run] failed to run".to_owned());
}
Ok(())
}
"repl" => {
use runtime::*;
use rustyline::error::ReadlineError;
use rustyline::Editor;
use std::collections::HashMap;
use std::rc::Rc;
println!("Tunk REPL");
println!("\nCommands:");
println!("#exit exit REPL");
println!("#symbols display symbol table\n");
let mut rl = Editor::<()>::new();
if rl.load_history(REPL_HISTORY_FILENAME).is_err() {
println!(
"[repl] no history file found, will store REPL history in {}",
REPL_HISTORY_FILENAME
);
}
let mut symbol_table: HashMap<String, Rc<Term>> = HashMap::new();
let mut line: String = "".to_owned();
let mut is_appending = false;
loop {
let readline = if is_appending {
line.push('\n');
rl.readline("| ")
} else {
line.clear();
rl.readline("> ")
};
match readline {
Ok(raw_line) => {
line.push_str(raw_line.trim());
if line.starts_with("#exit") {
rl.add_history_entry(line.as_str());
rl.save_history(REPL_HISTORY_FILENAME).unwrap();
break;
} else if line.starts_with("#symbols") {
if symbol_table.is_empty() {
println!("[repl] symbol table is empty");
}
for (symbol, expr) in &symbol_table {
println!("{}:\n{}\n", symbol, *expr);
}
rl.add_history_entry(line.as_str());
rl.save_history(REPL_HISTORY_FILENAME).unwrap();
continue;
} else if !line.ends_with(';') && !line.is_empty() && !line.starts_with('#')
{
is_appending = true;
continue;
} else {
is_appending = false;
}
rl.add_history_entry(line.as_str());
// Regular execution
let parse_tree_result =
lparser::LParser::parse(lparser::Rule::program, &line);
if let Err(err) = parse_tree_result {
println!("{}\n", err);
rl.save_history(REPL_HISTORY_FILENAME).unwrap();
continue;
}
let mut parse_tree = parse_tree_result.unwrap();
let syntax_tree: Program = ast::from_parse_tree(&mut parse_tree);
let process_result =
runtime::process(&syntax_tree, Some(&mut symbol_table));
if let Err(err) = process_result {
println!("{}\n", err);
}
rl.save_history(REPL_HISTORY_FILENAME).unwrap();
}
Err(ReadlineError::Interrupted) => {
println!("CTRL-C");
break;
}
Err(ReadlineError::Eof) => {
println!("CTRL-D");
break;
}
Err(err) => {
println!("{:?}", err);
break;
}
};
}
Ok(())
}
_ => Err("invalid subcommand".to_owned()),
}
}
|