소스 검색

Print units using short aliases

David Peter 2 년 전
부모
커밋
79adb6e6f7
11개의 변경된 파일222개의 추가작업 그리고 120개의 파일을 삭제
  1. 2 2
      numbat-cli/tests/integration.rs
  2. 6 2
      numbat/src/ast.rs
  3. 14 7
      numbat/src/bytecode_interpreter.rs
  4. 16 0
      numbat/src/decorator.rs
  5. 8 2
      numbat/src/interpreter.rs
  6. 84 44
      numbat/src/prefix.rs
  7. 1 1
      numbat/src/quantity.rs
  8. 30 14
      numbat/src/unit.rs
  9. 22 9
      numbat/src/vm.rs
  10. 28 27
      numbat/tests/interpreter.rs
  11. 11 12
      prelude.nbt

+ 2 - 2
numbat-cli/tests/integration.rs

@@ -11,7 +11,7 @@ fn pass_expression_on_command_line() {
         .arg("2 meter + 3 meter")
         .assert()
         .success()
-        .stdout(predicates::str::contains("5 meter"));
+        .stdout(predicates::str::contains("5 m"));
 
     numbat()
         .arg("--expression")
@@ -52,7 +52,7 @@ fn print_calls() {
         .arg("../examples/print.nbt")
         .assert()
         .success()
-        .stdout(predicates::str::contains("1.000000 \n2.000000 meter"));
+        .stdout(predicates::str::contains("1.000000 \n2.000000 m"));
 }
 
 #[test]

+ 6 - 2
numbat/src/ast.rs

@@ -115,7 +115,9 @@ fn pretty_print_binop(op: &BinaryOperator, lhs: &Expression, rhs: &Expression) -
         BinaryOperator::Mul => match (lhs, rhs) {
             (Expression::Scalar(s), Expression::UnitIdentifier(prefix, _name, full_name)) => {
                 // Fuse multiplication of a scalar and a unit to a quantity
-                pretty_scalar(*s) + m::space() + m::unit(format!("{}{}", prefix, full_name))
+                pretty_scalar(*s)
+                    + m::space()
+                    + m::unit(format!("{}{}", prefix.to_string_long(), full_name))
             }
             (Expression::Scalar(s), Expression::Identifier(name)) => {
                 // Fuse multiplication of a scalar and identifier
@@ -210,7 +212,9 @@ impl PrettyPrint for Expression {
         match self {
             Scalar(n) => pretty_scalar(*n),
             Identifier(name) => m::identifier(name),
-            UnitIdentifier(prefix, _name, full_name) => m::unit(format!("{}{}", prefix, full_name)),
+            UnitIdentifier(prefix, _name, full_name) => {
+                m::unit(format!("{}{}", prefix.to_string_long(), full_name))
+            }
             Negate(rhs) => m::operator("-") + with_parens(rhs),
             BinaryOperator(op, lhs, rhs) => pretty_print_binop(op, lhs, rhs),
             FunctionCall(name, args) => {

+ 14 - 7
numbat/src/bytecode_interpreter.rs

@@ -28,7 +28,7 @@ impl BytecodeInterpreter {
                 if let Some(position) = self.local_variables.iter().position(|n| n == identifier) {
                     self.vm.add_op1(Op::GetLocal, position as u8); // TODO: check overflow
                 } else {
-                    let identifier_idx = self.vm.add_global_identifier(identifier);
+                    let identifier_idx = self.vm.add_global_identifier(identifier, None);
                     self.vm.add_op1(Op::GetVariable, identifier_idx);
                 }
             }
@@ -119,7 +119,7 @@ impl BytecodeInterpreter {
             }
             Statement::DeclareVariable(identifier, expr, _dexpr) => {
                 self.compile_expression_with_simplify(expr)?;
-                let identifier_idx = self.vm.add_global_identifier(identifier);
+                let identifier_idx = self.vm.add_global_identifier(identifier, None);
                 self.vm.add_op1(Op::SetVariable, identifier_idx);
             }
             Statement::DeclareFunction(name, parameters, Some(expr), _return_type) => {
@@ -150,9 +150,10 @@ impl BytecodeInterpreter {
                     .add_base_unit(unit_name, dexpr.clone())
                     .map_err(RuntimeError::UnitRegistryError)?;
 
-                let constant_idx = self
-                    .vm
-                    .add_constant(Constant::Unit(Unit::new_base(unit_name)));
+                let constant_idx = self.vm.add_constant(Constant::Unit(Unit::new_base(
+                    unit_name,
+                    &crate::decorator::get_canonical_unit_name(unit_name.as_str(), &decorators[..]),
+                )));
                 for (name, _) in decorator::name_and_aliases(unit_name, decorators) {
                     self.unit_name_to_constant_index
                         .insert((Prefix::none(), name.into()), constant_idx);
@@ -165,8 +166,14 @@ impl BytecodeInterpreter {
 
                 let constant_idx = self
                     .vm
-                    .add_constant(Constant::Unit(Unit::new_base("<dummy>"))); // TODO: dummy is just a temp. value until the SetUnitConstant op runs
-                let identifier_idx = self.vm.add_global_identifier(unit_name); // TODO: there is some asymmetry here because we do not introduce identifiers for base units
+                    .add_constant(Constant::Unit(Unit::new_base("<dummy>", "<dummy>"))); // TODO: dummy is just a temp. value until the SetUnitConstant op runs
+                let identifier_idx = self.vm.add_global_identifier(
+                    unit_name,
+                    Some(&crate::decorator::get_canonical_unit_name(
+                        unit_name.as_str(),
+                        &decorators[..],
+                    )),
+                ); // TODO: there is some asymmetry here because we do not introduce identifiers for base units
 
                 self.compile_expression_with_simplify(expr)?;
                 self.vm

+ 16 - 0
numbat/src/decorator.rs

@@ -33,3 +33,19 @@ pub fn name_and_aliases<'a>(
         Box::new(aliases.into_iter())
     }
 }
+
+pub fn get_canonical_unit_name(unit_name: &str, decorators: &[Decorator]) -> String {
+    for decorator in decorators {
+        match decorator {
+            Decorator::Aliases(aliases) => {
+                for (alias, accepts_prefix) in aliases {
+                    if accepts_prefix.map(|ap| ap.short).unwrap_or(false) {
+                        return alias.into();
+                    }
+                }
+            }
+            _ => {}
+        }
+    }
+    unit_name.into()
+}

+ 8 - 2
numbat/src/interpreter.rs

@@ -85,9 +85,13 @@ mod tests {
         dimension Momentum = Mass * Speed
         dimension Frequency = 1 / Time
 
+        @aliases(m: short)
         unit meter : Length
+
+        @aliases(s: short)
         unit second : Time
-        
+
+        @aliases(Hz: short)
         unit hertz: Frequency = 1 / second
         
         fn sin(x: Scalar) -> Scalar
@@ -155,9 +159,11 @@ mod tests {
 
         assert_evaluates_to(
             "dimension Pixel
+             @aliases(px: short)
              unit pixel : Pixel
              2 * pixel",
-            (Quantity::from_scalar(2.0) * Quantity::from_unit(Unit::new_base("pixel"))).unwrap(),
+            (Quantity::from_scalar(2.0) * Quantity::from_unit(Unit::new_base("pixel", "px")))
+                .unwrap(),
         );
 
         assert_evaluates_to(

+ 84 - 44
numbat/src/prefix.rs

@@ -1,5 +1,3 @@
-use std::fmt::Display;
-
 use crate::number::Number;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -73,50 +71,92 @@ impl Prefix {
     pub fn is_binary(&self) -> bool {
         matches!(self, Prefix::Binary(_))
     }
-}
 
-impl Display for Prefix {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+    pub fn to_string_short(&self) -> String {
+        match self {
+            Prefix::Metric(-30) => "q".into(),
+            Prefix::Metric(-27) => "r".into(),
+            Prefix::Metric(-24) => "y".into(),
+            Prefix::Metric(-21) => "z".into(),
+            Prefix::Metric(-18) => "a".into(),
+            Prefix::Metric(-15) => "f".into(),
+            Prefix::Metric(-12) => "p".into(),
+            Prefix::Metric(-9) => "n".into(),
+            Prefix::Metric(-6) => "µ".into(),
+            Prefix::Metric(-3) => "m".into(),
+            Prefix::Metric(-2) => "c".into(),
+            Prefix::Metric(-1) => "d".into(),
+            Prefix::Metric(0) => "".into(),
+            Prefix::Metric(1) => "da".into(),
+            Prefix::Metric(2) => "h".into(),
+            Prefix::Metric(3) => "k".into(),
+            Prefix::Metric(6) => "M".into(),
+            Prefix::Metric(9) => "G".into(),
+            Prefix::Metric(12) => "T".into(),
+            Prefix::Metric(15) => "P".into(),
+            Prefix::Metric(18) => "E".into(),
+            Prefix::Metric(21) => "Z".into(),
+            Prefix::Metric(24) => "Y".into(),
+            Prefix::Metric(27) => "R".into(),
+            Prefix::Metric(30) => "Q".into(),
+
+            Prefix::Metric(n) => format!("<prefix 10^{}>", n),
+
+            Prefix::Binary(0) => "".into(),
+            Prefix::Binary(10) => "Ki".into(),
+            Prefix::Binary(20) => "Mi".into(),
+            Prefix::Binary(30) => "Gi".into(),
+            Prefix::Binary(40) => "Ti".into(),
+            Prefix::Binary(50) => "Pi".into(),
+            Prefix::Binary(60) => "Ei".into(),
+            Prefix::Binary(70) => "Zi".into(),
+            Prefix::Binary(80) => "Yi".into(),
+
+            Prefix::Binary(n) => format!("<prefix 2^{}>", n),
+        }
+    }
+
+    pub fn to_string_long(&self) -> String {
         match self {
-            Prefix::Metric(-30) => write!(f, "quecto"),
-            Prefix::Metric(-27) => write!(f, "ronto"),
-            Prefix::Metric(-24) => write!(f, "yocto"),
-            Prefix::Metric(-21) => write!(f, "zepto"),
-            Prefix::Metric(-18) => write!(f, "atto"),
-            Prefix::Metric(-15) => write!(f, "femto"),
-            Prefix::Metric(-12) => write!(f, "pico"),
-            Prefix::Metric(-9) => write!(f, "nano"),
-            Prefix::Metric(-6) => write!(f, "micro"),
-            Prefix::Metric(-3) => write!(f, "milli"),
-            Prefix::Metric(-2) => write!(f, "centi"),
-            Prefix::Metric(-1) => write!(f, "deci"),
-            Prefix::Metric(0) => write!(f, ""),
-            Prefix::Metric(1) => write!(f, "deca"),
-            Prefix::Metric(2) => write!(f, "hecto"),
-            Prefix::Metric(3) => write!(f, "kilo"),
-            Prefix::Metric(6) => write!(f, "mega"),
-            Prefix::Metric(9) => write!(f, "giga"),
-            Prefix::Metric(12) => write!(f, "tera"),
-            Prefix::Metric(15) => write!(f, "peta"),
-            Prefix::Metric(18) => write!(f, "exa"),
-            Prefix::Metric(21) => write!(f, "zetta"),
-            Prefix::Metric(24) => write!(f, "yotta"),
-            Prefix::Metric(27) => write!(f, "ronna"),
-            Prefix::Metric(30) => write!(f, "quetta"),
-
-            Prefix::Metric(n) => write!(f, "<prefix 10^{}>", n),
-
-            Prefix::Binary(0) => write!(f, ""),
-            Prefix::Binary(10) => write!(f, "kibi"),
-            Prefix::Binary(20) => write!(f, "mebi"),
-            Prefix::Binary(30) => write!(f, "gibi"),
-            Prefix::Binary(40) => write!(f, "tebi"),
-            Prefix::Binary(50) => write!(f, "pebi"),
-            Prefix::Binary(60) => write!(f, "exbi"),
-            Prefix::Binary(70) => write!(f, "zebi"),
-            Prefix::Binary(80) => write!(f, "yobi"),
-
-            Prefix::Binary(n) => write!(f, "<prefix 2^{}>", n),
+            Prefix::Metric(-30) => "quecto".into(),
+            Prefix::Metric(-27) => "ronto".into(),
+            Prefix::Metric(-24) => "yocto".into(),
+            Prefix::Metric(-21) => "zepto".into(),
+            Prefix::Metric(-18) => "atto".into(),
+            Prefix::Metric(-15) => "femto".into(),
+            Prefix::Metric(-12) => "pico".into(),
+            Prefix::Metric(-9) => "nano".into(),
+            Prefix::Metric(-6) => "micro".into(),
+            Prefix::Metric(-3) => "milli".into(),
+            Prefix::Metric(-2) => "centi".into(),
+            Prefix::Metric(-1) => "deci".into(),
+            Prefix::Metric(0) => "".into(),
+            Prefix::Metric(1) => "deca".into(),
+            Prefix::Metric(2) => "hecto".into(),
+            Prefix::Metric(3) => "kilo".into(),
+            Prefix::Metric(6) => "mega".into(),
+            Prefix::Metric(9) => "giga".into(),
+            Prefix::Metric(12) => "tera".into(),
+            Prefix::Metric(15) => "peta".into(),
+            Prefix::Metric(18) => "exa".into(),
+            Prefix::Metric(21) => "zetta".into(),
+            Prefix::Metric(24) => "yotta".into(),
+            Prefix::Metric(27) => "ronna".into(),
+            Prefix::Metric(30) => "quetta".into(),
+
+            Prefix::Metric(n) => format!("<prefix 10^{}>", n),
+
+            Prefix::Binary(0) => "".into(),
+            Prefix::Binary(10) => "kibi".into(),
+            Prefix::Binary(20) => "mebi".into(),
+            Prefix::Binary(30) => "gibi".into(),
+            Prefix::Binary(40) => "tebi".into(),
+            Prefix::Binary(50) => "pebi".into(),
+            Prefix::Binary(60) => "exbi".into(),
+            Prefix::Binary(70) => "zebi".into(),
+            Prefix::Binary(80) => "yobi".into(),
+
+            Prefix::Binary(n) => format!("<prefix 2^{}>", n),
         }
     }
 }

+ 1 - 1
numbat/src/quantity.rs

@@ -290,7 +290,7 @@ mod tests {
         use approx::assert_relative_eq;
 
         let meter = Unit::meter();
-        let foot = Unit::new_derived("foot", Number::from_f64(0.3048), meter.clone());
+        let foot = Unit::new_derived("foot", "ft", Number::from_f64(0.3048), meter.clone());
 
         let length = Quantity::new(Number::from_f64(2.0), meter.clone());
 

+ 30 - 14
numbat/src/unit.rs

@@ -23,6 +23,7 @@ pub enum UnitKind {
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct UnitIdentifier {
     name: String,
+    canonical_name: String,
     kind: UnitKind,
 }
 
@@ -33,7 +34,7 @@ impl UnitIdentifier {
 
     pub fn corresponding_base_unit(&self) -> Unit {
         match &self.kind {
-            UnitKind::Base => Unit::new_base(&self.name),
+            UnitKind::Base => Unit::new_base(&self.name, &self.canonical_name),
             UnitKind::Derived(_, base_unit) => base_unit.clone(),
         }
     }
@@ -118,24 +119,31 @@ impl Unit {
         Self::unity()
     }
 
-    pub fn new_base(name: &str) -> Self {
+    pub fn new_base(name: &str, canonical_name: &str) -> Self {
         Unit::from_factor(UnitFactor {
             prefix: Prefix::none(),
             unit_id: UnitIdentifier {
                 name: name.into(),
+                canonical_name: canonical_name.into(),
                 kind: UnitKind::Base,
             },
             exponent: Rational::from_integer(1),
         })
     }
 
-    pub fn new_derived(name: &str, factor: ConversionFactor, base_unit: Unit) -> Self {
+    pub fn new_derived(
+        name: &str,
+        canonical_name: &str,
+        factor: ConversionFactor,
+        base_unit: Unit,
+    ) -> Self {
         assert!(base_unit.iter().all(|f| f.unit_id.is_base()));
 
         Unit::from_factor(UnitFactor {
             prefix: Prefix::none(),
             unit_id: UnitIdentifier {
                 name: name.into(),
+                canonical_name: canonical_name.into(),
                 kind: UnitKind::Derived(factor, base_unit),
             },
             exponent: Rational::from_integer(1),
@@ -181,47 +189,52 @@ impl Unit {
 
     #[cfg(test)]
     pub fn meter() -> Self {
-        Self::new_base("meter")
+        Self::new_base("meter", "m")
     }
 
     #[cfg(test)]
     pub fn centimeter() -> Self {
-        Self::new_base("meter").with_prefix(Prefix::centi())
+        Self::new_base("meter", "m").with_prefix(Prefix::centi())
     }
 
     #[cfg(test)]
     pub fn millimeter() -> Self {
-        Self::new_base("meter").with_prefix(Prefix::milli())
+        Self::new_base("meter", "m").with_prefix(Prefix::milli())
     }
 
     #[cfg(test)]
     pub fn kilometer() -> Self {
-        Self::new_base("meter").with_prefix(Prefix::kilo())
+        Self::new_base("meter", "m").with_prefix(Prefix::kilo())
     }
 
     #[cfg(test)]
     pub fn second() -> Self {
-        Self::new_base("second")
+        Self::new_base("second", "s")
     }
 
     #[cfg(test)]
     pub fn hertz() -> Self {
-        Self::new_derived("hertz", Number::from_f64(1.0), Unit::second().powi(-1))
+        Self::new_derived(
+            "hertz",
+            "Hz",
+            Number::from_f64(1.0),
+            Unit::second().powi(-1),
+        )
     }
 
     #[cfg(test)]
     pub fn hour() -> Self {
-        Self::new_derived("hour", Number::from_f64(3600.0), Self::second())
+        Self::new_derived("hour", "h", Number::from_f64(3600.0), Self::second())
     }
 
     #[cfg(test)]
     pub fn mile() -> Self {
-        Self::new_derived("mile", Number::from_f64(1609.344), Self::meter())
+        Self::new_derived("mile", "mi", Number::from_f64(1609.344), Self::meter())
     }
 
     #[cfg(test)]
     pub fn bit() -> Self {
-        Self::new_base("bit")
+        Self::new_base("bit", "B")
     }
 }
 
@@ -234,8 +247,8 @@ impl Display for Unit {
             exponent,
         } in self.iter()
         {
-            result.push_str(&format!("{}", prefix));
-            result.push_str(&base_unit.name);
+            result.push_str(&format!("{}", prefix.to_string_short()));
+            result.push_str(&base_unit.canonical_name);
 
             if exponent == Ratio::from_integer(5) {
                 result.push('⁵');
@@ -284,6 +297,7 @@ mod tests {
                 prefix: Prefix::none(),
                 unit_id: UnitIdentifier {
                     name: "meter".into(),
+                    canonical_name: "m".into(),
                     kind: UnitKind::Base,
                 },
                 exponent: Rational::from_integer(1),
@@ -292,6 +306,7 @@ mod tests {
                 prefix: Prefix::none(),
                 unit_id: UnitIdentifier {
                     name: "second".into(),
+                    canonical_name: "s".into(),
                     kind: UnitKind::Base,
                 },
                 exponent: Rational::from_integer(-1),
@@ -354,6 +369,7 @@ mod tests {
                 prefix: Prefix::Metric(-3),
                 unit_id: UnitIdentifier {
                     name: "meter".into(),
+                    canonical_name: "m".into(),
                     kind: UnitKind::Base,
                 },
                 exponent: Rational::from_integer(1),

+ 22 - 9
numbat/src/vm.rs

@@ -159,8 +159,9 @@ pub struct Vm {
     /// Constants are numbers like '1.4' or a [Unit] like 'meter'.
     pub constants: Vec<Constant>,
 
-    /// The names of global variables or [Unit]s.
-    global_identifiers: Vec<String>,
+    /// The names of global variables or [Unit]s. The second
+    /// entry is the canonical name for units.
+    global_identifiers: Vec<(String, Option<String>)>,
 
     /// A dictionary of global variables and their respective values.
     globals: HashMap<String, Quantity>,
@@ -223,12 +224,23 @@ impl Vm {
         (self.constants.len() - 1) as u8 // TODO: this can overflow, see above
     }
 
-    pub fn add_global_identifier(&mut self, identifier: &str) -> u8 {
-        if let Some(idx) = self.global_identifiers.iter().position(|i| i == identifier) {
+    pub fn add_global_identifier(
+        &mut self,
+        identifier: &str,
+        canonical_unit_name: Option<&str>,
+    ) -> u8 {
+        if let Some(idx) = self
+            .global_identifiers
+            .iter()
+            .position(|i| i.0 == identifier)
+        {
             return idx as u8;
         }
 
-        self.global_identifiers.push(identifier.to_owned());
+        self.global_identifiers.push((
+            identifier.to_owned(),
+            canonical_unit_name.map(|s| s.to_owned()),
+        ));
         assert!(self.global_identifiers.len() <= u8::MAX as usize);
         (self.global_identifiers.len() - 1) as u8 // TODO: this can overflow, see above
     }
@@ -274,7 +286,7 @@ impl Vm {
         }
         println!(".IDENTIFIERS");
         for (idx, identifier) in self.global_identifiers.iter().enumerate() {
-            println!("  {:04} {}", idx, identifier);
+            println!("  {:04} {}", idx, identifier.0);
         }
         for (idx, (function_name, bytecode)) in self.bytecode.iter().enumerate() {
             println!(".CODE {idx} ({name})", idx = idx, name = function_name);
@@ -388,7 +400,8 @@ impl Vm {
                         defining_unit.to_base_unit_representation();
 
                     self.constants[constant_idx as usize] = Constant::Unit(Unit::new_derived(
-                        unit_name,
+                        &unit_name.0,
+                        unit_name.1.as_ref().unwrap(),
                         *conversion_value.unsafe_value() * factor,
                         base_unit_representation,
                     ));
@@ -399,7 +412,7 @@ impl Vm {
                     let identifier_idx = self.read_byte();
                     let quantity = self.pop();
                     let identifier: String =
-                        self.global_identifiers[identifier_idx as usize].clone();
+                        self.global_identifiers[identifier_idx as usize].0.clone();
 
                     self.globals.insert(identifier, quantity);
 
@@ -407,7 +420,7 @@ impl Vm {
                 }
                 Op::GetVariable => {
                     let identifier_idx = self.read_byte();
-                    let identifier = &self.global_identifiers[identifier_idx as usize];
+                    let identifier = &self.global_identifiers[identifier_idx as usize].0;
 
                     let quantity = self.globals.get(identifier).expect("Variable exists");
 

+ 28 - 27
numbat/tests/interpreter.rs

@@ -33,7 +33,7 @@ fn test_exponentiation() {
     expect_output("2²pi", "12.566371"); // TODO: when we support significant digits, this should be 12.5664
     expect_output("2² pi", "12.566371");
     expect_output("2²·pi", "12.566371");
-    expect_output("5m² to cm·m", "500 centimeter·meter");
+    expect_output("5m² to cm·m", "500 cm·m");
     expect_output("2⁵", "32");
     expect_output("-4¹", "-4");
     expect_output("2⁻¹", "0.5");
@@ -43,23 +43,23 @@ fn test_exponentiation() {
 
 #[test]
 fn test_conversions() {
-    expect_output("2in to cm", "5.08 centimeter"); // TODO: 5.08 cm
-    expect_output("5m^2 -> m*cm", "500 meter·centimeter"); // TODO: 500 m·cm
-    expect_output("5m^2 -> cm*m", "500 centimeter·meter"); // TODO: 500 cm·m
-    expect_output("1 kB / 10 ms -> MB/s", "0.1 megabyte·second⁻¹"); // TODO: 0.1 MB/s
+    expect_output("2in to cm", "5.08 cm");
+    expect_output("5m^2 -> m*cm", "500 m·cm");
+    expect_output("5m^2 -> cm*m", "500 cm·m");
+    expect_output("1 kB / 10 ms -> MB/s", "0.1 MB·s⁻¹"); // TODO: 0.1 MB/s
 }
 
 #[test]
 fn test_implicit_conversion() {
     let mut ctx = Context::new(false);
 
-    let _ = ctx.interpret("let x = 5 meter").unwrap();
+    let _ = ctx.interpret("let x = 5 m").unwrap();
 
-    expect_output_with_context(&mut ctx, "x", "5 meter");
-    expect_output_with_context(&mut ctx, "2x", "10 meter");
-    expect_output_with_context(&mut ctx, "2 x", "10 meter");
-    expect_output_with_context(&mut ctx, "x x", "25 meter²");
-    expect_output_with_context(&mut ctx, "x²", "25 meter²");
+    expect_output_with_context(&mut ctx, "x", "5 m");
+    expect_output_with_context(&mut ctx, "2x", "10 m");
+    expect_output_with_context(&mut ctx, "2 x", "10 m");
+    expect_output_with_context(&mut ctx, "x x", "25 m²");
+    expect_output_with_context(&mut ctx, "x²", "25 m²");
 
     expect_failure("x2");
 }
@@ -85,14 +85,14 @@ fn test_function_inverses() {
 
 #[test]
 fn test_temperature_conversions() {
-    expect_output("fromCelsius(11.5)", "284.65 kelvin");
-    expect_output("fromFahrenheit(89.3)", "304.983333 kelvin");
+    expect_output("fromCelsius(11.5)", "284.65 K");
+    expect_output("fromFahrenheit(89.3)", "304.983333 K");
     expect_output("toCelsius(0 K)", "-273.15");
     expect_output("toFahrenheit(30 K)", "-405.67");
     expect_output("toCelsius(fromCelsius(100))", "100");
     expect_output("toFahrenheit(fromFahrenheit(100))", "100.0");
-    expect_output("fromCelsius(toCelsius(123 K))", "123 kelvin");
-    expect_output("fromFahrenheit(toFahrenheit(123 K))", "123.0 kelvin");
+    expect_output("fromCelsius(toCelsius(123 K))", "123 K");
+    expect_output("fromFahrenheit(toFahrenheit(123 K))", "123.0 K");
 
     expect_output("-40 // fromFahrenheit // toCelsius", "-40.0");
 }
@@ -126,21 +126,22 @@ fn test_misc_examples() {
     expect_output("2^32", "4294967296");
     expect_output("sqrt(1.4^2 + 1.5^2) * cos(pi/3)^2", "0.512957");
 
-    // expect_output("2min + 30s", "2.5 min");
-    // expect_output("2min + 30s -> sec", "150 s");
-    // expect_output("4/3 * pi * (6000km)³", "904779000000 km³");
-    // expect_output("40kg * 9.8m/s^2 * 150cm", "588 m²·kg/s²");
+    expect_output("2min + 30s", "2.5 min");
+    expect_output("2min + 30s -> sec", "150 s");
+    expect_output("4/3 * pi * (6000km)³", "904778684233.860352 km³"); // TODO: insect prints this as 904779000000 km³ (sign. digits)
+    expect_output("40kg * 9.8m/s^2 * 150cm", "588 m²·s⁻²·kg"); // TODO: 588 m²·kg/s²
     expect_output("sin(30°)", "0.5");
 
-    // expect_output("60mph -> m/s", "26.8224 m/s");
-    // expect_output("240km/day -> km/h", "10 km/h");
-    // expect_output("1mrad -> °", "0.0572958°");
-    // expect_output("52weeks -> days", "364 d");
-    // expect_output("5in + 2ft -> cm", "73.66 cm");
-    // expect_output("atan(30cm / 2m) -> °", "8.53077°");
-    // expect_output("6Mbit/s * 1.5h -> GB", "4.05 GB");
+    expect_output("60mph -> m/s", "26.8224 m·s⁻¹"); // TODO: m/s
+    expect_output("240km/day -> km/h", "10.0 km·h⁻¹"); // TODO km/h
+    expect_output("1mrad -> °", "0.057296 deg"); // TODO: do we want that to be "x.y°"?
+    expect_output("52weeks -> days", "364 day");
+    expect_output("5in + 2ft -> cm", "73.66 cm");
+    expect_output("atan(30cm / 2m) -> °", "8.530766 deg");
+    expect_output("6Mbit/s * 1.5h -> GB", "4.05 GB");
+    expect_output("6Mbit/s * 1.5h -> GiB", "3.771856 GiB");
 
     expect_output("3m/4m", "0.75");
     expect_output("4/2*2", "4");
-    // expect_output("1/2 Hz -> s", "0.5 s");
+    expect_output("1/2 Hz -> s", "0.5 s");
 }

+ 11 - 12
prelude.nbt

@@ -56,7 +56,7 @@ dimension Temperature
 unit meter: Length
 
 @metric_prefixes
-@aliases(seconds, s: short)
+@aliases(seconds, s: short, sec: none)
 unit second: Time
 
 @metric_prefixes
@@ -113,19 +113,19 @@ unit kelvin: Temperature
 @aliases(radians, rad: short)
 unit radian = meter / meter
 
-@aliases(degrees, deg, °)
+@aliases(degrees, deg: short, °)
 unit degree = π radian / 180
 
 @metric_prefixes
-@aliases(litres, liter, liters)
+@aliases(litres, liter, liters, l: short, L: short)
 unit litre = 1 dm^3
 
 ### Time units
 
-@aliases(minutes, min)
+@aliases(minutes, min: short)
 unit minute: Time = 60 seconds
 
-@aliases(hours, h)
+@aliases(hours, h: short)
 unit hour: Time = 60 minutes
 
 @aliases(days)
@@ -151,7 +151,7 @@ unit bit: Bit
 
 @metric_prefixes
 @binary_prefixes
-@aliases(byte: both, bytes: both, octet, octets, B: short)
+@aliases(B: short, byte: both, bytes: both, octet, octets)
 unit byte: Bit = 8 bit
 
 @metric_prefixes
@@ -166,18 +166,18 @@ unit Wh = W h
 
 ### Imperial units
 
-@aliases(inches, in)
+@aliases(inches, in: short)
 unit inch: Length = 0.0254 meter
 
-@aliases(feet, ft)
+@aliases(feet, ft: short)
 unit foot: Length = 0.3048 meter
-assert_eq(1 foot, 12 inches, 0.001 inch)
+assert_eq(1 foot, 12 inches, 0.001 inch)  # TODO: move this somewhere else
 
-@aliases(yards, yd)
+@aliases(yards, yd: short)
 unit yard: Length = 0.9144 meter
 assert_eq(1 yard, 3 feet, 0.001 foot)
 
-@aliases(miles, mi)
+@aliases(miles, mi: short)
 unit mile: Length = 1609.344 meter
 assert_eq(1 mile, 1760 yard, 0.001 yard)
 
@@ -205,7 +205,6 @@ unit pixel: Pixel
 
 unit mph = miles per hour
 unit kph = kilometer per hour
-unit sec = second
 
 ### Physical constants