From 56ea8380b7c3878b5fb09c5aaebe42e2a8194e82 Mon Sep 17 00:00:00 2001 From: Kasper Date: Thu, 20 Aug 2020 17:11:19 +0200 Subject: [PATCH] Code improvements, added a bunch of doc comments There are technically no breaking changes because everything was broken in the first place --- README.md | 8 ++--- src/evaluator.rs | 18 +++++------ src/lexer.rs | 1 + src/lib.rs | 84 ++++++++++++++++++++++++++++++++++++++++++------ src/parser.rs | 14 ++++---- src/units.rs | 80 ++++++++++++++++++++++++++++++++++----------- 6 files changed, 157 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 2850946..86613c0 100644 --- a/README.md +++ b/README.md @@ -37,15 +37,15 @@ cpc = "1.*" [docs.rs documentation](https://docs.rs/cpc) ```rs -use cpc::{eval, Unit::*} +use cpc::{eval, Unit}; -match eval("3m + 1cm", true, Celcius) { +match eval("3m + 1cm", true, Unit::Celcius, false) { Ok(answer) => { // answer: Number { value: 301, unit: Unit::cm } println!("Evaluated value: {} {:?}", answer.value, answer.unit) }, Err(e) => { - println!(e) + println!("{}", e) } } ``` @@ -137,7 +137,7 @@ create_units!( The number associated with a unit is it's "weight". For example, if a second's weight is `1`, then a minute's weight is `1000`. -I have found [translatorscafe.com](https://www.translatorscafe.com/unit-converter) and [calculateme.com](https://www.calculateme.com/) to be good websites for unit convertion. Wikipedia is worth looking at as well. +I have found [translatorscafe.com](https://www.translatorscafe.com/unit-converter) and [calculateme.com](https://www.calculateme.com/) to be good websites for unit conversion. Wikipedia is worth looking at as well. #### 2. Add a test for the unit Make sure to also add a test for each unit. The tests look like this: diff --git a/src/evaluator.rs b/src/evaluator.rs index d14e919..ec8237c 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -1,6 +1,6 @@ use decimal::d128; -use crate::Token; -use crate::units::{Unit, UnitType, Number, convert, add, subtract, multiply, divide, modulo, pow}; +use crate::{Token, Number}; +use crate::units::{Unit, UnitType, convert, add, subtract, multiply, divide, modulo, pow}; use crate::parser::AstNode; use crate::Operator::{Caret, Divide, Minus, Modulo, Multiply, Plus}; use crate::Constant::{Pi, E}; @@ -14,11 +14,11 @@ pub fn evaluate(ast: &AstNode) -> Result { Ok(answer) } -fn factorial(input: d128) -> d128 { +pub fn factorial(input: d128) -> d128 { return lookup_factorial(input.into()); } -fn sqrt(input: d128) -> d128 { +pub fn sqrt(input: d128) -> d128 { let mut n = d128!(1); let half = d128!(0.5); for _ in 0..10 { @@ -27,7 +27,7 @@ fn sqrt(input: d128) -> d128 { return n } -fn cbrt(input: d128) -> d128 { +pub fn cbrt(input: d128) -> d128 { let mut n: d128 = input; // hope that 20 iterations makes it accurate enough let three = d128!(3); @@ -38,7 +38,7 @@ fn cbrt(input: d128) -> d128 { return n } -fn sin(mut input: d128) -> d128 { +pub fn sin(mut input: d128) -> d128 { let pi = d128!(3.141592653589793238462643383279503); let pi2 = d128!(6.283185307179586476925286766559006); @@ -67,16 +67,16 @@ fn sin(mut input: d128) -> d128 { } -fn cos(input: d128) -> d128 { +pub fn cos(input: d128) -> d128 { let half_pi = d128!(1.570796326794896619231321691639751); return sin(half_pi - input); } -fn tan(input: d128) -> d128 { +pub fn tan(input: d128) -> d128 { return sin(input) / cos(input); } -fn evaluate_node(ast_node: &AstNode) -> Result { +pub fn evaluate_node(ast_node: &AstNode) -> Result { let token = &ast_node.token; let children = &ast_node.children; match token { diff --git a/src/lexer.rs b/src/lexer.rs index 97d8e15..d7d1af5 100644 --- a/src/lexer.rs +++ b/src/lexer.rs @@ -10,6 +10,7 @@ use crate::FunctionIdentifier::{Cbrt, Ceil, Cos, Exp, Abs, Floor, Ln, Log, Round use crate::units::Unit; use crate::units::Unit::*; +/// Lex an input string and return a [`TokenVector`](../type.TokenVector.html) pub fn lex(input: &str, allow_trailing_operators: bool, default_degree: Unit) -> Result { let mut input = input.replace(",", ""); // ignore commas diff --git a/src/lib.rs b/src/lib.rs index 1e6979e..459c3e1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,43 @@ use crate::units::Unit; pub mod units; use std::time::{Instant}; use decimal::d128; +pub mod lexer; +pub mod parser; +pub mod evaluator; +pub mod lookup; #[derive(Clone, Debug)] +/// A number with a `Unit`. +/// +/// Example: +/// ```rust +/// use cpc::{eval,Number}; +/// use cpc::units::Unit; +/// use decimal::d128; +/// +/// let x = Number { +/// value: d128!(100), +/// unit: Unit::Meter, +/// }; +/// ``` +pub struct Number { + /// The number part of a `Number` struct + pub value: d128, + /// The unit of a `Number` struct. This can be `NoType` + pub unit: Unit, +} + +impl Number { + pub fn new(value: d128, unit: Unit) -> Number { + Number { + value: value, + unit: unit, + } + } +} + +#[derive(Clone, Debug)] +/// Math operators like [`Multiply`](enum.Operator.html#variant.Multiply), parentheses, etc. pub enum Operator { Plus, Minus, @@ -16,24 +51,28 @@ pub enum Operator { } #[derive(Clone, Debug)] +/// Unary operators like [`Percent`](enum.UnaryOperator.html#variant.Percent) and [`Factorial`](enum.UnaryOperator.html#variant.Factorial). pub enum UnaryOperator { Percent, Factorial, } #[derive(Clone, Debug)] +/// A Text operator like [`To`](enum.TextOperator.html#variant.To) or [`From`](enum.TextOperator.html#variant.From). pub enum TextOperator { To, Of, } #[derive(Clone, Debug)] +/// A constants like [`Pi`](enum.Constant.html#variant.Pi) or [`E`](enum.Constant.html#variant.E). pub enum Constant { Pi, E, } #[derive(Clone, Debug)] +/// Functions identifiers like [`Sqrt`](enum.FunctionIdentifier.html#variant.Sqrt), [`Sin`](enum.FunctionIdentifier.html#variant.Sin), [`Round`](enum.FunctionIdentifier.html#variant.Round), etc. pub enum FunctionIdentifier { Sqrt, Cbrt, @@ -53,6 +92,13 @@ pub enum FunctionIdentifier { } #[derive(Clone, Debug)] +/// A temporary enum used by the [`lexer`](lexer/index.html) to later determine what [`Token`](enum.Token.html) it is. +/// +/// For example, when a symbol like `%` is found, the lexer turns it into a +/// the [`PercentChar`](enum.LexerKeyword.html#variant.PercentChar) variant +/// and then later it checks the surrounding [`Token`](enum.Token.html)s and, +/// dependingon them, turns it into a [`Percent`](enum.UnaryOperator.html) or +/// [`Modulo`](enum.Operator.html) [`Token`](enum.Token.html). pub enum LexerKeyword { Per, PercentChar, @@ -66,28 +112,48 @@ pub enum LexerKeyword { } #[derive(Clone, Debug)] +/// A token like a [`Number`](enum.Token.html#variant.Number), [`Operator`](enum.Token.html#variant.Operator), [`Unit`](enum.Token.html#variant.Unit) etc. +/// +/// Strings can be divided up into these tokens by the [`lexer`](lexer/index.html), and then put into the [`parser`](parser/index.html). pub enum Token { Operator(Operator), UnaryOperator(UnaryOperator), Number(d128), FunctionIdentifier(FunctionIdentifier), Constant(Constant), - Paren, // parser only - Per, // lexer only + /// Used by the parser only + Paren, + /// Used by the lexer only + Per, + /// Used by the parser only LexerKeyword(LexerKeyword), TextOperator(TextOperator), - Negative, // parser only + /// Used by the parser only + Negative, Unit(units::Unit), } +/// A vector of [`Token`](enum.Token.html) pub type TokenVector = Vec; -mod lexer; -mod parser; -mod evaluator; -mod lookup; - -pub fn eval(input: &str, allow_trailing_operators: bool, default_degree: Unit, debug: bool) -> Result { +/// Evaluates a string into a resulting [`Number`](). +/// +/// Example: +/// ```rust +/// use cpc::{eval}; +/// use cpc::units::Unit; +/// +/// match eval("3m + 1cm", true, Unit::Celcius, false) { +/// Ok(answer) => { +/// // answer: Number { value: 301, unit: Unit::cm } +/// println!("Evaluated value: {} {:?}", answer.value, answer.unit) +/// }, +/// Err(e) => { +/// println!("{}", e) +/// } +/// } +/// ``` +pub fn eval(input: &str, allow_trailing_operators: bool, default_degree: Unit, debug: bool) -> Result { let lex_start = Instant::now(); diff --git a/src/parser.rs b/src/parser.rs index 79114ab..9364e33 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -28,7 +28,7 @@ pub fn parse(tokens: &TokenVector) -> Result { } // level 1 precedence (lowest): to, of -fn parse_level_1(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_1(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { // do higher precedences first, then come back down let (mut node, mut pos) = parse_level_2(tokens, pos)?; // now we loop through the next tokens @@ -55,7 +55,7 @@ fn parse_level_1(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 2 precedence: +, - -fn parse_level_2(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_2(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { let (mut node, mut pos) = parse_level_3(tokens, pos)?; loop { let token = tokens.get(pos); @@ -90,7 +90,7 @@ fn parse_level_2(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 3 precedence: *, /, modulo -fn parse_level_3(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_3(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { let (mut node, mut pos) = parse_level_4(tokens, pos)?; loop { @@ -188,7 +188,7 @@ fn parse_level_3(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 4 precedence: ^ -fn parse_level_4(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_4(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { let (mut node, mut pos) = parse_level_5(tokens, pos)?; loop { let token = tokens.get(pos); @@ -209,7 +209,7 @@ fn parse_level_4(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 5 precedence: - (as in -5, but not 4-5) -fn parse_level_5(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_5(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { // Here we parse the negative unary operator. If the current token // is a minus, we wrap the right_node inside a Negative AstNode. // @@ -235,7 +235,7 @@ fn parse_level_5(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 6 precedence: !, percent -fn parse_level_6(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_6(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { let (mut node, mut pos) = parse_level_7(tokens, pos)?; loop { let token = tokens.get(pos); @@ -266,7 +266,7 @@ fn parse_level_6(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S } // level 7 precedence: numbers, parens -fn parse_level_7(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { +pub fn parse_level_7(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), String> { let token: &Token = tokens.get(pos).ok_or(format!("Unexpected end of input at {}", pos))?; match token { &Token::Number(_number) => { diff --git a/src/units.rs b/src/units.rs index d28d0f1..f486efa 100644 --- a/src/units.rs +++ b/src/units.rs @@ -1,25 +1,44 @@ use decimal::d128; +use crate::Number; #[derive(Clone, Copy, PartialEq, Debug)] +/// An enum of all possible unit types, like `Length`, `DigitalStorage` etc. +/// There is also a `NoType` unit type for normal numbers. pub enum UnitType { + /// A normal number, for example `5` NoType, + /// A unit of time, for example `Hour` Time, + /// A unit of length, for example `Mile` Length, + /// A unit of area, for example `SquareKilometer` Area, + /// A unit of volume, for example `Liter` or `Tablespoon` Volume, + /// A unit of mass, for example `Kilobyte` Mass, + /// A unit of digital storage, for example `Kilobyte` DigitalStorage, + /// A unit of energy, for example `Joule` or `KilowattHour` Energy, + /// A unit of power, for example `Watt` Power, + /// A unit of pressure, for example `Bar` Pressure, + /// A unit of x, for example `KilometersPerHour` Speed, + /// A unit of temperature, for example `Kelvin` Temperature, } use UnitType::*; +// Macro for creating units. Not possible to extend/change the default units +// with this because the default units are imported into the lexer, parser +// and evaluator macro_rules! create_units { ( $( $variant:ident : $properties:expr ),*, ) => { #[derive(Clone, Copy, PartialEq, Debug)] + /// A Unit enum. Note that it can also be `NoUnit`. pub enum Unit { $($variant),* } @@ -208,25 +227,18 @@ create_units!( Fahrenheit: (Temperature, d128!(0)), ); -#[derive(Clone, Debug)] -pub struct Number { - pub value: d128, - pub unit: Unit, -} - -impl Number { - pub fn new(value: d128, unit: Unit) -> Number { - Number { - value: value, - unit: unit, - } - } -} - -fn get_convertion_factor(unit: Unit, to_unit: Unit) -> d128 { +/// Returns the conversion factor between two units. +/// +/// The conversion factor is what you need to multiply `unit` with to get +/// `to_unit`. For example, the conversion factor from 1 minute to 1 second +/// is 60. +/// +/// This is not sufficient for `Temperature` units. +pub fn get_conversion_factor(unit: Unit, to_unit: Unit) -> d128 { return unit.weight() / to_unit.weight(); } +/// Convert a `Number` to `to_unit`. pub fn convert(number: Number, to_unit: Unit) -> Result { if number.unit.category() != to_unit.category() { return Err(format!("Cannot convert from {:?} to {:?}", number.unit, to_unit)); @@ -249,11 +261,13 @@ pub fn convert(number: Number, to_unit: Unit) -> Result { _ => Err(format!("Error converting temperature {:?} to {:?}", number.unit, to_unit)), } } else { - let convertion_factor = get_convertion_factor(number.unit, to_unit); - ok(number.value * convertion_factor) + let conversion_factor = get_conversion_factor(number.unit, to_unit); + ok(number.value * conversion_factor) } } +/// If one of two provided `Number`s has a larger unit than the other, convert +/// the large one to the unit of the small one. pub fn convert_to_lowest(left: Number, right: Number) -> Result<(Number, Number), String> { if left.unit.weight() == right.unit.weight() { Ok((left, right)) @@ -266,6 +280,7 @@ pub fn convert_to_lowest(left: Number, right: Number) -> Result<(Number, Number) } } +/// Return the sum of `left` and `right` pub fn add(left: Number, right: Number) -> Result { if left.unit == right.unit { Ok(Number::new(left.value + right.value, left.unit)) @@ -277,6 +292,7 @@ pub fn add(left: Number, right: Number) -> Result { } } +/// Subtract a `left` from `right` pub fn subtract(left: Number, right: Number) -> Result { if left.unit == right.unit { Ok(Number::new(left.value - right.value, left.unit)) @@ -288,6 +304,12 @@ pub fn subtract(left: Number, right: Number) -> Result { } } +/// Convert `Number` to an ideal unit. +/// +/// If you have 1,000,000 millimeters, this will return 1 kilometer. +/// +/// This only affects units of `Length`, `Area` and `Volume`. Other units are +/// passed through. pub fn to_ideal_unit(number: Number) -> Number { let value = number.value * number.unit.weight(); if number.unit.category() == Length { @@ -330,6 +352,12 @@ pub fn to_ideal_unit(number: Number) -> Number { number } +/// Multiply `left` with `right` +/// +/// - Temperatures don't work +/// - If you multiple `NoType` with any other unit, the result gets that other unit +/// - If you multiple `Length` with `Length`, the result has a unit of `Area`, etc. +/// - If you multiple `Speed` with `Time`, the result has a unit of `Length` pub fn multiply(left: Number, right: Number) -> Result { let lcat = left.unit.category(); let rcat = right.unit.category(); @@ -373,6 +401,12 @@ pub fn multiply(left: Number, right: Number) -> Result { } } +/// Divide `left` by `right` +/// +/// - Temperatures don't work +/// - If you divide a unit by that same unit, the result has a unit of `NoType` +/// - If you divide `Volume` by `Length`, the result has a a unit of `Area`, etc. +/// - If you divide `Length` by `Time`, the result has a a unit of `Speed` pub fn divide(left: Number, right: Number) -> Result { let lcat = left.unit.category(); let rcat = right.unit.category(); @@ -415,7 +449,9 @@ pub fn divide(left: Number, right: Number) -> Result { Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) } } - +/// Modulo `left` by `right`.`left` and `right` need to have the same `UnitType`, and the result will have that same `UnitType`. +/// +/// Temperatures don't work. pub fn modulo(left: Number, right: Number) -> Result { if left.unit.category() == Temperature || right.unit.category() == Temperature { // if temperature @@ -429,6 +465,12 @@ pub fn modulo(left: Number, right: Number) -> Result { } } +/// Returns `left` to the power of `right` +/// +/// - If you take `Length` to the power of `NoType`, the result has a unit of `Area`. +/// - If you take `Length` to the power of `Length`, the result has a unit of `Area` +/// - If you take `Length` to the power of `Area`, the result has a unit of `Volume` +/// - etc. pub fn pow(left: Number, right: Number) -> Result { let lcat = left.unit.category(); let rcat = left.unit.category();