use crate::frontend::parser::{
bail, expressions, parse_expr_recursive, Expr, Param, ParserState, Pattern, Result, Span,
Token, Type, TypeKind,
};
pub fn parse_params(state: &mut ParserState) -> Result<Vec<Param>> {
state.tokens.expect(&Token::LeftParen)?;
let params = parse_param_list(state)?;
state.tokens.expect(&Token::RightParen)?;
Ok(params)
}
fn parse_param_list(state: &mut ParserState) -> Result<Vec<Param>> {
let mut params = Vec::new();
while !matches!(state.tokens.peek(), Some((Token::RightParen, _))) {
params.push(parse_single_param(state)?);
if !should_continue_param_list(state)? {
break;
}
}
Ok(params)
}
fn parse_single_param(state: &mut ParserState) -> Result<Param> {
let is_mutable = check_and_consume_mut(state);
let (pattern, (is_reference, is_ref_mut)) = parse_param_pattern(state)?;
let mut ty = parse_optional_type_annotation(state)?;
if is_reference {
if let Pattern::Identifier(name) = &pattern {
if name == "self" {
ty = Type {
kind: TypeKind::Reference {
is_mut: is_ref_mut,
lifetime: None, inner: Box::new(Type {
kind: TypeKind::Named("Self".to_string()),
span: Span { start: 0, end: 0 },
}),
},
span: ty.span,
};
}
}
}
let default_value = parse_optional_default_value(state)?;
Ok(Param {
pattern,
ty,
span: Span { start: 0, end: 0 },
is_mutable,
default_value,
})
}
fn check_and_consume_mut(state: &mut ParserState) -> bool {
if matches!(state.tokens.peek(), Some((Token::Mut, _))) {
state.tokens.advance();
true
} else {
false
}
}
fn parse_param_pattern(state: &mut ParserState) -> Result<(Pattern, (bool, bool))> {
match state.tokens.peek() {
Some((Token::Identifier(name), _)) => {
let name = name.clone();
state.tokens.advance();
Ok((Pattern::Identifier(name), (false, false)))
}
Some((Token::Ampersand, _)) => {
parse_reference_pattern(state)
}
Some((Token::DataFrame, _)) => {
state.tokens.advance();
Ok((Pattern::Identifier("df".to_string()), (false, false)))
}
Some((Token::Self_, _)) => {
state.tokens.advance();
Ok((Pattern::Identifier("self".to_string()), (false, false)))
}
Some((Token::LeftParen, _)) => {
let pattern = expressions::parse_tuple_pattern(state)?;
Ok((pattern, (false, false)))
}
Some((Token::LeftBracket, _)) => {
let pattern = expressions::parse_list_pattern(state)?;
Ok((pattern, (false, false)))
}
Some((Token::LeftBrace, _)) => {
let pattern = expressions::parse_struct_pattern(state)?;
Ok((pattern, (false, false)))
}
Some((Token::Default, _)) => {
state.tokens.advance();
Ok((Pattern::Identifier("default".to_string()), (false, false)))
}
Some((Token::From, _)) => {
bail!(
"'from' is a reserved keyword (for future import syntax).\n\
Suggestion: Use 'from_vertex', 'source', 'start_node', or similar instead.\n\
\n\
Example:\n\
✗ fun shortest_path(from, to) {{ ... }} // Error\n\
✓ fun shortest_path(source, target) {{ ... }} // OK\n\
\n\
See: https://github.com/paiml/ruchy/issues/23"
)
}
_ => bail!("Function parameters must be simple identifiers or destructuring patterns"),
}
}
fn parse_reference_pattern(state: &mut ParserState) -> Result<(Pattern, (bool, bool))> {
state.tokens.advance(); let is_mut_ref = matches!(state.tokens.peek(), Some((Token::Mut, _)));
if is_mut_ref {
state.tokens.advance(); }
match state.tokens.peek() {
Some((Token::Self_, _)) => {
state.tokens.advance();
Ok((Pattern::Identifier("self".to_string()), (true, is_mut_ref)))
}
Some((Token::Identifier(n), _)) => {
let expected = if is_mut_ref {
"'self' after '&mut'"
} else {
"'self' after '&'"
};
bail!("Expected {expected} (got identifier '{n}')")
}
_ => {
let expected = if is_mut_ref {
"'self' after '&mut'"
} else {
"'self' after '&'"
};
bail!("Expected {expected}")
}
}
}
fn parse_optional_type_annotation(state: &mut ParserState) -> Result<Type> {
if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
state.tokens.advance(); crate::frontend::parser::utils::parse_type(state)
} else {
Ok(Type {
kind: TypeKind::Named("Any".to_string()),
span: Span { start: 0, end: 0 },
})
}
}
fn parse_optional_default_value(state: &mut ParserState) -> Result<Option<Box<Expr>>> {
if matches!(state.tokens.peek(), Some((Token::Equal, _))) {
state.tokens.advance(); Ok(Some(Box::new(parse_expr_recursive(state)?)))
} else {
Ok(None)
}
}
fn should_continue_param_list(state: &mut ParserState) -> Result<bool> {
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance(); Ok(true)
} else {
Ok(false)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_state(source: &str) -> ParserState<'_> {
ParserState::new(source)
}
#[test]
fn test_parse_params_empty() {
let mut state = create_state("()");
let result = parse_params(&mut state);
assert!(result.is_ok());
assert!(result.unwrap().is_empty());
}
#[test]
fn test_parse_params_single_identifier() {
let mut state = create_state("(x)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].pattern, Pattern::Identifier(n) if n == "x"));
}
#[test]
fn test_parse_params_with_type() {
let mut state = create_state("(x: i32)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].ty.kind, TypeKind::Named(n) if n == "i32"));
}
#[test]
fn test_parse_params_multiple() {
let mut state = create_state("(x, y, z)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 3);
}
#[test]
fn test_parse_params_mutable() {
let mut state = create_state("(mut x)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(params[0].is_mutable);
}
#[test]
fn test_parse_params_self() {
let mut state = create_state("(self)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].pattern, Pattern::Identifier(n) if n == "self"));
}
#[test]
fn test_parse_params_ref_self() {
let mut state = create_state("(&self)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(
¶ms[0].ty.kind,
TypeKind::Reference { is_mut: false, .. }
));
}
#[test]
fn test_parse_params_ref_mut_self() {
let mut state = create_state("(&mut self)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(
¶ms[0].ty.kind,
TypeKind::Reference { is_mut: true, .. }
));
}
#[test]
fn test_parse_params_df_identifier() {
let mut state = create_state("(df)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].pattern, Pattern::Identifier(n) if n == "df"));
}
#[test]
fn test_parse_params_default_identifier() {
let mut state = create_state("(default)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].pattern, Pattern::Identifier(n) if n == "default"));
}
#[test]
fn test_parse_params_with_default_value() {
let mut state = create_state("(x = 42)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(params[0].default_value.is_some());
}
#[test]
fn test_parse_params_typed_with_default() {
let mut state = create_state("(x: i32 = 0)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].ty.kind, TypeKind::Named(n) if n == "i32"));
assert!(params[0].default_value.is_some());
}
#[test]
fn test_parse_params_mixed() {
let mut state = create_state("(x: i32, mut y, z = 5)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 3);
assert!(!params[0].is_mutable);
assert!(params[1].is_mutable);
assert!(params[2].default_value.is_some());
}
#[test]
fn test_parse_params_from_keyword_error() {
let mut state = create_state("(from)");
let result = parse_params(&mut state);
assert!(result.is_err());
let err = result.unwrap_err().to_string();
assert!(err.contains("reserved keyword"));
}
#[test]
fn test_parse_params_ref_non_self_error() {
let mut state = create_state("(&x)");
let result = parse_params(&mut state);
assert!(result.is_err());
}
#[test]
fn test_parse_params_reference_type() {
let mut state = create_state("(x: &str)");
let result = parse_params(&mut state);
assert!(result.is_ok());
let params = result.unwrap();
assert_eq!(params.len(), 1);
assert!(matches!(¶ms[0].ty.kind, TypeKind::Reference { .. }));
}
#[test]
fn test_check_and_consume_mut_true() {
let mut state = create_state("mut x");
let is_mut = check_and_consume_mut(&mut state);
assert!(is_mut);
}
#[test]
fn test_check_and_consume_mut_false() {
let mut state = create_state("x");
let is_mut = check_and_consume_mut(&mut state);
assert!(!is_mut);
}
#[test]
fn test_should_continue_with_comma() {
let mut state = create_state(", y");
let result = should_continue_param_list(&mut state);
assert!(result.is_ok());
assert!(result.unwrap());
}
#[test]
fn test_should_continue_without_comma() {
let mut state = create_state(")");
let result = should_continue_param_list(&mut state);
assert!(result.is_ok());
assert!(!result.unwrap());
}
}