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
+3
View File
@@ -0,0 +1,3 @@
pub mod parser;
pub use parser::Parser;
+630
View File
@@ -0,0 +1,630 @@
use crate::ast::*;
use crate::lexer::{Token, TokenKind};
use crate::error::RuntimeError;
pub struct Parser {
tokens: Vec<Token>,
current: usize,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self { tokens, current: 0 }
}
pub fn parse(&mut self) -> (Vec<Stmt>, Vec<RuntimeError>) {
let mut statements = Vec::new();
let mut errors = Vec::new();
while !self.is_at_end() {
match self.declaration() {
Ok(stmt) => statements.push(stmt),
Err(err) => {
errors.push(err);
self.synchronize();
}
}
}
(statements, errors)
}
fn declaration(&mut self) -> Result<Stmt, RuntimeError> {
if self.match_kind(&[TokenKind::Let]) {
self.let_declaration(true)
} else if self.match_kind(&[TokenKind::Const]) {
self.let_declaration(false)
} else if self.match_kind(&[TokenKind::Fn]) {
self.fn_declaration()
} else {
self.statement()
}
}
fn statement(&mut self) -> Result<Stmt, RuntimeError> {
if self.match_kind(&[TokenKind::If]) {
self.if_statement()
} else if self.match_kind(&[TokenKind::While]) {
self.while_statement()
} else if self.match_kind(&[TokenKind::For]) {
self.for_statement()
} else if self.match_kind(&[TokenKind::Break]) {
self.break_statement()
} else if self.match_kind(&[TokenKind::Continue]) {
self.continue_statement()
} else if self.match_kind(&[TokenKind::Return]) {
self.return_statement()
} else if self.match_kind(&[TokenKind::LeftBrace]) {
Ok(Stmt::Block(self.block()?))
} else {
self.expression_statement()
}
}
fn let_declaration(&mut self, mutable: bool) -> Result<Stmt, RuntimeError> {
let name = self.consume_ident("Expected variable name.")?;
self.consume(TokenKind::Equal, "Expected '=' after variable name.")?;
let initializer = self.expression()?;
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
Ok(Stmt::Let { name, initializer, mutable })
}
fn fn_declaration(&mut self) -> Result<Stmt, RuntimeError> {
let name = self.consume_ident("Expected function name.")?;
self.consume(TokenKind::LeftParen, "Expected '(' after function name.")?;
let mut params = Vec::new();
if !self.check(&TokenKind::RightParen) {
loop {
params.push(self.consume_ident("Expected parameter name.")?);
if !self.match_kind(&[TokenKind::Comma]) {
break;
}
}
}
self.consume(TokenKind::RightParen, "Expected ')' after parameters.")?;
self.consume(TokenKind::LeftBrace, "Expected '{' before function body.")?;
let body = self.block()?;
Ok(Stmt::Function { name, params, body })
}
fn lambda_expr(&mut self) -> Result<Expr, RuntimeError> {
self.consume(TokenKind::LeftParen, "Expected '(' after 'fn'.")?;
let mut params = Vec::new();
if !self.check(&TokenKind::RightParen) {
loop {
params.push(self.consume_ident("Expected parameter name.")?);
if !self.match_kind(&[TokenKind::Comma]) {
break;
}
}
}
self.consume(TokenKind::RightParen, "Expected ')' after parameters.")?;
self.consume(TokenKind::LeftBrace, "Expected '{' before function body.")?;
let body = self.block()?;
Ok(Expr::Lambda { params, body })
}
fn if_statement(&mut self) -> Result<Stmt, RuntimeError> {
self.consume(TokenKind::LeftParen, "Expected '(' after 'if'.")?;
let condition = self.expression()?;
self.consume(TokenKind::RightParen, "Expected ')' after condition.")?;
let then_branch = Box::new(self.statement()?);
let else_branch = if self.match_kind(&[TokenKind::Else]) {
Some(Box::new(self.statement()?))
} else {
None
};
Ok(Stmt::If { condition, then_branch, else_branch })
}
fn while_statement(&mut self) -> Result<Stmt, RuntimeError> {
self.consume(TokenKind::LeftParen, "Expected '(' after 'while'.")?;
let condition = self.expression()?;
self.consume(TokenKind::RightParen, "Expected ')' after condition.")?;
let body = Box::new(self.statement()?);
Ok(Stmt::While { condition, body })
}
fn for_statement(&mut self) -> Result<Stmt, RuntimeError> {
self.consume(TokenKind::LeftParen, "Expected '(' after 'for'.")?;
// Detect for-in: for (Identifier in expr) body
if let TokenKind::Identifier(var_name) = &self.peek().kind {
let var_name = var_name.clone();
if matches!(self.peek_next().kind, TokenKind::In) {
self.advance(); // consume identifier
self.advance(); // consume 'in'
let iterable = self.expression()?;
self.consume(TokenKind::RightParen, "Expected ')' after for-in expression.")?;
let body = Box::new(self.statement()?);
return Ok(Stmt::ForIn { var_name, iterable, body });
}
}
let initializer = if self.match_kind(&[TokenKind::Semicolon]) {
None
} else if self.match_kind(&[TokenKind::Let]) {
Some(self.let_declaration(true)?)
} else {
Some(self.expression_statement()?)
};
let condition = if !self.check(&TokenKind::Semicolon) {
Some(self.expression()?)
} else {
None
};
self.consume(TokenKind::Semicolon, "Expected ';' after loop condition.")?;
let step = if !self.check(&TokenKind::RightParen) {
Some(self.expression()?)
} else {
None
};
self.consume(TokenKind::RightParen, "Expected ')' after for clauses.")?;
let body = Box::new(self.statement()?);
Ok(Stmt::For {
initializer: initializer.map(Box::new),
condition,
step,
body
})
}
fn break_statement(&mut self) -> Result<Stmt, RuntimeError> {
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
Ok(Stmt::Break)
}
fn continue_statement(&mut self) -> Result<Stmt, RuntimeError> {
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
Ok(Stmt::Continue)
}
fn return_statement(&mut self) -> Result<Stmt, RuntimeError> {
let value = if !self.check(&TokenKind::Semicolon) && !self.check(&TokenKind::RightBrace) {
Some(self.expression()?)
} else {
None
};
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
Ok(Stmt::Return(value))
}
fn block(&mut self) -> Result<Vec<Stmt>, RuntimeError> {
let mut statements = Vec::new();
while !self.check(&TokenKind::RightBrace) && !self.is_at_end() {
statements.push(self.declaration()?);
}
self.consume(TokenKind::RightBrace, "Expected '}' after block.")?;
Ok(statements)
}
fn expression_statement(&mut self) -> Result<Stmt, RuntimeError> {
let expr = self.expression()?;
self.match_kind(&[TokenKind::Semicolon]); // 分号可选
Ok(Stmt::ExprStmt(expr))
}
fn expression(&mut self) -> Result<Expr, RuntimeError> {
self.assignment()
}
fn ternary(&mut self) -> Result<Expr, RuntimeError> {
let expr = self.logical_or()?;
if self.match_kind(&[TokenKind::Question]) {
let then_branch = self.assignment()?;
self.consume(TokenKind::Colon, "Expected ':' after then branch of ternary.")?;
let else_branch = self.assignment()?;
Ok(Expr::Ternary {
condition: Box::new(expr),
then_branch: Box::new(then_branch),
else_branch: Box::new(else_branch),
})
} else {
Ok(expr)
}
}
fn assignment(&mut self) -> Result<Expr, RuntimeError> {
let expr = self.ternary()?;
let op = if self.match_kind(&[TokenKind::Equal]) {
Some(AssignOp::Equal)
} else if self.match_kind(&[TokenKind::PlusEqual]) {
Some(AssignOp::PlusEqual)
} else if self.match_kind(&[TokenKind::MinusEqual]) {
Some(AssignOp::MinusEqual)
} else if self.match_kind(&[TokenKind::StarEqual]) {
Some(AssignOp::StarEqual)
} else if self.match_kind(&[TokenKind::SlashEqual]) {
Some(AssignOp::SlashEqual)
} else if self.match_kind(&[TokenKind::PercentEqual]) {
Some(AssignOp::PercentEqual)
} else {
None
};
if let Some(op) = op {
let value = self.assignment()?;
return match expr {
Expr::Variable(name) => Ok(Expr::Assign { name, op, value: Box::new(value) }),
Expr::Get { object, name } => Ok(Expr::Set { object, name, op, value: Box::new(value) }),
Expr::IndexGet { array, index } => Ok(Expr::IndexSet { array, index, op, value: Box::new(value) }),
_ => Err(RuntimeError::parse("Invalid assignment target.", self.previous().clone())),
};
}
Ok(expr)
}
fn logical_or(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.logical_and()?;
while self.match_kind(&[TokenKind::OrOr]) {
let right = self.logical_and()?;
expr = Expr::Logical {
left: Box::new(expr),
op: LogicalOp::Or,
right: Box::new(right),
};
}
Ok(expr)
}
fn logical_and(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.equality()?;
while self.match_kind(&[TokenKind::AndAnd]) {
let right = self.equality()?;
expr = Expr::Logical {
left: Box::new(expr),
op: LogicalOp::And,
right: Box::new(right),
};
}
Ok(expr)
}
fn equality(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.comparison()?;
while self.match_kind(&[TokenKind::EqualEqual, TokenKind::BangEqual]) {
let op = match self.previous().kind {
TokenKind::EqualEqual => BinaryOp::Equal,
TokenKind::BangEqual => BinaryOp::NotEqual,
_ => unreachable!(),
};
let right = self.comparison()?;
expr = Expr::Binary {
left: Box::new(expr),
op,
right: Box::new(right),
};
}
Ok(expr)
}
fn comparison(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.term()?;
while self.match_kind(&[TokenKind::Greater, TokenKind::GreaterEqual, TokenKind::Less, TokenKind::LessEqual]) {
let op = match self.previous().kind {
TokenKind::Greater => BinaryOp::Greater,
TokenKind::GreaterEqual => BinaryOp::GreaterEqual,
TokenKind::Less => BinaryOp::Less,
TokenKind::LessEqual => BinaryOp::LessEqual,
_ => unreachable!(),
};
let right = self.term()?;
expr = Expr::Binary {
left: Box::new(expr),
op,
right: Box::new(right),
};
}
Ok(expr)
}
fn term(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.factor()?;
while self.match_kind(&[TokenKind::Plus, TokenKind::Minus]) {
let op = match self.previous().kind {
TokenKind::Plus => BinaryOp::Add,
TokenKind::Minus => BinaryOp::Sub,
_ => unreachable!(),
};
let right = self.factor()?;
expr = Expr::Binary {
left: Box::new(expr),
op,
right: Box::new(right),
};
}
Ok(expr)
}
fn factor(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.unary()?;
while self.match_kind(&[TokenKind::Star, TokenKind::Slash, TokenKind::Percent]) {
let op = match self.previous().kind {
TokenKind::Star => BinaryOp::Mul,
TokenKind::Slash => BinaryOp::Div,
TokenKind::Percent => BinaryOp::Mod,
_ => unreachable!(),
};
let right = self.unary()?;
expr = Expr::Binary {
left: Box::new(expr),
op,
right: Box::new(right),
};
}
Ok(expr)
}
fn unary(&mut self) -> Result<Expr, RuntimeError> {
if self.match_kind(&[TokenKind::Bang, TokenKind::Minus]) {
let op = match self.previous().kind {
TokenKind::Bang => UnaryOp::Not,
TokenKind::Minus => UnaryOp::Negate,
_ => unreachable!(),
};
let right = self.unary()?;
return Ok(Expr::Unary {
op,
right: Box::new(right),
});
}
self.call()
}
fn call(&mut self) -> Result<Expr, RuntimeError> {
let mut expr = self.primary()?;
loop {
if self.match_kind(&[TokenKind::LeftParen]) {
let mut arguments = Vec::new();
if !self.check(&TokenKind::RightParen) {
loop {
arguments.push(self.expression()?);
if !self.match_kind(&[TokenKind::Comma]) {
break;
}
}
}
self.consume(TokenKind::RightParen, "Expected ')' after arguments.")?;
expr = Expr::Call {
callee: Box::new(expr),
arguments,
};
} else if self.match_kind(&[TokenKind::Dot]) {
let name = self.consume_ident("Expected property name after '.'.")?;
expr = Expr::Get {
object: Box::new(expr),
name,
};
} else if self.match_kind(&[TokenKind::LeftBracket]) {
let index = self.expression()?;
self.consume(TokenKind::RightBracket, "Expected ']' after index.")?;
expr = Expr::IndexGet {
array: Box::new(expr),
index: Box::new(index),
};
} else {
break;
}
}
Ok(expr)
}
fn primary(&mut self) -> Result<Expr, RuntimeError> {
// 布尔值
if self.match_kind(&[TokenKind::True]) {
return Ok(Expr::Literal(Literal::Bool(true)));
}
if self.match_kind(&[TokenKind::False]) {
return Ok(Expr::Literal(Literal::Bool(false)));
}
if self.match_kind(&[TokenKind::Nil]) {
return Ok(Expr::Literal(Literal::Nil));
}
// 数字
if let TokenKind::Number(n) = self.peek().kind {
self.advance();
return Ok(Expr::Literal(Literal::Number(n)));
}
// 字符串
if let TokenKind::String(s) = &self.peek().kind {
let s = s.clone();
self.advance();
return Ok(Expr::Literal(Literal::String(s)));
}
// 标识符(变量)
if let TokenKind::Identifier(name) = &self.peek().kind {
let name = name.clone();
self.advance();
return Ok(Expr::Variable(name));
}
// 匿名函数(闭包): fn(params) { body }
if self.match_kind(&[TokenKind::Fn]) {
return self.lambda_expr();
}
// 括号表达式
if self.match_kind(&[TokenKind::LeftParen]) {
let expr = self.expression()?;
self.consume(TokenKind::RightParen, "Expected ')' after expression.")?;
return Ok(expr);
}
if self.match_kind(&[TokenKind::LeftBrace]) {
return self.object_literal();
}
if self.match_kind(&[TokenKind::LeftBracket]) {
return self.array_literal();
}
Err(RuntimeError::parse("Expected expression.", self.peek().clone()))
}
fn match_kind(&mut self, kinds: &[TokenKind]) -> bool {
for kind in kinds {
if self.check(kind) {
self.advance();
return true;
}
}
false
}
fn consume(&mut self, kind: TokenKind, msg: &str) -> Result<(), RuntimeError> {
if self.check(&kind) {
self.advance();
Ok(())
} else {
Err(RuntimeError::parse(msg, self.peek().clone()))
}
}
fn consume_ident(&mut self, msg: &str) -> Result<String, RuntimeError> {
match &self.peek().kind {
TokenKind::Identifier(name) => {
let name = name.clone();
self.advance();
Ok(name)
}
_ => Err(RuntimeError::parse(msg, self.peek().clone())),
}
}
fn check(&self, kind: &TokenKind) -> bool {
if self.is_at_end() {
return false;
}
std::mem::discriminant(&self.peek().kind)
== std::mem::discriminant(kind)
}
fn advance(&mut self) -> &Token {
if !self.is_at_end() {
self.current += 1;
}
self.previous()
}
fn is_at_end(&self) -> bool {
matches!(self.peek().kind, TokenKind::EOF)
}
fn peek(&self) -> &Token {
&self.tokens[self.current]
}
fn peek_next(&self) -> &Token {
&self.tokens[self.current + 1]
}
fn previous(&self) -> &Token {
&self.tokens[self.current - 1]
}
fn object_literal(&mut self) -> Result<Expr, RuntimeError> {
let mut properties = Vec::new();
if !self.check(&TokenKind::RightBrace) {
loop {
let name = self.consume_ident("Expected property name in object literal")?;
self.consume(TokenKind::Colon, "Expected ':' after property name in object literal")?;
let value = self.expression()?;
properties.push((name, value));
if !self.match_kind(&[TokenKind::Comma]) {
break;
}
// Allow trailing comma
if self.check(&TokenKind::RightBrace) {
break;
}
}
}
self.consume(TokenKind::RightBrace, "Expected '}' after object literal")?;
Ok(Expr::ObjectLiteral { properties })
}
fn array_literal(&mut self) -> Result<Expr, RuntimeError> {
let mut elements = Vec::new();
if !self.check(&TokenKind::RightBracket) {
loop {
elements.push(self.expression()?);
if !self.match_kind(&[TokenKind::Comma]) {
break;
}
// Allow trailing comma
if self.check(&TokenKind::RightBracket) {
break;
}
}
}
self.consume(TokenKind::RightBracket, "Expected ']' after array literal")?;
Ok(Expr::ArrayLiteral { elements })
}
fn synchronize(&mut self) {
// Always consume at least one token to guarantee forward progress,
// preventing infinite loops when previous() is already a semicolon.
if !self.is_at_end() {
self.advance();
}
while !self.is_at_end() {
if self.current > 0 && matches!(self.previous().kind, TokenKind::Semicolon) {
return;
}
match self.peek().kind {
TokenKind::Let
| TokenKind::Const
| TokenKind::Fn
| TokenKind::If
| TokenKind::While
| TokenKind::For
| TokenKind::Return
| TokenKind::Break
| TokenKind::Continue => return,
_ => {
self.advance();
}
}
}
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;
+836
View File
@@ -0,0 +1,836 @@
use super::*;
use crate::lexer::Lexer;
/// Helper: lex + parse, return (stmts, errors).
fn parse(input: &str) -> (Vec<Stmt>, Vec<RuntimeError>) {
let (tokens, _) = Lexer::new(input).tokenize();
Parser::new(tokens).parse()
}
/// Helper: lex + parse single expression (wraps in `let _ = <expr>;`).
fn parse_expr(input: &str) -> Expr {
let (stmts, errors) = parse(&format!("let __x = {};", input));
assert!(errors.is_empty(), "Parse errors: {:?}", errors);
match &stmts[0] {
Stmt::Let { initializer, .. } => initializer.clone(),
_ => panic!("Expected Let statement"),
}
}
/// Helper: lex + parse single statement.
fn parse_stmt(input: &str) -> Stmt {
let (stmts, errors) = parse(input);
assert!(errors.is_empty(), "Parse errors: {:?}", errors);
assert_eq!(stmts.len(), 1);
stmts[0].clone()
}
// ----- literals -----
#[test]
fn parse_number() {
match parse_expr("42") {
Expr::Literal(Literal::Number(42.0)) => {}
e => panic!("Expected Number(42.0), got {:?}", e),
}
}
#[test]
fn parse_float() {
match parse_expr("3.14") {
Expr::Literal(Literal::Number(n)) => assert!((n - 3.14).abs() < 0.001),
e => panic!("Expected Number, got {:?}", e),
}
}
#[test]
fn parse_string() {
match parse_expr("\"hello\"") {
Expr::Literal(Literal::String(s)) => assert_eq!(s, "hello"),
e => panic!("Expected String, got {:?}", e),
}
}
#[test]
fn parse_bool_true() {
match parse_expr("true") {
Expr::Literal(Literal::Bool(true)) => {}
e => panic!("Expected Bool(true), got {:?}", e),
}
}
#[test]
fn parse_bool_false() {
match parse_expr("false") {
Expr::Literal(Literal::Bool(false)) => {}
e => panic!("Expected Bool(false), got {:?}", e),
}
}
#[test]
fn parse_nil() {
match parse_expr("nil") {
Expr::Literal(Literal::Nil) => {}
e => panic!("Expected Nil, got {:?}", e),
}
}
// ----- variables -----
#[test]
fn parse_variable() {
match parse_expr("x") {
Expr::Variable(name) => assert_eq!(name, "x"),
e => panic!("Expected Variable, got {:?}", e),
}
}
// ----- unary -----
#[test]
fn parse_negation() {
match parse_expr("-5") {
Expr::Unary { op: UnaryOp::Negate, right } => {
match *right {
Expr::Literal(Literal::Number(5.0)) => {}
e => panic!("Expected Number(5), got {:?}", e),
}
}
e => panic!("Expected Unary(Negate, Number), got {:?}", e),
}
}
#[test]
fn parse_not() {
match parse_expr("!true") {
Expr::Unary { op: UnaryOp::Not, right } => {
match *right {
Expr::Literal(Literal::Bool(true)) => {}
e => panic!("Expected Bool(true), got {:?}", e),
}
}
e => panic!("Expected Unary(Not, Bool), got {:?}", e),
}
}
#[test]
fn parse_double_negation() {
match parse_expr("!!true") {
Expr::Unary { op: UnaryOp::Not, right } => {
match *right {
Expr::Unary { op: UnaryOp::Not, .. } => {}
e => panic!("Expected nested Unary, got {:?}", e),
}
}
e => panic!("Expected Unary(Not, ...), got {:?}", e),
}
}
// ----- binary precedence -----
#[test]
fn parse_addition() {
match parse_expr("1 + 2") {
Expr::Binary { op: BinaryOp::Add, left, right } => {
match (*left, *right) {
(Expr::Literal(Literal::Number(1.0)), Expr::Literal(Literal::Number(2.0))) => {}
e => panic!("Expected (1, 2), got {:?}", e),
}
}
e => panic!("Expected Binary(Add), got {:?}", e),
}
}
#[test]
fn parse_multiplication_precedence() {
// 1 + 2 * 3 => 1 + (2 * 3)
match parse_expr("1 + 2 * 3") {
Expr::Binary { op: BinaryOp::Add, left, right } => {
match *left {
Expr::Literal(Literal::Number(1.0)) => {}
e => panic!("Expected left=1, got {:?}", e),
}
match *right {
Expr::Binary { op: BinaryOp::Mul, .. } => {}
e => panic!("Expected right=Binary(Mul), got {:?}", e),
}
}
e => panic!("Expected Binary(Add), got {:?}", e),
}
}
#[test]
fn parse_modulo() {
match parse_expr("7 % 3") {
Expr::Binary { op: BinaryOp::Mod, .. } => {}
e => panic!("Expected Binary(Mod), got {:?}", e),
}
}
// ----- comparison -----
#[test]
fn parse_comparison_chain() {
// a < b == c
match parse_expr("a < b == c") {
Expr::Binary { op: BinaryOp::Equal, left, right } => {
match *left {
Expr::Binary { op: BinaryOp::Less, .. } => {}
e => panic!("Expected left=Binary(Less), got {:?}", e),
}
match *right {
Expr::Variable(name) => assert_eq!(name, "c"),
e => panic!("Expected right=Variable(c), got {:?}", e),
}
}
e => panic!("Expected Binary(Equal), got {:?}", e),
}
}
// ----- logical -----
#[test]
fn parse_logical_and() {
match parse_expr("true && false") {
Expr::Logical { op: LogicalOp::And, .. } => {}
e => panic!("Expected Logical(And), got {:?}", e),
}
}
#[test]
fn parse_logical_or() {
match parse_expr("true || false") {
Expr::Logical { op: LogicalOp::Or, .. } => {}
e => panic!("Expected Logical(Or), got {:?}", e),
}
}
#[test]
fn parse_logical_precedence() {
// a || b && c => a || (b && c)
match parse_expr("a || b && c") {
Expr::Logical { op: LogicalOp::Or, left, right } => {
match *left {
Expr::Variable(name) => assert_eq!(name, "a"),
e => panic!("Expected left=a, got {:?}", e),
}
match *right {
Expr::Logical { op: LogicalOp::And, .. } => {}
e => panic!("Expected right=Logical(And), got {:?}", e),
}
}
e => panic!("Expected Logical(Or), got {:?}", e),
}
}
// ----- ternary -----
#[test]
fn parse_ternary_simple() {
match parse_expr("a ? 1 : 2") {
Expr::Ternary { condition, then_branch, else_branch } => {
assert!(matches!(*condition, Expr::Variable(_)));
assert!(matches!(*then_branch, Expr::Literal(Literal::Number(1.0))));
assert!(matches!(*else_branch, Expr::Literal(Literal::Number(2.0))));
}
e => panic!("Expected Ternary, got {:?}", e),
}
}
#[test]
fn parse_ternary_nested_right_assoc() {
// a ? b : c ? d : e → a ? b : (c ? d : e)
match parse_expr("a ? b : c ? d : e") {
Expr::Ternary { condition, then_branch, else_branch } => {
assert!(matches!(*condition, Expr::Variable(_)));
assert!(matches!(*then_branch, Expr::Variable(_)));
assert!(matches!(*else_branch, Expr::Ternary { .. }));
}
e => panic!("Expected Ternary with nested Ternary else, got {:?}", e),
}
}
#[test]
fn parse_ternary_with_assignment_in_else() {
// a ? b : c = d → a ? b : (c = d)
match parse_expr("a ? b : c = 5") {
Expr::Ternary { else_branch, .. } => {
assert!(matches!(*else_branch, Expr::Assign { .. }));
}
e => panic!("Expected Ternary with Assign else, got {:?}", e),
}
}
// ----- assignment -----
#[test]
fn parse_simple_assignment() {
match parse_expr("x = 5") {
Expr::Assign { name, op: AssignOp::Equal, value } => {
assert_eq!(name, "x");
match *value {
Expr::Literal(Literal::Number(5.0)) => {}
e => panic!("Expected Number(5), got {:?}", e),
}
}
e => panic!("Expected Assign, got {:?}", e),
}
}
#[test]
fn parse_compound_assignment() {
match parse_expr("x += 2") {
Expr::Assign { name, op: AssignOp::PlusEqual, value } => {
assert_eq!(name, "x");
match *value {
Expr::Literal(Literal::Number(2.0)) => {}
e => panic!("Expected Number(2), got {:?}", e),
}
}
e => panic!("Expected Assign(PlusEqual), got {:?}", e),
}
}
#[test]
fn parse_all_compound_ops() {
let ops = [
("+=", AssignOp::PlusEqual),
("-=", AssignOp::MinusEqual),
("*=", AssignOp::StarEqual),
("/=", AssignOp::SlashEqual),
("%=", AssignOp::PercentEqual),
];
for (op_str, expected_op) in &ops {
let expr = parse_expr(&format!("x {} 1", op_str));
match expr {
Expr::Assign { op, .. } => {
assert_eq!(std::mem::discriminant(&op), std::mem::discriminant(expected_op));
}
e => panic!("Expected Assign for '{}', got {:?}", op_str, e),
}
}
}
// ----- property access -----
#[test]
fn parse_property_get() {
match parse_expr("obj.name") {
Expr::Get { object, name } => {
match *object {
Expr::Variable(n) => assert_eq!(n, "obj"),
e => panic!("Expected Variable(obj), got {:?}", e),
}
assert_eq!(name, "name");
}
e => panic!("Expected Get, got {:?}", e),
}
}
#[test]
fn parse_property_set() {
match parse_expr("obj.name = 5") {
Expr::Set { object, name, op: AssignOp::Equal, value } => {
match *object {
Expr::Variable(n) => assert_eq!(n, "obj"),
e => panic!("Expected Variable(obj), got {:?}", e),
}
assert_eq!(name, "name");
match *value {
Expr::Literal(Literal::Number(5.0)) => {}
e => panic!("Expected Number(5), got {:?}", e),
}
}
e => panic!("Expected Set, got {:?}", e),
}
}
#[test]
fn parse_chained_property_access() {
match parse_expr("a.b.c") {
Expr::Get { object, name } => {
assert_eq!(name, "c");
match *object {
Expr::Get { name: ref inner, .. } => assert_eq!(inner, "b"),
e => panic!("Expected chained Get, got {:?}", e),
}
}
e => panic!("Expected Get, got {:?}", e),
}
}
// ----- index access -----
#[test]
fn parse_index_get() {
match parse_expr("arr[0]") {
Expr::IndexGet { array, index } => {
match *array {
Expr::Variable(n) => assert_eq!(n, "arr"),
e => panic!("Expected Variable(arr), got {:?}", e),
}
match *index {
Expr::Literal(Literal::Number(0.0)) => {}
e => panic!("Expected Number(0), got {:?}", e),
}
}
e => panic!("Expected IndexGet, got {:?}", e),
}
}
#[test]
fn parse_index_set() {
match parse_expr("arr[i] = 42") {
Expr::IndexSet { array, index, op: AssignOp::Equal, value } => {
match *array {
Expr::Variable(n) => assert_eq!(n, "arr"),
e => panic!("Expected Variable(arr), got {:?}", e),
}
match *index {
Expr::Variable(n) => assert_eq!(n, "i"),
e => panic!("Expected Variable(i), got {:?}", e),
}
match *value {
Expr::Literal(Literal::Number(42.0)) => {}
e => panic!("Expected Number(42), got {:?}", e),
}
}
e => panic!("Expected IndexSet, got {:?}", e),
}
}
#[test]
fn parse_nested_index() {
match parse_expr("a[1][2]") {
Expr::IndexGet { array, .. } => {
match *array {
Expr::IndexGet { .. } => {}
e => panic!("Expected nested IndexGet, got {:?}", e),
}
}
e => panic!("Expected IndexGet, got {:?}", e),
}
}
// ----- function calls -----
#[test]
fn parse_call_no_args() {
match parse_expr("f()") {
Expr::Call { callee, arguments } => {
match *callee {
Expr::Variable(n) => assert_eq!(n, "f"),
e => panic!("Expected Variable(f), got {:?}", e),
}
assert!(arguments.is_empty());
}
e => panic!("Expected Call, got {:?}", e),
}
}
#[test]
fn parse_call_with_args() {
match parse_expr("add(1, 2, 3)") {
Expr::Call { callee, arguments } => {
match *callee {
Expr::Variable(n) => assert_eq!(n, "add"),
e => panic!("Expected Variable(add), got {:?}", e),
}
assert_eq!(arguments.len(), 3);
}
e => panic!("Expected Call, got {:?}", e),
}
}
#[test]
fn parse_chained_calls() {
match parse_expr("f()()") {
Expr::Call { callee, .. } => {
match *callee {
Expr::Call { callee, .. } => {
match *callee {
Expr::Variable(n) => assert_eq!(n, "f"),
e => panic!("Expected Variable(f), got {:?}", e),
}
}
e => panic!("Expected inner Call, got {:?}", e),
}
}
e => panic!("Expected Call, got {:?}", e),
}
}
#[test]
fn parse_method_style_call() {
// obj.method()
match parse_expr("obj.method()") {
Expr::Call { callee, .. } => {
match *callee {
Expr::Get { .. } => {}
e => panic!("Expected Get, got {:?}", e),
}
}
e => panic!("Expected Call, got {:?}", e),
}
}
// ----- lambda -----
#[test]
fn parse_simple_lambda() {
match parse_expr("fn(x, y) { return x + y; }") {
Expr::Lambda { params, body } => {
assert_eq!(params, vec!["x", "y"]);
assert_eq!(body.len(), 1);
}
e => panic!("Expected Lambda, got {:?}", e),
}
}
#[test]
fn parse_lambda_no_params() {
match parse_expr("fn() { return 42; }") {
Expr::Lambda { params, body } => {
assert!(params.is_empty());
assert_eq!(body.len(), 1);
}
e => panic!("Expected Lambda, got {:?}", e),
}
}
// ----- object literal -----
#[test]
fn parse_empty_object() {
match parse_expr("{}") {
Expr::ObjectLiteral { properties } => assert!(properties.is_empty()),
e => panic!("Expected ObjectLiteral, got {:?}", e),
}
}
#[test]
fn parse_object_with_properties() {
match parse_expr("{ name: \"aster\", version: 1 }") {
Expr::ObjectLiteral { properties } => {
assert_eq!(properties.len(), 2);
assert_eq!(properties[0].0, "name");
assert_eq!(properties[1].0, "version");
}
e => panic!("Expected ObjectLiteral, got {:?}", e),
}
}
// ----- array literal -----
#[test]
fn parse_empty_array() {
match parse_expr("[]") {
Expr::ArrayLiteral { elements } => assert!(elements.is_empty()),
e => panic!("Expected ArrayLiteral, got {:?}", e),
}
}
#[test]
fn parse_array_with_elements() {
match parse_expr("[1, 2, 3]") {
Expr::ArrayLiteral { elements } => assert_eq!(elements.len(), 3),
e => panic!("Expected ArrayLiteral, got {:?}", e),
}
}
#[test]
fn parse_nested_arrays() {
match parse_expr("[[1, 2], [3, 4]]") {
Expr::ArrayLiteral { elements } => {
assert_eq!(elements.len(), 2);
for e in &elements {
assert!(matches!(e, Expr::ArrayLiteral { .. }));
}
}
e => panic!("Expected ArrayLiteral, got {:?}", e),
}
}
#[test]
fn parse_array_trailing_comma() {
match parse_expr("[1, 2, 3,]") {
Expr::ArrayLiteral { elements } => assert_eq!(elements.len(), 3),
e => panic!("Expected ArrayLiteral, got {:?}", e),
}
}
#[test]
fn parse_object_trailing_comma() {
match parse_expr("{a: 1, b: 2,}") {
Expr::ObjectLiteral { properties } => assert_eq!(properties.len(), 2),
e => panic!("Expected ObjectLiteral, got {:?}", e),
}
}
// ----- statements -----
#[test]
fn parse_let_statement() {
match parse_stmt("let x = 5;") {
Stmt::Let { name, initializer, .. } => {
assert_eq!(name, "x");
assert!(matches!(initializer, Expr::Literal(Literal::Number(5.0))));
}
e => panic!("Expected Let, got {:?}", e),
}
}
#[test]
fn parse_let_without_semicolon() {
// Semicolons are optional
match parse_stmt("let x = 5") {
Stmt::Let { name, .. } => assert_eq!(name, "x"),
e => panic!("Expected Let, got {:?}", e),
}
}
#[test]
fn parse_const_statement() {
match parse_stmt("const x = 5;") {
Stmt::Let { name, mutable, .. } => {
assert_eq!(name, "x");
assert!(!mutable, "const should set mutable=false");
}
e => panic!("Expected Let (const), got {:?}", e),
}
}
#[test]
fn parse_expression_statement() {
match parse_stmt("x;") {
Stmt::ExprStmt(Expr::Variable(name)) => assert_eq!(name, "x"),
e => panic!("Expected ExprStmt(Variable), got {:?}", e),
}
}
#[test]
fn parse_if_statement() {
match parse_stmt("if (x) { 1; }") {
Stmt::If { condition, then_branch, else_branch } => {
assert!(matches!(condition, Expr::Variable(_)));
assert!(matches!(*then_branch, Stmt::Block(_)));
assert!(else_branch.is_none());
}
e => panic!("Expected If, got {:?}", e),
}
}
#[test]
fn parse_if_else_statement() {
match parse_stmt("if (x) { 1; } else { 2; }") {
Stmt::If { else_branch, .. } => {
assert!(else_branch.is_some());
}
e => panic!("Expected If with else, got {:?}", e),
}
}
#[test]
fn parse_while_statement() {
match parse_stmt("while (x < 10) { x = x + 1; }") {
Stmt::While { condition, body } => {
assert!(matches!(condition, Expr::Binary { .. }));
assert!(matches!(*body, Stmt::Block(_)));
}
e => panic!("Expected While, got {:?}", e),
}
}
#[test]
fn parse_for_statement() {
match parse_stmt("for (let i = 0; i < 10; i = i + 1) { }") {
Stmt::For { initializer, condition, step, body } => {
assert!(initializer.is_some());
assert!(condition.is_some());
assert!(step.is_some());
assert!(matches!(*body, Stmt::Block(_)));
}
e => panic!("Expected For, got {:?}", e),
}
}
#[test]
fn parse_for_empty_clauses() {
match parse_stmt("for (;;) { }") {
Stmt::For { initializer, condition, step, .. } => {
assert!(initializer.is_none());
assert!(condition.is_none());
assert!(step.is_none());
}
e => panic!("Expected For, got {:?}", e),
}
}
#[test]
fn parse_for_in_array() {
match parse_stmt("for (x in arr) { print(x); }") {
Stmt::ForIn { var_name, iterable, body } => {
assert_eq!(var_name, "x");
assert!(matches!(iterable, Expr::Variable(_)));
assert!(matches!(*body, Stmt::Block(_)));
}
e => panic!("Expected ForIn, got {:?}", e),
}
}
#[test]
fn parse_for_in_with_object_literal() {
match parse_stmt("for (k in {a:1,b:2}) { print(k); }") {
Stmt::ForIn { var_name, iterable, .. } => {
assert_eq!(var_name, "k");
assert!(matches!(iterable, Expr::ObjectLiteral { .. }));
}
e => panic!("Expected ForIn, got {:?}", e),
}
}
#[test]
fn parse_for_in_with_expression() {
match parse_stmt("for (v in get_list()) { }") {
Stmt::ForIn { var_name, .. } => {
assert_eq!(var_name, "v");
}
e => panic!("Expected ForIn, got {:?}", e),
}
}
#[test]
fn parse_for_in_distinct_from_c_style() {
// "for (x = 0; ...)" is C-style for, not for-in
match parse_stmt("for (x = 0; x < 10; x = x + 1) { }") {
Stmt::For { .. } => {}
e => panic!("Expected C-style For, got {:?}", e),
}
}
#[test]
fn parse_block() {
match parse_stmt("{ let x = 1; x; }") {
Stmt::Block(stmts) => assert_eq!(stmts.len(), 2),
e => panic!("Expected Block, got {:?}", e),
}
}
#[test]
fn parse_return_statement() {
match parse_stmt("return 42;") {
Stmt::Return(Some(expr)) => {
assert!(matches!(expr, Expr::Literal(Literal::Number(42.0))));
}
e => panic!("Expected Return(42), got {:?}", e),
}
}
#[test]
fn parse_return_nil() {
match parse_stmt("return;") {
Stmt::Return(None) => {}
e => panic!("Expected Return(None), got {:?}", e),
}
}
#[test]
fn parse_break_statement() {
match parse_stmt("break;") {
Stmt::Break => {}
e => panic!("Expected Break, got {:?}", e),
}
}
#[test]
fn parse_continue_statement() {
match parse_stmt("continue;") {
Stmt::Continue => {}
e => panic!("Expected Continue, got {:?}", e),
}
}
#[test]
fn parse_function_declaration() {
match parse_stmt("fn add(a, b) { return a + b; }") {
Stmt::Function { name, params, body } => {
assert_eq!(name, "add");
assert_eq!(params, vec!["a", "b"]);
assert_eq!(body.len(), 1);
}
e => panic!("Expected Function, got {:?}", e),
}
}
#[test]
fn parse_function_no_params() {
match parse_stmt("fn greet() { print(\"hi\"); }") {
Stmt::Function { name, params, .. } => {
assert_eq!(name, "greet");
assert!(params.is_empty());
}
e => panic!("Expected Function, got {:?}", e),
}
}
// ----- error recovery -----
#[test]
fn error_recovery_skips_past_semicolon() {
// First statement is invalid, second should parse fine
let (stmts, errors) = parse("let = 5; let y = 10;");
assert!(!errors.is_empty(), "Should have parse errors");
// Should have at least the second statement
assert!(stmts.len() >= 1);
// The second statement should be a valid Let for y
match &stmts.last().unwrap() {
Stmt::Let { name, .. } => assert_eq!(name, "y"),
e => panic!("Expected Let(y), got {:?}", e),
}
}
#[test]
fn error_recovery_at_statement_boundary() {
let (stmts, errors) = parse("let x = @@@; let y = 10;");
assert!(!errors.is_empty());
assert!(stmts.len() >= 1);
}
// ----- parse errors -----
#[test]
fn error_invalid_assignment_target() {
let (_stmts, errors) = parse("5 = 10;");
assert!(!errors.is_empty());
}
#[test]
fn error_missing_closing_paren() {
let (_stmts, errors) = parse("if (x { 1; }");
assert!(!errors.is_empty());
}
#[test]
fn error_missing_semicolon_after_for_condition() {
// for(;;){} is valid — test something actually invalid
let (_stmts, _errors) = parse("for (let i = 0 i < 10) { }");
assert!(!_errors.is_empty());
}
// ----- parenthesized expression -----
#[test]
fn parse_parenthesized_expression() {
match parse_expr("(1 + 2)") {
Expr::Binary { op: BinaryOp::Add, .. } => {}
e => panic!("Expected Binary(Add), got {:?}", e),
}
}
#[test]
fn parse_nested_parentheses() {
match parse_expr("((42))") {
Expr::Literal(Literal::Number(42.0)) => {}
e => panic!("Expected Number(42), got {:?}", e),
}
}