diff --git a/kalk/src/ast.rs b/kalk/src/ast.rs index 5346d55..035f848 100644 --- a/kalk/src/ast.rs +++ b/kalk/src/ast.rs @@ -22,6 +22,7 @@ pub enum Expr { Literal(f64), Piecewise(Vec), Vector(Vec), + Matrix(Vec>), Indexer(Box, Box), } diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index cf24d81..52563a8 100644 --- a/kalk/src/interpreter.rs +++ b/kalk/src/interpreter.rs @@ -125,6 +125,7 @@ pub(crate) fn eval_expr( } Expr::Piecewise(pieces) => eval_piecewise(context, pieces, unit), Expr::Vector(values) => eval_vector(context, values), + Expr::Matrix(rows) => eval_matrix(context, rows), Expr::Indexer(var, index) => eval_indexer(context, var, index, unit), } } @@ -536,6 +537,20 @@ fn eval_vector(context: &mut Context, values: &Vec) -> Result>) -> Result { + let mut eval_rows = Vec::new(); + for row in rows { + let mut eval_row = Vec::new(); + for value in row { + eval_row.push(eval_expr(context, value, "")?) + } + + eval_rows.push(eval_row); + } + + Ok(KalkValue::Matrix(eval_rows)) +} + fn eval_indexer( context: &mut Context, var: &Expr, diff --git a/kalk/src/inverter.rs b/kalk/src/inverter.rs index 2baf943..5027c4d 100644 --- a/kalk/src/inverter.rs +++ b/kalk/src/inverter.rs @@ -88,6 +88,7 @@ fn invert( Expr::Literal(_) => Ok((target_expr, expr.clone())), Expr::Piecewise(_) => Err(CalcError::UnableToInvert(String::from("Piecewise"))), Expr::Vector(_) => Err(CalcError::UnableToInvert(String::from("Vector"))), + Expr::Matrix(_) => Err(CalcError::UnableToInvert(String::from("Matrix"))), Expr::Indexer(_, _) => Err(CalcError::UnableToInvert(String::from("Inverter"))), } } @@ -391,6 +392,9 @@ pub fn contains_var(symbol_table: &SymbolTable, expr: &Expr, var_name: &str) -> Expr::Vector(items) => items .iter() .any(|x| contains_var(symbol_table, x, var_name)), + Expr::Matrix(rows) => rows + .iter() + .any(|row| row.iter().any(|x| contains_var(symbol_table, x, var_name))), Expr::Indexer(_, _) => false, } } diff --git a/kalk/src/kalk_value/mod.rs b/kalk/src/kalk_value/mod.rs index d20558f..e3d8937 100644 --- a/kalk/src/kalk_value/mod.rs +++ b/kalk/src/kalk_value/mod.rs @@ -173,6 +173,7 @@ pub enum KalkValue { Number(Float, Float, String), Boolean(bool), Vector(Vec), + Matrix(Vec>), } impl KalkValue { @@ -192,7 +193,7 @@ impl KalkValue { if &as_str == "0" { imaginary_as_str } else { - format!("{} {} {}i", as_str, sign, imaginary_as_str) + format!("{} {} {}i", as_str, sign, imaginary_as_str) } } else { as_str @@ -206,18 +207,43 @@ impl KalkValue { } } KalkValue::Vector(values) => { + format!( + "({})", + values + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", ") + ) + } + KalkValue::Matrix(rows) => { + let mut value_strings = Vec::new(); + let mut longest = 0; + for row in rows { + for value in row { + let value_str = value.to_string(); + longest = longest.max(value_str.len()); + value_strings.push(format!("{},", value_str)); + } + + value_strings.last_mut().unwrap().pop(); // Trailing comma + value_strings.push(String::from("\n")); + } + let mut result = String::from("["); - for value in values { - result.push_str(&value.to_string()); - result.push_str(", "); + for value_str in value_strings { + if value_str == "\n" { + result.push_str("\n "); + } else { + result.push_str(&format!("{:width$} ", value_str, width = longest + 1)); + } } - if values.len() > 0 { - result.pop(); - result.pop(); - } - - result.push_str("]"); + result.pop(); // Trailing new-line + result.pop(); // Trailing space + result.pop(); // Trailing space + result.pop(); // Trailing comma + result.push(']'); result } @@ -656,6 +682,9 @@ impl KalkValue { KalkValue::Number(real, imaginary, _), KalkValue::Number(real_rhs, imaginary_rhs, unit), ) => KalkValue::Number(real + real_rhs, imaginary + imaginary_rhs, unit.to_string()), + (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => { + calculate_matrix(self, rhs, &KalkValue::add_without_unit) + } (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => { calculate_vector(self, rhs, &KalkValue::add_without_unit) } @@ -669,6 +698,9 @@ impl KalkValue { KalkValue::Number(real, imaginary, _), KalkValue::Number(real_rhs, imaginary_rhs, unit), ) => KalkValue::Number(real - real_rhs, imaginary - imaginary_rhs, unit.to_string()), + (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => { + calculate_matrix(self, rhs, &KalkValue::sub_without_unit) + } (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => { calculate_vector(self, rhs, &KalkValue::sub_without_unit) } @@ -677,26 +709,83 @@ impl KalkValue { } pub(crate) fn mul_without_unit(self, rhs: &KalkValue) -> KalkValue { - // (a + bi)(c + di) = ac + adi + bci + bdi² - match (self.clone(), rhs) { + // Make sure matrix is always first to avoid having to match + // different orders in the next match expression. + let (lhs, rhs) = match (&self, rhs) { + (KalkValue::Matrix(_), KalkValue::Matrix(_)) => (&self, rhs), + (_, KalkValue::Matrix(_)) => (rhs, &self), + _ => (&self, rhs), + }; + + match (lhs, rhs) { ( KalkValue::Number(real, imaginary, _), KalkValue::Number(real_rhs, imaginary_rhs, unit), ) => KalkValue::Number( - real.clone() * real_rhs.clone() - imaginary.clone() * imaginary_rhs.clone(), - real * imaginary_rhs + imaginary * real_rhs, + // (a + bi)(c + di) = ac + adi + bci + bdi² + real.clone() * real_rhs - imaginary.clone() * imaginary_rhs, + real.clone() * imaginary_rhs + imaginary * real_rhs, unit.to_string(), ), + (KalkValue::Matrix(rows), KalkValue::Number(_, _, _)) => KalkValue::Matrix( + rows.iter() + .map(|row| { + row.iter() + .map(|x| x.clone().mul_without_unit(rhs)) + .collect() + }) + .collect(), + ), + (KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => { + if rows.first().unwrap().len() != values_rhs.len() { + return KalkValue::nan(); + } + + let mut new_values: Vec = Vec::new(); + for row in rows { + new_values.push( + row.iter() + .zip(values_rhs) + .map(|(x, y)| x.clone().mul_without_unit(y)) + .sum(), + ) + } + + KalkValue::Vector(new_values) + } + (KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => { + if rows.first().unwrap().len() != rows_rhs.len() { + return KalkValue::nan(); + } + + let mut result = Vec::new(); + // For every row in lhs + for i in 0..rows.len() { + let mut dot_products = Vec::new(); + + // For every column in rhs + for j in 0..rows.len() { + let mut dot_product = KalkValue::from(0f64); + + // For every value in the current lhs row + for (k, value) in rows[i].iter().enumerate() { + let value_rhs = &rows_rhs[k][j]; + dot_product = dot_product + .add_without_unit(&value.clone().mul_without_unit(value_rhs)); + } + + dot_products.push(dot_product); + } + + result.push(dot_products); + } + + KalkValue::Matrix(result) + } (KalkValue::Vector(values), KalkValue::Number(_, _, _)) => KalkValue::Vector( values .iter() - .map(|x| x.clone().mul_without_unit(&self)) - .collect(), - ), - (KalkValue::Number(_, _, _), KalkValue::Vector(values_rhs)) => KalkValue::Vector( - values_rhs - .iter() - .map(|x| self.clone().mul_without_unit(x)) + .map(|x| x.clone().mul_without_unit(lhs)) .collect(), ), (KalkValue::Vector(values), KalkValue::Vector(values_rhs)) => { @@ -738,6 +827,9 @@ impl KalkValue { ) } } + (KalkValue::Matrix(_), _) | (_, KalkValue::Matrix(_)) => { + calculate_matrix(self, rhs, &KalkValue::div_without_unit) + } (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => { calculate_vector(self, rhs, &KalkValue::div_without_unit) } @@ -772,6 +864,35 @@ impl KalkValue { KalkValue::Number(pow(real, real_rhs.clone()), float!(0), unit.to_string()) } } + (KalkValue::Matrix(rows), KalkValue::Number(real, imaginary, _)) => { + if real < &0f64 + || real.clone().fract() > 0.000001f64 + || !(imaginary == &0f64 || imaginary == &-0f64) + || rows.len() != rows.first().unwrap().len() + { + return KalkValue::nan(); + } + + if real == &0f64 { + return KalkValue::from(1f64); + } + + let mut result = KalkValue::from(1f64); + for _ in 0..primitive!(real) as i32 { + result = result.mul_without_unit(&self); + } + + result + } + (KalkValue::Number(_, _, _), KalkValue::Matrix(rows)) => KalkValue::Matrix( + rows.iter() + .map(|row| { + row.iter() + .map(|x| self.clone().pow_without_unit(x)) + .collect() + }) + .collect(), + ), (KalkValue::Vector(_), _) | (_, KalkValue::Vector(_)) => { calculate_vector(self, rhs, &KalkValue::pow_without_unit) } @@ -898,6 +1019,66 @@ fn calculate_vector( } } +fn calculate_matrix( + x: KalkValue, + y: &KalkValue, + action: &dyn Fn(KalkValue, &KalkValue) -> KalkValue, +) -> KalkValue { + // Make sure matrix is always first to avoid having to match + // different orders in the next match expression. + let (x, y) = match (&x, y) { + (_, KalkValue::Matrix(_)) => (y, &x), + _ => (&x, y), + }; + + match (x, y) { + (KalkValue::Matrix(rows), KalkValue::Number(_, _, _)) => KalkValue::Matrix( + rows.iter() + .map(|row| row.iter().map(|x| action(x.clone(), y)).collect()) + .collect(), + ), + (KalkValue::Matrix(rows), KalkValue::Vector(values_rhs)) => { + if rows.len() != values_rhs.len() { + return KalkValue::nan(); + } + + let mut new_rows = Vec::new(); + for (i, row) in rows.iter().enumerate() { + new_rows.push(Vec::new()); + for value in row { + new_rows + .last_mut() + .unwrap() + .push(action(value.clone(), &values_rhs[i])) + } + } + + KalkValue::Matrix(new_rows) + } + (KalkValue::Matrix(rows), KalkValue::Matrix(rows_rhs)) => { + if rows.len() != rows_rhs.len() + || rows.first().unwrap().len() != rows_rhs.first().unwrap().len() + { + return KalkValue::nan(); + } + + let mut new_rows = Vec::new(); + for (i, row) in rows.iter().enumerate() { + new_rows.push(Vec::new()); + for (j, value) in row.iter().enumerate() { + new_rows + .last_mut() + .unwrap() + .push(action(value.clone(), &rows_rhs[i][j])) + } + } + + KalkValue::Matrix(new_rows) + } + _ => KalkValue::nan(), + } +} + fn calculate_unit( context: &mut crate::interpreter::Context, left: &KalkValue, @@ -937,6 +1118,20 @@ impl Into for ScientificNotation { } } +impl std::iter::Sum for KalkValue { + fn sum(iter: I) -> KalkValue + where + I: std::iter::Iterator, + { + let mut sum = KalkValue::from(0f64); + for x in iter { + sum = sum.add_without_unit(&x); + } + + sum + } +} + impl Into for KalkValue { fn into(self) -> String { self.to_string() diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 3e65beb..81c74a6 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -105,6 +105,7 @@ pub enum CalcError { ExpectedIf, IncorrectAmountOfArguments(usize, String, usize), ItemOfIndexDoesNotExist(usize), + InconsistentColumnWidths, InvalidNumberLiteral(String), InvalidOperator, InvalidUnit, @@ -134,6 +135,7 @@ impl ToString for CalcError { expected, func, got ), CalcError::ItemOfIndexDoesNotExist(index) => format!("Item of index {} does not exist.", index), + CalcError::InconsistentColumnWidths => format!("Inconsistent column widths. Matrix columns must be the same size."), CalcError::InvalidNumberLiteral(x) => format!("Invalid number literal: '{}'.", x), CalcError::InvalidOperator => format!("Invalid operator."), CalcError::InvalidUnit => format!("Invalid unit."), @@ -614,12 +616,39 @@ fn parse_group_fn(context: &mut Context) -> Result { } fn parse_vector(context: &mut Context) -> Result { - advance(context); + let kind = advance(context).kind; + + if kind == TokenKind::OpenBracket { + skip_newlines(context); + } + + let mut rows = vec![vec![parse_expr(context)?]]; + let mut column_count = None; + let mut items_in_row = 1; + while match_token(context, TokenKind::Comma) + || match_token(context, TokenKind::Semicolon) + || (match_token(context, TokenKind::Newline) + && peek_next(context).kind != TokenKind::ClosedBracket) + { + if kind == TokenKind::OpenBracket + && (match_token(context, TokenKind::Newline) + || match_token(context, TokenKind::Semicolon)) + { + if let Some(columns) = column_count { + if columns != items_in_row { + return Err(CalcError::InconsistentColumnWidths); + } + } else { + column_count = Some(items_in_row); + } + + rows.push(Vec::new()); + items_in_row = 0; + } - let mut values = vec![parse_expr(context)?]; - while match_token(context, TokenKind::Comma) { advance(context); - values.push(parse_expr(context)?); + rows.last_mut().unwrap().push(parse_expr(context)?); + items_in_row += 1; } if peek(context).kind == TokenKind::EOF { @@ -628,12 +657,21 @@ fn parse_vector(context: &mut Context) -> Result { ))); } + if kind == TokenKind::OpenBracket { + skip_newlines(context); + } + advance(context); - if values.len() == 1 { - Ok(Expr::Group(Box::new(values.pop().unwrap()))) + if rows.len() == 1 { + let mut values = rows.pop().unwrap(); + if values.len() == 1 { + Ok(Expr::Group(Box::new(values.pop().unwrap()))) + } else { + Ok(Expr::Vector(values)) + } } else { - Ok(Expr::Vector(values)) + Ok(Expr::Matrix(rows)) } } diff --git a/kalk/src/prelude/mod.rs b/kalk/src/prelude/mod.rs index ab960f8..6c4faf7 100644 --- a/kalk/src/prelude/mod.rs +++ b/kalk/src/prelude/mod.rs @@ -283,9 +283,8 @@ pub mod funcs { let (real, imaginary, unit) = as_number_or_return!(x.clone()); if imaginary != 0f64 || real > 1f64 || real < -1f64 { // -i * ln(i * sqrt(1 - z²) + z) - let root = sqrt( - KalkValue::from(1f64).sub_without_unit(&x.clone().mul_without_unit(&x.clone())), - ); + let root = + sqrt(KalkValue::from(1f64).sub_without_unit(&x.clone().mul_without_unit(&x))); let iroot = multiply_with_i(root.clone()); let (ln_real, ln_imaginary, ln_unit) = as_number_or_return!(ln(iroot.add_without_unit(&x))); @@ -361,14 +360,14 @@ pub mod funcs { let (real, imaginary, unit) = as_number_or_return!(x.clone()); if imaginary != 0f64 || real == 0f64 { let (inv_x2_real, inv_x2_imaginary, inv_x2_unit) = as_number_or_return!( - KalkValue::from(1f64).div_without_unit(&x.clone().mul_without_unit(&x.clone())) + KalkValue::from(1f64).div_without_unit(&x.clone().mul_without_unit(&x)) ); let sqrt = sqrt(KalkValue::Number( 1f64 + inv_x2_real, inv_x2_imaginary, inv_x2_unit, )); - let inv_x = KalkValue::from(1f64).div_without_unit(&x.clone()); + let inv_x = KalkValue::from(1f64).div_without_unit(&x); ln(sqrt.add_without_unit(&inv_x)) } else { @@ -390,7 +389,7 @@ pub mod funcs { let (real, imaginary, unit) = as_number_or_return!(x.clone()); if imaginary != 0f64 || real <= 0f64 || real > 1f64 { // 1/z - let inv = KalkValue::from(1f64).div_without_unit(&x.clone()); + let inv = KalkValue::from(1f64).div_without_unit(&x); let (inv_real, inv_imaginary, inv_unit) = as_number_or_return!(inv.clone()); // sqrt(1/z - 1) let sqrt1 = sqrt(KalkValue::Number( @@ -416,9 +415,8 @@ pub mod funcs { let (real, imaginary, unit) = as_number_or_return!(x.clone()); if imaginary != 0f64 || real > 1f64 || real < -1f64 { // i * ln(sqrt(1 - z²) - iz) - let root = sqrt( - KalkValue::from(1f64).sub_without_unit(&x.clone().mul_without_unit(&x.clone())), - ); + let root = + sqrt(KalkValue::from(1f64).sub_without_unit(&x.clone().mul_without_unit(&x))); let iz = multiply_with_i(x.clone()); let ln = ln(root.sub_without_unit(&iz)); multiply_with_i(ln) @@ -431,7 +429,7 @@ pub mod funcs { let (real, imaginary, unit) = as_number_or_return!(x.clone()); if imaginary != 0f64 { let (x2_real, x2_imaginary, x2_unit) = - as_number_or_return!(x.clone().mul_without_unit(&x.clone())); + as_number_or_return!(x.clone().mul_without_unit(&x)); let sqrt = sqrt(KalkValue::Number(x2_real + 1f64, x2_imaginary, x2_unit)); ln(x.add_without_unit(&sqrt)) diff --git a/web/src/KalkCalculator.svelte b/web/src/KalkCalculator.svelte index 70aa363..77423d2 100644 --- a/web/src/KalkCalculator.svelte +++ b/web/src/KalkCalculator.svelte @@ -47,8 +47,19 @@ let highlightedTextElement: HTMLElement; let ignoreNextInput = false; - function setText(text: string) { - const [highlighted, offset] = highlight(text); + enum HighlightType { + Output, + InputField, + History, + } + + function setText(text: string, isFinalBeforeSubmit = false) { + const [highlighted, offset] = highlight( + text, + isFinalBeforeSubmit + ? HighlightType.History + : HighlightType.InputField + ); const prevCursorPos = inputElement.selectionStart; setHtml(highlighted); setCaret(prevCursorPos + offset); @@ -90,12 +101,18 @@ } } - function hasUnevenAmountOfBraces(input: string): boolean { + function hasUnevenAmountOfBrackets( + input: string, + openChar: string, + closedChar: string, + onlyCheckFirstLine = false + ): boolean { let openCount = 0; let closedCount = 0; for (const char of input) { - if (char == "{") openCount++; - if (char == "}") closedCount++; + if (onlyCheckFirstLine && char == "\n") break; + if (char == openChar) openCount++; + if (char == closedChar) closedCount++; } return openCount > closedCount; @@ -104,8 +121,15 @@ function handleKeyDown(event: KeyboardEvent, kalk: Kalk) { if (event.key == "Enter") { if ( - hasUnevenAmountOfBraces( - (event.target as HTMLTextAreaElement).value + hasUnevenAmountOfBrackets( + (event.target as HTMLTextAreaElement).value, + "{", + "}" + ) || + hasUnevenAmountOfBrackets( + (event.target as HTMLTextAreaElement).value, + "[", + "]" ) ) { return; @@ -127,13 +151,13 @@ const [result, success] = calculate(kalk, input); output = success - ? highlight(result, true)[0] + ? highlight(result, HighlightType.Output)[0] : `${result}`; } // Highlight const target = event.target as HTMLInputElement; - setText(target.value); + setText(target.value, true); outputLines = output ? [...outputLines, [getHtml(), true], [output, false]] @@ -276,7 +300,7 @@ function highlight( input: string, - isOutput: boolean = false + highlightType: HighlightType ): [string, number] { if (!input) return ["", 0]; let result = input; @@ -316,9 +340,38 @@ if (substring.startsWith("\n")) { if (substring.endsWith("}")) { return "
}"; + } else if (substring.endsWith("]")) { + return "
]"; } else { - if (!substring.match(/\n\s\s/)) offset += 2; - return isOutput ? "
" : "
  "; + let spaceCount = 2; + const unclosedBracketInFirstLine = + hasUnevenAmountOfBrackets( + input, + "[", + "]", + true + ); + if (unclosedBracketInFirstLine) { + let bracketIndex = input.indexOf("["); + spaceCount = + bracketIndex == -1 + ? spaceCount + : bracketIndex + 1; + } + + if (!substring.match(/\n\s/)) offset += spaceCount; + if (highlightType == HighlightType.Output) { + return "
"; + } else if ( + highlightType == HighlightType.InputField + ) { + return "
" + " ".repeat(spaceCount); + } else if (highlightType == HighlightType.History) { + // Account for ">> " + return ( + "
" + " ".repeat(spaceCount + 3) + ); + } } } if (substring.match(/\s+/)) { @@ -326,7 +379,7 @@ } } - if (op && !isOutput) { + if (op && highlightType != HighlightType.Output) { if (substring == "*") return "⋅"; if (substring == "/") return "÷"; }