diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..313e832 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,100 @@ +use std::io; +use std::io::Write; +//use std::io::Read; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; + +use termcolor::{ + Color, + ColorChoice, + ColorSpec, + StandardStream, + WriteColor +}; + +pub mod tokenize; + +const PROMPT_PREFIX: &str = "==> "; + +/// Show a prompt and save trimmed input to `input`. +/// +/// # Arguments: +/// +/// * `stdout`: Where we should write the prompt +/// * `input`: Where we should save user input +/// +/// # Example usage: +/// ``` +/// let mut input = String::new(); +/// prompt(&mut stdout, &mut input)?; +/// ``` +fn prompt( + stdout: &mut StandardStream, + input: &mut String +) -> Result<(), std::io::Error> { + + // Print colored prompt prefix + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?; + write!(*stdout, "{PROMPT_PREFIX}")?; + stdout.reset()?; // reset colors + stdout.flush()?; // flush, we didn't print a full line yet. + + // Ask for input + io::stdin().read_line(input)?; + + // If this input doesn't end with a newline, + // the user terminated this prompt with ctrl-d. + // Add a newline to keep spacing consistent, + // and clear the input. + if match input.chars().last() { + Some(val) => val != '\n', + None => true + } { + write!(*stdout, "\n")?; + input.clear(); + } else { + (*input) = input.trim().to_string(); + } + + Ok(()) +} + +fn main() -> Result<(), std::io::Error> { + + let mut stdout = StandardStream::stdout(ColorChoice::Always); + + let term = Arc::new(AtomicBool::new(false)); + signal_hook::flag::register(signal_hook::consts::SIGINT, Arc::clone(&term))?; + while !term.load(Ordering::Relaxed) { + let mut input = String::with_capacity(64); + prompt(&mut stdout, &mut input).expect("Could not show prompt"); + let input = input; + + // Ignore empty input + if input == "" { + stdout.flush()?; + continue; + } + + // Tokenize input. + // Fail if we encounter invalid characters. + let tokens = match tokenize::tokenize(&input) { + Ok(v) => v, + Err(_) => { + continue; + } + }; + + + stdout.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; + write!(stdout, "\n => ")?; + stdout.reset()?; + write!(stdout, "Got {input}\n\n\n")?; + + writeln!(stdout, "Tokenized: {tokens:#?}")?; + + } + + writeln!(stdout, "Exiting.")?; + Ok(()) +} \ No newline at end of file diff --git a/src/tokenize.rs b/src/tokenize.rs index 944d931..aa96446 100644 --- a/src/tokenize.rs +++ b/src/tokenize.rs @@ -1,11 +1,11 @@ #[derive(Debug)] +#[derive(Clone)] pub enum Token { Negative, - StartGroup, - EndGroup, Number(String), Operator(String), Word(String), + Group(Vec), } /// Turn a string into a set of tokens. @@ -17,22 +17,30 @@ pub enum Token { // # Returns: // * `Ok(Vec)` if we were successful. // * `Err(())` if we couldn't tokenize this string. -pub fn tokenize(input: &String) -> Result, ()> { - let mut v: Vec = Vec::new(); +pub fn tokenize(input: &String) -> Result { let mut t: Option = None; + let mut g: Vec = Vec::with_capacity(8); + g.push(Token::Group(Vec::with_capacity(8))); + + for c in input.chars() { + let v_now: &mut Vec = match g.last_mut().unwrap() { + Token::Group(ref mut x) => x, + _ => panic!() + }; + match c { // Minus sign can be both a Negative and an Operator. // Needs special treatment. '-' => { - if t.is_some() { v.push(t.unwrap()); t = None; } - match v.last() { + if t.is_some() { v_now.push(t.unwrap()); t = None; } + match v_now.last() { // If previous token was any of the following, // this is the "minus" operator Some(Token::Number(_)) | - Some(Token::EndGroup) | + Some(Token::Group(_)) | Some(Token::Word(_)) => { - v.push(Token::Operator(String::from(c))); + v_now.push(Token::Operator(String::from(c))); }, // Otherwise, this is a negative sign. @@ -53,7 +61,7 @@ pub fn tokenize(input: &String) -> Result, ()> { // If we're not building a number, finalize // previous token and start one. _ => { - if t.is_some() { v.push(t.unwrap()); } + if t.is_some() { v_now.push(t.unwrap()); } t = Some(Token::Number(String::from(c))); } }; @@ -72,7 +80,7 @@ pub fn tokenize(input: &String) -> Result, ()> { // If we're not building a number, finalize // previous token and start one. _ => { - if t.is_some() { v.push(t.unwrap()); } + if t.is_some() { v_now.push(t.unwrap()); } t = Some(Token::Word(String::from(c))); } }; @@ -83,24 +91,31 @@ pub fn tokenize(input: &String) -> Result, ()> { // Always one character '+' | '*' | '/' | '^' => { // Finalize previous token - if t.is_some() { v.push(t.unwrap()); t = None; } - v.push(Token::Operator(String::from(c))); + if t.is_some() { v_now.push(t.unwrap()); t = None; } + v_now.push(Token::Operator(String::from(c))); } // Groups // Always one character '(' => { - if t.is_some() { v.push(t.unwrap()); t = None; } - v.push(Token::StartGroup); + if t.is_some() { v_now.push(t.unwrap()); t = None; } + g.push(Token::Group(Vec::with_capacity(8))); }, ')' => { - if t.is_some() { v.push(t.unwrap()); t = None; } - v.push(Token::EndGroup); + if t.is_some() { v_now.push(t.unwrap()); t = None; } + let new_group: Token = g.pop().unwrap(); + + let v_now: &mut Vec = match g.last_mut().unwrap() { + Token::Group(ref mut x) => x, + _ => panic!() + }; + + v_now.push(new_group); }, // Space. Basic seperator. ' ' => { - if t.is_some() { v.push(t.unwrap()); t = None; } + if t.is_some() { v_now.push(t.unwrap()); t = None; } } // Invalid token @@ -108,6 +123,12 @@ pub fn tokenize(input: &String) -> Result, ()> { }; } - if t.is_some() { v.push(t.unwrap()); } - return Ok(v); + + let v_now: &mut Vec = match g.last_mut().unwrap() { + Token::Group(ref mut x) => x, + _ => panic!() + }; + if t.is_some() { v_now.push(t.unwrap()); } + + return Ok(Token::Group(v_now.to_vec())); } \ No newline at end of file