Compare commits

..

5 Commits

Author SHA1 Message Date
Mark 09996801d8
Updated TODO 2023-08-17 10:32:05 -07:00
Mark da1507e1fc
Version bump 2023-08-17 10:11:28 -07:00
Mark 7c54c1eb91
Updated TODO 2023-08-17 10:10:43 -07:00
Mark e0ca8be79f
Added terminal color detection
Added configuration
Cleaned up context args
2023-08-17 10:10:38 -07:00
Mark fc4c28543f
Updated README 2023-08-16 22:44:54 -07:00
15 changed files with 288 additions and 113 deletions

2
Cargo.lock generated
View File

@ -28,7 +28,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "daisycalc"
version = "1.0.0"
version = "1.0.1"
dependencies = [
"cfg-if",
"rug",

View File

@ -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"

View File

@ -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

11
TODO.md
View File

@ -2,9 +2,9 @@
- update Cargo.toml
- run cargo test
- commit
- git tag -a v1.0.0 -m "Version 1.0.0"
- git push
- git push origin v1.0.0
- push
- merge
- git tag -a v1.0.0 -m "Version 1.0.0" on merge commit
- cargo publish
- Update packages
@ -27,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

View File

@ -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),
));
}
}

View File

@ -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<Expression>,
variables: HashMap<String, Expression>,
functions: HashMap<String, (Vec<String>, 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(),

View File

@ -1,4 +1,3 @@
use std::io::Write;
use std::io::stdout;
use std::io::stdin;
use std::env;
@ -6,19 +5,33 @@ 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
@ -28,7 +41,21 @@ pub fn main() -> Result<(), std::io::Error> {
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(());
}
@ -45,7 +72,7 @@ pub fn main() -> Result<(), std::io::Error> {
// while inside a substitution
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" {
@ -83,6 +110,6 @@ pub fn main() -> Result<(), std::io::Error> {
}
}
write!(stdout, "\r\n")?;
FormattedText::newline(&mut stdout)?;
return Ok(());
}

View File

@ -78,8 +78,8 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp
return Err((
*la + *lb + *op_loc,
DaisyError::IncompatibleUnits(
a.convert_to_base().unit.to_string(),
b.convert_to_base().unit.to_string()
a.convert_to_base().unit.display(context),
b.convert_to_base().unit.display(context)
)
));
}
@ -101,8 +101,8 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp
return Err((
*la + *lb + *op_loc,
DaisyError::IncompatibleUnits(
a.convert_to_base().unit.to_string(),
b.convert_to_base().unit.to_string()
a.convert_to_base().unit.display(context),
b.convert_to_base().unit.display(context)
)
));
}
@ -179,8 +179,8 @@ pub fn eval_operator(context: &mut Context, g: &Expression) -> Result<Option<Exp
return Err((
*la + *lb + *op_loc,
DaisyError::IncompatibleUnits(
va.convert_to_base().unit.to_string(),
vb.convert_to_base().unit.to_string()
va.convert_to_base().unit.display(context),
vb.convert_to_base().unit.display(context)
)
));
}

View File

@ -5,6 +5,7 @@ use termion::style;
use termion::clear;
use termion::cursor;
use std::ops::Add;
use crate::context::Context;
#[derive(Debug)]
@ -18,8 +19,92 @@ impl ToString for FormattedText {
}
impl FormattedText {
fn format_map_none(c: char) -> Option<String> {
Some(match c {
'n'|'i'|'t'|'a'|
'e'|'c'|'s'|'r'
=> { "".to_string() },
_ => { return None }
})
}
fn format_map_ansi(c: char) -> Option<String> {
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<String> {
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<std::io::Stdout>) -> Result<(), std::io::Error> {
write!(
stdout,
"\r\n",
)?;
return Ok(());
}
}
impl FormattedText {
pub fn new(s: String) -> FormattedText {
return FormattedText {
text: s
@ -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);
}
},
_ => {

View File

@ -85,13 +85,13 @@ fn do_expression(
// 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));
@ -224,7 +224,7 @@ 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
@ -266,7 +266,7 @@ fn do_assignment(
// Display parsed string
output.push(&format!(
" [t]=>[n] {left} = {}\n\n",
g.to_string()
g.display(context)
));
// Evaluate expression

View File

@ -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<Expression>),
}
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::<Vec<String>>()
.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::<Vec<String>>()
.join(", ")
)

View File

@ -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<Expression>) -> String {
pub fn display(&self, context: &Context, args: &VecDeque<Expression>) -> 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])
);
},
@ -208,13 +208,13 @@ impl Operator {
let q = &args[1];
if {
//context.config.enable_super_powers &&
context.config.enable_super_powers &&
q.is_unitless_integer() &&
!q.to_string().contains("e")
!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' => '⁰',
@ -233,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])
);
},
@ -283,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)
);
}
},
@ -313,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));
}
};
}

View File

@ -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 {

View File

@ -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<FreeUnit, Scalar>
}
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; }

View File

@ -10,8 +10,10 @@ fn eval_to_str(s: &str) -> Result<String, ()> {
};
//let out_str = g.print();
return match evaluate(&mut Context::new(), &g) {
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(())
};
}