diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index f680d9a..8dbb898 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -156,6 +156,12 @@ fn eval_binary_expr( TokenKind::Slash => left.div(context, right), TokenKind::Percent => left.rem(context, right), TokenKind::Power => left.pow(context, right), + TokenKind::Equals => left.eq(context, right), + TokenKind::NotEquals => left.not_eq(context, right), + TokenKind::GreaterThan => left.greater_than(context, right), + TokenKind::LessThan => left.less_than(context, right), + TokenKind::GreaterOrEquals => left.greater_or_equals(context, right), + TokenKind::LessOrEquals => left.less_or_equals(context, right), _ => KalkNum::from(1), }; @@ -468,12 +474,54 @@ mod tests { let mul = Stmt::Expr(binary(literal(2f64), Star, literal(3f64))); let div = Stmt::Expr(binary(literal(2f64), Slash, literal(4f64))); let pow = Stmt::Expr(binary(literal(2f64), Power, literal(3f64))); + let equals = Stmt::Expr(binary(literal(2f64), Equals, literal(3f64))); + let not_equals = Stmt::Expr(binary(literal(2f64), NotEquals, literal(3f64))); + let greater_than = Stmt::Expr(binary(literal(2f64), GreaterThan, literal(3f64))); + let less_than = Stmt::Expr(binary(literal(2f64), LessThan, literal(3f64))); + let greater_or_equals = Stmt::Expr(binary(literal(2f64), GreaterOrEquals, literal(3f64))); + let less_or_equals = Stmt::Expr(binary(literal(2f64), LessOrEquals, literal(3f64))); assert_eq!(interpret(add).unwrap().unwrap().to_f64(), 5f64); assert_eq!(interpret(sub).unwrap().unwrap().to_f64(), -1f64); assert_eq!(interpret(mul).unwrap().unwrap().to_f64(), 6f64); assert_eq!(interpret(div).unwrap().unwrap().to_f64(), 0.5f64); assert_eq!(interpret(pow).unwrap().unwrap().to_f64(), 8f64); + + let result = interpret(equals).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, false) + ); + + let result = interpret(not_equals).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, true) + ); + + let result = interpret(greater_than).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, false) + ); + + let result = interpret(less_than).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, true) + ); + + let result = interpret(greater_or_equals).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, false) + ); + + let result = interpret(less_or_equals).unwrap().unwrap(); + assert_eq!( + (result.to_f64(), result.boolean_value.unwrap()), + (3f64, true) + ); } #[test] diff --git a/kalk/src/kalk_num/mod.rs b/kalk/src/kalk_num/mod.rs index a01bf3a..2c7cd4d 100644 --- a/kalk/src/kalk_num/mod.rs +++ b/kalk/src/kalk_num/mod.rs @@ -186,6 +186,10 @@ impl KalkNum { } pub fn to_string_pretty(&self) -> String { + if let Some(boolean_value) = self.boolean_value { + return boolean_value.to_string(); + } + let real_f64 = self.to_f64(); let imaginary_f64 = self.imaginary_to_f64(); if real_f64.is_nan() || imaginary_f64.is_nan() { @@ -314,6 +318,58 @@ impl KalkNum { KalkNum::new(self.value % right.value, &right.unit) } + pub(crate) fn eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value == right.value); + right + } + + pub(crate) fn not_eq(self, context: &mut crate::interpreter::Context, rhs: KalkNum) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value != right.value); + right + } + + pub(crate) fn greater_than( + self, + context: &mut crate::interpreter::Context, + rhs: KalkNum, + ) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value > right.value); + right + } + + pub(crate) fn less_than( + self, + context: &mut crate::interpreter::Context, + rhs: KalkNum, + ) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value < right.value); + right + } + + pub(crate) fn greater_or_equals( + self, + context: &mut crate::interpreter::Context, + rhs: KalkNum, + ) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value >= right.value); + right + } + + pub(crate) fn less_or_equals( + self, + context: &mut crate::interpreter::Context, + rhs: KalkNum, + ) -> KalkNum { + let mut right = calculate_unit(context, &self, rhs.clone()).unwrap_or(rhs); + right.boolean_value = Some(self.value <= right.value); + right + } + pub(crate) fn add_without_unit(self, rhs: KalkNum) -> KalkNum { KalkNum::new_with_imaginary( self.value + rhs.value, diff --git a/kalk/src/kalk_num/regular.rs b/kalk/src/kalk_num/regular.rs index cf5b338..de8568a 100644 --- a/kalk/src/kalk_num/regular.rs +++ b/kalk/src/kalk_num/regular.rs @@ -7,6 +7,7 @@ pub struct KalkNum { pub(crate) value: f64, pub(crate) unit: String, pub(crate) imaginary_value: f64, + pub(crate) boolean_value: Option, } #[wasm_bindgen] @@ -16,6 +17,7 @@ impl KalkNum { value, unit: unit.to_string(), imaginary_value: 0f64, + boolean_value: None, } } @@ -28,6 +30,7 @@ impl KalkNum { } else { imaginary_value }, + boolean_value: None, } } @@ -36,6 +39,16 @@ impl KalkNum { value: 0f64, unit: String::new(), imaginary_value: value, + boolean_value: None, + } + } + + pub fn from_bool(value: bool) -> Self { + Self { + value: 0f64, + unit: String::new(), + imaginary_value: 0f64, + boolean_value: Some(value), } } diff --git a/kalk/src/kalk_num/with_rug.rs b/kalk/src/kalk_num/with_rug.rs index b230395..ae99e7d 100644 --- a/kalk/src/kalk_num/with_rug.rs +++ b/kalk/src/kalk_num/with_rug.rs @@ -11,6 +11,7 @@ pub struct KalkNum { pub(crate) value: Float, pub(crate) unit: String, pub(crate) imaginary_value: Float, + pub(crate) boolean_value: Option, } impl KalkNum { @@ -19,6 +20,7 @@ impl KalkNum { value, unit: unit.to_string(), imaginary_value: Float::with_val(63, 0), + boolean_value: None, } } @@ -31,6 +33,7 @@ impl KalkNum { } else { imaginary_value }, + boolean_value: None, } } @@ -39,6 +42,16 @@ impl KalkNum { value: Float::with_val(63, 0), unit: String::new(), imaginary_value: value, + boolean_value: None, + } + } + + pub fn from_bool(value: bool) -> Self { + Self { + value: Float::with_val(63, 0), + unit: String::new(), + imaginary_value: Float::with_val(63, 0), + boolean_value: Some(value), } } diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index fb9f25f..cc5981f 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -13,10 +13,15 @@ pub enum TokenKind { Star, Slash, Power, - Equals, Exclamation, Percent, Tick, + GreaterThan, + LessThan, + Equals, + NotEquals, + GreaterOrEquals, + LessOrEquals, UnitKeyword, ToKeyword, @@ -28,6 +33,8 @@ pub enum TokenKind { ClosedFloor, OpenParenthesis, ClosedParenthesis, + OpenBracket, + ClosedBracket, Comma, Semicolon, @@ -110,12 +117,19 @@ impl<'a> Lexer<'a> { '⌋' => build(TokenKind::ClosedFloor, "", span), '(' => build(TokenKind::OpenParenthesis, "", span), ')' => build(TokenKind::ClosedParenthesis, "", span), - '=' => build(TokenKind::Equals, "", span), + '[' => build(TokenKind::OpenBracket, "", span), + ']' => build(TokenKind::ClosedBracket, "", span), '!' => build(TokenKind::Exclamation, "", span), + '=' => build(TokenKind::Equals, "", span), + '>' => build(TokenKind::GreaterThan, "", span), + '<' => build(TokenKind::LessThan, "", span), ',' => build(TokenKind::Comma, "", span), ';' => build(TokenKind::Semicolon, "", span), '%' => build(TokenKind::Percent, "", span), '\'' => build(TokenKind::Tick, "", span), + '≠' => build(TokenKind::NotEquals, "", span), + '≥' => build(TokenKind::GreaterOrEquals, "", span), + '≤' => build(TokenKind::LessOrEquals, "", span), // Some of the special symbols will be lexed here, // so that they don't merge with other symbols. 'π' => build(TokenKind::Identifier, "π", span), @@ -128,13 +142,25 @@ impl<'a> Lexer<'a> { self.advance(); - // Handle ** - if let (TokenKind::Star, Some(c)) = (token.kind, self.peek()) { - if *c == '*' { + // Handle tokens with two characters + match (token.kind, self.peek()) { + (TokenKind::Star, Some('*')) => { self.advance(); - return build(TokenKind::Power, "", span); } + (TokenKind::Exclamation, Some('=')) => { + self.advance(); + return build(TokenKind::NotEquals, "", span); + } + (TokenKind::GreaterThan, Some('=')) => { + self.advance(); + return build(TokenKind::GreaterOrEquals, "", span); + } + (TokenKind::LessThan, Some('=')) => { + self.advance(); + return build(TokenKind::LessOrEquals, "", span); + } + _ => (), } token @@ -228,7 +254,8 @@ fn is_valid_identifier(c: Option<&char>) -> bool { if let Some(c) = c { match c { '+' | '-' | '/' | '*' | '%' | '^' | '!' | '(' | ')' | '=' | '.' | ',' | ';' | '|' - | '⌊' | '⌋' | '⌈' | '⌉' | ']' | 'π' | '√' | 'τ' | 'ϕ' | 'Γ' => false, + | '⌊' | '⌋' | '⌈' | '⌉' | '[' | ']' | 'π' | '√' | 'τ' | 'ϕ' | 'Γ' | '<' | '>' | '≠' + | '≥' | '≤' => false, _ => !c.is_digit(10), } } else { @@ -274,6 +301,22 @@ mod tests { match_tokens(tokens, expected); } + #[test] + #[wasm_bindgen_test] + fn test_brackets() { + let tokens = Lexer::lex("[1 < 2]"); + let expected = vec![ + TokenKind::OpenBracket, + TokenKind::Literal, + TokenKind::LessThan, + TokenKind::Literal, + TokenKind::ClosedBracket, + TokenKind::EOF, + ]; + + match_tokens(tokens, expected); + } + #[test] #[wasm_bindgen_test] fn test_empty() { diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index c86c3f5..a3ce17d 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -149,7 +149,9 @@ pub fn eval( ) -> Result, CalcError> { // Variable and function declaration parsers will set this to false // if the equal sign is for one of those instead. - context.contains_equation_equal_sign = input.contains("="); + // It also should not contain an iverson bracket, since equal signs in there + // mean something else. This is not super reliable, and should probably be improved in the future. + context.contains_equation_equal_sign = input.contains("=") && !input.contains("["); let statements = parse(context, input)?; let mut interpreter = interpreter::Context::new( @@ -302,13 +304,14 @@ fn parse_unit_decl_stmt(context: &mut Context) -> Result { } fn parse_expr(context: &mut Context) -> Result { - Ok(parse_equation(context)?) + Ok(parse_equality(context)?) } -fn parse_equation(context: &mut Context) -> Result { - let left = parse_to(context)?; +fn parse_equality(context: &mut Context) -> Result { + let mut left = parse_to(context)?; - if match_token(context, TokenKind::Equals) { + // Equation + if match_token(context, TokenKind::Equals) && context.contains_equation_equal_sign { advance(context); let right = parse_to(context)?; let var_name = if let Some(var_name) = &context.equation_variable { @@ -333,9 +336,25 @@ fn parse_equation(context: &mut Context) -> Result { Identifier::from_full_name(var_name), Box::new(inverted.clone()), )); + return Ok(inverted); } + // Equality check + while match_token(context, TokenKind::Equals) + || match_token(context, TokenKind::NotEquals) + || match_token(context, TokenKind::GreaterThan) + || match_token(context, TokenKind::LessThan) + || match_token(context, TokenKind::GreaterOrEquals) + || match_token(context, TokenKind::LessOrEquals) + { + let op = peek(context).kind; + advance(context); + let right = parse_to(context)?; + + left = Expr::Binary(Box::new(left), op, Box::new(right)); + } + Ok(left) } @@ -462,7 +481,9 @@ fn parse_factorial(context: &mut Context) -> Result { fn parse_primary(context: &mut Context) -> Result { let expr = match peek(context).kind { TokenKind::OpenParenthesis => parse_group(context)?, - TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?, + TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor | TokenKind::OpenBracket => { + parse_group_fn(context)? + } TokenKind::Identifier => parse_identifier(context)?, TokenKind::Literal => Expr::Literal(string_to_num(&advance(context).value)?), _ => return Err(CalcError::UnableToParseExpression), @@ -484,6 +505,7 @@ fn parse_group_fn(context: &mut Context) -> Result { TokenKind::Pipe => "abs", TokenKind::OpenCeil => "ceil", TokenKind::OpenFloor => "floor", + TokenKind::OpenBracket => "iverson", _ => unreachable!(), }; diff --git a/kalk/src/prelude/mod.rs b/kalk/src/prelude/mod.rs index 87c3e80..0e2bee2 100644 --- a/kalk/src/prelude/mod.rs +++ b/kalk/src/prelude/mod.rs @@ -89,6 +89,7 @@ lazy_static! { m.insert("abs", (UnaryFuncInfo(abs, Other), "")); m.insert("cbrt", (UnaryFuncInfo(cbrt, Other), "")); m.insert("ceil", (UnaryFuncInfo(ceil, Other), "")); + m.insert("iverson", (UnaryFuncInfo(inverson, Other), "")); m.insert("exp", (UnaryFuncInfo(exp, Other), "")); m.insert("floor", (UnaryFuncInfo(floor, Other), "")); m.insert("frac", (UnaryFuncInfo(frac, Other), "")); @@ -506,6 +507,18 @@ pub mod funcs { KalkNum::new_with_imaginary(x.value, "", KalkNum::default().value) } + pub fn inverson(x: KalkNum) -> KalkNum { + KalkNum::from(if let Some(boolean_value) = x.boolean_value { + if boolean_value { + 1 + } else { + 0 + } + } else { + 1 + }) + } + pub fn log(x: KalkNum) -> KalkNum { if x.has_imaginary() || x.value < 0f64 { // ln(z) / ln(10) diff --git a/kalk_cli/src/repl.rs b/kalk_cli/src/repl.rs index aae2794..e9f429d 100644 --- a/kalk_cli/src/repl.rs +++ b/kalk_cli/src/repl.rs @@ -76,7 +76,7 @@ impl Highlighter for LineHighlighter { let reg = Regex::new( r"(?x) - (?P[^!-@\s_|^⌊⌋⌈⌉]+(_\d+)?) | + (?P[^!-@\s_|^⌊⌋⌈⌉\[\]≠≥≤]+(_\d+)?) | (?P[+\-/*%^!])", ) .unwrap(); @@ -122,6 +122,9 @@ lazy_static! { m.insert("sqrt", "√"); m.insert("tau", "τ"); m.insert("(", "()"); + m.insert("!=", "≠"); + m.insert(">=", "≥"); + m.insert("<=", "≤"); m }; }