From 95a5718e38dffc7d6e9b9feb22b6f1b93aa08cc0 Mon Sep 17 00:00:00 2001 From: PaddiM8 Date: Fri, 31 Dec 2021 19:32:04 +0100 Subject: [PATCH] Print result in another base when appropriate --- kalk/src/kalk_num/mod.rs | 56 ++++++++++++++++++++++++----------- kalk/src/kalk_num/regular.rs | 5 ++++ kalk/src/kalk_num/with_rug.rs | 5 ++++ kalk/src/lexer.rs | 44 +++++++++++++++++++-------- kalk/src/parser.rs | 16 ++++++++-- kalk/src/radix.rs | 47 +++++++++++++++++++++++++++-- 6 files changed, 138 insertions(+), 35 deletions(-) diff --git a/kalk/src/kalk_num/mod.rs b/kalk/src/kalk_num/mod.rs index ded8505..391101e 100644 --- a/kalk/src/kalk_num/mod.rs +++ b/kalk/src/kalk_num/mod.rs @@ -13,6 +13,7 @@ pub mod regular; pub use regular::*; use crate::ast::Expr; +use crate::radix; use lazy_static::lazy_static; use std::collections::HashMap; use wasm_bindgen::prelude::*; @@ -76,7 +77,7 @@ impl ScientificNotation { let mut digits_and_mul = if self.digits == "1" { String::new() } else { - format!("{}*", &self.digits) + format!("{}×", &self.digits) }; if self.digits.len() > 1 { @@ -107,8 +108,8 @@ impl KalkNum { complex_number_type: ComplexNumberType, ) -> ScientificNotation { let value_string = match complex_number_type { - ComplexNumberType::Real => self.to_string_real(), - ComplexNumberType::Imaginary => self.to_string_imaginary(false), + ComplexNumberType::Real => self.to_string_real(10), + ComplexNumberType::Imaginary => self.to_string_imaginary(10, false), } .trim_start_matches("-") .to_string(); @@ -170,12 +171,12 @@ impl KalkNum { } } - pub fn to_string_real(&self) -> String { - format_number(self.to_f64()) + pub fn to_string_real(&self, radix: u8) -> String { + radix::to_radix_pretty(self.to_f64(), radix) } - pub fn to_string_imaginary(&self, include_i: bool) -> String { - let value = format_number(self.imaginary_to_f64()); + pub fn to_string_imaginary(&self, radix: u8, include_i: bool) -> String { + let value = radix::to_radix_pretty(self.imaginary_to_f64(), radix); if include_i && value == "1" { String::from("i") } else if include_i && value == "-1" { @@ -187,7 +188,7 @@ impl KalkNum { } } - pub fn to_string_pretty(&self) -> String { + fn to_string_pretty_radix(&self, radix: u8) -> String { if let Some(boolean_value) = self.boolean_value { return boolean_value.to_string(); } @@ -205,12 +206,16 @@ impl KalkNum { let sci_notation_real = self.to_scientific_notation(ComplexNumberType::Real); let mut adjusted_num = self.clone(); let result_str = if (-6..8).contains(&sci_notation_real.exponent) || self.value == 0f64 { - self.to_string_real() + self.to_string_real(radix) } else if sci_notation_real.exponent <= -14 { adjusted_num.value = KalkNum::from(0f64).value; String::from("0") } else { - sci_notation_real.to_string().trim().to_string() + if radix == 10 { + sci_notation_real.to_string().trim().to_string() + } else { + return String::new(); + } }; let sci_notation_imaginary = self.to_scientific_notation(ComplexNumberType::Imaginary); @@ -218,12 +223,16 @@ impl KalkNum { || self.imaginary_value == 0f64 || self.imaginary_value == 1f64 { - self.to_string_imaginary(true) + self.to_string_imaginary(radix, true) } else if sci_notation_imaginary.exponent <= -14 { adjusted_num.imaginary_value = KalkNum::from(0f64).value; String::from("0") } else { - format!("{}", sci_notation_imaginary.to_string().trim()) + if radix == 10 { + format!("{}", sci_notation_imaginary.to_string().trim()) + } else { + return String::new(); + } }; let mut output = result_str; @@ -256,7 +265,7 @@ impl KalkNum { } if let Some(estimate) = adjusted_num.estimate() { - if estimate != output { + if estimate != output && radix == 10 { output.push_str(&format!(" ≈ {}", estimate)); } } @@ -264,6 +273,17 @@ impl KalkNum { output } + pub fn to_string_pretty(&self) -> String { + if let Some(other_radix) = self.other_radix { + let with_other_radix = self.to_string_pretty_radix(other_radix); + if with_other_radix != "" { + return format!("{}\n{}", self.to_string_pretty_radix(10), with_other_radix); + } + } + + self.to_string_pretty_radix(10) + } + pub fn is_too_big(&self) -> bool { self.value.is_infinite() } @@ -474,13 +494,13 @@ impl KalkNum { if let Some(value) = rounded_real { output.push_str(&value); } else if self.has_real() { - output.push_str(&self.to_string_real()); + output.push_str(&self.to_string_real(10)); } let imaginary_value = if let Some(value) = rounded_imaginary { Some(value) } else if self.has_imaginary() { - Some(self.to_string_imaginary(false)) + Some(self.to_string_imaginary(10, false)) } else { None }; @@ -520,7 +540,9 @@ impl KalkNum { fn estimate_one_value(&self, complex_number_type: ComplexNumberType) -> Option { let (value, value_string) = match complex_number_type { ComplexNumberType::Real => (&self.value, self.to_string()), - ComplexNumberType::Imaginary => (&self.imaginary_value, self.to_string_imaginary(true)), + ComplexNumberType::Imaginary => { + (&self.imaginary_value, self.to_string_imaginary(10, true)) + } }; let fract = value.clone().fract().abs(); @@ -679,7 +701,7 @@ impl KalkNum { } } -fn format_number(input: f64) -> String { +pub fn format_number(input: f64) -> String { let rounded = format!("{:.1$}", input, 10); if rounded.contains(".") { rounded diff --git a/kalk/src/kalk_num/regular.rs b/kalk/src/kalk_num/regular.rs index de8568a..526539b 100644 --- a/kalk/src/kalk_num/regular.rs +++ b/kalk/src/kalk_num/regular.rs @@ -8,6 +8,7 @@ pub struct KalkNum { pub(crate) unit: String, pub(crate) imaginary_value: f64, pub(crate) boolean_value: Option, + pub(crate) other_radix: Option, } #[wasm_bindgen] @@ -18,6 +19,7 @@ impl KalkNum { unit: unit.to_string(), imaginary_value: 0f64, boolean_value: None, + other_radix: None, } } @@ -31,6 +33,7 @@ impl KalkNum { imaginary_value }, boolean_value: None, + other_radix: None, } } @@ -40,6 +43,7 @@ impl KalkNum { unit: String::new(), imaginary_value: value, boolean_value: None, + other_radix: None, } } @@ -49,6 +53,7 @@ impl KalkNum { unit: String::new(), imaginary_value: 0f64, boolean_value: Some(value), + other_radix: None, } } diff --git a/kalk/src/kalk_num/with_rug.rs b/kalk/src/kalk_num/with_rug.rs index 070e677..72f0a89 100644 --- a/kalk/src/kalk_num/with_rug.rs +++ b/kalk/src/kalk_num/with_rug.rs @@ -12,6 +12,7 @@ pub struct KalkNum { pub(crate) unit: String, pub(crate) imaginary_value: Float, pub(crate) boolean_value: Option, + pub(crate) other_radix: Option, } impl KalkNum { @@ -22,6 +23,7 @@ impl KalkNum { unit: unit.to_string(), imaginary_value: Float::with_val(precision, 0), boolean_value: None, + other_radix: None, } } @@ -35,6 +37,7 @@ impl KalkNum { imaginary_value }, boolean_value: None, + other_radix: None, } } @@ -44,6 +47,7 @@ impl KalkNum { unit: String::new(), imaginary_value: value, boolean_value: None, + other_radix: None, } } @@ -53,6 +57,7 @@ impl KalkNum { unit: String::new(), imaginary_value: Float::with_val(63, 0), boolean_value: Some(value), + other_radix: None, } } diff --git a/kalk/src/lexer.rs b/kalk/src/lexer.rs index f31f7d5..4389e6b 100644 --- a/kalk/src/lexer.rs +++ b/kalk/src/lexer.rs @@ -56,18 +56,23 @@ pub struct Token { pub struct Lexer<'a> { chars: Peekable>, index: usize, + other_radix: Option, } impl<'a> Lexer<'a> { - pub fn lex(source: &str) -> Vec { - let mut lexer = Lexer { + pub fn new(source: &'a str) -> Self { + Lexer { chars: source.chars().peekable(), index: 0, - }; + other_radix: None, + } + } + + pub fn lex(&mut self) -> Vec { let mut tokens = Vec::new(); loop { - let next = lexer.next(); + let next = self.next(); if let TokenKind::EOF = next.kind { tokens.push(next); @@ -80,6 +85,10 @@ impl<'a> Lexer<'a> { tokens } + pub fn get_other_radix(&self) -> Option { + self.other_radix + } + fn next(&mut self) -> Token { let eof = build(TokenKind::EOF, "", (self.index, self.index)); let mut c = if let Some(c) = self.peek() { @@ -179,7 +188,7 @@ impl<'a> Lexer<'a> { let mut end = start; let mut value = String::new(); let mut leading_zero = self.peek().unwrap_or(&'\0') == &'0'; - let mut base = 10u32; + let mut base = 10u8; loop { let c = if let Some(c) = self.peek() { @@ -210,7 +219,7 @@ impl<'a> Lexer<'a> { } } - if !c.is_digit(base) && c != '.' && c != '_' && !c.is_whitespace() + if !c.is_digit(base as u32) && c != '.' && c != '_' && !c.is_whitespace() || c == '\n' || c == '\r' { @@ -231,12 +240,21 @@ impl<'a> Lexer<'a> { if base_str != "" { base = crate::text_utils::subscript_to_digits(base_str.chars()) - .parse::() + .parse::() .unwrap_or(10); } if base != 10 { value.push_str(&format!("_{}", base)); + if let Some(other_radix) = self.other_radix { + // Don't bother keeping track of radixes + // if several different ones are used + if other_radix != base { + self.other_radix = None; + } + } else { + self.other_radix = Some(base); + } } build(TokenKind::Literal, &value, (start, end)) @@ -358,7 +376,7 @@ mod tests { #[test] #[wasm_bindgen_test] fn test_token_kinds() { - let tokens = Lexer::lex("+-*/%^()|=!,"); + let tokens = Lexer::new("+-*/%^()|=!,").lex(); let expected = vec![ TokenKind::Plus, TokenKind::Minus, @@ -381,7 +399,7 @@ mod tests { #[test] #[wasm_bindgen_test] fn test_brackets() { - let tokens = Lexer::lex("[1 < 2]"); + let tokens = Lexer::new("[1 < 2]").lex(); let expected = vec![ TokenKind::OpenBracket, TokenKind::Literal, @@ -401,7 +419,7 @@ mod tests { let test_cases = vec![" ", " ", "test ", " test "]; for input in test_cases { - let tokens = Lexer::lex(input); + let tokens = Lexer::new(input).lex(); if regex::Regex::new(r"^\s*$").unwrap().is_match(input) { let expected = vec![TokenKind::EOF]; @@ -417,7 +435,7 @@ mod tests { #[test_case("24")] #[test_case("56.4")] fn test_number_literal(input: &str) { - let tokens = Lexer::lex(input); + let tokens = Lexer::new(input).lex(); let expected = vec![TokenKind::Literal, TokenKind::EOF]; assert_eq!(&tokens[0].value, input); @@ -427,7 +445,7 @@ mod tests { #[test_case("x")] #[test_case("xy")] fn test_identifier(input: &str) { - let tokens = Lexer::lex(input); + let tokens = Lexer::new(input).lex(); let expected = vec![TokenKind::Identifier, TokenKind::EOF]; assert_eq!(&tokens[0].value, input); @@ -436,7 +454,7 @@ mod tests { #[test] fn test_function_call() { - let tokens = Lexer::lex("f(x)"); + let tokens = Lexer::new("f(x)").lex(); let expected = vec![ TokenKind::Identifier, TokenKind::OpenParenthesis, diff --git a/kalk/src/parser.rs b/kalk/src/parser.rs index 4cfe63f..9dd34d9 100644 --- a/kalk/src/parser.rs +++ b/kalk/src/parser.rs @@ -33,6 +33,7 @@ pub struct Context { is_in_integral: bool, current_function: Option, current_function_parameters: Option>, + other_radix: Option, } #[wasm_bindgen] @@ -53,6 +54,7 @@ impl Context { is_in_integral: false, current_function: None, current_function_parameters: None, + other_radix: None, }; parse(&mut context, crate::prelude::INIT).unwrap(); @@ -176,17 +178,25 @@ pub fn eval( None }, ); - interpreter.interpret(statements) + let result = interpreter.interpret(statements); + if let Ok(Some(mut num)) = result { + num.other_radix = context.other_radix; + Ok(Some(num)) + } else { + result + } } /// Parse expressions/declarations and return a syntax tree. /// /// `None` will be returned if the last statement is a declaration. pub fn parse(context: &mut Context, input: &str) -> Result, CalcError> { - context.tokens = Lexer::lex(input); + let mut lexer = Lexer::new(input); + context.tokens = lexer.lex(); context.pos = 0; context.parsing_unit_decl = false; context.unit_decl_base_unit = None; + context.other_radix = lexer.get_other_radix(); let mut statements: Vec = Vec::new(); while !is_at_end(context) { @@ -856,7 +866,7 @@ fn is_at_end(context: &Context) -> bool { 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(CalcError::InvalidNumberLiteral(value.into())) diff --git a/kalk/src/radix.rs b/kalk/src/radix.rs index ef650a8..c099c65 100644 --- a/kalk/src/radix.rs +++ b/kalk/src/radix.rs @@ -1,4 +1,4 @@ -pub fn parse_float_radix(value: String, radix: u8) -> Option { +pub fn parse_float_radix(value: &str, radix: u8) -> Option { if radix == 10 { return if let Ok(result) = value.parse::() { Some(result) @@ -9,7 +9,7 @@ pub fn parse_float_radix(value: String, radix: u8) -> Option { let mut sum = 0f64; let length = value.find('_').unwrap_or(value.len()); - let mut i = (value.find('.').unwrap_or(length) - 1) as i32; + let mut i = (value.find('.').unwrap_or(length) as i32) - 1; for c in value.chars() { if c == '_' { break; @@ -26,3 +26,46 @@ pub fn parse_float_radix(value: String, radix: u8) -> Option { return Some(sum); } + +const DIGITS: &'static str = "0123456789abcdefghijklmnopqrstuvwxyz"; +pub fn int_to_radix(value: i64, radix: u8) -> String { + let mut num = value.abs(); + let mut result_str = String::new(); + while num > 0 { + let digit_index = (num % radix as i64) as usize; + result_str.insert(0, DIGITS.as_bytes()[digit_index] as char); + num /= radix as i64; + } + + if result_str == "" { + return String::from("0"); + } + + let sign = if value.is_positive() { "" } else { "-" }; + format!("{}{}", sign, result_str) +} + +pub fn float_to_radix(value: f64, radix: u8) -> String { + let mut result = int_to_radix(value.floor() as i64, radix); + let fract = value.fract(); + if fract != 0f64 { + result.push('.'); + let precision = 10; + let fract_digits = (fract * (radix as i64).pow(precision) as f64) as i64; + result.push_str(&int_to_radix(fract_digits, radix).trim_end_matches('0')) + } + + result +} + +pub fn to_radix_pretty(value: f64, radix: u8) -> String { + if radix == 10 { + crate::kalk_num::format_number(value) + } else { + format!( + "{}{}", + float_to_radix(value, radix), + crate::text_utils::digits_to_subscript(radix.to_string().chars()) + ) + } +}