Code improvements, added a bunch of doc comments

There are technically no breaking changes because everything was broken in the first place
This commit is contained in:
Kasper 2020-08-20 17:11:19 +02:00
parent 04e61cafd7
commit 56ea8380b7
6 changed files with 157 additions and 48 deletions

View File

@ -37,15 +37,15 @@ cpc = "1.*"
[docs.rs documentation](https://docs.rs/cpc) [docs.rs documentation](https://docs.rs/cpc)
```rs ```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) => { Ok(answer) => {
// answer: Number { value: 301, unit: Unit::cm } // answer: Number { value: 301, unit: Unit::cm }
println!("Evaluated value: {} {:?}", answer.value, answer.unit) println!("Evaluated value: {} {:?}", answer.value, answer.unit)
}, },
Err(e) => { 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`. 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 #### 2. Add a test for the unit
Make sure to also add a test for each unit. The tests look like this: Make sure to also add a test for each unit. The tests look like this:

View File

@ -1,6 +1,6 @@
use decimal::d128; use decimal::d128;
use crate::Token; use crate::{Token, Number};
use crate::units::{Unit, UnitType, Number, convert, add, subtract, multiply, divide, modulo, pow}; use crate::units::{Unit, UnitType, convert, add, subtract, multiply, divide, modulo, pow};
use crate::parser::AstNode; use crate::parser::AstNode;
use crate::Operator::{Caret, Divide, Minus, Modulo, Multiply, Plus}; use crate::Operator::{Caret, Divide, Minus, Modulo, Multiply, Plus};
use crate::Constant::{Pi, E}; use crate::Constant::{Pi, E};
@ -14,11 +14,11 @@ pub fn evaluate(ast: &AstNode) -> Result<Number, String> {
Ok(answer) Ok(answer)
} }
fn factorial(input: d128) -> d128 { pub fn factorial(input: d128) -> d128 {
return lookup_factorial(input.into()); return lookup_factorial(input.into());
} }
fn sqrt(input: d128) -> d128 { pub fn sqrt(input: d128) -> d128 {
let mut n = d128!(1); let mut n = d128!(1);
let half = d128!(0.5); let half = d128!(0.5);
for _ in 0..10 { for _ in 0..10 {
@ -27,7 +27,7 @@ fn sqrt(input: d128) -> d128 {
return n return n
} }
fn cbrt(input: d128) -> d128 { pub fn cbrt(input: d128) -> d128 {
let mut n: d128 = input; let mut n: d128 = input;
// hope that 20 iterations makes it accurate enough // hope that 20 iterations makes it accurate enough
let three = d128!(3); let three = d128!(3);
@ -38,7 +38,7 @@ fn cbrt(input: d128) -> d128 {
return n return n
} }
fn sin(mut input: d128) -> d128 { pub fn sin(mut input: d128) -> d128 {
let pi = d128!(3.141592653589793238462643383279503); let pi = d128!(3.141592653589793238462643383279503);
let pi2 = d128!(6.283185307179586476925286766559006); 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); let half_pi = d128!(1.570796326794896619231321691639751);
return sin(half_pi - input); return sin(half_pi - input);
} }
fn tan(input: d128) -> d128 { pub fn tan(input: d128) -> d128 {
return sin(input) / cos(input); return sin(input) / cos(input);
} }
fn evaluate_node(ast_node: &AstNode) -> Result<Number, String> { pub fn evaluate_node(ast_node: &AstNode) -> Result<Number, String> {
let token = &ast_node.token; let token = &ast_node.token;
let children = &ast_node.children; let children = &ast_node.children;
match token { match token {

View File

@ -10,6 +10,7 @@ use crate::FunctionIdentifier::{Cbrt, Ceil, Cos, Exp, Abs, Floor, Ln, Log, Round
use crate::units::Unit; use crate::units::Unit;
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<TokenVector, String> { pub fn lex(input: &str, allow_trailing_operators: bool, default_degree: Unit) -> Result<TokenVector, String> {
let mut input = input.replace(",", ""); // ignore commas let mut input = input.replace(",", ""); // ignore commas

View File

@ -2,8 +2,43 @@ use crate::units::Unit;
pub mod units; pub mod units;
use std::time::{Instant}; use std::time::{Instant};
use decimal::d128; use decimal::d128;
pub mod lexer;
pub mod parser;
pub mod evaluator;
pub mod lookup;
#[derive(Clone, Debug)] #[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 { pub enum Operator {
Plus, Plus,
Minus, Minus,
@ -16,24 +51,28 @@ pub enum Operator {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// Unary operators like [`Percent`](enum.UnaryOperator.html#variant.Percent) and [`Factorial`](enum.UnaryOperator.html#variant.Factorial).
pub enum UnaryOperator { pub enum UnaryOperator {
Percent, Percent,
Factorial, Factorial,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// A Text operator like [`To`](enum.TextOperator.html#variant.To) or [`From`](enum.TextOperator.html#variant.From).
pub enum TextOperator { pub enum TextOperator {
To, To,
Of, Of,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// A constants like [`Pi`](enum.Constant.html#variant.Pi) or [`E`](enum.Constant.html#variant.E).
pub enum Constant { pub enum Constant {
Pi, Pi,
E, E,
} }
#[derive(Clone, Debug)] #[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 { pub enum FunctionIdentifier {
Sqrt, Sqrt,
Cbrt, Cbrt,
@ -53,6 +92,13 @@ pub enum FunctionIdentifier {
} }
#[derive(Clone, Debug)] #[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 { pub enum LexerKeyword {
Per, Per,
PercentChar, PercentChar,
@ -66,28 +112,48 @@ pub enum LexerKeyword {
} }
#[derive(Clone, Debug)] #[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 { pub enum Token {
Operator(Operator), Operator(Operator),
UnaryOperator(UnaryOperator), UnaryOperator(UnaryOperator),
Number(d128), Number(d128),
FunctionIdentifier(FunctionIdentifier), FunctionIdentifier(FunctionIdentifier),
Constant(Constant), Constant(Constant),
Paren, // parser only /// Used by the parser only
Per, // lexer only Paren,
/// Used by the lexer only
Per,
/// Used by the parser only
LexerKeyword(LexerKeyword), LexerKeyword(LexerKeyword),
TextOperator(TextOperator), TextOperator(TextOperator),
Negative, // parser only /// Used by the parser only
Negative,
Unit(units::Unit), Unit(units::Unit),
} }
/// A vector of [`Token`](enum.Token.html)
pub type TokenVector = Vec<Token>; pub type TokenVector = Vec<Token>;
mod lexer; /// Evaluates a string into a resulting [`Number`]().
mod parser; ///
mod evaluator; /// Example:
mod lookup; /// ```rust
/// use cpc::{eval};
pub fn eval(input: &str, allow_trailing_operators: bool, default_degree: Unit, debug: bool) -> Result<units::Number, String> { /// 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<Number, String> {
let lex_start = Instant::now(); let lex_start = Instant::now();

View File

@ -28,7 +28,7 @@ pub fn parse(tokens: &TokenVector) -> Result<AstNode, String> {
} }
// level 1 precedence (lowest): to, of // 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 // do higher precedences first, then come back down
let (mut node, mut pos) = parse_level_2(tokens, pos)?; let (mut node, mut pos) = parse_level_2(tokens, pos)?;
// now we loop through the next tokens // 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: +, - // 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)?; let (mut node, mut pos) = parse_level_3(tokens, pos)?;
loop { loop {
let token = tokens.get(pos); 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 // 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)?; let (mut node, mut pos) = parse_level_4(tokens, pos)?;
loop { loop {
@ -188,7 +188,7 @@ fn parse_level_3(tokens: &TokenVector, pos: usize) -> Result<(AstNode, usize), S
} }
// level 4 precedence: ^ // 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)?; let (mut node, mut pos) = parse_level_5(tokens, pos)?;
loop { loop {
let token = tokens.get(pos); 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) // 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 // Here we parse the negative unary operator. If the current token
// is a minus, we wrap the right_node inside a Negative AstNode. // 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 // 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)?; let (mut node, mut pos) = parse_level_7(tokens, pos)?;
loop { loop {
let token = tokens.get(pos); 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 // 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))?; let token: &Token = tokens.get(pos).ok_or(format!("Unexpected end of input at {}", pos))?;
match token { match token {
&Token::Number(_number) => { &Token::Number(_number) => {

View File

@ -1,25 +1,44 @@
use decimal::d128; use decimal::d128;
use crate::Number;
#[derive(Clone, Copy, PartialEq, Debug)] #[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 { pub enum UnitType {
/// A normal number, for example `5`
NoType, NoType,
/// A unit of time, for example `Hour`
Time, Time,
/// A unit of length, for example `Mile`
Length, Length,
/// A unit of area, for example `SquareKilometer`
Area, Area,
/// A unit of volume, for example `Liter` or `Tablespoon`
Volume, Volume,
/// A unit of mass, for example `Kilobyte`
Mass, Mass,
/// A unit of digital storage, for example `Kilobyte`
DigitalStorage, DigitalStorage,
/// A unit of energy, for example `Joule` or `KilowattHour`
Energy, Energy,
/// A unit of power, for example `Watt`
Power, Power,
/// A unit of pressure, for example `Bar`
Pressure, Pressure,
/// A unit of x, for example `KilometersPerHour`
Speed, Speed,
/// A unit of temperature, for example `Kelvin`
Temperature, Temperature,
} }
use UnitType::*; 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 { macro_rules! create_units {
( $( $variant:ident : $properties:expr ),*, ) => { ( $( $variant:ident : $properties:expr ),*, ) => {
#[derive(Clone, Copy, PartialEq, Debug)] #[derive(Clone, Copy, PartialEq, Debug)]
/// A Unit enum. Note that it can also be `NoUnit`.
pub enum Unit { pub enum Unit {
$($variant),* $($variant),*
} }
@ -208,25 +227,18 @@ create_units!(
Fahrenheit: (Temperature, d128!(0)), Fahrenheit: (Temperature, d128!(0)),
); );
#[derive(Clone, Debug)] /// Returns the conversion factor between two units.
pub struct Number { ///
pub value: d128, /// The conversion factor is what you need to multiply `unit` with to get
pub unit: Unit, /// `to_unit`. For example, the conversion factor from 1 minute to 1 second
} /// is 60.
///
impl Number { /// This is not sufficient for `Temperature` units.
pub fn new(value: d128, unit: Unit) -> Number { pub fn get_conversion_factor(unit: Unit, to_unit: Unit) -> d128 {
Number {
value: value,
unit: unit,
}
}
}
fn get_convertion_factor(unit: Unit, to_unit: Unit) -> d128 {
return unit.weight() / to_unit.weight(); return unit.weight() / to_unit.weight();
} }
/// Convert a `Number` to `to_unit`.
pub fn convert(number: Number, to_unit: Unit) -> Result<Number, String> { pub fn convert(number: Number, to_unit: Unit) -> Result<Number, String> {
if number.unit.category() != to_unit.category() { if number.unit.category() != to_unit.category() {
return Err(format!("Cannot convert from {:?} to {:?}", number.unit, to_unit)); return Err(format!("Cannot convert from {:?} to {:?}", number.unit, to_unit));
@ -249,11 +261,13 @@ pub fn convert(number: Number, to_unit: Unit) -> Result<Number, String> {
_ => Err(format!("Error converting temperature {:?} to {:?}", number.unit, to_unit)), _ => Err(format!("Error converting temperature {:?} to {:?}", number.unit, to_unit)),
} }
} else { } else {
let convertion_factor = get_convertion_factor(number.unit, to_unit); let conversion_factor = get_conversion_factor(number.unit, to_unit);
ok(number.value * convertion_factor) 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> { pub fn convert_to_lowest(left: Number, right: Number) -> Result<(Number, Number), String> {
if left.unit.weight() == right.unit.weight() { if left.unit.weight() == right.unit.weight() {
Ok((left, right)) 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<Number, String> { pub fn add(left: Number, right: Number) -> Result<Number, String> {
if left.unit == right.unit { if left.unit == right.unit {
Ok(Number::new(left.value + right.value, left.unit)) Ok(Number::new(left.value + right.value, left.unit))
@ -277,6 +292,7 @@ pub fn add(left: Number, right: Number) -> Result<Number, String> {
} }
} }
/// Subtract a `left` from `right`
pub fn subtract(left: Number, right: Number) -> Result<Number, String> { pub fn subtract(left: Number, right: Number) -> Result<Number, String> {
if left.unit == right.unit { if left.unit == right.unit {
Ok(Number::new(left.value - right.value, left.unit)) Ok(Number::new(left.value - right.value, left.unit))
@ -288,6 +304,12 @@ pub fn subtract(left: Number, right: Number) -> Result<Number, String> {
} }
} }
/// 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 { pub fn to_ideal_unit(number: Number) -> Number {
let value = number.value * number.unit.weight(); let value = number.value * number.unit.weight();
if number.unit.category() == Length { if number.unit.category() == Length {
@ -330,6 +352,12 @@ pub fn to_ideal_unit(number: 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<Number, String> { pub fn multiply(left: Number, right: Number) -> Result<Number, String> {
let lcat = left.unit.category(); let lcat = left.unit.category();
let rcat = right.unit.category(); let rcat = right.unit.category();
@ -373,6 +401,12 @@ pub fn multiply(left: Number, right: Number) -> Result<Number, String> {
} }
} }
/// 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<Number, String> { pub fn divide(left: Number, right: Number) -> Result<Number, String> {
let lcat = left.unit.category(); let lcat = left.unit.category();
let rcat = right.unit.category(); let rcat = right.unit.category();
@ -415,7 +449,9 @@ pub fn divide(left: Number, right: Number) -> Result<Number, String> {
Err(format!("Cannot divide {:?} by {:?}", left.unit, right.unit)) 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<Number, String> { pub fn modulo(left: Number, right: Number) -> Result<Number, String> {
if left.unit.category() == Temperature || right.unit.category() == Temperature { if left.unit.category() == Temperature || right.unit.category() == Temperature {
// if temperature // if temperature
@ -429,6 +465,12 @@ pub fn modulo(left: Number, right: Number) -> Result<Number, String> {
} }
} }
/// 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<Number, String> { pub fn pow(left: Number, right: Number) -> Result<Number, String> {
let lcat = left.unit.category(); let lcat = left.unit.category();
let rcat = left.unit.category(); let rcat = left.unit.category();