refactoring

This commit is contained in:
Simon Gardling
2022-04-22 11:34:11 -04:00
parent e603cbb975
commit d11480ed95
17 changed files with 156 additions and 151 deletions

View File

@@ -22,8 +22,8 @@ jobs:
with: with:
command: check command: check
test: parsing_test:
name: Tests name: Parsing Crate Tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout sources - name: Checkout sources

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
/pkg /pkg
/tmp /tmp
/assets.tar.zst /assets.tar.zst
/Cargo.lock

12
Cargo.lock generated
View File

@@ -1379,6 +1379,16 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "parsing"
version = "0.1.0"
dependencies = [
"exmex",
"lazy_static",
"phf",
"phf_codegen",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.1.0" version = "2.1.0"
@@ -2312,10 +2322,10 @@ dependencies = [
"emath", "emath",
"epaint", "epaint",
"epi", "epi",
"exmex",
"instant", "instant",
"itertools", "itertools",
"lazy_static", "lazy_static",
"parsing",
"phf", "phf",
"phf_codegen", "phf_codegen",
"rayon", "rayon",

View File

@@ -2,7 +2,6 @@
name = "ytbn_graphing_software" name = "ytbn_graphing_software"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
build = "build.rs"
license = "AGPL-3.0" license = "AGPL-3.0"
repository = "https://github.com/Titaniumtown/YTBN-Graphing-Software" repository = "https://github.com/Titaniumtown/YTBN-Graphing-Software"
description = "Crossplatform (and web-compatible) graphing calculator" description = "Crossplatform (and web-compatible) graphing calculator"
@@ -14,20 +13,9 @@ crate-type = ["cdylib"]
threading = ["async-lock", "rayon"] threading = ["async-lock", "rayon"]
[profile.release]
debug = false
codegen-units = 1
opt-level = "z" #optimize for size
lto = true
strip = true
[profile.dev]
debug = true
opt-level = 0
lto = true
strip = false
[dependencies] [dependencies]
parsing = { path = "./parsing" }
eframe = { git = "https://github.com/Titaniumtown/egui.git", default-features = false } eframe = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
egui = { git = "https://github.com/Titaniumtown/egui.git", default-features = false } egui = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
epaint = { git = "https://github.com/Titaniumtown/egui.git", default-features = false } epaint = { git = "https://github.com/Titaniumtown/egui.git", default-features = false }
@@ -39,9 +27,6 @@ const_format = { version = "0.2.22", default-features = false, features = [
"fmt", "fmt",
] } ] }
cfg-if = "1.0.0" cfg-if = "1.0.0"
exmex = { git = "https://github.com/bertiqwerty/exmex.git", branch = "main", features = [
"partial",
] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
tar = "0.4.38" tar = "0.4.38"
ruzstd = { git = "https://github.com/KillingSpark/zstd-rs.git", branch = "ringbuffer" } ruzstd = { git = "https://github.com/KillingSpark/zstd-rs.git", branch = "ringbuffer" }
@@ -74,6 +59,5 @@ wasm-bindgen = { version = "0.2.80", default-features = false, features = [
web-sys = "0.3.57" web-sys = "0.3.57"
tracing-wasm = "0.2.1" tracing-wasm = "0.2.1"
[package.metadata.cargo-all-features] [package.metadata.cargo-all-features]
skip_optional_dependencies = true #don't test optional dependencies, only features skip_optional_dependencies = true #don't test optional dependencies, only features

View File

@@ -1,71 +1,11 @@
use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
const SUPPORTED_FUNCTIONS: [&str; 22] = [
"abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor",
"round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10",
];
fn main() { fn main() {
// rebuild if new commit or contents of `assets` folder changed // rebuild if new commit or contents of `assets` folder changed
println!("cargo:rerun-if-changed=.git/logs/HEAD"); println!("cargo:rerun-if-changed=.git/logs/HEAD");
println!("cargo:rerun-if-changed=assets/*"); println!("cargo:rerun-if-changed=assets/*");
let _ = command_run::Command::with_args("./pack_assets.sh", &[""]) let _ = command_run::Command::with_args("../pack_assets.sh", &[""])
.enable_capture() .enable_capture()
.run(); .run();
shadow_rs::new().expect("Could not initialize shadow_rs"); shadow_rs::new().expect("Could not initialize shadow_rs");
generate_hashmap();
} }
fn generate_hashmap() {
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
let mut file = BufWriter::new(File::create(&path).expect("Could not create file"));
let max_len: usize = SUPPORTED_FUNCTIONS
.to_vec()
.iter()
.map(|func| func.len())
.max()
.unwrap();
let string_hashmap = compile_hashmap(
SUPPORTED_FUNCTIONS
.to_vec()
.iter()
.map(|a| a.to_string())
.collect(),
);
let mut hashmap = phf_codegen::Map::new();
for (key, value) in string_hashmap.iter() {
hashmap.entry(key, value);
}
write!(
&mut file,
"static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};",
hashmap.build()
)
.expect("Could not write to file");
writeln!(&mut file, "const MAX_COMPLETION_LEN: usize = {};", max_len)
.expect("Could not write to file");
write!(
&mut file,
"#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};",
SUPPORTED_FUNCTIONS.len(),
SUPPORTED_FUNCTIONS.to_vec()
)
.expect("Could not write to file");
}
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/autocomplete_helper.rs"
));

2
parsing/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
/Cargo.lock

20
parsing/Cargo.toml Normal file
View File

@@ -0,0 +1,20 @@
[package]
name = "parsing"
version = "0.1.0"
edition = "2021"
build = "build.rs"
license = "AGPL-3.0"
repository = "https://github.com/Titaniumtown/YTBN-Graphing-Software/tree/main/parsing"
description = "Parsing library for YTBN-Graphing-Software"
[lib]
[dependencies]
phf = "0.10.1"
exmex = { git = "https://github.com/bertiqwerty/exmex.git", branch = "main", features = [
"partial",
] }
lazy_static = "1.4.0"
[build-dependencies]
phf_codegen = "0.10.0"

55
parsing/build.rs Normal file
View File

@@ -0,0 +1,55 @@
use std::env;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
const SUPPORTED_FUNCTIONS: [&str; 22] = [
"abs", "signum", "sin", "cos", "tan", "asin", "acos", "atan", "sinh", "cosh", "tanh", "floor",
"round", "ceil", "trunc", "fract", "exp", "sqrt", "cbrt", "ln", "log2", "log10",
];
fn main() {
println!("cargo:rerun-if-changed=.git/logs/HEAD");
println!("cargo:rerun-if-changed=src/*");
generate_hashmap();
}
fn generate_hashmap() {
let path = Path::new(&env::var("OUT_DIR").unwrap()).join("codegen.rs");
let mut file = BufWriter::new(File::create(&path).expect("Could not create file"));
let string_hashmap = compile_hashmap(
SUPPORTED_FUNCTIONS
.to_vec()
.iter()
.map(|a| a.to_string())
.collect(),
);
let mut hashmap = phf_codegen::Map::new();
for (key, value) in string_hashmap.iter() {
hashmap.entry(key, value);
}
write!(
&mut file,
"static COMPLETION_HASHMAP: phf::Map<&'static str, Hint> = {};",
hashmap.build()
)
.expect("Could not write to file");
write!(
&mut file,
"#[allow(dead_code)] pub const SUPPORTED_FUNCTIONS: [&str; {}] = {:?};",
SUPPORTED_FUNCTIONS.len(),
SUPPORTED_FUNCTIONS.to_vec()
)
.expect("Could not write to file");
}
include!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/src/autocomplete_helper.rs"
));

View File

@@ -128,7 +128,7 @@ fn prettyify_function_str(func: &str) -> String {
} }
} }
const VALID_VARIABLES: [char; 5] = ['x', 'X', 'e', 'E', 'π']; pub const VALID_VARIABLES: [char; 5] = ['x', 'X', 'e', 'E', 'π'];
const LETTERS: [char; 52] = [ const LETTERS: [char; 52] = [
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's',
't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
@@ -136,6 +136,15 @@ const LETTERS: [char; 52] = [
]; ];
const NUMBERS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']; const NUMBERS: [char; 10] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'];
#[inline]
pub fn is_variable(c: &char) -> bool { VALID_VARIABLES.contains(&c) }
#[inline]
pub fn is_letter(c: &char) -> bool { LETTERS.contains(&c) }
#[inline]
pub fn is_number(c: &char) -> bool { NUMBERS.contains(&c) }
/* /*
EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again. EXTREMELY Janky function that tries to put asterisks in the proper places to be parsed. This is so cursed. But it works, and I hopefully won't ever have to touch it again.
One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter. One limitation though, variables with multiple characters like `pi` cannot be multiplied (like `pipipipi` won't result in `pi*pi*pi*pi`). But that's such a niche use case (and that same thing could be done by using exponents) that it doesn't really matter.
@@ -169,11 +178,11 @@ pub fn process_func_str(function_in: &str) -> String {
' ' ' '
}; };
let c_is_number = NUMBERS.contains(c); let c_is_number = is_number(c);
let c_is_letter = LETTERS.contains(c); let c_is_letter = is_letter(c);
let c_is_variable = VALID_VARIABLES.contains(c); let c_is_variable = is_variable(c);
let prev_char_is_variable = VALID_VARIABLES.contains(&prev_char); let prev_char_is_variable = is_variable(&prev_char);
let prev_char_is_number = NUMBERS.contains(&prev_char); let prev_char_is_number = is_number(&prev_char);
// makes special case for log with base of a 1-2 digit number // makes special case for log with base of a 1-2 digit number
if ((prev_prev_prev_char == 'l') if ((prev_prev_prev_char == 'l')
@@ -187,7 +196,7 @@ pub fn process_func_str(function_in: &str) -> String {
} }
let c_letters_var = c_is_letter | c_is_variable; let c_letters_var = c_is_letter | c_is_variable;
let prev_letters_var = prev_char_is_variable | LETTERS.contains(&prev_char); let prev_letters_var = prev_char_is_variable | is_letter(&prev_char);
if prev_char == ')' { if prev_char == ')' {
// cases like `)x`, `)2`, and `)(` // cases like `)x`, `)2`, and `)(`
@@ -196,7 +205,7 @@ pub fn process_func_str(function_in: &str) -> String {
} }
} else if *c == '(' { } else if *c == '(' {
// cases like `x(` and `2(` // cases like `x(` and `2(`
if (prev_char_is_variable | prev_char_is_number) && !LETTERS.contains(&prev_prev_char) { if (prev_char_is_variable | prev_char_is_number) && !is_letter(&prev_prev_char) {
add_asterisk = true; add_asterisk = true;
} }
} else if prev_char_is_number { } else if prev_char_is_number {
@@ -235,7 +244,7 @@ pub fn process_func_str(function_in: &str) -> String {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::function_handling::suggestions::SUPPORTED_FUNCTIONS; use crate::suggestions::SUPPORTED_FUNCTIONS;
use std::collections::HashMap; use std::collections::HashMap;
/// returns if function with string `func_str` is valid after processing through [`process_func_str`] /// returns if function with string `func_str` is valid after processing through [`process_func_str`]

View File

@@ -1,8 +1,17 @@
use crate::misc::chars_take; use crate::parsing::is_number;
pub const HINT_EMPTY: Hint = Hint::Single("x^2"); pub const HINT_EMPTY: Hint = Hint::Single("x^2");
const HINT_CLOSED_PARENS: Hint = Hint::Single(")"); const HINT_CLOSED_PARENS: Hint = Hint::Single(")");
/// Only enacts println if cfg(test) is enabled
macro_rules! test_print {
($($arg:tt)*) => {
#[cfg(test)]
println!($($arg)*)
};
}
/// Generate a hint based on the input `input`, returns an `Option<String>` /// Generate a hint based on the input `input`, returns an `Option<String>`
pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> { pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
if input.is_empty() { if input.is_empty() {
@@ -23,20 +32,38 @@ pub fn generate_hint<'a>(input: &str) -> &'a Hint<'a> {
return &HINT_CLOSED_PARENS; return &HINT_CLOSED_PARENS;
} }
let len = chars.len(); // let len = chars.len();
for key in (1..=MAX_COMPLETION_LEN)
.rev() let mut split: Vec<String> = Vec::new();
.filter(|i| len >= *i)
.map(|i| chars_take(&chars, i)) let mut buffer: Vec<char> = Vec::new();
.filter(|cut_string| !cut_string.is_empty()) for c in chars {
{ buffer.push(c);
if let Some(output) = COMPLETION_HASHMAP.get(&key) { if c == ')' {
return output; split.push(buffer.iter().collect::<String>());
buffer.clear();
continue;
}
let buffer_string = buffer.iter().collect::<String>();
if ((&buffer_string == "log") | (&buffer_string == "log1")) && is_number(&c) {
continue;
} }
} }
&Hint::None if !buffer.is_empty() {
split.push(buffer.iter().collect::<String>());
}
test_print!("split: {:?}", split);
if split.is_empty() {
return COMPLETION_HASHMAP.get(input).unwrap_or(&Hint::None);
}
COMPLETION_HASHMAP.get(& unsafe {split.last().unwrap_unchecked()}.as_str()).unwrap_or(&Hint::None)
} }
#[derive(PartialEq)] #[derive(PartialEq)]
@@ -95,6 +122,7 @@ mod tests {
("sin(", Hint::Single(")")), ("sin(", Hint::Single(")")),
("sqrt", Hint::Single("(")), ("sqrt", Hint::Single("(")),
("ln(x)", Hint::None), ("ln(x)", Hint::None),
("ln(x)cos", Hint::Many(&["(", "h("])),
]); ]);
for (key, value) in values { for (key, value) in values {

View File

@@ -1,7 +1,5 @@
#![allow(clippy::too_many_arguments)] // Clippy, shut #![allow(clippy::too_many_arguments)] // Clippy, shut
use crate::function_handling::parsing::{process_func_str, BackingFunction};
use crate::function_handling::suggestions::Hint;
use crate::math_app::AppSettings; use crate::math_app::AppSettings;
use crate::misc::*; use crate::misc::*;
use crate::widgets::{widgets_ontop, AutoComplete, Movement}; use crate::widgets::{widgets_ontop, AutoComplete, Movement};
@@ -12,6 +10,10 @@ use egui::{
}; };
use emath::vec2; use emath::vec2;
use epaint::Color32; use epaint::Color32;
use parsing::{
parsing::{process_func_str, BackingFunction},
suggestions::Hint,
};
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::ops::BitXorAssign; use std::ops::BitXorAssign;

View File

@@ -4,10 +4,8 @@
#[macro_use] #[macro_use]
extern crate static_assertions; extern crate static_assertions;
mod autocomplete_helper;
mod consts; mod consts;
mod function_entry; mod function_entry;
mod function_handling;
mod math_app; mod math_app;
mod misc; mod misc;
mod widgets; mod widgets;

View File

@@ -4,10 +4,8 @@
#[macro_use] #[macro_use]
extern crate static_assertions; extern crate static_assertions;
mod autocomplete_helper;
mod consts; mod consts;
mod function_entry; mod function_entry;
mod function_handling;
mod math_app; mod math_app;
mod misc; mod misc;
mod widgets; mod widgets;

View File

@@ -333,31 +333,6 @@ pub fn step_helper(max_i: usize, min_x: &f64, step: &f64) -> Vec<f64> {
(0..max_i).map(|x| (x as f64 * step) + min_x).collect() (0..max_i).map(|x| (x as f64 * step) + min_x).collect()
} }
/// Takes `take` number of chars from the end of `chars` and returns a string
pub fn chars_take(chars: &[char], take: usize) -> String {
let len = chars.len();
assert!(len >= take);
match take {
0 => {
// return empty string if `take == 0`
String::new()
}
1 => {
// return last character as a string if take == 1
chars[len - 1].to_string()
}
_ if take == len => {
// return `chars` turned into a string if `take == len`
return chars.iter().collect::<String>();
}
_ => {
// actually do the thing
return chars.iter().rev().take(take).rev().collect::<String>();
}
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -450,21 +425,4 @@ mod tests {
assert_eq!(option_vec_printer(&key), value); assert_eq!(option_vec_printer(&key), value);
} }
} }
#[test]
fn chars_take_test() {
let values = HashMap::from([
(("test", 2), "st"),
(("cool text", 4), "text"),
(("aaa", 0), ""),
(("aaab", 1), "b"),
]);
for ((in_str, i), value) in values {
assert_eq!(
chars_take(&in_str.chars().collect::<Vec<char>>(), i),
value.to_owned()
);
}
}
} }

View File

@@ -1,6 +1,6 @@
use crate::function_handling::suggestions::{self, generate_hint, Hint};
use egui::{text::CCursor, text_edit::CursorRange, TextEdit}; use egui::{text::CCursor, text_edit::CursorRange, TextEdit};
use epaint::text::cursor::{Cursor, PCursor, RCursor}; use epaint::text::cursor::{Cursor, PCursor, RCursor};
use parsing::suggestions::{self, generate_hint, Hint};
#[derive(PartialEq, Debug)] #[derive(PartialEq, Debug)]
pub enum Movement { pub enum Movement {