Added formattedtext and better error class

pull/2/head
Mark 2023-08-03 22:13:38 -07:00
parent 8076990a41
commit e67a5c4696
Signed by: Mark
GPG Key ID: AD62BB059C2AAEE4
14 changed files with 522 additions and 532 deletions

View File

@ -1,15 +1,7 @@
use std::io::Write;
use crate::context::Context; use crate::context::Context;
use crate::parser::Constant; use crate::parser::Constant;
use crate::parser::substitute; use crate::parser::substitute;
use crate::formattedtext::FormattedText;
use termion::{
raw::RawTerminal,
color,
style,
clear,
cursor
};
pub fn is_command( pub fn is_command(
s: &String s: &String
@ -30,159 +22,132 @@ pub fn is_command(
} }
#[inline(always)] #[inline(always)]
fn draw_greeter(stdout: &mut RawTerminal<std::io::Stdout>) -> Result<(), std::io::Error> { fn greeter() -> FormattedText {
write!( return FormattedText::new(
stdout, format!(
concat!( concat!(
"{a} ###### {b} @@@@@@\r\n", "[a] ###### [n] @@@@@@\r\n",
"{a} # ##{b}@@ @\r\n", "[a] # ##[n]@@ @\r\n",
"{a} ## #{b}@ @@\r\n", "[a] ## #[n]@ @@\r\n",
"{a} {b}@@@@@@@@@@@@@{a}\r\n", "[a] [n]@@@@@@@@@@@@@[a]\r\n",
"{b} @@ @{a}# ##\r\n", "[n] @@ @[a]# ##\r\n",
"{b} @ @@{a}## #\r\n", "[n] @ @@[a]## #\r\n",
"{b} @@@@@@ {a} ###### {r}\r\n", "[n] @@@@@@ [a] ###### [n]\r\n",
" {t}Daisy{r} {v}v{ver}{r}\r\n", " [t]Daisy[n] [i]v{ver}[n]\r\n",
"\n" "\n"
), ),
r = format!("{}{}", color::Fg(color::Reset), style::Reset), ver = env!("CARGO_PKG_VERSION")
a = color::Fg(color::Magenta), )
b = color::Fg(color::White), );
t = format!("{}{}", color::Fg(color::White), style::Bold),
v = format!("{}{}", color::Fg(color::White), style::Italic),
ver = env!("CARGO_PKG_VERSION"),
)?;
return Ok(());
} }
#[inline(always)] #[inline(always)]
pub fn do_command( pub fn do_command(
stdout: &mut RawTerminal<std::io::Stdout>,
s: &String, s: &String,
context: &mut Context context: &mut Context
) -> Result<(), std::io::Error> { ) -> FormattedText {
let args: Vec<&str> = s.split(" ").collect(); let args: Vec<&str> = s.split(" ").collect();
let first = args[0]; let first = args[0];
match first { match first {
"help" => { "help" => {
draw_greeter(stdout)?; let mut t = greeter();
write!(stdout, t.push(
concat!( concat!(
"Daisy is a high-precision, general-purpose\r\n", "Daisy is a high-precision, general-purpose\r\n",
"scientific calculator.\r\n", "scientific calculator.\r\n",
"\n", "\n",
" - Use Up/Down arrows to navigate history.\r\n", " - Use Up/Down arrows to navigate history.\r\n",
" - Use Ctrl-C or Ctrl-D to quit.\r\n", " - Use Ctrl-C or Ctrl-D to quit.\r\n",
" - Use {c}ans{r} to reference the last result.\r\n", " - Use [c]ans[n] to reference the last result.\r\n",
" - Use {c}var = 1337{r} to define varibles.\r\n", " - Use [c]var = 1337[n] to define varibles.\r\n",
"\n", "\n",
"╞═══════════════ {t}Commands{r} ═══════════════╡\r\n", "╞═══════════════ [t]Commands[n] ═══════════════╡\r\n",
" {c}help{r} Show this help\r\n", " [c]help[n] Show this help\r\n",
" {c}clear{r} Clear the terminal\r\n", " [c]clear[n] Clear the terminal\r\n",
" {c}quit{r} Exit daisy\r\n", " [c]quit[n] Exit daisy\r\n",
//" {c}units{r} List available units\r\n", //" [c]units[n] List available units\r\n",
" {c}consts{r} List built-in constants\r\n", " [c]consts[n] List built-in constants\r\n",
" {c}ops{r} List built-in operators\r\n", " [c]ops[n] List built-in operators\r\n",
" {c}fns{r} List built-in functions\r\n", " [c]fns[n] List built-in functions\r\n",
" {c}vars{r} List user-defined variables\r\n", " [c]vars[n] List user-defined variables\r\n",
" {c}del{r} Delete a variable\r\n", " [c]del[n] Delete a variable\r\n",
"\n\n", "\n\n",
), )
);
r = format!("{}{}", color::Fg(color::Reset), style::Reset), return t;
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic),
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
)?;
}, },
"clear" => { "clear" => {
write!( return FormattedText::new("[clear]".to_string());
stdout,
"{}{}",
clear::All,
cursor::Goto(1, 1)
)?;
}, },
"ops" | "operators" => { "ops" | "operators" => {
write!(stdout, return FormattedText::new(
concat!( concat!(
"\r\n", "\r\n",
"Operators, sorted by priority (high to low).\r\n", "Operators, sorted by priority (high to low).\r\n",
"High-piority operators are applied first.\r\n\n", "High-piority operators are applied first.\r\n\n",
"╞═════ {t}Operator{r} ═════╪═════ {t}Syntax{r} ═════╡\r\n", "╞═════ [t]Operator[n] ═════╪═════ [t]Syntax[n] ═════╡\r\n",
" function {c}sin, cos, etc{r}\r\n", " function [c]sin, cos, etc[n]\r\n",
" factorial {c}!{r}\r\n", " factorial [c]![n]\r\n",
" powers {c}^, **{r}\r\n", " powers [c]^, **[n]\r\n",
" implicit multiply {c}3π, 3(2+1), etc{r}\r\n", " implicit multiply [c]3π, 3(2+1), etc[n]\r\n",
" square root {c}sqrt, rt, √{r}\r\n", " square root [c]sqrt, rt, √[n]\r\n",
" negate {c}-3, -(1 + 2){r}\r\n", " negate [c]-3, -(1 + 2)[n]\r\n",
" modulo (short) {c}%{r}\r\n", " modulo (short) [c]%[n]\r\n",
" multiply, divide {c}*, /, ×, ÷{r}\r\n", " multiply, divide [c]*, /, ×, ÷[n]\r\n",
" add, subtract {c}+, -{r}\r\n", " add, subtract [c]+, -[n]\r\n",
" unit conversion {c}to{r}\r\n", " unit conversion [c]to[n]\r\n",
" division (long) {c}per{r}\r\n", " division (long) [c]per[n]\r\n",
" modulo (long) {c}mod{r}\r\n", " modulo (long) [c]mod[n]\r\n",
"\n\n" "\n\n"
), ).to_string()
);
r = format!("{}{}", color::Fg(color::Reset), style::Reset),
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic),
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
)?;
}, },
"fns" | "functions" => { "fns" | "functions" => {
write!(stdout, return FormattedText::new(
concat!( concat!(
"\r\n╞═══════ {t}Function{r} ═══════╪══════ {t}Syntax{r} ══════╡\r\n", "\r\n╞═══════ [t]Function[n] ═══════╪══════ [t]Syntax[n] ══════╡\r\n",
" absolute value {c}abs{r}\r\n", " absolute value [c]abs[n]\r\n",
" floor, ceiling, round {c}floor, ceil, round{r}\r\n", " floor, ceiling, round [c]floor, ceil, round[n]\r\n",
" log base e {c}ln{r}\r\n", " log base e [c]ln[n]\r\n",
" log base 10 {c}log{r}\r\n", " log base 10 [c]log[n]\r\n",
" sin, arcsin, cosecant {c}sin, asin, csc{r}\r\n", " sin, arcsin, cosecant [c]sin, asin, csc[n]\r\n",
" cos, arccos, secant {c}cos, acos, secant{r}\r\n", " cos, arccos, secant [c]cos, acos, secant[n]\r\n",
" tan, arctan, cotan {c}tan, atan, cot{r}\r\n", " tan, arctan, cotan [c]tan, atan, cot[n]\r\n",
" hyperbolic sin, etc {c}sinh, asinh, csch{r}\r\n", " hyperbolic sin, etc [c]sinh, asinh, csch[n]\r\n",
" hyperbolic cos, etc {c}cosh, acosh, sech{r}\r\n", " hyperbolic cos, etc [c]cosh, acosh, sech[n]\r\n",
" hyperbolic tan, etc {c}tanh, atanh, coth{r}\r\n", " hyperbolic tan, etc [c]tanh, atanh, coth[n]\r\n",
"\n", "\n",
" Celsius to Kelvin {c}fromC, fromCelsius{r}\r\n", " Celsius to Kelvin [c]fromC, fromCelsius[n]\r\n",
" Kelvin to Celsius {c}toC, toCelsius{r}\r\n", " Kelvin to Celsius [c]toC, toCelsius[n]\r\n",
" Fahrenheit to Kelvin {c}fromF, fromFahrenheit{r}\r\n", " Fahrenheit to Kelvin [c]fromF, fromFahrenheit[n]\r\n",
" Kelvin to Fahrenheit {c}toF, toFahrenheit{r}\r\n", " Kelvin to Fahrenheit [c]toF, toFahrenheit[n]\r\n",
"\n", "\n",
" convert to base unit {c}tobase{r}\r\n", " convert to base unit [c]tobase[n]\r\n",
" remove units {c}nounit{r}\r\n", " remove units [c]nounit[n]\r\n",
"\n\n" "\n\n"
), ).to_string()
);
r = format!("{}{}", color::Fg(color::Reset), style::Reset),
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic),
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
)?;
}, },
"vars" => { "vars" => {
let v = context.get_variables(); let v = context.get_variables();
if v.len() == 0 { if v.len() == 0 {
write!(stdout, return FormattedText::new(
"You have not defined any variables.\r\n\n", "You have not defined any variables\r\n\n".to_string()
)?; );
return Ok(());
} }
write!(stdout, let mut t = FormattedText::new(
"\r\n╞═══ {t}User-Defined Variables{r} ═══╡\r\n", "\r\n╞═══ [t]User-Defined Variables[n] ═══╡\r\n".to_string()
r = format!("{}{}", color::Fg(color::Reset), style::Reset), );
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
)?;
let mut longest = 0; let mut longest = 0;
for (key, _) in v { for (key, _) in v {
@ -194,30 +159,23 @@ pub fn do_command(
for (key, value) in v { for (key, value) in v {
let padding = " ".repeat(longest - key.len()); let padding = " ".repeat(longest - key.len());
write!(stdout, t.push(&format!(
concat!( " {key}{padding} = [c]{v}[n]\r\n",
" {k}{p} = {c}{v}{r}\r\n", v = value.to_string(),
), ));
k = key, v = value.to_string(),
p = padding,
r = format!("{}{}", color::Fg(color::Reset), style::Reset),
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic),
)?;
} }
write!(stdout, t.push("\r\n\n");
"\r\n\n", return t;
)?;
}, },
"consts" | "constants" => { "consts" | "constants" => {
let a = Constant::all_consts(); let a = Constant::all_consts();
write!(stdout, let mut t = FormattedText::new(
"\r\n╞═══ {t}Built-in Constants{r} ═══╡\r\n", "\r\n╞═══ [t]Built-in Constants[n] ═══╡\r\n".to_string()
r = format!("{}{}", color::Fg(color::Reset), style::Reset), );
t = format!("{}{}", color::Fg(color::Magenta), style::Bold)
)?;
for c in a { for c in a {
let Some(p) = c.pretty_name() else { continue }; let Some(p) = c.pretty_name() else { continue };
@ -226,66 +184,43 @@ pub fn do_command(
// your padding length is too short. // your padding length is too short.
let padding = " ".repeat(25 - p.chars().count()); let padding = " ".repeat(25 - p.chars().count());
write!(stdout, t.push(&format!(
" {n}{p}: {c}{s}{r}", " {p}{padding}: [c]{s}[n]",
p = padding,
n = p,
s = c.source_strings().join(", "), s = c.source_strings().join(", "),
));
r = format!("{}{}", color::Fg(color::Reset), style::Reset), t.push(&"\n");
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic),
)?;
write!(stdout, "\r\n")?;
} }
write!(stdout, t.push(&"\n\n");
"\r\n\n", return t;
)?;
}, },
"del" | "delete" => { "del" | "delete" => {
if args.len() != 2 { if args.len() != 2 {
write!(stdout, return FormattedText::new(
"{c}{cmd}{r} {t}takes exactly two arguments.{r}\r\n\n", format!(
cmd = first, "[c]{first}[n] [t]takes exactly two arguments.[n]\r\n\n",
r = format!("{}{}", color::Fg(color::Reset), style::Reset), )
t = format!("{}{}", color::Fg(color::Red), style::Bold), );
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic)
)?;
return Ok(());
} }
let v = args[1].to_string(); let v = args[1].to_string();
let v = substitute(&v); let v = substitute(&v);
let r = context.delete_variable(&v); let r = context.delete_variable(&v);
match r { return match r {
Ok(()) => { Ok(()) => { FormattedText::new("".to_string()) },
/*write!(stdout,
"Deleted variable {c}{v}{r}\r\n\n",
v = v,
r = format!("{}{}", color::Fg(color::Reset), style::Reset),
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic)
)?;*/
},
Err(()) => { Err(()) => {
write!(stdout, FormattedText::new(
"{c}{v}{r} {t}isn't a variable.{r}\r\n\n", format!(
v = v, "[c]{v}[n] [t]isn't a variable.[n]\r\n\n",
r = format!("{}{}", color::Fg(color::Reset), style::Reset), )
t = format!("{}{}", color::Fg(color::Red), style::Bold), )
c = format!("{}{}", color::Fg(color::LightBlack), style::Italic)
)?;
} }
} };
return Ok(());
}, },
_ => unreachable!("Bad command!") _ => unreachable!("Bad command!")
}; };
return Ok(());
} }

View File

@ -13,10 +13,13 @@ use termion::{
}; };
use super::promptbuffer::PromptBuffer; use super::promptbuffer::PromptBuffer;
use crate::errors::DaisyError;
use crate::formattedtext::FormattedText;
use crate::parser; use crate::parser;
use crate::command; use crate::command;
use crate::evaluate; use crate::evaluate;
use crate::context::Context; use crate::context::Context;
use crate::parser::LineLocation;
use crate::parser::substitute; use crate::parser::substitute;
@ -25,38 +28,21 @@ fn do_expression(
stdout: &mut RawTerminal<std::io::Stdout>, stdout: &mut RawTerminal<std::io::Stdout>,
s: &String, s: &String,
context: &mut Context context: &mut Context
) -> Result<parser::Expression, ()> { ) -> Result<parser::Expression, (LineLocation, DaisyError)> {
// Parse string
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::suspend_raw_mode(&stdout).unwrap(); RawTerminal::suspend_raw_mode(&stdout).unwrap();
let g = parser::parse(&s, context); let g = parser::parse(&s, context)?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::activate_raw_mode(&stdout).unwrap(); RawTerminal::activate_raw_mode(&stdout).unwrap();
// Check for parse errors // Evaluate expression
if let Err((l, e)) = g { #[cfg(debug_assertions)]
write!( RawTerminal::suspend_raw_mode(&stdout).unwrap();
stdout, "{}{}{}{}{}{}\r\n", let g_evaluated = evaluate::evaluate(&g, context, false)?;
color::Fg(color::Red), #[cfg(debug_assertions)]
style::Bold, RawTerminal::activate_raw_mode(&stdout).unwrap();
" ".repeat(l.pos + 4),
"^".repeat(l.len),
color::Fg(color::Reset),
style::Reset,
).unwrap();
write!(
stdout, " {}{}Parse Error: {}{}{}\r\n\n",
style::Bold,
color::Fg(color::Red),
style::Reset,
e.to_string(),
color::Fg(color::Reset),
).unwrap();
return Err(());
}
let Ok(g) = g else {panic!()};
// Display parsed string // Display parsed string
write!( write!(
@ -66,29 +52,20 @@ fn do_expression(
g.to_string() g.to_string()
).unwrap(); ).unwrap();
// Evaluate expression // Display result
#[cfg(debug_assertions)]
RawTerminal::suspend_raw_mode(&stdout).unwrap();
let g_evaluated = evaluate::evaluate(&g, context, false);
#[cfg(debug_assertions)]
RawTerminal::activate_raw_mode(&stdout).unwrap();
// Show output
if let Ok(q) = g_evaluated {
write!( write!(
stdout, "\n {}{}={} {}{}\r\n\n", stdout, "\n {}{}={} {}{}\r\n\n",
style::Bold, style::Bold,
color::Fg(color::Green), color::Fg(color::Green),
style::Reset, style::Reset,
q.to_string_outer(), g_evaluated.to_string_outer(),
color::Fg(color::Reset) color::Fg(color::Reset)
).unwrap(); ).unwrap();
return Ok(q);
} else { return Ok(g_evaluated);
match g_evaluated {
Ok(_) => unreachable!(),
/*
Err((l, e)) => { Err((l, e)) => {
// Display user input // Display user input
let s = substitute(&s); let s = substitute(&s);
@ -99,7 +76,6 @@ fn do_expression(
s s
).unwrap(); ).unwrap();
write!( write!(
stdout, "{}{}{}{}{}{}\r\n", stdout, "{}{}{}{}{}{}\r\n",
color::Fg(color::Red), color::Fg(color::Red),
@ -111,7 +87,7 @@ fn do_expression(
).unwrap(); ).unwrap();
write!( write!(
stdout, " {}{}Evaluation Error: {}{}{}\r\n\n", stdout, " {}{}Error: {}{}{}\r\n\n",
style::Bold, style::Bold,
color::Fg(color::Red), color::Fg(color::Red),
style::Reset, style::Reset,
@ -121,8 +97,7 @@ fn do_expression(
} }
} }
} }
*/
return Err(());
} }
@ -131,58 +106,35 @@ fn do_assignment(
stdout: &mut RawTerminal<std::io::Stdout>, stdout: &mut RawTerminal<std::io::Stdout>,
s: &String, s: &String,
context: &mut Context context: &mut Context
) { ) -> Result<(), (LineLocation, DaisyError)> {
let parts = s.split("=").collect::<Vec<&str>>(); let parts = s.split("=").collect::<Vec<&str>>();
if parts.len() != 2 { if parts.len() != 2 {
write!( return Err((
stdout, " {}{}Parse Error: {}Syntax{}\r\n\n", LineLocation::new_zero(),
style::Bold, DaisyError::Syntax
color::Fg(color::Red), ));
style::Reset,
color::Fg(color::Reset),
).unwrap();
return;
} }
let offset = parts[0].chars().count() + 1; //let offset = parts[0].chars().count() + 1;
let left = parts[0].trim().to_string(); let left = parts[0].trim().to_string();
let right = parts[1].trim().to_string(); let right = parts[1].trim().to_string();
let right = substitute(&right); let right = substitute(&right);
let left = substitute(&left); let left = substitute(&left);
if !context.valid_varible(&left) {
return Err((
LineLocation::new_zero(),
DaisyError::Syntax
));
}
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::suspend_raw_mode(&stdout).unwrap(); RawTerminal::suspend_raw_mode(&stdout).unwrap();
let g = parser::parse(&right, context); let g = parser::parse(&right, context)?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::activate_raw_mode(&stdout).unwrap(); RawTerminal::activate_raw_mode(&stdout).unwrap();
// Check for parse errors
if let Err((l, e)) = g {
write!(
stdout, "{}{}{}{}{}{}\r\n",
color::Fg(color::Red),
style::Bold,
" ".repeat(l.pos + offset + 4),
"^".repeat(l.len),
color::Fg(color::Reset),
style::Reset,
).unwrap();
write!(
stdout, " {}{}Parse Error: {}{}{}\r\n\n",
style::Bold,
color::Fg(color::Red),
style::Reset,
e.to_string(),
color::Fg(color::Reset),
).unwrap();
return;
}
let Ok(g) = g else {panic!()};
// Display parsed string // Display parsed string
write!( write!(
stdout, " {}{}=>{}{} {left} = {}\r\n\n", stdout, " {}{}=>{}{} {left} = {}\r\n\n",
@ -194,61 +146,15 @@ fn do_assignment(
// Evaluate expression // Evaluate expression
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::suspend_raw_mode(&stdout).unwrap(); RawTerminal::suspend_raw_mode(&stdout).unwrap();
let g_evaluated = evaluate::evaluate(&g, context, true); let g_evaluated = evaluate::evaluate(&g, context, false)?;
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
RawTerminal::activate_raw_mode(&stdout).unwrap(); RawTerminal::activate_raw_mode(&stdout).unwrap();
// Show output context.push_var(left.to_string(), g_evaluated).unwrap();
if let Ok(q) = g_evaluated { return Ok(());
let r = context.push_var(left.to_string(), q);
if r.is_err() {
write!(
stdout, " {}{}Definition failed: {}bad variable name{}\r\n\n",
style::Bold,
color::Fg(color::Red),
style::Reset,
color::Fg(color::Reset),
).unwrap();
} }
return;
} else {
match g_evaluated {
Ok(_) => unreachable!(),
Err((l, e)) => {
// Display user input
let s = substitute(&s);
write!(
stdout, "\n{}{}==>{}{} {}\r\n",
style::Bold, color::Fg(color::Red),
style::Reset, color::Fg(color::Reset),
s
).unwrap();
write!(
stdout, "{}{}{}{}{}{}\r\n",
color::Fg(color::Red),
style::Bold,
" ".repeat(l.pos + offset + 4),
"^".repeat(l.len),
color::Fg(color::Reset),
style::Reset,
).unwrap();
write!(
stdout, " {}{}Evaluation Error: {}{}{}\r\n\n",
style::Bold,
color::Fg(color::Red),
style::Reset,
e.to_string(),
color::Fg(color::Reset),
).unwrap();
}
}
}
}
#[inline(always)] #[inline(always)]
pub fn main() -> Result<(), std::io::Error> { pub fn main() -> Result<(), std::io::Error> {
let mut stdout = stdout().into_raw_mode().unwrap(); let mut stdout = stdout().into_raw_mode().unwrap();
@ -259,7 +165,8 @@ pub fn main() -> Result<(), std::io::Error> {
// Handle command-line arguments // Handle command-line arguments
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
if args.iter().any(|s| s == "--help") { if args.iter().any(|s| s == "--help") {
command::do_command(&mut stdout, &String::from("help"), &mut context)?; let t = command::do_command(&String::from("help"), &mut context);
t.write(&mut stdout)?;
return Ok(()); return Ok(());
} else if args.iter().any(|s| s == "--version") { } else if args.iter().any(|s| s == "--version") {
write!(stdout, "Daisy v{}\r\n", env!("CARGO_PKG_VERSION"))?; write!(stdout, "Daisy v{}\r\n", env!("CARGO_PKG_VERSION"))?;
@ -288,12 +195,47 @@ pub fn main() -> Result<(), std::io::Error> {
if in_str.trim() == "quit" { if in_str.trim() == "quit" {
break 'outer; break 'outer;
} else if command::is_command(&in_str) { } else if command::is_command(&in_str) {
command::do_command(&mut stdout, &in_str, &mut context)?; let t = command::do_command(&in_str, &mut context);
t.write(&mut stdout)?;
} else if in_str.contains("=") { } else if in_str.contains("=") {
do_assignment(&mut stdout, &in_str, &mut context); let r = do_assignment(&mut stdout, &in_str, &mut context);
if let Err((l, e)) = r {
let t = FormattedText::new(
format!(
concat!(
"{}[e]{}[n]\n",
" {}\n"
),
" ".repeat(l.pos + 4),
"^".repeat(l.len),
e.text().to_string(),
)
);
t.write(&mut stdout).unwrap();
}
} else { } else {
let r = do_expression(&mut stdout, &in_str, &mut context); let r = do_expression(&mut stdout, &in_str, &mut context);
if let Ok(t) = r { context.push_hist(t); } if let Ok(t) = r {
context.push_hist(t);
} else {
let Err((l, e)) = r else { unreachable!() };
let t = FormattedText::new(
format!(
concat!(
"{}[e]{}[n]\n",
" {}\n"
),
" ".repeat(l.pos + 4),
"^".repeat(l.len),
e.text().to_string(),
)
);
t.write(&mut stdout).unwrap();
}
} }
break; break;

95
src/errors.rs Normal file
View File

@ -0,0 +1,95 @@
use crate::formattedtext::FormattedText;
#[derive(Debug)]
pub enum DaisyError {
// Parser errors
//MissingCloseParen,
ExtraCloseParen,
EmptyGroup,
Syntax,
BadNumber,
// Evaluation errors
BadMath,
TooBig,
ZeroDivision,
IncompatibleUnit,
IncompatibleUnits(String, String),
Undefined(String),
EvaluationError,
BadArguments(String, usize, usize)
}
impl DaisyError {
pub fn text(&self) -> FormattedText {
match self {
//DaisyError::MissingCloseParen => {
// String::from("Missing close parenthesis")
//},
DaisyError::ExtraCloseParen => {
return FormattedText::new(
"[e]Syntax Error:[n] Extra close parenthesis".to_string()
);
},
DaisyError::EmptyGroup => {
return FormattedText::new(
"[e]Syntax Error:[n] Groups can't be empty".to_string()
);
},
DaisyError::Syntax => {
return FormattedText::new(
"[e]Syntax Error[n]".to_string()
);
},
DaisyError::BadNumber => {
return FormattedText::new(
"[e]Syntax Error:[n] Invalid number".to_string()
);
}
DaisyError::BadMath => {
return FormattedText::new(
"[e]Evaluation Error:[n] Failed to evaluate expression".to_string()
);
},
DaisyError::TooBig => {
return FormattedText::new(
"[e]Evaluation Error:[n] Number too big".to_string()
);
},
DaisyError::ZeroDivision => {
return FormattedText::new(
"[e]Evaluation Error:[n] Division by zero".to_string()
);
},
DaisyError::IncompatibleUnit => {
return FormattedText::new(
"[e]Evaluation Error:[n] Incompatible unit".to_string()
);
},
DaisyError::IncompatibleUnits(a, b) => {
return FormattedText::new(format!(
"[e]Evaluation Error:[n] Incompatible units ([c]{a}[n] and [c]{b}[n])"
));
},
DaisyError::Undefined(s) => {
return FormattedText::new(format!(
"[e]Evaluation Error:[n] [c]{s}[n] is not defined"
));
},
DaisyError::EvaluationError => {
return FormattedText::new(
"[e]Evaluation Error:[n] Could not evaluate".to_string()
);
},
DaisyError::BadArguments(s, want, got) => {
return FormattedText::new(format!(
"[e]Evaluation Error:[n] [c]{s}[n] takes {want} argument{}, got {got}",
if *want == 1 {""} else {"s"},
));
}
}
}
}

View File

@ -2,12 +2,20 @@ use crate::parser::Expression;
use crate::parser::Operator; use crate::parser::Operator;
use crate::context::Context; use crate::context::Context;
use crate::parser::LineLocation; use crate::parser::LineLocation;
use crate::errors::DaisyError;
use super::operator::eval_operator; use super::operator::eval_operator;
use super::function::eval_function; use super::function::eval_function;
use super::EvalError;
pub fn evaluate(t: &Expression, context: &mut Context, allow_incomplete: bool) -> Result<Expression, (LineLocation, EvalError)> {
pub fn evaluate(
t: &Expression,
context: &mut Context,
allow_incomplete: bool
) -> Result<
Expression,
(LineLocation, DaisyError)
> {
// Keeps track of our position in the expression tree. // Keeps track of our position in the expression tree.
// For example, the coordinates [0, 2, 1] are interpreted as follows: // For example, the coordinates [0, 2, 1] are interpreted as follows:
@ -54,7 +62,7 @@ pub fn evaluate(t: &Expression, context: &mut Context, allow_incomplete: bool) -
// Error if variable is undefined. // Error if variable is undefined.
// Comment this to allow floating varables. // Comment this to allow floating varables.
if v.is_none() { return Err((*l, EvalError::Undefined(s.clone()))); } if v.is_none() { return Err((*l, DaisyError::Undefined(s.clone()))); }
v v
}, },
@ -76,7 +84,7 @@ pub fn evaluate(t: &Expression, context: &mut Context, allow_incomplete: bool) -
if let Expression::Quantity(_, _) = g {} if let Expression::Quantity(_, _) = g {}
else { else {
let l = g.get_linelocation(); let l = g.get_linelocation();
return Err((l, EvalError::EvaluationError)) return Err((l, DaisyError::EvaluationError))
} }
} }

View File

@ -6,7 +6,7 @@ use crate::quantity::FreeUnit;
use crate::quantity::WholeUnit; use crate::quantity::WholeUnit;
use crate::quantity::Quantity; use crate::quantity::Quantity;
use crate::quantity::Scalar; use crate::quantity::Scalar;
use super::EvalError; use crate::errors::DaisyError;
// If unitless, do nothing // If unitless, do nothing
@ -26,7 +26,7 @@ fn to_radians(q: Quantity) -> Result<Quantity, ()> {
pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalError)> { pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, DaisyError)> {
let Expression::Operator(loc, Operator::Function(f), args) = g else {unreachable!()}; let Expression::Operator(loc, Operator::Function(f), args) = g else {unreachable!()};
@ -37,7 +37,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
if let Expression::Tuple(l, v) = a { if let Expression::Tuple(l, v) = a {
return Err(( return Err((
*l + *loc, *l + *loc,
EvalError::BadArguments(f.to_string(), 1, v.len()) DaisyError::BadArguments(f.to_string(), 1, v.len())
)) ))
}; };
@ -51,102 +51,102 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
Function::Abs => { Function::Abs => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.abs())); return Ok(Expression::Quantity(*loc + *l, q.abs()));
}, },
Function::Floor => { Function::Floor => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.floor())); return Ok(Expression::Quantity(*loc + *l, q.floor()));
}, },
Function::Ceil => { Function::Ceil => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.ceil())); return Ok(Expression::Quantity(*loc + *l, q.ceil()));
}, },
Function::Round => { Function::Round => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.round())); return Ok(Expression::Quantity(*loc + *l, q.round()));
}, },
Function::NaturalLog => { Function::NaturalLog => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.ln())); return Ok(Expression::Quantity(*loc + *l, q.ln()));
}, },
Function::TenLog => { Function::TenLog => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.log10())); return Ok(Expression::Quantity(*loc + *l, q.log10()));
}, },
Function::Sin => { Function::Sin => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.sin())); return Ok(Expression::Quantity(*loc + *l, q.sin()));
}, },
Function::Cos => { Function::Cos => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.cos())); return Ok(Expression::Quantity(*loc + *l, q.cos()));
}, },
Function::Tan => { Function::Tan => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.tan())); return Ok(Expression::Quantity(*loc + *l, q.tan()));
}, },
Function::Csc => { Function::Csc => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.csc())); return Ok(Expression::Quantity(*loc + *l, q.csc()));
}, },
Function::Sec => { Function::Sec => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.sec())); return Ok(Expression::Quantity(*loc + *l, q.sec()));
}, },
Function::Cot => { Function::Cot => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.cot())); return Ok(Expression::Quantity(*loc + *l, q.cot()));
}, },
Function::Sinh => { Function::Sinh => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.sinh())); return Ok(Expression::Quantity(*loc + *l, q.sinh()));
}, },
Function::Cosh => { Function::Cosh => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.cosh())); return Ok(Expression::Quantity(*loc + *l, q.cosh()));
}, },
Function::Tanh => { Function::Tanh => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.tanh())); return Ok(Expression::Quantity(*loc + *l, q.tanh()));
}, },
Function::Csch => { Function::Csch => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.csch())); return Ok(Expression::Quantity(*loc + *l, q.csch()));
}, },
Function::Sech => { Function::Sech => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.sech())); return Ok(Expression::Quantity(*loc + *l, q.sech()));
}, },
Function::Coth => { Function::Coth => {
let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, EvalError::IncompatibleUnit)); }; let Ok(q) = to_radians(q.clone()) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)); };
return Ok(Expression::Quantity(*loc + *l, q.coth())); return Ok(Expression::Quantity(*loc + *l, q.coth()));
}, },
Function::Asin => { Function::Asin => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.asin())); return Ok(Expression::Quantity(*loc + *l, q.asin()));
}, },
Function::Acos => { Function::Acos => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.acos())); return Ok(Expression::Quantity(*loc + *l, q.acos()));
}, },
Function::Atan => { Function::Atan => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.atan())); return Ok(Expression::Quantity(*loc + *l, q.atan()));
}, },
Function::Asinh => { Function::Asinh => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.asinh())); return Ok(Expression::Quantity(*loc + *l, q.asinh()));
}, },
Function::Acosh => { Function::Acosh => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.acosh())); return Ok(Expression::Quantity(*loc + *l, q.acosh()));
}, },
Function::Atanh => { Function::Atanh => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
return Ok(Expression::Quantity(*loc + *l, q.atanh())); return Ok(Expression::Quantity(*loc + *l, q.atanh()));
}, },
@ -155,7 +155,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
Function::ToCelsius => { Function::ToCelsius => {
let mut k = Quantity::new_rational(1f64).unwrap(); let mut k = Quantity::new_rational(1f64).unwrap();
k.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); k.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap());
let Some(q) = q.convert_to(k) else { return Err((*loc + *l, EvalError::IncompatibleUnit)) }; let Some(q) = q.convert_to(k) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)) };
let mut r = q.without_unit(); let mut r = q.without_unit();
r += Quantity::new_rational(-273.15f64).unwrap(); r += Quantity::new_rational(-273.15f64).unwrap();
@ -165,7 +165,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
Function::ToFahrenheit => { Function::ToFahrenheit => {
let mut k = Quantity::new_rational(1f64).unwrap(); let mut k = Quantity::new_rational(1f64).unwrap();
k.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap()); k.insert_unit(FreeUnit::from_whole(WholeUnit::Kelvin), Scalar::new_rational(1f64).unwrap());
let Some(q) = q.convert_to(k) else { return Err((*loc + *l, EvalError::IncompatibleUnit)) }; let Some(q) = q.convert_to(k) else { return Err((*loc + *l, DaisyError::IncompatibleUnit)) };
let mut r = q.without_unit(); let mut r = q.without_unit();
r *= Quantity::new_rational_from_frac(9i64, 5i64).unwrap(); r *= Quantity::new_rational_from_frac(9i64, 5i64).unwrap();
@ -175,7 +175,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
return Ok(Expression::Quantity(*loc + *l, r)); return Ok(Expression::Quantity(*loc + *l, r));
}, },
Function::FromCelsius => { Function::FromCelsius => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
let mut r = Quantity::new_rational(273.15f64).unwrap(); let mut r = Quantity::new_rational(273.15f64).unwrap();
r += q.clone(); r += q.clone();
@ -184,7 +184,7 @@ pub fn eval_function(g: &Expression) -> Result<Expression, (LineLocation, EvalEr
return Ok(Expression::Quantity(*loc + *l, r)); return Ok(Expression::Quantity(*loc + *l, r));
}, },
Function::FromFahrenheit => { Function::FromFahrenheit => {
if !q.unitless() { return Err((*loc + *l, EvalError::IncompatibleUnit));} if !q.unitless() { return Err((*loc + *l, DaisyError::IncompatibleUnit));}
let mut r = q.clone(); let mut r = q.clone();
r += Quantity::new_rational(459.67).unwrap(); r += Quantity::new_rational(459.67).unwrap();

View File

@ -3,49 +3,3 @@ mod function;
mod evaluate; mod evaluate;
pub use self::evaluate::evaluate; pub use self::evaluate::evaluate;
#[derive(Debug)]
pub enum EvalError {
BadMath,
TooBig,
ZeroDivision,
IncompatibleUnit,
IncompatibleUnits(String, String),
Undefined(String),
EvaluationError,
BadArguments(String, usize, usize)
}
impl ToString for EvalError {
fn to_string(&self) -> String {
match self {
EvalError::BadMath => {
String::from("Failed to evaluate expression")
},
EvalError::TooBig => {
String::from("Number too big")
},
EvalError::ZeroDivision => {
String::from("Division by zero")
},
EvalError::IncompatibleUnit => {
String::from("Incompatible unit")
},
EvalError::IncompatibleUnits(a, b) => {
format!("Incompatible units ({a} and {b})")
},
EvalError::Undefined(s) => {
format!("{s} is undefined")
},
EvalError::EvaluationError => {
String::from("Could not evaluate")
},
EvalError::BadArguments(s, want, got) => {
format!("{s} takes {want} argument{}, got {got}",
if *want == 1 {""} else {"s"},
)
}
}
}
}

View File

@ -3,11 +3,11 @@ use crate::parser::LineLocation;
use crate::quantity::Quantity; use crate::quantity::Quantity;
use crate::parser::Operator; use crate::parser::Operator;
use crate::parser::Expression; use crate::parser::Expression;
use super::EvalError;
use crate::context::Context; use crate::context::Context;
use crate::errors::DaisyError;
pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Expression>, (LineLocation, EvalError)> { pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Expression>, (LineLocation, DaisyError)> {
let Expression::Operator(op_loc, op, args) = g else {panic!()}; let Expression::Operator(op_loc, op, args) = g else {panic!()};
@ -49,44 +49,26 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
}, },
Operator::Add => { Operator::Add => {
let mut sum: Quantity; if args.len() != 2 { panic!() };
let mut loc: LineLocation; let a = &args[0];
if let Expression::Quantity(l, s) = &args[0] { let b = &args[1];
sum = s.clone();
loc = *l;
} else { return Ok(None); };
if let Expression::Quantity(la, a) = a {
// Flag that is set to true if we find incompatible units. if let Expression::Quantity(lb, b) = b {
// We don't stop right away because we need to add all linelocations if !a.unit.compatible_with(&b.unit) {
// to show a pretty error. return Err((
let mut incompatible_units = false; *la + *lb + *op_loc,
DaisyError::IncompatibleUnits(
let mut i: usize = 1; a.convert_to_base().unit.to_string(),
while i < args.len() { b.convert_to_base().unit.to_string()
let j = &args[i]; )
if let Expression::Quantity(l, v) = j { ));
}
if !sum.unit.compatible_with(&v.unit) { return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() + b.clone())));
incompatible_units = true; }
} }
if !incompatible_units { sum += v.clone(); }
loc += *l;
} else {
if incompatible_units {
return Err((loc + *op_loc, EvalError::IncompatibleUnit));
}
return Ok(None); return Ok(None);
}
i += 1;
}
if incompatible_units {
return Err((loc + *op_loc, EvalError::IncompatibleUnit));
}
return Ok(Some(Expression::Quantity(loc + *op_loc, sum)));
}, },
Operator::Subtract => { Operator::Subtract => {
@ -96,6 +78,15 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if let Expression::Quantity(la, a) = a { if let Expression::Quantity(la, a) = a {
if let Expression::Quantity(lb, b) = b { if let Expression::Quantity(lb, b) = b {
if !a.unit.compatible_with(&b.unit) {
return Err((
*la + *lb + *op_loc,
DaisyError::IncompatibleUnits(
a.convert_to_base().unit.to_string(),
b.convert_to_base().unit.to_string()
)
));
}
return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() - b.clone()))); return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() - b.clone())));
} }
} }
@ -112,7 +103,7 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if let Expression::Quantity(la, a) = a { if let Expression::Quantity(la, a) = a {
if let Expression::Quantity(lb, b) = b { if let Expression::Quantity(lb, b) = b {
if b.is_zero() { return Err((*la + *lb + *op_loc, EvalError::ZeroDivision)); } if b.is_zero() { return Err((*la + *lb + *op_loc, DaisyError::ZeroDivision)); }
return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() / b.clone()))); return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() / b.clone())));
} }
} }
@ -122,23 +113,17 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
Operator::ImplicitMultiply | Operator::ImplicitMultiply |
Operator::Multiply => { Operator::Multiply => {
let mut prod: Quantity; if args.len() != 2 { panic!() };
let mut loc: LineLocation; let a = &args[0];
if let Expression::Quantity(l, s) = &args[0] { let b = &args[1];
prod = s.clone();
loc = *l;
} else { return Ok(None); };
let mut i: usize = 1; if let Expression::Quantity(la, a) = a {
while i < args.len() { if let Expression::Quantity(lb, b) = b {
let j = &args[i]; return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, a.clone() * b.clone())));
if let Expression::Quantity(l, v) = j {
prod *= v.clone();
loc += *l;
} else { return Ok(None); }
i += 1;
} }
return Ok(Some(Expression::Quantity(loc + *op_loc, prod))); }
return Ok(None);
}, },
Operator::ModuloLong Operator::ModuloLong
@ -151,12 +136,12 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if let Expression::Quantity(lb, vb) = b { if let Expression::Quantity(lb, vb) = b {
if !(va.unitless() && vb.unitless()) { if !(va.unitless() && vb.unitless()) {
return Err((*la + *lb + *op_loc, EvalError::IncompatibleUnit)); return Err((*la + *lb + *op_loc, DaisyError::IncompatibleUnit));
} }
if vb <= &Quantity::new_rational(1f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } if vb <= &Quantity::new_rational(1f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); }
if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } if va.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); }
if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, EvalError::BadMath)); } if vb.fract() != Quantity::new_rational(0f64).unwrap() { return Err((*la + *lb + *op_loc, DaisyError::BadMath)); }
return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, va.clone() % vb.clone()))); return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, va.clone() % vb.clone())));
} else { return Ok(None); } } else { return Ok(None); }
@ -174,7 +159,7 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if n.is_none() { if n.is_none() {
return Err(( return Err((
*la + *lb + *op_loc, *la + *lb + *op_loc,
EvalError::IncompatibleUnits( DaisyError::IncompatibleUnits(
va.convert_to_base().unit.to_string(), va.convert_to_base().unit.to_string(),
vb.convert_to_base().unit.to_string() vb.convert_to_base().unit.to_string()
) )
@ -191,9 +176,9 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
let a = &args[0]; let a = &args[0];
if let Expression::Quantity(l, v) = a { if let Expression::Quantity(l, v) = a {
if v.is_negative() { return Err((*l + *op_loc, EvalError::BadMath)); } if v.is_negative() { return Err((*l + *op_loc, DaisyError::BadMath)); }
let p = v.pow(Quantity::new_rational_from_string("0.5").unwrap()); let p = v.pow(Quantity::new_rational_from_string("0.5").unwrap());
if p.is_nan() {return Err((*l + *op_loc, EvalError::BadMath));} if p.is_nan() {return Err((*l + *op_loc, DaisyError::BadMath));}
return Ok(Some(Expression::Quantity(*l, p))); return Ok(Some(Expression::Quantity(*l, p)));
} else { return Ok(None); } } else { return Ok(None); }
}, },
@ -207,15 +192,15 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if let Expression::Quantity(lb, vb) = b { if let Expression::Quantity(lb, vb) = b {
if !vb.unitless() { if !vb.unitless() {
return Err((*lb, EvalError::IncompatibleUnit)); return Err((*lb, DaisyError::IncompatibleUnit));
} }
if va.is_zero() && vb.is_negative() { if va.is_zero() && vb.is_negative() {
return Err((*la + *lb + *op_loc, EvalError::ZeroDivision)); return Err((*la + *lb + *op_loc, DaisyError::ZeroDivision));
} }
let p = va.pow(vb.clone()); let p = va.pow(vb.clone());
if p.is_nan() {return Err((*la + *lb + *op_loc, EvalError::BadMath));} if p.is_nan() {return Err((*la + *lb + *op_loc, DaisyError::BadMath));}
return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, p))); return Ok(Some(Expression::Quantity(*la + *lb + *op_loc, p)));
} else { return Ok(None); } } else { return Ok(None); }
} else { return Ok(None); } } else { return Ok(None); }
@ -228,11 +213,11 @@ pub fn eval_operator(g: &Expression, _context: &mut Context) -> Result<Option<Ex
if let Expression::Quantity(l, v) = args { if let Expression::Quantity(l, v) = args {
if !v.unitless() { if !v.unitless() {
return Err((*l + *op_loc, EvalError::IncompatibleUnit)); return Err((*l + *op_loc, DaisyError::IncompatibleUnit));
} }
if !v.fract().is_zero() { return Err((*l + *op_loc, EvalError::BadMath)); } if !v.fract().is_zero() { return Err((*l + *op_loc, DaisyError::BadMath)); }
if v > &Quantity::new_rational(50_000f64).unwrap() { return Err((*l + *op_loc, EvalError::TooBig)); } if v > &Quantity::new_rational(50_000f64).unwrap() { return Err((*l + *op_loc, DaisyError::TooBig)); }
let mut prod = Quantity::new_rational(1f64).unwrap(); let mut prod = Quantity::new_rational(1f64).unwrap();
let mut u = v.clone(); let mut u = v.clone();

104
src/formattedtext.rs Normal file
View File

@ -0,0 +1,104 @@
use std::io::Write;
use termion::raw::RawTerminal;
use termion::color;
use termion::style;
use termion::clear;
use termion::cursor;
use std::ops::Add;
#[derive(Debug)]
#[derive(Clone)]
pub struct FormattedText {
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);
}
pub fn write(&self, stdout: &mut RawTerminal<std::io::Stdout>) -> 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 chars = self.text.chars();
while let Some(c) = chars.next() {
match c {
'[' => {
let a = chars.next().unwrap();
// Handle double [[ as escaped [
if a == '[' { s.push('['); }
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));
},
_ => {
s.push('[');
s.push(a);
s.push(b);
}
}
},
'\n' => { s.push_str("\r\n") },
_ => s.push(c)
}
}
write!(stdout, "{}", 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));
}
}

View File

@ -3,6 +3,8 @@ pub mod command;
pub mod quantity; pub mod quantity;
pub mod evaluate; pub mod evaluate;
pub mod context; pub mod context;
pub mod errors;
pub mod formattedtext;
mod entry; mod entry;
use crate::entry::main_e; use crate::entry::main_e;

View File

@ -1,14 +1,10 @@
mod stage; mod stage;
mod token; mod token;
mod parsererror;
mod expression; mod expression;
mod linelocation; mod linelocation;
use self::{ use self::token::Token;
token::Token,
parsererror::ParserError,
};
pub use self::{ pub use self::{
expression::Expression, expression::Expression,
@ -19,11 +15,11 @@ pub use self::{
}; };
use crate::context::Context; use crate::context::Context;
use crate::errors::DaisyError;
pub fn parse( pub fn parse(
s: &String, context: &Context s: &String, context: &Context
) -> Result<Expression, (LineLocation, ParserError)> { ) -> Result<Expression, (LineLocation, DaisyError)> {
let expressions = stage::tokenize(s); let expressions = stage::tokenize(s);
let (_, expressions) = stage::find_subs(expressions); let (_, expressions) = stage::find_subs(expressions);
@ -33,7 +29,7 @@ pub fn parse(
return Ok(g); return Ok(g);
} }
pub fn parse_no_context(s: &String) -> Result<Expression, (LineLocation, ParserError)> { pub fn parse_no_context(s: &String) -> Result<Expression, (LineLocation, DaisyError)> {
parse(s, &Context::new()) parse(s, &Context::new())
} }

View File

@ -1,32 +0,0 @@
/// Types of parser errors.
/// If we cannot parse a string, one of these is returned.
#[derive(Debug)]
pub enum ParserError {
//MissingCloseParen,
ExtraCloseParen,
EmptyGroup,
Syntax,
BadNumber
}
impl ToString for ParserError {
fn to_string(&self) -> String {
match self {
//ParserError::MissingCloseParen => {
// String::from("This group is never closed")
//},
ParserError::ExtraCloseParen => {
String::from("Extra close parenthesis")
},
ParserError::EmptyGroup => {
String::from("Groups can't be empty")
},
ParserError::Syntax => {
String::from("Syntax")
},
ParserError::BadNumber => {
String::from("Invalid number")
}
}
}
}

View File

@ -3,14 +3,15 @@ use std::collections::VecDeque;
use super::super::{ use super::super::{
Token, Token,
LineLocation, LineLocation,
ParserError,
Operator Operator
}; };
use crate::errors::DaisyError;
fn lookback_signs( fn lookback_signs(
g: &mut VecDeque<Token> g: &mut VecDeque<Token>
) -> Result<(), (LineLocation, ParserError)> { ) -> Result<(), (LineLocation, DaisyError)> {
// Convert `-` operators to `neg` operators // Convert `-` operators to `neg` operators
// Delete `+`s that mean "positive" instead of "add" // Delete `+`s that mean "positive" instead of "add"
@ -99,7 +100,7 @@ fn lookback_signs(
// Inserts implicit operators // Inserts implicit operators
fn lookback( fn lookback(
g: &mut VecDeque<Token> g: &mut VecDeque<Token>
) -> Result<(), (LineLocation, ParserError)> { ) -> Result<(), (LineLocation, DaisyError)> {
lookback_signs(g)?; lookback_signs(g)?;
@ -178,7 +179,7 @@ fn lookback(
// The following are syntax errors // The following are syntax errors
(Token::Quantity(la,_), Token::Quantity(lb,_)) (Token::Quantity(la,_), Token::Quantity(lb,_))
=> { => {
return Err((*la + *lb, ParserError::Syntax)); return Err((*la + *lb, DaisyError::Syntax));
}, },
_ => {g.insert(i-1, b); g.insert(i-1, a);} _ => {g.insert(i-1, b); g.insert(i-1, a);}
} }
@ -195,7 +196,7 @@ pub fn groupify(
mut g: VecDeque<Token> mut g: VecDeque<Token>
) -> Result< ) -> Result<
Token, Token,
(LineLocation, ParserError) (LineLocation, DaisyError)
> { > {
let last_linelocation: LineLocation = *g.back().unwrap().get_line_location(); let last_linelocation: LineLocation = *g.back().unwrap().get_line_location();
@ -220,8 +221,8 @@ pub fn groupify(
Token::GroupEnd(l) => { Token::GroupEnd(l) => {
let l = *l_now + l; let l = *l_now + l;
if i_level == 0 { return Err((l, ParserError::ExtraCloseParen)) } if i_level == 0 { return Err((l, DaisyError::ExtraCloseParen)) }
if v_now.len() == 0 { return Err((l, ParserError::EmptyGroup)) } if v_now.len() == 0 { return Err((l, DaisyError::EmptyGroup)) }
i_level -= 1; i_level -= 1;
@ -243,7 +244,7 @@ pub fn groupify(
// Error on missing parenthesis // Error on missing parenthesis
if levels.len() != 1 { if levels.len() != 1 {
let (l, _) = levels.pop().unwrap(); let (l, _) = levels.pop().unwrap();
return Err((l, ParserError::MissingCloseParen)) return Err((l, DaisyError::MissingCloseParen))
} }
*/ */
@ -252,7 +253,7 @@ pub fn groupify(
let (l, mut v) = levels.pop().unwrap(); let (l, mut v) = levels.pop().unwrap();
let (_, v_now) = levels.last_mut().unwrap(); let (_, v_now) = levels.last_mut().unwrap();
if v.len() == 0 { return Err((l, ParserError::EmptyGroup)) } if v.len() == 0 { return Err((l, DaisyError::EmptyGroup)) }
lookback(&mut v)?; lookback(&mut v)?;
v_now.push_back(Token::Group(l, v)); v_now.push_back(Token::Group(l, v));

View File

@ -1,9 +1,9 @@
use std::collections::VecDeque; use std::collections::VecDeque;
use crate::context::Context; use crate::context::Context;
use crate::errors::DaisyError;
use super::super::{ use super::super::{
Token, Token,
ParserError,
LineLocation, LineLocation,
Expression, Expression,
Operator Operator
@ -13,7 +13,7 @@ fn treeify_binary(
i: usize, i: usize,
g_inner: &mut VecDeque<Token>, g_inner: &mut VecDeque<Token>,
context: &Context context: &Context
) -> Result<bool, (LineLocation, ParserError)> { ) -> Result<bool, (LineLocation, DaisyError)> {
let this: &Token = &g_inner[i]; let this: &Token = &g_inner[i];
@ -23,7 +23,7 @@ fn treeify_binary(
Token::Operator(l, _) => l, Token::Operator(l, _) => l,
_ => panic!() _ => panic!()
}; };
return Err((*l, ParserError::Syntax)); // left argument is empty return Err((*l, DaisyError::Syntax)); // left argument is empty
} }
@ -35,7 +35,7 @@ fn treeify_binary(
Token::Operator(l, _) => l, Token::Operator(l, _) => l,
_ => panic!() _ => panic!()
}; };
return Err((*l, ParserError::Syntax)); // Left argument is empty return Err((*l, DaisyError::Syntax)); // Left argument is empty
} }
}; };
@ -47,7 +47,7 @@ fn treeify_binary(
Token::Operator(l, _) => l, Token::Operator(l, _) => l,
_ => panic!() _ => panic!()
}; };
return Err((*l, ParserError::Syntax)); // right argument is empty return Err((*l, DaisyError::Syntax)); // right argument is empty
} }
}; };
@ -57,7 +57,7 @@ fn treeify_binary(
if let Token::Operator(l, s) = left { if let Token::Operator(l, s) = left {
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string
let o = o.unwrap(); let o = o.unwrap();
if { if {
@ -67,13 +67,13 @@ fn treeify_binary(
return Ok(false); return Ok(false);
} else { } else {
let tl = *this.get_line_location() + *l; let tl = *this.get_line_location() + *l;
return Err((tl, ParserError::Syntax)); // left argument isn't valid return Err((tl, DaisyError::Syntax)); // left argument isn't valid
} }
} }
if let Token::Operator(l, s) = right { if let Token::Operator(l, s) = right {
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string
let o = o.unwrap(); let o = o.unwrap();
if { if {
@ -83,7 +83,7 @@ fn treeify_binary(
return Ok(false); return Ok(false);
} else { } else {
let tl = *this.get_line_location() + *l; let tl = *this.get_line_location() + *l;
return Err((tl, ParserError::Syntax)); // right argument isn't valid (two operators next to each other) return Err((tl, DaisyError::Syntax)); // right argument isn't valid (two operators next to each other)
} }
} }
@ -92,7 +92,7 @@ fn treeify_binary(
let this_op = { let this_op = {
let Token::Operator(l, s) = this else {panic!()}; let Token::Operator(l, s) = this else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // bad operator string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // bad operator string
o.unwrap() o.unwrap()
}; };
@ -100,14 +100,14 @@ fn treeify_binary(
let left_op = if i > 1 { let left_op = if i > 1 {
let Token::Operator(l, s) = &g_inner[i-2] else {panic!()}; let Token::Operator(l, s) = &g_inner[i-2] else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad operator string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad operator string
Some(o.unwrap()) Some(o.unwrap())
} else { None }; } else { None };
let right_op = if i < g_inner.len()-2 { let right_op = if i < g_inner.len()-2 {
let Token::Operator(l, s) = &g_inner[i+2] else {panic!()}; let Token::Operator(l, s) = &g_inner[i+2] else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad operator string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad operator string
Some(o.unwrap()) Some(o.unwrap())
} else { None }; } else { None };
@ -159,7 +159,7 @@ fn treeify_unary(
g_inner: &mut VecDeque<Token>, g_inner: &mut VecDeque<Token>,
left_associative: bool, left_associative: bool,
context: &Context context: &Context
) -> Result<bool, (LineLocation, ParserError)> { ) -> Result<bool, (LineLocation, DaisyError)> {
let this: &Token = &g_inner[i]; let this: &Token = &g_inner[i];
let next: &Token; let next: &Token;
@ -172,7 +172,7 @@ fn treeify_unary(
Token::Operator(l, _) => l, Token::Operator(l, _) => l,
_ => panic!() _ => panic!()
}; };
return Err((*l, ParserError::Syntax)); // argument is missing return Err((*l, DaisyError::Syntax)); // argument is missing
} }
}; };
} else { } else {
@ -184,7 +184,7 @@ fn treeify_unary(
Token::Operator(l, _) => l, Token::Operator(l, _) => l,
_ => panic!() _ => panic!()
}; };
return Err((*l, ParserError::Syntax)); // argument is missing return Err((*l, DaisyError::Syntax)); // argument is missing
} }
}; };
} }
@ -204,7 +204,7 @@ fn treeify_unary(
// Previous operator is invalid // Previous operator is invalid
return Err(( return Err((
*this.get_line_location(), *this.get_line_location(),
ParserError::Syntax DaisyError::Syntax
)); ));
} }
} }
@ -212,14 +212,14 @@ fn treeify_unary(
if let Token::Operator(l, _) = next { if let Token::Operator(l, _) = next {
let tl = *this.get_line_location() + *l; let tl = *this.get_line_location() + *l;
// Argument is invalid // Argument is invalid
return Err((tl, ParserError::Syntax)); return Err((tl, DaisyError::Syntax));
} else { } else {
// This operator // This operator
let this_op = { let this_op = {
let Token::Operator(l, s) = this else {panic!()}; let Token::Operator(l, s) = this else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string
o.unwrap() o.unwrap()
}; };
@ -228,14 +228,14 @@ fn treeify_unary(
if i > 1 { if i > 1 {
let Token::Operator(l, s) = &g_inner[i-2] else {panic!()}; let Token::Operator(l, s) = &g_inner[i-2] else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string
Some(o.unwrap()) Some(o.unwrap())
} else { None } } else { None }
} else { } else {
if i < g_inner.len()-2 { if i < g_inner.len()-2 {
let Token::Operator(l, s) = &g_inner[i+2] else {panic!()}; let Token::Operator(l, s) = &g_inner[i+2] else {panic!()};
let o = Operator::from_string(s); let o = Operator::from_string(s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } // Bad string if o.is_none() { return Err((*l, DaisyError::Syntax)); } // Bad string
Some(o.unwrap()) Some(o.unwrap())
} else { None } } else { None }
}; };
@ -285,7 +285,7 @@ fn treeify_unary(
pub fn treeify( pub fn treeify(
mut g: Token, mut g: Token,
context: &Context context: &Context
) -> Result<Expression, (LineLocation, ParserError)> { ) -> Result<Expression, (LineLocation, DaisyError)> {
let (l, g_inner): (LineLocation, &mut VecDeque<Token>) = match g { let (l, g_inner): (LineLocation, &mut VecDeque<Token>) = match g {
Token::Group(l, ref mut x) => (l, x), Token::Group(l, ref mut x) => (l, x),
_ => panic!() _ => panic!()
@ -293,7 +293,7 @@ pub fn treeify(
if g_inner.len() == 0 { if g_inner.len() == 0 {
// This shouldn't ever happen. // This shouldn't ever happen.
return Err((l, ParserError::EmptyGroup)); return Err((l, DaisyError::EmptyGroup));
} }
let mut left_associative = true; let mut left_associative = true;
@ -315,7 +315,7 @@ pub fn treeify(
let this_op = match &g_inner[i] { let this_op = match &g_inner[i] {
Token::Operator(l, s) => { Token::Operator(l, s) => {
let o = Operator::from_string(&s); let o = Operator::from_string(&s);
if o.is_none() { return Err((*l, ParserError::Syntax)); } if o.is_none() { return Err((*l, DaisyError::Syntax)); }
o.unwrap() o.unwrap()
}, },
_ => { _ => {
@ -356,7 +356,7 @@ pub fn treeify(
return match g { return match g {
// Catch edge cases // Catch edge cases
Token::Operator(l, _) => { Token::Operator(l, _) => {
Err((l, ParserError::Syntax)) Err((l, DaisyError::Syntax))
}, },
Token::Group(_,_) => { Token::Group(_,_) => {
treeify(g, context) treeify(g, context)

View File

@ -2,10 +2,10 @@ use std::collections::VecDeque;
use crate::quantity::Unit; use crate::quantity::Unit;
use crate::quantity::Quantity; use crate::quantity::Quantity;
use crate::context::Context; use crate::context::Context;
use crate::errors::DaisyError;
use super::{ use super::{
LineLocation, LineLocation,
ParserError,
Expression, Expression,
Constant Constant
}; };
@ -57,7 +57,7 @@ impl Token {
} }
#[inline(always)] #[inline(always)]
pub fn to_expression(self, context: &Context) -> Result<Expression, (LineLocation, ParserError)>{ pub fn to_expression(self, context: &Context) -> Result<Expression, (LineLocation, DaisyError)>{
match self { match self {
Token::Quantity(l, mut s) => { Token::Quantity(l, mut s) => {
@ -71,7 +71,7 @@ impl Token {
let r = Quantity::new_rational_from_string(&s); let r = Quantity::new_rational_from_string(&s);
if r.is_none() { if r.is_none() {
return Err((l, ParserError::BadNumber)) return Err((l, DaisyError::BadNumber))
} }
return Ok(Expression::Quantity(l, r.unwrap())); return Ok(Expression::Quantity(l, r.unwrap()));