diff --git a/crates/unitdc-cli/src/main.rs b/crates/unitdc-cli/src/main.rs index 5081116..2ad8ac5 100644 --- a/crates/unitdc-cli/src/main.rs +++ b/crates/unitdc-cli/src/main.rs @@ -17,6 +17,7 @@ fn main() { .run_str(include_str!("../../../unitdc.rc")) .expect("unitdc.rc should run"); + // REPL loop for line in std::io::stdin().lines() { let line = line.expect("line should exist"); if let Err(e) = interpreter.run_str(&line) { diff --git a/crates/unitdc-web/src/lib.rs b/crates/unitdc-web/src/lib.rs index 56ba757..5452001 100644 --- a/crates/unitdc-web/src/lib.rs +++ b/crates/unitdc-web/src/lib.rs @@ -13,6 +13,7 @@ extern "C" { fn log(s: String); } +/// Runs a string in the interpreter. #[wasm_bindgen] pub fn unitdc_input(input: String) -> Result<(), JsValue> { unsafe { @@ -23,6 +24,7 @@ pub fn unitdc_input(input: String) -> Result<(), JsValue> { Ok(()) } +/// Initializes the interpreter, taking a callback function to send output to. #[wasm_bindgen] pub fn unitdc_init(js_output: Function) { unsafe { diff --git a/src/interpreter/mod.rs b/src/interpreter/mod.rs index 5d7fc42..bcfc052 100644 --- a/src/interpreter/mod.rs +++ b/src/interpreter/mod.rs @@ -13,8 +13,11 @@ use crate::{ use thiserror::Error; +/// All other operations. pub mod ops; +/// Macro operations. pub mod ops_macros; +/// Variable I/O operations. pub mod ops_variables; pub struct Interpreter<'a> { @@ -64,6 +67,7 @@ impl<'a> Interpreter<'a> { output, } } + /// Reads all tokens from the tokenizer and processes them. pub fn process_tokens<R: Read>( &mut self, tokenizer: &mut Tokenizer<R>, diff --git a/src/interpreter/ops.rs b/src/interpreter/ops.rs index 5f90476..75f66a0 100644 --- a/src/interpreter/ops.rs +++ b/src/interpreter/ops.rs @@ -12,10 +12,17 @@ use crate::{ use super::{Interpreter, InterpreterError, InterpreterResult, Output}; impl<'a> Interpreter<'a> { + /// A literal number input, pushes a unit-less quantity to the stack. pub fn op_number(&mut self, number: BigRational) -> InterpreterResult<()> { self.stack.push(Quantity::new(number, UnitCombo::new())); Ok(()) } + /// A literal unit input. + /// + /// - If the unit is unit-less (1), the top of the stack will be converted to a unit-less quantity. + /// - If the top of the stack is a unit-less quantity, it will be converted to the given unit. + /// - If the top of the stack is a quantity with equivalent units, it will be converted to the given unit. + /// - Otherwise, an error will be returned. pub fn op_unit(&mut self, unit: &str) -> InterpreterResult<()> { let mut q = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -67,6 +74,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Adds the top two quantities on the stack. pub fn op_add(&mut self) -> InterpreterResult<()> { let rhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; let lhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -76,6 +84,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Subtracts the top two quantities on the stack. pub fn op_sub(&mut self) -> InterpreterResult<()> { let rhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; let lhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -85,6 +94,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Multiplies the top two quantities on the stack. pub fn op_mul(&mut self) -> InterpreterResult<()> { let rhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; let lhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -93,6 +103,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Divides the top two quantities on the stack. pub fn op_div(&mut self) -> InterpreterResult<()> { let rhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; let lhs = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -101,6 +112,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Prints the top of the stack without altering it. pub fn op_p(&mut self) -> InterpreterResult<()> { let q = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -110,6 +122,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Prints the top of the stack and pops it. pub fn op_n(&mut self) -> InterpreterResult<()> { let q = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -117,11 +130,13 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Prints the entire stack. pub fn op_f(&mut self) -> InterpreterResult<()> { (self.output)(Output::QuantityList(self.stack.clone())); Ok(()) } + /// Duplicates the top of the stack. pub fn op_d(&mut self) -> InterpreterResult<()> { let q = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -130,11 +145,13 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Clears the stack. pub fn op_c(&mut self) -> InterpreterResult<()> { self.stack.clear(); Ok(()) } + /// Swaps the top two elements of the stack. pub fn op_r(&mut self) -> InterpreterResult<()> { let a = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; let b = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; @@ -144,6 +161,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Prints a summary of the unit system, including all base units, derived units, and their scale and offset. pub fn op_upper_u(&mut self) -> InterpreterResult<()> { let mut output = String::from("Base units:\n"); @@ -165,9 +183,11 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Invokes the unit solver. See [here](https://github.com/eternal-flame-AD/unitdc-rs/wiki/The-Unit-Solver) for instructions. pub fn op_s(&mut self) -> InterpreterResult<()> { let target = self.stack.pop().ok_or(InterpreterError::StackUnderflow)?; + // first figure out how many known quantities we have let n_src_quantities = target.number_in_derived_unit().to_integer(); if n_src_quantities.to_usize().unwrap_or(0) > self.stack.len() { return Err(InterpreterError::StackUnderflow); @@ -176,6 +196,7 @@ impl<'a> Interpreter<'a> { .stack .split_off(self.stack.len() - n_src_quantities.to_usize().unwrap_or(0)); let dst_unit = target.unit.reduce(); + // Check that all units involved are present let mut units_involved = Vec::new(); for q in &src_quantities { for u in &q.unit.0 { diff --git a/src/interpreter/ops_macros.rs b/src/interpreter/ops_macros.rs index 5e83b33..c0ad8a6 100644 --- a/src/interpreter/ops_macros.rs +++ b/src/interpreter/ops_macros.rs @@ -3,6 +3,10 @@ use crate::quantity::units::{BaseUnit, DerivedUnit}; use super::{Interpreter, InterpreterError, InterpreterResult}; impl<'a> Interpreter<'a> { + /// Defines a new base unit. + /// + /// For example to define a unit "usd" (US Dollar), you would do: + /// `@base(usd)` pub fn op_macro_baseunit(&mut self, arg: &str) -> InterpreterResult<()> { let symbol = arg.trim(); @@ -16,6 +20,11 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Defines a new derived unit. + /// + /// This is done by popping a "scale" and then an "offset" from the stack. + /// For example, to define a new unit "mpg" (miles per gallon), you would do: + /// `0 (mi) 1 (gal) / 1 (mi) 1 (gal) / @derived(mpg)` pub fn op_macro_derivedunit(&mut self, arg: &str) -> InterpreterResult<()> { let symbol = arg.trim(); diff --git a/src/interpreter/ops_variables.rs b/src/interpreter/ops_variables.rs index ae041b0..f4c2fd0 100644 --- a/src/interpreter/ops_variables.rs +++ b/src/interpreter/ops_variables.rs @@ -1,6 +1,7 @@ use super::{Interpreter, InterpreterError, InterpreterResult}; impl<'a> Interpreter<'a> { + /// Pops a quantity from the stack and stores it in a variable. pub fn op_store(&mut self, arg: &str) -> InterpreterResult<()> { let symbol = arg.trim(); @@ -10,6 +11,7 @@ impl<'a> Interpreter<'a> { Ok(()) } + /// Pushes a quantity from a variable onto the stack. pub fn op_recall(&mut self, arg: &str) -> InterpreterResult<()> { let symbol = arg.trim(); diff --git a/src/lib.rs b/src/lib.rs index 0bc033b..823603a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,8 @@ +/// Interpreter for UnitDC expressions. pub mod interpreter; +/// A module for evaluating linear systems of equations. pub mod linear_system; +/// A module for manipulating units and quantities. pub mod quantity; +/// Tokenizer for the interpreter. pub mod tokenizer; diff --git a/src/quantity/mod.rs b/src/quantity/mod.rs index dcceb98..ff8f432 100644 --- a/src/quantity/mod.rs +++ b/src/quantity/mod.rs @@ -12,6 +12,7 @@ pub mod units; use units::{DerivedUnit, UnitCombo}; +/// A quantity of a combination of a number, the unit combination, and a list of derived units to use when formatting the quantity. #[derive(Debug, Clone, PartialEq, Deserialize)] pub struct Quantity { pub number: BigRational, @@ -41,6 +42,7 @@ pub enum QuantityError { } impl Quantity { + /// Create a new quantity with the given number and unit combination. pub fn new(number: BigRational, unit: UnitCombo) -> Self { Quantity { number, @@ -48,6 +50,7 @@ impl Quantity { use_derived_unit: Vec::new(), } } + /// Computes the "user-facing" number of the quantity, considering the offset and scale of matching derived units. pub fn number_in_derived_unit(&self) -> BigRational { let mut number = self.number.clone(); diff --git a/src/quantity/units.rs b/src/quantity/units.rs index 695e86d..9f97aa4 100644 --- a/src/quantity/units.rs +++ b/src/quantity/units.rs @@ -124,12 +124,14 @@ impl Display for DerivedUnit { } } +/// A `UnitExponent` is the combination of a base unit and an exponent. #[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] pub struct UnitExponent { pub unit: BaseUnit, pub exponent: i32, } +/// A `UnitCombo` is a combination of `UnitExponent`s, which described an arbitrary "unit". #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UnitCombo(pub Vec<UnitExponent>);