Use more deterministic data structures

This commit is contained in:
Tiffany Bennett 2016-08-25 22:32:38 -04:00
parent 755c853cc7
commit 685e5149d1
5 changed files with 62 additions and 31 deletions

View file

@ -6,7 +6,7 @@ use ast::{DatePattern, DateToken, show_datepattern};
use chrono::format::Parsed; use chrono::format::Parsed;
use chrono::{Weekday, DateTime, UTC, FixedOffset, Duration, Date}; use chrono::{Weekday, DateTime, UTC, FixedOffset, Duration, Date};
use eval::Context; use eval::Context;
use number::Number; use number::{Number, Dim};
use std::iter::Peekable; use std::iter::Peekable;
pub fn parse_date<I>( pub fn parse_date<I>(
@ -225,7 +225,7 @@ pub fn to_duration(num: &Number) -> Result<Duration, String> {
use gmp::mpq::Mpq; use gmp::mpq::Mpq;
use gmp::mpz::Mpz; use gmp::mpz::Mpz;
if num.1.len() != 1 || num.1.get(&"s".to_owned()) != Some(&1) { if num.1.len() != 1 || num.1.get("s") != Some(&1) {
return Err(format!("Expected seconds")) return Err(format!("Expected seconds"))
} }
let max = Mpq::ratio(&Mpz::from(i64::max_value() / 1000), &Mpz::one()); let max = Mpq::ratio(&Mpz::from(i64::max_value() / 1000), &Mpz::one());
@ -239,12 +239,11 @@ pub fn to_duration(num: &Number) -> Result<Duration, String> {
pub fn from_duration(duration: &Duration) -> Result<Number, String> { pub fn from_duration(duration: &Duration) -> Result<Number, String> {
use gmp::mpq::Mpq; use gmp::mpq::Mpq;
use gmp::mpz::Mpz; use gmp::mpz::Mpz;
use std::rc::Rc;
let ns = try!(duration.num_nanoseconds() let ns = try!(duration.num_nanoseconds()
.ok_or(format!("Implementation error: Duration is out of range"))); .ok_or(format!("Implementation error: Duration is out of range")));
let div = Mpz::from(1_000_000_000); let div = Mpz::from(1_000_000_000);
Ok(Number::new_unit(Mpq::ratio(&Mpz::from(ns), &div), Rc::new("s".to_owned()))) Ok(Number::new_unit(Mpq::ratio(&Mpz::from(ns), &div), Dim::new("s")))
} }
pub fn now() -> DateTime<FixedOffset> { pub fn now() -> DateTime<FixedOffset> {

View file

@ -2,11 +2,10 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std::collections::HashMap; use std::collections::{HashMap, BTreeMap, BTreeSet};
use std::collections::BTreeMap;
use gmp::mpq::Mpq; use gmp::mpq::Mpq;
use chrono::{DateTime, FixedOffset}; use chrono::{DateTime, FixedOffset};
use number::{Number, Unit}; use number::{Number, Unit, Dim};
use date; use date;
use ast::{DatePattern, Expr, SuffixOp, Def, Defs, Query, Conversion}; use ast::{DatePattern, Expr, SuffixOp, Def, Defs, Query, Conversion};
use std::ops::{Add, Div, Mul, Neg, Sub}; use std::ops::{Add, Div, Mul, Neg, Sub};
@ -22,10 +21,10 @@ pub enum Value {
/// The evaluation context that contains unit definitions. /// The evaluation context that contains unit definitions.
#[derive(Debug)] #[derive(Debug)]
pub struct Context { pub struct Context {
pub dimensions: Vec<Rc<String>>, pub dimensions: BTreeSet<Dim>,
pub units: HashMap<String, Number>, pub units: HashMap<String, Number>,
pub aliases: HashMap<Unit, String>, pub aliases: HashMap<Unit, String>,
pub reverse: HashMap<Unit, String>, pub reverse: BTreeMap<Unit, String>,
pub prefixes: Vec<(String, Number)>, pub prefixes: Vec<(String, Number)>,
pub definitions: HashMap<String, Expr>, pub definitions: HashMap<String, Expr>,
pub datepatterns: Vec<Vec<DatePattern>>, pub datepatterns: Vec<Vec<DatePattern>>,
@ -161,10 +160,8 @@ impl Context {
/// Given a unit name, returns its value if it exists. Supports SI /// Given a unit name, returns its value if it exists. Supports SI
/// prefixes, plurals, bare dimensions like length, and aliases. /// prefixes, plurals, bare dimensions like length, and aliases.
pub fn lookup(&self, name: &str) -> Option<Number> { pub fn lookup(&self, name: &str) -> Option<Number> {
for k in &self.dimensions { if let Some(k) = self.dimensions.get(name) {
if name == &***k { return Some(Number::one_unit(k.to_owned()))
return Some(Number::one_unit(k.to_owned()))
}
} }
if let Some(v) = self.units.get(name).cloned() { if let Some(v) = self.units.get(name).cloned() {
return Some(v) return Some(v)
@ -191,10 +188,8 @@ impl Context {
/// Given a unit name, try to return a canonical name (expanding aliases and such) /// Given a unit name, try to return a canonical name (expanding aliases and such)
pub fn canonicalize(&self, name: &str) -> Option<String> { pub fn canonicalize(&self, name: &str) -> Option<String> {
for k in &self.dimensions { if let Some(k) = self.dimensions.get(name) {
if name == &***k { return Some((*k.0).clone())
return Some((**k).clone())
}
} }
if let Some(v) = self.definitions.get(name) { if let Some(v) = self.definitions.get(name) {
if let Expr::Unit(ref name) = *v { if let Expr::Unit(ref name) = *v {
@ -337,7 +332,7 @@ impl Context {
match *expr { match *expr {
Expr::Unit(ref name) if name == "now" => Ok(Value::DateTime(date::now())), Expr::Unit(ref name) if name == "now" => Ok(Value::DateTime(date::now())),
Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)).map(Value::Number), Expr::Unit(ref name) => self.lookup(name).ok_or(format!("Unknown unit {}", name)).map(Value::Number),
Expr::Quote(ref name) => Ok(Value::Number(Number::one_unit(Rc::new(name.clone())))), Expr::Quote(ref name) => Ok(Value::Number(Number::one_unit(Dim::new(&**name)))),
Expr::Const(ref num, ref frac, ref exp) => Expr::Const(ref num, ref frac, ref exp) =>
Number::from_parts( Number::from_parts(
num, num,
@ -775,10 +770,10 @@ impl Context {
/// Creates a new, empty context /// Creates a new, empty context
pub fn new() -> Context { pub fn new() -> Context {
Context { Context {
dimensions: Vec::new(), dimensions: BTreeSet::new(),
units: HashMap::new(), units: HashMap::new(),
aliases: HashMap::new(), aliases: HashMap::new(),
reverse: HashMap::new(), reverse: BTreeMap::new(),
prefixes: Vec::new(), prefixes: Vec::new(),
definitions: HashMap::new(), definitions: HashMap::new(),
datepatterns: Vec::new(), datepatterns: Vec::new(),
@ -958,8 +953,7 @@ impl Context {
}; };
match *def { match *def {
Def::Dimension(ref dname) => { Def::Dimension(ref dname) => {
let dname = Rc::new(dname.clone()); self.dimensions.insert(Dim::new(&**dname));
self.dimensions.push(dname.clone());
}, },
Def::Unit(ref expr) => match self.eval(expr) { Def::Unit(ref expr) => match self.eval(expr) {
Ok(Value::Number(v)) => { Ok(Value::Number(v)) => {

View file

@ -2,9 +2,9 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this // License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/. // file, You can obtain one at https://mozilla.org/MPL/2.0/.
use std::collections::{BTreeMap, BinaryHeap, HashMap}; use std::collections::{BTreeMap, BinaryHeap};
use std::rc::Rc; use std::rc::Rc;
use number::{Number, Unit}; use number::{Number, Unit, Dim};
use std::cmp; use std::cmp;
use gmp::mpq::Mpq; use gmp::mpq::Mpq;
@ -23,7 +23,7 @@ impl cmp::Ord for Factors {
} }
} }
pub fn fast_decompose(value: &Number, aliases: &HashMap<Unit, String>) -> Unit { pub fn fast_decompose(value: &Number, aliases: &BTreeMap<Unit, String>) -> Unit {
let mut best = None; let mut best = None;
for (unit, name) in aliases.iter() { for (unit, name) in aliases.iter() {
let num = Number(Mpq::one(), unit.clone()); let num = Number(Mpq::one(), unit.clone());
@ -39,7 +39,7 @@ pub fn fast_decompose(value: &Number, aliases: &HashMap<Unit, String>) -> Unit {
if let Some((name, unit, pow, score)) = best { if let Some((name, unit, pow, score)) = best {
if score < value.complexity_score() { if score < value.complexity_score() {
let mut res = (value / &Number(Mpq::one(), unit.clone()).powi(pow)).unwrap().1; let mut res = (value / &Number(Mpq::one(), unit.clone()).powi(pow)).unwrap().1;
res.insert(Rc::new(name.clone()), pow as i64); res.insert(Dim::new(&**name), pow as i64);
return res return res
} }
} }

View file

@ -9,18 +9,39 @@ use eval::Show;
use std::ops::{Add, Div, Mul, Neg, Sub}; use std::ops::{Add, Div, Mul, Neg, Sub};
use std::rc::Rc; use std::rc::Rc;
use std::fmt; use std::fmt;
use std::borrow::Borrow;
/// Number type /// Number type
pub type Num = Mpq; pub type Num = Mpq;
/// A simple alias to add semantic meaning for when we pass around dimension IDs.
pub type Dim = Rc<String>;
/// Alias for the primary representation of dimensionality. /// Alias for the primary representation of dimensionality.
pub type Unit = BTreeMap<Dim, i64>; pub type Unit = BTreeMap<Dim, i64>;
/// A newtype for a string dimension ID, so that we can implement traits for it.
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct Dim(pub Rc<String>);
/// The basic representation of a number with a unit. /// The basic representation of a number with a unit.
#[derive(Clone, PartialEq, Eq)] #[derive(Clone, PartialEq, Eq)]
pub struct Number(pub Num, pub Unit); pub struct Number(pub Num, pub Unit);
impl Borrow<str> for Dim {
fn borrow(&self) -> &str {
&**self.0
}
}
impl fmt::Display for Dim {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(fmt)
}
}
impl Dim {
pub fn new(dim: &str) -> Dim {
Dim(Rc::new(dim.to_owned()))
}
}
fn one() -> Mpq { fn one() -> Mpq {
Mpq::one() Mpq::one()
} }
@ -320,7 +341,7 @@ impl Show for Number {
let e = value.1.iter().next().unwrap(); let e = value.1.iter().next().unwrap();
let ref n = *e.0; let ref n = *e.0;
if *e.1 == 1 { if *e.1 == 1 {
Some((**n).clone()) Some((&*n.0).clone())
} else { } else {
Some(format!("{}^{}", n, e.1)) Some(format!("{}^{}", n, e.1))
} }

View file

@ -16,9 +16,26 @@ fn test(input: &str, output: &str) {
} }
#[test] #[test]
fn test_queries() { fn test_definition() {
test("watt", "Definition: watt = J / s = 1 watt (power; kg m^2 / s^3)"); test("watt", "Definition: watt = J / s = 1 watt (power; kg m^2 / s^3)");
}
#[test]
fn test_eval() {
test("5 inch", "0.127 m (length)"); test("5 inch", "0.127 m (length)");
test("5 inch -> cm", "12.7 cm (length)"); }
#[test]
fn test_convert() {
test("5 inch -> cm", "12.7 centim (length)");
}
#[test]
fn test_temp() {
test("2 degC 2 -> degC", "277.15 °C (temperature)"); test("2 degC 2 -> degC", "277.15 °C (temperature)");
} }
#[test]
fn test_determinism() {
test("pascal m", "1 A tesla (spectral_irradiance_frequency)");
}