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:
parent
04e61cafd7
commit
56ea8380b7
@ -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:
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
84
src/lib.rs
84
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<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();
|
||||
|
||||
|
||||
@ -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) => {
|
||||
|
||||
80
src/units.rs
80
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<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();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user