diff --git a/Cargo.lock b/Cargo.lock index 2ddd8a4..2cbc20d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "daisycalc" -version = "1.0.0" +version = "1.0.1" dependencies = [ "cfg-if", "rug", diff --git a/Cargo.toml b/Cargo.toml index 94e21f2..d611062 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "daisycalc" -version = "1.0.0" +version = "1.0.1" edition = "2021" build = "buildscript/main.rs" license = "GPL-3.0-only" diff --git a/README.md b/README.md index ee42a5a..fe53747 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ A high-precision scientific calculator with support for units, derivatives, and Many features are missing, this is still under development. # 📦 Installation + - **Cargo:** `cargo install daisycalc` - **Arch:** `yay -S daisy` - **Debian:** coming soon @@ -14,7 +15,7 @@ Binary will be in `target/release/daisy` # 📹 Screenshot -![](https://betalupi.com/static/git/daisy.png) +![Screenshot](https://github.com/rm-dr/daisy/assets/96270320/cc71887a-0fde-46b2-a13b-96b05098b158) # 🛠️ Features - Open-source @@ -65,4 +66,4 @@ Daisy instead provides four functions (`fromCelsius`, `toCelsius`, `fromFahrenhe ## Multiplication Order -Implicit multiplication has a higher priority than division. `pi/2 radians` will parse as `pi/(2 radians)`. Type `(pi/2) radians` or `pi/2 * radians` to get 90 degrees. \ No newline at end of file +Implicit multiplication has a higher priority than division. `pi/2 radians` will parse as `pi/(2 radians)`. Type `(pi/2) radians` or `pi/2 * radians` to get 90 degrees. diff --git a/TODO.md b/TODO.md index ca817cb..470cf3d 100644 --- a/TODO.md +++ b/TODO.md @@ -3,7 +3,8 @@ - run cargo test - commit - git tag -a v1.0.0 -m "Version 1.0.0" - - push + - git push + - git push origin v1.0.0 - cargo publish - Update packages @@ -26,11 +27,6 @@ - Optional history file - daisyrc file - Compile to WASM, publish a webapp - - Options: - - disable replacement - - disable special characters - - 1/ as -1 power toggle - - powers as superscripts toggle - evaluate straight from command line - Auto-push to crates.io - Package for debian diff --git a/src/command/mod.rs b/src/command/mod.rs index 86219e2..68f16bc 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -44,8 +44,8 @@ fn greeter() -> FormattedText { #[inline(always)] pub fn do_command( + context: &mut Context, s: &String, - context: &mut Context ) -> FormattedText { let args: Vec<&str> = s.split(" ").collect(); let first = args[0]; @@ -170,7 +170,7 @@ pub fn do_command( t.push(&format!( " {key}{padding} = [c]{v}[n]\n", - v = value.to_string(), + v = value.display(context), )); } } @@ -184,7 +184,7 @@ pub fn do_command( t.push(&format!( " {s}{padding} = [c]{v}[n]\n", - v = exp.to_string(), + v = exp.display(context), )); } } @@ -230,7 +230,7 @@ pub fn do_command( } let v = args[1].to_string(); - let v = substitute(&v, context); + let v = substitute(context, &v); let r = context.delete(&v); return match r { diff --git a/src/context.rs b/src/context.rs index 4bcbecf..b1fef92 100644 --- a/src/context.rs +++ b/src/context.rs @@ -3,7 +3,68 @@ use crate::quantity::freeunit_from_string; use std::collections::HashMap; #[derive(Debug)] +#[derive(Clone)] +pub struct Config { + + // How to color terminal text. + // 0: No colors + // 1: ANSI-compatible, 8 colors + // 2: Full 256 color and special styles + pub term_color_type: u8, + + // Should we accept input and print in unicode? + //pub enable_unicode: bool, + + // Should we replace certain strings (like "pi") + // with prettier unicode alternatives? + // + // Automatically disabled if enable_unicode is off. + //pub enable_substituion: bool, + + // Should we print simple powers + // as unicode superscript chars? + // + // Automatically disables if enable_unicode is off. + pub enable_super_powers: bool, + + // Should we write "one-over" fractions + // as -1 powers? + // + // Automatically disabled if enable_super_powers is off. + pub enable_one_over_power: bool, +} + +impl Config { + pub fn new() -> Config { + Config{ + term_color_type: 2, + //enable_substituion: true, + //enable_unicode: true, + enable_super_powers: true, + enable_one_over_power: true + } + } + + pub fn check(&mut self) { + //if !self.enable_unicode { + // self.enable_substituion = false; + // self.enable_super_powers = false; + //} + + if !self.enable_super_powers { + self.enable_one_over_power = false + } + } +} + + + + +#[derive(Debug)] +#[derive(Clone)] pub struct Context { + pub config: Config, + history: Vec, variables: HashMap, functions: HashMap, Expression)>, @@ -16,6 +77,7 @@ pub struct Context { impl Context { pub fn new() -> Context { Context{ + config: Config::new(), history: Vec::new(), variables: HashMap::new(), functions: HashMap::new(), @@ -84,10 +146,10 @@ impl Context { } pub fn is_varible(&self, s: &str) -> bool { - return self.valid_varible(s) && ( - self.variables.contains_key(s) || - self.shadow.contains_key(s) - ); + return { + self.valid_varible(s) && + (self.variables.contains_key(s) || self.shadow.contains_key(s)) + }; } pub fn get_variables(&self) -> &HashMap { diff --git a/src/entrypoint/unix/promptbuffer.rs b/src/entrypoint/unix/promptbuffer.rs index 5a3ed83..2b711ab 100644 --- a/src/entrypoint/unix/promptbuffer.rs +++ b/src/entrypoint/unix/promptbuffer.rs @@ -37,9 +37,9 @@ impl PromptBuffer { } // Same as write_primpt, but pretends there is no cursor - pub fn write_prompt_nocursor(&mut self, stdout: &mut RawTerminal, context: &Context) -> Result<(), std::io::Error> { + pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { // Draw prettyprinted expression - let (_, s) = substitute_cursor(&self.get_contents(), self.buffer.chars().count(), context); + let (_, s) = substitute_cursor(context, &self.get_contents(), self.buffer.chars().count()); write!( stdout, "\r{}{}==>{}{} {}", @@ -64,12 +64,12 @@ impl PromptBuffer { return Ok(()); } - pub fn write_prompt(&mut self, stdout: &mut RawTerminal, context: &Context) -> Result<(), std::io::Error> { + pub fn write_prompt(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { let l = self.buffer.chars().count(); let i = if l == 0 {0} else {l - self.cursor}; // Draw prettyprinted expression - let (display_cursor, s) = substitute_cursor(&self.get_contents(), i, context); + let (display_cursor, s) = substitute_cursor(context, &self.get_contents(), i); write!( stdout, "\r{}{}==>{}{} {}", diff --git a/src/entrypoint/unix/unix.rs b/src/entrypoint/unix/unix.rs index 2df098e..b0a6347 100644 --- a/src/entrypoint/unix/unix.rs +++ b/src/entrypoint/unix/unix.rs @@ -1,4 +1,3 @@ -use std::io::Write; use std::io::stdout; use std::io::stdin; use std::env; @@ -6,35 +5,63 @@ use std::env; use termion::{ event::Key, input::TermRead, - raw::IntoRawMode + raw::IntoRawMode, + color::DetectColors }; use super::promptbuffer::PromptBuffer; use crate::command; use crate::context::Context; - +use crate::FormattedText; #[inline(always)] pub fn main() -> Result<(), std::io::Error> { let mut stdout = stdout().into_raw_mode().unwrap(); let mut pb: PromptBuffer = PromptBuffer::new(64); - let mut context: Context = Context::new(); + let mut context = Context::new(); + + // Set color compatibilty + let term_colors = stdout.available_colors().unwrap_or(0); + if term_colors >= 256 { + context.config.term_color_type = 2 + } else if term_colors >= 8 { + context.config.term_color_type = 1 + } else { + context.config.term_color_type = 0 + } + + context.config.check(); + // Handle command-line arguments let args: Vec = env::args().collect(); if args.iter().any(|s| s == "--help") { - let t = command::do_command(&String::from("help"), &mut context); - t.write(&mut stdout)?; + let t = command::do_command(&mut context, &String::from("help")); + t.write(&context, &mut stdout)?; return Ok(()); } else if args.iter().any(|s| s == "--version") { - write!(stdout, "Daisy v{}\r\n", env!("CARGO_PKG_VERSION"))?; + let t = FormattedText::new(format!( + "Daisy v{}\n", env!("CARGO_PKG_VERSION") + )); + t.write(&context, &mut stdout)?; + return Ok(()); + } else if args.iter().any(|s| s == "--debug") { + let t = FormattedText::new(format!( + concat!( + "Daisy v{}\n", + "Your terminal supports {} colors.\n" + ), + env!("CARGO_PKG_VERSION"), + term_colors + )); + t.write(&context, &mut stdout)?; return Ok(()); } 'outer: loop { - pb.write_prompt(&mut stdout, &context)?; + pb.write_prompt(&mut context, &mut stdout)?; let stdin = stdin(); for c in stdin.keys() { @@ -43,19 +70,19 @@ pub fn main() -> Result<(), std::io::Error> { '\n' => { // Print again without cursor, in case we pressed enter // while inside a substitution - pb.write_prompt_nocursor(&mut stdout, &context)?; + pb.write_prompt_nocursor(&mut context, &mut stdout)?; let in_str = pb.enter(); - write!(stdout, "\r\n")?; + FormattedText::newline(&mut stdout)?; if in_str == "" { break; } if in_str.trim() == "quit" { break 'outer; } else { - let r = crate::do_string(&in_str, &mut context); + let r = crate::do_string(&mut context, &in_str); match r { Ok(t) | Err(t) => { - t.write(&mut stdout).unwrap(); + t.write(&context, &mut stdout).unwrap(); } } } @@ -79,10 +106,10 @@ pub fn main() -> Result<(), std::io::Error> { }; }; - pb.write_prompt(&mut stdout, &context)?; + pb.write_prompt(&mut context, &mut stdout)?; } } - write!(stdout, "\r\n")?; + FormattedText::newline(&mut stdout)?; return Ok(()); } \ No newline at end of file diff --git a/src/evaluate/evaluate.rs b/src/evaluate/evaluate.rs index 7bd37d2..4c46d95 100644 --- a/src/evaluate/evaluate.rs +++ b/src/evaluate/evaluate.rs @@ -9,8 +9,8 @@ use super::function::eval_function; pub fn evaluate( - t: &Expression, - context: &mut Context + context: &mut Context, + t: &Expression ) -> Result< Expression, (LineLocation, DaisyError) @@ -51,7 +51,7 @@ pub fn evaluate( let new = match g { Expression::Quantity(_, _) => None, Expression::Tuple(_, _) => None, - Expression::Constant(_, c) => { Some(evaluate(&c.value(), context).unwrap()) }, + Expression::Constant(_, c) => { Some(evaluate(context, &c.value()).unwrap()) }, Expression::Variable(l, s) => { // Don't move up, re-evaluate // This makes variables containing floating variables work properly @@ -64,7 +64,7 @@ pub fn evaluate( context.get_variable(&s) }, Expression::Operator(_, Operator::Function(_), _) => { Some(eval_function(g)?) }, - Expression::Operator(_, _, _) => { eval_operator(g, context)? }, + Expression::Operator(_, _, _) => { eval_operator(context, g)? }, }; if let Some(mut new) = new { diff --git a/src/evaluate/operator.rs b/src/evaluate/operator.rs index 6374dab..bc77de4 100644 --- a/src/evaluate/operator.rs +++ b/src/evaluate/operator.rs @@ -7,7 +7,7 @@ use crate::errors::DaisyError; use super::evaluate; -pub fn eval_operator(g: &Expression, context: &mut Context) -> Result, (LineLocation, DaisyError)> { +pub fn eval_operator(context: &mut Context, g: &Expression) -> Result, (LineLocation, DaisyError)> { let Expression::Operator(op_loc, op, args) = g else {panic!()}; @@ -52,7 +52,7 @@ pub fn eval_operator(g: &Expression, context: &mut Context) -> Result Result Result Result Option { + Some(match c { + 'n'|'i'|'t'|'a'| + 'e'|'c'|'s'|'r' + => { "".to_string() }, + _ => { return None } + }) +} + +fn format_map_ansi(c: char) -> Option { + Some(match c { + 'n' => { // Normal text + format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) + }, + 'i' => { // Normal italic text + format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) + }, + 't' => { // Title text + format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) + }, + 'a' => { // Colored text + format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) + }, + 'e' => { // Error titles + format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) + }, + 'c' => { // Console text + format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) + }, + 's' => { // Repeat prompt (how => is styled) + format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) + }, + 'r' => { // Result prompt (how = is styled) + format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset)) + }, + + _ => { return None } + }) +} + + +fn format_map_full(c: char) -> Option { + Some(match c { + 'n' => { // Normal text + format!("{}{}", color::Fg(color::Reset), style::Reset) + }, + 'i' => { // Normal italic text + format!("{}{}", color::Fg(color::Reset), style::Italic) + }, + 't' => { // Title text + format!("{}{}", color::Fg(color::Magenta), style::Bold) + }, + 'a' => { // Colored text + format!("{}{}", color::Fg(color::Magenta), style::Reset) + }, + 'e' => { // Error titles + format!("{}{}", color::Fg(color::Red), style::Bold) + }, + 'c' => { // Console text + format!("{}{}", color::Fg(color::LightBlack), style::Italic) + }, + 's' => { // Repeat prompt (how => is styled) + format!("{}{}", color::Fg(color::Magenta), style::Bold) + }, + 'r' => { // Result prompt (how = is styled) + format!("{}{}", color::Fg(color::Green), style::Bold) + }, + + _ => { return None } + }) +} + + +impl FormattedText { + pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> { + write!( + stdout, + "\r\n", + )?; + return Ok(()); + } +} + + +impl FormattedText { pub fn new(s: String) -> FormattedText { return FormattedText { text: s @@ -31,7 +116,7 @@ impl FormattedText { } - pub fn write(&self, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { + pub fn write(&self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { if self.text == "[clear]" { write!( @@ -58,30 +143,22 @@ impl FormattedText { let b = chars.next().unwrap(); match (a, b) { - ('n', ']') => { // Normal text - s.push_str(&format!("{}{}", color::Fg(color::Reset), style::Reset)); - }, - ('i', ']') => { // Normal italic text - s.push_str(&format!("{}{}", color::Fg(color::Reset), style::Italic)); - }, - ('t', ']') => { // Title text - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Bold)); - }, - ('a', ']') => { // Colored text - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Reset)); - }, - ('e', ']') => { // Error titles - s.push_str(&format!("{}{}", color::Fg(color::Red), style::Bold)); - }, - ('c', ']') => { // Console text - s.push_str(&format!("{}{}", color::Fg(color::LightBlack), style::Italic)); - }, + (c, ']') => { // Normal text - ('s', ']') => { // Repeat prompt (how => is styled) - s.push_str(&format!("{}{}", color::Fg(color::Magenta), style::Bold)); - }, - ('r', ']') => { // Result prompt (how = is styled) - s.push_str(&format!("{}{}", color::Fg(color::Green), style::Bold)); + let q = match context.config.term_color_type { + 0 => format_map_none(c), + 1 => format_map_ansi(c), + 2 => format_map_full(c), + _ => unreachable!("Invalid term_color_type") + }; + + if q.is_some() { + s.push_str(&q.unwrap()); + } else { + s.push('['); + s.push(a); + s.push(b); + } }, _ => { diff --git a/src/main.rs b/src/main.rs index fa97ee6..0841f07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,21 +26,21 @@ fn main() -> Result<(), std::io::Error> { #[inline(always)] pub fn do_string( - s: &String, - mut context: &mut Context + context: &mut Context, + s: &String ) -> Result { let r: (LineLocation, DaisyError); if command::is_command(s) { - return Ok(command::do_command(s, &mut context)); + return Ok(command::do_command(context, s)); } else if s.contains("=") { - let x = do_assignment(s, &mut context); + let x = do_assignment(context, s); match x { Ok(t) => { return Ok(t) }, Err(t) => { r = t } }; } else { - let x = do_expression(s, &mut context); + let x = do_expression(context, s); match x { Ok((t, e)) => { context.push_hist(e); return Ok(t) }, Err(t) => { r = t } @@ -73,25 +73,25 @@ pub fn do_string( // Returns a FormattedText with output that should be printed. #[inline(always)] fn do_expression( - s: &String, - context: &mut Context + context: &mut Context, + s: &String ) -> Result<(FormattedText, parser::Expression), (LineLocation, DaisyError)> { let mut output = FormattedText::new("".to_string()); - let g = parser::parse(&s, context)?; - let g_evaluated = evaluate::evaluate(&g, context)?; + let g = parser::parse(context, &s)?; + let g_evaluated = evaluate::evaluate(context, &g)?; // Display parsed string output.push(&format!( " [s]=>[n] {}\n\n", - g.to_string() + g.display(context) )); // Display result output.push(&format!( " [r]=[n] {}\n\n", - g_evaluated.to_string_outer(), + g_evaluated.display_outer(context), )); return Ok((output, g_evaluated)); @@ -102,8 +102,8 @@ fn do_expression( // Returns a FormattedText with output that should be printed. #[inline(always)] fn do_assignment( - s: &String, - context: &mut Context + context: &mut Context, + s: &String ) -> Result { let mut output = FormattedText::new("".to_string()); @@ -135,8 +135,8 @@ fn do_assignment( .unwrap_or_else(|| parts[0].len()); - let left = substitute(&parts[0].trim().to_string(), &context); - let right = substitute(&parts[1].trim().to_string(), &context); + let left = substitute(context, &parts[0].trim().to_string()); + let right = substitute(context, &parts[1].trim().to_string()); let is_function = left.contains("("); // The order of methods below is a bit odd. @@ -212,7 +212,7 @@ fn do_assignment( } // Parse right hand side - let g = parser::parse(&right, context); + let g = parser::parse(context, &right); let Ok(g) = g else { let Err((l, e)) = g else { unreachable!() }; return Err(( @@ -224,12 +224,12 @@ fn do_assignment( // Display parsed string output.push(&format!( " [s]=>[n] {left} = {}\n\n", - g.to_string() + g.display(context) )); // Evaluate expression with shadow variables for a in &args { context.add_shadow(a.to_string(), None);} - let g_evaluated = evaluate::evaluate(&g, context); + let g_evaluated = evaluate::evaluate(context, &g); context.clear_shadow(); let Ok(_g_evaluated) = g_evaluated else { let Err((l, e)) = g_evaluated else { unreachable!() }; @@ -254,7 +254,7 @@ fn do_assignment( } // Parse right hand side - let g = parser::parse(&right, context); + let g = parser::parse(context, &right); let Ok(g) = g else { let Err((l, e)) = g else { unreachable!() }; return Err(( @@ -266,11 +266,11 @@ fn do_assignment( // Display parsed string output.push(&format!( " [t]=>[n] {left} = {}\n\n", - g.to_string() + g.display(context) )); // Evaluate expression - let g_evaluated = evaluate::evaluate(&g, context); + let g_evaluated = evaluate::evaluate(context, &g); let Ok(g_evaluated) = g_evaluated else { let Err((l, e)) = g_evaluated else { unreachable!() }; return Err(( diff --git a/src/parser/expression/expression.rs b/src/parser/expression/expression.rs index 74f9a61..564a162 100644 --- a/src/parser/expression/expression.rs +++ b/src/parser/expression/expression.rs @@ -1,5 +1,6 @@ use std::collections::VecDeque; use crate::quantity::Quantity; +use crate::context::Context; use super::Operator; use super::Constant; @@ -17,17 +18,17 @@ pub enum Expression { Tuple(LineLocation, VecDeque), } -impl ToString for Expression { - fn to_string(&self) -> String { +impl Expression { + pub fn display(&self, context: &Context) -> String { match self { - Expression::Quantity(_, v) => v.to_string(), + Expression::Quantity(_, v) => v.display(context), Expression::Constant(_, c) => c.to_string(), Expression::Variable(_, s) => s.clone(), - Expression::Operator(_, o,a) => o.print(a), + Expression::Operator(_, o,a) => o.display(context, a), Expression::Tuple(_, v) => { format!("({})", v.iter() - .map(|x| x.to_string()) + .map(|x| x.display(context)) .collect::>() .join(", ") ) @@ -39,16 +40,16 @@ impl ToString for Expression { impl Expression { // This is called only when this is the outermost Expression. // This sometimes leads to different--usually more verbose--behavior. - pub fn to_string_outer(&self) -> String { + pub fn display_outer(&self, context: &Context) -> String { match self { - Expression::Quantity(_, v) => v.to_string_outer(), + Expression::Quantity(_, v) => v.display_outer(context), Expression::Constant(_, c) => c.to_string(), Expression::Variable(_, s) => s.clone(), - Expression::Operator(_, o,a) => o.print(a), + Expression::Operator(_, o,a) => o.display(context, a), Expression::Tuple(_, v) => { format!("({})", v.iter() - .map(|x| x.to_string()) + .map(|x| x.display(context)) .collect::>() .join(", ") ) diff --git a/src/parser/expression/operator.rs b/src/parser/expression/operator.rs index 707dbca..0ae5b1a 100644 --- a/src/parser/expression/operator.rs +++ b/src/parser/expression/operator.rs @@ -62,7 +62,7 @@ impl Operator { } #[inline(always)] - pub fn from_string(s: &str, context: &Context) -> Option { + pub fn from_string(context: &Context, s: &str) -> Option { let f = Function::from_string(s); if let Some(f) = f { @@ -128,8 +128,8 @@ impl Operator { } #[inline(always)] - fn add_parens_to_arg(&self, arg: &Expression) -> String { - let mut astr: String = arg.to_string(); + fn add_parens_to_arg(&self, context: &Context, arg: &Expression) -> String { + let mut astr: String = arg.display(context); if let Expression::Operator(_, o,_) = arg { if o.print_map() < self.print_map() { astr = format!("({})", astr); @@ -139,8 +139,8 @@ impl Operator { } #[inline(always)] - fn add_parens_to_arg_strict(&self, arg: &Expression) -> String { - let mut astr: String = arg.to_string(); + fn add_parens_to_arg_strict(&self, context: &Context, arg: &Expression) -> String { + let mut astr: String = arg.display(context); if let Expression::Operator(_, o,_) = arg { if o.print_map() <= self.print_map() { astr = format!("({})", astr); @@ -150,56 +150,56 @@ impl Operator { } - pub fn print(&self, args: &VecDeque) -> String { + pub fn display(&self, context: &Context, args: &VecDeque) -> String { match self { Operator::Negative => { - return format!("-{}", self.add_parens_to_arg(&args[0])); + return format!("-{}", self.add_parens_to_arg(context, &args[0])); }, Operator::Sqrt => { return format!( "√{}", - self.add_parens_to_arg(&args[0]), + self.add_parens_to_arg(context, &args[0]), ); }, Operator::ModuloLong => { return format!( "{} mod {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::DivideLong => { return format!( "{} per {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::UnitConvert => { return format!( "{} to {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::Modulo => { return format!( "{} % {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, Operator::Subtract => { return format!( "{} - {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, @@ -207,10 +207,14 @@ impl Operator { let q = &args[1]; - if q.is_unitless_integer() && !q.to_string().contains("e") { + if { + context.config.enable_super_powers && + q.is_unitless_integer() && + !q.display(context).contains("e") + } { // Write integer powers as a superscript let mut b = String::new(); - for c in q.to_string().chars() { + for c in q.display(context).chars() { b.push(match c { '-' => '⁻', '0' => '⁰', @@ -229,27 +233,27 @@ impl Operator { return format!( "{}{}", - self.add_parens_to_arg_strict(&args[0]), + self.add_parens_to_arg_strict(context, &args[0]), b ); } else { return format!( "{}^{}", - self.add_parens_to_arg_strict(&args[0]), - self.add_parens_to_arg_strict(&args[1]) + self.add_parens_to_arg_strict(context, &args[0]), + self.add_parens_to_arg_strict(context, &args[1]) ); } }, Operator::Factorial => { - return format!("{}!", self.add_parens_to_arg(&args[0])); + return format!("{}!", self.add_parens_to_arg(context, &args[0])); }, Operator::Add => { return format!( "{} + {}", - self.add_parens_to_arg(&args[0]), - self.add_parens_to_arg(&args[1]) + self.add_parens_to_arg(context, &args[0]), + self.add_parens_to_arg(context, &args[1]) ); }, @@ -279,26 +283,26 @@ impl Operator { if let Expression::Quantity(_, u) = b { if u.unit.no_space() { return format!("{}{}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } else { return format!("{} {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } } else { return format!("{}{}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); }; } else { return format!("{} × {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); } }, @@ -309,25 +313,25 @@ impl Operator { if let Expression::Quantity(_, q) = a { - if q.is_one() { + if q.is_one() && context.config.enable_one_over_power { return format!("{}⁻¹", - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, b) ); } } return format!("{} ÷ {}", - self.add_parens_to_arg_strict(a), - self.add_parens_to_arg_strict(b) + self.add_parens_to_arg_strict(context, a), + self.add_parens_to_arg_strict(context, b) ); }, Operator::Function(s) => { - return format!("{}({})", s.to_string(), args[0].to_string()); + return format!("{}({})", s.to_string(), args[0].display(context)); }, Operator::UserFunction(s) => { - return format!("{}({})", s.to_string(), args[0].to_string()); + return format!("{}({})", s, args[0].display(context)); } }; } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 6d2f792..32c608d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -18,30 +18,30 @@ use crate::context::Context; use crate::errors::DaisyError; pub fn parse( - s: &String, context: &Context + context: &Context, s: &String ) -> Result { - let expressions = stage::tokenize(s, context); + let expressions = stage::tokenize(context, s); let (_, expressions) = stage::find_subs(expressions); - let g = stage::groupify(expressions, context)?; - let g = stage::treeify(g, context)?; + let g = stage::groupify(context, expressions)?; + let g = stage::treeify(context, g)?; return Ok(g); } pub fn parse_no_context(s: &String) -> Result { - parse(s, &Context::new()) + parse(&Context::new(), s) } -pub fn substitute(s: &String, context: &Context) -> String { - let (_, s) = substitute_cursor(s, s.chars().count(), context); +pub fn substitute(context: &Context, s: &String) -> String { + let (_, s) = substitute_cursor(context, s, s.chars().count()); return s; } pub fn substitute_cursor( + context: &Context, s: &String, // The string to substitute - c: usize, // Location of the cursor right now - context: &Context + c: usize // Location of the cursor right now ) -> ( usize, // Location of cursor in substituted string String // String with substitutions @@ -50,7 +50,7 @@ pub fn substitute_cursor( let mut new_s = s.clone(); let l = s.chars().count(); - let expressions = stage::tokenize(s, context); + let expressions = stage::tokenize(context, s); let (mut subs, _) = stage::find_subs(expressions); let mut new_c = l - c; diff --git a/src/parser/stage/groupify.rs b/src/parser/stage/groupify.rs index d0f9b51..1297795 100644 --- a/src/parser/stage/groupify.rs +++ b/src/parser/stage/groupify.rs @@ -11,8 +11,8 @@ use crate::context::Context; fn lookback_signs( - g: &mut VecDeque, - context: &Context + context: &Context, + g: &mut VecDeque ) -> Result<(), (LineLocation, DaisyError)> { // Convert `-` operators to `neg` operators @@ -44,7 +44,7 @@ fn lookback_signs( (Token::Operator(_, sa), Token::Operator(l,sb)) => { if { - let o = Operator::from_string(sa, context); + let o = Operator::from_string(context, sa); o.is_some() && ( @@ -101,11 +101,11 @@ fn lookback_signs( // Inserts implicit operators fn lookback( - g: &mut VecDeque, - context: &Context + context: &Context, + g: &mut VecDeque ) -> Result<(), (LineLocation, DaisyError)> { - lookback_signs(g, context)?; + lookback_signs(context, g)?; let mut i: usize = 0; while i < g.len() { @@ -142,7 +142,7 @@ fn lookback( => { let la = la.clone(); let lb = lb.clone(); - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); g.insert(i-1, b); if o.is_some() { @@ -164,7 +164,7 @@ fn lookback( => { let la = la.clone(); let lb = lb.clone(); - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); g.insert(i-1, b); if o.is_some() { @@ -196,8 +196,8 @@ fn lookback( pub fn groupify( - mut g: VecDeque, - context: &Context + context: &Context, + mut g: VecDeque ) -> Result< Token, (LineLocation, DaisyError) @@ -240,7 +240,7 @@ pub fn groupify( let (_, mut v) = levels.pop().unwrap(); let (_, v_now) = levels.last_mut().unwrap(); - lookback(&mut v, context)?; + lookback(context, &mut v)?; let q = is_tuple.pop().unwrap(); if q { @@ -275,7 +275,7 @@ pub fn groupify( let (_, v_now) = levels.last_mut().unwrap(); if v.len() == 0 { return Err((l, DaisyError::EmptyGroup)) } - lookback(&mut v, context)?; + lookback(context, &mut v)?; let q = is_tuple.pop().unwrap(); if q { @@ -294,7 +294,7 @@ pub fn groupify( return Err((l, DaisyError::BadTuple)); } - lookback(&mut v, context)?; + lookback(context, &mut v)?; return Ok(Token::Group( LineLocation{pos:0, len:last_linelocation.pos + last_linelocation.len}, diff --git a/src/parser/stage/tokenize.rs b/src/parser/stage/tokenize.rs index 613bb44..e449d01 100644 --- a/src/parser/stage/tokenize.rs +++ b/src/parser/stage/tokenize.rs @@ -10,10 +10,10 @@ use super::super::{ // Called whenever a token is finished. #[inline(always)] fn push_token( + context: &Context, g: &mut VecDeque, t: Option, - stop_i: usize, - context: &Context + stop_i: usize ) { if t.is_none() { return } @@ -60,7 +60,7 @@ fn push_token( // Some operators are written as words. if let Token::Word(l, s) = &t { - if Operator::from_string(s, context).is_some() { + if Operator::from_string(context, s).is_some() { t = Token::Operator(*l, s.clone()); } } @@ -69,7 +69,7 @@ fn push_token( } /// Turns a string into Tokens. First stage of parsing. -pub fn tokenize(input: &String, context: &Context) -> VecDeque { +pub fn tokenize(context: &Context, input: &String) -> VecDeque { let mut t: Option = None; // The current token we're reading let mut g: VecDeque = VecDeque::with_capacity(32); @@ -88,7 +88,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { // If we're not building a number, finalize // previous token and start one. _ => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Quantity(LineLocation{pos: i, len: 0}, String::from(c))); } }; @@ -102,7 +102,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { Some(Token::Quantity(_, val)) => { val.push(c); }, _ => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Word(LineLocation{pos: i, len: 0}, String::from(c))); } }; @@ -122,7 +122,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { } else { // Otherwise, end the number. // We probably have a subtraction. - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Operator( LineLocation{pos: i, len: 1}, String::from(c) @@ -134,7 +134,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { // Multi-character operators with - and + are NOT supported! // (for example, we can't use -> for unit conversion) _ => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Operator( LineLocation{pos: i, len: 1}, String::from(c) @@ -144,7 +144,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { }, ',' => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::TupleDelim(LineLocation{pos: i, len: 1})); }, @@ -157,7 +157,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { match &mut t { Some(Token::Operator(_, val)) => { val.push(c); }, _ => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Operator(LineLocation{pos: i, len: 0}, String::from(c))); } }; @@ -165,17 +165,17 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { // Group '(' => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::GroupStart(LineLocation{pos: i, len: 0})); }, ')' => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::GroupEnd(LineLocation{pos: i, len: 0})); }, // Space. Basic seperator. ' ' => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = None; } @@ -185,7 +185,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { Some(Token::Word(_, val)) => { val.push(c); }, _ => { - push_token(&mut g, t, i, context); + push_token(context, &mut g, t, i); t = Some(Token::Word(LineLocation{pos: i, len: 0}, String::from(c))); } }; @@ -193,7 +193,7 @@ pub fn tokenize(input: &String, context: &Context) -> VecDeque { }; } - push_token(&mut g, t, input.chars().count(), context); + push_token(context, &mut g, t, input.chars().count()); return g; } \ No newline at end of file diff --git a/src/parser/stage/treeify.rs b/src/parser/stage/treeify.rs index 4bbd4cc..54c441a 100644 --- a/src/parser/stage/treeify.rs +++ b/src/parser/stage/treeify.rs @@ -10,9 +10,9 @@ use super::super::{ }; fn treeify_binary( + context: &Context, i: usize, - g_inner: &mut VecDeque, - context: &Context + g_inner: &mut VecDeque ) -> Result { let this: &Token = &g_inner[i]; @@ -56,7 +56,7 @@ fn treeify_binary( if let Token::Operator(l, s) = left { - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string let o = o.unwrap(); @@ -72,7 +72,7 @@ fn treeify_binary( } if let Token::Operator(l, s) = right { - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string let o = o.unwrap(); @@ -91,7 +91,7 @@ fn treeify_binary( // This operator let this_op = { let Token::Operator(l, s) = this else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // bad operator string o.unwrap() }; @@ -99,14 +99,14 @@ fn treeify_binary( // The operators contesting our arguments let left_op = if i > 1 { let Token::Operator(l, s) = &g_inner[i-2] else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad operator string Some(o.unwrap()) } else { None }; let right_op = if i < g_inner.len()-2 { let Token::Operator(l, s) = &g_inner[i+2] else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad operator string Some(o.unwrap()) } else { None }; @@ -122,20 +122,20 @@ fn treeify_binary( let right_pre = g_inner.remove(i-1).unwrap(); let mut left: Expression; let mut right: Expression; if let Token::Group(l, _) = right_pre { - right = treeify(right_pre, context)?; + right = treeify(context, right_pre)?; right.set_linelocation(&(right.get_linelocation() + l)); } else if let Token::Tuple(l, _) = right_pre { - right = treeify(right_pre, context)?; + right = treeify(context, right_pre)?; right.set_linelocation(&(right.get_linelocation() + l)); } else { right = right_pre.to_expression(context)?; } if let Token::Group(l, _) = left_pre { - left = treeify(left_pre, context)?; + left = treeify(context, left_pre)?; left.set_linelocation(&(left.get_linelocation() + l)); } else if let Token::Tuple(l, _) = left_pre { - left = treeify(left_pre, context)?; + left = treeify(context, left_pre)?; left.set_linelocation(&(left.get_linelocation() + l)); } else { left = left_pre.to_expression(context)?; @@ -143,7 +143,7 @@ fn treeify_binary( let (l, o) = { let Token::Operator(l, s) = this_pre else {panic!()}; - let o = Operator::from_string(&s, context); + let o = Operator::from_string(context, &s); if o.is_none() { panic!() } (l, o.unwrap()) }; @@ -161,10 +161,10 @@ fn treeify_binary( } fn treeify_unary( + context: &Context, i: usize, g_inner: &mut VecDeque, - left_associative: bool, - context: &Context + left_associative: bool ) -> Result { let this: &Token = &g_inner[i]; @@ -224,7 +224,7 @@ fn treeify_unary( // This operator let this_op = { let Token::Operator(l, s) = this else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string o.unwrap() }; @@ -233,14 +233,14 @@ fn treeify_unary( let next_op = if left_associative { if i > 1 { let Token::Operator(l, s) = &g_inner[i-2] else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string Some(o.unwrap()) } else { None } } else { if i < g_inner.len()-2 { let Token::Operator(l, s) = &g_inner[i+2] else {panic!()}; - let o = Operator::from_string(s, context); + let o = Operator::from_string(context, s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string Some(o.unwrap()) } else { None } @@ -255,10 +255,10 @@ fn treeify_unary( next_pre = g_inner.remove(i).unwrap(); } if let Token::Group(l, _) = next_pre { - next = treeify(next_pre, context)?; + next = treeify(context, next_pre)?; next.set_linelocation(&(next.get_linelocation() + l)); } else if let Token::Tuple(l, _) = next_pre { - next = treeify(next_pre, context)?; + next = treeify(context, next_pre)?; next.set_linelocation(&(next.get_linelocation() + l)); } else { next = next_pre.to_expression(context)?; @@ -267,7 +267,7 @@ fn treeify_unary( let (l, o) = { let Token::Operator(l, s) = this_pre else {panic!()}; - let o = Operator::from_string(&s, context); + let o = Operator::from_string(context, &s); if o.is_none() { panic!() } (l, o.unwrap()) }; @@ -292,8 +292,8 @@ fn treeify_unary( pub fn treeify( - mut g: Token, - context: &Context + context: &Context, + mut g: Token ) -> Result { let (l, g_inner): (LineLocation, &mut VecDeque) = match g { @@ -301,7 +301,7 @@ pub fn treeify( Token::Tuple(l, parts) => { let mut t: VecDeque = VecDeque::new(); for p in parts { - t.push_back(treeify(p, context)?); + t.push_back(treeify(context, p)?); }; return Ok(Expression::Tuple(l, t)); @@ -332,7 +332,7 @@ pub fn treeify( // If not an operator, move on. let this_op = match &g_inner[i] { Token::Operator(l, s) => { - let o = Operator::from_string(&s, context); + let o = Operator::from_string(context, &s); if o.is_none() { return Err((*l, DaisyError::Syntax)); } o.unwrap() }, @@ -346,9 +346,9 @@ pub fn treeify( let mut changed = false; if this_op.is_left_associative() { if this_op.is_binary() { - changed = treeify_binary(i, g_inner, context)?; + changed = treeify_binary(context, i, g_inner)?; } else { - changed = treeify_unary(i, g_inner, left_associative, context)?; + changed = treeify_unary(context, i, g_inner, left_associative)?; } } @@ -360,9 +360,9 @@ pub fn treeify( } else { if !this_op.is_left_associative() { if this_op.is_binary() { - treeify_binary(i, g_inner, context)?; + treeify_binary(context, i, g_inner)?; } else { - treeify_unary(i, g_inner, left_associative, context)?; + treeify_unary(context, i, g_inner, left_associative)?; } } j -= 1 @@ -378,7 +378,7 @@ pub fn treeify( }, Token::Tuple(_, _) | Token::Group(_,_) => { - treeify(g, context) + treeify(context, g) }, diff --git a/src/quantity/quantity.rs b/src/quantity/quantity.rs index fd63e5b..b42af9f 100644 --- a/src/quantity/quantity.rs +++ b/src/quantity/quantity.rs @@ -7,6 +7,7 @@ use std::ops::{ }; use std::cmp::Ordering; +use crate::context::Context; use crate::quantity::Unit; use crate::quantity::FreeUnit; @@ -21,12 +22,12 @@ pub struct Quantity { -impl ToString for Quantity { - fn to_string(&self) -> String { +impl Quantity { + pub fn display(&self, context: &Context) -> String { let n = self.scalar.to_string(); if self.unitless() { return n; } - let u = self.unit.to_string(); + let u = self.unit.display(context); if self.is_one() { return u; }; if self.unit.no_space() { @@ -38,11 +39,11 @@ impl ToString for Quantity { } impl Quantity { - pub fn to_string_outer(&self) -> String { + pub fn display_outer(&self, context: &Context) -> String { let n = self.scalar.to_string(); if self.unitless() { return n; } - let u = self.unit.to_string(); + let u = self.unit.display(context); if self.unit.no_space() { return format!("{n}{u}"); } else { diff --git a/src/quantity/unit/unit.rs b/src/quantity/unit/unit.rs index 40e35c4..c49e0b3 100644 --- a/src/quantity/unit/unit.rs +++ b/src/quantity/unit/unit.rs @@ -4,6 +4,7 @@ use std::ops::{ MulAssign, DivAssign }; +use crate::context::Context; use crate::quantity::Scalar; use crate::quantity::Quantity; use super::FreeUnit; @@ -16,8 +17,8 @@ pub struct Unit { pub val: HashMap } -impl ToString for Unit { - fn to_string(&self) -> String { +impl Unit { + pub fn display(&self, context: &Context) -> String { if self.unitless() { return String::new(); }; @@ -37,7 +38,11 @@ impl ToString for Unit { if *p == Scalar::new_rational(1f64).unwrap() { t.push_str(&format!("{c}·")); - } else if p.is_int() && !p.to_string().contains("e"){ + } else if { + context.config.enable_super_powers && + p.is_int() && + !p.to_string().contains("e") + } { t.push_str(&c); for c in p.to_string().chars() { t.push( match c { @@ -74,7 +79,11 @@ impl ToString for Unit { bottom_count += 1; if t.len() != 0 && *p == Scalar::new_rational(-1f64).unwrap() { b.push_str(&format!("{c}·")); - } else if p.is_int() && !p.to_string().contains("e") { + } else if { + context.config.enable_super_powers && + p.is_int() && + !p.to_string().contains("e") + } { b.push_str(&c); for c in p.to_string().chars() { if c == '-' && t.len() != 0 { continue; } diff --git a/src/tests.rs b/src/tests.rs index 204b313..0f41c8d 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -10,8 +10,10 @@ fn eval_to_str(s: &str) -> Result { }; //let out_str = g.print(); - return match evaluate(&g, &mut Context::new()) { - Ok(x) => Ok(x.to_string_outer()), + let mut c = Context::new(); + + return match evaluate(&mut c, &g) { + Ok(x) => Ok(x.display_outer(&c)), Err(_) => Err(()) }; }