use std::{any::Any, collections::HashMap, str::Chars}; use crate::ast::{Fragment, Line, MarkdownAST, Token}; pub mod emphasis; pub mod headings; type ProcessFn = fn(&char, &mut ParserInternalStatus) -> ParserMatchResult; fn match_fn_list() -> Vec { // 要求以 预处理、词、行、块、后处理 的顺序编写列表 // 然后 词处理器 将会写入 records_tokens 由 行处理器 消费 // 接着 行处理器 将会写入 records_lines 由 块处理器 消费 // 块处理器则将自身加入块列表中 vec![ // 预处理器 // ... // 词处理器 emphasis::proc, // 行处理器 // ... // 块处理器 headings::proc, // 后处理器 post, ] } /// 错误类型 pub enum MarkdownASTParseError { /// 语法错误 SyntaxError { msg: String, raw: String, begin_col: u16, begin_row: u32, end_col: u16, end_row: u32, }, } /// 转换器规则 pub struct MarkdownParserConfig { ending_rule: LineEndingRule, } /// 换行规则 #[derive(Debug, PartialEq, Eq)] #[repr(u8)] pub enum LineEndingRule { CRLF, LF, } pub(crate) struct ParserInternalStatus<'a> { /// 语法树当前状态 ast: MarkdownAST, /// 总配置 cfg: MarkdownParserConfig, /// 当前行的所有已处理字符 lookback: String, /// 所有字符 chars: Chars<'a>, /// 当前扫描的行 row: u32, /// 当前扫描的列 col: u16, /// 记录的 Line, 用于暂存无归属的 Line records_lines: Vec, /// 记录的 Token,用于暂存无归属的 Token records_tokens: Vec, /// 记录的 Fragment,用于暂存无归属的 Fragment records_fragment: Fragment, /// 临时类型表 tmp: HashMap<&'a str, Box>, } impl<'a> ParserInternalStatus<'a> { /// 通过字符键初始化或获取临时值 pub(crate) fn get_tmp_or_init(&mut self, key: &'a str) -> &mut T where T: Any + Default, { if !self.tmp.contains_key(key) { let value = Box::new(T::default()); self.tmp.insert(key, value); } // SAFETY: 前方代码可以保证此处一定能拿到 `tmp` 的可变借用 let boxed = unsafe { self.tmp.get_mut(key).unwrap_unchecked() }; // SAFETY: 此处类型安全由解析器内部实现保证,`ParserInternalStatus` 不会对外部 API 开放 unsafe { &mut *(boxed.as_mut() as *mut dyn Any as *mut T) } } } pub(crate) enum ParserMatchResult { /// 标记已完成,该字符无需继续处理 Done, /// 标记未完成,该字符需要继续处理 Sad, /// 标记放弃,该处理器本行内不参与计算 Abort, /// 语法错误,需要立刻崩溃 SyntaxError { begin_col: u16, begin_row: u32, end_col: u16, end_row: u32, msg: String, }, } pub fn markdown_parser( content: String, cfg: MarkdownParserConfig, ) -> Result { // 创建空 AST,无任何内容 let ast = MarkdownAST { blocks: vec![] }; // 在末尾添加换行符,以确保末尾行一定能被执行 let mut content = content; let ending = match cfg.ending_rule { LineEndingRule::CRLF => "\r\n", LineEndingRule::LF => "\n", }; content.push_str(ending); // 初始化内部状态 let mut inr = ParserInternalStatus { ast, cfg: cfg, lookback: String::new(), chars: content.chars(), row: 0, col: 0, records_lines: Vec::new(), records_tokens: Vec::new(), records_fragment: Fragment::default(), tmp: HashMap::new(), }; // 所有的匹配处理函数 let match_vec: Vec = match_fn_list(); // 放弃列表 // > 为什么不用 HashSet? // > `aborted` 在整个生命周期内数量最大不超过 100 条 // > HashSet 虽然 O(1) 但是缓存不友好,更推荐使用 O(n) 线性扫描的 Vec 来处理 let mut aborted: Vec = Vec::new(); // 扫描循环 while let Some(c) = inr.chars.next() { // 当 Lookback 为空时,说明当前为新行 if inr.lookback.is_empty() { // 清空放弃列表 aborted.clear(); } // 当前处理函数的索引值 let mut idx: u8 = 0; // 该字符是否已完成处理 let mut done = false; for v in &match_vec { // 当前处理函数在放弃列表中 if aborted.contains(&idx) { // 跳过当前函数的处理逻辑 idx += 1; continue; } match v(&c, &mut inr) { // 本字符处理完成 ParserMatchResult::Done => { // 排除对 `\r` 和 `\n` 的完成处理 // // 在 `post` 中,必须处理该字符的换行逻辑,否则会产生字符位置指针异常 if !matches!(c, '\r' | '\n') { // 为字符标记为已完成处理 done = true; // 跳过当前步骤前,提前将列指针右移 inr.col += 1; // 所有处理器跳过当前步骤 break; } } // 放弃整行的处理 ParserMatchResult::Abort => { // 将当前函数处理加入放弃列表 aborted.push(idx); } // 本字符未处理完成 ParserMatchResult::Sad => {} // 发生语法错误 ParserMatchResult::SyntaxError { begin_col, begin_row, end_col, end_row, msg, } => { return Err(handle_syntax_error( content.to_string(), begin_col, begin_row, end_col, end_row, msg, )); } } // 下一个处理器继续处理 idx += 1; } if !done { // 如果字符未完成处理,说明是普通字符,需要加入 records_fragment inr.records_fragment.str.push(c); } // 将列指针右移 inr.col += 1; } // 当所有字符处理完成后,AST 应当已经构成,直接返回 Ok(inr.ast) } fn post(c: &char, inr: &mut ParserInternalStatus) -> ParserMatchResult { // 将该字符加入 Lookback inr.lookback.push(*c); // 获得基础循环的结果 match c { // 当 CRLF 模式:`\r` 处理为下移一行 // 当 LF 模式:`\r` 不处理 '\r' => { if inr.cfg.ending_rule == LineEndingRule::CRLF { inr.row += 1; } // 因为行指针被移动,所以清理 Lookback inr.lookback.clear(); ParserMatchResult::Done } // 当 CRLF 模式:`\n` 为移动到行首 // 当 LF 模式:`\n` 为移动到下一行、然后移动到行首 '\n' => { if inr.cfg.ending_rule != LineEndingRule::CRLF { inr.row += 1; // 因为行指针被移动,所以清理 Lookback inr.lookback.clear(); } inr.col = 0; // 必须返回 `Sad`,原因如下: // 1. 若返回 `Done`,会导致 `col` 指针被右移 // 2. 因为 `post` 在最后处理,所以 `Done` 的字符跳过是无用的 ParserMatchResult::Sad } _ => ParserMatchResult::Sad, } } fn handle_syntax_error( raw: String, begin_col: u16, begin_row: u32, end_col: u16, end_row: u32, msg: String, ) -> MarkdownASTParseError { MarkdownASTParseError::SyntaxError { msg, raw, begin_col, begin_row, end_col, end_row, } }