diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/lexer.rs | 74 | ||||
| -rw-r--r-- | src/main.rs | 89 | ||||
| -rw-r--r-- | src/num.rs | 183 | ||||
| -rw-r--r-- | src/parser.rs | 202 |
4 files changed, 548 insertions, 0 deletions
diff --git a/src/lexer.rs b/src/lexer.rs new file mode 100644 index 0000000..49d2d04 --- /dev/null +++ b/src/lexer.rs @@ -0,0 +1,74 @@ +pub struct Lexer<'a> { + pub buf: &'a str, +} + +impl<'a> Lexer<'a> { + fn skip_whitespace(&mut self) { + while let Some(b' ') = self.buf.as_bytes().first() { + self.buf = &self.buf[1..] + } + } + + fn parse_num(&mut self) -> Option<&'a str> { + let mut i = 0; + while matches!(self.buf.as_bytes().get(i), Some(&b) if b.is_ascii_digit()) { + i += 1; + } + if let Some(b'.') = self.buf.as_bytes().get(i) { + i += 1; + } + while matches!(self.buf.as_bytes().get(i), Some(&b) if b.is_ascii_digit()) { + i += 1; + } + let (Some(b'e') | Some(b'E')) = self.buf.as_bytes().get(i) else { + let t = &self.buf[..i]; + self.buf = &self.buf[i..]; + return Some(t); + }; + i += 1; + if let Some(b'-') = self.buf.as_bytes().get(i) { + i += 1; + } + while matches!(self.buf.as_bytes().get(i), Some(&b) if b.is_ascii_digit()) { + i += 1; + } + let t = &self.buf[..i]; + self.buf = &self.buf[i..]; + Some(t) + } +} + +impl<'a> Iterator for Lexer<'a> { + type Item = &'a str; + + fn next(&mut self) -> Option<&'a str> { + self.skip_whitespace(); + let &first_byte = self.buf.as_bytes().first()?; + if first_byte.is_ascii_digit() || first_byte == b'.' { + return self.parse_num(); + } + match first_byte { + b'/' => { + if let Some(b'/') = self.buf.as_bytes().get(1) { + self.buf = &self.buf[2..]; + return Some("//"); + } + self.buf = &self.buf[1..]; + return Some("/"); + } + b'+' | b'-' | b'*' | b'^' | b'(' | b')' => { + let t = &self.buf[..1]; + self.buf = &self.buf[1..]; + return Some(t); + } + _ => (), + } + let mut i = 0; + while let Some(b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9') = self.buf.as_bytes().get(i) { + i += 1; + } + let t = &self.buf[..i]; + self.buf = &self.buf[i..]; + Some(t) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1f4291e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,89 @@ +mod lexer; +mod num; +mod parser; + +use gdk4::{Key, ModifierType}; +use gtk4::glib::{clone, ExitCode}; +use gtk4::prelude::{ + ApplicationExt, ApplicationExtManual, EditableExt, GridExt, GtkWindowExt, WidgetExt, +}; +use gtk4::{glib, Application, ApplicationWindow, Entry, EventControllerKey, Grid}; +use std::sync::{Arc, Mutex}; + +fn send_mail<T>(m: &Mutex<Option<T>>, msg: T) { + let mut guard = m.lock().unwrap(); + *guard = Some(msg); +} + +fn get_mail<T>(m: &Mutex<Option<T>>) -> Option<T> { + let mut guard = m.lock().unwrap(); + std::mem::take(&mut guard) +} + +fn main() -> ExitCode { + let app = Application::builder() + .application_id("com.github.rhogenson.qc") + .build(); + app.connect_activate(|app| { + let window = ApplicationWindow::builder() + .application(app) + .decorated(false) + .resizable(false) + .build(); + + let grid = Grid::new(); + window.set_child(Some(&grid)); + + let input = Entry::new(); + grid.attach(&input, 0, 0, 1, 1); + + let output = Entry::builder().editable(false).build(); + grid.attach(&output, 0, 1, 1, 1); + + let (ping_send, ping_recv) = std::sync::mpsc::sync_channel(1); + let mailbox = Arc::new(Mutex::new(None)); + + let key_controller = EventControllerKey::new(); + key_controller.connect_key_released( + clone!(@weak window, @weak input, @strong mailbox => move |_, k, _, modifiers| { + if k == Key::Escape + || k == Key::Return + || k == Key::bracketleft && modifiers.contains(ModifierType::CONTROL_MASK) + { + window.destroy(); + } + send_mail(mailbox.as_ref(), format!("{}", input.text())); + let _ = ping_send.try_send(()); + }), + ); + window.add_controller(key_controller); + + let (output_send, output_recv) = async_channel::bounded(1); + let (confirm_send, confirm_recv) = async_channel::bounded(1); + + gtk4::gio::spawn_blocking(move || loop { + let Ok(_) = ping_recv.recv() else { + return; + }; + let Some(input) = get_mail(mailbox.as_ref()) else { + continue; + }; + let Some(result) = parser::parse(&input) else { + continue; + }; + output_send.send_blocking(result).unwrap(); + confirm_recv.recv_blocking().unwrap(); + }); + + glib::spawn_future_local(clone!(@weak output => async move { + while let Ok(result) = output_recv.recv().await { + output.set_text(&format!("{}", result)); + confirm_send.send(()).await.unwrap(); + } + })); + + window.present(); + }); + + app.run() +} diff --git a/src/num.rs b/src/num.rs new file mode 100644 index 0000000..f17c076 --- /dev/null +++ b/src/num.rs @@ -0,0 +1,183 @@ +use num_bigint::BigInt; +use num_traits::cast::ToPrimitive; +use num_traits::pow::Pow; +use num_traits::FromPrimitive; +use std::fmt::{Display, Formatter}; + +pub enum Num { + Int(BigInt), + Float(f64), +} + +pub fn int(i: BigInt) -> Num { + Num::Int(i) +} + +pub fn float(f: f64) -> Num { + Num::Float(f) +} + +fn float_from_int(i: &BigInt) -> f64 { + if let Some(f) = i.to_f64() { + return f; + } + if i > &BigInt::ZERO { + std::f64::INFINITY + } else { + std::f64::NEG_INFINITY + } +} + +impl Num { + fn as_float(&self) -> f64 { + match self { + Num::Int(i) => float_from_int(i), + Num::Float(f) => *f, + } + } + + pub fn pow(self, other: Num) -> Num { + match (self, other) { + (Num::Int(i1), Num::Int(i2)) => { + if i2 < BigInt::ZERO { + return float(float_from_int(&i1).powf(float_from_int(&i2))); + } + let (_, u2) = i2.into_parts(); + int(i1.pow(u2)) + } + (n1, n2) => float(n1.as_float().powf(n2.as_float())), + } + } + + pub fn mul(self, other: Num) -> Num { + match (self, other) { + (Num::Int(i1), Num::Int(i2)) => int(i1 * i2), + (n1, n2) => float(n1.as_float() * n2.as_float()), + } + } + + pub fn div(self, other: Num) -> Num { + float(self.as_float() / other.as_float()) + } + + pub fn int_div(self, other: Num) -> Num { + match (self, other) { + (Num::Int(i1), Num::Int(i2)) => int(i1 / i2), + (Num::Float(f1), Num::Float(f2)) => { + let r = f1 / f2; + if let Some(i) = BigInt::from_f64(r) { + return int(i); + } + float(r) + } + (Num::Int(i), Num::Float(f)) => { + if let Some(fi) = BigInt::from_f64(f) { + return int(i / fi); + } + float(float_from_int(&i) / f) + } + (Num::Float(f), Num::Int(i)) => { + if let Some(fi) = BigInt::from_f64(f) { + return int(fi / i); + } + float(f / float_from_int(&i)) + } + } + } + + pub fn add(self, other: Num) -> Num { + match (self, other) { + (Num::Int(i1), Num::Int(i2)) => int(i1 + i2), + (n1, n2) => float(n1.as_float() + n2.as_float()), + } + } + + pub fn sub(self, other: Num) -> Num { + match (self, other) { + (Num::Int(i1), Num::Int(i2)) => int(i1 - i2), + (n1, n2) => Num::Float(n1.as_float() - n2.as_float()), + } + } + + pub fn sin(self) -> Num { + float(self.as_float().sin()) + } + + pub fn cos(self) -> Num { + float(self.as_float().cos()) + } + + pub fn tan(self) -> Num { + float(self.as_float().tan()) + } + + pub fn sqrt(self) -> Num { + match self { + Num::Int(i) if i >= BigInt::ZERO => { + let s = i.sqrt(); + if i != &s * &s { + return float(float_from_int(&i).sqrt()); + } + int(s) + } + n => float(n.as_float().sqrt()), + } + } + + pub fn log(self) -> Num { + float(self.as_float().ln()) + } + + pub fn floor(self) -> Num { + match self { + Num::Int(i) => int(i), + Num::Float(f) => { + if let Some(i) = BigInt::from_f64(f.floor()) { + return int(i); + } + float(f) + } + } + } + + pub fn ceil(self) -> Num { + match self { + Num::Int(i) => int(i), + Num::Float(f) => { + if let Some(i) = BigInt::from_f64(f.ceil()) { + return int(i); + } + float(f) + } + } + } + + pub fn round(self) -> Num { + match self { + Num::Int(i) => int(i), + Num::Float(f) => { + if let Some(i) = BigInt::from_f64(f.round_ties_even()) { + return int(i); + } + float(f) + } + } + } +} + +fn format_float(f: f64) -> String { + let mut s = format!("{f:.7}"); + while let Some(b'0') = s.as_bytes().get(s.len() - 1) { + s.truncate(s.len() - 1); + } + s +} + +impl Display for Num { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + match self { + Num::Int(n) => write!(f, "{n}"), + Num::Float(n) => write!(f, "{}", format_float(*n)), + } + } +} diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..fda744d --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,202 @@ +use crate::lexer::Lexer; +use crate::num; +use crate::num::Num; +use num_bigint::BigInt; +use std::str::FromStr; + +struct Parser<'a> { + r: Lexer<'a>, + first_token: Option<&'a str>, +} + +impl<'a> Parser<'a> { + fn peek(&mut self) -> Option<&'a str> { + if let Some(t) = self.first_token { + return Some(t); + } + self.first_token = self.r.next(); + self.first_token + } + + fn next(&mut self) -> Option<&'a str> { + if let Some(t) = std::mem::take(&mut self.first_token) { + return Some(t); + } + self.r.next() + } + + fn symbol(&mut self, s: &str) -> bool { + let Some(t) = self.peek() else { + return false; + }; + if t != s { + return false; + } + self.next(); + true + } + + fn parse_num(&mut self) -> Option<Num> { + let negative = self.symbol("-"); + let t = self.next()?; + if t.contains('.') || t.contains('e') || t.contains('E') { + let f = f64::from_str(t).ok()?; + if negative { + return Some(num::float(-f)); + } + return Some(num::float(f)); + } + let i = BigInt::from_str(t).ok()?; + if negative { + return Some(num::int(-i)); + } + Some(num::int(i)) + } + + fn paren_expr(&mut self) -> Option<Num> { + if !self.symbol("(") { + return None; + } + let n = self.expr()?; + if !self.symbol(")") { + return None; + } + Some(n) + } + + fn parse_const(&mut self) -> Option<Num> { + if self.symbol("e") { + return Some(num::float(std::f64::consts::E)); + } + if self.symbol("pi") { + return Some(num::float(std::f64::consts::PI)); + } + None + } + + fn atom(&mut self) -> Option<Num> { + if let Some(n) = self.paren_expr() { + return Some(n); + } + if let Some(n) = self.parse_const() { + return Some(n); + } + self.parse_num() + } + + fn parse_fun(&mut self) -> Option<Num> { + if self.symbol("sin") { + return Some(self.atom()?.sin()); + } + if self.symbol("cos") { + return Some(self.atom()?.cos()); + } + if self.symbol("tan") { + return Some(self.atom()?.tan()); + } + if self.symbol("sqrt") { + return Some(self.atom()?.sqrt()); + } + if self.symbol("log") || self.symbol("ln") { + return Some(self.atom()?.log()); + } + if self.symbol("floor") { + return Some(self.atom()?.floor()); + } + if self.symbol("ceil") || self.symbol("ceiling") { + return Some(self.atom()?.ceil()); + } + if self.symbol("round") { + return Some(self.atom()?.round()); + } + None + } + + fn app_expr_fold(&mut self, e1: Num) -> Option<Num> { + if let Some(e2) = self.paren_expr() { + return self.app_expr_fold(e1.mul(e2)); + } + if let Some(e2) = self.parse_const() { + return self.app_expr_fold(e1.mul(e2)); + } + if let Some(e2) = self.parse_fun() { + return Some(e1.mul(e2)); + } + Some(e1) + } + + fn app_expr(&mut self) -> Option<Num> { + if let Some(e) = self.parse_fun() { + return Some(e); + } + let e1 = self.atom()?; + self.app_expr_fold(e1) + } + + fn expt_expr_fold(&mut self, e1: Num) -> Option<Num> { + if !self.symbol("^") { + return Some(e1); + } + let e2 = self.app_expr()?; + if let Some(e2) = self.expt_expr_fold(e2) { + return Some(e1.pow(e2)); + } + None + } + + fn expt_expr(&mut self) -> Option<Num> { + let n = self.app_expr()?; + self.expt_expr_fold(n) + } + + fn mul_expr_fold(&mut self, e1: Num) -> Option<Num> { + if self.symbol("*") { + let e2 = self.expt_expr()?; + let e = e1.mul(e2); + return self.mul_expr_fold(e); + } + if self.symbol("/") { + let e2 = self.expt_expr()?; + let e = e1.div(e2); + return self.mul_expr_fold(e); + } + if self.symbol("//") { + let e2 = self.expt_expr()?; + let e = e1.int_div(e2); + return self.mul_expr_fold(e); + } + Some(e1) + } + + fn mul_expr(&mut self) -> Option<Num> { + let n = self.expt_expr()?; + self.mul_expr_fold(n) + } + + fn add_expr_fold(&mut self, e1: Num) -> Option<Num> { + if self.symbol("+") { + let e2 = self.mul_expr()?; + let e = e1.add(e2); + return self.add_expr_fold(e); + } + if self.symbol("-") { + let e2 = self.mul_expr()?; + let e = e1.sub(e2); + return self.add_expr_fold(e); + } + Some(e1) + } + + fn expr(&mut self) -> Option<Num> { + let n = self.mul_expr()?; + self.add_expr_fold(n) + } +} + +pub fn parse(expr: &str) -> Option<Num> { + let mut p = Parser { + r: Lexer { buf: expr }, + first_token: None, + }; + p.expr() +} |
