From 2f1f8a080184fa8bd8bd5784d63a184bc5920934 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 11:29:50 -0700 Subject: [PATCH 01/26] Fixed dependencies --- Cargo.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 327e09e..6aab7d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,13 @@ panic = "abort" [dependencies] cfg-if = "1.0.0" - -[target.'cfg(target_family = "unix")'.dependencies] -termion = "2.0.1" num = "0.4.1" #astro-float = "0.7.1" + +[target.'cfg(target_family = "unix")'.dependencies] +termion = "2.0.1" + + [build-dependencies] toml = "0.7.4" \ No newline at end of file From fde1220a96fea2b64845777bbe0ba7892e8dd9c6 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 13:18:41 -0700 Subject: [PATCH 02/26] Rearranged code, created lib.rs --- Cargo.toml | 4 + src/entrypoint/mod.rs | 13 - src/entrypoint/unix/mod.rs | 3 - src/entrypoint/unix/unix.rs | 115 ------- src/formattedtext.rs | 20 +- src/lib.rs | 283 +++++++++++++++++ src/main.rs | 357 ++++++---------------- src/{entrypoint/unix => }/promptbuffer.rs | 10 +- src/tests.rs | 6 +- 9 files changed, 402 insertions(+), 409 deletions(-) delete mode 100644 src/entrypoint/mod.rs delete mode 100644 src/entrypoint/unix/mod.rs delete mode 100644 src/entrypoint/unix/unix.rs create mode 100644 src/lib.rs rename src/{entrypoint/unix => }/promptbuffer.rs (95%) diff --git a/Cargo.toml b/Cargo.toml index 6aab7d4..052610a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,10 @@ readme = "README.md" name = "daisy" path = "src/main.rs" +[lib] +name = "daisycalc" +path = "src/lib.rs" + [profile.release] opt-level = 3 debug = 0 diff --git a/src/entrypoint/mod.rs b/src/entrypoint/mod.rs deleted file mode 100644 index 54ff10f..0000000 --- a/src/entrypoint/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ - - -// Select main script for target system -cfg_if::cfg_if! { - if #[cfg(target_family = "unix")] { - mod unix; - pub use unix::main as main_e; - } else { - pub fn main_e() -> Result<(), std::io::Error> { - unimplemented!("Not yet implemented."); - } - } -} \ No newline at end of file diff --git a/src/entrypoint/unix/mod.rs b/src/entrypoint/unix/mod.rs deleted file mode 100644 index bca160f..0000000 --- a/src/entrypoint/unix/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod unix; -mod promptbuffer; -pub use self::unix::main; \ No newline at end of file diff --git a/src/entrypoint/unix/unix.rs b/src/entrypoint/unix/unix.rs deleted file mode 100644 index 104a60a..0000000 --- a/src/entrypoint/unix/unix.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::io::stdout; -use std::io::stdin; -use std::env; - -use termion::{ - event::Key, - input::TermRead, - 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::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(&mut context, &String::from("help")); - t.write(&context, &mut stdout)?; - return Ok(()); - } else if args.iter().any(|s| s == "--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 context, &mut stdout)?; - - let stdin = stdin(); - for c in stdin.keys() { - if let Key::Char(q) = c.as_ref().unwrap() { - match q { - '\n' => { - // Print again without cursor, in case we pressed enter - // while inside a substitution - pb.write_prompt_nocursor(&mut context, &mut stdout)?; - let in_str = pb.enter(); - FormattedText::newline(&mut stdout)?; - if in_str == "" { break; } - - if in_str.trim() == "quit" { - break 'outer; - } else { - let r = crate::do_string(&mut context, &in_str); - - match r { - Ok(t) | Err(t) => { - t.write(&context, &mut stdout).unwrap(); - } - } - } - - break; - }, - _ => { pb.add_char(*q); } - }; - } else { - match c.unwrap() { - Key::Backspace => { pb.backspace(); }, - Key::Delete => { pb.delete(); }, - Key::Left => { pb.cursor_left(); }, - Key::Right => { pb.cursor_right(); }, - Key::Up => { pb.hist_up(); }, - Key::Down => { pb.hist_down(); }, - - Key::Ctrl('d') | - Key::Ctrl('c') => { break 'outer; }, - _ => {} - }; - }; - - pb.write_prompt(&mut context, &mut stdout)?; - } - } - - FormattedText::newline(&mut stdout)?; - return Ok(()); -} \ No newline at end of file diff --git a/src/formattedtext.rs b/src/formattedtext.rs index dfcfc55..ed7c6ef 100644 --- a/src/formattedtext.rs +++ b/src/formattedtext.rs @@ -102,14 +102,7 @@ fn format_map_full(c: char) -> Option { }) } -pub fn format_map(c: char, context: &Context) -> Option { - 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") - } -} + impl FormattedText { @@ -117,6 +110,15 @@ impl FormattedText { write!(stdout, "\n")?; return Ok(()); } + + pub fn format_map(c: char, context: &Context) -> Option { + 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") + } + } } @@ -161,7 +163,7 @@ impl FormattedText { match (a, b) { (c, ']') => { // Normal text - let q = format_map(c, context); + let q = Self::format_map(c, context); if q.is_some() { s.push_str(&q.unwrap()); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..1e85639 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,283 @@ +pub mod parser; +pub mod command; +pub mod quantity; + +use crate::parser::substitute; +use crate::parser::LineLocation; + + +mod context; +mod formattedtext; +mod errors; +mod evaluate; + +pub use crate::formattedtext::FormattedText; +pub use crate::context::Context; +pub use crate::errors::DaisyError; +pub use crate::evaluate::evaluate; + + + +#[inline(always)] +pub fn do_string( + context: &mut Context, + s: &String +) -> Result { + + let r: (LineLocation, DaisyError); + if command::is_command(s) { + return Ok(command::do_command(context, s)); + } else if s.contains("=") { + let x = do_assignment(context, s); + match x { + Ok(t) => { return Ok(t) }, + Err(t) => { r = t } + }; + } else { + let x = do_expression(context, s); + match x { + Ok((t, e)) => { context.push_hist(e); return Ok(t) }, + Err(t) => { r = t } + }; + } + + let (l, e) = r; + let mut t = FormattedText::new("".to_string()); + if l.zero() { + t.push(&format!( + "\n {}\n\n", + e.text().to_string(), + )); + } else { + t.push(&format!( + concat!( + "{}[e]{}[n]\n", + " {}\n\n" + ), + " ".repeat(l.pos + 4), + "^".repeat(l.len), + e.text().to_string(), + )); + } + + return Err(t); +} + +// Handle a simple evaluation string. +// Returns a FormattedText with output that should be printed. +#[inline(always)] +fn do_expression( + context: &mut Context, + s: &String +) -> Result<(FormattedText, parser::Expression), (LineLocation, DaisyError)> { + + let mut output = FormattedText::new("".to_string()); + + let g = parser::parse(context, &s)?; + let g_evaluated = evaluate::evaluate(context, &g)?; + + // Display parsed string + output.push(&format!( + " [s]=>[n] {}\n\n", + g.display(context) + )); + + // Display result + output.push(&format!( + " [r]=[n] {}\n\n", + g_evaluated.display_outer(context), + )); + + return Ok((output, g_evaluated)); +} + + +// Handle a variable or function definition string. +// Returns a FormattedText with output that should be printed. +#[inline(always)] +fn do_assignment( + context: &mut Context, + s: &String +) -> Result { + + let mut output = FormattedText::new("".to_string()); + + let parts = s.split("=").collect::>(); + if parts.len() != 2 { + return Err(( + LineLocation::new_zero(), + DaisyError::Syntax + )); + } + + // Index of first non-whitespace character in left + // (relative to whole prompt) + let starting_left = parts[0] + .char_indices() + .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n')) + .map(|(i, _)| i) + .unwrap_or_else(|| parts[0].len()); + + // Index of first non-whitespace character in right + // (relative to whole prompt) + // +1 accounts for equals sign + let starting_right = parts[0].chars().count() + 1 + + parts[1] + .char_indices() + .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n')) + .map(|(i, _)| i) + .unwrap_or_else(|| parts[0].len()); + + + 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. + // This is intentional, since we want to check a definition's + // variable name before even attempting to parse its content. + if is_function { + let mut mode = 0; + let mut name = String::new(); + let mut args = String::new(); + for c in left.chars() { + match mode { + + // Mode 0: reading function name + 0 => { + if c == '(' { + mode = 1; continue; + } else { name.push(c); } + }, + + // Mode 1: reading arguments + 1 => { + if c == ')' { + mode = 2; continue; + } else { args.push(c); } + }, + + // Mode 2: we should be done by now. + // That close paren should've been the last character. + 2 => { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::Syntax + )); + }, + + _ => unreachable!() + } + } + + + let args = args + .split(",").collect::>() + .iter().map(|x| x.trim().to_string()).collect::>(); + + if name.len() == 0 { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::Syntax + )); + }; + + if !context.valid_function(&name) { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::BadFunction + )); + }; + + if args.iter().find(|x| &x[..] == "").is_some() { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::Syntax + )); + }; + + for a in &args { + if !context.valid_varible(a) { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::BadVariable + )); + } + } + + // Parse right hand side + let g = parser::parse(context, &right); + let Ok(g) = g else { + let Err((l, e)) = g else { unreachable!() }; + return Err(( + LineLocation{ pos: l.pos + starting_right, len: l.len}, + e + )); + }; + + // Display parsed string + output.push(&format!( + " [s]=>[n] {left} = {}\n\n", + g.display(context) + )); + + // Evaluate expression with shadow variables + for a in &args { context.add_shadow(a.to_string(), None);} + 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!() }; + return Err(( + LineLocation{ pos: l.pos + starting_right, len: l.len}, + e + )); + }; + + // We could push g_evaluated instead, but an un-evaluated string + // makes the 'vars' command prettier. + // + // We still need to evaluate g above, though, to make sure it works. + context.push_function(name, args, g).unwrap(); + } else { + + if !context.valid_varible(&left) { + return Err(( + LineLocation{ pos: starting_left, len: left.chars().count() }, + DaisyError::BadVariable + )); + } + + // Parse right hand side + let g = parser::parse(context, &right); + let Ok(g) = g else { + let Err((l, e)) = g else { unreachable!() }; + return Err(( + LineLocation{ pos: l.pos + starting_right, len: l.len}, + e + )); + }; + + // Display parsed string + output.push(&format!( + " [t]=>[n] {left} = {}\n\n", + g.display(context) + )); + + // Evaluate expression + let g_evaluated = evaluate::evaluate(context, &g); + let Ok(g_evaluated) = g_evaluated else { + let Err((l, e)) = g_evaluated else { unreachable!() }; + return Err(( + LineLocation{ pos: l.pos + starting_right, len: l.len}, + e + )); + }; + + context.push_variable(left.to_string(), g_evaluated).unwrap(); + } + + return Ok(output); + +} + + diff --git a/src/main.rs b/src/main.rs index 0841f07..ac2c86d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,289 +1,124 @@ -pub mod parser; -pub mod command; -pub mod quantity; -pub mod evaluate; -pub mod context; -pub mod errors; -pub mod formattedtext; +pub mod promptbuffer; -use crate::parser::substitute; -use crate::errors::DaisyError; -use crate::formattedtext::FormattedText; -use crate::context::Context; -use crate::parser::LineLocation; +use std::io::stdout; +use std::io::stdin; +use std::env; +use termion::{ + event::Key, + input::TermRead, + raw::IntoRawMode, + color::DetectColors +}; -// Run main script for target system -mod entrypoint; -use crate::entrypoint::main_e; +use promptbuffer::PromptBuffer; +use daisycalc::command; +use daisycalc::Context; +use daisycalc::FormattedText; + +use daisycalc::do_string; #[cfg(test)] mod tests; -fn main() -> Result<(), std::io::Error> { - return main_e(); -} + #[inline(always)] -pub fn do_string( - context: &mut Context, - s: &String -) -> Result { +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::new(); - let r: (LineLocation, DaisyError); - if command::is_command(s) { - return Ok(command::do_command(context, s)); - } else if s.contains("=") { - let x = do_assignment(context, s); - match x { - Ok(t) => { return Ok(t) }, - Err(t) => { r = t } - }; + // 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 { - let x = do_expression(context, s); - match x { - Ok((t, e)) => { context.push_hist(e); return Ok(t) }, - Err(t) => { r = t } - }; + context.config.term_color_type = 0; } - let (l, e) = r; - let mut t = FormattedText::new("".to_string()); - if l.zero() { - t.push(&format!( - "\n {}\n\n", - e.text().to_string(), + 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(&mut context, &String::from("help")); + t.write(&context, &mut stdout)?; + return Ok(()); + } else if args.iter().any(|s| s == "--version") { + let t = FormattedText::new(format!( + "Daisy v{}\n", env!("CARGO_PKG_VERSION") )); - } else { - t.push(&format!( + t.write(&context, &mut stdout)?; + return Ok(()); + } else if args.iter().any(|s| s == "--debug") { + let t = FormattedText::new(format!( concat!( - "{}[e]{}[n]\n", - " {}\n\n" + "Daisy v{}\n", + "Your terminal supports {} colors.\n" ), - " ".repeat(l.pos + 4), - "^".repeat(l.len), - e.text().to_string(), + env!("CARGO_PKG_VERSION"), + term_colors )); + t.write(&context, &mut stdout)?; + return Ok(()); } - return Err(t); -} + 'outer: loop { -// Handle a simple evaluation string. -// Returns a FormattedText with output that should be printed. -#[inline(always)] -fn do_expression( - context: &mut Context, - s: &String -) -> Result<(FormattedText, parser::Expression), (LineLocation, DaisyError)> { + pb.write_prompt(&mut context, &mut stdout)?; - let mut output = FormattedText::new("".to_string()); + let stdin = stdin(); + for c in stdin.keys() { + if let Key::Char(q) = c.as_ref().unwrap() { + match q { + '\n' => { + // Print again without cursor, in case we pressed enter + // while inside a substitution + pb.write_prompt_nocursor(&mut context, &mut stdout)?; + let in_str = pb.enter(); + FormattedText::newline(&mut stdout)?; + if in_str == "" { break; } - let g = parser::parse(context, &s)?; - let g_evaluated = evaluate::evaluate(context, &g)?; + if in_str.trim() == "quit" { + break 'outer; + } else { + let r = crate::do_string(&mut context, &in_str); - // Display parsed string - output.push(&format!( - " [s]=>[n] {}\n\n", - g.display(context) - )); + match r { + Ok(t) | Err(t) => { + t.write(&context, &mut stdout).unwrap(); + } + } + } - // Display result - output.push(&format!( - " [r]=[n] {}\n\n", - g_evaluated.display_outer(context), - )); + break; + }, + _ => { pb.add_char(*q); } + }; + } else { + match c.unwrap() { + Key::Backspace => { pb.backspace(); }, + Key::Delete => { pb.delete(); }, + Key::Left => { pb.cursor_left(); }, + Key::Right => { pb.cursor_right(); }, + Key::Up => { pb.hist_up(); }, + Key::Down => { pb.hist_down(); }, - return Ok((output, g_evaluated)); -} + Key::Ctrl('d') | + Key::Ctrl('c') => { break 'outer; }, + _ => {} + }; + }; - -// Handle a variable or function definition string. -// Returns a FormattedText with output that should be printed. -#[inline(always)] -fn do_assignment( - context: &mut Context, - s: &String -) -> Result { - - let mut output = FormattedText::new("".to_string()); - - let parts = s.split("=").collect::>(); - if parts.len() != 2 { - return Err(( - LineLocation::new_zero(), - DaisyError::Syntax - )); + pb.write_prompt(&mut context, &mut stdout)?; + } } - // Index of first non-whitespace character in left - // (relative to whole prompt) - let starting_left = parts[0] - .char_indices() - .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n')) - .map(|(i, _)| i) - .unwrap_or_else(|| parts[0].len()); - - // Index of first non-whitespace character in right - // (relative to whole prompt) - // +1 accounts for equals sign - let starting_right = parts[0].chars().count() + 1 + - parts[1] - .char_indices() - .find(|(_, ch)| !(ch.is_whitespace() && *ch != '\n')) - .map(|(i, _)| i) - .unwrap_or_else(|| parts[0].len()); - - - 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. - // This is intentional, since we want to check a definition's - // variable name before even attempting to parse its content. - if is_function { - let mut mode = 0; - let mut name = String::new(); - let mut args = String::new(); - for c in left.chars() { - match mode { - - // Mode 0: reading function name - 0 => { - if c == '(' { - mode = 1; continue; - } else { name.push(c); } - }, - - // Mode 1: reading arguments - 1 => { - if c == ')' { - mode = 2; continue; - } else { args.push(c); } - }, - - // Mode 2: we should be done by now. - // That close paren should've been the last character. - 2 => { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::Syntax - )); - }, - - _ => unreachable!() - } - } - - - let args = args - .split(",").collect::>() - .iter().map(|x| x.trim().to_string()).collect::>(); - - if name.len() == 0 { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::Syntax - )); - }; - - if !context.valid_function(&name) { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::BadFunction - )); - }; - - if args.iter().find(|x| &x[..] == "").is_some() { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::Syntax - )); - }; - - for a in &args { - if !context.valid_varible(a) { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::BadVariable - )); - } - } - - // Parse right hand side - let g = parser::parse(context, &right); - let Ok(g) = g else { - let Err((l, e)) = g else { unreachable!() }; - return Err(( - LineLocation{ pos: l.pos + starting_right, len: l.len}, - e - )); - }; - - // Display parsed string - output.push(&format!( - " [s]=>[n] {left} = {}\n\n", - g.display(context) - )); - - // Evaluate expression with shadow variables - for a in &args { context.add_shadow(a.to_string(), None);} - 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!() }; - return Err(( - LineLocation{ pos: l.pos + starting_right, len: l.len}, - e - )); - }; - - // We could push g_evaluated instead, but an un-evaluated string - // makes the 'vars' command prettier. - // - // We still need to evaluate g above, though, to make sure it works. - context.push_function(name, args, g).unwrap(); - } else { - - if !context.valid_varible(&left) { - return Err(( - LineLocation{ pos: starting_left, len: left.chars().count() }, - DaisyError::BadVariable - )); - } - - // Parse right hand side - let g = parser::parse(context, &right); - let Ok(g) = g else { - let Err((l, e)) = g else { unreachable!() }; - return Err(( - LineLocation{ pos: l.pos + starting_right, len: l.len}, - e - )); - }; - - // Display parsed string - output.push(&format!( - " [t]=>[n] {left} = {}\n\n", - g.display(context) - )); - - // Evaluate expression - let g_evaluated = evaluate::evaluate(context, &g); - let Ok(g_evaluated) = g_evaluated else { - let Err((l, e)) = g_evaluated else { unreachable!() }; - return Err(( - LineLocation{ pos: l.pos + starting_right, len: l.len}, - e - )); - }; - - context.push_variable(left.to_string(), g_evaluated).unwrap(); - } - - return Ok(output); - -} - - + FormattedText::newline(&mut stdout)?; + return Ok(()); +} \ No newline at end of file diff --git a/src/entrypoint/unix/promptbuffer.rs b/src/promptbuffer.rs similarity index 95% rename from src/entrypoint/unix/promptbuffer.rs rename to src/promptbuffer.rs index 619f759..0b6bc6b 100644 --- a/src/entrypoint/unix/promptbuffer.rs +++ b/src/promptbuffer.rs @@ -1,9 +1,9 @@ use std::collections::VecDeque; use std::io::Write; use termion::raw::RawTerminal; -use crate::formattedtext; -use crate::parser::substitute_cursor; -use crate::context::Context; +use daisycalc::FormattedText; +use daisycalc::parser::substitute_cursor; +use daisycalc::Context; const PROMPT_STR: &str = "==> "; @@ -55,8 +55,8 @@ impl PromptBuffer { write!( stdout, "\r{}{PROMPT_STR}{}{}", - formattedtext::format_map('p', context).unwrap(), - formattedtext::format_map('n', context).unwrap(), + FormattedText::format_map('p', context).unwrap(), + FormattedText::format_map('n', context).unwrap(), s )?; diff --git a/src/tests.rs b/src/tests.rs index d54f10f..e765a57 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,7 +1,7 @@ // Many of these have been borrowed from insect. -use crate::parser; -use crate::evaluate::evaluate; -use crate::context::Context; +use daisycalc::parser; +use daisycalc::evaluate; +use daisycalc::Context; fn eval_to_str(s: &str) -> Result { let g = match parser::parse_no_context(&String::from(s)) { From c237e9d5ec741485fd370b790735f85cd6b3874b Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:23:24 -0700 Subject: [PATCH 03/26] Added wasm dependencies --- Cargo.lock | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 6 +++ 2 files changed, 114 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 62b4b82..8a0e193 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,12 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "cfg-if" version = "1.0.0" @@ -28,6 +34,7 @@ dependencies = [ "num", "termion", "toml", + "wasm-bindgen", ] [[package]] @@ -52,6 +59,12 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "memchr" version = "2.5.0" @@ -140,6 +153,30 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef" +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "proc-macro2" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -173,6 +210,17 @@ dependencies = [ "serde", ] +[[package]] +name = "syn" +version = "2.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "termion" version = "2.0.1" @@ -219,6 +267,66 @@ dependencies = [ "winnow", ] +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + [[package]] name = "winnow" version = "0.4.6" diff --git a/Cargo.toml b/Cargo.toml index 052610a..1df7360 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ path = "src/main.rs" [lib] name = "daisycalc" path = "src/lib.rs" +crate-type = ["cdylib", "rlib"] [profile.release] opt-level = 3 @@ -32,10 +33,15 @@ cfg-if = "1.0.0" num = "0.4.1" #astro-float = "0.7.1" +[package.metadata.wasm-pack.profile.release] +wasm-opt = false [target.'cfg(target_family = "unix")'.dependencies] termion = "2.0.1" +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" + [build-dependencies] toml = "0.7.4" \ No newline at end of file From 470c3a49ed06e12ddf0b784fd6b94cb6eb7db4ff Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:23:46 -0700 Subject: [PATCH 04/26] Added wasm implementations for formattedtext --- src/formattedtext/formattedtext.rs | 33 +++++++++ src/formattedtext/mod.rs | 12 ++++ .../unix_backend.rs} | 67 +++++-------------- src/formattedtext/wasm_backend.rs | 12 ++++ 4 files changed, 72 insertions(+), 52 deletions(-) create mode 100644 src/formattedtext/formattedtext.rs create mode 100644 src/formattedtext/mod.rs rename src/{formattedtext.rs => formattedtext/unix_backend.rs} (87%) create mode 100644 src/formattedtext/wasm_backend.rs diff --git a/src/formattedtext/formattedtext.rs b/src/formattedtext/formattedtext.rs new file mode 100644 index 0000000..45b5a5d --- /dev/null +++ b/src/formattedtext/formattedtext.rs @@ -0,0 +1,33 @@ +use std::ops::Add; + + +#[derive(Debug)] +#[derive(Clone)] +pub struct FormattedText { + pub(super) text: String +} + +impl ToString for FormattedText { + fn to_string(&self) -> String { return self.text.clone(); } +} + +impl FormattedText { + pub fn new(s: String) -> FormattedText { + return FormattedText { + text: s + } + } + + pub fn push(&mut self, s: &str) { + self.text.push_str(s); + } +} + + +impl Add for FormattedText { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + return FormattedText::new(format!("{}{}", self.text, other.text)); + } +} \ No newline at end of file diff --git a/src/formattedtext/mod.rs b/src/formattedtext/mod.rs new file mode 100644 index 0000000..d30b4f7 --- /dev/null +++ b/src/formattedtext/mod.rs @@ -0,0 +1,12 @@ +mod formattedtext; +pub use formattedtext::FormattedText; + + +// Select write implementation by target system +cfg_if::cfg_if! { + if #[cfg(target_family = "unix")] { + mod unix_backend; + } else if #[cfg(target_arch = "wasm32")] { + mod wasm_backend; + } +} \ No newline at end of file diff --git a/src/formattedtext.rs b/src/formattedtext/unix_backend.rs similarity index 87% rename from src/formattedtext.rs rename to src/formattedtext/unix_backend.rs index ed7c6ef..857ffed 100644 --- a/src/formattedtext.rs +++ b/src/formattedtext/unix_backend.rs @@ -1,34 +1,12 @@ +use super::FormattedText; use std::io::Write; +use crate::context::Context; + use termion::raw::RawTerminal; use termion::color; use termion::style; use termion::clear; use termion::cursor; -use std::ops::Add; -use crate::context::Context; - - -#[derive(Debug)] -#[derive(Clone)] -pub struct FormattedText { - text: String -} - -impl ToString for FormattedText { - fn to_string(&self) -> String { return self.text.clone(); } -} - - -fn format_map_none(c: char) -> Option { - Some(match c { - 'n'|'i'|'t'|'a'| - 'e'|'c'|'s'|'r'| - 'p' - => { "".to_string() }, - _ => { return None } - }) -} - fn format_map_ansi(c: char) -> Option { Some(match c { @@ -65,6 +43,17 @@ fn format_map_ansi(c: char) -> Option { } + +fn format_map_none(c: char) -> Option { + Some(match c { + 'n'|'i'|'t'|'a'| + 'e'|'c'|'s'|'r'| + 'p' + => { "".to_string() }, + _ => { return None } + }) +} + // style::reset also resets color. // Make sure color comes AFTER style reset. fn format_map_full(c: char) -> Option { @@ -102,9 +91,6 @@ fn format_map_full(c: char) -> Option { }) } - - - impl FormattedText { pub fn newline(stdout: &mut RawTerminal) -> Result<(), std::io::Error> { write!(stdout, "\n")?; @@ -119,20 +105,6 @@ impl FormattedText { _ => unreachable!("Invalid term_color_type") } } -} - - -impl FormattedText { - pub fn new(s: String) -> FormattedText { - return FormattedText { - text: s - } - } - - pub fn push(&mut self, s: &str) { - self.text.push_str(s); - } - pub fn write(&self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { @@ -155,7 +127,7 @@ impl FormattedText { '[' => { let a = chars.next().unwrap(); - // Handle double [[ as escaped [ + // Treat double [[ as escaped [ if a == '[' { s.push('['); } let b = chars.next().unwrap(); @@ -189,13 +161,4 @@ impl FormattedText { write!(stdout, "\r{}", s)?; return Ok(()); } -} - - -impl Add for FormattedText { - type Output = Self; - - fn add(self, other: Self) -> Self::Output { - return FormattedText::new(format!("{}{}", self.text, other.text)); - } } \ No newline at end of file diff --git a/src/formattedtext/wasm_backend.rs b/src/formattedtext/wasm_backend.rs new file mode 100644 index 0000000..8235705 --- /dev/null +++ b/src/formattedtext/wasm_backend.rs @@ -0,0 +1,12 @@ +use super::FormattedText; + +impl FormattedText { + pub fn newline() -> Result<(), ()> { + print!("\n"); + return Ok(()); + } + + pub fn write(&self) -> String { + return self.text.clone(); + } +} \ No newline at end of file From dbde7cb6f4ab66ba5241e2b7de63cae51171f8ae Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:24:15 -0700 Subject: [PATCH 05/26] Comments --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 1df7360..cd74d21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ num = "0.4.1" #astro-float = "0.7.1" [package.metadata.wasm-pack.profile.release] +# wasm-opt doesn't work, need to figure out why wasm-opt = false [target.'cfg(target_family = "unix")'.dependencies] From 1bd0b2580b953a75f69d3f182f4b4518a7429e83 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:24:39 -0700 Subject: [PATCH 06/26] Added wasm binding --- src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 1e85639..d0737fc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,6 +17,24 @@ pub use crate::errors::DaisyError; pub use crate::evaluate::evaluate; +cfg_if::cfg_if! { + if #[cfg(target_arch = "wasm32")] { + use wasm_bindgen::prelude::*; + + #[wasm_bindgen] + pub fn dostr(x: String) -> String{ + let mut context = Context::new(); + let x = x.trim().to_string(); + let r = do_string(&mut context, &x); + + match r { + Ok(t) | Err(t) => { + return t.write(); + } + } + } + } +} #[inline(always)] pub fn do_string( From c8ebec59ae1d41428055f91d041b8bcd86588e29 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:25:10 -0700 Subject: [PATCH 07/26] Renamed --debug to --info --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index ac2c86d..8907f3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ pub fn main() -> Result<(), std::io::Error> { )); t.write(&context, &mut stdout)?; return Ok(()); - } else if args.iter().any(|s| s == "--debug") { + } else if args.iter().any(|s| s == "--info") { let t = FormattedText::new(format!( concat!( "Daisy v{}\n", From 4aba24ec691d29a13d5b8b62c12d8aaefd9e97f9 Mon Sep 17 00:00:00 2001 From: Mark Date: Wed, 20 Sep 2023 14:29:14 -0700 Subject: [PATCH 08/26] Added HTML test page --- index.html | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..e6acdaa --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + + +
+
+
+ $ + +
+
+ + + + + From 0c07cb258b2f3c7977959ba1724f9d1415bc40ad Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:02:11 -0700 Subject: [PATCH 09/26] Promptbuffer now uses FormattedText --- src/promptbuffer.rs | 89 +++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 48 deletions(-) diff --git a/src/promptbuffer.rs b/src/promptbuffer.rs index 0b6bc6b..dc9d7d1 100644 --- a/src/promptbuffer.rs +++ b/src/promptbuffer.rs @@ -1,9 +1,7 @@ use std::collections::VecDeque; -use std::io::Write; -use termion::raw::RawTerminal; -use daisycalc::FormattedText; -use daisycalc::parser::substitute_cursor; -use daisycalc::Context; +use crate::FormattedText; +use crate::parser::substitute_cursor; +use crate::Context; const PROMPT_STR: &str = "==> "; @@ -18,12 +16,49 @@ pub struct PromptBuffer { // 1 means "on last item of history" hist_cursor: usize, - buffer: String, + pub buffer: String, buffer_changed: bool, cursor: usize, last_print_len: usize } +impl PromptBuffer { + // Same as write_primpt, but pretends there is no cursor + pub fn write_prompt_nocursor(&mut self, context: &Context) -> FormattedText { + let tmp = self.cursor; + self.cursor = 0; + let r = self.write_prompt(context); + self.cursor = tmp; + return r; + } + + pub fn write_prompt(&mut self, context: &Context) -> FormattedText { + let l = self.buffer.chars().count(); + let i = if l == 0 {0} else {l - self.cursor}; + + // Draw prettyprinted expression + let (display_c, s) = substitute_cursor(context, &self.get_contents(), i); + + let mut tx = FormattedText::new("".to_string()); + + tx.push(&format!("\r[p]{PROMPT_STR}[n]{s}")); + + + // If this string is shorter, clear the remaining old one. + if s.chars().count() < self.last_print_len { + tx.push(&" ".repeat(self.last_print_len - s.chars().count())); + } + + let q = (display_c + PROMPT_STR.chars().count()) as u16; + tx.push(&format!("\r[cursorright{q}]")); + + self.last_print_len = s.chars().count(); + + return tx; + } +} + + impl PromptBuffer { pub fn new(maxlen: usize) -> PromptBuffer { return PromptBuffer { @@ -37,48 +72,6 @@ impl PromptBuffer { }; } - // Same as write_primpt, but pretends there is no cursor - pub fn write_prompt_nocursor(&mut self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { - let tmp = self.cursor; - self.cursor = 0; - let r = self.write_prompt(context, stdout); - self.cursor = tmp; - return r; - } - - 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_c, s) = substitute_cursor(context, &self.get_contents(), i); - - write!( - stdout, "\r{}{PROMPT_STR}{}{}", - FormattedText::format_map('p', context).unwrap(), - FormattedText::format_map('n', context).unwrap(), - s - )?; - - // If this string is shorter, clear the remaining old one. - if s.chars().count() < self.last_print_len { - write!( - stdout, "{}", - " ".repeat(self.last_print_len - s.chars().count()), - )?; - } - - write!( - stdout, "\r{}", - termion::cursor::Right((display_c + PROMPT_STR.chars().count()) as u16) - )?; - - stdout.flush()?; - self.last_print_len = s.chars().count(); - - return Ok(()); - } - // Prompt methods pub fn get_contents(&self) -> &String {&self.buffer} From 7d78d0d74d462f5d8a9dc643ed0104a5e95c9fbf Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:02:40 -0700 Subject: [PATCH 10/26] Improved formattedtext --- src/formattedtext/formattedtext.rs | 7 ++ src/formattedtext/unix_backend.rs | 149 ++++++++++++++++------------- src/formattedtext/wasm_backend.rs | 80 ++++++++++++++-- 3 files changed, 163 insertions(+), 73 deletions(-) diff --git a/src/formattedtext/formattedtext.rs b/src/formattedtext/formattedtext.rs index 45b5a5d..53188f3 100644 --- a/src/formattedtext/formattedtext.rs +++ b/src/formattedtext/formattedtext.rs @@ -1,4 +1,5 @@ use std::ops::Add; +use std::ops::AddAssign; #[derive(Debug)] @@ -30,4 +31,10 @@ impl Add for FormattedText { fn add(self, other: Self) -> Self::Output { return FormattedText::new(format!("{}{}", self.text, other.text)); } +} + +impl AddAssign for FormattedText where { + fn add_assign(&mut self, other: Self) { + self.text.push_str(&other.text); + } } \ No newline at end of file diff --git a/src/formattedtext/unix_backend.rs b/src/formattedtext/unix_backend.rs index 857ffed..789e14a 100644 --- a/src/formattedtext/unix_backend.rs +++ b/src/formattedtext/unix_backend.rs @@ -8,33 +8,33 @@ use termion::style; use termion::clear; use termion::cursor; -fn format_map_ansi(c: char) -> Option { - Some(match c { - 'n' => { // Normal text +fn format_map_ansi(s: &str) -> Option { + Some(match s { + "n" => { // Normal text format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) }, - 'i' => { // Normal italic text + "i" => { // Normal italic text format!("{}{}", color::Fg(color::Reset), color::Bg(color::Reset)) }, - 't' => { // Title text (should be cyan) + "t" => { // Title text (should be cyan) format!("{}{}", color::Fg(color::AnsiValue(6)), color::Bg(color::Reset)) }, - 'a' => { // Colored text (should be pink) + "a" => { // Colored text (should be pink) format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) }, - 'e' => { // Error titles (should be red) + "e" => { // Error titles (should be red) format!("{}{}", color::Fg(color::AnsiValue(1)), color::Bg(color::Reset)) }, - 'c' => { // Console text (inverted black on white) + "c" => { // Console text (inverted black on white) format!("{}{}", color::Fg(color::AnsiValue(0)), color::Bg(color::AnsiValue(7))) }, - 'p' => { // Input prompt (how ==> is styled) (should be blue) + "p" => { // Input prompt (how ==> is styled) (should be blue) format!("{}{}", color::Fg(color::AnsiValue(4)), color::Bg(color::Reset)) }, - 's' => { // Repeat prompt (how => is styled) (should be pink) + "s" => { // Repeat prompt (how => is styled) (should be pink) format!("{}{}", color::Fg(color::AnsiValue(5)), color::Bg(color::Reset)) }, - 'r' => { // Result prompt (how = is styled) (should be green) + "r" => { // Result prompt (how = is styled) (should be green) format!("{}{}", color::Fg(color::AnsiValue(2)), color::Bg(color::Reset)) }, @@ -44,11 +44,11 @@ fn format_map_ansi(c: char) -> Option { -fn format_map_none(c: char) -> Option { - Some(match c { - 'n'|'i'|'t'|'a'| - 'e'|'c'|'s'|'r'| - 'p' +fn format_map_none(s: &str) -> Option { + Some(match s { + "n"|"i"|"t"|"a"| + "e"|"c"|"s"|"r"| + "p" => { "".to_string() }, _ => { return None } }) @@ -56,33 +56,33 @@ fn format_map_none(c: char) -> Option { // style::reset also resets color. // Make sure color comes AFTER style reset. -fn format_map_full(c: char) -> Option { - Some(match c { - 'n' => { // Normal text +fn format_map_full(s: &str) -> Option { + Some(match s { + "n" => { // Normal text format!("{}{}", style::Reset, color::Fg(color::Reset)) }, - 'i' => { // Normal italic text + "i" => { // Normal italic text format!("{}{}", color::Fg(color::Reset), style::Italic) }, - 't' => { // Title text + "t" => { // Title text format!("{}{}", color::Fg(color::Magenta), style::Bold) }, - 'a' => { // Colored text + "a" => { // Colored text format!("{}{}", style::Reset, color::Fg(color::Magenta)) }, - 'e' => { // Error titles + "e" => { // Error titles format!("{}{}", color::Fg(color::Red), style::Bold) }, - 'c' => { // Console text + "c" => { // Console text format!("{}{}", color::Fg(color::LightBlack), style::Italic) }, - 'p' => { // Input prompt (how ==> is styled) + "p" => { // Input prompt (how ==> is styled) format!("{}{}", color::Fg(color::Blue), style::Bold) }, - 's' => { // Repeat prompt (how => is styled) + "s" => { // Repeat prompt (how => is styled) format!("{}{}", color::Fg(color::Magenta), style::Bold) }, - 'r' => { // Result prompt (how = is styled) + "r" => { // Result prompt (how = is styled) format!("{}{}", color::Fg(color::Green), style::Bold) }, @@ -97,68 +97,83 @@ impl FormattedText { return Ok(()); } - pub fn format_map(c: char, context: &Context) -> Option { + pub fn format_map(s: &str, context: &Context) -> Option { match context.config.term_color_type { - 0 => format_map_none(c), - 1 => format_map_ansi(c), - 2 => format_map_full(c), + 0 => format_map_none(s), + 1 => format_map_ansi(s), + 2 => format_map_full(s), _ => unreachable!("Invalid term_color_type") } } pub fn write(&self, context: &Context, stdout: &mut RawTerminal) -> Result<(), std::io::Error> { - if self.text == "[clear]" { - write!( - stdout, - "{}{}", - clear::All, - cursor::Goto(1, 1) - )?; - return Ok(()); - } - - - let mut s = String::new(); + let mut word = String::new(); + let mut reading = false; // are we reading a word? let mut chars = self.text.chars(); + let mut out = String::new(); while let Some(c) = chars.next() { + match c { '[' => { - let a = chars.next().unwrap(); + if reading { + // Discard old word, start reading again. + out.push_str(&word); + word.clear(); + } + + // Start reading a new word + reading = true; + word.push(c); + }, - // Treat double [[ as escaped [ - if a == '[' { s.push('['); } + ']' => { + if !reading { + out.push(c); + } else { + word.push(c); - let b = chars.next().unwrap(); - match (a, b) { - (c, ']') => { // Normal text + let f = Self::format_map(&word[1..word.len()-1], context); - let q = Self::format_map(c, context); - - if q.is_some() { - s.push_str(&q.unwrap()); - } else { - s.push('['); - s.push(a); - s.push(b); - } - }, - - _ => { - s.push('['); - s.push(a); - s.push(b); + if f.is_some() { + out.push_str(&f.unwrap()); + } else if word == "[clear]" { + out.push_str(&format!( + "{}{}", + clear::All, + cursor::Goto(1, 1) + )); + } else if word.starts_with("[cursorright") { + let n: u16 = word[12..word.len()-1].parse().unwrap(); + out.push_str(&format!( + "{}", + cursor::Right(n), + )); + } else { + out.push_str(&word); } + + reading = false; + word.clear(); } }, - '\n' => { s.push_str("\r\n") }, - _ => s.push(c) + + '\n' => { + if reading { word.push_str("\r\n"); } + else { out.push_str("\r\n"); } + }, + + _ => { + if reading { word.push(c); } + else { out.push(c); } + } } } - write!(stdout, "\r{}", s)?; + write!(stdout, "\r{}", out)?; + stdout.flush()?; return Ok(()); } } \ No newline at end of file diff --git a/src/formattedtext/wasm_backend.rs b/src/formattedtext/wasm_backend.rs index 8235705..61ac800 100644 --- a/src/formattedtext/wasm_backend.rs +++ b/src/formattedtext/wasm_backend.rs @@ -1,12 +1,80 @@ use super::FormattedText; -impl FormattedText { - pub fn newline() -> Result<(), ()> { - print!("\n"); - return Ok(()); - } +fn format_map(s: &str) -> Option { + Some(match s { + "n" => {"\x1B[0m"}, + "i" => {"\x1B[3m"}, + "t" => {"\x1B[1;35m"}, + "a" => {"\x1B[0;35m"}, + "e" => {"\x1B[1;31m"}, + "c" => {"\x1B[3;90m"}, + "p" => {"\x1B[1;34m"}, + "s" => {"\x1B[1;35m"}, + "r" => {"\x1B[1;32m"}, + _ => { return None } + }.to_string()) +} + +impl FormattedText { pub fn write(&self) -> String { - return self.text.clone(); + + let mut word = String::new(); + let mut reading = false; // are we reading a word? + let mut chars = self.text.chars(); + let mut out = String::new(); + + while let Some(c) = chars.next() { + + match c { + '[' => { + if reading { + // Discard old word, start reading again. + out.push_str(&word); + word.clear(); + } + + // Start reading a new word + reading = true; + word.push(c); + }, + + ']' => { + if !reading { + out.push(c); + } else { + word.push(c); + + let f = format_map(&word[1..word.len()-1]); + + if f.is_some() { + out.push_str(&f.unwrap()); + } else if word == "[clear]" { + out.push_str(&format!("\x1B[2J\x1B[H")); + } else if word.starts_with("[cursorright") { + let n: u16 = word[12..word.len()-1].parse().unwrap(); + out.push_str(&format!("\x1B[{n}C")); + } else { + out.push_str(&word); + } + + reading = false; + word.clear(); + } + }, + + '\n' => { + if reading { word.push_str("\r\n"); } + else { out.push_str("\r\n"); } + }, + + _ => { + if reading { word.push(c); } + else { out.push(c); } + } + } + } + + return out; } } \ No newline at end of file From cb9f69948c812abefce4d0542f86ef40bcf7505b Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:03:01 -0700 Subject: [PATCH 11/26] Migrate to new formattedtext & promptbuffer --- src/main.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index ac2c86d..640d9eb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -pub mod promptbuffer; - use std::io::stdout; use std::io::stdin; use std::env; @@ -11,11 +9,10 @@ use termion::{ color::DetectColors }; -use promptbuffer::PromptBuffer; +use daisycalc::PromptBuffer; use daisycalc::command; use daisycalc::Context; use daisycalc::FormattedText; - use daisycalc::do_string; #[cfg(test)] @@ -70,7 +67,8 @@ pub fn main() -> Result<(), std::io::Error> { 'outer: loop { - pb.write_prompt(&mut context, &mut stdout)?; + let t = pb.write_prompt(&mut context); + t.write(&context, &mut stdout)?; let stdin = stdin(); for c in stdin.keys() { @@ -79,7 +77,10 @@ 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 context, &mut stdout)?; + let t = pb.write_prompt_nocursor(&mut context); + t.write(&context, &mut stdout)?; + + let in_str = pb.enter(); FormattedText::newline(&mut stdout)?; if in_str == "" { break; } @@ -115,7 +116,8 @@ pub fn main() -> Result<(), std::io::Error> { }; }; - pb.write_prompt(&mut context, &mut stdout)?; + let t = pb.write_prompt(&mut context); + t.write(&context, &mut stdout)?; } } From 921bd4d7045e524f75a629cb5dc0e0b471ba1d4d Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:03:28 -0700 Subject: [PATCH 12/26] Added proper WASM api --- src/lib.rs | 84 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 10 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0737fc..ceed6cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,28 +10,92 @@ mod context; mod formattedtext; mod errors; mod evaluate; +mod promptbuffer; pub use crate::formattedtext::FormattedText; pub use crate::context::Context; pub use crate::errors::DaisyError; pub use crate::evaluate::evaluate; +pub use crate::promptbuffer::PromptBuffer; + cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { use wasm_bindgen::prelude::*; + #[derive(Debug)] + pub struct State { + pub context: Context, + pub promptbuffer: PromptBuffer + } + #[wasm_bindgen] - pub fn dostr(x: String) -> String{ - let mut context = Context::new(); - let x = x.trim().to_string(); - let r = do_string(&mut context, &x); - - match r { - Ok(t) | Err(t) => { - return t.write(); - } - } + pub extern fn daisy_init() -> *mut State { + Box::into_raw(Box::new(State { + context: Context::new(), + promptbuffer: PromptBuffer::new(64) + })) + } + + #[wasm_bindgen] + pub extern fn daisy_free(state: *mut State) { + unsafe { drop(Box::from_raw(state)) }; + } + + #[wasm_bindgen] + pub fn daisy_prompt(state: *mut State) -> String { + let t = unsafe { (*state).promptbuffer.write_prompt(&mut (*state).context) }; + return t.write(); + } + + + #[wasm_bindgen] + pub fn daisy_char(state: *mut State, s: String) -> String { + let mut out = FormattedText::new("".to_string()); + + match &s[..] { + "\r" => { + // Print again without cursor, in case we pressed enter + // while inside a substitution + let t = unsafe { (*state).promptbuffer.write_prompt_nocursor(&mut (*state).context) }; + out += t; + + + let in_str = unsafe { (*state).promptbuffer.enter() }; + out += FormattedText::new("\n".to_string()); + if in_str == "" { + return format!("\r\n{}", daisy_prompt(state)); + } + + if in_str.trim() == "quit" { + return "[quit]".to_string(); + } else { + let r = crate::do_string( unsafe { &mut (*state).context }, &in_str); + + match r { + Ok(t) | Err(t) => { + out += t; + } + } + } + }, + + "\x7F" => { unsafe { (*state).promptbuffer.backspace(); } }, + "\x1B[3~" => { unsafe { (*state).promptbuffer.delete(); } }, + "\x1B[D" => { unsafe { (*state).promptbuffer.cursor_left(); } }, + "\x1B[C" => { unsafe { (*state).promptbuffer.cursor_right(); } }, + "\x1B[A" => { unsafe { (*state).promptbuffer.hist_up(); } }, + "\x1B[B" => { unsafe { (*state).promptbuffer.hist_down(); } }, + + //'\x04' | '\x03' + //=> { break 'outer; }, + + _ => { unsafe { (*state).promptbuffer.add_char(s.chars().next().unwrap()); } }, + }; + + let t = unsafe { (*state).promptbuffer.write_prompt(&mut (*state).context) }; + return (out + t).write(); } } } From f9382616b211b2f255d3c14d8e1dbbdd91405828 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:03:44 -0700 Subject: [PATCH 13/26] Cleaned up art --- misc/banner.svg | 137 ++++++++++++++++++++++++ misc/daisy-dark.svg | 137 ++++++++++++++++++++++++ misc/daisy-light.svg | 137 ++++++++++++++++++++++++ misc/daisy.svg | 240 ------------------------------------------- 4 files changed, 411 insertions(+), 240 deletions(-) create mode 100644 misc/banner.svg create mode 100644 misc/daisy-dark.svg create mode 100644 misc/daisy-light.svg delete mode 100644 misc/daisy.svg diff --git a/misc/banner.svg b/misc/banner.svg new file mode 100644 index 0000000..efa19b7 --- /dev/null +++ b/misc/banner.svg @@ -0,0 +1,137 @@ + + + + diff --git a/misc/daisy-dark.svg b/misc/daisy-dark.svg new file mode 100644 index 0000000..2eb030b --- /dev/null +++ b/misc/daisy-dark.svg @@ -0,0 +1,137 @@ + + + + diff --git a/misc/daisy-light.svg b/misc/daisy-light.svg new file mode 100644 index 0000000..a4610fb --- /dev/null +++ b/misc/daisy-light.svg @@ -0,0 +1,137 @@ + + + + diff --git a/misc/daisy.svg b/misc/daisy.svg deleted file mode 100644 index 4fb6cc0..0000000 --- a/misc/daisy.svg +++ /dev/null @@ -1,240 +0,0 @@ - - - -DaisyDaisyDaisy From 69ba2ab715596a50cd3019d899beb726e904873a Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:06:13 -0700 Subject: [PATCH 14/26] Improved WASM webpage --- index.html | 148 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 130 insertions(+), 18 deletions(-) diff --git a/index.html b/index.html index e6acdaa..21422db 100644 --- a/index.html +++ b/index.html @@ -1,37 +1,149 @@ + + + + - -
-
-
- $ - -
+ + +
+ + - From 9f9cc5d084760a4ecefce56df4796f5ddbad88a7 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:35:59 -0700 Subject: [PATCH 15/26] Added character limits --- src/main.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8907f3f..0ec0960 100644 --- a/src/main.rs +++ b/src/main.rs @@ -98,7 +98,15 @@ pub fn main() -> Result<(), std::io::Error> { break; }, - _ => { pb.add_char(*q); } + + // Only process sane characters + 'a'..='z' | 'A'..='Z' | '0'..='9' + |'!'|'@'|'#'|'$'|'%'|'^'|'&'|'*'|'('|')' + |'?'|'~'|','|'.'|'['|']' + |'<'|'>'|'/'|'_'|'-'|':'|'|'|'='|'+'|';' + => { pb.add_char(*q); }, + + _ => {} }; } else { match c.unwrap() { From 599c9742d21b2f4a2503698af08063313a89c0a2 Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 12:50:16 -0700 Subject: [PATCH 16/26] Added a few more commands and arguments --- src/command/mod.rs | 22 ++++++++++++++++++++++ src/main.rs | 27 ++++++++++++++++++++++----- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/command/mod.rs b/src/command/mod.rs index ebc774c..7426a67 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -16,6 +16,7 @@ pub fn is_command( | "vars" | "consts" | "constants" | "del" | "delete" + | "flags" => true, _ => false } @@ -81,6 +82,27 @@ pub fn do_command( return t; }, + "flags" => { + return FormattedText::new( + concat!( + "\n", + "A list of command-line arguments is below\n", + "\n", + "╞════ [t]Flag[n] ════╪════════════════ [t]Function[n] ════════════════╡\n", + " [c]--help[n] Show help\n", + " [c]--version[n] Show version\n", + " [c]--info[n] Show system information\n", + " [c]--256color[n] Use full color support (default)\n", + " [c]--8color[n] Use reduced colors (ANSI, no styling)\n", + " [c]--nocolor[n] Do not use colors\n", + " [c]--nosub[n] Disable inline substitution\n", + " [c]--nosuper[n] Disable superscript powers\n", + " [c]--nooneover[n] Disable \"one-over\" fractions as -1 power\n", + "\n\n" + ).to_string() + ); + }, + "clear" => { return FormattedText::new("[clear]".to_string()); }, diff --git a/src/main.rs b/src/main.rs index 0ec0960..24747aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -29,7 +29,9 @@ pub fn main() -> Result<(), std::io::Error> { let mut pb: PromptBuffer = PromptBuffer::new(64); let mut context = Context::new(); - // Set color compatibilty + // Detect color compatibilty + // Currently unused, this is slow. + /* let term_colors = stdout.available_colors().unwrap_or(0); if term_colors >= 256 { context.config.term_color_type = 2; @@ -38,9 +40,7 @@ pub fn main() -> Result<(), std::io::Error> { } else { context.config.term_color_type = 0; } - - context.config.check(); - + */ // Handle command-line arguments @@ -48,6 +48,8 @@ pub fn main() -> Result<(), std::io::Error> { if args.iter().any(|s| s == "--help") { let t = command::do_command(&mut context, &String::from("help")); t.write(&context, &mut stdout)?; + let t = command::do_command(&mut context, &String::from("flags")); + t.write(&context, &mut stdout)?; return Ok(()); } else if args.iter().any(|s| s == "--version") { let t = FormattedText::new(format!( @@ -62,12 +64,27 @@ pub fn main() -> Result<(), std::io::Error> { "Your terminal supports {} colors.\n" ), env!("CARGO_PKG_VERSION"), - term_colors + stdout.available_colors().unwrap_or(0) )); t.write(&context, &mut stdout)?; return Ok(()); + } else if args.iter().any(|s| s == "--256color") { + context.config.term_color_type = 2; + } else if args.iter().any(|s| s == "--8color") { + context.config.term_color_type = 1; + } else if args.iter().any(|s| s == "--0color") { + context.config.term_color_type = 0; + } else if args.iter().any(|s| s == "--nosub") { + context.config.enable_substituion = false; + } else if args.iter().any(|s| s == "--nosuper") { + context.config.enable_super_powers = false; + } else if args.iter().any(|s| s == "--nooneover") { + context.config.enable_one_over_power = false; } + context.config.check(); + + 'outer: loop { pb.write_prompt(&mut context, &mut stdout)?; From 161f18443733fde1e68537124d6df3333875c0ba Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 13:53:46 -0700 Subject: [PATCH 17/26] Updated help text --- src/command/mod.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/command/mod.rs b/src/command/mod.rs index 7426a67..39a0096 100644 --- a/src/command/mod.rs +++ b/src/command/mod.rs @@ -67,6 +67,7 @@ pub fn do_command( "\n", "╞═══════════════ [t]Commands[n] ═══════════════╡\n", " [c]help[n] Show this help\n", + " [c]flags[n] Show command-line options\n", " [c]clear[n] Clear the terminal\n", " [c]quit[n] Exit daisy\n", //" [c]units[n] List available units\n", @@ -94,7 +95,7 @@ pub fn do_command( " [c]--info[n] Show system information\n", " [c]--256color[n] Use full color support (default)\n", " [c]--8color[n] Use reduced colors (ANSI, no styling)\n", - " [c]--nocolor[n] Do not use colors\n", + " [c]--nocolor[n] Do not use colors and styling\n", " [c]--nosub[n] Disable inline substitution\n", " [c]--nosuper[n] Disable superscript powers\n", " [c]--nooneover[n] Disable \"one-over\" fractions as -1 power\n", From a70fd5f0e5d2adaf3a4a92fe89caf9838bb5c29f Mon Sep 17 00:00:00 2001 From: Mark Date: Thu, 21 Sep 2023 14:07:21 -0700 Subject: [PATCH 18/26] Cleaned up site files --- .gitignore | 3 +-- README.md | 2 +- site/.gitignore | 3 +++ index.html => site/index.html | 5 ++--- site/package.json | 5 +++++ ...ns Mono Regular Nerd Font Complete Mono.ttf | Bin 0 -> 972172 bytes {misc => site/resources}/banner.svg | 0 {misc => site/resources}/daisy-dark.svg | 0 {misc => site/resources}/daisy-light.svg | 0 .../resources/readme-banner.png | Bin 10 files changed, 12 insertions(+), 6 deletions(-) create mode 100644 site/.gitignore rename index.html => site/index.html (93%) create mode 100644 site/package.json create mode 100755 site/resources/Fantasque Sans Mono Regular Nerd Font Complete Mono.ttf rename {misc => site/resources}/banner.svg (100%) rename {misc => site/resources}/daisy-dark.svg (100%) rename {misc => site/resources}/daisy-light.svg (100%) rename misc/banner.png => site/resources/readme-banner.png (100%) diff --git a/.gitignore b/.gitignore index ee19741..5cdd539 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ /target /src/target -/pkg -*.pkg.* \ No newline at end of file +/pkg \ No newline at end of file diff --git a/README.md b/README.md index fe53747..053efbc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![](./misc/banner.png) +![](./site/misc/readme-banner.png) A high-precision scientific calculator with support for units, derivatives, and more. diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..6bd0e57 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,3 @@ +/node_modules +/package-lock.json +/pkg \ No newline at end of file diff --git a/index.html b/site/index.html similarity index 93% rename from index.html rename to site/index.html index 21422db..1714f03 100644 --- a/index.html +++ b/site/index.html @@ -2,12 +2,11 @@ -