mirror of
https://github.com/tiffany352/rink-rs
synced 2024-11-10 05:34:14 +00:00
Context refactor (#124)
Yet another giant refactor PR. I'm trying to clean up the Context internals. I moved a lot of the fields into a new Registry object with the hopes of making future refactors to it easier. A lot of things are poorly named, and I've been going through and updating those. Some of these fields I didn't even know what they did until I studied them carefully, since I'd forgotten over time. This makes some breaking changes to the Defs serialization format, but I didn't touch Unit or Substance which are the only ones that are used by the backend currently. Some other refactors I did: - Updated quantities to no longer reference units, only base units and other quantities. - Added a debug dump option to the CLI for showing the contents of the Context. - Merged Def::BaseUnit & Canonicalization. - Merged Def::Prefix & SPrefix. I don't actually know what SPrefix stood for originally (standalone? definitely not short). - Prefixes are now required to be dimensionless. - Added missing tests for the Def serialization format. I'm not entirely done yet though. There's still one dependency on Context from the loader, which is on the eval() method, used by units and substances. I'm still thinking about how I can reduce that coupling.
This commit is contained in:
parent
4c1d138b2a
commit
b921d7a68d
19 changed files with 798 additions and 499 deletions
|
@ -44,6 +44,14 @@ async fn main() -> Result<()> {
|
|||
.long("config-path")
|
||||
.help("Prints a path to the config file, then exits")
|
||||
)
|
||||
.arg(
|
||||
Arg::new("dump")
|
||||
.long("dump")
|
||||
.help("Generates a file containing the contents of the Context object, then exits")
|
||||
.takes_value(true)
|
||||
.default_missing_value("dump.txt")
|
||||
.hide(true)
|
||||
)
|
||||
.arg(
|
||||
Arg::new("service")
|
||||
.long("service")
|
||||
|
@ -60,6 +68,15 @@ async fn main() -> Result<()> {
|
|||
color_eyre::install()?;
|
||||
let config = config::read_config()?;
|
||||
|
||||
if let Some(filename) = matches.value_of("dump") {
|
||||
use std::io::Write;
|
||||
|
||||
let ctx = config::load(&config)?;
|
||||
let mut file = std::fs::File::create(filename)?;
|
||||
writeln!(&mut file, "{:#?}", ctx)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if matches.is_present("config-path") {
|
||||
println!("{}", config::config_path("config.toml").unwrap().display());
|
||||
Ok(())
|
||||
|
|
|
@ -521,23 +521,23 @@ Hz hertz
|
|||
#
|
||||
|
||||
dimensionless ? 1
|
||||
length ? meter
|
||||
length ? m
|
||||
area ? length^2
|
||||
volume ? length^3
|
||||
mass ? kilogram
|
||||
current ? ampere
|
||||
amount ? mole
|
||||
mass ? kg
|
||||
current ? A
|
||||
amount ? mol
|
||||
angle ? radian
|
||||
solid_angle ? steradian
|
||||
force ? newton
|
||||
pressure ? pascal
|
||||
solid_angle ? sr
|
||||
force ? acceleration mass
|
||||
pressure ? force / area
|
||||
stress pascal
|
||||
charge ? coulomb
|
||||
capacitance ? farad
|
||||
resistance ? ohm
|
||||
conductance ? siemens
|
||||
inductance ? henry
|
||||
frequency ? hertz
|
||||
charge ? A s
|
||||
capacitance ? charge / electrical_potential
|
||||
resistance ? electrical_potential / current
|
||||
conductance ? resistance^-1
|
||||
inductance ? magnetic_flux / current
|
||||
frequency ? time^-1
|
||||
velocity ? length / time
|
||||
acceleration ? velocity / time
|
||||
jerk ? acceleration / time
|
||||
|
@ -548,15 +548,15 @@ density ? mass / volume
|
|||
linear_density ? mass / length
|
||||
viscosity ? pressure time
|
||||
kinematic_viscosity ? viscosity / density
|
||||
magnetic_flux ? weber
|
||||
magnetic_flux_density ? tesla
|
||||
magnetic_flux ? electrical_potential time
|
||||
magnetic_flux_density ? magnetic_flux / area
|
||||
magnetization ? current / length
|
||||
electrical_potential ? volt
|
||||
electric_field ? newton / coulomb
|
||||
electrical_potential ? power / current
|
||||
electric_field ? force / charge
|
||||
entropy ? energy / temperature
|
||||
thermal_inductance ? energy temperature^2 / power^2
|
||||
permittivity ? farad / meter
|
||||
permeability ? henry / meter
|
||||
permittivity ? capacitance / length
|
||||
permeability ? inductance / length
|
||||
angular_momentum ? mass area / time
|
||||
area_density ? mass / area
|
||||
catalytic_activity ? amount / time
|
||||
|
@ -817,7 +817,7 @@ fine 1|1000 # Measure of gold purity
|
|||
# with "temp".
|
||||
#
|
||||
|
||||
temperature ? kelvin
|
||||
temperature ? K
|
||||
temperature_difference kelvin
|
||||
|
||||
# In 1741 Anders Celsius introduced a temperature scale with water boiling at
|
||||
|
@ -1280,28 +1280,28 @@ ozcu ouncecopper # in circuitboard fabrication
|
|||
|
||||
!category radiometric "Radiometric Units"
|
||||
|
||||
radiant_energy J # Basic unit of radiation
|
||||
radiant_energy_density J/m^3
|
||||
radiant_flux W
|
||||
spectral_flux_frequency W/Hz
|
||||
spectral_flux_wavelength ? W/m
|
||||
radiant_intensity ? W/sr
|
||||
spectral_intensity_frequency ? W sr^-1 Hz^-1
|
||||
spectral_intensity_wavelength ? W sr^-1 m^-1
|
||||
radiance ? W sr^-1 m^-2
|
||||
spectral_radiance_frequency ? W sr^-1 m^-2 Hz^-1
|
||||
spectral_radiance_wavelength ? W sr^-1 m^-3
|
||||
spectral_irradiance_frequency W m^-2 Hz^-1
|
||||
spectral_irradiance_wavelength ? W/m^3
|
||||
radiosity W/m^2
|
||||
spectral_radiosity_frequency W m^-2 Hz^-1
|
||||
spectral_radiosity_wavelength W m^-3
|
||||
radiant_exitance W/m^2
|
||||
spectral_exitance_frequency W m^-2 Hz^-1
|
||||
spectral_exitance_wavelength W/m^3
|
||||
radiant_exposure J/m^2
|
||||
spectral_exposure_frequency ? J m^-2 Hz^-1
|
||||
spectral_exposure_wavelength J/m^3
|
||||
radiant_energy energy # Basic unit of radiation
|
||||
radiant_energy_density energy / volume
|
||||
radiant_flux power
|
||||
spectral_flux_frequency power / frequency
|
||||
spectral_flux_wavelength ? power / length
|
||||
radiant_intensity ? power / solid_angle
|
||||
spectral_intensity_frequency ? power / solid_angle frequency
|
||||
spectral_intensity_wavelength ? power / solid_angle length
|
||||
radiance ? power / solid_angle area
|
||||
spectral_radiance_frequency ? power / solid_angle area frequency
|
||||
spectral_radiance_wavelength ? power / solid_angle volume
|
||||
spectral_irradiance_frequency power / area frequency
|
||||
spectral_irradiance_wavelength ? power / volume
|
||||
radiosity power / area
|
||||
spectral_radiosity_frequency power / area frequency
|
||||
spectral_radiosity_wavelength power / volume
|
||||
radiant_exitance power / area
|
||||
spectral_exitance_frequency power / area frequency
|
||||
spectral_exitance_wavelength power / volume
|
||||
radiant_exposure energy / area
|
||||
spectral_exposure_frequency ? energy / area frequency
|
||||
spectral_exposure_wavelength energy / volume
|
||||
|
||||
!endcategory
|
||||
|
||||
|
@ -1311,10 +1311,10 @@ spectral_exposure_wavelength J/m^3
|
|||
|
||||
!category photometric "Photometric Units"
|
||||
|
||||
luminous_intensity ? candela
|
||||
luminous_flux ? lumen
|
||||
luminous_energy ? talbot
|
||||
illuminance ? lux
|
||||
luminous_intensity ? cd
|
||||
luminous_flux ? luminous_intensity solid_angle
|
||||
luminous_energy ? luminous_flux time
|
||||
illuminance ? luminous_flux / area
|
||||
|
||||
candle 1.02 candela # Standard unit for luminous intensity
|
||||
hefnerunit 0.9 candle # in use before candela
|
||||
|
@ -1350,7 +1350,7 @@ skot 1e-3 apostilb # measurements relating to dark adapted
|
|||
# eyes.
|
||||
# Luminance measures
|
||||
|
||||
luminance ? nit
|
||||
luminance ? luminous_intensity / area
|
||||
|
||||
nit cd/m^2 # Luminance: the intensity per projected
|
||||
stilb cd / cm^2 # area of an extended luminous source.
|
||||
|
@ -1620,7 +1620,7 @@ C_illum C_apex1961
|
|||
# and a description of how to compute the correction to mean time.
|
||||
#
|
||||
|
||||
time ? second
|
||||
time ? s
|
||||
|
||||
anomalisticyear 365.2596 days # The time between successive
|
||||
# perihelion passages of the
|
||||
|
@ -2982,7 +2982,7 @@ count /pound # For measuring the size of shrimp
|
|||
# Other units of work, energy, power, etc
|
||||
#
|
||||
|
||||
energy ? joule
|
||||
energy ? force length
|
||||
|
||||
# Calories: energy to raise a gram of water one degree celsius
|
||||
|
||||
|
@ -3023,7 +3023,7 @@ therm UStherm
|
|||
celsiusheatunit cal lb K / gram K
|
||||
chu celsiusheatunit
|
||||
|
||||
power ? watt
|
||||
power ? energy / time
|
||||
|
||||
# "Apparent" average power in an AC circuit, the product of rms voltage
|
||||
# and rms current, equal to the true power in watts when voltage and
|
||||
|
@ -3061,14 +3061,14 @@ chevalvapeur metrichorsepower
|
|||
# cross sectional area, t is the time, and L is the length (thickness).
|
||||
# Thermal conductivity is a material property.
|
||||
|
||||
thermal_conductivity ? power / area (temperature_difference/length)
|
||||
thermal_conductivity ? power / area (temperature/length)
|
||||
thermal_resistivity ? 1/thermal_conductivity
|
||||
|
||||
# Thermal conductance is the rate at which heat flows across a given
|
||||
# object, so the area and thickness have been fixed. It depends on
|
||||
# the size of the object and is hence not a material property.
|
||||
|
||||
thermal_conductance ? power / temperature_difference
|
||||
thermal_conductance ? power / temperature
|
||||
thermal_resistance ? 1/thermal_conductance
|
||||
|
||||
# Thermal admittance is the rate of heat flow per area across an
|
||||
|
|
|
@ -6,6 +6,9 @@ use std::collections::BTreeMap;
|
|||
|
||||
use crate::types::{BaseUnit, Dimensionality, Number, Numeric};
|
||||
|
||||
/// Breaks down the dimensionality of a unit into a simpler form that
|
||||
/// uses SI derived units.
|
||||
///
|
||||
/// Uses a single-pass algorithm for finding a suitable decomposition. A
|
||||
/// more robust one would require factorization, but that's expensive.
|
||||
///
|
||||
|
@ -15,10 +18,10 @@ use crate::types::{BaseUnit, Dimensionality, Number, Numeric};
|
|||
/// complex decompositions.
|
||||
pub(crate) fn fast_decompose(
|
||||
value: &Number,
|
||||
quantities: &BTreeMap<Dimensionality, String>,
|
||||
derived_units: &BTreeMap<Dimensionality, String>,
|
||||
) -> Dimensionality {
|
||||
let mut best = None;
|
||||
'outer: for (unit, name) in quantities.iter() {
|
||||
'outer: for (unit, name) in derived_units.iter() {
|
||||
// make sure we aren't doing something weird like introducing new base units
|
||||
for (dim, pow) in unit.iter() {
|
||||
let vpow = value.unit.get(dim).cloned().unwrap_or(0);
|
||||
|
|
|
@ -67,18 +67,14 @@ pub struct Property {
|
|||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum Def {
|
||||
// Named dimension for backwards compat reasons.
|
||||
#[serde(rename = "dimension")]
|
||||
BaseUnit,
|
||||
Canonicalization {
|
||||
of: String,
|
||||
BaseUnit {
|
||||
#[serde(rename = "longName")]
|
||||
long_name: Option<String>,
|
||||
},
|
||||
Prefix {
|
||||
expr: ExprString,
|
||||
},
|
||||
#[serde(rename = "sprefix")]
|
||||
SPrefix {
|
||||
expr: ExprString,
|
||||
#[serde(rename = "isLong")]
|
||||
is_long: bool,
|
||||
},
|
||||
Unit {
|
||||
expr: ExprString,
|
||||
|
@ -91,6 +87,7 @@ pub enum Def {
|
|||
properties: Vec<Property>,
|
||||
},
|
||||
Category {
|
||||
#[serde(rename = "displayName")]
|
||||
display_name: String,
|
||||
},
|
||||
Error {
|
||||
|
|
|
@ -13,12 +13,12 @@ pub(crate) fn search_internal<'a>(
|
|||
query: &str,
|
||||
num_results: usize,
|
||||
) -> Vec<&'a str> {
|
||||
let dimensions = ctx.dimensions.iter().map(|dim| &dim.id[..]);
|
||||
let units = ctx.units.keys().map(|name| &name[..]);
|
||||
let quantities = ctx.quantities.values().map(|name| &name[..]);
|
||||
let substances = ctx.substances.keys().map(|name| &name[..]);
|
||||
let base_units = ctx.registry.base_units.iter().map(|dim| &dim.id[..]);
|
||||
let units = ctx.registry.units.keys().map(|name| &name[..]);
|
||||
let quantities = ctx.registry.quantities.values().map(|name| &name[..]);
|
||||
let substances = ctx.registry.substances.keys().map(|name| &name[..]);
|
||||
|
||||
let iter = dimensions.chain(units).chain(quantities).chain(substances);
|
||||
let iter = base_units.chain(units).chain(quantities).chain(substances);
|
||||
crate::algorithms::search_impl(iter, query, num_results)
|
||||
}
|
||||
|
||||
|
@ -31,7 +31,7 @@ pub fn search(ctx: &Context, query: &str, num_results: usize) -> SearchReply {
|
|||
.lookup(name)
|
||||
.map(|x| x.to_parts(ctx))
|
||||
.or_else(|| {
|
||||
if ctx.substances.get(name).is_some() {
|
||||
if ctx.registry.substances.get(name).is_some() {
|
||||
Some(NumberParts {
|
||||
quantity: Some("substance".to_owned()),
|
||||
..Default::default()
|
||||
|
|
|
@ -2,35 +2,34 @@
|
|||
// 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/.
|
||||
|
||||
use super::Registry;
|
||||
use crate::ast::{DatePattern, Expr, Query};
|
||||
use crate::output::{ConversionReply, Digits, NotFoundError, NumberParts, QueryError, QueryReply};
|
||||
use crate::runtime::Substance;
|
||||
use crate::types::{BaseUnit, BigInt, Dimensionality, Number, Numeric};
|
||||
use crate::{commands, Value};
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// The evaluation context that contains unit definitions.
|
||||
#[derive(Debug)]
|
||||
pub struct Context {
|
||||
pub dimensions: BTreeSet<BaseUnit>,
|
||||
pub canonicalizations: BTreeMap<String, String>,
|
||||
pub units: BTreeMap<String, Number>,
|
||||
pub quantities: BTreeMap<Dimensionality, String>,
|
||||
pub reverse: BTreeMap<Dimensionality, String>,
|
||||
pub prefixes: Vec<(String, Number)>,
|
||||
pub definitions: BTreeMap<String, Expr>,
|
||||
pub docs: BTreeMap<String, String>,
|
||||
pub categories: BTreeMap<String, String>,
|
||||
pub category_names: BTreeMap<String, String>,
|
||||
pub datepatterns: Vec<Vec<DatePattern>>,
|
||||
pub substances: BTreeMap<String, Substance>,
|
||||
pub substance_symbols: BTreeMap<String, String>,
|
||||
pub temporaries: BTreeMap<String, Number>,
|
||||
/// Contains all the information about units.
|
||||
pub registry: Registry,
|
||||
/// Used only during initialization.
|
||||
pub(crate) temporaries: BTreeMap<String, Number>,
|
||||
/// The current time, as set by the caller.
|
||||
///
|
||||
/// This is used instead of directly asking the OS for the time
|
||||
/// since it allows determinism in unit tests, and prevents edge
|
||||
/// cases like `now - now` being non-zero.
|
||||
pub now: DateTime<Local>,
|
||||
pub short_output: bool,
|
||||
/// Enables the use of chrono-humanize. It can be disabled for unit
|
||||
/// tests, as well as in wasm builds where the time API panics.
|
||||
pub use_humanize: bool,
|
||||
/// Whether to save the previous query result and make it available
|
||||
/// as the `ans` variable.
|
||||
pub save_previous_result: bool,
|
||||
/// The previous query result.
|
||||
pub previous_result: Option<Number>,
|
||||
}
|
||||
|
||||
|
@ -45,26 +44,11 @@ impl Context {
|
|||
/// Creates a new, empty context
|
||||
pub fn new() -> Context {
|
||||
Context {
|
||||
short_output: false,
|
||||
registry: Registry::default(),
|
||||
temporaries: BTreeMap::new(),
|
||||
now: Local.timestamp(0, 0),
|
||||
use_humanize: true,
|
||||
save_previous_result: false,
|
||||
|
||||
now: Local.timestamp(0, 0),
|
||||
|
||||
dimensions: BTreeSet::new(),
|
||||
prefixes: vec![],
|
||||
datepatterns: vec![],
|
||||
canonicalizations: BTreeMap::new(),
|
||||
units: BTreeMap::new(),
|
||||
quantities: BTreeMap::new(),
|
||||
reverse: BTreeMap::new(),
|
||||
definitions: BTreeMap::new(),
|
||||
docs: BTreeMap::new(),
|
||||
categories: BTreeMap::new(),
|
||||
category_names: BTreeMap::new(),
|
||||
substances: BTreeMap::new(),
|
||||
substance_symbols: BTreeMap::new(),
|
||||
temporaries: BTreeMap::new(),
|
||||
previous_result: None,
|
||||
}
|
||||
}
|
||||
|
@ -78,119 +62,25 @@ impl Context {
|
|||
}
|
||||
|
||||
pub fn load_dates(&mut self, mut dates: Vec<Vec<DatePattern>>) {
|
||||
self.datepatterns.append(&mut dates)
|
||||
self.registry.datepatterns.append(&mut dates)
|
||||
}
|
||||
|
||||
/// Given a unit name, returns its value if it exists. Supports SI
|
||||
/// prefixes, plurals, bare dimensions like length, and quantities.
|
||||
pub fn lookup(&self, name: &str) -> Option<Number> {
|
||||
fn inner(ctx: &Context, name: &str) -> Option<Number> {
|
||||
if name == "ans" || name == "ANS" || name == "_" {
|
||||
return ctx.previous_result.clone();
|
||||
}
|
||||
if let Some(v) = ctx.temporaries.get(name).cloned() {
|
||||
return Some(v);
|
||||
}
|
||||
if let Some(k) = ctx.dimensions.get(name) {
|
||||
return Some(Number::one_unit(k.to_owned()));
|
||||
}
|
||||
if let Some(v) = ctx.units.get(name).cloned() {
|
||||
return Some(v);
|
||||
}
|
||||
for (unit, quantity) in &ctx.quantities {
|
||||
if name == quantity {
|
||||
return Some(Number {
|
||||
value: Numeric::one(),
|
||||
unit: unit.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
if name == "ans" || name == "ANS" || name == "_" {
|
||||
return self.previous_result.clone();
|
||||
}
|
||||
if let Some(v) = self.temporaries.get(name).cloned() {
|
||||
return Some(v);
|
||||
}
|
||||
|
||||
let outer = |name: &str| -> Option<Number> {
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v);
|
||||
}
|
||||
for &(ref pre, ref value) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = inner(self, &name[pre.len()..]) {
|
||||
return Some((&v * value).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let res = outer(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
// after so that "ks" is kiloseconds
|
||||
if name.ends_with('s') {
|
||||
let name = &name[0..name.len() - 1];
|
||||
outer(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.registry.lookup(name)
|
||||
}
|
||||
|
||||
/// Given a unit name, try to return a canonical name (expanding aliases and such)
|
||||
pub fn canonicalize(&self, name: &str) -> Option<String> {
|
||||
fn inner(ctx: &Context, name: &str) -> Option<String> {
|
||||
if let Some(v) = ctx.canonicalizations.get(name) {
|
||||
return Some(v.clone());
|
||||
}
|
||||
if let Some(k) = ctx.dimensions.get(name) {
|
||||
return Some((*k.id).clone());
|
||||
}
|
||||
if let Some(v) = ctx.definitions.get(name) {
|
||||
if let Expr::Unit { ref name } = *v {
|
||||
if let Some(r) = ctx.canonicalize(&*name) {
|
||||
return Some(r);
|
||||
} else {
|
||||
return Some(name.clone());
|
||||
}
|
||||
} else {
|
||||
// we cannot canonicalize it further
|
||||
return Some(name.to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
let outer = |name: &str| -> Option<String> {
|
||||
if let Some(v) = inner(self, name) {
|
||||
return Some(v);
|
||||
}
|
||||
for &(ref pre, ref val) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = inner(self, &name[pre.len()..]) {
|
||||
let mut pre = pre;
|
||||
for &(ref other, ref otherval) in &self.prefixes {
|
||||
if other.len() > pre.len() && val == otherval {
|
||||
pre = other;
|
||||
}
|
||||
}
|
||||
return Some(format!("{}{}", pre, v));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
let res = outer(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
if name.ends_with('s') {
|
||||
let name = &name[0..name.len() - 1];
|
||||
outer(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.registry.canonicalize(name)
|
||||
}
|
||||
|
||||
/// Describes a value's unit, gives true if the unit is reciprocal
|
||||
|
@ -213,21 +103,23 @@ impl Context {
|
|||
unit: value.unit.clone(),
|
||||
})
|
||||
.unwrap();
|
||||
if let Some(name) = self.quantities.get(&value.unit) {
|
||||
if let Some(name) = self.registry.quantities.get(&value.unit) {
|
||||
write!(buf, "{}", name).unwrap();
|
||||
} else if let Some(name) = square.and_then(|square| self.quantities.get(&square.unit)) {
|
||||
} else if let Some(name) =
|
||||
square.and_then(|square| self.registry.quantities.get(&square.unit))
|
||||
{
|
||||
write!(buf, "{}^2", name).unwrap();
|
||||
} else if let Some(name) = self.quantities.get(&inverse.unit) {
|
||||
} else if let Some(name) = self.registry.quantities.get(&inverse.unit) {
|
||||
recip = true;
|
||||
write!(buf, "{}", name).unwrap();
|
||||
} else {
|
||||
let helper = |dim: &BaseUnit, pow: i64, buf: &mut Vec<u8>| {
|
||||
let unit = Dimensionality::new_dim(dim.clone(), pow);
|
||||
if let Some(name) = self.quantities.get(&unit) {
|
||||
if let Some(name) = self.registry.quantities.get(&unit) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
let unit = Dimensionality::base_unit(dim.clone());
|
||||
if let Some(name) = self.quantities.get(&unit) {
|
||||
if let Some(name) = self.registry.quantities.get(&unit) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
write!(buf, " '{}'", dim).unwrap();
|
||||
|
@ -256,7 +148,7 @@ impl Context {
|
|||
}
|
||||
for (dim, pow) in frac {
|
||||
let unit = Dimensionality::new_dim(dim.clone(), pow);
|
||||
if let Some(name) = self.quantities.get(&unit) {
|
||||
if let Some(name) = self.registry.quantities.get(&unit) {
|
||||
write!(buf, " {}", name).unwrap();
|
||||
} else {
|
||||
helper(dim, pow, &mut buf);
|
||||
|
|
|
@ -374,6 +374,7 @@ pub fn parse(iter: &mut Iter<'_>) -> Defs {
|
|||
name,
|
||||
def: Rc::new(Def::Prefix {
|
||||
expr: ExprString(expr),
|
||||
is_long: false,
|
||||
}),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
|
@ -381,8 +382,9 @@ pub fn parse(iter: &mut Iter<'_>) -> Defs {
|
|||
} else {
|
||||
map.push(DefEntry {
|
||||
name,
|
||||
def: Rc::new(Def::SPrefix {
|
||||
def: Rc::new(Def::Prefix {
|
||||
expr: ExprString(expr),
|
||||
is_long: true,
|
||||
}),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
|
@ -391,30 +393,22 @@ pub fn parse(iter: &mut Iter<'_>) -> Defs {
|
|||
} else {
|
||||
// unit
|
||||
if let Some(&Token::Bang) = iter.peek() {
|
||||
// dimension
|
||||
// base unit
|
||||
iter.next();
|
||||
if let Some(Token::Ident(ref long)) = iter.peek().cloned() {
|
||||
|
||||
let long_name = if let Some(Token::Ident(ref long)) = iter.peek().cloned() {
|
||||
iter.next();
|
||||
map.push(DefEntry {
|
||||
name: name.clone(),
|
||||
def: Rc::new(Def::BaseUnit),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
});
|
||||
map.push(DefEntry {
|
||||
name: long.clone(),
|
||||
def: Rc::new(Def::Canonicalization { of: name.clone() }),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
});
|
||||
Some(long.clone())
|
||||
} else {
|
||||
map.push(DefEntry {
|
||||
name: name.clone(),
|
||||
def: Rc::new(Def::BaseUnit),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
});
|
||||
}
|
||||
None
|
||||
};
|
||||
|
||||
map.push(DefEntry {
|
||||
name: name.clone(),
|
||||
def: Rc::new(Def::BaseUnit { long_name }),
|
||||
doc: doc.take(),
|
||||
category: category.clone(),
|
||||
});
|
||||
} else if let Some(&Token::Question) = iter.peek() {
|
||||
// quantity
|
||||
iter.next();
|
||||
|
|
|
@ -3,40 +3,36 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use super::Context;
|
||||
use crate::ast::{BinOpExpr, Def, DefEntry, Defs, Expr};
|
||||
use crate::ast::{BinOpExpr, BinOpType, Def, DefEntry, Defs, Expr, UnaryOpExpr, UnaryOpType};
|
||||
use crate::runtime::{Properties, Property, Substance, Value};
|
||||
use crate::types::{BaseUnit, Number, Numeric};
|
||||
use crate::types::{BaseUnit, Dimensionality, Number, Numeric};
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::convert::TryInto;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
enum Name {
|
||||
Unit(Rc<String>),
|
||||
Prefix(Rc<String>),
|
||||
Quantity(Rc<String>),
|
||||
Category(Rc<String>),
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
|
||||
enum Namespace {
|
||||
Unit,
|
||||
Prefix,
|
||||
Quantity,
|
||||
Category,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
fn name(&self) -> String {
|
||||
match &self {
|
||||
Name::Unit(ref name)
|
||||
| Name::Prefix(ref name)
|
||||
| Name::Quantity(ref name)
|
||||
| Name::Category(ref name) => (**name).clone(),
|
||||
}
|
||||
}
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
|
||||
struct Id {
|
||||
namespace: Namespace,
|
||||
name: Rc<String>,
|
||||
}
|
||||
|
||||
struct Resolver {
|
||||
interned: BTreeSet<Rc<String>>,
|
||||
input: BTreeMap<Name, Rc<Def>>,
|
||||
sorted: Vec<Name>,
|
||||
unmarked: BTreeSet<Name>,
|
||||
temp_marks: BTreeSet<Name>,
|
||||
docs: BTreeMap<Name, String>,
|
||||
categories: BTreeMap<Name, String>,
|
||||
input: BTreeMap<Id, Rc<Def>>,
|
||||
sorted: Vec<Id>,
|
||||
unmarked: BTreeSet<Id>,
|
||||
temp_marks: BTreeSet<Id>,
|
||||
docs: BTreeMap<Id, String>,
|
||||
categories: BTreeMap<Id, String>,
|
||||
}
|
||||
|
||||
impl Resolver {
|
||||
|
@ -52,106 +48,221 @@ impl Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
fn lookup(&mut self, name: &Rc<String>) -> bool {
|
||||
fn inner(ctx: &mut Resolver, name: &Rc<String>) -> bool {
|
||||
[Name::Unit, Name::Prefix, Name::Quantity].iter().any(|f| {
|
||||
let unit = f(name.clone());
|
||||
ctx.input.contains_key(&unit) && {
|
||||
ctx.visit(&unit);
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let mut outer = |name: &Rc<String>| -> bool {
|
||||
if inner(self, name) {
|
||||
fn lookup_exact(&mut self, name: &Rc<String>, context: Namespace) -> bool {
|
||||
let to_check: &[Namespace] = match context {
|
||||
Namespace::Quantity => &[Namespace::Quantity],
|
||||
_ => &[Namespace::Unit, Namespace::Prefix, Namespace::Quantity],
|
||||
};
|
||||
for namespace in to_check.iter().copied() {
|
||||
let id = Id {
|
||||
namespace,
|
||||
name: name.clone(),
|
||||
};
|
||||
if self.input.contains_key(&id) {
|
||||
self.visit(&id);
|
||||
return true;
|
||||
}
|
||||
let mut found = vec![];
|
||||
for pre in self.input.keys() {
|
||||
if let Name::Prefix(ref pre) = *pre {
|
||||
if (*name).starts_with(&**pre) {
|
||||
found.push(pre.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
found.into_iter().any(|pre| {
|
||||
inner(self, &Rc::new(name[pre.len()..].to_owned())) && {
|
||||
let unit = Name::Prefix(pre);
|
||||
self.visit(&unit);
|
||||
true
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
outer(name)
|
||||
fn lookup_with_prefix(&mut self, name: &Rc<String>, context: Namespace) -> bool {
|
||||
if self.lookup_exact(name, context) {
|
||||
return true;
|
||||
}
|
||||
let mut found = vec![];
|
||||
for prefix in self.input.keys() {
|
||||
if prefix.namespace == Namespace::Prefix && name.starts_with(&prefix.name[..]) {
|
||||
found.push(prefix.clone());
|
||||
}
|
||||
}
|
||||
found.into_iter().any(|pre| {
|
||||
self.lookup_exact(&Rc::new(name[pre.name.len()..].to_owned()), context) && {
|
||||
self.visit(&pre);
|
||||
true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn lookup(&mut self, name: &Rc<String>, context: Namespace) -> bool {
|
||||
self.lookup_with_prefix(name, context)
|
||||
|| name.ends_with('s') && {
|
||||
let name = &Rc::new(name[0..name.len() - 1].to_owned());
|
||||
outer(name)
|
||||
self.lookup_with_prefix(name, context)
|
||||
}
|
||||
}
|
||||
|
||||
fn eval(&mut self, expr: &Expr) {
|
||||
fn eval(&mut self, expr: &Expr, context: Namespace) {
|
||||
match *expr {
|
||||
Expr::Unit { ref name } => {
|
||||
let name = self.intern(name);
|
||||
self.lookup(&name);
|
||||
self.lookup(&name, context);
|
||||
}
|
||||
Expr::BinOp(BinOpExpr {
|
||||
ref left,
|
||||
ref right,
|
||||
..
|
||||
}) => {
|
||||
self.eval(left);
|
||||
self.eval(right);
|
||||
self.eval(left, context);
|
||||
self.eval(right, context);
|
||||
}
|
||||
Expr::UnaryOp(ref unaryop) => self.eval(&unaryop.expr),
|
||||
Expr::Of { ref expr, .. } => self.eval(expr),
|
||||
Expr::UnaryOp(ref unaryop) => self.eval(&unaryop.expr, context),
|
||||
Expr::Of { ref expr, .. } => self.eval(expr, context),
|
||||
|
||||
Expr::Mul { ref exprs }
|
||||
| Expr::Call {
|
||||
args: ref exprs, ..
|
||||
} => {
|
||||
for expr in exprs {
|
||||
self.eval(expr);
|
||||
self.eval(expr, context);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&mut self, name: &Name) {
|
||||
if self.temp_marks.get(name).is_some() {
|
||||
println!("Unit {:?} has a dependency cycle", name);
|
||||
fn visit(&mut self, id: &Id) {
|
||||
if self.temp_marks.get(id).is_some() {
|
||||
println!("Unit {:?} has a dependency cycle", id);
|
||||
return;
|
||||
}
|
||||
if self.unmarked.get(name).is_some() {
|
||||
self.temp_marks.insert(name.clone());
|
||||
if let Some(v) = self.input.get(name).cloned() {
|
||||
if self.unmarked.get(id).is_some() {
|
||||
self.temp_marks.insert(id.clone());
|
||||
if let Some(v) = self.input.get(id).cloned() {
|
||||
match *v {
|
||||
Def::Prefix { ref expr }
|
||||
| Def::SPrefix { ref expr }
|
||||
Def::Prefix { ref expr, .. }
|
||||
| Def::Unit { ref expr }
|
||||
| Def::Quantity { ref expr } => self.eval(expr),
|
||||
Def::Canonicalization { ref of } => {
|
||||
self.lookup(&Rc::new(of.clone()));
|
||||
}
|
||||
| Def::Quantity { ref expr } => self.eval(expr, id.namespace),
|
||||
Def::Substance { ref properties, .. } => {
|
||||
for prop in properties {
|
||||
self.eval(&prop.input);
|
||||
self.eval(&prop.output);
|
||||
self.eval(&prop.input, id.namespace);
|
||||
self.eval(&prop.output, id.namespace);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
self.unmarked.remove(name);
|
||||
self.temp_marks.remove(name);
|
||||
self.sorted.push(name.clone());
|
||||
self.unmarked.remove(id);
|
||||
self.temp_marks.remove(id);
|
||||
self.sorted.push(id.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_prefix(prefixes: &BTreeMap<String, Numeric>, expr: &Expr) -> Result<Numeric, String> {
|
||||
match expr {
|
||||
Expr::Const { ref value } => Ok(value.clone()),
|
||||
Expr::Unit { name: ref other } => {
|
||||
if let Some(value) = prefixes.get(other).cloned() {
|
||||
Ok(value)
|
||||
} else {
|
||||
Err(format!("References non-existent prefix {other}"))
|
||||
}
|
||||
}
|
||||
Expr::BinOp(BinOpExpr {
|
||||
op: BinOpType::Frac,
|
||||
left,
|
||||
right,
|
||||
}) => {
|
||||
let left = eval_prefix(prefixes, &*left)?;
|
||||
let right = eval_prefix(prefixes, &*right)?;
|
||||
Ok(&left / &right)
|
||||
}
|
||||
Expr::BinOp(BinOpExpr {
|
||||
op: BinOpType::Pow,
|
||||
left,
|
||||
right,
|
||||
}) => {
|
||||
let left = eval_prefix(prefixes, &*left)?;
|
||||
let right = eval_prefix(prefixes, &*right)?;
|
||||
let right: i32 = right
|
||||
.to_int()
|
||||
.and_then(|value| value.try_into().ok())
|
||||
.ok_or_else(|| "Exponent is too big".to_string())?;
|
||||
Ok(left.pow(right))
|
||||
}
|
||||
Expr::UnaryOp(UnaryOpExpr {
|
||||
op: UnaryOpType::Negative,
|
||||
expr,
|
||||
}) => {
|
||||
let value = eval_prefix(prefixes, &*expr)?;
|
||||
Ok(&value * &Numeric::from(-1))
|
||||
}
|
||||
ref expr => Err(format!("Not a numeric constant: {expr}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_quantity(
|
||||
base_units: &BTreeSet<BaseUnit>,
|
||||
quantities: &BTreeMap<String, Dimensionality>,
|
||||
expr: &Expr,
|
||||
) -> Result<Dimensionality, String> {
|
||||
match *expr {
|
||||
Expr::Unit { ref name } => {
|
||||
if let Some(base) = base_units.get(&name[..]) {
|
||||
Ok(Dimensionality::base_unit(base.clone()))
|
||||
} else if let Some(quantity) = quantities.get(&name[..]) {
|
||||
Ok(quantity.clone())
|
||||
} else {
|
||||
Err(format!("No quantity or base unit named {}", name))
|
||||
}
|
||||
}
|
||||
Expr::Const { ref value } if *value == Numeric::one() => Ok(Dimensionality::default()),
|
||||
Expr::Mul { ref exprs } => {
|
||||
exprs
|
||||
.iter()
|
||||
.fold(Ok(Dimensionality::default()), |acc, value| {
|
||||
let acc = acc?;
|
||||
let value = eval_quantity(base_units, quantities, value)?;
|
||||
Ok(&acc * &value)
|
||||
})
|
||||
}
|
||||
Expr::BinOp(BinOpExpr {
|
||||
op: BinOpType::Frac,
|
||||
ref left,
|
||||
ref right,
|
||||
}) => {
|
||||
let left = eval_quantity(base_units, quantities, &*left)?;
|
||||
let right = eval_quantity(base_units, quantities, &*right)?;
|
||||
Ok(&left / &right)
|
||||
}
|
||||
Expr::BinOp(BinOpExpr {
|
||||
op: BinOpType::Pow,
|
||||
ref left,
|
||||
ref right,
|
||||
}) => {
|
||||
let left = eval_quantity(base_units, quantities, &*left)?;
|
||||
match **right {
|
||||
Expr::Const { ref value } => {
|
||||
let value = value
|
||||
.to_int()
|
||||
.ok_or_else(|| "RHS of `^` is too big".to_string())?;
|
||||
Ok(left.pow(value))
|
||||
}
|
||||
Expr::UnaryOp(UnaryOpExpr {
|
||||
op: UnaryOpType::Negative,
|
||||
ref expr,
|
||||
}) => {
|
||||
if let Expr::Const { ref value } = **expr {
|
||||
let value = -value
|
||||
.to_int()
|
||||
.ok_or_else(|| "RHS of `^` is too big".to_string())?;
|
||||
Ok(left.pow(value))
|
||||
} else {
|
||||
Err(format!("RHS of `^` must be a constant: {expr}"))
|
||||
}
|
||||
}
|
||||
_ => Err(format!("RHS of `^` must be a constant: {expr}")),
|
||||
}
|
||||
}
|
||||
Expr::UnaryOp(UnaryOpExpr {
|
||||
op: UnaryOpType::Negative,
|
||||
ref expr,
|
||||
}) => Ok(eval_quantity(base_units, quantities, &*expr)?.recip()),
|
||||
ref expr => Err(format!("Invalid expression in quantity: {expr}")),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
||||
let mut resolver = Resolver {
|
||||
interned: BTreeSet::new(),
|
||||
|
@ -169,31 +280,58 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
category,
|
||||
} in defs.defs.into_iter()
|
||||
{
|
||||
// Base units can have two different names from one Def, handle that here.
|
||||
if let Def::BaseUnit {
|
||||
long_name: Some(ref long_name),
|
||||
} = *def
|
||||
{
|
||||
let long_name = resolver.intern(long_name);
|
||||
resolver.input.insert(
|
||||
Id {
|
||||
namespace: Namespace::Unit,
|
||||
name: long_name,
|
||||
},
|
||||
def.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let name = resolver.intern(&name);
|
||||
let unit = match *def {
|
||||
Def::Prefix { .. } | Def::SPrefix { .. } => Name::Prefix(name),
|
||||
Def::Quantity { .. } => Name::Quantity(name),
|
||||
Def::Category { .. } => Name::Category(name),
|
||||
_ => Name::Unit(name),
|
||||
let id = match *def {
|
||||
Def::Prefix { .. } => Id {
|
||||
namespace: Namespace::Prefix,
|
||||
name,
|
||||
},
|
||||
Def::Quantity { .. } => Id {
|
||||
namespace: Namespace::Quantity,
|
||||
name,
|
||||
},
|
||||
Def::Category { .. } => Id {
|
||||
namespace: Namespace::Category,
|
||||
name,
|
||||
},
|
||||
_ => Id {
|
||||
namespace: Namespace::Unit,
|
||||
name,
|
||||
},
|
||||
};
|
||||
if let Some(doc) = doc {
|
||||
resolver.docs.insert(unit.clone(), doc);
|
||||
resolver.docs.insert(id.clone(), doc);
|
||||
}
|
||||
if let Some(category) = category {
|
||||
resolver.categories.insert(unit.clone(), category);
|
||||
resolver.categories.insert(id.clone(), category);
|
||||
}
|
||||
if resolver.input.insert(unit.clone(), def).is_some() {
|
||||
let (ty, name) = match unit {
|
||||
Name::Prefix(ref name) => ("prefixes", name),
|
||||
Name::Quantity(ref name) => ("quantities", name),
|
||||
Name::Unit(ref name) => ("units", name),
|
||||
Name::Category(ref name) => ("category", name),
|
||||
if resolver.input.insert(id.clone(), def).is_some() {
|
||||
let namespace = match id.namespace {
|
||||
Namespace::Prefix => "prefixes",
|
||||
Namespace::Quantity => "quantities",
|
||||
Namespace::Unit => "units",
|
||||
Namespace::Category => "category",
|
||||
};
|
||||
if ty != "category" {
|
||||
println!("warning: multiple {} named {}", ty, name);
|
||||
if namespace != "category" {
|
||||
println!("warning: multiple {} named {}", namespace, id.name);
|
||||
}
|
||||
}
|
||||
resolver.unmarked.insert(unit);
|
||||
resolver.unmarked.insert(id);
|
||||
}
|
||||
|
||||
while let Some(name) = resolver.unmarked.iter().next().cloned() {
|
||||
|
@ -206,50 +344,57 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
(name, res)
|
||||
});
|
||||
|
||||
let mut reverse = BTreeSet::new();
|
||||
reverse.insert("newton");
|
||||
reverse.insert("pascal");
|
||||
reverse.insert("joule");
|
||||
reverse.insert("watt");
|
||||
reverse.insert("coulomb");
|
||||
reverse.insert("volt");
|
||||
reverse.insert("ohm");
|
||||
reverse.insert("siemens");
|
||||
reverse.insert("farad");
|
||||
reverse.insert("weber");
|
||||
reverse.insert("henry");
|
||||
reverse.insert("tesla");
|
||||
reverse.insert("lumen");
|
||||
reverse.insert("lux");
|
||||
reverse.insert("gray");
|
||||
reverse.insert("katal");
|
||||
let mut decomposition_units = BTreeSet::new();
|
||||
decomposition_units.insert("newton");
|
||||
decomposition_units.insert("pascal");
|
||||
decomposition_units.insert("joule");
|
||||
decomposition_units.insert("watt");
|
||||
decomposition_units.insert("coulomb");
|
||||
decomposition_units.insert("volt");
|
||||
decomposition_units.insert("ohm");
|
||||
decomposition_units.insert("siemens");
|
||||
decomposition_units.insert("farad");
|
||||
decomposition_units.insert("weber");
|
||||
decomposition_units.insert("henry");
|
||||
decomposition_units.insert("tesla");
|
||||
decomposition_units.insert("lumen");
|
||||
decomposition_units.insert("lux");
|
||||
decomposition_units.insert("gray");
|
||||
decomposition_units.insert("katal");
|
||||
|
||||
for (name, def) in udefs {
|
||||
let name = name.name();
|
||||
let mut prefix_lookup = BTreeMap::new();
|
||||
let mut quantities = BTreeMap::new();
|
||||
|
||||
for (id, def) in udefs {
|
||||
let name = id.name.to_string();
|
||||
match *def {
|
||||
Def::BaseUnit => {
|
||||
ctx.dimensions.insert(BaseUnit::new(&*name));
|
||||
}
|
||||
Def::Canonicalization { ref of } => {
|
||||
ctx.canonicalizations.insert(of.clone(), name.clone());
|
||||
match ctx.lookup(of) {
|
||||
Some(v) => {
|
||||
ctx.definitions
|
||||
.insert(name.clone(), Expr::new_unit(of.clone()));
|
||||
ctx.units.insert(name.clone(), v);
|
||||
}
|
||||
None => {
|
||||
println!("Canonicalization {} is malformed: {} not found", name, of)
|
||||
}
|
||||
Def::BaseUnit { ref long_name } => {
|
||||
let unit = BaseUnit::new(&*name);
|
||||
ctx.registry.base_units.insert(unit.clone());
|
||||
if let Some(long_name) = long_name {
|
||||
ctx.registry
|
||||
.base_unit_long_names
|
||||
.insert(name.clone(), long_name.clone());
|
||||
ctx.registry
|
||||
.definitions
|
||||
.insert(long_name.clone(), Expr::new_unit(name.clone()));
|
||||
ctx.registry
|
||||
.units
|
||||
.insert(long_name.clone(), Number::one_unit(unit));
|
||||
}
|
||||
}
|
||||
Def::Unit { ref expr } => match ctx.eval(expr) {
|
||||
Ok(Value::Number(v)) => {
|
||||
if v.value == Numeric::one() && reverse.contains(&*name) {
|
||||
ctx.reverse.insert(v.unit.clone(), name.clone());
|
||||
// This is one of the SI derived units, so put it in the map.
|
||||
if v.value == Numeric::one() && decomposition_units.contains(&*name) {
|
||||
ctx.registry
|
||||
.decomposition_units
|
||||
.insert(v.unit.clone(), name.clone());
|
||||
}
|
||||
ctx.definitions.insert(name.clone(), expr.0.clone());
|
||||
ctx.units.insert(name.clone(), v);
|
||||
ctx.registry
|
||||
.definitions
|
||||
.insert(name.clone(), expr.0.clone());
|
||||
ctx.registry.units.insert(name.clone(), v);
|
||||
}
|
||||
Ok(Value::Substance(sub)) => {
|
||||
let sub = if sub.properties.name.contains('+') {
|
||||
|
@ -257,41 +402,45 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
} else {
|
||||
sub
|
||||
};
|
||||
if ctx.substances.insert(name.clone(), sub).is_some() {
|
||||
if ctx.registry.substances.insert(name.clone(), sub).is_some() {
|
||||
println!("Warning: Conflicting substances for {}", name);
|
||||
}
|
||||
}
|
||||
Ok(_) => println!("Unit {} is not a number", name),
|
||||
Err(e) => println!("Unit {} is malformed: {}", name, e),
|
||||
},
|
||||
Def::Prefix { ref expr } => match ctx.eval(expr) {
|
||||
Ok(Value::Number(v)) => {
|
||||
ctx.prefixes.push((name.clone(), v));
|
||||
}
|
||||
Ok(_) => println!("Prefix {} is not a number", name),
|
||||
Err(e) => println!("Prefix {} is malformed: {}", name, e),
|
||||
},
|
||||
Def::SPrefix { ref expr } => match ctx.eval(expr) {
|
||||
Ok(Value::Number(v)) => {
|
||||
ctx.prefixes.push((name.clone(), v.clone()));
|
||||
ctx.units.insert(name.clone(), v);
|
||||
}
|
||||
Ok(_) => println!("Prefix {} is not a number", name),
|
||||
Err(e) => println!("Prefix {} is malformed: {}", name, e),
|
||||
},
|
||||
Def::Quantity { ref expr } => match ctx.eval(expr) {
|
||||
Ok(Value::Number(v)) => {
|
||||
let res = ctx.quantities.insert(v.unit, name.clone());
|
||||
if !ctx.definitions.contains_key(&name) {
|
||||
ctx.definitions.insert(name.clone(), expr.0.clone());
|
||||
}
|
||||
if let Some(old) = res {
|
||||
println!("Warning: Conflicting quantities {} and {}", name, old);
|
||||
Def::Prefix { ref expr, is_long } => match eval_prefix(&prefix_lookup, &expr.0) {
|
||||
Ok(value) => {
|
||||
prefix_lookup.insert(name.clone(), value.clone());
|
||||
ctx.registry.prefixes.push((name.clone(), value.clone()));
|
||||
if is_long {
|
||||
ctx.registry
|
||||
.units
|
||||
.insert(name.clone(), Number::new(value.clone()));
|
||||
}
|
||||
}
|
||||
Ok(_) => println!("Quantity {} is not a number", name),
|
||||
Err(e) => println!("Quantity {} is malformed: {}", name, e),
|
||||
Err(err) => println!("Prefix {name}: {err}"),
|
||||
},
|
||||
Def::Quantity { ref expr } => {
|
||||
match eval_quantity(&ctx.registry.base_units, &quantities, &expr.0) {
|
||||
Ok(dimensionality) => {
|
||||
quantities.insert(name.clone(), dimensionality.clone());
|
||||
let res = ctx
|
||||
.registry
|
||||
.quantities
|
||||
.insert(dimensionality.clone(), name.clone());
|
||||
if !ctx.registry.definitions.contains_key(&name) {
|
||||
ctx.registry
|
||||
.definitions
|
||||
.insert(name.clone(), expr.0.clone());
|
||||
}
|
||||
if let Some(old) = res {
|
||||
println!("Warning: Conflicting quantities {} and {}", name, old);
|
||||
}
|
||||
}
|
||||
Err(err) => println!("Quantity {name}: {err}"),
|
||||
}
|
||||
}
|
||||
Def::Substance {
|
||||
ref properties,
|
||||
ref symbol,
|
||||
|
@ -370,7 +519,7 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
ctx.temporaries.clear();
|
||||
match res {
|
||||
Ok(res) => {
|
||||
ctx.substances.insert(
|
||||
ctx.registry.substances.insert(
|
||||
name.clone(),
|
||||
Substance {
|
||||
amount: Number::one(),
|
||||
|
@ -381,14 +530,17 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
},
|
||||
);
|
||||
if let Some(ref symbol) = symbol {
|
||||
ctx.substance_symbols.insert(symbol.clone(), name.clone());
|
||||
ctx.registry
|
||||
.substance_symbols
|
||||
.insert(symbol.clone(), name.clone());
|
||||
}
|
||||
}
|
||||
Err(e) => println!("Substance {} is malformed: {}", name, e),
|
||||
}
|
||||
}
|
||||
Def::Category { ref display_name } => {
|
||||
ctx.category_names
|
||||
ctx.registry
|
||||
.category_names
|
||||
.insert(name.clone(), display_name.clone());
|
||||
}
|
||||
Def::Error { ref message } => println!("Def {}: {}", name, message),
|
||||
|
@ -396,15 +548,15 @@ pub(crate) fn load_defs(ctx: &mut Context, defs: Defs) {
|
|||
}
|
||||
|
||||
for (name, val) in resolver.docs {
|
||||
let name = name.name();
|
||||
if ctx.docs.insert(name.clone(), val).is_some() {
|
||||
let name = name.name.to_string();
|
||||
if ctx.registry.docs.insert(name.clone(), val).is_some() {
|
||||
println!("Doc conflict for {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
for (name, val) in resolver.categories {
|
||||
let name = name.name();
|
||||
if ctx.categories.insert(name.clone(), val).is_some() {
|
||||
let name = name.name.to_string();
|
||||
if ctx.registry.categories.insert(name.clone(), val).is_some() {
|
||||
println!("Category conflict for {}", name);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@
|
|||
mod context;
|
||||
pub mod gnu_units;
|
||||
mod load;
|
||||
mod registry;
|
||||
|
||||
pub use context::Context;
|
||||
pub use registry::Registry;
|
||||
|
||||
pub(crate) use load::load_defs;
|
||||
|
|
145
core/src/loader/registry.rs
Normal file
145
core/src/loader/registry.rs
Normal file
|
@ -0,0 +1,145 @@
|
|||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use crate::{
|
||||
ast::{DatePattern, Expr},
|
||||
runtime::Substance,
|
||||
types::{BaseUnit, Dimensionality, Number, Numeric},
|
||||
};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Registry {
|
||||
/// Contains the base units, e.g. `kg`, `bit`.
|
||||
pub base_units: BTreeSet<BaseUnit>,
|
||||
/// Mappings from short forms of base units to long forms, e.g. `kg` → `kilogram`.
|
||||
pub base_unit_long_names: BTreeMap<String, String>,
|
||||
/// Contains numerical values of units.
|
||||
pub units: BTreeMap<String, Number>,
|
||||
/// Maps dimensionality to named physical quantities like `energy`.
|
||||
pub quantities: BTreeMap<Dimensionality, String>,
|
||||
/// Maps dimensionality to names of SI derived units (newton,
|
||||
/// pascal, etc.) for showing simplified forms of units.
|
||||
pub decomposition_units: BTreeMap<Dimensionality, String>,
|
||||
/// A list of prefixes that can be applied to units, like `kilo`.
|
||||
pub prefixes: Vec<(String, Numeric)>,
|
||||
/// Contains the original expressions defining a unit.
|
||||
pub definitions: BTreeMap<String, Expr>,
|
||||
/// Contains documentation strings.
|
||||
pub docs: BTreeMap<String, String>,
|
||||
/// Maps unit names to category IDs.
|
||||
pub categories: BTreeMap<String, String>,
|
||||
/// Maps category IDs to display names.
|
||||
pub category_names: BTreeMap<String, String>,
|
||||
/// Used for matching date formats.
|
||||
pub datepatterns: Vec<Vec<DatePattern>>,
|
||||
/// Objects or materials that have certain properties.
|
||||
pub substances: BTreeMap<String, Substance>,
|
||||
/// Maps elemental names (like `He`) to substance names (`helium`),
|
||||
/// used for parsing molecular formulas, e.g. `H2O`.
|
||||
pub substance_symbols: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
fn lookup_exact(&self, name: &str) -> Option<Number> {
|
||||
if let Some(k) = self.base_units.get(name) {
|
||||
return Some(Number::one_unit(k.to_owned()));
|
||||
}
|
||||
if let Some(v) = self.units.get(name).cloned() {
|
||||
return Some(v);
|
||||
}
|
||||
for (unit, quantity) in &self.quantities {
|
||||
if name == quantity {
|
||||
return Some(Number {
|
||||
value: Numeric::one(),
|
||||
unit: unit.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn lookup_with_prefix(&self, name: &str) -> Option<Number> {
|
||||
if let Some(v) = self.lookup_exact(name) {
|
||||
return Some(v);
|
||||
}
|
||||
for &(ref pre, ref value) in &self.prefixes {
|
||||
if name.starts_with(pre) {
|
||||
if let Some(v) = self.lookup_exact(&name[pre.len()..]) {
|
||||
return Some((&v * &Number::new(value.clone())).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn lookup(&self, name: &str) -> Option<Number> {
|
||||
let res = self.lookup_with_prefix(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Check for plurals, but only do this after exhausting every
|
||||
// other possibility, so that `ks` is kiloseconds instead of
|
||||
// kelvin.
|
||||
if let Some(name) = name.strip_suffix('s') {
|
||||
self.lookup_with_prefix(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_exact(&self, name: &str) -> Option<String> {
|
||||
if let Some(v) = self.base_unit_long_names.get(name) {
|
||||
return Some(v.clone());
|
||||
}
|
||||
if let Some(base_unit) = self.base_units.get(name) {
|
||||
return Some(base_unit.to_string());
|
||||
}
|
||||
if let Some(expr) = self.definitions.get(name) {
|
||||
if let Expr::Unit { ref name } = *expr {
|
||||
if let Some(canonicalized) = self.canonicalize(&*name) {
|
||||
return Some(canonicalized);
|
||||
} else {
|
||||
return Some(name.clone());
|
||||
}
|
||||
} else {
|
||||
// we cannot canonicalize it further
|
||||
return Some(name.to_owned());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn canonicalize_with_prefix(&self, name: &str) -> Option<String> {
|
||||
if let Some(v) = self.canonicalize_exact(name) {
|
||||
return Some(v);
|
||||
}
|
||||
for &(ref prefix, ref value) in &self.prefixes {
|
||||
if let Some(name) = name.strip_prefix(prefix) {
|
||||
if let Some(canonicalized) = self.canonicalize_exact(name) {
|
||||
let mut prefix = prefix;
|
||||
for &(ref other, ref otherval) in &self.prefixes {
|
||||
if other.len() > prefix.len() && value == otherval {
|
||||
prefix = other;
|
||||
}
|
||||
}
|
||||
return Some(format!("{}{}", prefix, canonicalized));
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Given a unit name, try to return a canonical name (expanding aliases and such)
|
||||
pub fn canonicalize(&self, name: &str) -> Option<String> {
|
||||
let res = self.canonicalize_with_prefix(name);
|
||||
if res.is_some() {
|
||||
return res;
|
||||
}
|
||||
|
||||
if let Some(name) = name.strip_suffix('s') {
|
||||
self.canonicalize_with_prefix(name)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
|
@ -357,7 +357,7 @@ fn attempt(
|
|||
|
||||
pub fn try_decode(date: &[DateToken], context: &Context) -> Result<GenericDateTime, String> {
|
||||
let mut best = None;
|
||||
for pat in &context.datepatterns {
|
||||
for pat in &context.registry.datepatterns {
|
||||
match attempt(context.now, date, pat) {
|
||||
Ok(datetime) => return Ok(datetime),
|
||||
Err((e, c)) => {
|
||||
|
|
|
@ -29,15 +29,32 @@ pub(crate) fn eval_expr(ctx: &Context, expr: &Expr) -> Result<Value, QueryError>
|
|||
Expr::Unit { ref name } => ctx
|
||||
.lookup(name)
|
||||
.map(Value::Number)
|
||||
.or_else(|| ctx.substances.get(name).cloned().map(Value::Substance))
|
||||
.or_else(|| {
|
||||
ctx.substance_symbols.get(name).and_then(|full_name| {
|
||||
ctx.substances.get(full_name).cloned().map(Value::Substance)
|
||||
})
|
||||
ctx.registry
|
||||
.substances
|
||||
.get(name)
|
||||
.cloned()
|
||||
.map(Value::Substance)
|
||||
})
|
||||
.or_else(|| {
|
||||
formula::substance_from_formula(name, &ctx.substance_symbols, &ctx.substances)
|
||||
.map(Value::Substance)
|
||||
ctx.registry
|
||||
.substance_symbols
|
||||
.get(name)
|
||||
.and_then(|full_name| {
|
||||
ctx.registry
|
||||
.substances
|
||||
.get(full_name)
|
||||
.cloned()
|
||||
.map(Value::Substance)
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
formula::substance_from_formula(
|
||||
name,
|
||||
&ctx.registry.substance_symbols,
|
||||
&ctx.registry.substances,
|
||||
)
|
||||
.map(Value::Substance)
|
||||
})
|
||||
.ok_or_else(|| QueryError::NotFound(ctx.unknown_unit_err(name))),
|
||||
Expr::Quote { ref string } => Ok(Value::Number(Number::one_unit(BaseUnit::new(string)))),
|
||||
|
@ -643,15 +660,15 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
match *expr {
|
||||
Query::Expr(Expr::Unit { ref name })
|
||||
if {
|
||||
let a = ctx.definitions.contains_key(name);
|
||||
let a = ctx.registry.definitions.contains_key(name);
|
||||
let b = ctx
|
||||
.canonicalize(name)
|
||||
.map(|x| ctx.definitions.contains_key(&*x))
|
||||
.map(|x| ctx.registry.definitions.contains_key(&*x))
|
||||
.unwrap_or(false);
|
||||
let c = ctx.dimensions.contains(&**name);
|
||||
let c = ctx.registry.base_units.contains(&**name);
|
||||
let d = ctx
|
||||
.canonicalize(name)
|
||||
.map(|x| ctx.dimensions.contains(&*x))
|
||||
.map(|x| ctx.registry.base_units.contains(&*x))
|
||||
.unwrap_or(false);
|
||||
a || b || c || d
|
||||
} =>
|
||||
|
@ -659,22 +676,23 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
let mut name = name.clone();
|
||||
let mut canon = ctx.canonicalize(&name).unwrap_or_else(|| name.clone());
|
||||
while let Some(&Expr::Unit { name: ref unit }) = {
|
||||
ctx.definitions
|
||||
ctx.registry
|
||||
.definitions
|
||||
.get(&name)
|
||||
.or_else(|| ctx.definitions.get(&*canon))
|
||||
.or_else(|| ctx.registry.definitions.get(&*canon))
|
||||
} {
|
||||
if ctx.dimensions.contains(&*name) {
|
||||
if ctx.registry.base_units.contains(&*name) {
|
||||
break;
|
||||
}
|
||||
let unit_canon = ctx.canonicalize(unit).unwrap_or_else(|| unit.clone());
|
||||
if ctx.dimensions.contains(&**unit) {
|
||||
if ctx.registry.base_units.contains(&**unit) {
|
||||
name = unit.clone();
|
||||
canon = unit_canon;
|
||||
break;
|
||||
}
|
||||
if ctx.definitions.get(unit).is_none() {
|
||||
if ctx.definitions.get(&unit_canon).is_none() {
|
||||
if !ctx.dimensions.contains(&**unit) {
|
||||
if ctx.registry.definitions.get(unit).is_none() {
|
||||
if ctx.registry.definitions.get(&unit_canon).is_none() {
|
||||
if !ctx.registry.base_units.contains(&**unit) {
|
||||
break;
|
||||
} else {
|
||||
assert!(name != *unit || canon != unit_canon);
|
||||
|
@ -693,7 +711,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
canon = unit_canon.clone();
|
||||
}
|
||||
}
|
||||
let (def, def_expr, res) = if ctx.dimensions.contains(&*name) {
|
||||
let (def, def_expr, res) = if ctx.registry.base_units.contains(&*name) {
|
||||
let parts = ctx
|
||||
.lookup(&name)
|
||||
.expect("Lookup of base unit failed")
|
||||
|
@ -705,7 +723,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
};
|
||||
(Some(def), None, None)
|
||||
} else {
|
||||
let def = ctx.definitions.get(&name);
|
||||
let def = ctx.registry.definitions.get(&name);
|
||||
(
|
||||
def.as_ref().map(|x| x.to_string()),
|
||||
def,
|
||||
|
@ -717,7 +735,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
def,
|
||||
def_expr: def_expr.as_ref().map(|x| ExprReply::from(*x)),
|
||||
value: res,
|
||||
doc: ctx.docs.get(&name).cloned(),
|
||||
doc: ctx.registry.docs.get(&name).cloned(),
|
||||
})))
|
||||
}
|
||||
Query::Convert(ref top, Conversion::None, Some(base), digits) => {
|
||||
|
@ -853,7 +871,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
.map(|list| {
|
||||
QueryReply::UnitList(UnitListReply {
|
||||
rest: NumberParts {
|
||||
quantity: ctx.quantities.get(&top.unit).cloned(),
|
||||
quantity: ctx.registry.quantities.get(&top.unit).cloned(),
|
||||
..Default::default()
|
||||
},
|
||||
list,
|
||||
|
@ -945,7 +963,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
Query::Factorize(ref expr) => {
|
||||
let mut val = None;
|
||||
if let Expr::Unit { ref name } = *expr {
|
||||
for (u, k) in &ctx.quantities {
|
||||
for (u, k) in &ctx.registry.quantities {
|
||||
if name == k {
|
||||
val = Some(Number {
|
||||
value: Numeric::one(),
|
||||
|
@ -971,6 +989,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
Some(val) => val,
|
||||
};
|
||||
let quantities = ctx
|
||||
.registry
|
||||
.quantities
|
||||
.iter()
|
||||
.map(|(a, b)| (a.clone(), Rc::new(b.clone())))
|
||||
|
@ -995,7 +1014,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
Query::UnitsFor(ref expr) => {
|
||||
let mut val = None;
|
||||
if let Expr::Unit { ref name } = *expr {
|
||||
for (u, k) in &ctx.quantities {
|
||||
for (u, k) in &ctx.registry.quantities {
|
||||
if name == k {
|
||||
val = Some(Number {
|
||||
value: Numeric::one(),
|
||||
|
@ -1022,11 +1041,11 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
};
|
||||
let dim_name;
|
||||
let mut out = vec![];
|
||||
for (name, unit) in ctx.units.iter() {
|
||||
if let Some(&Expr::Unit { .. }) = ctx.definitions.get(name) {
|
||||
for (name, unit) in ctx.registry.units.iter() {
|
||||
if let Some(&Expr::Unit { .. }) = ctx.registry.definitions.get(name) {
|
||||
continue;
|
||||
}
|
||||
let category = ctx.categories.get(name);
|
||||
let category = ctx.registry.categories.get(name);
|
||||
if val.unit == unit.unit {
|
||||
out.push((category, name));
|
||||
}
|
||||
|
@ -1035,7 +1054,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
dim_name = ctx
|
||||
.canonicalize(dim.as_str())
|
||||
.unwrap_or_else(|| dim.to_string());
|
||||
let category = ctx.categories.get(&dim_name);
|
||||
let category = ctx.registry.categories.get(&dim_name);
|
||||
out.push((category, &dim_name));
|
||||
}
|
||||
out.sort_by(|&(ref c1, ref n1), &(ref c2, ref n2)| {
|
||||
|
@ -1053,7 +1072,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
for (category, name) in out {
|
||||
if category != cur_cat {
|
||||
if !cur.is_empty() {
|
||||
let cat_name = cur_cat.and_then(|x| ctx.category_names.get(x));
|
||||
let cat_name = cur_cat.and_then(|x| ctx.registry.category_names.get(x));
|
||||
categories.push(UnitsInCategory {
|
||||
category: cat_name.map(ToOwned::to_owned),
|
||||
units: cur.drain(..).collect(),
|
||||
|
@ -1064,7 +1083,7 @@ pub(crate) fn eval_query(ctx: &Context, expr: &Query) -> Result<QueryReply, Quer
|
|||
cur.push(name.clone());
|
||||
}
|
||||
if !cur.is_empty() {
|
||||
let cat_name = cur_cat.and_then(|x| ctx.category_names.get(x));
|
||||
let cat_name = cur_cat.and_then(|x| ctx.registry.category_names.get(x));
|
||||
categories.push(UnitsInCategory {
|
||||
category: cat_name.map(ToOwned::to_owned),
|
||||
units: cur,
|
||||
|
|
|
@ -127,7 +127,7 @@ impl Substance {
|
|||
if self.amount.dimless() {
|
||||
Ok(SubstanceReply {
|
||||
name: self.properties.name.clone(),
|
||||
doc: context.docs.get(&self.properties.name).cloned(),
|
||||
doc: context.registry.docs.get(&self.properties.name).cloned(),
|
||||
amount: self.amount.to_parts(context),
|
||||
properties: self
|
||||
.properties
|
||||
|
@ -254,7 +254,7 @@ impl Substance {
|
|||
};
|
||||
Ok(SubstanceReply {
|
||||
name: self.properties.name.clone(),
|
||||
doc: context.docs.get(&self.properties.name).cloned(),
|
||||
doc: context.registry.docs.get(&self.properties.name).cloned(),
|
||||
amount: self.amount.to_parts(context),
|
||||
properties: once(Ok(Some(amount)))
|
||||
.chain(self.properties.properties.iter().map(func))
|
||||
|
@ -270,7 +270,7 @@ impl Substance {
|
|||
if self.amount.dimless() {
|
||||
Ok(SubstanceReply {
|
||||
name: self.properties.name.clone(),
|
||||
doc: context.docs.get(&self.properties.name).cloned(),
|
||||
doc: context.registry.docs.get(&self.properties.name).cloned(),
|
||||
amount: self.amount.to_parts(context),
|
||||
properties: self
|
||||
.properties
|
||||
|
@ -346,7 +346,7 @@ impl Substance {
|
|||
};
|
||||
Ok(SubstanceReply {
|
||||
name: self.properties.name.clone(),
|
||||
doc: context.docs.get(&self.properties.name).cloned(),
|
||||
doc: context.registry.docs.get(&self.properties.name).cloned(),
|
||||
amount: self.amount.to_parts(context),
|
||||
properties: once(Ok(Some(amount)))
|
||||
.chain(self.properties.properties.iter().map(func))
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
use crate::algorithms::btree_merge;
|
||||
|
||||
use super::BaseUnit;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
@ -51,6 +53,44 @@ impl Dimensionality {
|
|||
pub fn is_dimensionless(&self) -> bool {
|
||||
self.dims.is_empty()
|
||||
}
|
||||
|
||||
pub fn recip(mut self) -> Dimensionality {
|
||||
for (_, power) in self.dims.iter_mut() {
|
||||
*power *= -1;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pow(mut self, exp: i64) -> Dimensionality {
|
||||
for (_, power) in self.dims.iter_mut() {
|
||||
*power *= exp;
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ops::Mul for &'a Dimensionality {
|
||||
type Output = Dimensionality;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
let dims = btree_merge(&self.dims, &rhs.dims, |a, b| {
|
||||
if a + b != 0 {
|
||||
Some(a + b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Dimensionality { dims }
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
impl<'a> ops::Div for &'a Dimensionality {
|
||||
type Output = Dimensionality;
|
||||
|
||||
fn div(self, rhs: Self) -> Self::Output {
|
||||
self * &rhs.clone().recip()
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////
|
||||
|
@ -85,9 +125,3 @@ impl IntoIterator for Dimensionality {
|
|||
self.dims.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Map> for Dimensionality {
|
||||
fn from(dims: Map) -> Self {
|
||||
Dimensionality { dims }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -194,15 +194,15 @@ impl Number {
|
|||
} else {
|
||||
(self.value.clone(), (orig.0.clone(), orig.1))
|
||||
};
|
||||
for &(ref p, ref v) in &context.prefixes {
|
||||
for &(ref p, ref v) in &context.registry.prefixes {
|
||||
if !prefixes.contains(&**p) {
|
||||
continue;
|
||||
}
|
||||
let abs = val.abs();
|
||||
if abs >= v.value.pow(orig.1 as i32)
|
||||
&& abs < (&v.value * &Numeric::from(1000)).pow(orig.1 as i32)
|
||||
if abs >= v.pow(orig.1 as i32)
|
||||
&& abs < (v * &Numeric::from(1000)).pow(orig.1 as i32)
|
||||
{
|
||||
let res = &val / &v.value.pow(orig.1 as i32);
|
||||
let res = &val / &v.pow(orig.1 as i32);
|
||||
// tonne special case
|
||||
let unit = if &**(orig.0).id == "gram" && p == "mega" {
|
||||
"tonne".to_string()
|
||||
|
@ -227,17 +227,22 @@ impl Number {
|
|||
let value = self.prettify(context);
|
||||
let (exact, approx) = value.numeric_value(10, Digits::Default);
|
||||
|
||||
let quantity = context.quantities.get(&self.unit).cloned().or_else(|| {
|
||||
if let Some((unit, power)) = self.unit.as_single() {
|
||||
if power == 1 {
|
||||
Some(unit.to_string())
|
||||
let quantity = context
|
||||
.registry
|
||||
.quantities
|
||||
.get(&self.unit)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
if let Some((unit, power)) = self.unit.as_single() {
|
||||
if power == 1 {
|
||||
Some(unit.to_string())
|
||||
} else {
|
||||
Some(format!("{}^{}", unit, power))
|
||||
}
|
||||
} else {
|
||||
Some(format!("{}^{}", unit, power))
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
NumberParts {
|
||||
raw_value: Some(self.clone()),
|
||||
|
@ -294,13 +299,14 @@ impl Number {
|
|||
}
|
||||
|
||||
fn pretty_unit(&self, context: &Context) -> Dimensionality {
|
||||
let pretty = crate::algorithms::fast_decompose(self, &context.reverse);
|
||||
let pretty = crate::algorithms::fast_decompose(self, &context.registry.decomposition_units);
|
||||
pretty
|
||||
.into_iter()
|
||||
.map(|(k, p)| {
|
||||
(
|
||||
context
|
||||
.canonicalizations
|
||||
.registry
|
||||
.base_unit_long_names
|
||||
.get(&*k.id)
|
||||
.map(|x| BaseUnit::new(x))
|
||||
.unwrap_or(k),
|
||||
|
@ -375,18 +381,10 @@ impl<'a> Neg for &'a Number {
|
|||
impl<'a, 'b> Mul<&'b Number> for &'a Number {
|
||||
type Output = Option<Number>;
|
||||
|
||||
#[allow(clippy::suspicious_arithmetic_impl)]
|
||||
fn mul(self, other: &Number) -> Self::Output {
|
||||
let val = crate::algorithms::btree_merge(&self.unit, &other.unit, |a, b| {
|
||||
if a + b != 0 {
|
||||
Some(a + b)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
Some(Number {
|
||||
value: &self.value * &other.value,
|
||||
unit: val.into(),
|
||||
unit: &self.unit * &other.unit,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,15 +72,16 @@ fn check_defs() {
|
|||
})
|
||||
);
|
||||
|
||||
// Prefix and SPrefix.
|
||||
// Prefixes
|
||||
assert_json_eq!(
|
||||
serde_json::to_value([
|
||||
DefEntry::new(
|
||||
"kilo",
|
||||
None,
|
||||
None,
|
||||
Def::SPrefix {
|
||||
expr: ExprString(expr("1000"))
|
||||
Def::Prefix {
|
||||
expr: ExprString(expr("1000")),
|
||||
is_long: true,
|
||||
},
|
||||
),
|
||||
DefEntry::new(
|
||||
|
@ -88,7 +89,8 @@ fn check_defs() {
|
|||
None,
|
||||
None,
|
||||
Def::Prefix {
|
||||
expr: ExprString(expr("kilo"))
|
||||
expr: ExprString(expr("kilo")),
|
||||
is_long: false,
|
||||
}
|
||||
)
|
||||
])
|
||||
|
@ -98,7 +100,8 @@ fn check_defs() {
|
|||
"name": "kilo",
|
||||
"doc": null,
|
||||
"category": null,
|
||||
"type": "sprefix",
|
||||
"type": "prefix",
|
||||
"isLong": true,
|
||||
"expr": "1000"
|
||||
},
|
||||
{
|
||||
|
@ -106,6 +109,7 @@ fn check_defs() {
|
|||
"doc": null,
|
||||
"category": null,
|
||||
"type": "prefix",
|
||||
"isLong": false,
|
||||
"expr": "kilo"
|
||||
}
|
||||
])
|
||||
|
@ -113,33 +117,46 @@ fn check_defs() {
|
|||
|
||||
// Base units.
|
||||
assert_json_eq!(
|
||||
serde_json::to_value([
|
||||
DefEntry::new("m", Some("base unit of length"), None, Def::BaseUnit),
|
||||
DefEntry::new(
|
||||
"meter",
|
||||
None,
|
||||
None,
|
||||
Def::Canonicalization { of: "m".to_owned() }
|
||||
)
|
||||
])
|
||||
serde_json::to_value([DefEntry::new(
|
||||
"m",
|
||||
Some("base unit of length"),
|
||||
None,
|
||||
Def::BaseUnit {
|
||||
long_name: Some("meter".to_owned())
|
||||
}
|
||||
),])
|
||||
.unwrap(),
|
||||
json!([
|
||||
{
|
||||
"name": "m",
|
||||
"longName": "meter",
|
||||
"doc": "base unit of length",
|
||||
"category": null,
|
||||
"type": "dimension"
|
||||
},
|
||||
{
|
||||
"name": "meter",
|
||||
"doc": null,
|
||||
"category": null,
|
||||
"type": "canonicalization",
|
||||
"of": "m"
|
||||
"type": "baseUnit"
|
||||
}
|
||||
])
|
||||
);
|
||||
|
||||
// Quantities
|
||||
assert_json_eq!(
|
||||
serde_json::to_value(DefEntry::new(
|
||||
"watt",
|
||||
Some("SI derived unit for power"),
|
||||
None,
|
||||
Def::Quantity {
|
||||
expr: ExprString(expr("energy / time"))
|
||||
}
|
||||
))
|
||||
.unwrap(),
|
||||
json!({
|
||||
"name": "watt",
|
||||
"doc": "SI derived unit for power",
|
||||
"category": null,
|
||||
"type": "quantity",
|
||||
"expr": "energy / time",
|
||||
})
|
||||
);
|
||||
|
||||
// Substances
|
||||
assert_json_eq!(
|
||||
serde_json::to_value(DefEntry::new(
|
||||
|
@ -175,4 +192,44 @@ fn check_defs() {
|
|||
}]
|
||||
})
|
||||
);
|
||||
|
||||
// Categories
|
||||
assert_json_eq!(
|
||||
serde_json::to_value(DefEntry::new(
|
||||
"cool_beans",
|
||||
Some("Units that are cool beans."),
|
||||
None,
|
||||
Def::Category {
|
||||
display_name: "Cool Beans".to_owned(),
|
||||
}
|
||||
))
|
||||
.unwrap(),
|
||||
json!({
|
||||
"name": "cool_beans",
|
||||
"doc": "Units that are cool beans.",
|
||||
"category": null,
|
||||
"type": "category",
|
||||
"displayName": "Cool Beans",
|
||||
})
|
||||
);
|
||||
|
||||
// Errors
|
||||
assert_json_eq!(
|
||||
serde_json::to_value(DefEntry::new(
|
||||
"foo",
|
||||
Some("Definition of foo"),
|
||||
None,
|
||||
Def::Error {
|
||||
message: "Syntax error".to_owned()
|
||||
}
|
||||
))
|
||||
.unwrap(),
|
||||
json!({
|
||||
"name": "foo",
|
||||
"doc": "Definition of foo",
|
||||
"category": null,
|
||||
"type": "error",
|
||||
"message": "Syntax error",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use rink_core::*;
|
|||
#[test]
|
||||
fn canonicalizations() {
|
||||
let ctx = simple_context().unwrap();
|
||||
for (name, value) in &ctx.units {
|
||||
for (name, value) in &ctx.registry.units {
|
||||
let canon = match ctx.canonicalize(&*name) {
|
||||
Some(x) => x,
|
||||
None => continue,
|
||||
|
|
|
@ -5,22 +5,14 @@ export interface DefBase {
|
|||
}
|
||||
|
||||
export interface BaseUnit extends DefBase {
|
||||
type: "dimension";
|
||||
}
|
||||
|
||||
export interface Canonicalization extends DefBase {
|
||||
type: "canonicalization";
|
||||
of: string;
|
||||
type: "baseUnit";
|
||||
longName: string | null;
|
||||
}
|
||||
|
||||
export interface Prefix extends DefBase {
|
||||
type: "prefix";
|
||||
expr: string;
|
||||
}
|
||||
|
||||
export interface SPrefix extends DefBase {
|
||||
type: "sprefix";
|
||||
expr: string;
|
||||
isLong: boolean;
|
||||
}
|
||||
|
||||
export interface Unit extends DefBase {
|
||||
|
@ -60,9 +52,7 @@ export interface Error extends DefBase {
|
|||
|
||||
export type Def =
|
||||
| BaseUnit
|
||||
| Canonicalization
|
||||
| Prefix
|
||||
| SPrefix
|
||||
| Unit
|
||||
| Quantity
|
||||
| Substance
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Def } from "./defs";
|
||||
import { QueryResult } from "./reply";
|
||||
|
||||
export interface Query {
|
||||
|
|
Loading…
Reference in a new issue