Mercurial > lbo > hg > arithmetal
changeset 1:77d76a662f3e draft
Initial implementation, to be extended
author | Lewin Bormann <lbo@spheniscida.de> |
---|---|
date | Tue, 22 Nov 2016 21:01:41 +0100 |
parents | 4d4c433053a2 |
children | 7ed53d7e278e |
files | src/lib.rs |
diffstat | 1 files changed, 287 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib.rs Tue Nov 22 21:01:41 2016 +0100 @@ -0,0 +1,287 @@ +/// Syntax (so far) +/// +/// pexpr ::= '(' pexpr ')' | expr +/// expr ::= pexpr (+|-) pexpr | term +/// term ::= pexpr (*|/) pexpr | sym +/// sym ::= -num | num +/// num ::= 0..9* +/// +/// May be expanded by using variables. +/// + +use std::iter::FromIterator; +use std::str::FromStr; + +/// Expression tree +#[derive(Debug, PartialEq)] +enum Ast { + Addition(Box<Ast>, Box<Ast>), + Subtraction(Box<Ast>, Box<Ast>), + Multiplication(Box<Ast>, Box<Ast>), + Division(Box<Ast>, Box<Ast>), + Num(i64), +} + +#[derive(Debug)] +struct State<'a> { + from: usize, + to: usize, + + text: &'a Vec<char>, +} + +impl<'a> State<'a> { + /// Returns the ix'th character starting from the current parse offset (self.from) + fn at(&self, ix: usize) -> char { + if ix > (self.to - self.from) { + panic!("") + } else { + self.text[self.from + ix] + } + } + + fn len(&self) -> usize { + self.to - self.from + } + + fn restrict(&self, from: usize, to: usize) -> State<'a> { + if from > to || (to > (self.to - self.from)) { + panic!(format!("Bad indices ({},{}) in restrict on ({},{})", + from, + to, + self.from, + self.to)) + } else { + State { + text: self.text, + from: self.from + from, + to: self.from + to, + } + } + } + + fn iter(&self) -> StateIter<'a> { + StateIter { + ix: self.from, + max: self.to, + text: self.text, + } + } +} + +struct StateIter<'a> { + ix: usize, + max: usize, + text: &'a Vec<char>, +} + +impl<'a> Iterator for StateIter<'a> { + type Item = char; + + fn next(&mut self) -> Option<char> { + if self.ix < self.max { + self.ix += 1; + Some(self.text[self.ix - 1]) + } else { + None + } + } +} + +#[derive(Debug, PartialEq)] +enum ParseResult { + Ok(Ast), + Err(String, String), +} + +impl ParseResult { + fn err(&self) -> String { + match self { + &ParseResult::Ok(_) => String::new(), + &ParseResult::Err(ref s, ref t) => format!("{} <=> {}", s, t), + } + } + + fn ok(&self) -> bool { + match self { + &ParseResult::Ok(_) => true, + _ => false, + } + } +} + +fn err<'a>(s: &str, st: State<'a>) -> ParseResult { + ParseResult::Err(s.to_string(), format!("at {}-{}", st.from, st.to)) +} + +// Parse helpers + +fn str_to_vec(s: &str) -> Vec<char> { + s.chars().collect() +} + +fn state<'a>(text: &'a Vec<char>) -> State<'a> { + State { + text: text, + from: 0, + to: text.len(), + } +} + +/// takes a preprocessed input string (no whitespace) +fn enter(text: Vec<char>) -> ParseResult { + let st = State { + from: 0, + to: text.len(), + text: &text, + }; + pexpr(st) +} + +// Parsers + +/// An expression in parentheses +fn pexpr<'a>(st: State<'a>) -> ParseResult { + if st.len() == 0 { + err("Empty", st) + } else if st.at(0) == '(' && st.at(st.len() - 1) == ')' { + // parentheses around expression; parse subexpression + pexpr(st.restrict(1, st.len() - 1)) + } else { + expr(st) + } +} + +fn expr<'a>(st: State<'a>) -> ParseResult { + if st.len() == 0 { + err("Empty", st) + } else { + // Test if addition or subtraction + let mut paren_lvl = 0; + let mut ix = 0; + let mut op_pos: i32 = -1; + + for c in st.iter() { + if c == '(' { + paren_lvl += 1; + } else if c == ')' { + paren_lvl -= 1; + } else if paren_lvl == 0 && (c == '+' || c == '-') { + // yep! + op_pos = ix; + break; + } + ix += 1; + } + + println!("{}", op_pos); + if op_pos > -1 { + let left = pexpr(st.restrict(0, op_pos as usize)); + let right = pexpr(st.restrict(op_pos as usize + 1, st.len())); + + if left.ok() && right.ok() { + match (left, right) { + (ParseResult::Ok(a1), ParseResult::Ok(a2)) => { + if st.at(op_pos as usize) == '-' { + ParseResult::Ok(Ast::Subtraction(Box::new(a1), Box::new(a2))) + } else { + ParseResult::Ok(Ast::Addition(Box::new(a1), Box::new(a2))) + } + } + _ => panic!("Expected OK results"), + } + } else if !left.ok() { + err("Left part of expr is bad", st) + } else { + err("Right or both parts of expr is|are bad", st) + } + } else { + // term(st) + sym(st) + } + } +} + +fn term<'a>(st: State<'a>) -> ParseResult { + err("term", st) +} + +fn sym<'a>(st: State<'a>) -> ParseResult { + if st.len() == 0 { + err("Empty", st) + } else if st.at(0) == '-' { + match num(st.restrict(1, st.len())) { + ParseResult::Ok(Ast::Num(n)) => ParseResult::Ok(Ast::Num(-n)), + x => x, + } + } else { + num(st) + } +} + +fn num<'a>(st: State<'a>) -> ParseResult { + if st.len() == 0 { + err("Empty", st) + } else if st.iter().all(|c| c >= '0' && c <= '9') { + let s = String::from_iter(st.iter()); + match i64::from_str(&s) { + Ok(n) => ParseResult::Ok(Ast::Num(n)), + Err(e) => err(&format!("int parse err: {}", e), st), + } + } else { + err("Not a number", st) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use super::{ParseResult, Ast}; + + #[test] + fn test_num() { + let v = super::str_to_vec("234"); + let st = super::state(&v); + + assert_eq!(super::num(st), ParseResult::Ok(Ast::Num(234))); + } + + #[test] + fn test_sym() { + let v = super::str_to_vec("234"); + let st = super::state(&v); + + assert_eq!(super::sym(st), ParseResult::Ok(Ast::Num(234))); + + let v = super::str_to_vec("-234"); + let st = super::state(&v); + assert_eq!(super::sym(st), ParseResult::Ok(Ast::Num(-234))); + + let v = super::str_to_vec("-a"); + let st = super::state(&v); + assert_eq!(super::sym(st).err(), "Not a number <=> at 1-2".to_string()); + } + + #[test] + fn test_expr() { + let v = super::str_to_vec("2+4"); + let st = super::state(&v); + + assert_eq!(super::expr(st), + ParseResult::Ok(Ast::Addition(Box::new(Ast::Num(2)), Box::new(Ast::Num(4))))); + + let v = super::str_to_vec("2-4"); + let st = super::state(&v); + + assert_eq!(super::expr(st), + ParseResult::Ok(Ast::Subtraction(Box::new(Ast::Num(2)), Box::new(Ast::Num(4))))); + + let v = super::str_to_vec("2+4+5"); + let st = super::state(&v); + + assert_eq!(super::expr(st), + ParseResult::Ok(Ast::Addition(Box::new(Ast::Num(2)), + Box::new(Ast::Addition(Box::new(Ast::Num(4)), + Box::new(Ast::Num(5))))))); + } +}