Separate expressions from top-level queries

This commit is contained in:
Tiffany Bennett 2016-08-24 13:46:30 -04:00
parent 52c19f6920
commit 3f32bc30a7
4 changed files with 83 additions and 87 deletions

View file

@ -39,17 +39,29 @@ pub enum Expr {
Sub(Box<Expr>, Box<Expr>),
Neg(Box<Expr>),
Plus(Box<Expr>),
Convert(Box<Expr>, Box<Expr>),
Equals(Box<Expr>, Box<Expr>),
Suffix(SuffixOp, Box<Expr>),
Call(String, Vec<Expr>),
Factorize(Box<Expr>),
Error(String),
}
#[derive(Debug, Clone)]
pub enum Conversion {
Expr(Expr),
DegC,
DegF,
DegRe,
DegRo,
DegDe,
DegN,
List(Vec<String>),
}
#[derive(Debug, Clone)]
pub enum Query {
Expr(Expr),
Convert(Expr, Conversion),
Factorize(Expr),
Error(String),
}
@ -95,7 +107,7 @@ impl fmt::Display for Expr {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
#[derive(PartialOrd, Ord, PartialEq, Eq)]
enum Prec {
Term, Plus, Pow, Mul, Div, Add, Equals, Convert
Term, Plus, Pow, Mul, Div, Add, Equals
}
fn recurse(expr: &Expr, fmt: &mut fmt::Formatter, prec: Prec) -> fmt::Result {
@ -146,11 +158,11 @@ impl fmt::Display for Expr {
Expr::Call(ref name, ref args) => {
try!(write!(fmt, "{}(", name));
if let Some(first) = args.first() {
try!(recurse(first, fmt, Prec::Convert));
try!(recurse(first, fmt, Prec::Equals));
}
for arg in args.iter().skip(1) {
try!(write!(fmt, ", "));
try!(recurse(arg, fmt, Prec::Convert));
try!(recurse(arg, fmt, Prec::Equals));
}
write!(fmt, ")")
},
@ -166,7 +178,6 @@ impl fmt::Display for Expr {
try!(write!(fmt, "-"));
recurse(expr, fmt, Prec::Plus)
},
Expr::Convert(ref left, ref right) => binop!(left, right, Prec::Convert, Prec::Equals, " -> "),
Expr::Equals(ref left, ref right) => binop!(left, right, Prec::Equals, Prec::Add, " = "),
Expr::Suffix(ref op, ref expr) => {
if prec < Prec::Mul {
@ -179,28 +190,11 @@ impl fmt::Display for Expr {
}
Ok(())
},
Expr::Factorize(ref op) => {
if prec < Prec::Convert {
try!(write!(fmt, "("));
}
try!(write!(fmt, "factorize "));
try!(recurse(op, fmt, Prec::Convert));
if prec < Prec::Convert {
try!(write!(fmt, ")"));
}
Ok(())
}
Expr::DegC => write!(fmt, "°C"),
Expr::DegF => write!(fmt, "°F"),
Expr::DegRe => write!(fmt, "°Ré"),
Expr::DegRo => write!(fmt, "°Rø"),
Expr::DegDe => write!(fmt, "°De"),
Expr::DegN => write!(fmt, "°N"),
Expr::Error(ref err) => write!(fmt, "<error: {}>", err)
}
}
recurse(self, fmt, Prec::Convert)
recurse(self, fmt, Prec::Equals)
}
}

View file

@ -8,7 +8,7 @@ use gmp::mpq::Mpq;
use chrono::{DateTime, FixedOffset};
use number::{Number, Unit};
use date;
use ast::{DatePattern, Expr, SuffixOp, Def, Defs};
use ast::{DatePattern, Expr, SuffixOp, Def, Defs, Query, Conversion};
use std::ops::{Add, Div, Mul, Neg, Sub};
use std::rc::Rc;
use factorize::{factorize, Factors};
@ -308,12 +308,6 @@ impl Context {
Expr::Date(ref date) => date::try_decode(date, self).map(Value::DateTime),
Expr::Neg(ref expr) => self.eval(&**expr).and_then(|v| -&v),
Expr::Plus(ref expr) => self.eval(&**expr),
Expr::DegC => Err(format!("°C is an operator")),
Expr::DegF => Err(format!("°F is an operator")),
Expr::DegRe => Err(format!("°Ré is an operator")),
Expr::DegRo => Err(format!("°Rø is an operator")),
Expr::DegDe => Err(format!("°De is an operator")),
Expr::DegN => Err(format!("°N is an operator")),
Expr::Frac(ref left, ref right) => operator!(left div / right),
Expr::Add(ref left, ref right) => operator!(left add + right),
@ -340,8 +334,6 @@ impl Context {
Ok((&a * &b).unwrap())
})
}),
Expr::Convert(_, _) => Err(format!("Conversions (->) must be top-level expressions")),
Expr::Factorize(_) => Err(format!("Derivatives must be top-level expressions")),
Expr::Equals(_, ref right) => self.eval(right),
Expr::Call(ref name, ref args) => {
let args = try!(args.iter().map(|x| self.eval(x)).collect::<Result<Vec<_>, _>>());
@ -426,18 +418,15 @@ impl Context {
},
Expr::Neg(ref v) => self.eval_unit_name(v),
Expr::Plus(ref v) => self.eval_unit_name(v),
Expr::Suffix(_, _) | Expr::DegC | Expr::DegF | Expr::DegRe | Expr::DegRo |
Expr::DegDe | Expr::DegN =>
Expr::Suffix(_, _) =>
Err(format!("Temperature conversions must not be compound units")),
Expr::Date(_) => Err(format!("Dates are not allowed in the right hand side of conversions")),
Expr::Convert(_, _) => Err(format!("Conversions are not allowed in the right hand of conversions")),
Expr::Factorize(_) => Err(format!("Derivatives are not allowed in the right hand of conversions")),
Expr::Error(ref e) => Err(e.clone()),
}
}
/// Evaluates an expression, include `->` conversions.
pub fn eval_outer(&self, expr: &Expr) -> Result<String, String> {
pub fn eval_outer(&self, expr: &Query) -> Result<String, String> {
let conformance_err = |top: &Number, bottom: &Number| -> String {
use std::io::Write;
@ -527,7 +516,7 @@ impl Context {
};
match *expr {
Expr::Unit(ref name) if self.definitions.contains_key(name) => {
Query::Expr(Expr::Unit(ref name)) if self.definitions.contains_key(name) => {
let mut name = name;
while let Some(&Expr::Unit(ref unit)) = self.definitions.get(name) {
if self.definitions.get(unit).is_none() {
@ -539,7 +528,7 @@ impl Context {
let res = self.lookup(name).unwrap();
Ok(format!("Definition: {} = {} = {}", name, def, res.show(self)))
},
Expr::Convert(ref top, ref bottom) => match (self.eval(&**top), self.eval(&**bottom), self.eval_unit_name(&**bottom)) {
Query::Convert(ref top, Conversion::Expr(ref bottom)) => match (self.eval(top), self.eval(bottom), self.eval_unit_name(bottom)) {
(Ok(top), Ok(bottom), Ok(bottom_name)) => {
let (top, bottom) = match (top, bottom) {
(Value::Number(top), Value::Number(bottom)) => (top, bottom),
@ -556,43 +545,51 @@ impl Context {
Err(conformance_err(&top, &bottom))
}
},
(Ok(ref top), Err(ref e), _) => {
macro_rules! temperature {
($name:expr, $base:expr, $scale:expr) => {{
let top = match *top {
Value::Number(ref num) => num,
_ => return Err(format!("Cannot convert <{}> to °{}",
top.show(self), $name))
};
let bottom = self.lookup($scale)
.expect(&*format!("Unit {} missing", $scale));
if top.1 != bottom.1 {
Err(conformance_err(&top, &bottom))
} else {
let res = (top - &self.lookup($base)
.expect(&*format!("Constant {} missing", $base))).unwrap();
let res = (&res / &bottom).unwrap();
let mut name = BTreeMap::new();
name.insert(format!("°{}", $name), 1);
Ok(show(&res, &bottom, name))
}
}}
}
match **bottom {
Expr::DegC => temperature!("C", "zerocelsius", "kelvin"),
Expr::DegF => temperature!("F", "zerofahrenheit", "degrankine"),
Expr::DegRe => temperature!("", "zerocelsius", "reaumur_absolute"),
Expr::DegRo => temperature!("", "zeroromer", "romer_absolute"),
Expr::DegDe => temperature!("De", "zerodelisle", "delisle_absolute"),
Expr::DegN => temperature!("N", "zerocelsius", "newton_absolute"),
_ => Err(e.clone())
}
},
(Err(e), _, _) => Err(e),
(_, Err(e), _) => Err(e),
(_, _, Err(e)) => Err(e),
},
Expr::Factorize(ref expr) => {
Query::Convert(ref _top, Conversion::List(ref _list)) => unimplemented!(),
Query::Convert(ref top, ref which @ Conversion::DegC) |
Query::Convert(ref top, ref which @ Conversion::DegF) |
Query::Convert(ref top, ref which @ Conversion::DegN) |
Query::Convert(ref top, ref which @ Conversion::DegRe) |
Query::Convert(ref top, ref which @ Conversion::DegRo) |
Query::Convert(ref top, ref which @ Conversion::DegDe) => {
let top = try!(self.eval(top));
macro_rules! temperature {
($name:expr, $base:expr, $scale:expr) => {{
let top = match top {
Value::Number(ref num) => num,
_ => return Err(format!("Cannot convert <{}> to °{}",
top.show(self), $name))
};
let bottom = self.lookup($scale)
.expect(&*format!("Unit {} missing", $scale));
if top.1 != bottom.1 {
Err(conformance_err(&top, &bottom))
} else {
let res = (top - &self.lookup($base)
.expect(&*format!("Constant {} missing", $base))).unwrap();
let res = (&res / &bottom).unwrap();
let mut name = BTreeMap::new();
name.insert(format!("°{}", $name), 1);
Ok(show(&res, &bottom, name))
}
}}
}
match *which {
Conversion::DegC => temperature!("C", "zerocelsius", "kelvin"),
Conversion::DegF => temperature!("F", "zerofahrenheit", "degrankine"),
Conversion::DegRe => temperature!("", "zerocelsius", "reaumur_absolute"),
Conversion::DegRo => temperature!("", "zeroromer", "romer_absolute"),
Conversion::DegDe => temperature!("De", "zerodelisle", "delisle_absolute"),
Conversion::DegN => temperature!("N", "zerocelsius", "newton_absolute"),
_ => panic!()
}
},
Query::Factorize(ref expr) => {
let val = try!(self.eval(expr));
let val = match val {
Value::Number(val) => val,
@ -626,10 +623,11 @@ impl Context {
|a, x| format!("{}; {}", a, x));
Ok(format!("Factorizations: {}{}", results, if len < 10 {""} else {"; ..."}))
},
_ => {
Query::Expr(ref expr) => {
let val = try!(self.eval(expr));
Ok(val.show(self))
},
Query::Error(ref e) => Err(e.clone()),
}
}

View file

@ -144,7 +144,7 @@ pub fn load() -> Result<Context, String> {
/// Evaluates a single line within a context.
pub fn one_line(ctx: &mut Context, line: &str) -> Result<String, String> {
let mut iter = text_query::TokenIterator::new(line.trim()).peekable();
let expr = text_query::parse_expr(&mut iter);
let expr = text_query::parse_query(&mut iter);
ctx.eval_outer(&expr)
}

View file

@ -496,11 +496,15 @@ fn parse_eq(mut iter: &mut Iter) -> Expr {
}
}
pub fn parse_expr(mut iter: &mut Iter) -> Expr {
pub fn parse_expr(iter: &mut Iter) -> Expr {
parse_eq(iter)
}
pub fn parse_query(mut iter: &mut Iter) -> Query {
match iter.peek().cloned() {
Some(Token::Ident(ref s)) if s == "factorize" => {
iter.next();
return Expr::Factorize(Box::new(parse_eq(iter)))
return Query::Factorize(parse_eq(iter))
},
_ => ()
}
@ -509,17 +513,17 @@ pub fn parse_expr(mut iter: &mut Iter) -> Expr {
Token::DashArrow => {
iter.next();
let right = match iter.peek().cloned().unwrap() {
Token::DegC => Expr::DegC,
Token::DegF => Expr::DegF,
Token::DegRe => Expr::DegRe,
Token::DegRo => Expr::DegRo,
Token::DegDe => Expr::DegDe,
Token::DegN => Expr::DegN,
_ => parse_eq(iter)
Token::DegC => Conversion::DegC,
Token::DegF => Conversion::DegF,
Token::DegRe => Conversion::DegRe,
Token::DegRo => Conversion::DegRo,
Token::DegDe => Conversion::DegDe,
Token::DegN => Conversion::DegN,
_ => Conversion::Expr(parse_eq(iter))
};
Expr::Convert(Box::new(left), Box::new(right))
Query::Convert(left, right)
},
_ => left
_ => Query::Expr(left)
}
}