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
@@ -0,0 +1,203 @@
//! 标准库:stringsplit, trim, substring, replace, contains, upper, lower, starts_with, ends_with
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use std::cell::RefCell;
use std::rc::Rc;
fn require_string(val: &Value, arg_name: &str) -> Result<String, RuntimeError> {
match val {
Value::String(s) => Ok(s.clone()),
_ => Err(RuntimeError::RuntimeError {
message: format!("{} must be a string", arg_name),
token: None,
}),
}
}
// ============================================================================
// split(str, delim) → array of substrings
// ============================================================================
pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "split() expects 2 arguments (string, delimiter)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let delim = require_string(&args[1], "Second argument (delimiter)")?;
let parts: Vec<Value> = if delim.is_empty() {
// Split by empty string → characters
s.chars().map(|c| Value::String(c.to_string())).collect()
} else {
s.split(&delim).map(|p| Value::String(p.to_string())).collect()
};
Ok(Value::Array(Rc::new(RefCell::new(parts))))
}
// ============================================================================
// trim(str) → string with leading/trailing whitespace removed
// ============================================================================
pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "trim() expects 1 argument (string)".into(),
token: None,
});
}
let s = require_string(&args[0], "Argument")?;
Ok(Value::String(s.trim().to_string()))
}
// ============================================================================
// substring(str, start, len?) → substring
// ============================================================================
pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let start = as_usize(&args[1], "Second argument (start)")?;
let chars: Vec<char> = s.chars().collect();
let start = start.min(chars.len());
let end = if args.len() >= 3 {
(start + as_usize(&args[2], "Third argument (length)")?).min(chars.len())
} else {
chars.len()
};
Ok(Value::String(chars[start..end].iter().collect()))
}
// ============================================================================
// replace(str, old, new) → string with all occurrences replaced
// ============================================================================
pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 3 {
return Err(RuntimeError::RuntimeError {
message: "replace() expects 3 arguments (string, old, new)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let old = require_string(&args[1], "Second argument (old)")?;
let new = require_string(&args[2], "Third argument (new)")?;
if old.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "replace(): 'old' must not be empty".into(),
token: None,
});
}
Ok(Value::String(s.replace(&old, &new)))
}
// ============================================================================
// contains(str, needle) → bool
// ============================================================================
pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "contains() expects 2 arguments (string, needle)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let needle = require_string(&args[1], "Second argument (needle)")?;
Ok(Value::Bool(s.contains(&needle)))
}
// ============================================================================
// upper(str) → uppercase (ASCII)
// ============================================================================
pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "upper() expects 1 argument (string)".into(),
token: None,
});
}
let s = require_string(&args[0], "Argument")?;
Ok(Value::String(s.to_ascii_uppercase()))
}
// ============================================================================
// lower(str) → lowercase (ASCII)
// ============================================================================
pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "lower() expects 1 argument (string)".into(),
token: None,
});
}
let s = require_string(&args[0], "Argument")?;
Ok(Value::String(s.to_ascii_lowercase()))
}
// ============================================================================
// starts_with(str, prefix) → bool
// ============================================================================
pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "starts_with() expects 2 arguments (string, prefix)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let prefix = require_string(&args[1], "Second argument (prefix)")?;
Ok(Value::Bool(s.starts_with(&prefix)))
}
// ============================================================================
// ends_with(str, suffix) → bool
// ============================================================================
pub fn ends_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "ends_with() expects 2 arguments (string, suffix)".into(),
token: None,
});
}
let s = require_string(&args[0], "First argument")?;
let suffix = require_string(&args[1], "Second argument (suffix)")?;
Ok(Value::Bool(s.ends_with(&suffix)))
}
// ============================================================================
// Helper
// ============================================================================
fn as_usize(val: &Value, arg_name: &str) -> Result<usize, RuntimeError> {
match val {
Value::Number(n) => {
if *n < 0.0 || n.fract() != 0.0 {
return Err(RuntimeError::RuntimeError {
message: format!("{} must be a non-negative integer, got {}", arg_name, n),
token: None,
});
}
Ok(*n as usize)
}
_ => Err(RuntimeError::RuntimeError {
message: format!("{} must be a number", arg_name),
token: None,
}),
}
}