From 1a57ba40b4b9980d060fe22b4364bac89a16647e Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:03:38 +0100 Subject: [PATCH 1/8] WIP --- src/interpreting/interpreter.rs | 2 + src/interpreting/mod.rs | 1 + src/interpreting/stdlib.rs | 23 ++++++++++ src/lexing/lexer.rs | 5 +++ src/lexing/token.rs | 6 ++- src/parsing/ast.rs | 2 + src/parsing/parselets/infix_parselet.rs | 59 ++++++++++++++++++++++++- src/parsing/parser.rs | 13 +++++- 8 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 src/interpreting/stdlib.rs diff --git a/src/interpreting/interpreter.rs b/src/interpreting/interpreter.rs index 96f9bc9..807e0c2 100644 --- a/src/interpreting/interpreter.rs +++ b/src/interpreting/interpreter.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::interpreting::function::{add, assign, divide, expo, minus, mult}; +use crate::interpreting::stdlib::exec; use crate::parsing::ast::{Ast, Parameters}; pub fn interpret(ast: Ast, mut ram: &mut HashMap) -> Parameters { @@ -29,6 +30,7 @@ pub fn interpret(ast: Ast, mut ram: &mut HashMap) -> Paramet Parameters::Float(f) => Parameters::Float(f), Parameters::Int(i) => Parameters::Int(i), Parameters::Identifier(s) => Parameters::Identifier(s), + Parameters::Call(s) => exec(s, param1, Some(&ram)), Parameters::Null => Parameters::Null, }; last.clone() diff --git a/src/interpreting/mod.rs b/src/interpreting/mod.rs index 8be6046..4645d72 100644 --- a/src/interpreting/mod.rs +++ b/src/interpreting/mod.rs @@ -1,2 +1,3 @@ mod function; pub(crate) mod interpreter; +mod stdlib; diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs new file mode 100644 index 0000000..708f6ba --- /dev/null +++ b/src/interpreting/stdlib.rs @@ -0,0 +1,23 @@ +use crate::parsing::ast::{Ast, Parameters}; +use std::collections::HashMap; + +pub fn exec(s: String, p: Parameters, ram: Option<&HashMap>) -> Parameters { + match s { + _ => cos(p, ram), + } +} + +pub fn cos(p: Parameters, ram: Option<&HashMap>) -> Parameters { + match p { + Parameters::Int(i) => Parameters::Float((i.clone() as f64).cos()), + Parameters::Float(f) => Parameters::Float(f.clone().cos()), + Parameters::Identifier(s) => match ram { + None => Parameters::Null, + Some(i_ram) => match i_ram.get(s.as_str()) { + None => Parameters::Null, + Some(t) => cos(t.clone(), ram), + }, + }, + _ => Parameters::Null, + } +} diff --git a/src/lexing/lexer.rs b/src/lexing/lexer.rs index 38a4c3c..9f2e3e5 100644 --- a/src/lexing/lexer.rs +++ b/src/lexing/lexer.rs @@ -15,6 +15,7 @@ pub fn is_an_allowed_char(character: char) -> bool { || character == '.' || character == '=' || character == '^' + || character == ',' } fn lex_int( @@ -140,6 +141,10 @@ pub fn lex(input: String) -> Vec { vec.push(Token::OPE(EXPO)); current_pos += 1 } + ',' => { + vec.push(Token::COMMA); + current_pos += 1 + } ch => { if ch.is_numeric() { let (a, b) = lex_int(current_character, &mut chars, current_pos, length); diff --git a/src/lexing/token.rs b/src/lexing/token.rs index 68330f5..d848fe5 100644 --- a/src/lexing/token.rs +++ b/src/lexing/token.rs @@ -18,6 +18,7 @@ pub enum Token { EQUAL, RPAR, LPAR, + COMMA, Null, } @@ -34,6 +35,7 @@ pub enum TokenType { RPAR, LPAR, Null, + COMMA, EXPO, } @@ -47,7 +49,7 @@ pub enum Precedence { EXPONENT = 7, //PREFIX = 8, //POSTFIX = 9, - //CALL = 10, + CALL = 10, } impl Display for Operator { @@ -72,6 +74,7 @@ impl Display for Token { Token::INT(i) => write!(f, "{}", i), Token::IDENTIFIER(s) => write!(f, "{}", s), Token::OPE(s) => write!(f, "{}", s), + Token::COMMA => write!(f, ","), Token::Null => write!(f, "Null"), } } @@ -111,6 +114,7 @@ impl Token { Token::EQUAL => TokenType::EQUAL, Token::RPAR => TokenType::RPAR, Token::LPAR => TokenType::LPAR, + Token::COMMA => TokenType::COMMA, Token::Null => TokenType::Null, } } diff --git a/src/parsing/ast.rs b/src/parsing/ast.rs index 1290041..92400ef 100644 --- a/src/parsing/ast.rs +++ b/src/parsing/ast.rs @@ -17,6 +17,7 @@ pub enum Parameters { Assign, Null, ExpoOperation, + Call(String), } #[derive(Debug, Clone, PartialEq)] @@ -42,6 +43,7 @@ impl Display for Parameters { Assign => write!(f, "="), Null => write!(f, ""), ExpoOperation => write!(f, "^"), + Call(s) => write!(f, "{s}"), } } } diff --git a/src/parsing/parselets/infix_parselet.rs b/src/parsing/parselets/infix_parselet.rs index f49c082..22fbea0 100644 --- a/src/parsing/parselets/infix_parselet.rs +++ b/src/parsing/parselets/infix_parselet.rs @@ -1,4 +1,4 @@ -use crate::lexing::token::{Precedence, Token}; +use crate::lexing::token::{Precedence, Token, TokenType}; use crate::parsing::ast::{Ast, Parameters}; use crate::parsing::parser::CalcParser; @@ -30,6 +30,8 @@ pub struct ExpoParselet { pub is_right: bool, } +pub struct CallParselet {} + pub struct NullParset {} impl InfixParselet for PlusParselet { @@ -142,6 +144,61 @@ impl InfixParselet for AssignParselet { } } +fn create_ast_from_lst(lst: &Vec, name: String) -> Ast { + fn aux(lst: &[Ast], mut acc: Ast, name: String) -> Ast { + match lst { + [] => acc, + [h, q @ ..] => { + acc = Ast::Node { + value: Parameters::Call(name.clone()), + left: Box::from(h.clone()), + right: Box::from(acc), + }; + aux(q, acc, name.clone()) + } + } + } + + aux(lst.as_slice(), Ast::Nil, name) +} + +impl InfixParselet for CallParselet { + fn parse(&self, parser: &mut CalcParser, left: &Ast, token: Token) -> Ast { + let name = match left { + Ast::Nil => "", + Ast::Node { + value: v, + left: _left, + right: _right, + } => match v { + Parameters::Identifier(s) => s.as_str(), + _ => "", + }, + }; + + let mut lst: Vec = Vec::new(); + if !parser.match_token(TokenType::RPAR) { + lst.push(parser.parse_expression_empty()); + while parser.match_token(TokenType::COMMA) { + parser.consume(); + let ast = parser.parse_expression_empty(); + lst.push(ast); + } + parser.consume_expected(TokenType::RPAR); + } + let l = create_ast_from_lst(&lst, name.clone().to_string()); + Ast::Node { + value: Parameters::Call(name.to_string().clone()), + left: Box::new(Ast::Nil), + right: Box::new(Ast::Nil), + } + } + + fn get_precedence(&self) -> i64 { + Precedence::CALL as i64 + } +} + impl InfixParselet for NullParset { fn parse(&self, _parser: &mut CalcParser, left: &Ast, _token: Token) -> Ast { left.clone() diff --git a/src/parsing/parser.rs b/src/parsing/parser.rs index bd1d602..4bbf5a1 100644 --- a/src/parsing/parser.rs +++ b/src/parsing/parser.rs @@ -4,8 +4,8 @@ use crate::lexing::token::Token::*; use crate::lexing::token::{Token, TokenType}; use crate::parsing::ast::Ast; use crate::parsing::parselets::infix_parselet::{ - AssignParselet, DivideParselet, ExpoParselet, InfixParselet, MinusParselet, MultParselet, - NullParset, PlusParselet, + AssignParselet, CallParselet, DivideParselet, ExpoParselet, InfixParselet, MinusParselet, + MultParselet, NullParset, PlusParselet, }; use crate::parsing::parselets::prefix_parselet::{ FloatParselet, GroupParselet, IdentifierParselet, IntParselet, NullParselet, @@ -70,6 +70,14 @@ impl CalcParser<'_> { self.read.remove(0) } + pub fn match_token(&mut self, expected: TokenType) -> bool { + let token = self.look_ahead(0); + if token.to_token_type() != expected { + return false; + } + return true; + } + pub fn consume_expected(&mut self, expected: TokenType) -> Token { self.look_ahead(0); if self.read.len() == 0 { @@ -105,6 +113,7 @@ impl CalcParser<'_> { TokenType::DIVIDE => Some(Box::from(DivideParselet { is_right: false })), TokenType::EQUAL => Some(Box::from(AssignParselet {})), TokenType::EXPO => Some(Box::from(ExpoParselet { is_right: false })), + TokenType::LPAR => Some(Box::from(CallParselet {})), _ => Some(Box::from(NullParset {})), } } From 0529c91f7071150ddc50904cc7243d66a425ecae Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:07:25 +0100 Subject: [PATCH 2/8] work --- src/parsing/parselets/infix_parselet.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/parsing/parselets/infix_parselet.rs b/src/parsing/parselets/infix_parselet.rs index 22fbea0..ff46ea8 100644 --- a/src/parsing/parselets/infix_parselet.rs +++ b/src/parsing/parselets/infix_parselet.rs @@ -187,11 +187,7 @@ impl InfixParselet for CallParselet { parser.consume_expected(TokenType::RPAR); } let l = create_ast_from_lst(&lst, name.clone().to_string()); - Ast::Node { - value: Parameters::Call(name.to_string().clone()), - left: Box::new(Ast::Nil), - right: Box::new(Ast::Nil), - } + l } fn get_precedence(&self) -> i64 { From 36988db79abd1be090405b819ae685b59694a6db Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:38:14 +0100 Subject: [PATCH 3/8] add call add cos to stdlib --- src/interpreting/function.rs | 30 ++++++++++++++++ src/interpreting/interpreter.rs | 6 +++- src/interpreting/stdlib.rs | 48 +++++++++++++++++++------ src/parsing/ast.rs | 8 ++++- src/parsing/parselets/infix_parselet.rs | 28 ++++----------- 5 files changed, 87 insertions(+), 33 deletions(-) diff --git a/src/interpreting/function.rs b/src/interpreting/function.rs index 228ba7d..04146fc 100644 --- a/src/interpreting/function.rs +++ b/src/interpreting/function.rs @@ -71,6 +71,12 @@ pub fn add(i: Parameters, i2: Parameters, ram: Option<&HashMap { apply_operator(Parameters::Identifier(s), Parameters::Int(i), ram, add) } + (Parameters::Null, Parameters::Identifier(s)) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, add) + } + (Parameters::Identifier(s), Parameters::Null) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, add) + } (Parameters::Int(i), Parameters::Identifier(s)) => { apply_operator(Parameters::Identifier(s), Parameters::Int(i), ram, add) } @@ -107,6 +113,12 @@ pub fn minus( (Parameters::Identifier(s), Parameters::Int(i)) => { apply_operator(Parameters::Identifier(s), Parameters::Int(i), ram, minus) } + (Parameters::Null, Parameters::Identifier(s)) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, minus) + } + (Parameters::Identifier(s), Parameters::Null) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, minus) + } (Parameters::Int(i), Parameters::Identifier(s)) => { let v = apply_operator(Parameters::Identifier(s), Parameters::Int(i), ram, minus); match v { @@ -154,6 +166,12 @@ pub fn mult( (Parameters::Int(i), Parameters::Identifier(s)) => { apply_operator(Parameters::Identifier(s), Parameters::Int(i), ram, mult) } + (Parameters::Null, Parameters::Identifier(s)) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, mult) + } + (Parameters::Identifier(s), Parameters::Null) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, mult) + } (Parameters::Identifier(s), Parameters::Float(i)) => { apply_operator(Parameters::Identifier(s), Parameters::Float(i), ram, mult) } @@ -194,6 +212,12 @@ pub fn divide( _ => Parameters::Null, } } + (Parameters::Null, Parameters::Identifier(s)) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, divide) + } + (Parameters::Identifier(s), Parameters::Null) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, divide) + } (Parameters::Identifier(s), Parameters::Float(i)) => { apply_operator(Parameters::Identifier(s), Parameters::Float(i), ram, divide) } @@ -237,6 +261,12 @@ pub fn expo( (Parameters::Identifier(s), Parameters::Float(i)) => { apply_operator(Parameters::Identifier(s), Parameters::Float(i), ram, expo) } + (Parameters::Identifier(s), Parameters::Null) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, expo) + } + (Parameters::Null, Parameters::Identifier(s)) => { + apply_operator(Parameters::Identifier(s), Parameters::Null, ram, expo) + } (Parameters::Float(i), Parameters::Identifier(s)) => { apply_operator_reverse(Parameters::Float(i), Parameters::Identifier(s), ram, expo) } diff --git a/src/interpreting/interpreter.rs b/src/interpreting/interpreter.rs index 807e0c2..5830338 100644 --- a/src/interpreting/interpreter.rs +++ b/src/interpreting/interpreter.rs @@ -30,11 +30,15 @@ pub fn interpret(ast: Ast, mut ram: &mut HashMap) -> Paramet Parameters::Float(f) => Parameters::Float(f), Parameters::Int(i) => Parameters::Int(i), Parameters::Identifier(s) => Parameters::Identifier(s), - Parameters::Call(s) => exec(s, param1, Some(&ram)), + Parameters::Call(s) => Parameters::Identifier(s), Parameters::Null => Parameters::Null, }; last.clone() } + Ast::Call { name: n, lst: list } => { + let v: Vec = list.iter().map(|x| interpret(x.clone(), ram)).collect(); + exec(n, v, Some(&ram)) + } } } diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs index 708f6ba..558bd19 100644 --- a/src/interpreting/stdlib.rs +++ b/src/interpreting/stdlib.rs @@ -1,21 +1,49 @@ -use crate::parsing::ast::{Ast, Parameters}; +use crate::parsing::ast::Parameters; use std::collections::HashMap; +use std::f64::consts::PI; -pub fn exec(s: String, p: Parameters, ram: Option<&HashMap>) -> Parameters { +pub fn exec( + s: String, + lst: Vec, + ram: Option<&HashMap>, +) -> Parameters { match s { - _ => cos(p, ram), + _ => cos(&lst, ram), } } -pub fn cos(p: Parameters, ram: Option<&HashMap>) -> Parameters { - match p { - Parameters::Int(i) => Parameters::Float((i.clone() as f64).cos()), - Parameters::Float(f) => Parameters::Float(f.clone().cos()), +pub fn cos(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.cos()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.cos()) + } Parameters::Identifier(s) => match ram { - None => Parameters::Null, - Some(i_ram) => match i_ram.get(s.as_str()) { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { None => Parameters::Null, - Some(t) => cos(t.clone(), ram), + Some(t) => cos(&vec![t.clone()], ram), }, }, _ => Parameters::Null, diff --git a/src/parsing/ast.rs b/src/parsing/ast.rs index 92400ef..fd58120 100644 --- a/src/parsing/ast.rs +++ b/src/parsing/ast.rs @@ -28,6 +28,10 @@ pub enum Ast { left: Box, right: Box, }, + Call { + name: String, + lst: Vec, + }, } impl Display for Parameters { @@ -51,7 +55,7 @@ impl Display for Parameters { impl Parameters { pub fn pretty_print(&self, ram: Option<&HashMap>) { match self { - Parameters::Identifier(s) => { + Identifier(s) => { if ram == None { println!("{self}") } else { @@ -101,6 +105,7 @@ impl Ast { left: Box::from(node), right: right.clone(), }, + _ => node, } } pub fn insert_right(self, node: Ast) -> Self { @@ -115,6 +120,7 @@ impl Ast { left: left.clone(), right: Box::from(node), }, + _ => node, } } } diff --git a/src/parsing/parselets/infix_parselet.rs b/src/parsing/parselets/infix_parselet.rs index ff46ea8..00e34f1 100644 --- a/src/parsing/parselets/infix_parselet.rs +++ b/src/parsing/parselets/infix_parselet.rs @@ -1,4 +1,5 @@ use crate::lexing::token::{Precedence, Token, TokenType}; +use crate::parsing::ast::Ast::Call; use crate::parsing::ast::{Ast, Parameters}; use crate::parsing::parser::CalcParser; @@ -144,26 +145,8 @@ impl InfixParselet for AssignParselet { } } -fn create_ast_from_lst(lst: &Vec, name: String) -> Ast { - fn aux(lst: &[Ast], mut acc: Ast, name: String) -> Ast { - match lst { - [] => acc, - [h, q @ ..] => { - acc = Ast::Node { - value: Parameters::Call(name.clone()), - left: Box::from(h.clone()), - right: Box::from(acc), - }; - aux(q, acc, name.clone()) - } - } - } - - aux(lst.as_slice(), Ast::Nil, name) -} - impl InfixParselet for CallParselet { - fn parse(&self, parser: &mut CalcParser, left: &Ast, token: Token) -> Ast { + fn parse(&self, parser: &mut CalcParser, left: &Ast, _token: Token) -> Ast { let name = match left { Ast::Nil => "", Ast::Node { @@ -174,6 +157,7 @@ impl InfixParselet for CallParselet { Parameters::Identifier(s) => s.as_str(), _ => "", }, + _ => "", }; let mut lst: Vec = Vec::new(); @@ -186,8 +170,10 @@ impl InfixParselet for CallParselet { } parser.consume_expected(TokenType::RPAR); } - let l = create_ast_from_lst(&lst, name.clone().to_string()); - l + Call { + name: name.to_string(), + lst, + } } fn get_precedence(&self) -> i64 { From 714bff45c44e64b386a862c65145b413bba76b33 Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:41:36 +0100 Subject: [PATCH 4/8] add sin to stdlib --- src/interpreting/stdlib.rs | 42 +++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs index 558bd19..fbed9e7 100644 --- a/src/interpreting/stdlib.rs +++ b/src/interpreting/stdlib.rs @@ -7,7 +7,9 @@ pub fn exec( lst: Vec, ram: Option<&HashMap>, ) -> Parameters { - match s { + match s.as_str() { + "cos" => cos(&lst, ram), + "sin" => sin(&lst, ram), _ => cos(&lst, ram), } } @@ -49,3 +51,41 @@ pub fn cos(p: &Vec, ram: Option<&HashMap>) -> Pa _ => Parameters::Null, } } + +pub fn sin(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.sin()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.sin()) + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => sin(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} From 6ffd3cb839189194147b1a21714ac098621228e9 Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:48:15 +0100 Subject: [PATCH 5/8] add exp --- README.md | 4 +-- src/interpreting/stdlib.rs | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 3d6e4ac..99a4f52 100644 --- a/README.md +++ b/README.md @@ -70,10 +70,10 @@ cargo install mini-calc - [ ] Add more operations - [X] exponent - [ ] Add support for functions - - [ ] exp + - [X] exp - [ ] ln - [ ] log base a - - [ ] cos/sin/tan + - [X] cos/sin/tan - [ ] cosh/sinh/tanh - [ ] For later - [ ] Defining your own functions diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs index fbed9e7..9a829d2 100644 --- a/src/interpreting/stdlib.rs +++ b/src/interpreting/stdlib.rs @@ -10,6 +10,7 @@ pub fn exec( match s.as_str() { "cos" => cos(&lst, ram), "sin" => sin(&lst, ram), + "exp" => exp(&lst, ram), _ => cos(&lst, ram), } } @@ -89,3 +90,52 @@ pub fn sin(p: &Vec, ram: Option<&HashMap>) -> Pa _ => Parameters::Null, } } + +pub fn exp(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut plus = false; + let mut ln: f64 = 0.0; + + if p.len() > 1 { + match p.get(1) { + None => plus = false, + Some(t) => { + plus = true; + match t { + Parameters::Float(f) => ln = (*f), + Parameters::Int(i) => ln = ((*i) as f64), + _ => ln = 0.0, + } + } + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = (*i) as f64; + if plus { + Parameters::Float(ln.powf(fs)) + } else { + Parameters::Float(fs.exp()) + } + } + Parameters::Float(f) => { + if plus { + Parameters::Float(ln.powf(*f)) + } else { + Parameters::Float((*f).exp()) + } + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => exp(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} From fea8bc21443ebbf8949ab277c45a545e0887b652 Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:55:46 +0100 Subject: [PATCH 6/8] add tanh cosh sinh atan acos asin --- src/interpreting/stdlib.rs | 279 ++++++++++++++++++++++++++++++++++++- 1 file changed, 278 insertions(+), 1 deletion(-) diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs index 9a829d2..475bd69 100644 --- a/src/interpreting/stdlib.rs +++ b/src/interpreting/stdlib.rs @@ -1,7 +1,8 @@ -use crate::parsing::ast::Parameters; use std::collections::HashMap; use std::f64::consts::PI; +use crate::parsing::ast::Parameters; + pub fn exec( s: String, lst: Vec, @@ -10,7 +11,14 @@ pub fn exec( match s.as_str() { "cos" => cos(&lst, ram), "sin" => sin(&lst, ram), + "tan" => tan(&lst, ram), + "cosh" => cosh(&lst, ram), + "sinh" => sinh(&lst, ram), + "tanh" => tanh(&lst, ram), "exp" => exp(&lst, ram), + "acos" => acos(&lst, ram), + "asin" => asin(&lst, ram), + "atan" => atan(&lst, ram), _ => cos(&lst, ram), } } @@ -91,6 +99,275 @@ pub fn sin(p: &Vec, ram: Option<&HashMap>) -> Pa } } +pub fn tan(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.tan()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.tan()) + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => tan(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn cosh(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.cosh()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.cosh()) + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => cos(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn sinh(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.sinh()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.sinh()) + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => sinh(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn tanh(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = if degrees { + ((*i).clone() as f64) * (PI / 180.0) + } else { + (*i).clone() as f64 + }; + Parameters::Float(fs.tanh()) + } + Parameters::Float(f) => { + let fs: f64 = if degrees { (*f) * (PI / 180.0) } else { *f }; + Parameters::Float(fs.tanh()) + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => tanh(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn acos(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = (*i) as f64; + Parameters::Float(if degrees { + fs.acos() * (180.0 / PI) + } else { + fs.acos() + }) + } + Parameters::Float(f) => Parameters::Float(if degrees { + f.acos() * (180.0 / PI) + } else { + f.acos() + }), + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => acos(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn asin(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = (*i) as f64; + Parameters::Float(if degrees { + fs.asin() * (180.0 / PI) + } else { + fs.asin() + }) + } + Parameters::Float(f) => Parameters::Float(if degrees { + f.asin() * (180.0 / PI) + } else { + f.asin() + }), + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => asin(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + +pub fn atan(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut degrees = false; + + if p.len() > 1 { + match p.get(1) { + None => degrees = false, + Some(_) => degrees = true, + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = (*i) as f64; + Parameters::Float(if degrees { + fs.atan() * (180.0 / PI) + } else { + fs.atan() + }) + } + Parameters::Float(f) => Parameters::Float(if degrees { + f.atan() * (180.0 / PI) + } else { + f.atan() + }), + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => atan(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} + pub fn exp(p: &Vec, ram: Option<&HashMap>) -> Parameters { if p.len() < 1 { return Parameters::Null; From 01eaad1512afc12ca3ee87866c316def70edddae Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 15:59:17 +0100 Subject: [PATCH 7/8] add ln/log --- README.md | 11 +++++---- src/interpreting/stdlib.rs | 50 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 99a4f52..9a64acb 100644 --- a/README.md +++ b/README.md @@ -67,14 +67,15 @@ cargo install mini-calc - [X] Config - [X] Config colours - [X] Config prompt -- [ ] Add more operations +- [X] Add more operations - [X] exponent -- [ ] Add support for functions +- [X] Add support for functions - [X] exp - - [ ] ln - - [ ] log base a + - [X] ln + - [X] log base a - [X] cos/sin/tan - - [ ] cosh/sinh/tanh + - [X] cosh/sinh/tanh + - [X] atan/acos/asin - [ ] For later - [ ] Defining your own functions - [ ] Add RPN mode diff --git a/src/interpreting/stdlib.rs b/src/interpreting/stdlib.rs index 475bd69..a6c7de6 100644 --- a/src/interpreting/stdlib.rs +++ b/src/interpreting/stdlib.rs @@ -19,6 +19,8 @@ pub fn exec( "acos" => acos(&lst, ram), "asin" => asin(&lst, ram), "atan" => atan(&lst, ram), + "ln" => ln(&lst, ram), + "log" => ln(&lst, ram), _ => cos(&lst, ram), } } @@ -416,3 +418,51 @@ pub fn exp(p: &Vec, ram: Option<&HashMap>) -> Pa _ => Parameters::Null, } } +pub fn ln(p: &Vec, ram: Option<&HashMap>) -> Parameters { + if p.len() < 1 { + return Parameters::Null; + } + + let mut plus = false; + let mut sln: f64 = 0.0; + + if p.len() > 1 { + match p.get(1) { + None => plus = false, + Some(t) => { + plus = true; + match t { + Parameters::Float(f) => sln = (*f), + Parameters::Int(i) => sln = ((*i) as f64), + _ => sln = 0.0, + } + } + } + } + + match p.get(0).unwrap() { + Parameters::Int(i) => { + let fs: f64 = (*i) as f64; + if plus { + Parameters::Float(fs.log(sln)) + } else { + Parameters::Float(fs.ln()) + } + } + Parameters::Float(f) => { + if plus { + Parameters::Float((*f).log(sln)) + } else { + Parameters::Float((*f).ln()) + } + } + Parameters::Identifier(s) => match ram { + None => Parameters::Identifier("This variable is not initialized yet".to_string()), + Some(t) => match t.get(s.as_str()) { + None => Parameters::Null, + Some(t) => ln(&vec![t.clone()], ram), + }, + }, + _ => Parameters::Null, + } +} From 52ce053a9138b61552c9d288d34069b543f1cab3 Mon Sep 17 00:00:00 2001 From: Charlotte Thomas Date: Tue, 14 Nov 2023 16:06:57 +0100 Subject: [PATCH 8/8] bump to 2.3.0 add function to readme --- README.md | 29 ++++++++++++++++++++++++++++- docs/assets/expln.png | Bin 0 -> 20285 bytes docs/assets/trigo.png | Bin 0 -> 27070 bytes src/configuration/loader.rs | 2 +- src/main.rs | 2 +- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 docs/assets/expln.png create mode 100644 docs/assets/trigo.png diff --git a/README.md b/README.md index 9a64acb..3d22ca7 100644 --- a/README.md +++ b/README.md @@ -132,4 +132,31 @@ Configuration: It looks like: -![img.png](docs/assets/config_looks.png) \ No newline at end of file +![img.png](docs/assets/config_looks.png) + +## Functions + +The following functions are available + +- sin +- cos +- tan +- sinh +- cosh +- tanh +- asin +- acos +- atan +- exp +- ln +- log (alias of ln) + +### Trigonometry + +For trigonometry, the input are assumed to be in radian, if not, you have to put "false" or "true" as second argument, example shown bellow +![img.png](docs/assets/trigo.png) + +### Exp/ln + +If you use the exp function you can pass a second argument for the base you are using, if no second arguments are passed this is assumed to be in natural base +![img.png](docs/assets/expln.png) diff --git a/docs/assets/expln.png b/docs/assets/expln.png new file mode 100644 index 0000000000000000000000000000000000000000..eda28da17db59c65984b19b7d83cd306fd1791e9 GIT binary patch literal 20285 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 SmTU%^!tl=D;YyqW5C09ame@Z4 literal 0 HcmV?d00001 diff --git a/docs/assets/trigo.png b/docs/assets/trigo.png new file mode 100644 index 0000000000000000000000000000000000000000..570d86feee8edfc333de8daa170f8bfda08f6901 GIT binary patch literal 27070 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ΜUF{2T**DR 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 Y4xvm8ufVP|kiY@SNhwPfNxTgBAA1hVYXATM literal 0 HcmV?d00001 diff --git a/src/configuration/loader.rs b/src/configuration/loader.rs index 8d72e38..33d0427 100644 --- a/src/configuration/loader.rs +++ b/src/configuration/loader.rs @@ -119,7 +119,7 @@ pub fn load_color(string: String) -> Color { pub fn replace_variable(str: String) -> String { str.replace("%author%", "Charlotte Thomas") - .replace("%version%", "v2.2.2") + .replace("%version%", "v2.3.0") .to_string() } diff --git a/src/main.rs b/src/main.rs index a70f886..8fb5d1c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ fn main() { let style = loaded.prompt_style; let text = loaded.prompt; let mut verbose = false; - let version: String = "v2.2.2".to_string(); + let version: String = "v2.3.0".to_string(); interface .set_prompt(&format!( "\x01{prefix}\x02{text}\x01{suffix}\x02",