Files
endorphin-tessera-gui/src/runtime/endor_script.rs
T

268 lines
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 区分完整 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 解析路径。
/// 这里只产出 AST 计划,不执行已接受的片段;endor_host 先完成所有根的预算检查和编译,再进入源代码执行。
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() {
// 不自造宿主错误字符串;失败则保留前端诊断,成功也不把这次仅用于校验的根追加到 roots。
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);
}
}
}
}