From a9dd9bc87c4c9d4f4e7f986f8b284b0253568c8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Mekina?= Date: Tue, 29 Apr 2025 22:04:32 +0200 Subject: [PATCH] add base --- .gitignore | 1 + Cargo.lock | 11 + Cargo.toml | 3 + Makefile | 0 client/.gitignore | 1 + client/bun.lockb | Bin 0 -> 17998 bytes client/make.mk | 0 client/package.json | 14 ++ client/tsconfig.json | 27 +++ lib/search_and_replace/Cargo.toml | 9 + lib/search_and_replace/src/main.rs | 371 +++++++++++++++++++++++++++++ theseus-server/Cargo.toml | 6 + theseus-server/src/main.rs | 3 + 13 files changed, 446 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 client/.gitignore create mode 100755 client/bun.lockb create mode 100644 client/make.mk create mode 100644 client/package.json create mode 100644 client/tsconfig.json create mode 100644 lib/search_and_replace/Cargo.toml create mode 100644 lib/search_and_replace/src/main.rs create mode 100644 theseus-server/Cargo.toml create mode 100644 theseus-server/src/main.rs 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 0000000000000000000000000000000000000000..b7315c77c4f916894885e4bc7fc33f7de7a799ef GIT binary patch literal 17998 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, +} + +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!"); +}