summaryrefslogtreecommitdiff
path: root/src/ast/parser.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-04-23 16:57:33 +0800
committer魏曹先生 <1992414357@qq.com>2026-04-23 16:57:33 +0800
commit7525fe0834e47bef425135e8cda1d576c44060a5 (patch)
tree0d9367b7f0aa0b3542f165095c10ab698b3b2c05 /src/ast/parser.rs
parent277bc93f84b298c7cb24e136f67eb237fb3a68a2 (diff)
Initialize Rust project with Markdown AST structure
Diffstat (limited to 'src/ast/parser.rs')
-rw-r--r--src/ast/parser.rs281
1 files changed, 281 insertions, 0 deletions
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<ProcessFn> {
+ // 要求以 预处理、词、行、层、后处理 的顺序编写列表
+ // 因为 词处理器 将会写入 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<Line>,
+
+ /// 记录的 Token,用于暂存无归属的 Token
+ records_tokens: Vec<Token>,
+
+ /// 临时类型表
+ tmp: HashMap<&'a str, Box<dyn Any>>,
+}
+
+impl<'a> ParserInternalStatus<'a> {
+ /// 通过字符键初始化或获取临时值
+ pub(crate) fn get_tmp_or_init<T>(&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<MarkdownAST, MarkdownASTParseError> {
+ // 创建空 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<ProcessFn> = match_fn_list();
+
+ // 放弃列表
+ // > 为什么不用 HashSet?
+ // > `aborted` 在整个生命周期内数量最大不超过 100 条
+ // > HashSet 虽然 O(1) 但是缓存不友好,更推荐使用 O(n) 线性扫描的 Vec 来处理
+ let mut aborted: Vec<u8> = 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,
+ }
+}