From 7525fe0834e47bef425135e8cda1d576c44060a5 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Thu, 23 Apr 2026 16:57:33 +0800 Subject: Initialize Rust project with Markdown AST structure --- src/ast/parser.rs | 281 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 src/ast/parser.rs (limited to 'src/ast/parser.rs') diff --git a/src/ast/parser.rs b/src/ast/parser.rs new file mode 100644 index 0000000..9add926 --- /dev/null +++ b/src/ast/parser.rs @@ -0,0 +1,281 @@ +use std::{any::Any, collections::HashMap, str::Chars}; + +use crate::ast::{Layer, Line, MarkdownAST, Token}; + +pub mod headings; + +type ProcessFn = fn(&char, &mut ParserInternalStatus) -> ParserMatchResult; + +fn match_fn_list() -> Vec { + // 要求以 预处理、词、行、层、后处理 的顺序编写列表 + // 因为 词处理器 将会写入 records_tokens 由 行处理器 消费 + // 接着 行处理器 将会写入 records_lines 由 层处理器 消费 + // 最后 层处理器 将所有行写入当前层中 + vec![ + // 预处理器 + // ... + // 词处理器 + // ... + // 行处理器 + 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, + + /// 临时类型表 + 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: &str, + cfg: MarkdownParserConfig, +) -> Result { + // 创建空 AST,无任何内容 + let ast = MarkdownAST { + root: Layer { + range_row_begin: 0, + range_row_end: 0, + lines: Vec::new(), + }, + }; + + // 初始化内部状态 + 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(), + 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; + + 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') { + // 跳过当前步骤前,提前将列指针右移 + 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; + } + + // 将列指针右移 + 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, + } +} -- cgit