Implement evaluator and do REPL unit conversions

This commit is contained in:
Tiffany Bennett 2016-08-02 20:51:38 -04:00
parent ae8568b991
commit c8afaaa62a
3 changed files with 219 additions and 9 deletions

180
src/eval.rs Normal file
View file

@ -0,0 +1,180 @@
use std::collections::HashMap;
use std::collections::BTreeMap;
pub type Dim = usize;
pub type Unit = BTreeMap<Dim, i64>;
#[derive(Clone)]
pub struct Value(f64, Unit);
pub struct Context {
dimensions: Vec<String>,
units: HashMap<String, Value>,
aliases: HashMap<Unit, String>,
}
impl Value {
pub fn new(num: f64) -> Value {
Value(num, Unit::new())
}
pub fn new_unit(num: f64, unit: Dim) -> Value {
let mut map = Unit::new();
map.insert(unit, 1);
Value(num, map)
}
pub fn invert(&self) -> Value {
Value(1.0 / self.0,
self.1.iter()
.map(|(&k, &power)| (k, -power))
.collect::<Unit>())
}
pub fn mul(&self, other: &Value) -> Value {
let mut val = Unit::new();
let mut a = self.1.iter().peekable();
let mut b = other.1.iter().peekable();
loop {
match (a.peek().cloned(), b.peek().cloned()) {
(Some((ka, pa)), Some((kb, pb))) if ka == kb => {
// merge
let power = pa+pb;
if power != 0 {
val.insert(ka.clone(), power);
}
a.next();
b.next();
},
(Some((ka, _)), Some((kb, vb))) if ka > kb => {
// push vb, advance
val.insert(kb.clone(), vb.clone());
b.next();
},
(Some((ka, va)), Some((kb, _))) if ka < kb => {
// push va, advance
val.insert(ka.clone(), va.clone());
a.next();
},
(Some(_), Some(_)) => panic!(),
(None, Some((kb, vb))) => {
// push vb, advance
val.insert(kb.clone(), vb.clone());
b.next();
},
(Some((ka, va)), None) => {
// push va, advance
val.insert(ka.clone(), va.clone());
a.next();
},
(None, None) => break
}
}
Value(self.0 * other.0, val)
}
pub fn pow(&self, exp: i32) -> Value {
Value(self.0.powi(exp),
self.1.iter()
.map(|(&k, &power)| (k, power * exp as i64))
.collect::<Unit>())
}
}
impl Context {
pub fn print(&self, value: &Value) {
print!("{}", value.0);
for (&dim, &exp) in &value.1 {
print!(" {}^{}", self.dimensions[dim], exp);
}
if let Some(name) = self.aliases.get(&value.1) {
print!(" ({})", name);
}
println!("");
}
pub fn lookup(&self, name: &str) -> Option<Value> {
self.units.get(name).cloned().or_else(|| {
for (i, ref k) in self.dimensions.iter().enumerate() {
if name == *k {
return Some(Value::new_unit(1.0, i))
}
}
None
})
}
pub fn eval(&self, expr: &::unit_defs::Expr) -> Result<Value, String> {
use unit_defs::Expr;
match *expr {
Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)),
Expr::Const(num) => Ok(Value::new(num)),
Expr::Neg(ref expr) => self.eval(&**expr).and_then(|mut v| {
v.0 = -v.0;
Ok(v)
}),
Expr::Plus(ref expr) => self.eval(&**expr),
Expr::Frac(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom)) {
(Ok(top), Ok(bottom)) => Ok(top.mul(&bottom.invert())),
(Err(e), _) => Err(e),
(_, Err(e)) => Err(e),
},
Expr::Mul(ref args) => args.iter().fold(Ok(Value::new(1.0)), |a, b| {
a.and_then(|a| {
let b = try!(self.eval(b));
Ok(a.mul(&b))
})
}),
Expr::Pow(ref base, ref exp) => {
let base = try!(self.eval(&**base));
let exp = try!(self.eval(&**exp));
if exp.1.len() != 0 {
Err(format!("Exponent not dimensionless"))
} else if exp.0.trunc() != exp.0 {
Err(format!("Exponent not integer"))
} else {
Ok(base.pow(exp.0 as i32))
}
},
Expr::Error(ref e) => Err(e.clone()),
}
}
pub fn new(defs: ::unit_defs::Defs) -> Context {
use unit_defs::Def;
let mut ctx = Context {
dimensions: Vec::new(),
units: HashMap::new(),
aliases: HashMap::new(),
};
for (name, def) in defs.defs {
match *def {
Def::Dimension(ref name) => {
ctx.dimensions.push(name.clone());
},
Def::Unit(ref expr) => match ctx.eval(expr) {
Ok(v) => {
ctx.units.insert(name.clone(), v);
},
Err(e) => println!("Unit {} is malformed: {}", name, e)
},
Def::Error(ref err) => println!("Def {}: {}", name, err),
_ => ()
};
}
for (expr, name) in defs.aliases {
match ctx.eval(&expr) {
Ok(v) => {
ctx.aliases.insert(v.1, name);
},
Err(e) => println!("Alias {}: {}", name, e)
}
}
ctx
}
}

View file

@ -1,14 +1,36 @@
pub mod unit_defs;
pub mod eval;
fn main() {
use std::io::{stdin, Read};
use std::io::{stdin, stdout, Read, Write};
use std::fs::File;
let mut f = stdin();//File::open("units.txt").unwrap();
let mut f = File::open("units.txt").unwrap();
let mut buf = vec![];
f.read_to_end(&mut buf).unwrap();
let string = String::from_utf8_lossy(&*buf);
let mut iter = unit_defs::TokenIterator::new(&*string).peekable();
//let res = unit_defs::tokens(&mut iter);
let res = unit_defs::parse(&mut iter);
println!("{:#?}", res);
let ctx = eval::Context::new(res);
//println!("{:#?}", res);
let f = stdin();
let mut line = String::new();
loop {
print!("> ");
stdout().flush().unwrap();
match f.read_line(&mut line) {
Ok(_) => (),
Err(_) => return
};
{
let mut iter = unit_defs::TokenIterator::new(&*line).peekable();
let expr = unit_defs::parse_expr(&mut iter);
match ctx.eval(&expr) {
Ok(value) => ctx.print(&value),
Err(e) => println!("{}", e)
};
}
line.clear();
}
}

View file

@ -1,4 +1,3 @@
use std::collections::HashMap;
use std::str::Chars;
use std::iter::Peekable;
use std::str::FromStr;
@ -160,6 +159,12 @@ pub enum Def {
Error(String),
}
#[derive(Debug)]
pub struct Defs {
pub defs: Vec<(String, Rc<Def>)>,
pub aliases: Vec<(Expr, String)>,
}
fn parse_term(mut iter: &mut Iter) -> Expr {
match iter.next().unwrap() {
Token::Ident(name) => Expr::Unit(name),
@ -194,7 +199,7 @@ fn parse_frac(mut iter: &mut Iter) -> Expr {
}
}
fn parse_expr(mut iter: &mut Iter) -> Expr {
pub fn parse_expr(mut iter: &mut Iter) -> Expr {
let mut terms = vec![];
loop {
match *iter.peek().unwrap() {
@ -239,8 +244,8 @@ fn parse_alias(mut iter: &mut Iter) -> Option<(Expr, String)> {
Some((expr, name))
}
pub fn parse(mut iter: &mut Iter) -> (HashMap<String, Rc<Def>>, Vec<(Expr, String)>) {
let mut map = HashMap::new();
pub fn parse(mut iter: &mut Iter) -> Defs {
let mut map = vec![];
let mut aliases = vec![];
loop {
let mut copy = iter.clone();
@ -277,12 +282,15 @@ pub fn parse(mut iter: &mut Iter) -> (HashMap<String, Rc<Def>>, Vec<(Expr, Strin
Def::Error(format!("Unknown definition"))
}
};
map.insert(name.clone(), Rc::new(def));
map.push((name.clone(), Rc::new(def)));
},
_ => parse_unknown(iter)
};
}
(map, aliases)
Defs {
defs: map,
aliases: aliases
}
}
pub fn tokens(mut iter: &mut Iter) -> Vec<Token> {