-
Notifications
You must be signed in to change notification settings - Fork 174
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement MeasureUnit #4360
Implement MeasureUnit #4360
Changes from 39 commits
ebcc3fe
aefef1a
c2edbd6
5ea721a
dd9000a
63c44ce
e282232
5861684
a2e5600
427b008
3440254
82bcd7c
534fa73
55ea5e1
27b65a0
2d2bc47
77e459d
8dd3c86
dff30b7
6427f4e
28fe962
4b0a395
42ebe7f
64ba8e5
f13d1cb
a4b923c
e839685
754d17e
dbd3e9c
01f4009
e01e211
bc6fcb8
193ec7a
1002837
7b19d2d
d320904
9fcf51a
bfff7dd
d0d1a5d
1931b07
9df4bac
7c0916f
705355b
5c6f1ee
c288a8a
894bb60
bb144d4
4ed7efb
03ee92f
6cc4f86
0eff919
0acd58f
fe3382d
e4fc883
20d2013
70c9f31
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,269 @@ | ||||||||||||||||||
// This file is part of ICU4X. For terms of use, please see the file | ||||||||||||||||||
// called LICENSE at the top level of the ICU4X source tree | ||||||||||||||||||
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ). | ||||||||||||||||||
|
||||||||||||||||||
use zerotrie::ZeroTrie; | ||||||||||||||||||
use zerovec::ZeroVec; | ||||||||||||||||||
|
||||||||||||||||||
use crate::{ | ||||||||||||||||||
provider::{Base, MeasureUnitItem, SiPrefix}, | ||||||||||||||||||
ConversionError, | ||||||||||||||||||
}; | ||||||||||||||||||
|
||||||||||||||||||
// TODO(#4369): split this struct to two structs: MeasureUnitParser for parsing the identifier and MeasureUnit to represent the unit. | ||||||||||||||||||
#[zerovec::make_varule(MeasureUnitULE)] | ||||||||||||||||||
#[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Default)] | ||||||||||||||||||
#[cfg_attr( | ||||||||||||||||||
feature = "datagen", | ||||||||||||||||||
derive(databake::Bake), | ||||||||||||||||||
databake(path = icu_unitsconversion::provider), | ||||||||||||||||||
)] | ||||||||||||||||||
#[cfg_attr( | ||||||||||||||||||
feature = "datagen", | ||||||||||||||||||
derive(serde::Serialize), | ||||||||||||||||||
zerovec::derive(Serialize) | ||||||||||||||||||
)] | ||||||||||||||||||
#[cfg_attr( | ||||||||||||||||||
feature = "serde", | ||||||||||||||||||
derive(serde::Deserialize), | ||||||||||||||||||
zerovec::derive(Deserialize) | ||||||||||||||||||
)] | ||||||||||||||||||
#[zerovec::derive(Debug)] | ||||||||||||||||||
pub struct MeasureUnit<'data> { | ||||||||||||||||||
/// Contains the processed units. | ||||||||||||||||||
#[cfg_attr(feature = "serde", serde(borrow))] | ||||||||||||||||||
pub contained_units: ZeroVec<'data, MeasureUnitItem>, | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
impl MeasureUnit<'_> { | ||||||||||||||||||
// TODO: consider returning Option<(u8, &str)> instead of (1, part) for the case when the power is not found. | ||||||||||||||||||
// TODO: complete all the cases for the powers. | ||||||||||||||||||
// TODO: consider using a trie for the powers. | ||||||||||||||||||
/// Get the power of the unit. | ||||||||||||||||||
/// NOTE: | ||||||||||||||||||
/// if the power is found, the function will return (power, part without the power). | ||||||||||||||||||
/// if the power is not found, the function will return (1, part). | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: These docs seem out of date There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||||||||||||||
fn get_power(part: &str) -> (u8, &str) { | ||||||||||||||||||
if let Some(part) = part.strip_prefix("square-") { | ||||||||||||||||||
(2, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow2-") { | ||||||||||||||||||
(2, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("cubic-") { | ||||||||||||||||||
(3, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow3-") { | ||||||||||||||||||
(3, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow4-") { | ||||||||||||||||||
(4, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow5-") { | ||||||||||||||||||
(5, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow6-") { | ||||||||||||||||||
(6, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow7-") { | ||||||||||||||||||
(7, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow8-") { | ||||||||||||||||||
(8, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pow9-") { | ||||||||||||||||||
(9, part) | ||||||||||||||||||
} else { | ||||||||||||||||||
(1, part) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// TODO: complete all the cases for the prefixes. | ||||||||||||||||||
// TODO: consider using a trie for the prefixes. | ||||||||||||||||||
/// Get the SI prefix. | ||||||||||||||||||
/// NOTE: | ||||||||||||||||||
/// if the prefix is found, the function will return (power, base, part without the prefix). | ||||||||||||||||||
/// if the prefix is not found, the function will return (0, Base::NotExist, part). | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: Docs seem out of date There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||||||||||||||||||
fn get_si_prefix(part: &str) -> (SiPrefix, &str) { | ||||||||||||||||||
let (si_prefix_base_10, part) = Self::get_si_prefix_base_10(part); | ||||||||||||||||||
if si_prefix_base_10 != 0 { | ||||||||||||||||||
return ( | ||||||||||||||||||
SiPrefix { | ||||||||||||||||||
power: si_prefix_base_10, | ||||||||||||||||||
base: Base::Decimal, | ||||||||||||||||||
}, | ||||||||||||||||||
part, | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
let (si_prefix_base_2, part) = Self::get_si_prefix_base_two(part); | ||||||||||||||||||
if si_prefix_base_2 != 0 { | ||||||||||||||||||
return ( | ||||||||||||||||||
SiPrefix { | ||||||||||||||||||
power: si_prefix_base_2, | ||||||||||||||||||
base: Base::Binary, | ||||||||||||||||||
}, | ||||||||||||||||||
part, | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
( | ||||||||||||||||||
SiPrefix { | ||||||||||||||||||
power: 0, | ||||||||||||||||||
base: Base::Decimal, | ||||||||||||||||||
}, | ||||||||||||||||||
part, | ||||||||||||||||||
) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// TODO: consider returning Option<(i8, &str)> instead of (0, part) for the case when the prefix is not found. | ||||||||||||||||||
// TODO: consider using a trie for the prefixes. | ||||||||||||||||||
// TODO: complete all the cases for the prefixes. | ||||||||||||||||||
/// Get the SI prefix for base 10. | ||||||||||||||||||
/// NOTE: | ||||||||||||||||||
/// if the prefix is found, the function will return (power, part without the prefix). | ||||||||||||||||||
/// if the prefix is not found, the function will return (0, part). | ||||||||||||||||||
fn get_si_prefix_base_10(part: &str) -> (i8, &str) { | ||||||||||||||||||
if let Some(part) = part.strip_prefix("quetta") { | ||||||||||||||||||
(30, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("ronna") { | ||||||||||||||||||
(27, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("yotta") { | ||||||||||||||||||
(24, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("zetta") { | ||||||||||||||||||
(21, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("exa") { | ||||||||||||||||||
(18, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("peta") { | ||||||||||||||||||
(15, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("tera") { | ||||||||||||||||||
(12, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("giga") { | ||||||||||||||||||
(9, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("mega") { | ||||||||||||||||||
(6, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("kilo") { | ||||||||||||||||||
(3, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("hecto") { | ||||||||||||||||||
(2, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("deca") { | ||||||||||||||||||
(1, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("deci") { | ||||||||||||||||||
(-1, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("centi") { | ||||||||||||||||||
(-2, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("milli") { | ||||||||||||||||||
(-3, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("micro") { | ||||||||||||||||||
(-6, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("nano") { | ||||||||||||||||||
(-9, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pico") { | ||||||||||||||||||
(-12, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("femto") { | ||||||||||||||||||
(-15, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("atto") { | ||||||||||||||||||
(-18, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("zepto") { | ||||||||||||||||||
(-21, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("yocto") { | ||||||||||||||||||
(-24, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("ronto") { | ||||||||||||||||||
(-27, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("quecto") { | ||||||||||||||||||
(-30, part) | ||||||||||||||||||
} else { | ||||||||||||||||||
robertbastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
(0, part) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// TODO: consider returning Option<(i8, &str)> instead of (0, part) for the case when the prefix is not found. | ||||||||||||||||||
// TODO: consider using a trie for the prefixes. | ||||||||||||||||||
// TODO: complete all the cases for the prefixes. | ||||||||||||||||||
/// Get the SI prefix for base 2. | ||||||||||||||||||
/// NOTE: | ||||||||||||||||||
/// if the prefix is found, the function will return (power, part without the prefix). | ||||||||||||||||||
/// if the prefix is not found, the function will return (0, part). | ||||||||||||||||||
fn get_si_prefix_base_two(part: &str) -> (i8, &str) { | ||||||||||||||||||
if let Some(part) = part.strip_prefix("kibi") { | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: here you're searching from 0 to +inf, which I think makes sense. In the decimal units you do +inf -> 0 -> -inf. It probably makes sense to start at 0 and do +1 -> -1 -> +2 -> -2 ... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it will be confusing: "deca" , "deci" , "hecta", "centi", "kilo", "milli" .... etc. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think so, I see the pattern. Just a nit though There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||||||||||||||||||
(10, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("mebi") { | ||||||||||||||||||
(20, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("gibi") { | ||||||||||||||||||
(30, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("tebi") { | ||||||||||||||||||
(40, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("pebi") { | ||||||||||||||||||
(50, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("exbi") { | ||||||||||||||||||
(60, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("zebi") { | ||||||||||||||||||
(70, part) | ||||||||||||||||||
} else if let Some(part) = part.strip_prefix("yobi") { | ||||||||||||||||||
(80, part) | ||||||||||||||||||
} else { | ||||||||||||||||||
(0, part) | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// TODO: consider using a sufficient trie search for finding the unit id. | ||||||||||||||||||
/// Get the unit id. | ||||||||||||||||||
/// NOTE: | ||||||||||||||||||
/// if the unit id is found, the function will return (unit id, part without the unit id and without `-` at the beginning of the remaining part if it exists). | ||||||||||||||||||
/// if the unit id is not found, the function will return None. | ||||||||||||||||||
fn get_unit_id<'data>( | ||||||||||||||||||
part: &'data str, | ||||||||||||||||||
trie: &ZeroTrie<ZeroVec<'data, u8>>, | ||||||||||||||||||
) -> Option<(usize, &'data str)> { | ||||||||||||||||||
// TODO(#4379): this is inefficient way to search for an item in a trie. | ||||||||||||||||||
// we must implement a way to search for a prefix in a trie. | ||||||||||||||||||
let mut result = None; | ||||||||||||||||||
for (index, _) in part.char_indices() { | ||||||||||||||||||
let identifier = &part[..=index]; | ||||||||||||||||||
robertbastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
if let Some(value) = trie.get(identifier.as_bytes()) { | ||||||||||||||||||
result = Some((value, &part[identifier.len()..])); | ||||||||||||||||||
} | ||||||||||||||||||
} | ||||||||||||||||||
robertbastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
result | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
/// Process a part of an identifier. | ||||||||||||||||||
/// For example, if the whole identifier is: "square-kilometer-per-second", | ||||||||||||||||||
/// this function will be called for "square-kilometer" with sign (1) and "second" with sign (-1). | ||||||||||||||||||
fn analyze_identifier_part( | ||||||||||||||||||
identifier_part: &str, | ||||||||||||||||||
sign: i8, | ||||||||||||||||||
result: &mut Vec<MeasureUnitItem>, | ||||||||||||||||||
trie: &ZeroTrie<ZeroVec<'_, u8>>, | ||||||||||||||||||
sffc marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
) -> Result<(), ConversionError> { | ||||||||||||||||||
let mut identifier = identifier_part; | ||||||||||||||||||
while !identifier.is_empty() { | ||||||||||||||||||
let (power, identifier_after_power) = Self::get_power(identifier); | ||||||||||||||||||
let (si_prefix, identifier_after_si) = Self::get_si_prefix(identifier_after_power); | ||||||||||||||||||
let (unit_id, identifier_after_unit) = | ||||||||||||||||||
match Self::get_unit_id(identifier_after_si, trie) { | ||||||||||||||||||
Some((unit_id, identifier_after_unit)) => (unit_id, identifier_after_unit), | ||||||||||||||||||
None => return Err(ConversionError::InvalidUnit), | ||||||||||||||||||
}; | ||||||||||||||||||
robertbastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
|
||||||||||||||||||
result.push(MeasureUnitItem { | ||||||||||||||||||
power: power as i8 * sign, | ||||||||||||||||||
si_prefix, | ||||||||||||||||||
unit_id: unit_id as u16, | ||||||||||||||||||
}); | ||||||||||||||||||
|
||||||||||||||||||
identifier = identifier_after_unit.get(1..).unwrap_or(""); | ||||||||||||||||||
robertbastian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
Ok(()) | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
// TODO: add test cases for this function. | ||||||||||||||||||
/// Process an identifier. | ||||||||||||||||||
pub fn try_from_identifier<'data>( | ||||||||||||||||||
identifier: &'data str, | ||||||||||||||||||
trie: &ZeroTrie<ZeroVec<'data, u8>>, | ||||||||||||||||||
) -> Result<Vec<MeasureUnitItem>, ConversionError> { | ||||||||||||||||||
let (num_part, den_part) = identifier | ||||||||||||||||||
.split_once("-per-") | ||||||||||||||||||
.or_else(|| identifier.split_once("per-")) | ||||||||||||||||||
.unwrap_or((identifier, "")); | ||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized we actually only need one search:
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. okay, done, but I feel we over complicated it |
||||||||||||||||||
|
||||||||||||||||||
let mut measure_unit_items = Vec::<MeasureUnitItem>::new(); | ||||||||||||||||||
Self::analyze_identifier_part(num_part, 1, &mut measure_unit_items, trie)?; | ||||||||||||||||||
Self::analyze_identifier_part(den_part, -1, &mut measure_unit_items, trie)?; | ||||||||||||||||||
Ok(measure_unit_items) | ||||||||||||||||||
} | ||||||||||||||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this runtime code, or just datagen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to have also:
let meter = MeasureUnit::try_from_id("meter");
So, I want to use
MeasureUnit
in the runtime and in the datagen