From 098bb8fb90d02b33825e79bd9ad09725315180b0 Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Wed, 5 Jan 2022 22:41:41 +0100 Subject: [PATCH] Vector indexing with lowered numbers, eg. v_1 --- cli/src/repl.rs | 2 +- kalk/src/ast.rs | 9 +++++++++ kalk/src/interpreter.rs | 27 ++++++++++++++++++++++++--- kalk/src/inverter.rs | 2 ++ kalk/src/lexer.rs | 26 ++++++++++++++++++++++---- kalk/src/parser.rs | 31 +++++++++++++++++++++++++++++-- kalk/src/radix.rs | 2 +- kalk/src/symbol_table.rs | 2 +- kalk/src/text_utils.rs | 21 +++++++++++++++------ 9 files changed, 104 insertions(+), 18 deletions(-) diff --git a/cli/src/repl.rs b/cli/src/repl.rs index 03cb9db..522df5e 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -201,7 +201,7 @@ impl Completer for RLHelper { } if subscript_digits.len() > 0 { - let value = kalk::text_utils::digits_to_subscript(subscript_digits.chars()); + let value = kalk::text_utils::normal_to_subscript(subscript_digits.chars()); return Ok((pos - subscript_digits.chars().count() - 1, vec![value])); } } diff --git a/kalk/src/ast.rs b/kalk/src/ast.rs index 7780d93..5346d55 100644 --- a/kalk/src/ast.rs +++ b/kalk/src/ast.rs @@ -22,6 +22,7 @@ pub enum Expr { Literal(f64), Piecewise(Vec), Vector(Vec), + Indexer(Box, Box), } #[derive(Debug, Clone, PartialEq)] @@ -67,6 +68,14 @@ impl Identifier { prime_count: 0u32, } } + + pub fn get_name_without_lowered(&self) -> &str { + if let Some(underscore_pos) = self.pure_name.find('_') { + &self.pure_name[0..underscore_pos] + } else { + &self.pure_name + } + } } pub fn build_literal_ast(kalk_value: &crate::kalk_value::KalkValue) -> Expr { diff --git a/kalk/src/interpreter.rs b/kalk/src/interpreter.rs index c038cca..cf24d81 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::Indexer(var, index) => eval_indexer(context, var, index, unit), } } @@ -250,9 +251,10 @@ fn eval_var_expr( .symbol_table .get_var(identifier.full_name.as_ref() as &str) .cloned(); - match var_decl { - Some(Stmt::VarDecl(_, expr)) => eval_expr(context, &expr, unit), - _ => Err(CalcError::UndefinedVar(identifier.full_name.clone())), + if let Some(Stmt::VarDecl(_, expr)) = var_decl { + eval_expr(context, &expr, unit) + } else { + Err(CalcError::UndefinedVar(identifier.full_name.clone())) } } @@ -534,6 +536,25 @@ fn eval_vector(context: &mut Context, values: &Vec) -> Result Result { + let var_value = eval_expr(context, var, unit)?; + if let KalkValue::Vector(values) = var_value { + let index_value = eval_expr(context, index, unit)?.to_f64() as usize; + if let Some(value) = values.get(index_value) { + Ok(value.clone()) + } else { + Err(CalcError::ItemOfIndexDoesNotExist(index_value)) + } + } else { + Err(CalcError::CanOnlyIndexVectors) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/kalk/src/inverter.rs b/kalk/src/inverter.rs index 380b68a..2baf943 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::Indexer(_, _) => Err(CalcError::UnableToInvert(String::from("Inverter"))), } } @@ -390,6 +391,7 @@ 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::Indexer(_, _) => false, } } diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index f1fc168..04c916f 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -241,7 +241,7 @@ impl<'a> Lexer<'a> { } if base_str != "" { - base = crate::text_utils::subscript_to_digits(base_str.chars()) + base = crate::text_utils::subscript_to_normal(base_str.chars()) .parse::() .unwrap_or(10); } @@ -266,6 +266,7 @@ impl<'a> Lexer<'a> { let start = self.index; let mut end = start; let mut value = String::new(); + let mut subscript = String::new(); while is_valid_identifier(self.peek()) { let c = *self.peek().unwrap(); @@ -292,8 +293,13 @@ impl<'a> Lexer<'a> { break; } + if is_subscript(&c) { + subscript.push(c); + } else { + value.push(c); + } + end += 1; - value.push(c); self.advance(); } @@ -323,10 +329,22 @@ impl<'a> Lexer<'a> { "sech⁻¹" => String::from("asech"), "∛" => String::from("cbrt"), "°" => String::from("deg"), - _ => value, // things like log₂ are handled in the parser + _ => value, // things like log_2 are handled in the parser }; - build(kind, &value, (start, end)) + if subscript.len() > 0 { + build( + kind, + &format!( + "{}_{}", + value, + crate::text_utils::subscript_to_normal(subscript.chars()) + ), + (start, end), + ) + } else { + build(kind, &value, (start, end)) + } } fn peek(&mut self) -> Option<&char> { diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 86373de..c61313a 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -14,6 +14,7 @@ pub const DEFAULT_ANGLE_UNIT: &'static str = "rad"; /// Struct containing the current state of the parser. It stores user-defined functions and variables. #[wasm_bindgen] +#[derive(Clone)] pub struct Context { tokens: Vec, pos: usize, @@ -99,10 +100,12 @@ impl Default for Context { /// Error that occured during parsing or evaluation. #[derive(Debug, Clone, PartialEq)] pub enum CalcError { + CanOnlyIndexVectors, Expected(String), ExpectedDx, ExpectedIf, IncorrectAmountOfArguments(usize, String, usize), + ItemOfIndexDoesNotExist(usize), InvalidNumberLiteral(String), InvalidOperator, InvalidUnit, @@ -123,6 +126,7 @@ pub enum CalcError { impl ToString for CalcError { fn to_string(&self) -> String { match self { + CalcError::CanOnlyIndexVectors => format!("Indexing (getting an item with a specific index) is only possible on vectors."), CalcError::Expected(description) => format!("Expected: {}", description), CalcError::ExpectedDx => format!("Expected eg. dx, to specify for which variable the operation is being done to. Example with integration: ∫(0, 1, x dx) or ∫(0, 1, x, dx). You may need to put parenthesis around the expression before dx/dy/du/etc."), CalcError::ExpectedIf => format!("Expected 'if', with a condition after it."), @@ -130,6 +134,7 @@ impl ToString for CalcError { "Expected {} arguments for function {}, but got {}.", expected, func, got ), + CalcError::ItemOfIndexDoesNotExist(index) => format!("Item of index {} does not exist.", index), CalcError::InvalidNumberLiteral(x) => format!("Invalid number literal: '{}'.", x), CalcError::InvalidOperator => format!("Invalid operator."), CalcError::InvalidUnit => format!("Invalid unit."), @@ -607,7 +612,7 @@ fn parse_vector(context: &mut Context) -> Result { if peek(context).kind == TokenKind::EOF { return Err(CalcError::Expected(String::from( - "Closing group symbol, eg. ⌋", + "Closing group symbol, eg. )", ))); } @@ -626,7 +631,7 @@ fn parse_identifier(context: &mut Context) -> Result { let mut log_base = None; if identifier.full_name.starts_with("log") { if let Some(c) = identifier.full_name.chars().nth(3) { - if crate::text_utils::is_subscript(&c) { + if c == '_' { log_base = Some(Expr::Literal(get_base(&identifier.full_name)? as f64)); } } @@ -700,6 +705,28 @@ fn parse_identifier(context: &mut Context) -> Result { // Eg. x if parse_as_var_instead || context.symbol_table.contains_var(&identifier.pure_name) { Ok(build_var(context, &identifier.full_name)) + } else if context + .symbol_table + .contains_var(&identifier.get_name_without_lowered()) + { + let underscore_pos = identifier.pure_name.find('_').unwrap(); + let var_name = &identifier.pure_name[0..underscore_pos]; + let lowered = &identifier.pure_name[underscore_pos + 1..]; + + // Create a new identical context to avoid changing the current one + let mut lowered_context = context.clone(); + let mut parsed_stmts = parse(&mut lowered_context, &lowered)?; + let parsed_lowered = parsed_stmts + .pop() + .unwrap_or(Stmt::Expr(Box::new(Expr::Literal(0f64)))); + if let Stmt::Expr(lowered_expr) = parsed_lowered { + Ok(Expr::Indexer( + Box::new(Expr::Var(Identifier::from_full_name(&var_name))), + lowered_expr, + )) + } else { + Err(CalcError::UnableToParseExpression) + } } else if context.parsing_unit_decl { context.unit_decl_base_unit = Some(identifier.full_name); Ok(Expr::Var(Identifier::from_full_name(DECL_UNIT))) diff --git a/kalk/src/radix.rs b/kalk/src/radix.rs index 00201ad..1e3aa8c 100644 --- a/kalk/src/radix.rs +++ b/kalk/src/radix.rs @@ -65,7 +65,7 @@ pub fn to_radix_pretty(value: f64, radix: u8) -> String { format!( "{}{}", float_to_radix(value, radix), - crate::text_utils::digits_to_subscript(radix.to_string().chars()) + crate::text_utils::normal_to_subscript(radix.to_string().chars()) ) } } diff --git a/kalk/src/symbol_table.rs b/kalk/src/symbol_table.rs index 207cc1a..e711656 100644 --- a/kalk/src/symbol_table.rs +++ b/kalk/src/symbol_table.rs @@ -1,7 +1,7 @@ use crate::{ast::Expr, ast::Identifier, ast::Stmt, prelude}; use std::collections::HashMap; -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct SymbolTable { pub(crate) hashmap: HashMap, pub(crate) unit_types: HashMap, diff --git a/kalk/src/text_utils.rs b/kalk/src/text_utils.rs index 217e9f6..f91b8f1 100644 --- a/kalk/src/text_utils.rs +++ b/kalk/src/text_utils.rs @@ -9,22 +9,21 @@ pub fn is_superscript(c: &char) -> bool { pub fn is_subscript(c: &char) -> bool { match c { - '₀' | '₁' | '₂' | '₃' | '₄' | '₅' | '₆' | '₇' | '₈' | '₉' | '₊' | '₋' | '₌' | '₍' | '₎' => { - true - } + '₀' | '₁' | '₂' | '₃' | '₄' | '₅' | '₆' | '₇' | '₈' | '₉' | '₊' | '₋' | '₌' | '₍' | '₎' + | 'ₖ' | 'ₗ' | 'ₘ' | 'ₙ' | 'ₓ' => true, _ => false, } } pub fn parse_subscript(chars: impl Iterator) -> Option { - if let Ok(result) = subscript_to_digits(chars).parse::() { + if let Ok(result) = subscript_to_normal(chars).parse::() { Some(result) } else { None } } -pub fn subscript_to_digits(chars: impl Iterator) -> String { +pub fn subscript_to_normal(chars: impl Iterator) -> String { let mut regular = String::new(); for c in chars { regular.push(match c { @@ -43,6 +42,11 @@ pub fn subscript_to_digits(chars: impl Iterator) -> String { '₌' => '=', '₍' => '(', '₎' => ')', + 'ₖ' => 'k', + 'ₗ' => 'l', + 'ₘ' => 'm', + 'ₙ' => 'n', + 'ₓ' => 'x', _ => c, }); } @@ -50,7 +54,7 @@ pub fn subscript_to_digits(chars: impl Iterator) -> String { return regular.trim().to_string(); } -pub fn digits_to_subscript(chars: impl Iterator) -> String { +pub fn normal_to_subscript(chars: impl Iterator) -> String { let mut subscript = String::new(); for c in chars { subscript.push(match c { @@ -69,6 +73,11 @@ pub fn digits_to_subscript(chars: impl Iterator) -> String { '=' => '₌', '(' => '₍', ')' => '₎', + 'k' => 'ₖ', + 'l' => 'ₗ', + 'm' => 'ₘ', + 'n' => 'ₙ', + 'x' => 'ₓ', _ => c, }); }