diff --git a/src/parser.rs b/src/parser.rs index da9b970..7a1d259 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -298,203 +298,222 @@ impl ParserError { #[cfg(test)] mod tests { use super::*; - use crate::{ast::*, logging::start_test_tracing, scanner::scan}; - use claims::assert_matches; - fn parse_to_ast(input: &str) -> Ast { - let tokens = - scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - parse(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")) - } + mod stmts { + use super::tests::helper::*; + use crate::{ast::*, parser::*}; + use claims::assert_matches; + + #[test] + fn parses_multiple_stmts() { + let ast = parse_to_ast("print 1 + 1; 3 > 0;"); + assert_eq!(ast.0.len(), 2); + assert_matches!(&ast.0[0].as_ref(), Stmt::Print { .. }); + assert_matches!(&ast.0[1].as_ref(), Stmt::Expr { .. }); + } - fn parse_to_expr(input: &str) -> Spanned { - let tokens = - scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - parse_expr(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")) - } + #[test] + fn parses_expr_stmt() { + let stmt = parse_stmt("1 + 1;"); + assert_matches!(stmt.unwrap_expr().as_ref(), Expr::Binary { .. }) + } - fn parse_stmt(input: &str) -> Spanned { - let tokens = - scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - let ast = - parse(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")); - ast.0 - .into_iter() - .next() - .unwrap_or_else(|| panic!("expected at least one statement in input: {input}")) - } + #[test] + fn parses_print_stmt() { + let stmt = parse_stmt("print true;"); + assert_matches!(stmt.unwrap_print().as_ref(), Expr::Literal { .. }) + } - fn parse_to_expr_err(input: &str) -> ParserError { - let tokens = - scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - parse_expr(tokens).unwrap_err() + #[test] + fn errs_on_missing_semicolon() { + assert_matches!( + parse_stmt_to_err("1 + 1"), + ParserError::MissingSemicolon { .. } + ); + assert_matches!( + parse_stmt_to_err("print true"), + ParserError::MissingSemicolon { .. } + ); + } } - fn parse_stmt_to_err(input: &str) -> ParserError { - let tokens = - scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); - parse(tokens).unwrap_err() - } + mod exprs { + use super::helper::*; + use crate::{ast::*, logging::start_test_tracing, parser::*}; + use claims::assert_matches; - fn assert_binary_expr_op(input_map: &[(&'static str, BinaryOp)]) { - for (input, expected_op) in input_map { - let e = parse_to_expr(&format!("1 {input} 2")); - let (left, parsed_op, right) = e.unwrap_binary(); - assert_eq!(parsed_op, expected_op, "should parse to `{expected_op:?}`"); + #[test] + fn parses_mixed() { + let expr = parse_to_expr("1 + -1"); + let (left, op, right) = expr.unwrap_binary(); assert_eq!(left.unwrap_literal().unwrap_number(), 1.0); - assert_eq!(right.unwrap_literal().unwrap_number(), 2.0); - } - } + assert_eq!(*op, BinaryOp::Add); - #[test] - fn parses_multiple_stmts() { - let ast = parse_to_ast("print 1 + 1; 3 > 0;"); - assert_eq!(ast.0.len(), 2); - assert_matches!(&ast.0[0].as_ref(), Stmt::Print { .. }); - assert_matches!(&ast.0[1].as_ref(), Stmt::Expr { .. }); - } + let (u_op, u_expr) = right.unwrap_unary(); + assert_eq!(*u_op, UnaryOp::Negative); + assert_eq!(u_expr.unwrap_literal().unwrap_number(), 1.0); + } - #[test] - fn parses_expr_stmt() { - let stmt = parse_stmt("1 + 1;"); - assert_matches!(stmt.unwrap_expr().as_ref(), Expr::Binary { .. }) - } + #[test] + fn parses_complex() { + parse_to_expr("(1 + -1) * 2 > 3 == false"); + } - #[test] - fn parses_print_stmt() { - let stmt = parse_stmt("print true;"); - assert_matches!(stmt.unwrap_print().as_ref(), Expr::Literal { .. }) - } + #[test] + fn parses_equality_expr() { + use BinaryOp::*; + start_test_tracing(); + assert_binary_expr_op(&[("==", Eq), ("!=", NotEq)]); + } - #[test] - fn errs_on_missing_semicolon() { - assert_matches!( - parse_stmt_to_err("1 + 1"), - ParserError::MissingSemicolon { .. } - ); - assert_matches!( - parse_stmt_to_err("print true"), - ParserError::MissingSemicolon { .. } - ); - } + #[test] + fn parses_comparison_expr() { + use BinaryOp::*; + start_test_tracing(); + assert_binary_expr_op(&[ + (">", Greater), + (">=", GreaterEq), + ("<", Less), + ("<=", LessEq), + ]); + } - #[test] - fn parses_mixed() { - let expr = parse_to_expr("1 + -1"); - let (left, op, right) = expr.unwrap_binary(); - assert_eq!(left.unwrap_literal().unwrap_number(), 1.0); - assert_eq!(*op, BinaryOp::Add); + #[test] + fn parses_term_expr() { + use BinaryOp::*; + start_test_tracing(); + assert_binary_expr_op(&[("+", Add), ("-", Sub)]); + } - let (u_op, u_expr) = right.unwrap_unary(); - assert_eq!(*u_op, UnaryOp::Negative); - assert_eq!(u_expr.unwrap_literal().unwrap_number(), 1.0); - } + #[test] + fn parses_factor_expr() { + use BinaryOp::*; + start_test_tracing(); + assert_binary_expr_op(&[("*", Mult), ("/", Div)]); + } - #[test] - fn parses_complex() { - parse_to_expr("(1 + -1) * 2 > 3 == false"); - } + #[test] + fn parses_unary_expr() { + fn assert_unary_expr_op(input: &str, expected_op: UnaryOp) { + let expr = parse_to_expr(&format!("{input} 1")); + let (op, inner) = expr.unwrap_unary(); + assert_eq!(*op, expected_op, "should parse to op `{expected_op:?}`"); + assert_eq!(inner.unwrap_literal().unwrap_number(), 1.0); + } - #[test] - fn parses_equality_expr() { - use BinaryOp::*; - start_test_tracing(); - assert_binary_expr_op(&[("==", Eq), ("!=", NotEq)]); - } + start_test_tracing(); - #[test] - fn parses_comparison_expr() { - use BinaryOp::*; - start_test_tracing(); - assert_binary_expr_op(&[ - (">", Greater), - (">=", GreaterEq), - ("<", Less), - ("<=", LessEq), - ]); - } + assert_unary_expr_op("-", UnaryOp::Negative); + assert_unary_expr_op("!", UnaryOp::Invert); + } - #[test] - fn parses_term_expr() { - use BinaryOp::*; - start_test_tracing(); - assert_binary_expr_op(&[("+", Add), ("-", Sub)]); - } + #[test] + fn parses_primary_expr() { + #![allow(clippy::bool_assert_comparison)] + start_test_tracing(); - #[test] - fn parses_factor_expr() { - use BinaryOp::*; - start_test_tracing(); - assert_binary_expr_op(&[("*", Mult), ("/", Div)]); - } + assert_eq!( + parse_to_expr(r#""hello""#).unwrap_literal().unwrap_string(), + "hello" + ); + assert_eq!( + parse_to_expr("12.34").unwrap_literal().unwrap_number(), + 12.34 + ); + assert_eq!(parse_to_expr("true").unwrap_literal().unwrap_bool(), true); + assert_eq!(parse_to_expr("false").unwrap_literal().unwrap_bool(), false); + assert!(parse_to_expr("nil").unwrap_literal().is_nil()); + } - #[test] - fn parses_unary_expr() { - fn assert_unary_expr_op(input: &str, expected_op: UnaryOp) { - let expr = parse_to_expr(&format!("{input} 1")); - let (op, inner) = expr.unwrap_unary(); - assert_eq!(*op, expected_op, "should parse to op `{expected_op:?}`"); - assert_eq!(inner.unwrap_literal().unwrap_number(), 1.0); + #[test] + fn parses_grouping() { + start_test_tracing(); + assert_eq!( + parse_to_expr("(1)") + .unwrap_grouping() + .unwrap_literal() + .unwrap_number(), + 1.0 + ); } - start_test_tracing(); + #[test] + fn errs_with_unclosed_grouping() { + start_test_tracing(); + assert_matches!( + parse_to_expr_err("(1"), + ParserError::UnclosedGrouping { start: 0, end: 1 } + ); - assert_unary_expr_op("-", UnaryOp::Negative); - assert_unary_expr_op("!", UnaryOp::Invert); - } + assert_matches!( + parse_to_expr_err("(1 foo"), + ParserError::UnclosedGrouping { start: 0, end: 1 } + ); + } - #[test] - fn parses_primary_expr() { - #![allow(clippy::bool_assert_comparison)] - start_test_tracing(); - - assert_eq!( - parse_to_expr(r#""hello""#).unwrap_literal().unwrap_string(), - "hello" - ); - assert_eq!( - parse_to_expr("12.34").unwrap_literal().unwrap_number(), - 12.34 - ); - assert_eq!(parse_to_expr("true").unwrap_literal().unwrap_bool(), true); - assert_eq!(parse_to_expr("false").unwrap_literal().unwrap_bool(), false); - assert!(parse_to_expr("nil").unwrap_literal().is_nil()); + #[test] + fn errs_with_unexpected_token() { + assert_matches!( + parse_to_expr_err("}"), + ParserError::UnexpectedToken { + kind: TokenKind::RightBrace, + span: _, + } + ); + } } - #[test] - fn parses_grouping() { - start_test_tracing(); - assert_eq!( - parse_to_expr("(1)") - .unwrap_grouping() - .unwrap_literal() - .unwrap_number(), - 1.0 - ); - } + mod helper { + use crate::{ + ast::{Ast, BinaryOp, Expr, Stmt}, + parser::{parse, parse_expr, ParserError}, + scanner::scan, + span::Spanned, + }; - #[test] - fn errs_with_unclosed_grouping() { - start_test_tracing(); - assert_matches!( - parse_to_expr_err("(1"), - ParserError::UnclosedGrouping { start: 0, end: 1 } - ); - - assert_matches!( - parse_to_expr_err("(1 foo"), - ParserError::UnclosedGrouping { start: 0, end: 1 } - ); - } + pub fn parse_to_ast(input: &str) -> Ast { + let tokens = + scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); + parse(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")) + } - #[test] - fn errs_with_unexpected_token() { - assert_matches!( - parse_to_expr_err("}"), - ParserError::UnexpectedToken { - kind: TokenKind::RightBrace, - span: _, + pub fn parse_to_expr(input: &str) -> Spanned { + let tokens = + scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); + parse_expr(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")) + } + + pub fn parse_stmt(input: &str) -> Spanned { + let tokens = + scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); + let ast = + parse(tokens).unwrap_or_else(|e| panic!("input `{input}` to parse. error: {e:?}")); + ast.0 + .into_iter() + .next() + .unwrap_or_else(|| panic!("expected at least one statement in input: {input}")) + } + + pub fn parse_to_expr_err(input: &str) -> ParserError { + let tokens = + scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); + parse_expr(tokens).unwrap_err() + } + + pub fn parse_stmt_to_err(input: &str) -> ParserError { + let tokens = + scan(input).unwrap_or_else(|e| panic!("input `{input}` should scan. error: {e:?}")); + parse(tokens).unwrap_err() + } + + pub fn assert_binary_expr_op(input_map: &[(&'static str, BinaryOp)]) { + for (input, expected_op) in input_map { + let e = parse_to_expr(&format!("1 {input} 2")); + let (left, parsed_op, right) = e.unwrap_binary(); + assert_eq!(parsed_op, expected_op, "should parse to `{expected_op:?}`"); + assert_eq!(left.unwrap_literal().unwrap_number(), 1.0); + assert_eq!(right.unwrap_literal().unwrap_number(), 2.0); } - ); + } } }