openbirch-rs/src/cli-repl.rs
2025-02-21 20:42:56 +01:00

219 lines
7.4 KiB
Rust

use std::io::{self, StdoutLock, Write, stdout};
use std::rc::Rc;
use libopenbirch::environment::Environment;
use libopenbirch::node::constant::Constant;
use libopenbirch::node::empty::Empty;
use libopenbirch::node::{Node, NodeEnum};
use libopenbirch::parser::{Lexer, Parser, ParserError};
use malachite::base::num::basic::traits::Zero;
use malachite::rational::Rational;
#[cfg(feature = "async")]
use termion::AsyncReader;
use termion::color;
use termion::event::Key;
use termion::input::TermReadEventsAndRaw;
use termion::raw::{IntoRawMode, RawTerminal};
pub struct Input {
#[cfg(not(feature = "async"))]
stdin: std::io::Stdin,
stdout: RawTerminal<StdoutLock<'static>>,
buffer: String,
current_char: usize,
}
impl Input {
pub fn new() -> Self {
#[cfg(not(feature = "async"))]
let stdin = std::io::stdin();
let stdout = stdout().lock().into_raw_mode().unwrap();
Self {
stdin,
stdout,
buffer: "".into(),
current_char: 0,
}
}
#[inline]
fn draw_line(
buffer: &String,
stdout: &mut RawTerminal<StdoutLock<'static>>,
current_char: usize,
) {
print!("\r{}> {}", termion::clear::CurrentLine, buffer,);
let left_diff = buffer.len() - current_char;
if left_diff > 0 {
print!("{}", termion::cursor::Left(left_diff.try_into().unwrap()));
}
let _ = stdout.flush();
}
/// Gets input from `io::stdin`.
/// Returns `None` when the user presses <C-c> or types `:quit`
pub fn get(&mut self) -> Result<Option<String>, io::Error> {
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
loop {
for b in (&mut self.stdin).events_and_raw() {
let (event, _slice) = b?;
match event {
termion::event::Event::Key(key) => match key {
Key::Char(char) if char == '\n' => {
if &self.buffer == ":quit" {
println!("\r");
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
let r = self.buffer.clone();
self.buffer.clear();
self.current_char = 0;
print!("\n\r");
return Ok(Some(r));
}
Key::Backspace => {
if self.current_char == 0 {
continue;
}
self.current_char -= 1;
self.buffer.remove(self.current_char);
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
let _ = self.stdout.flush();
}
Key::Delete => {
if self.current_char == self.buffer.len() {
continue;
}
self.buffer.remove(self.current_char);
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
let _ = self.stdout.flush();
}
Key::Left => {
if self.current_char == 0 {
continue;
}
self.current_char -= 1;
print!("{}", termion::cursor::Left(1));
let _ = self.stdout.flush();
}
Key::Right => {
if self.current_char == self.buffer.len() {
continue;
}
self.current_char += 1;
print!("{}", termion::cursor::Right(1));
let _ = self.stdout.flush();
}
Key::Char(char) => {
self.buffer.insert(self.current_char, char);
self.current_char += 1;
Self::draw_line(&self.buffer, &mut self.stdout, self.current_char);
}
Key::Ctrl(c) if c == 'c' => {
println!("\r");
return Err(io::Error::from(io::ErrorKind::Interrupted));
}
_ => {
#[cfg(debug_assertions)]
return Err(io::Error::other(format!(
"Key {key:?} is not implemented"
)));
}
},
termion::event::Event::Mouse(mouse_event) => {}
termion::event::Event::Unsupported(items) => {}
}
}
}
}
pub fn disable_raw(&mut self) -> io::Result<()> {
self.stdout.suspend_raw_mode()
}
pub fn enable_raw(&mut self) -> io::Result<()> {
self.stdout.activate_raw_mode()
}
}
fn print_err(i: usize, len: usize, exp: String) {
println!(
"\r{}{}{}",
" ".repeat(i + 3 - len),
color::Fg(color::Yellow),
"^".repeat(len)
);
println!(
"\r{}{}{}",
color::Fg(color::Red),
exp,
color::Fg(color::Reset)
);
}
fn print(args: &Vec<Rc<NodeEnum>>, env: &mut Environment) -> Result<Rc<NodeEnum>, String> {
for expr in args {
println!("\r{}", expr.as_string(Some(env)));
}
Ok(Empty::new(""))
}
fn main() -> Result<(), io::Error> {
let mut input = Input::new();
let mut env = Environment::new();
env.define_native_function("print", print);
while let Some(source) = input.get()? {
input.disable_raw()?;
let mut lexer = Lexer::new(&source);
let tokens_result = lexer.lex();
if tokens_result.is_err() {
match tokens_result.err().unwrap() {
libopenbirch::parser::LexerError::UnexpectedChar(i, exp) => print_err(i, 1, exp),
}
continue;
}
let tokens = tokens_result.unwrap();
let mut parser = Parser::new(tokens, &mut env);
let nodes = match parser.parse() {
Ok(nodes) => nodes,
Err(err) => {
match err {
ParserError::UnexpectedEndOfTokens(exp) => print_err(source.len(), 1, exp),
ParserError::UnexpectedToken(i, len, exp)
| ParserError::Unimplemented(i, len, exp) => print_err(i, len, exp),
ParserError::UnexpectedNode(i, exp) => print_err(i, 1, exp),
}
continue;
}
};
print!("{}", color::Fg(color::Blue));
for node in nodes {
let evaluated = node.evaluate(&mut env);
match evaluated {
Ok(result) => println!("\r\t{}", result.as_string(Some(&env))),
Err(exp) => print_err(0, 1, exp),
}
}
input.enable_raw()?;
print!("{}", color::Fg(color::Reset));
// println!("\t{}{source}{}", termion::color::Fg(termion::color::Blue), termion::color::Fg(termion::color::Reset));
}
Ok(())
}