use std::{borrow::Cow, collections::HashMap, str::Lines}; struct PreProcessor<'s> { src: &'s str, pos: usize, lines: Lines<'s>, macros: HashMap<&'s str, &'s str>, } impl<'s> PreProcessor<'s> { fn new(src: &'s str) -> Self { let lines = src.lines(); let macros = HashMap::new(); Self { src, pos: 0, lines, macros, } } fn next(&mut self) -> Option<&'s str> { self.lines.next().map(|line| { self.pos += line.len() + 1; line.trim() }) } fn read_macro(&mut self, full_name: &'s str) { let name = &full_name[8..]; let start = self.pos; let mut end = start; while let Some(line) = self.next() && !matches!(line, "%end") { end = self.pos; } let str = &self.src[start..end]; self.macros.insert(name, str); } fn read_macros(&mut self) -> String { let mut buf = String::new(); while let Some(line) = self.next() { if line.starts_with("%define ") { self.read_macro(line); } else { buf.push_str(line); buf.push('\n'); } } buf } fn process(&mut self) -> String { let rest = self.read_macros(); let mut lines = rest.lines().map(Cow::Borrowed).collect::>(); loop { let mut count = 0; for (name, body) in &self.macros { count += fill_macro(&mut lines, name, body); } if count == 0 { break; } } lines.join("\n") } } fn fill_macro(contents: &mut Vec>, name: &str, body: &str) -> usize { let mut count = 0; let mut idx = 0; loop { if idx >= contents.len() { break; } let line = &contents[idx]; if line.starts_with(name) { fill_macro_once(contents, idx, body); count += 1; } idx += 1; } count } fn fill_macro_once(contents: &mut Vec>, idx: usize, body: &str) { let invoke_line = contents.remove(idx); let args = invoke_line.split_whitespace().skip(1).collect::>(); for line in body.lines().rev() { let mut buf = String::from(line); for (idx, arg) in args.iter().enumerate() { let key = format!("${}", idx + 1); buf = buf.replace(&key, arg); } contents.insert(idx, Cow::Owned(buf)); } } pub fn process(src: &str) -> String { PreProcessor::new(src).process() }