diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 3a391bc..0b07a68 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -105,7 +105,7 @@ impl Highlighter for LineHighlighter { let reg = Regex::new( r"(?x) - (?P([+\-/*%^!×÷⋅∧∨ᵀ]|if|otherwise|\sand|\sor|\smod|load|exit|clear|help)) | + (?P([+\-/*%^!×÷⋅∧∨¬ᵀ]|if|otherwise|\b(and|or|mod|true|false|not)\b|load|exit|clear|help)) | (?P0[box][a-zA-Z0-9]+) | (?P[^!-@\s_|^⌊⌋⌈⌉\[\]\{\}⟦⟧≠≥≤⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎ᵀ]+(_\d+)?)", ) @@ -159,6 +159,7 @@ lazy_static! { m.insert("<=", "≤"); m.insert(" and", " ∧"); m.insert(" or", " ∨"); + m.insert(" not", " ¬"); m.insert("*", "×"); m.insert("/", "÷"); m.insert("^T", "ᵀ"); @@ -194,6 +195,14 @@ impl Completer for RLHelper { return Ok((pos - key.len(), vec![value.to_string()])); } + // If the key starts with a space, it should also be expanded + // if it is at the start of the line. To do this, the strings + // are compared with the space removed in these situations. + if key.starts_with(' ') && slice.len() == key.len() - 1 && slice == &key[1..] { + let value = &(*COMPLETION_FUNCS.get(key).unwrap())[1..]; + return Ok((pos - (key.len() - 1), vec![value.to_string()])); + } + let mut subscript_digits = String::new(); for c in slice.chars().rev() { if c.is_digit(10) { diff --git a/kalk/src/analysis.rs b/kalk/src/analysis.rs index 9120944..6505d69 100644 --- a/kalk/src/analysis.rs +++ b/kalk/src/analysis.rs @@ -212,7 +212,7 @@ fn analyse_expr(context: &mut Context, expr: Expr) -> Result { Expr::Var(identifier) => analyse_var(context, identifier, None, None)?, Expr::Group(value) => Expr::Group(Box::new(analyse_expr(context, *value)?)), Expr::FnCall(identifier, arguments) => analyse_fn(context, identifier, arguments)?, - Expr::Literal(_) => expr, + Expr::Literal(_) | Expr::Boolean(_) => expr, Expr::Piecewise(pieces) => { let mut analysed_pieces = Vec::new(); for piece in pieces { diff --git a/kalk/src/ast.rs b/kalk/src/ast.rs index 2e13a42..8e9022e 100644 --- a/kalk/src/ast.rs +++ b/kalk/src/ast.rs @@ -20,6 +20,7 @@ pub enum Expr { Group(Box), FnCall(Identifier, Vec), Literal(f64), + Boolean(bool), Piecewise(Vec), Vector(Vec), Matrix(Vec>), diff --git a/kalk/src/errors.rs b/kalk/src/errors.rs index e135167..2e898e5 100644 --- a/kalk/src/errors.rs +++ b/kalk/src/errors.rs @@ -23,7 +23,7 @@ pub enum KalkError { VariableReferencesItself, PiecewiseConditionsAreFalse, EvaluationError(String), - UnexpectedToken(TokenKind, TokenKind), + UnexpectedToken(TokenKind, Option), UnexpectedType(String, Vec), UndefinedFn(String), UndefinedVar(String), @@ -66,7 +66,11 @@ impl ToString for KalkError { KalkError::PiecewiseConditionsAreFalse => String::from("All the conditions in the piecewise are false."), KalkError::EvaluationError(msg) => format!("Evaluation error: {}", msg), KalkError::UnexpectedToken(got, expected) => { - format!("Unexpected token: '{:?}', expected '{:?}'.", got, expected) + if let Some(expected) = expected { + format!("Unexpected token: '{:?}', expected '{:?}'.", got, expected) + } else { + format!("Unexpected token: '{:?}'.", got) + } } KalkError::UnexpectedType(got, expected) => { format!("Unexpected type. Got {:?} but expected: {:?}.", got, expected.join(", ")) diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index 4e12a78..e25e1e5 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -126,6 +126,7 @@ pub(crate) fn eval_expr( Expr::Unit(identifier, expr) => eval_unit_expr(context, identifier, expr), Expr::Var(identifier) => eval_var_expr(context, identifier, unit), Expr::Literal(value) => eval_literal_expr(context, *value, unit), + Expr::Boolean(value) => Ok(KalkValue::Boolean(*value)), Expr::Group(expr) => eval_group_expr(context, expr, unit), Expr::FnCall(identifier, expressions) => { eval_fn_call_expr(context, identifier, expressions, unit) @@ -209,6 +210,10 @@ fn eval_unary_expr( match op { TokenKind::Minus => num.mul(context, KalkValue::from(-1f64)), + TokenKind::Not => match num { + KalkValue::Boolean(boolean) => Ok(KalkValue::Boolean(!boolean)), + _ => Err(KalkError::InvalidOperator), + }, TokenKind::Percent => num.mul(context, KalkValue::from(0.01f64)), TokenKind::Exclamation => prelude::special_funcs::factorial(num), _ => Err(KalkError::InvalidOperator), diff --git a/kalk/src/inverter.rs b/kalk/src/inverter.rs index fab1fff..26dd799 100644 --- a/kalk/src/inverter.rs +++ b/kalk/src/inverter.rs @@ -85,7 +85,7 @@ fn invert( arguments, unknown_var, ), - Expr::Literal(_) => Ok((target_expr, expr.clone())), + Expr::Literal(_) | Expr::Boolean(_) => Ok((target_expr, expr.clone())), Expr::Piecewise(_) => Err(KalkError::UnableToInvert(String::from("Piecewise"))), Expr::Vector(_) => Err(KalkError::UnableToInvert(String::from("Vector"))), Expr::Matrix(_) => Err(KalkError::UnableToInvert(String::from("Matrix"))), @@ -391,7 +391,7 @@ pub fn contains_var(symbol_table: &SymbolTable, expr: &Expr, var_name: &str) -> false } - Expr::Literal(_) => false, + Expr::Literal(_) | Expr::Boolean(_) => false, Expr::Piecewise(_) => true, // Let it try to invert this. It will just display the error message. Expr::Vector(items) => items .iter() diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index 88d4179..13a15ec 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -25,6 +25,9 @@ pub enum TokenKind { LessOrEquals, And, Or, + Not, + True, + False, UnitKeyword, ToKeyword, @@ -153,6 +156,7 @@ impl<'a> Lexer<'a> { '<' => build(TokenKind::LessThan, "", span), '∧' => build(TokenKind::And, "", span), '∨' => build(TokenKind::Or, "", span), + '¬' => build(TokenKind::Not, "", span), ',' => build(TokenKind::Comma, "", span), ':' => build(TokenKind::Colon, "", span), ';' => build(TokenKind::Semicolon, "", span), @@ -324,6 +328,9 @@ impl<'a> Lexer<'a> { let kind = match value.as_ref() { "and" => TokenKind::And, "or" => TokenKind::Or, + "not" => TokenKind::Not, + "true" => TokenKind::True, + "false" => TokenKind::False, "mod" => TokenKind::Percent, "unit" => TokenKind::UnitKeyword, "to" => TokenKind::ToKeyword, @@ -391,7 +398,7 @@ fn is_valid_identifier(c: Option<&char>) -> bool { match c { '+' | '-' | '/' | '*' | '%' | '^' | '!' | '(' | ')' | '=' | '.' | ',' | ';' | '|' | '⌊' | '⌋' | '⌈' | '⌉' | '[' | ']' | '{' | '}' | 'π' | '√' | 'τ' | 'ϕ' | 'Γ' | '<' - | '>' | '≠' | '≥' | '≤' | '×' | '÷' | '⋅' | '⟦' | '⟧' | '∧' | '∨' | ':' | 'ᵀ' + | '>' | '≠' | '≥' | '≤' | '×' | '÷' | '⋅' | '⟦' | '⟧' | '∧' | '∨' | '¬' | ':' | 'ᵀ' | '\n' => false, _ => !c.is_digit(10) || is_superscript(c) || is_subscript(c), } diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 5861962..3a7ccc9 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -454,6 +454,7 @@ fn parse_exponent(context: &mut Context) -> Result { if match_token(context, TokenKind::Power) { let op = advance(context).kind; let right = Box::new(parse_exponent(context)?); + return Ok(Expr::Binary(Box::new(left), op, right)); } @@ -461,9 +462,10 @@ fn parse_exponent(context: &mut Context) -> Result { } fn parse_unary(context: &mut Context) -> Result { - if match_token(context, TokenKind::Minus) { + if match_token(context, TokenKind::Minus) || match_token(context, TokenKind::Not) { let op = advance(context).kind; let expr = Box::new(parse_unary(context)?); + return Ok(Expr::Unary(op, expr)); } @@ -511,7 +513,15 @@ fn parse_primary(context: &mut Context) -> Result { TokenKind::Pipe | TokenKind::OpenCeil | TokenKind::OpenFloor => parse_group_fn(context)?, TokenKind::Identifier => parse_identifier(context)?, TokenKind::Literal => Expr::Literal(string_to_num(&advance(context).value)?), - _ => return Err(KalkError::UnableToParseExpression), + TokenKind::True => { + advance(context); + Expr::Boolean(true) + } + TokenKind::False => { + advance(context); + Expr::Boolean(false) + } + _ => return Err(KalkError::UnexpectedToken(peek(context).kind, None)), }; Ok(expr) @@ -713,7 +723,7 @@ fn consume(context: &mut Context, kind: TokenKind) -> Result<&Token, KalkError> return Ok(advance(context)); } - Err(KalkError::UnexpectedToken(peek(context).kind, kind)) + Err(KalkError::UnexpectedToken(peek(context).kind, Some(kind))) } fn is_at_end(context: &Context) -> bool { @@ -728,7 +738,7 @@ fn skip_newlines(context: &mut Context) { fn string_to_num(value: &str) -> Result { let base = get_base(value)?; - if let Some(result) = crate::radix::parse_float_radix(&value.replace(" ", ""), base) { + if let Some(result) = crate::radix::parse_float_radix(&value.replace(' ', ""), base) { Ok(result) } else { Err(KalkError::InvalidNumberLiteral(value.into())) diff --git a/web/package.json b/web/package.json index 0c792a8..1fad995 100644 --- a/web/package.json +++ b/web/package.json @@ -48,8 +48,8 @@ "svelte-preprocess": "^4.6.1", "terser-webpack-plugin": "^4.2.3", "ts-loader": "^8.0.2", - "ts-node": "^9.0.0", - "typescript": "^4.1.2", + "ts-node": "^10.8.0", + "typescript": "^4.7.2", "webpack": "^4.44.1", "webpack-cli": "^3.3.12", "webpack-dev-server": "^3.11.0" @@ -61,4 +61,4 @@ "browserslist": [ "defaults" ] -} +} \ No newline at end of file diff --git a/web/src/KalkCalculator.svelte b/web/src/KalkCalculator.svelte index 570ffab..737f02b 100644 --- a/web/src/KalkCalculator.svelte +++ b/web/src/KalkCalculator.svelte @@ -310,7 +310,7 @@ let result = input; let offset = 0; result = result.replace( - /(?\^[0-9T])|(?\[\[)|(?0[box][a-zA-Z0-9]+)|(?(!=|[<>]=?))|(?[<>&]|(\n\s*\}?|\s+))|(?([+\-/*%^!≈×÷⋅∧∨ᵀ]|if|otherwise|and|or|mod)|(?[^!-@\s_|^⌊⌋⌈⌉≈\[\]\{\}⟦⟧≠≥≤⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎×÷⋅∧∨ᵀ]+(_\d+)?)\(?)/g, + /(?\^[0-9T])|(?\[\[)|(?0[box][a-zA-Z0-9]+)|(?(!=|[<>]=?))|(?[<>&]|(\n\s*\}?|\s+))|(?([+\-/*%^!≈×÷⋅∧∨¬ᵀ]|if|otherwise|and|or|mod|true|false|not)|(?[^!-@\s_|^⌊⌋⌈⌉≈\[\]\{\}⟦⟧≠≥≤⁰¹²³⁴⁵⁶⁷⁸⁹⁺⁻⁼⁽⁾₀₁₂₃₄₅₆₇₈₉₊₋₌₍₎×÷⋅∧∨ᵀ]+(_\d+)?)\(?)/g, ( substring, power, @@ -395,6 +395,7 @@ if (substring == "/") substring = "÷"; if (substring == "and") substring = "∧"; if (substring == "or") substring = "∨"; + if (substring == "not") substring = "¬"; } if (identifier) {