diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..93fd3dc --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,11 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "search_and_replace" +version = "0.1.0" + +[[package]] +name = "theseus-server" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..713d475 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +resolver = "2" +members = ["lib/search_and_replace", "theseus-server"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e69de29 diff --git a/client/.gitignore b/client/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/client/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/client/bun.lockb b/client/bun.lockb new file mode 100755 index 0000000..b7315c7 Binary files /dev/null and b/client/bun.lockb differ diff --git a/client/make.mk b/client/make.mk new file mode 100644 index 0000000..e69de29 diff --git a/client/package.json b/client/package.json new file mode 100644 index 0000000..1ef9211 --- /dev/null +++ b/client/package.json @@ -0,0 +1,14 @@ +{ + "name": "client", + "type": "module", + "devDependencies": { + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "html-minifier": "^4.0.0", + "sass": "^1.87.0" + } +} diff --git a/client/tsconfig.json b/client/tsconfig.json new file mode 100644 index 0000000..238655f --- /dev/null +++ b/client/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + // Enable latest features + "lib": ["ESNext", "DOM"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags (disabled by default) + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false + } +} diff --git a/lib/search_and_replace/Cargo.toml b/lib/search_and_replace/Cargo.toml new file mode 100644 index 0000000..3125d2c --- /dev/null +++ b/lib/search_and_replace/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "search_and_replace" +version = "0.1.0" +edition = "2021" + +[profile.release] +strip = true + +[dependencies] diff --git a/lib/search_and_replace/src/main.rs b/lib/search_and_replace/src/main.rs new file mode 100644 index 0000000..f5a7df9 --- /dev/null +++ b/lib/search_and_replace/src/main.rs @@ -0,0 +1,371 @@ +use std::{env::args, io::Read}; + +#[derive(Debug, PartialEq)] +struct JumpTable { + jumps: Vec, +} + +impl JumpTable { + fn resolve_char( + pat: &Vec, + ptr: &usize, + cur_char: &char, + ) -> usize { + if pat.get(*ptr).unwrap() == cur_char { + return *ptr + 1; + } + if pat.get(0).unwrap() == cur_char { + return 1; + } + return 0; + } + + pub fn new(pat: &Vec) -> Self { + if pat.len() == 0 { + return Self { jumps: vec![] }; + } + let mut ptr = 0_usize; + let mut jumps = vec![0]; + for cur_char in pat.iter().skip(1) { + ptr = JumpTable::resolve_char(pat, &ptr, cur_char); + jumps.push(ptr); + } + + Self { jumps } + } + + pub fn search(&self, pat: &Vec, needle: &char, ptr: &usize) -> usize { + let mut ptr = *ptr; + while ptr != 0 { + if pat.get(ptr).unwrap() == needle { + break; + } + ptr = *self.jumps.get(ptr.saturating_sub(1)).unwrap(); + } + ptr + } +} + +#[derive(Debug, PartialEq)] +struct Pattern { + pat: Vec, + jt: JumpTable, + ptr: usize, +} + +impl Pattern { + pub fn new(pat: String) -> Result { + if pat.len() == 0 { + return Err(()); + } + let pat = pat.chars().collect::>(); + let jt = JumpTable::new(&pat); + Ok(Self { + pat, + jt, + ptr: 0, + }) + } + + pub fn search_and_update(&mut self, cur_char: &char) -> bool { + if self.pat.get(self.ptr).unwrap() == cur_char { + self.ptr += 1; + return if self.ptr == self.pat.len() { true } else { false }; + } + let found = self.jt.search(&self.pat, &cur_char, &self.ptr); + self.ptr = found; + false + } + + pub fn reset_ptr(&mut self) { + self.ptr = 0; + } + + pub fn len(&self) -> usize { + self.pat.len() + } +} + +#[derive(Debug, PartialEq, Eq)] +struct Match { + pub pat_id: usize, + pub start_pos: usize, + pub len: usize, +} + +impl Match { + pub fn new(pat_id: usize, start_pos: usize, len: usize) -> Self { + Self { pat_id, start_pos, len } + } +} + +#[derive(Debug)] +struct MPSearch { + pats: Vec, +} + +impl MPSearch { + pub fn new(pats: Vec) -> Self { + Self { pats } + } + + fn reset_all_ptrs(&mut self) { + for pat in self.pats.iter_mut() { + pat.reset_ptr(); + } + } + + fn search_at_pos(&mut self, cur_char: &char, pos: &usize) -> Option { + let mut cur_match = None; + for (idx, pat) in self.pats.iter_mut().enumerate() { + if pat.search_and_update(cur_char) { + cur_match = Some(Match::new(idx, pos.saturating_sub(pat.len() - 1), pat.len())); + break; + } + } + if let Some(_) = cur_match { + self.reset_all_ptrs(); + } + cur_match + } + + pub fn search(&mut self, haystack: &String) -> Vec { + let mut res = Vec::new(); + for (cur_idx, cur_char) in haystack.chars().enumerate() { + if let Some(cur_match) = self.search_at_pos(&cur_char, &cur_idx) { + res.push(cur_match); + } + } + res + } +} + +macro_rules! skip_n { + ($iter: ident, $n: expr) => { + for _ in 0..$n { + $iter.next().ok_or(())?; + } + } +} + +macro_rules! append_n { + ($iter: ident, $target: ident, $n: expr) => { + for _ in 0..$n { + $target.push($iter.next().ok_or(())?); + } + } +} + + +#[derive(Debug)] +struct MPReplace { + replacements: Vec, + matches: Vec, +} + +impl MPReplace { + pub fn new(matches: Vec, replacements: Vec) -> Self { + Self { replacements, matches } + } + + pub fn replace(&self, target: &String) -> Result { + let mut ptr = 0; + let mut iter = target.chars(); + let mut res = String::new(); + for cur_match in self.matches.iter() { + append_n!(iter, res, cur_match.start_pos - ptr); + skip_n!(iter, cur_match.len); + res.push_str(self.replacements.get(cur_match.pat_id).ok_or(())?); + ptr = cur_match.start_pos + cur_match.len; + } + append_n!(iter, res, target.chars().count() - ptr); + Ok(res) + } +} + +struct PatMatchArgs> { + inner: I, +} + +impl> PatMatchArgs { + pub fn new(inner: I, count: usize) -> Result { + if count % 2 != 0 { + return Err(()); + } + Ok(Self { inner }) + } +} + +impl> Iterator for PatMatchArgs { + type Item = (String, String); + + fn next(&mut self) -> Option { + Some((self.inner.next()?, self.inner.next()?)) + } +} + +fn main() { + let mut contents = Vec::new(); + std::io::stdin().lock().read_to_end(&mut contents).unwrap(); + let contents = std::str::from_utf8(&contents).unwrap().to_string(); + + let args = match PatMatchArgs::new(args().skip(1), args().len() - 1) { + Ok(val) => val, + Err(()) => { + eprintln!("the number of arguments after filepath must be divisible by two"); + std::process::exit(1); + } + }; + let mut pats = Vec::new(); + let mut replacements = Vec::new(); + for (cur_pat, cur_replacement) in args { + let Ok(cur_pat) = Pattern::new(cur_pat) else { + eprintln!("the patterns can't be empty"); + std::process::exit(1); + }; + pats.push(cur_pat); + replacements.push(cur_replacement); + } + + let matches = MPSearch::new(pats).search(&contents); + let replaced = MPReplace::new(matches, replacements) + .replace(&contents).unwrap(); + println!("{}", replaced); +} + +#[cfg(test)] +mod test { + use crate::{JumpTable, MPReplace, MPSearch, Match, Pattern}; + + #[test] + fn jumps_01() { + let src = String::from("thisthen").chars().collect::>(); + let target = vec![0_usize, 0, 0, 0, 1, 2, 0, 0]; + let jt = JumpTable::new(&src); + assert_eq!(jt.jumps, target); + } + + #[test] + fn jumps_02() { + let src = String::from("tthis").chars().collect::>(); + let target = vec![0, 1, 0, 0, 0]; + let jt = JumpTable::new(&src); + assert_eq!(jt.jumps, target); + } + + #[test] + fn jumps_03() { + let src = String::from("t").chars().collect::>(); + let target = vec![0]; + let jt = JumpTable::new(&src); + assert_eq!(jt.jumps, target); + } + + #[test] + fn jumps_04() { + let src = String::from("tt").chars().collect::>(); + let target = vec![0, 1]; + let jt = JumpTable::new(&src); + assert_eq!(jt.jumps, target); + } + + #[test] + fn jumps_05() { + let src = String::from("").chars().collect::>(); + let target = vec![]; + let jt = JumpTable::new(&src); + assert_eq!(jt.jumps, target); + } + + #[test] + fn search_01() { + let pat = String::from("tthis").chars().collect::>(); + let jt = JumpTable::new(&pat); + assert_eq!(jt.search(&pat, &'t', &1), 1); + } + + #[test] + fn search_02() { + let pat = String::from("testtesa").chars().collect::>(); + let jt = JumpTable::new(&pat); + assert_eq!(jt.search(&pat, &'t', &7), 3); + } + + #[test] + fn search_03() { + let pat = String::from("ahojahoj").chars().collect::>(); + let jt = JumpTable::new(&pat); + assert_eq!(jt.search(&pat, &'j', &7), 7); + } + + #[test] + fn search_04() { + let pat = String::from("ahojahoj").chars().collect::>(); + let jt = JumpTable::new(&pat); + assert_eq!(jt.search(&pat, &'j', &7), 7); + } + + #[test] + fn search_05() { + let pat = String::from("jojojojojojoja").chars().collect::>(); + let jt = JumpTable::new(&pat); + assert_eq!(jt.search(&pat, &'o', &13), 11); + } + + #[test] + fn search_and_update_01() { + let pat = String::from("test"); + let mut pat = Pattern::new(pat).unwrap(); + let haystack = String::from("thisisatest"); + for cur_haystack in haystack.chars().take(haystack.len() - 1) { + assert_eq!(pat.search_and_update(&cur_haystack), false); + println!("{:?}", pat); + } + println!("{:?}", pat); + assert_eq!(pat.search_and_update(&'t'), true); + } + + #[test] + fn empty_pattern() { + assert_eq!(Pattern::new("".chars().collect()), Err(())); + } + + #[test] + fn mpsearch_01() { + let mut mpsearch = MPSearch::new( + vec!["this", "is", "a", "test"] + .iter() + .map(|p| Pattern::new(p.to_string()).unwrap()) + .collect() + ); + println!("{:?}", mpsearch); + let haystack = String::from("this is a test"); + let target = vec![ + Match::new(0, 0, 4), + Match::new(1, 5, 2), + Match::new(2, 8, 1), + Match::new(3, 10, 4) + ]; + assert_eq!(mpsearch.search(&haystack), target); + } + + #[test] + fn mpreplace_01() { + let mut mpsearch = MPSearch::new( + vec!["this", "is", "a", "test"] + .iter() + .map(|p| Pattern::new(p.to_string()).unwrap()) + .collect() + ); + let haystack = String::from("this-is.a*test///"); + let matches = mpsearch.search(&haystack); + let mpreplace = MPReplace::new(matches, vec![ + "that".to_string(), + "isn't".to_string(), + "the".to_string(), + "final".to_string(), + ]); + let replaced = mpreplace.replace(&haystack).unwrap(); + assert_eq!(replaced, "that-isn't.the*final///"); + } +} diff --git a/theseus-server/Cargo.toml b/theseus-server/Cargo.toml new file mode 100644 index 0000000..b6255bd --- /dev/null +++ b/theseus-server/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "theseus-server" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/theseus-server/src/main.rs b/theseus-server/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/theseus-server/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +}