enum Context<'a> { HalfIncludeGuard(&'a str), IncludeGuard(&'a str), If(&'a str), Ifndef(&'a str), } impl<'a> Context<'a> { fn modality(&self) -> Option> { match self { Context::HalfIncludeGuard(_) => None, Context::IncludeGuard(_) => None, Context::If(x) => Some(Modality::If(x)), Context::Ifndef(x) => Some(Modality::Ifndef(x)), } } } #[derive(Debug)] enum Modality<'a> { If(&'a str), Ifndef(&'a str), And(Vec), } impl<'a> Modality<'a> { fn and(a: Option, b: Option) -> Option { match (a, b) { (Some(a), Some(b)) => Some(Self::And(vec![a, b])), (Some(a), None) => Some(a), (None, Some(b)) => Some(b), (None, None) => None, } } } trait VecContextExt<'a> { fn modality(&self) -> Option>; } impl<'a> VecContextExt<'a> for Vec> { fn modality(&self) -> Option> { self.iter() .rev() .map(Context::modality) .fold(None, Modality::and) } } #[derive(Debug)] enum FileItem<'a> { LineComment(&'a str), IncludeLocal(&'a str), IncludeGlobal(&'a str), } #[derive(Debug)] pub struct FullFileItem<'a> { data: FileItem<'a>, modality: Option>, } pub fn parse(file: &str) -> Vec { let mut context: Vec = vec![]; let mut result = vec![]; for line in file.lines() { if line.starts_with("//") { result.push(FullFileItem { data: FileItem::LineComment(line.strip_prefix("//").unwrap()), modality: context.modality(), }); } else if line.trim().is_empty() { // don't do anything } else if line.starts_with("#ifndef ") && context.is_empty() { // this is probably the include guard context.push(Context::HalfIncludeGuard( line.strip_prefix("#ifndef ").unwrap(), )); } else if line.starts_with("#define ") { match context.last() { Some(Context::HalfIncludeGuard(expected_guard)) => { // this is the other half of the include guard let actual_guard = line.strip_prefix("#define ").unwrap(); assert_eq!( actual_guard, *expected_guard, "weird other half of include guard" ); context.pop(); context.push(Context::IncludeGuard(actual_guard)); } _ => { todo!("handle #define in non-include guard contexts: {:?}", line); } } } else if line.trim().starts_with("#include ") { let good_part = line.trim().strip_prefix("#include ").unwrap(); let item = if line.ends_with("\"") { FileItem::IncludeLocal(good_part.trim_matches('"')) } else if line.ends_with(">") { FileItem::IncludeGlobal(good_part.trim_start_matches('<').trim_end_matches('>')) } else { todo!("handle weird includes {:?}", line); }; result.push(FullFileItem { data: item, modality: context.modality(), }); } else if line.starts_with("#if ") { let condition = line.strip_prefix("#if ").unwrap(); context.push(Context::If(condition)); } else if line.starts_with("#ifndef ") { let condition = line.strip_prefix("#ifndef ").unwrap(); context.push(Context::Ifndef(condition)); } else if line.starts_with("#endif") { context.pop(); } else { todo!("handle line {:?}", line); } } result }