use super::{bail, Expr, ExprKind, ParserState, Result, Span, Token};
fn parse_struct_base(state: &mut ParserState) -> Result<Option<Box<Expr>>> {
if matches!(state.tokens.peek(), Some((Token::DotDot, _))) {
state.tokens.advance(); let base = Some(Box::new(super::parse_expr_recursive(state)?));
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance();
}
Ok(base)
} else {
Ok(None)
}
}
fn parse_field_name(state: &mut ParserState) -> Result<String> {
if let Some((Token::Identifier(name), _)) = state.tokens.peek() {
let name = name.clone();
state.tokens.advance();
Ok(name)
} else {
bail!("Expected field name");
}
}
fn parse_field_value(state: &mut ParserState, field_name: &str, start_span: Span) -> Result<Expr> {
if matches!(state.tokens.peek(), Some((Token::Colon, _))) {
state.tokens.advance(); super::parse_expr_recursive(state)
} else {
Ok(Expr::new(
ExprKind::Identifier(field_name.to_string()),
start_span,
))
}
}
fn consume_trailing_comma(state: &mut ParserState) -> bool {
if matches!(state.tokens.peek(), Some((Token::Comma, _))) {
state.tokens.advance();
true
} else {
false
}
}
pub fn parse_struct_literal(
state: &mut ParserState,
name: String,
start_span: Span,
) -> Result<Expr> {
state.tokens.expect(&Token::LeftBrace)?;
let mut fields = Vec::new();
let mut base = None;
while !matches!(state.tokens.peek(), Some((Token::RightBrace, _))) {
if let Some(base_expr) = parse_struct_base(state)? {
base = Some(base_expr);
break;
}
let field_name = parse_field_name(state)?;
let value = parse_field_value(state, &field_name, start_span)?;
fields.push((field_name, value));
if !consume_trailing_comma(state) {
break;
}
}
state.tokens.expect(&Token::RightBrace)?;
Ok(Expr::new(
ExprKind::StructLiteral { name, fields, base },
start_span,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_struct_literal_empty() {
let mut state = ParserState::new("{ }");
let result = parse_struct_literal(&mut state, "Point".to_string(), Span::default());
assert!(result.is_ok());
let expr = result.unwrap();
if let ExprKind::StructLiteral { name, fields, base } = expr.kind {
assert_eq!(name, "Point");
assert!(fields.is_empty());
assert!(base.is_none());
} else {
panic!("Expected StructLiteral");
}
}
#[test]
fn test_parse_struct_literal_single_field() {
let mut state = ParserState::new("{ x: 10 }");
let result = parse_struct_literal(&mut state, "Point".to_string(), Span::default());
assert!(result.is_ok());
let expr = result.unwrap();
if let ExprKind::StructLiteral { name, fields, .. } = expr.kind {
assert_eq!(name, "Point");
assert_eq!(fields.len(), 1);
assert_eq!(fields[0].0, "x");
} else {
panic!("Expected StructLiteral");
}
}
#[test]
fn test_parse_struct_literal_multiple_fields() {
let mut state = ParserState::new("{ x: 10, y: 20 }");
let result = parse_struct_literal(&mut state, "Point".to_string(), Span::default());
assert!(result.is_ok());
let expr = result.unwrap();
if let ExprKind::StructLiteral { fields, .. } = expr.kind {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].0, "x");
assert_eq!(fields[1].0, "y");
} else {
panic!("Expected StructLiteral");
}
}
#[test]
fn test_parse_struct_literal_trailing_comma() {
let mut state = ParserState::new("{ x: 10, y: 20, }");
let result = parse_struct_literal(&mut state, "Point".to_string(), Span::default());
assert!(result.is_ok());
let expr = result.unwrap();
if let ExprKind::StructLiteral { fields, .. } = expr.kind {
assert_eq!(fields.len(), 2);
} else {
panic!("Expected StructLiteral");
}
}
#[test]
fn test_parse_struct_literal_field_shorthand() {
let mut state = ParserState::new("{ x, y }");
let result = parse_struct_literal(&mut state, "Point".to_string(), Span::default());
assert!(result.is_ok());
let expr = result.unwrap();
if let ExprKind::StructLiteral { fields, .. } = expr.kind {
assert_eq!(fields.len(), 2);
assert_eq!(fields[0].0, "x");
assert_eq!(fields[1].0, "y");
} else {
panic!("Expected StructLiteral");
}
}
#[test]
fn test_consume_trailing_comma() {
let mut state = ParserState::new(", x");
let result = consume_trailing_comma(&mut state);
assert!(result);
}
#[test]
fn test_consume_trailing_comma_none() {
let mut state = ParserState::new("x");
let result = consume_trailing_comma(&mut state);
assert!(!result);
}
}