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)
```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:

View File

@ -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<Number, String> {
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<Number, String> {
pub fn evaluate_node(ast_node: &AstNode) -> Result<Number, String> {
let token = &ast_node.token;
let children = &ast_node.children;
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::*;
/// 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> {
let mut input = input.replace(",", ""); // ignore commas

View File

@ -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<Token>;
mod lexer;
mod parser;
mod evaluator;
mod lookup;
pub fn eval(input: &str, allow_trailing_operators: bool, default_degree: Unit, debug: bool) -> Result<units::Number, String> {
/// 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<Number, String> {
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
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) => {

View File

@ -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<Number, String> {
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<Number, String> {
_ => 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<Number, String> {
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<Number, String> {
}
}
/// Subtract a `left` from `right`
pub fn subtract(left: Number, right: Number) -> Result<Number, String> {
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<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 {
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<Number, String> {
let lcat = left.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> {
let lcat = left.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))
}
}
/// 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> {
if left.unit.category() == Temperature || right.unit.category() == 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> {
let lcat = left.unit.category();
let rcat = left.unit.category();