///|
pub enum ScalarError {
EmptyScalar
InvalidNumber
UnknownUnit(String)
IncompatibleUnits(String, String)
InvalidConversion
} derive(Debug, Eq)
///|
pub struct ScalarValue {
value : String
unit : String
} derive(Debug, Eq)
///|
pub fn scalar(value : String, unit : String) -> ScalarValue {
{ value: value.trim().to_owned(), unit: unit.trim().to_owned() }
}
///|
fn decimal_scaled(text : String) -> Int? {
let s = text.trim()
if s == "" {
return None
}
let negative = s.has_prefix("-")
let body = if negative || s.has_prefix("+") { s[1:] } else { s[:] }
let parts = body.split(".").collect()
if parts.length() > 2 {
return None
}
let whole_option = if parts[0] == "" {
Some(0)
} else {
parse_digits(parts[0])
}
let whole = match whole_option {
Some(v) => v
None => return None
}
let fraction_option = if parts.length() == 1 {
Some(0)
} else {
parse_fraction(parts[1])
}
let fraction = match fraction_option {
Some(v) => v
None => return None
}
let result = whole * 1000 + fraction
Some(if negative { -result } else { result })
}
///|
fn parse_digits(text : StringView) -> Int? {
if text.length() == 0 {
return None
}
let mut value = 0
for c in text {
if c < '0' || c > '9' {
return None
} else {
value = value * 10 + (c.to_int() - '0'.to_int())
}
}
Some(value)
}
///|
fn parse_fraction(text : StringView) -> Int? {
if text.length() == 0 {
return Some(0)
}
if text.length() > 3 {
return None
}
let digits = match parse_digits(text) {
Some(v) => v
None => return None
}
let mut scale = 3 - text.length()
let mut value = digits
while scale > 0 {
value *= 10
scale -= 1
}
Some(value)
}
///|
pub fn parse_scalar(text : String) -> Result[ScalarValue, ScalarError] {
let fields : Array[String] = []
for field in text.trim().split(" ") {
if field.trim() != "" {
fields.push(field.trim().to_owned())
}
}
if fields.length() < 1 {
return Err(EmptyScalar)
}
if decimal_scaled(fields[0]) is None {
return Err(InvalidNumber)
}
let unit = if fields.length() == 1 { "1" } else { fields[1] }
if unit_lookup(unit) is None {
return Err(UnknownUnit(unit))
}
Ok(scalar(fields[0], unit))
}
///|
fn scaled_string(value : Int) -> String {
let sign = if value < 0 { "-" } else { "" }
let absolute = if value < 0 { -value } else { value }
let whole = absolute / 1000
let fraction = absolute % 1000
if fraction == 0 {
"\{sign}\{whole}.000"
} else {
"\{sign}\{whole}.\{fraction}"
}
}
///|
pub fn convert_scalar(
value : ScalarValue,
target : String,
) -> Result[ScalarValue, ScalarError] {
let source_spec = unit_lookup(value.unit)
let target_spec = unit_lookup(target)
if source_spec is None {
return Err(UnknownUnit(value.unit))
}
if target_spec is None {
return Err(UnknownUnit(target))
}
if source_spec.unwrap().dimension != target_spec.unwrap().dimension {
return Err(IncompatibleUnits(value.unit, target))
}
let base = decimal_scaled(value.value)
if base is None {
return Err(InvalidNumber)
}
let source_factor = decimal_scaled(source_spec.unwrap().to_base)
let target_factor = decimal_scaled(target_spec.unwrap().to_base)
let source_offset = decimal_scaled(source_spec.unwrap().offset)
let target_offset = decimal_scaled(target_spec.unwrap().offset)
if source_factor is None ||
target_factor is None ||
source_offset is None ||
target_offset is None {
return Err(InvalidConversion)
}
let base_value = base.unwrap() * source_factor.unwrap() / 1000 +
source_offset.unwrap()
let converted = (base_value - target_offset.unwrap()) *
1000 /
target_factor.unwrap()
Ok(scalar(scaled_string(converted), target))
}