267 lines
15 KiB
Rust
267 lines
15 KiB
Rust
//! 区分完整 Module 与顶层 Script,并保留输入源码位置。
|
||
//! 解析包装或片段拼接产生的 span 偏移要递归修正,保证诊断和 Sources 视图仍指向用户原始行列。
|
||
//!
|
||
//! runtime::endor_host 的一次性 compile 和持久会话加载调用 parse,再按 roots 进行 AST 预算检查与编译。
|
||
//! 语句、词法和 AST 定义属于 Aster;这里只把完整源码组织成 Module 或连续 Interactive 输入。
|
||
//! 片段先用局部字节位置解析,shift_* 递归加回起点,编译错误和 Sources 浏览才能仍对应原文件。
|
||
use anyhow::Result;
|
||
use frontend::{
|
||
ParseMode, SourceUnit, Span, TokenKind,
|
||
ast::{AstRoot, Decl, DeclKind, Expr, ExprKind, InteractiveInput, Stmt, StmtKind},
|
||
};
|
||
|
||
/// 待编译输入是完整模块还是顶层脚本;两者保留不同根结构但使用统一后续接口。
|
||
pub enum SourcePlan {
|
||
// 保留 Aster 完整模块根,后续作为一个编译/执行单元,不拆开函数或类的定义体。
|
||
Module(AstRoot),
|
||
// 顶层脚本按完整交互输入连续执行;各段仍共享所属运行时的全局环境,而非各建一个 VM。
|
||
Script(Vec<AstRoot>),
|
||
}
|
||
|
||
impl SourcePlan {
|
||
/// endor_host 的 AST 预算访问和 compile_plan 调用,借用统一根切片,不克隆 AST 或重新解析。
|
||
pub fn roots(&self) -> &[AstRoot] {
|
||
match self {
|
||
// 单模块以单元素切片适配,让调用方不必维护两套遍历分支。
|
||
Self::Module(root) => std::slice::from_ref(root),
|
||
// 脚本直接保留原输入段顺序,声明和后续使用语句的先后关系不能重排。
|
||
Self::Script(roots) => roots,
|
||
}
|
||
}
|
||
}
|
||
|
||
/// endor_host 执行入口调用,优先接受完整 Module,失败则按顶层换行候选边界拼出 Aster 可解析的交互输入。
|
||
/// 模块解析失败本身不立即作为最终错误:普通顶层脚本本就需要走后面的 Interactive 解析路径。
|
||
pub fn parse(source: &SourceUnit) -> Result<SourcePlan> {
|
||
// 完整模块优先;只有模块解析失败才退回连续交互输入模式,宿主不自行实现 Endor 语法。
|
||
if let Ok(module) = frontend::parse_source_ast(source, ParseMode::Module) {
|
||
// 模块成功就不再按行切分,避免破坏跨行声明与完整模块语义。
|
||
return Ok(SourcePlan::Module(module));
|
||
}
|
||
// 一次词法分析获得原始字节 span,候选分段依赖 token 之间的实际换行而非直接按行切字符串。
|
||
let tokens = frontend::lex_source(source)?;
|
||
// 只保存已被 Aster 成功接受的交互根,失败候选不留下半棵 AST。
|
||
let mut roots = Vec::new();
|
||
// start 为当前待解析片段在原文件中的字节起点,仅在候选成功时前移。
|
||
let mut start = 0;
|
||
// 深度只是候选边界启发式;括号失配是否合法仍由 Aster 判定,不在宿主偷偷修正。
|
||
let mut depth = 0_i64;
|
||
for (index, token) in tokens.iter().enumerate() {
|
||
// 追踪圆/花括号嵌套,括号内换行不能直接结束输入;最终语法是否合法仍交给 Aster。
|
||
match token.kind {
|
||
// token 已由词法器识别,字符串/注释里的括号不会当作结构括号改变深度。
|
||
TokenKind::LeftParen | TokenKind::LeftBrace => depth += 1,
|
||
TokenKind::RightParen | TokenKind::RightBrace => depth -= 1,
|
||
// 其他 token 不改变边界嵌套,不能靠普通字符扫描替代此判断。
|
||
_ => {}
|
||
}
|
||
// token 尾为原文本字节位置,用它取与下一 token 之间的空白/注释区域查换行。
|
||
let end = token.span.end;
|
||
// 末尾 token 总是尝试解析;非末尾要求顶层换行,且下一 token 不像成员访问、调用或算术续接。
|
||
let boundary = tokens.get(index + 1).is_none_or(|next| {
|
||
// 尚在括号内,或下一 token 明显续接上个表达式时,不尝试提前提交当前交互片段。
|
||
depth == 0
|
||
&& source.text()[end..next.span.start].contains(['\r', '\n'])
|
||
&& !matches!(
|
||
next.kind,
|
||
TokenKind::Dot
|
||
| TokenKind::LeftParen
|
||
| TokenKind::Plus
|
||
| TokenKind::Minus
|
||
| TokenKind::Star
|
||
| TokenKind::Slash
|
||
)
|
||
});
|
||
if !boundary {
|
||
// 保留 start,之后尝试更长的片段;不是丢弃当前 token 对应源码。
|
||
continue;
|
||
}
|
||
// 当前片段从上一次成功边界 start 开始,保留期间的空白与注释;解析器看到的是片段局部字节位置。
|
||
match frontend::parse_ast(&source.text()[start..end], ParseMode::Interactive) {
|
||
Ok(mut root) => {
|
||
// Interactive 模式的成功根必须为 Interactive;违反是前端接口不变量错误。
|
||
let AstRoot::Interactive(input) = &mut root else {
|
||
unreachable!("interactive mode");
|
||
};
|
||
// 成功后递归加回 start,把整个 AST 的局部 span 转成原文件位置,再推进下一段起点。
|
||
shift_input(input, start);
|
||
roots.push(root);
|
||
start = end;
|
||
}
|
||
// 错误恰在片段末尾且还有 token 时,视为可能未完成的交互输入,保持 start 不动继续拼接。
|
||
Err(diagnostic) if index + 1 < tokens.len() && diagnostic.span.start == end - start => {
|
||
// 只有“诊断正好指向片段末尾”才暂缓,下一候选会连同当前片段一起再交 Aster 解析。
|
||
}
|
||
Err(mut diagnostic) => {
|
||
// 其他语法错误立即恢复原始偏移,再用 SourceUnit 添加原文件来源与行列诊断。
|
||
shift_span(&mut diagnostic.span, start);
|
||
return Err(source.annotate(diagnostic).into());
|
||
}
|
||
}
|
||
}
|
||
// 空输入不能静默产生空计划;再次委托交互入口给出解析器自己的空输入诊断与来源。
|
||
if roots.is_empty() {
|
||
// 不自造宿主错误字符串,保留前端对空输入的原始诊断和 SourceUnit 来源信息。
|
||
frontend::parse_source_ast(source, ParseMode::Interactive)?;
|
||
}
|
||
Ok(SourcePlan::Script(roots))
|
||
}
|
||
|
||
/// 下方所有 shift_* 调用的基本操作,给单个半开字节范围加偏移,不递归、不改变源码内容。
|
||
fn shift_span(span: &mut Span, offset: usize) {
|
||
// span 采用原文本字节偏移,start/end 同增 offset 保持长度;不是按字符数或行数偏移。
|
||
span.start += offset;
|
||
span.end += offset;
|
||
}
|
||
|
||
/// parse 在接受一个 Interactive 根后调用,平移交互根范围,再把语句交给 shift_statement 递归。
|
||
fn shift_input(input: &mut InteractiveInput, offset: usize) {
|
||
shift_span(&mut input.span, offset);
|
||
// 根范围之外,逐条语句递归处理所有内层节点,避免错误只在顶层位置正确。
|
||
for statement in &mut input.statements {
|
||
shift_statement(statement, offset);
|
||
}
|
||
}
|
||
|
||
/// shift_input/函数体/分支体的递归入口,按语句种类平移自己的范围和所有内嵌表达式/声明。
|
||
fn shift_statement(statement: &mut Stmt, offset: usize) {
|
||
shift_span(&mut statement.span, offset);
|
||
match &mut statement.kind {
|
||
StmtKind::If(branch) => {
|
||
// If 节点、条件、then 块各有独立 span,平移外层 Stmt 不能自动更新它们。
|
||
shift_span(&mut branch.span, offset);
|
||
shift_expression(&mut branch.condition, offset);
|
||
shift_span(&mut branch.body.span, offset);
|
||
for s in &mut branch.body.statements {
|
||
// then 内可能继续嵌套 if/while/声明,不能只调整第一层语句外框。
|
||
shift_statement(s, offset);
|
||
}
|
||
if let Some(body) = &mut branch.otherwise {
|
||
// else 可省略;存在时块范围和其中每条语句都使用同一个原片段偏移。
|
||
shift_span(&mut body.span, offset);
|
||
for s in &mut body.statements {
|
||
shift_statement(s, offset);
|
||
}
|
||
}
|
||
}
|
||
StmtKind::While(loop_) => {
|
||
// while 条件和循环体均可含更深的调用/控制流,沿各自 AST 子结构递归而非重新扫描文本。
|
||
shift_span(&mut loop_.span, offset);
|
||
shift_expression(&mut loop_.condition, offset);
|
||
shift_span(&mut loop_.body.span, offset);
|
||
for s in &mut loop_.body.statements {
|
||
shift_statement(s, offset);
|
||
}
|
||
}
|
||
StmtKind::Binding(binding) => {
|
||
// 新绑定独立保存名字和右值范围,诊断未定义符号时需要内部表达式位置正确。
|
||
shift_span(&mut binding.span, offset);
|
||
shift_span(&mut binding.name.span, offset);
|
||
shift_expression(&mut binding.value, offset);
|
||
}
|
||
StmtKind::Assignment(assignment) => {
|
||
// 赋值不同于声明,但同样保留名字与右值两个来源范围,不能仅平移整个语句。
|
||
shift_span(&mut assignment.span, offset);
|
||
shift_span(&mut assignment.name.span, offset);
|
||
shift_expression(&mut assignment.value, offset);
|
||
}
|
||
// 成员赋值同时包含目标对象表达式、成员名和右值,三部分都要恢复原始位置。
|
||
StmtKind::MemberAssignment(assignment) => {
|
||
shift_span(&mut assignment.span, offset);
|
||
shift_span(&mut assignment.name.span, offset);
|
||
shift_expression(&mut assignment.object, offset);
|
||
shift_expression(&mut assignment.value, offset);
|
||
}
|
||
StmtKind::Return(returned) => {
|
||
// return 自身 span 必须平移;无返回表达式的 Unit 返回不能访问不存在的 value。
|
||
shift_span(&mut returned.span, offset);
|
||
if let Some(expression) = &mut returned.value {
|
||
// 返回值可以是任意复合调用/算术表达式,继续下钻到全部名称和实参。
|
||
shift_expression(expression, offset);
|
||
}
|
||
}
|
||
// 表达式语句复用完整表达式递归,避免调用参数中的错误仍显示片段局部行列。
|
||
StmtKind::Expression(expression) => shift_expression(expression, offset),
|
||
// 顶层/局部声明转交类与函数专用入口,处理参数、类型名及函数体。
|
||
StmtKind::Declaration(declaration) => shift_declaration(declaration, offset),
|
||
}
|
||
}
|
||
|
||
/// shift_statement 在声明语句中调用,平移 Decl 外层,再区分函数定义与类定义的内部位置。
|
||
fn shift_declaration(declaration: &mut Decl, offset: usize) {
|
||
shift_span(&mut declaration.span, offset);
|
||
match &mut declaration.kind {
|
||
// 普通函数和类方法使用相同函数结构,统一由 shift_function 平移签名和函数体。
|
||
DeclKind::Function(function) => shift_function(function, offset),
|
||
// 类的名称、基类、字段类型和方法均保存独立 span,不能仅移动声明外层范围。
|
||
DeclKind::Class(class) => {
|
||
shift_span(&mut class.span, offset);
|
||
shift_span(&mut class.name.span, offset);
|
||
if let Some(base) = &mut class.base {
|
||
// 无显式基类时不新增默认基类 span,保留前端原始 AST 的可选结构。
|
||
shift_span(&mut base.span, offset);
|
||
}
|
||
for field in &mut class.fields {
|
||
// 字段节点整体、字段名、类型名分别保存位置,错误类型注解必须指回原文件。
|
||
shift_span(&mut field.span, offset);
|
||
shift_span(&mut field.name.span, offset);
|
||
shift_span(&mut field.type_name.span, offset);
|
||
}
|
||
for method in &mut class.methods {
|
||
// 方法体可能包含更多声明,沿同一套递归入口继续处理。
|
||
shift_function(method, offset);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/// shift_declaration 调用,平移函数名、参数/返回注解和函数体,供编译器类型错误使用原输入坐标。
|
||
fn shift_function(function: &mut frontend::ast::Function, offset: usize) {
|
||
shift_span(&mut function.span, offset);
|
||
shift_span(&mut function.name.span, offset);
|
||
shift_span(&mut function.return_type.span, offset);
|
||
// 参数自身、名称、类型各有范围;函数返回类型和函数体范围也独立移动。
|
||
for parameter in &mut function.parameters {
|
||
shift_span(&mut parameter.span, offset);
|
||
shift_span(&mut parameter.name.span, offset);
|
||
shift_span(&mut parameter.type_name.span, offset);
|
||
}
|
||
shift_span(&mut function.body.span, offset);
|
||
for statement in &mut function.body.statements {
|
||
// 函数体内每条语句继续按 kind 递归,不能用整体函数范围覆盖内部诊断。
|
||
shift_statement(statement, offset);
|
||
}
|
||
}
|
||
|
||
/// 语句/函数递归助手调用,平移表达式与名称成员的范围;语义值和运算符本身保持不变。
|
||
fn shift_expression(expression: &mut Expr, offset: usize) {
|
||
shift_span(&mut expression.span, offset);
|
||
match &mut expression.kind {
|
||
// 这些叶节点只用表达式外层 span;Symbol/Member 的名称还保存额外 span,需继续调整。
|
||
ExprKind::Name(_) | ExprKind::Number(_) | ExprKind::String(_) => {}
|
||
ExprKind::Symbol(name) => shift_span(&mut name.span, offset),
|
||
ExprKind::Unary { operand, .. } | ExprKind::Await(operand) => {
|
||
// unary 与 await 都只有一个子表达式,操作符来源已在外层表达式 span 覆盖。
|
||
shift_expression(operand, offset)
|
||
}
|
||
ExprKind::Binary { left, right, .. } => {
|
||
// 两侧都可能含复合表达式,不能只调整最终报错概率更高的右侧。
|
||
shift_expression(left, offset);
|
||
shift_expression(right, offset);
|
||
}
|
||
ExprKind::Member { object, name } => {
|
||
// object 可能是调用返回值,name 则是独立标识符,分别调整两类来源。
|
||
shift_expression(object, offset);
|
||
shift_span(&mut name.span, offset);
|
||
}
|
||
// 调用目标可继续是成员/调用等复合表达式;逐个递归实参而非只改整个 Call 的范围。
|
||
ExprKind::Call { callee, arguments } => {
|
||
shift_expression(callee, offset);
|
||
for argument in arguments {
|
||
// 保持实参顺序和值不变,只平移每个实参树内部保存的字节位置。
|
||
shift_expression(argument, offset);
|
||
}
|
||
}
|
||
}
|
||
}
|