feat: IDE支持 — workspace拆分、LSP服务器、VS Code扩展

**Workspace 拆分**
- aster-core: 纯库,zero-dependency,包含 lexer/parser/ast/interpreter/error/analysis
- aster: REPL 二进制,薄封装 aster-core
- aster-lsp: LSP 语言服务器

**VS Code 扩展 (vscode-ext/)**
- TextMate 语法高亮 (.ast 文件)
- 语言配置 (注释切换、括号配对、自动缩进)
- LSP 客户端 (extension.js)

**LSP 服务端功能**
- Diagnostics: 实时显示 lex/parse 错误红色波浪线
- Completion: 关键字 + 内置函数 + 用户定义符号补全
- Hover: 悬停显示变量/函数信息
- Signature Help: 函数参数提示
- Goto Definition: Ctrl+Click 跳转到声明处
- Find References: 查找所有引用位置
- Rename: F2 重命名符号

**新增 analysis 模块**
- collect_symbols: AST 遍历收集符号
- find_declaration/find_all_references: Token 扫描定位声明和引用
This commit is contained in:
2026-06-25 00:28:13 +08:00
parent 99c9e9c9bd
commit bdffe9e3c5
65 changed files with 1709 additions and 47 deletions
+244
View File
@@ -0,0 +1,244 @@
use crate::ast::{Expr, Stmt};
use crate::lexer::{Token, TokenKind};
/// A symbol collected from walking the AST.
#[derive(Debug, Clone)]
pub struct Symbol {
pub name: String,
pub kind: SymbolKind,
}
#[derive(Debug, Clone)]
pub enum SymbolKind {
Variable,
Function { params: Vec<String> },
}
/// Collect all declared symbols from the given statements.
/// Walks into nested scopes but does NOT perform scope-aware filtering —
/// that's the caller's job (e.g. the LSP completion handler).
pub fn collect_symbols(stmts: &[Stmt]) -> Vec<Symbol> {
let mut symbols = Vec::new();
collect_stmts(stmts, &mut symbols);
symbols
}
fn collect_stmts(stmts: &[Stmt], symbols: &mut Vec<Symbol>) {
for stmt in stmts {
collect_stmt(stmt, symbols);
}
}
fn collect_stmt(stmt: &Stmt, symbols: &mut Vec<Symbol>) {
match stmt {
Stmt::Let { name, initializer, .. } => {
symbols.push(Symbol {
name: name.clone(),
kind: SymbolKind::Variable,
});
collect_expr(initializer, symbols);
}
Stmt::Function { name, params, body } => {
symbols.push(Symbol {
name: name.clone(),
kind: SymbolKind::Function {
params: params.clone(),
},
});
for p in params {
symbols.push(Symbol {
name: p.clone(),
kind: SymbolKind::Variable,
});
}
collect_stmts(body, symbols);
}
Stmt::Block(body) => collect_stmts(body, symbols),
Stmt::If {
condition,
then_branch,
else_branch,
} => {
collect_expr(condition, symbols);
collect_stmt(then_branch, symbols);
if let Some(else_b) = else_branch {
collect_stmt(else_b, symbols);
}
}
Stmt::While { condition, body } => {
collect_expr(condition, symbols);
collect_stmt(body, symbols);
}
Stmt::For {
initializer,
condition,
step,
body,
} => {
if let Some(init) = initializer {
collect_stmt(init, symbols);
}
if let Some(cond) = condition {
collect_expr(cond, symbols);
}
if let Some(step_expr) = step {
collect_expr(step_expr, symbols);
}
collect_stmt(body, symbols);
}
Stmt::ForIn {
var_name,
iterable,
body,
} => {
symbols.push(Symbol {
name: var_name.clone(),
kind: SymbolKind::Variable,
});
collect_expr(iterable, symbols);
collect_stmt(body, symbols);
}
Stmt::ExprStmt(expr) | Stmt::Return(Some(expr)) => {
collect_expr(expr, symbols);
}
Stmt::Return(None) | Stmt::Break | Stmt::Continue => {}
}
}
fn collect_expr(expr: &Expr, symbols: &mut Vec<Symbol>) {
match expr {
Expr::Literal(_) | Expr::Variable(_) => {}
Expr::Assign { value, .. } => collect_expr(value, symbols),
Expr::Get { object, .. } => collect_expr(object, symbols),
Expr::Set { object, value, .. } => {
collect_expr(object, symbols);
collect_expr(value, symbols);
}
Expr::ObjectLiteral { properties } => {
for (_, val) in properties {
collect_expr(val, symbols);
}
}
Expr::ArrayLiteral { elements } => {
for elem in elements {
collect_expr(elem, symbols);
}
}
Expr::IndexGet { array, index } => {
collect_expr(array, symbols);
collect_expr(index, symbols);
}
Expr::IndexSet {
array,
index,
value,
..
} => {
collect_expr(array, symbols);
collect_expr(index, symbols);
collect_expr(value, symbols);
}
Expr::Unary { right, .. } => collect_expr(right, symbols),
Expr::Binary { left, right, .. } | Expr::Logical { left, right, .. } => {
collect_expr(left, symbols);
collect_expr(right, symbols);
}
Expr::Ternary {
condition,
then_branch,
else_branch,
} => {
collect_expr(condition, symbols);
collect_expr(then_branch, symbols);
collect_expr(else_branch, symbols);
}
Expr::Call {
callee,
arguments,
} => {
collect_expr(callee, symbols);
for arg in arguments {
collect_expr(arg, symbols);
}
}
Expr::Lambda { params, body } => {
for p in params {
symbols.push(Symbol {
name: p.clone(),
kind: SymbolKind::Variable,
});
}
collect_stmts(body, symbols);
}
}
}
// ── Token-based position lookups ──────────────────────────────────
/// Scan tokens for the declaration site of `name`.
/// Recognises `let name`, `const name`, `fn name`, and `for (name in …)`.
/// Returns 1-based (line, column).
pub fn find_declaration(tokens: &[Token], name: &str) -> Option<(usize, usize)> {
let len = tokens.len();
let mut i = 0;
while i < len {
match &tokens[i].kind {
TokenKind::Let | TokenKind::Const => {
if let Some(tok) = tokens.get(i + 1) {
if let TokenKind::Identifier(n) = &tok.kind {
if n == name {
return Some((tok.line, tok.column));
}
}
}
}
TokenKind::Fn => {
// fn name( ... or fn name { ...
if let Some(tok) = tokens.get(i + 1) {
if let TokenKind::Identifier(n) = &tok.kind {
if n == name {
return Some((tok.line, tok.column));
}
}
}
}
TokenKind::For => {
// for ( name in ... )
// Skip past LeftParen
let mut j = i + 1;
if j < len && tokens[j].kind == TokenKind::LeftParen {
j += 1;
}
if j < len {
if let TokenKind::Identifier(n) = &tokens[j].kind {
if n == name {
return Some((tokens[j].line, tokens[j].column));
}
}
}
}
_ => {}
}
i += 1;
}
None
}
/// Scan tokens for every occurrence of `Identifier(name)`.
/// Returns 1-based (line, column) for each match.
pub fn find_all_references(tokens: &[Token], name: &str) -> Vec<(usize, usize)> {
tokens
.iter()
.filter_map(|tok| {
if let TokenKind::Identifier(n) = &tok.kind {
if n == name {
Some((tok.line, tok.column))
} else {
None
}
} else {
None
}
})
.collect()
}