use crate::ast::{Block, CallArg, Expr, FieldDef, MatchArm, MethodDef, ParamDef, Pattern, StringPart, TypeExpr};
use crate::error::SapphireError;
use crate::lexer::Lexer;
use crate::token::{Token, TokenKind};
use crate::value::Value;
fn type_expr_display_name(te: &TypeExpr) -> String {
match te {
TypeExpr::Named(n) => n.clone(),
TypeExpr::Apply(n, args) => format!(
"{}[{}]",
n,
args.iter()
.map(type_expr_display_name)
.collect::<Vec<_>>()
.join(", ")
),
TypeExpr::Literal(Value::Int(n)) => n.to_string(),
TypeExpr::Literal(Value::Float(n)) => n.to_string(),
TypeExpr::Literal(Value::Str(s)) => format!("{:?}", s),
TypeExpr::Literal(Value::Bool(b)) => b.to_string(),
TypeExpr::Literal(Value::Nil) => "Nil".to_string(),
TypeExpr::Union(arms) => arms
.iter()
.map(type_expr_display_name)
.collect::<Vec<_>>()
.join(" | "),
TypeExpr::Any => "Any".to_string(),
}
}
pub struct Parser {
tokens: Vec<Token>,
current: usize,
allow_trailing_block: bool,
}
impl Parser {
pub fn new(tokens: Vec<Token>) -> Self {
Self {
tokens,
current: 0,
allow_trailing_block: true,
}
}
fn peek(&self) -> &Token {
&self.tokens[self.current]
}
fn is_block_start(&self) -> bool {
if !self.check(&TokenKind::LeftBrace) {
return false;
}
match self.tokens.get(self.current + 1).map(|t| &t.kind) {
Some(TokenKind::Pipe) => true,
Some(TokenKind::RightBrace) => false,
Some(TokenKind::Identifier(_)) => !matches!(
self.tokens.get(self.current + 2).map(|t| &t.kind),
Some(TokenKind::Colon)
),
_ => true,
}
}
fn parse_type_ann(&mut self) -> Result<Option<TypeExpr>, SapphireError> {
if !self.check(&TokenKind::Colon) {
return Ok(None);
}
self.advance(); Ok(Some(self.parse_type_expr()?))
}
fn parse_return_type(&mut self) -> Result<Option<TypeExpr>, SapphireError> {
if !self.check(&TokenKind::Arrow) {
return Ok(None);
}
self.advance(); Ok(Some(self.parse_type_expr()?))
}
fn parse_type_expr(&mut self) -> Result<TypeExpr, SapphireError> {
self.skip_terminators(); if self.check(&TokenKind::Pipe) {
self.advance();
self.skip_terminators();
}
let first = self.parse_single_type()?;
self.skip_terminators();
if !self.check(&TokenKind::Pipe) {
return Ok(first);
}
let mut arms = vec![first];
while self.check(&TokenKind::Pipe) {
self.advance();
self.skip_terminators();
arms.push(self.parse_single_type()?);
self.skip_terminators();
}
let mut flat: Vec<TypeExpr> = Vec::new();
for arm in arms {
match arm {
TypeExpr::Union(inner) => flat.extend(inner),
other => flat.push(other),
}
}
if flat.iter().any(|t| matches!(t, TypeExpr::Any)) {
return Ok(TypeExpr::Any);
}
if flat
.iter()
.any(|t| matches!(t, TypeExpr::Named(n) if n == "Nil"))
{
let non_nil_names: Vec<String> = flat
.iter()
.filter(|t| !matches!(t, TypeExpr::Named(n) if n == "Nil"))
.map(type_expr_display_name)
.collect();
let suggestion = if non_nil_names.len() == 1 {
format!("{}?", non_nil_names[0])
} else {
format!("({})?", non_nil_names.join(" | "))
};
return Err(SapphireError::ParseError {
message: format!(
"Nil is not allowed as a union arm; use {} instead",
suggestion
),
line: self.peek().line,
column: self.peek().column,
});
}
Ok(TypeExpr::Union(flat))
}
fn parse_single_type(&mut self) -> Result<TypeExpr, SapphireError> {
if self.check(&TokenKind::LeftParen) {
self.advance();
let inner = self.parse_type_expr()?;
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after type group".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); if self.check(&TokenKind::Question) {
self.advance(); return Ok(match inner {
TypeExpr::Union(mut arms) => {
arms.push(TypeExpr::Named("Nil".to_string()));
TypeExpr::Union(arms)
}
other => TypeExpr::Union(vec![other, TypeExpr::Named("Nil".to_string())]),
});
}
return Ok(inner);
}
match self.peek().kind.clone() {
TokenKind::Identifier(t) => {
self.advance();
if t.ends_with('?') {
let base = t[..t.len() - 1].to_string();
return Ok(TypeExpr::Union(vec![
TypeExpr::Named(base),
TypeExpr::Named("Nil".to_string()),
]));
}
if self.check(&TokenKind::LeftBracket) {
let args = self.parse_type_args()?;
return Ok(TypeExpr::Apply(t, args));
}
Ok(TypeExpr::Named(t))
}
TokenKind::Number(n) => {
self.advance();
Ok(TypeExpr::Literal(Value::Int(n)))
}
TokenKind::Float(n) => {
self.advance();
Ok(TypeExpr::Literal(Value::Float(n)))
}
TokenKind::StringLit(s) => {
self.advance();
Ok(TypeExpr::Literal(Value::Str(s)))
}
TokenKind::True => {
self.advance();
Ok(TypeExpr::Literal(Value::Bool(true)))
}
TokenKind::False => {
self.advance();
Ok(TypeExpr::Literal(Value::Bool(false)))
}
TokenKind::Nil => {
self.advance();
Ok(TypeExpr::Named("Nil".to_string()))
}
_ => Err(SapphireError::ParseError {
message: "expected type".into(),
line: self.peek().line,
column: self.peek().column,
}),
}
}
fn parse_type_args(&mut self) -> Result<Vec<TypeExpr>, SapphireError> {
self.advance(); let mut args = Vec::new();
self.skip_terminators();
args.push(self.parse_type_expr()?);
while self.check(&TokenKind::Comma) {
self.advance();
self.skip_terminators();
args.push(self.parse_type_expr()?);
}
if !self.check(&TokenKind::RightBracket) {
return Err(SapphireError::ParseError {
message: "expected ']' after type arguments".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(args)
}
fn parse_type_param_names(&mut self) -> Result<Vec<String>, SapphireError> {
if !self.check(&TokenKind::LeftBracket) {
return Ok(vec![]);
}
self.advance(); let mut params = Vec::new();
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
params.push(n);
}
_ => {
return Err(SapphireError::ParseError {
message: "expected type parameter name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
if !self.check(&TokenKind::RightBracket) {
return Err(SapphireError::ParseError {
message: "expected ']' after type parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(params)
}
fn is_at_end(&self) -> bool {
self.peek().kind == TokenKind::Eof
}
fn check(&self, kind: &TokenKind) -> bool {
!self.is_at_end() && &self.peek().kind == kind
}
fn advance(&mut self) -> &Token {
if !self.is_at_end() {
self.current += 1;
}
&self.tokens[self.current - 1]
}
fn skip_terminators(&mut self) {
while self.check(&TokenKind::Newline) || self.check(&TokenKind::Semicolon) {
self.advance();
}
}
fn next_non_newline_is_dot(&self) -> bool {
let mut i = self.current;
while i < self.tokens.len() {
match &self.tokens[i].kind {
TokenKind::Newline | TokenKind::Semicolon => i += 1,
TokenKind::Dot => return true,
_ => return false,
}
}
false
}
pub fn parse(&mut self) -> Result<Vec<Expr>, SapphireError> {
let mut exprs = Vec::new();
loop {
self.skip_terminators();
if self.is_at_end() {
break;
}
exprs.push(self.statement()?);
}
Ok(exprs)
}
fn statement(&mut self) -> Result<Expr, SapphireError> {
let stmt = self.statement_inner()?;
if self.check(&TokenKind::If) {
self.advance();
self.allow_trailing_block = false;
let condition = self.logical()?;
self.allow_trailing_block = true;
return Ok(Expr::If {
condition: Box::new(condition),
then_branch: vec![stmt],
else_branch: None,
});
}
Ok(stmt)
}
fn statement_inner(&mut self) -> Result<Expr, SapphireError> {
if self.check(&TokenKind::Return) {
self.advance();
return Ok(Expr::Return(Box::new(self.logical()?)));
}
if self.check(&TokenKind::Break) {
self.advance();
let val = if self.check(&TokenKind::Newline)
|| self.check(&TokenKind::Semicolon)
|| self.check(&TokenKind::If)
|| self.check(&TokenKind::RightBrace)
|| self.is_at_end()
{
Expr::Literal(Value::Nil)
} else {
self.logical()?
};
return Ok(Expr::Break(Box::new(val)));
}
if self.check(&TokenKind::Next) {
self.advance();
let val = if self.check(&TokenKind::Newline)
|| self.check(&TokenKind::Semicolon)
|| self.check(&TokenKind::If)
|| self.check(&TokenKind::RightBrace)
|| self.is_at_end()
{
Expr::Literal(Value::Nil)
} else {
self.logical()?
};
return Ok(Expr::Next(Box::new(val)));
}
if self.check(&TokenKind::Abstract) {
return self.abstract_decl();
}
if self.check(&TokenKind::Class) {
return self.class_def();
}
if self.check(&TokenKind::Module) {
return self.module_def();
}
if self.check(&TokenKind::Interface) {
return self.interface_def();
}
if self.check(&TokenKind::Def) {
return self.function_def();
}
if self.check(&TokenKind::If) {
return self.if_expr();
}
if self.check(&TokenKind::While) {
return self.while_statement();
}
if matches!(self.peek().kind, TokenKind::Identifier(_))
&& self.current + 1 < self.tokens.len()
&& self.tokens[self.current + 1].kind == TokenKind::Comma
{
return self.multi_assign();
}
if self.check(&TokenKind::Raise) {
self.advance();
return Ok(Expr::Raise(Box::new(self.logical()?)));
}
if self.check(&TokenKind::Begin) {
return self.begin_expr();
}
if self.check(&TokenKind::Print) {
self.advance();
return Ok(Expr::Print(Box::new(self.logical()?)));
}
if self.check(&TokenKind::Type) {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected type alias name after 'type'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
if !self.check(&TokenKind::Eq) {
return Err(SapphireError::ParseError {
message: "expected '=' after type alias name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let type_expr = self.parse_type_expr()?;
return Ok(Expr::TypeAlias { name, type_expr });
}
if self.check(&TokenKind::Import) {
self.advance();
match self.peek().kind.clone() {
TokenKind::StringLit(path) => {
if !path.starts_with("./") && !path.starts_with("../") {
return Err(SapphireError::ParseError {
message: format!(
"import path must be relative (start with ./ or ../): {:?}",
path
),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
return Ok(Expr::Import { path });
}
_ => {
return Err(SapphireError::ParseError {
message: "expected a string literal after 'import'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
}
self.logical()
}
fn parse_include_statement(&mut self, includes: &mut Vec<String>) -> Result<(), SapphireError> {
if !self.check(&TokenKind::LeftParen) {
return Err(SapphireError::ParseError {
message: "expected '(' after 'include'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); self.skip_terminators();
let first = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected mixin name in include(...)".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let mut path = first;
self.skip_terminators();
while self.check(&TokenKind::Dot) {
self.advance();
self.skip_terminators();
let segment = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected identifier after '.' in include(...)".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
path.push('.');
path.push_str(&segment);
self.skip_terminators();
}
self.skip_terminators();
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after include(...) mixin name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
includes.push(path);
Ok(())
}
fn module_def(&mut self) -> Result<Expr, SapphireError> {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected module name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after module name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let fields = Vec::new();
let mut methods = Vec::new();
let mut nested = Vec::new();
let mut constants = Vec::new();
let mut includes = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
if self.check(&TokenKind::Include) {
self.advance();
self.parse_include_statement(&mut includes)?;
} else if self.check(&TokenKind::Class) {
nested.push(self.class_def()?);
} else if self.check(&TokenKind::Module) {
nested.push(self.module_def()?);
} else if let TokenKind::Identifier(n) = self.peek().kind.clone() {
let next_is_eq = self
.tokens
.get(self.current + 1)
.map(|t| t.kind == TokenKind::Eq)
.unwrap_or(false);
if n.chars()
.all(|c| c.is_uppercase() || c == '_' || c.is_ascii_digit())
&& next_is_eq
{
self.advance();
self.advance(); let val = self.logical()?;
constants.push((n, Box::new(val)));
} else {
return Err(SapphireError::ParseError {
message:
"expected 'class', 'def', 'defp', 'include', 'module', or constant in module body"
.into(),
line: self.peek().line,
column: self.peek().column,
});
}
} else if self.check(&TokenKind::Attr) {
return Err(SapphireError::ParseError {
message: "'attr' is not allowed in a module body".into(),
line: self.peek().line,
column: self.peek().column,
});
} else if self.check(&TokenKind::Def) || self.check(&TokenKind::Defp) {
let private = self.check(&TokenKind::Defp);
methods.push(self.method_def(private)?);
} else if self.check(&TokenKind::SelfKw) {
self.advance();
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after 'self' in module body".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
if self.check(&TokenKind::Def) || self.check(&TokenKind::Defp) {
let private = self.check(&TokenKind::Defp);
let mut m = self.method_def(private)?;
m.class_method = true;
methods.push(m);
} else {
return Err(SapphireError::ParseError {
message: "expected 'def' or 'defp' inside 'self' block".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}' to close 'self' block".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
} else {
return Err(SapphireError::ParseError {
message:
"expected 'class', 'def', 'defp', 'include', 'module', or 'self' in module body"
.into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
Ok(Expr::Class {
name,
type_params,
superclass: None,
is_abstract: false,
is_module: true,
includes,
fields,
methods,
nested,
constants,
})
}
fn abstract_decl(&mut self) -> Result<Expr, SapphireError> {
self.advance(); if !self.check(&TokenKind::Class) {
return Err(SapphireError::ParseError {
message: "expected 'class' after 'abstract'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); self.parse_class_from_name(true)
}
fn class_def(&mut self) -> Result<Expr, SapphireError> {
self.advance(); self.parse_class_from_name(false)
}
fn interface_def(&mut self) -> Result<Expr, SapphireError> {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected interface name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after interface name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
let mut methods = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
if self.check(&TokenKind::Def) {
methods.push(self.interface_method_def()?);
} else {
return Err(SapphireError::ParseError {
message: "expected 'def' in interface body".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
Ok(Expr::Interface {
name,
type_params,
methods,
})
}
fn parse_class_from_name(&mut self, class_is_abstract: bool) -> Result<Expr, SapphireError> {
let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected class name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
let superclass = if self.check(&TokenKind::Less) {
self.advance(); let first = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected superclass name after '<'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let mut expr: Expr = Expr::Variable(first);
while self.check(&TokenKind::Dot) {
self.advance(); let dot_line = self.peek().line;
let field = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected identifier after '.' in superclass".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
expr = Expr::Get {
object: Box::new(expr),
name: field,
line: dot_line,
};
}
Some(Box::new(expr))
} else {
None
};
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after class name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let mut fields = Vec::new();
let mut methods = Vec::new();
let mut nested = Vec::new();
let mut constants = Vec::new();
let mut includes = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
if self.check(&TokenKind::Include) {
self.advance();
self.parse_include_statement(&mut includes)?;
} else if self.check(&TokenKind::Class) {
nested.push(self.class_def()?);
} else if self.check(&TokenKind::Module) {
nested.push(self.module_def()?);
} else if let TokenKind::Identifier(n) = self.peek().kind.clone() {
let next_is_eq = self
.tokens
.get(self.current + 1)
.map(|t| t.kind == TokenKind::Eq)
.unwrap_or(false);
if n.chars()
.all(|c| c.is_uppercase() || c == '_' || c.is_ascii_digit())
&& next_is_eq
{
self.advance(); self.advance(); let val = self.logical()?;
constants.push((n, Box::new(val)));
} else {
return Err(SapphireError::ParseError {
message: "expected 'attr', 'abstract', 'class', 'def', 'defp', 'include', 'module', or 'self' in class body"
.into(),
line: self.peek().line, column: self.peek().column,
});
}
} else if self.check(&TokenKind::Attr) {
self.advance(); let field_name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected field name after 'attr'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_ann = self.parse_type_ann()?;
let default = if self.check(&TokenKind::Eq) {
self.advance();
Some(self.logical()?)
} else {
None
};
fields.push(FieldDef {
name: field_name,
type_ann,
default,
});
} else if self.check(&TokenKind::Abstract) {
self.advance(); if !self.check(&TokenKind::Def) && !self.check(&TokenKind::Defp) {
return Err(SapphireError::ParseError {
message: "expected 'def' or 'defp' after 'abstract' in class body".into(),
line: self.peek().line,
column: self.peek().column,
});
}
let private = self.check(&TokenKind::Defp);
methods.push(self.abstract_method_def(private)?);
} else if self.check(&TokenKind::Def) || self.check(&TokenKind::Defp) {
let private = self.check(&TokenKind::Defp);
methods.push(self.method_def(private)?);
} else if self.check(&TokenKind::SelfKw) {
self.advance(); if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after 'self' in class body".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
if self.check(&TokenKind::Def) || self.check(&TokenKind::Defp) {
let private = self.check(&TokenKind::Defp);
let mut m = self.method_def(private)?;
m.class_method = true;
methods.push(m);
} else {
return Err(SapphireError::ParseError {
message: "expected 'def' or 'defp' inside 'self' block".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}' to close 'self' block".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); } else {
return Err(SapphireError::ParseError {
message: "expected 'attr', 'abstract', 'class', 'def', 'defp', 'include', 'module', or 'self' in class body"
.into(),
line: self.peek().line, column: self.peek().column,
});
}
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(Expr::Class {
name,
type_params,
superclass,
is_abstract: class_is_abstract,
is_module: false,
includes,
fields,
methods,
nested,
constants,
})
}
fn if_expr(&mut self) -> Result<Expr, SapphireError> {
self.advance(); self.allow_trailing_block = false;
let condition = self.logical()?;
self.allow_trailing_block = true;
let then_branch = self.block()?;
self.skip_terminators(); let else_branch = if self.check(&TokenKind::Elsif) {
Some(vec![self.elsif_chain()?])
} else if self.check(&TokenKind::Else) {
self.advance();
Some(self.block()?)
} else {
None
};
Ok(Expr::If {
condition: Box::new(condition),
then_branch,
else_branch,
})
}
fn elsif_chain(&mut self) -> Result<Expr, SapphireError> {
self.advance(); self.allow_trailing_block = false;
let condition = self.logical()?;
self.allow_trailing_block = true;
let then_branch = self.block()?;
self.skip_terminators(); let else_branch = if self.check(&TokenKind::Elsif) {
Some(vec![self.elsif_chain()?])
} else if self.check(&TokenKind::Else) {
self.advance();
Some(self.block()?)
} else {
None
};
Ok(Expr::If {
condition: Box::new(condition),
then_branch,
else_branch,
})
}
fn function_def(&mut self) -> Result<Expr, SapphireError> {
self.advance(); if self.check(&TokenKind::LeftParen) {
return self.lambda_def();
}
let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected function name or '(' after 'def'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
let mut params = Vec::new();
if self.check(&TokenKind::LeftParen) {
self.advance(); if !self.check(&TokenKind::RightParen) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(p) => {
self.advance();
let type_ann = self.parse_type_ann()?;
params.push(ParamDef { name: p, type_ann });
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); }
let return_type = self.parse_return_type()?;
let body = self.block_with_rescue()?;
Ok(Expr::Function {
name,
type_params,
params,
return_type,
body,
})
}
fn lambda_def(&mut self) -> Result<Expr, SapphireError> {
self.advance(); let mut params = Vec::new();
if !self.check(&TokenKind::RightParen) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(p) => {
self.advance();
params.push(p);
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name in lambda".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after lambda parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let body = self.block()?;
Ok(Expr::Lambda { params, body })
}
fn method_def(&mut self, private: bool) -> Result<MethodDef, SapphireError> {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected method name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
let mut params = Vec::new();
if self.check(&TokenKind::LeftParen) {
self.advance(); if !self.check(&TokenKind::RightParen) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(p) => {
self.advance();
let type_ann = self.parse_type_ann()?;
params.push(ParamDef { name: p, type_ann });
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); }
let return_type = self.parse_return_type()?;
let body = self.block_with_rescue()?;
Ok(MethodDef {
name,
type_params,
params,
return_type,
body,
private,
class_method: false,
is_abstract: false,
})
}
fn abstract_method_def(&mut self, private: bool) -> Result<MethodDef, SapphireError> {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected method name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
let mut params = Vec::new();
if self.check(&TokenKind::LeftParen) {
self.advance(); if !self.check(&TokenKind::RightParen) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(p) => {
self.advance();
let type_ann = self.parse_type_ann()?;
params.push(ParamDef { name: p, type_ann });
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); }
let return_type = self.parse_return_type()?;
Ok(MethodDef {
name,
type_params,
params,
return_type,
body: vec![],
private,
class_method: false,
is_abstract: true,
})
}
fn interface_method_def(&mut self) -> Result<MethodDef, SapphireError> {
self.advance(); let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
_ => {
return Err(SapphireError::ParseError {
message: "expected method name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
let type_params = self.parse_type_param_names()?;
let mut params = Vec::new();
if self.check(&TokenKind::LeftParen) {
self.advance();
if !self.check(&TokenKind::RightParen) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(p) => {
self.advance();
let type_ann = self.parse_type_ann()?;
params.push(ParamDef { name: p, type_ann });
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance();
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance();
}
let return_type = self.parse_return_type()?;
Ok(MethodDef {
name,
type_params,
params,
return_type,
body: vec![],
private: false,
class_method: false,
is_abstract: true,
})
}
fn while_statement(&mut self) -> Result<Expr, SapphireError> {
self.advance(); self.allow_trailing_block = false;
let condition = self.logical()?;
self.allow_trailing_block = true;
let body = self.block()?;
Ok(Expr::While {
condition: Box::new(condition),
body,
})
}
fn multi_assign(&mut self) -> Result<Expr, SapphireError> {
let mut names = Vec::new();
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
names.push(n);
}
_ => {
return Err(SapphireError::ParseError {
message: "expected identifier in multiple assignment".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if self.check(&TokenKind::Comma) {
self.advance();
} else {
break;
}
}
if !self.check(&TokenKind::Eq) {
return Err(SapphireError::ParseError {
message: "expected '=' in multiple assignment".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let mut values = Vec::new();
loop {
values.push(self.logical()?);
if self.check(&TokenKind::Comma) {
self.advance();
} else {
break;
}
}
Ok(Expr::MultiAssign { names, values })
}
fn begin_expr(&mut self) -> Result<Expr, SapphireError> {
self.advance(); let mut body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::Rescue) || self.check(&TokenKind::End) || self.is_at_end() {
break;
}
body.push(self.statement()?);
}
let (rescue_var, rescue_body) = if self.check(&TokenKind::Rescue) {
self.advance(); let var = if let TokenKind::Identifier(n) = self.peek().kind.clone() {
self.advance();
Some(n)
} else {
None
};
let mut rescue_body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::Else) || self.check(&TokenKind::End) || self.is_at_end() {
break;
}
rescue_body.push(self.statement()?);
}
(var, rescue_body)
} else {
(None, Vec::new())
};
let else_body = if self.check(&TokenKind::Else) {
self.advance(); let mut else_body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::End) || self.is_at_end() {
break;
}
else_body.push(self.statement()?);
}
else_body
} else {
Vec::new()
};
if !self.check(&TokenKind::End) {
return Err(SapphireError::ParseError {
message: "expected 'end' to close 'begin'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(Expr::Begin {
body,
rescue_var,
rescue_body,
else_body,
})
}
fn block_with_rescue(&mut self) -> Result<Vec<Expr>, SapphireError> {
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let mut body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::Rescue)
|| self.check(&TokenKind::RightBrace)
|| self.is_at_end()
{
break;
}
body.push(self.statement()?);
}
if self.check(&TokenKind::Rescue) {
self.advance(); let rescue_var = if let TokenKind::Identifier(n) = self.peek().kind.clone() {
self.advance();
Some(n)
} else {
None
};
let mut rescue_body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
rescue_body.push(self.statement()?);
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(vec![Expr::Begin {
body,
rescue_var,
rescue_body,
else_body: Vec::new(),
}])
} else {
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(body)
}
}
fn block(&mut self) -> Result<Vec<Expr>, SapphireError> {
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let mut stmts = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
stmts.push(self.statement()?);
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(stmts)
}
fn logical(&mut self) -> Result<Expr, SapphireError> {
if self.check(&TokenKind::If) {
return self.if_expr();
}
if self.check(&TokenKind::Begin) {
return self.begin_expr();
}
if self.check(&TokenKind::While) {
return self.while_statement();
}
let mut left = self.range()?;
while self.check(&TokenKind::AmpAmp) || self.check(&TokenKind::PipePipe) {
let op = self.advance().clone();
let right = self.range()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn range(&mut self) -> Result<Expr, SapphireError> {
let left = self.bitwise_or()?;
if self.check(&TokenKind::DotDot) {
self.advance();
let right = self.bitwise_or()?;
return Ok(Expr::Range {
from: Box::new(left),
to: Box::new(right),
});
}
Ok(left)
}
fn bitwise_or(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.bitwise_xor()?;
while self.check(&TokenKind::Pipe) {
let op = self.advance().clone();
let right = self.bitwise_xor()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn bitwise_xor(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.bitwise_and()?;
while self.check(&TokenKind::Caret) {
let op = self.advance().clone();
let right = self.bitwise_and()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn bitwise_and(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.equality()?;
while self.check(&TokenKind::Amp) {
let op = self.advance().clone();
let right = self.equality()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn equality(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.comparison()?;
while self.check(&TokenKind::EqEq) || self.check(&TokenKind::BangEq) {
let op = self.advance().clone();
let right = self.comparison()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn comparison(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.shift()?;
while self.check(&TokenKind::Less)
|| self.check(&TokenKind::LessEq)
|| self.check(&TokenKind::Greater)
|| self.check(&TokenKind::GreaterEq)
{
let op = self.advance().clone();
let right = self.shift()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn shift(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.term()?;
while self.check(&TokenKind::LessLess) || self.check(&TokenKind::GreaterGreater) {
let op = self.advance().clone();
let right = self.term()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn term(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.factor()?;
while self.check(&TokenKind::Plus) || self.check(&TokenKind::Minus) {
let op = self.advance().clone();
let right = self.factor()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn factor(&mut self) -> Result<Expr, SapphireError> {
let mut left = self.unary()?;
while self.check(&TokenKind::Star)
|| self.check(&TokenKind::Slash)
|| self.check(&TokenKind::Percent)
{
let op = self.advance().clone();
let right = self.unary()?;
left = Expr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
};
}
Ok(left)
}
fn unary(&mut self) -> Result<Expr, SapphireError> {
if self.check(&TokenKind::Bang)
|| self.check(&TokenKind::Minus)
|| self.check(&TokenKind::Tilde)
{
let op = self.advance().clone();
let right = self.unary()?;
return Ok(Expr::Unary {
op,
right: Box::new(right),
});
}
self.call()
}
fn call(&mut self) -> Result<Expr, SapphireError> {
let mut expr = self.primary()?;
loop {
if self.check(&TokenKind::Newline) && self.next_non_newline_is_dot() {
self.skip_terminators();
}
if self.check(&TokenKind::LeftParen) {
expr = self.finish_call(expr)?;
} else if self.check(&TokenKind::Dot) {
self.advance(); let dot_line = self.peek().line;
let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
TokenKind::Class => { self.advance(); "class".to_string() }
TokenKind::Match => { self.advance(); "match".to_string() }
_ => {
return Err(SapphireError::ParseError {
message: "expected field or method name after '.'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
if self.check(&TokenKind::Eq) {
self.advance(); let value = self.logical()?;
expr = Expr::Set {
object: Box::new(expr),
name,
value: Box::new(value),
};
break;
}
if self.allow_trailing_block && self.is_block_start() {
let block = self.parse_block()?;
let get = Expr::Get {
object: Box::new(expr),
name,
line: dot_line,
};
expr = Expr::Call {
callee: Box::new(get),
args: Vec::new(),
block,
};
continue;
}
let get = Expr::Get {
object: Box::new(expr),
name,
line: dot_line,
};
if self.check(&TokenKind::LeftParen) {
expr = self.finish_call(get)?;
} else {
expr = Expr::Call {
callee: Box::new(get),
args: Vec::new(),
block: None,
};
}
} else if self.check(&TokenKind::AmpDot) {
self.advance(); let amp_line = self.peek().line;
let name = match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
n
}
TokenKind::Match => { self.advance(); "match".to_string() }
_ => {
return Err(SapphireError::ParseError {
message: "expected method or field name after '&.'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
if self.check(&TokenKind::LeftParen) {
let safe_get = Expr::SafeGet {
object: Box::new(expr),
name,
line: amp_line,
};
let call = self.finish_call(safe_get)?;
expr = call;
} else {
expr = Expr::SafeGet {
object: Box::new(expr),
name,
line: amp_line,
};
}
} else if self.check(&TokenKind::LeftBracket) {
self.advance(); let index = self.logical()?;
if !self.check(&TokenKind::RightBracket) {
return Err(SapphireError::ParseError {
message: "expected ']' after index".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); if self.check(&TokenKind::Eq) {
self.advance(); let value = self.logical()?;
expr = Expr::IndexSet {
object: Box::new(expr),
index: Box::new(index),
value: Box::new(value),
};
break;
}
expr = Expr::Index {
object: Box::new(expr),
index: Box::new(index),
};
} else {
break;
}
}
if self.allow_trailing_block
&& let Expr::Variable(_) = &expr
&& self.is_block_start()
{
let block = self.parse_block()?;
expr = Expr::Call {
callee: Box::new(expr),
args: Vec::new(),
block,
};
}
Ok(expr)
}
fn finish_call(&mut self, callee: Expr) -> Result<Expr, SapphireError> {
self.advance(); let mut args = Vec::new();
if !self.check(&TokenKind::RightParen) {
args.push(self.parse_arg()?);
while self.check(&TokenKind::Comma) {
self.advance();
args.push(self.parse_arg()?);
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after arguments".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let block = self.parse_block()?;
Ok(Expr::Call {
callee: Box::new(callee),
args,
block,
})
}
fn parse_block(&mut self) -> Result<Option<Block>, SapphireError> {
if !self.allow_trailing_block || !self.check(&TokenKind::LeftBrace) {
return Ok(None);
}
self.advance(); let params = if self.check(&TokenKind::Pipe) {
self.advance(); let mut params = Vec::new();
if !self.check(&TokenKind::Pipe) {
loop {
match self.peek().kind.clone() {
TokenKind::Identifier(n) => {
self.advance();
params.push(n);
}
_ => {
return Err(SapphireError::ParseError {
message: "expected parameter name in block".into(),
line: self.peek().line,
column: self.peek().column,
});
}
}
if !self.check(&TokenKind::Comma) {
break;
}
self.advance(); }
}
if !self.check(&TokenKind::Pipe) {
return Err(SapphireError::ParseError {
message: "expected '|' after block parameters".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); params
} else {
Vec::new()
};
let mut body = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
body.push(self.statement()?);
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}'".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); Ok(Some(Block { params, body }))
}
fn parse_arg(&mut self) -> Result<CallArg, SapphireError> {
if let TokenKind::Identifier(name) = self.peek().kind.clone()
&& self.current + 1 < self.tokens.len()
&& self.tokens[self.current + 1].kind == TokenKind::Colon
{
self.advance(); self.advance(); return Ok(CallArg {
name: Some(name),
value: self.logical()?,
});
}
Ok(CallArg {
name: None,
value: self.logical()?,
})
}
fn primary(&mut self) -> Result<Expr, SapphireError> {
if self.check(&TokenKind::Match) {
return self.parse_match();
}
if let TokenKind::Number(n) = self.peek().kind {
self.advance();
return Ok(Expr::Literal(Value::Int(n)));
}
if let TokenKind::Float(f) = self.peek().kind {
self.advance();
return Ok(Expr::Literal(Value::Float(f)));
}
if let TokenKind::StringLit(s) = self.peek().kind.clone() {
self.advance();
return Ok(Expr::Literal(Value::Str(s)));
}
if let TokenKind::StringInterp(raw_parts) = self.peek().kind.clone() {
self.advance();
let mut parts = Vec::new();
for (content, is_expr) in raw_parts {
if is_expr {
let tokens = Lexer::new(&content).scan_tokens();
let expr = Parser::new(tokens).logical()?;
parts.push(StringPart::Expr(Box::new(expr)));
} else {
parts.push(StringPart::Lit(content));
}
}
return Ok(Expr::StringInterp(parts));
}
if self.check(&TokenKind::True) {
self.advance();
return Ok(Expr::Literal(Value::Bool(true)));
}
if self.check(&TokenKind::False) {
self.advance();
return Ok(Expr::Literal(Value::Bool(false)));
}
if self.check(&TokenKind::Nil) {
self.advance();
return Ok(Expr::Literal(Value::Nil));
}
if self.check(&TokenKind::SelfKw) {
self.advance();
return Ok(Expr::SelfExpr);
}
if self.check(&TokenKind::Def) {
return self.function_def();
}
if self.check(&TokenKind::Yield) {
self.advance();
let args = if self.check(&TokenKind::LeftParen) {
self.advance(); let mut args = Vec::new();
if !self.check(&TokenKind::RightParen) {
args.push(self.parse_arg()?);
while self.check(&TokenKind::Comma) {
self.advance();
args.push(self.parse_arg()?);
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after yield arguments".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); args
} else {
Vec::new()
};
return Ok(Expr::Yield { args });
}
if self.check(&TokenKind::SuperKw) {
self.advance();
if self.check(&TokenKind::Dot) {
return Err(SapphireError::ParseError {
message: "unexpected '.' after 'super'; use bare super or super(...)".into(),
line: self.peek().line,
column: self.peek().column,
});
}
let (args, block, forward_args) = if self.check(&TokenKind::LeftParen) {
self.advance(); let mut args = Vec::new();
if !self.check(&TokenKind::RightParen) {
args.push(self.parse_arg()?);
while self.check(&TokenKind::Comma) {
self.advance();
args.push(self.parse_arg()?);
}
}
if !self.check(&TokenKind::RightParen) {
return Err(SapphireError::ParseError {
message: "expected ')' after arguments".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let block = self.parse_block()?;
let forward_args = args.is_empty();
(args, block, forward_args)
} else {
let block = self.parse_block()?;
(Vec::new(), block, true)
};
return Ok(Expr::Super {
args,
forward_args,
block,
});
}
if let TokenKind::Identifier(name) = self.peek().kind.clone() {
self.advance();
if self.check(&TokenKind::Eq) {
self.advance(); let value = self.logical()?;
return Ok(Expr::Assign {
name,
value: Box::new(value),
});
}
return Ok(Expr::Variable(name));
}
if self.check(&TokenKind::LeftParen) {
self.advance();
let expr = self.logical()?;
self.advance(); return Ok(Expr::Grouping(Box::new(expr)));
}
if self.check(&TokenKind::LeftBracket) {
self.advance(); let mut elements = Vec::new();
if !self.check(&TokenKind::RightBracket) {
elements.push(self.logical()?);
while self.check(&TokenKind::Comma) {
self.advance();
elements.push(self.logical()?);
}
}
if !self.check(&TokenKind::RightBracket) {
return Err(SapphireError::ParseError {
message: "expected ']' after list elements".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); return Ok(Expr::ListLit(elements));
}
if self.check(&TokenKind::LeftBrace) {
self.advance(); let mut pairs = Vec::new();
if !self.check(&TokenKind::RightBrace) {
loop {
let key = match self.peek().kind.clone() {
TokenKind::Identifier(k) => {
self.advance();
k
}
_ => {
return Err(SapphireError::ParseError {
message: "expected key name in map literal".into(),
line: self.peek().line,
column: self.peek().column,
});
}
};
if !self.check(&TokenKind::Colon) {
return Err(SapphireError::ParseError {
message: "expected ':' after map key".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); let value = self.logical()?;
pairs.push((key, value));
if !self.check(&TokenKind::Comma) {
break;
}
self.advance(); }
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}' after map literal".into(),
line: self.peek().line,
column: self.peek().column,
});
}
self.advance(); return Ok(Expr::MapLit(pairs));
}
Err(SapphireError::ParseError {
message: format!("unexpected token '{:?}'", self.peek().kind),
line: self.peek().line,
column: self.peek().column,
})
}
fn parse_match(&mut self) -> Result<Expr, SapphireError> {
self.advance(); self.allow_trailing_block = false;
let scrutinee = self.logical()?;
self.allow_trailing_block = true;
if !self.check(&TokenKind::LeftBrace) {
return Err(SapphireError::ParseError {
message: "expected '{' after match scrutinee".into(),
line: self.peek().line, column: self.peek().column,
});
}
self.advance(); let mut arms: Vec<MatchArm> = Vec::new();
loop {
self.skip_terminators();
if self.check(&TokenKind::RightBrace) || self.is_at_end() {
break;
}
arms.push(self.parse_arm()?);
}
if !self.check(&TokenKind::RightBrace) {
return Err(SapphireError::ParseError {
message: "expected '}' to close match".into(),
line: self.peek().line, column: self.peek().column,
});
}
self.advance(); if arms.is_empty() {
return Err(SapphireError::ParseError {
message: "match expression has no arms".into(),
line: self.peek().line, column: self.peek().column,
});
}
let last = arms.last().unwrap();
let last_exhaustive = last.guard.is_none()
&& last.patterns.len() == 1
&& matches!(last.patterns[0], Pattern::Wildcard | Pattern::Binding(_));
if !last_exhaustive {
return Err(SapphireError::ParseError {
message: "last match arm must be a wildcard '_' or bare binding (exhaustive fallback)".into(),
line: self.peek().line, column: self.peek().column,
});
}
Ok(Expr::Match {
scrutinee: Box::new(scrutinee),
arms,
})
}
fn parse_arm(&mut self) -> Result<MatchArm, SapphireError> {
let mut patterns = vec![self.parse_pattern()?];
while self.check(&TokenKind::Comma) {
self.advance(); self.skip_terminators();
patterns.push(self.parse_pattern()?);
}
let guard = if self.check(&TokenKind::If) {
if patterns.len() != 1 || !matches!(patterns[0], Pattern::Binding(_)) {
return Err(SapphireError::ParseError {
message: "guard 'if' is only allowed on a single binding pattern".into(),
line: self.peek().line, column: self.peek().column,
});
}
self.advance(); self.allow_trailing_block = false;
let g = self.logical()?;
self.allow_trailing_block = true;
Some(Box::new(g))
} else {
None
};
if !self.check(&TokenKind::FatArrow) {
return Err(SapphireError::ParseError {
message: "expected '=>' after match pattern".into(),
line: self.peek().line, column: self.peek().column,
});
}
self.advance(); let body = self.block()?;
Ok(MatchArm { patterns, guard, body })
}
fn parse_pattern(&mut self) -> Result<Pattern, SapphireError> {
if self.check(&TokenKind::Underscore) {
self.advance();
return Ok(Pattern::Wildcard);
}
if self.check(&TokenKind::LeftBracket) {
self.advance(); let mut elements = Vec::new();
self.skip_terminators();
while !self.check(&TokenKind::RightBracket) && !self.is_at_end() {
elements.push(self.parse_pattern()?);
self.skip_terminators();
if self.check(&TokenKind::Comma) {
self.advance();
self.skip_terminators();
}
}
if !self.check(&TokenKind::RightBracket) {
return Err(SapphireError::ParseError {
message: "expected ']' to close list pattern".into(),
line: self.peek().line, column: self.peek().column,
});
}
self.advance(); return Ok(Pattern::List(elements));
}
if self.check(&TokenKind::Nil) {
self.advance();
return Ok(Pattern::Literal(Value::Nil));
}
if self.check(&TokenKind::True) {
self.advance();
return Ok(Pattern::Literal(Value::Bool(true)));
}
if self.check(&TokenKind::False) {
self.advance();
return Ok(Pattern::Literal(Value::Bool(false)));
}
if let TokenKind::StringLit(s) = self.peek().kind.clone() {
self.advance();
return Ok(Pattern::Literal(Value::Str(s)));
}
if let TokenKind::Number(n) = self.peek().kind {
self.advance();
if self.check(&TokenKind::DotDot) {
self.advance(); if let TokenKind::Number(hi) = self.peek().kind {
self.advance();
return Ok(Pattern::Range(Value::Int(n), Value::Int(hi)));
}
return Err(SapphireError::ParseError {
message: "expected integer after '..' in range pattern".into(),
line: self.peek().line, column: self.peek().column,
});
}
return Ok(Pattern::Literal(Value::Int(n)));
}
if self.check(&TokenKind::Minus) {
let next = self.tokens.get(self.current + 1).map(|t| t.kind.clone());
if let Some(TokenKind::Number(n)) = next {
self.advance(); self.advance(); return Ok(Pattern::Literal(Value::Int(-n)));
}
}
if let TokenKind::Identifier(name) = self.peek().kind.clone() {
self.advance();
let first_char = name.chars().next().unwrap_or('_');
if first_char.is_uppercase() {
return Ok(Pattern::Type(name));
} else {
return Ok(Pattern::Binding(name));
}
}
Err(SapphireError::ParseError {
message: format!("expected pattern, got '{:?}'", self.peek().kind),
line: self.peek().line, column: self.peek().column,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{Expr, TypeExpr};
use crate::lexer::Lexer;
fn parse_expr(source: &str) -> Expr {
let tokens = Lexer::new(source).scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
exprs.remove(0)
}
#[test]
fn test_literal() {
assert!(matches!(parse_expr("42"), Expr::Literal(Value::Int(42))));
}
#[test]
fn test_addition() {
assert!(matches!(parse_expr("1+2"), Expr::Binary { .. }));
}
#[test]
fn test_precedence() {
let expr = parse_expr("1+2*3");
if let Expr::Binary { op, right, .. } = expr {
assert_eq!(op.kind, TokenKind::Plus);
assert!(matches!(*right, Expr::Binary { .. }));
} else {
panic!("expected Binary");
}
}
#[test]
fn test_grouping() {
let expr = parse_expr("(1+2)*3");
if let Expr::Binary { op, left, .. } = expr {
assert_eq!(op.kind, TokenKind::Star);
assert!(matches!(*left, Expr::Grouping(_)));
} else {
panic!("expected Binary");
}
}
#[test]
fn test_parse_error() {
let tokens = Lexer::new("1+").scan_tokens();
assert!(Parser::new(tokens).parse().is_err());
}
#[test]
fn test_print_statement() {
let tokens = Lexer::new("print 42").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Print(inner) => {
assert!(matches!(*inner, Expr::Literal(Value::Int(42))));
}
other => panic!("expected print expr, got {:?}", other),
}
}
#[test]
fn test_multiple_statements() {
let tokens = Lexer::new("x = 1; x + 2").scan_tokens();
let stmts = Parser::new(tokens).parse().unwrap();
assert_eq!(stmts.len(), 2);
}
#[test]
fn test_include_requires_parens() {
let ok = Parser::new(Lexer::new("class C { include(M) }\n").scan_tokens()).parse();
assert!(ok.is_ok());
let ok2 =
Parser::new(Lexer::new("class C { include(Outer.Inner) }\n").scan_tokens()).parse();
assert!(ok2.is_ok());
assert!(
Parser::new(Lexer::new("class C { include M }\n").scan_tokens())
.parse()
.is_err()
);
}
#[test]
fn test_class_def() {
let tokens = Lexer::new("class Point { attr x; attr y }").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
assert!(matches!(
exprs.remove(0),
Expr::Class { name, .. } if name == "Point"
));
}
#[test]
fn test_field_access() {
let expr = parse_expr("p.x");
assert!(matches!(
expr,
Expr::Call { callee, args, .. }
if args.is_empty() && matches!(callee.as_ref(), Expr::Get { name, .. } if name == "x")
));
}
#[test]
fn test_named_arg_call() {
let expr = parse_expr("Point.new(x: 1, y: 2)");
assert!(matches!(expr, Expr::Call { .. }));
}
#[test]
fn test_generic_class_def() {
let tokens = Lexer::new("class Box[T] { attr value: T }").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Class {
name, type_params, ..
} => {
assert_eq!(name, "Box");
assert_eq!(type_params, vec!["T"]);
}
_ => panic!("expected class"),
}
}
#[test]
fn test_generic_multi_param_class() {
let tokens = Lexer::new("class Pair[A, B] { attr first: A\nattr second: B }").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Class {
name, type_params, ..
} => {
assert_eq!(name, "Pair");
assert_eq!(type_params, vec!["A", "B"]);
}
_ => panic!("expected class"),
}
}
#[test]
fn test_parameterized_type_annotation() {
let tokens = Lexer::new("def foo(x: List[Int]) {}").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Function { params, .. } => {
assert_eq!(
params[0].type_ann,
Some(TypeExpr::Apply(
"List".into(),
vec![TypeExpr::Named("Int".into())]
))
);
}
_ => panic!("expected function"),
}
}
#[test]
fn test_generic_function_def() {
let tokens = Lexer::new("def identity[T](x: T) -> T { x }").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Function {
name, type_params, ..
} => {
assert_eq!(name, "identity");
assert_eq!(type_params, vec!["T"]);
}
_ => panic!("expected function"),
}
}
#[test]
fn test_map_type_annotation() {
let tokens = Lexer::new("def foo(m: Map[String, Int]) {}").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::Function { params, .. } => {
assert_eq!(
params[0].type_ann,
Some(TypeExpr::Apply(
"Map".into(),
vec![
TypeExpr::Named("String".into()),
TypeExpr::Named("Int".into())
]
))
);
}
_ => panic!("expected function"),
}
}
#[test]
fn test_type_alias_string_literal_type() {
let tokens = Lexer::new("type Mode = \"dev\" | \"prod\"").scan_tokens();
let mut exprs = Parser::new(tokens).parse().unwrap();
match exprs.remove(0) {
Expr::TypeAlias { type_expr, .. } => match type_expr {
TypeExpr::Union(arms) => {
assert!(matches!(arms[0], TypeExpr::Literal(Value::Str(_))));
assert!(matches!(arms[1], TypeExpr::Literal(Value::Str(_))));
}
other => panic!("expected union type, got {:?}", other),
},
other => panic!("expected type alias, got {:?}", other),
}
}
}