aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2024-05-27 16:04:31 -0700
committerRose Hogenson <rosehogenson@posteo.net>2024-05-27 16:04:31 -0700
commit4debd88807c538aa430ce69b83fdb291f687b1b1 (patch)
tree5def99861585367826937c2bdbbfba7f23e07f0b /src
downloadqc-4debd88807c538aa430ce69b83fdb291f687b1b1.tar.zst
Rewrite in Rust.
Diffstat (limited to 'src')
-rw-r--r--src/lexer.rs74
-rw-r--r--src/main.rs89
-rw-r--r--src/num.rs183
-rw-r--r--src/parser.rs202
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()
+}