Skip to main content

rusterm/
lib.rs

1/*
2 * Copyright (c) 2021 Thomas Duckworth <tduck973564@gmail.com>.
3 * This file is under the `rusterm` project, which is licensed under the GNU GPL v3.0 which you can read here: https://www.gnu.org/licenses/gpl-3.0.en.html
4 */
5#[warn(missing_docs)]
6use doc_comment::doc_comment;
7doc_comment!(include_str!("../README.md"));
8
9use colored::Colorize as Colourise; // CORRECT ENGLISH!!!
10use rustyline::{error::ReadlineError, Cmd, Editor, KeyEvent};
11use std::collections::HashMap;
12use std::error::Error;
13
14pub mod error;
15pub mod lex;
16pub mod prelude;
17mod scan;
18mod tests;
19
20/// The type for Commands passed into the Console. All functions passed in must be of type `fn(brc::lex::Arguments) -> Result<(), brc::error::Error>`
21pub type Command = fn(lex::Arguments) -> Result<(), error::Error>;
22
23/// The main constructor owning the console. It contains the command table and the prompt string.
24pub struct Console {
25    /// Used for aliasing function pointers with names to be looked up later when their alias is typed in to the prompt.
26    pub command_table: HashMap<String, Command>,
27    /// The characters that come before input, like `>> ` or `Console -> `.
28    pub prompt: String,
29}
30
31impl Console {
32    /// Creates a new instance of the Console struct. It takes two arguments, the command table and the prompt string.
33    pub fn new(command_table: HashMap<String, Command>, prompt: &str) -> Console {
34        Console {
35            command_table,
36            prompt: prompt.to_owned(),
37        }
38    }
39    /// Runs the Read, Execute and Print Loop. It displays a prompt where the user can input the command they want, to then be read and parsed.
40    pub fn run_repl(&self) {
41        loop {
42            let mut rl = Editor::<()>::new();
43
44            rl.bind_sequence(KeyEvent::ctrl('A'), Cmd::HistorySearchForward);
45            rl.bind_sequence(KeyEvent::ctrl('B'), Cmd::HistorySearchBackward);
46
47            if rl.load_history(".rusterm_history").is_err() {
48                eprintln!("Could not load history file.");
49            }
50
51            let input = match rl.readline(&self.prompt) {
52                Ok(x) if x.is_empty() => continue,
53                Ok(x) if x == *"exit" => break,
54                Ok(x) => {
55                    rl.add_history_entry(x.clone());
56                    x
57                }
58                Err(ReadlineError::Interrupted) => {
59                    eprintln!("^C");
60                    break;
61                }
62                Err(ReadlineError::Eof) => {
63                    eprintln!("^D");
64                    break;
65                }
66                Err(x) => {
67                    eprintln!(
68                        "{}{} {}",
69                        "Error while reading input".red().bold(),
70                        ":".bold(),
71                        x
72                    );
73                    break;
74                }
75            };
76            if let Err(x) = self.parse(input) {
77                eprintln!("{}", x);
78            }
79            if rl.append_history(".rusterm_history").is_err() {
80                eprintln!("Could not append to history file.");
81            }
82        }
83    }
84    fn parse(&self, input: String) -> Result<(), Box<dyn Error>> {
85        let mut scanned_input = scan::scan(input);
86        let function_name = scanned_input.get(0).unwrap_or(&"".to_string()).clone();
87        if !function_name.is_empty() {
88            scanned_input.remove(0);
89        }
90        let lexed_input = lex::lex(scanned_input);
91        let function = match function_name {
92            x if x == *"help" => {
93                self.help();
94                return Ok(());
95            }
96            _ => self
97                .command_table
98                .get(&function_name)
99                .ok_or(error::Error::NoSuchCommand)?,
100        };
101        if let Err(x) = function(lexed_input) {
102            println!("{}", x)
103        }
104        Ok(())
105    }
106
107    fn help(&self) {
108        println!("{}", "List of commands:".bold());
109        for name in self.command_table.keys() {
110            println!("{}", name);
111        }
112        println!("help");
113        println!("exit")
114    }
115}