use dever::syntax::{ ast::{Declaration, ExpressionKind, Statement}, source::Source, }; #[test] fn local_annotations_require_a_name_type_and_initializer() { let source = Source::new( 0, "case.dever", "main() () { value: List = [null, 1] }".into(), ); let parsed = dever::syntax::parse(&source).unwrap(); let Declaration::Function(function) = &parsed.declarations[0] else { panic!() }; let Statement::Assign { annotation: Some(kind), .. } = &function.body[0] else { panic!() }; assert_eq!(kind.name, "List"); assert!(kind.arguments[0].nullable); for body in ["row.value: Int = 1", "value: Int", "value: = 1"] { let source = Source::new(0, "case.dever", format!("main() () {{ {body} }}")); assert!(dever::syntax::parse(&source).is_err(), "{body}"); } } #[test] fn preserves_expression_precedence_and_original_unicode_positions() { let source = Source::new( 0, "case.dever", "# 中文\r\nmain() (answer: Int) { answer = 2 + 3 * 4 }".into(), ); let parsed = dever::syntax::parse(&source).unwrap(); let Declaration::Function(function) = &parsed.declarations[0] else { panic!() }; let Statement::Assign { value, .. } = &function.body[0] else { panic!() }; let ExpressionKind::Binary(operator, _, right) = &value.kind else { panic!() }; assert_eq!(operator, "+"); assert!(matches!(&right.kind, ExpressionKind::Binary(operator, _, _) if operator == "*")); assert_eq!(source.position(function.span.start), (2, 1)); } #[test] fn parses_range_clauses_and_rejects_excluded_control_flow() { let source = Source::new(0, "case.dever", "pick(value: Int < 0) (answer: Int) { answer = -value }\npick(value: Int >= 0) (answer: Int) { answer = value }".into()); assert_eq!(dever::syntax::parse(&source).unwrap().declarations.len(), 2); let source = Source::new(0, "case.dever", "main() () { while true {} }".into()); assert_eq!(dever::syntax::parse(&source).unwrap_err().code, "L003"); } #[test] fn parses_handler_signatures_only_at_function_inputs() { let source = Source::new(0, "handler.dever", "apply(route: handler(value: List) (answer: Text), value: Int) (answer: Text) { answer = route([value]) }".into()); let module = dever::syntax::parse(&source).unwrap(); let Declaration::Function(function) = &module.declarations[0] else { panic!() }; let dever::syntax::ast::Pattern::Handler(inputs, outputs) = &function.inputs[0].pattern else { panic!() }; assert_eq!(inputs[0].kind.name, "List"); assert!(inputs[0].kind.arguments[0].nullable); assert_eq!(outputs[0].name, "answer"); assert_eq!(outputs[0].kind.name, "Text"); for text in [ "type Row { route: handler() () }", "value() (route: handler() ()) {}", ] { assert!(dever::syntax::parse(&Source::new(0, "invalid.dever", text.into())).is_err()); } } #[test] fn text_patterns_keep_decoded_values_spans_and_declaration_lookahead() { let source = Source::new( 0, "tokens.dever", "pick(value: \"雪\\u{1f642}\") (answer: Text) { answer = \"值\" }\r\npick(value: other) (answer: Text) { answer = value }\r\nget() (answer: Text) { answer = \"get\" }\r\nseed() (answer: Text) { answer = \"seed\" }".into(), ); let module = dever::syntax::parse(&source).unwrap(); let names = module .declarations .iter() .map(|declaration| { let Declaration::Function(function) = declaration else { panic!() }; assert_eq!(&source.text[function.span.end - 1..function.span.end], "}"); function.name.as_str() }) .collect::>(); assert_eq!(names, ["pick", "pick", "get", "seed"]); let Declaration::Function(function) = &module.declarations[0] else { panic!() }; assert!(matches!( &function.inputs[0].pattern, dever::syntax::ast::Pattern::Literal(dever::syntax::ast::Literal::Text(value)) if value == "雪🙂" )); } #[test] fn unexpected_eof_keeps_original_source_position() { for text in [ "# 雪\r\nmain() (answer: Text) { answer =", "# 雪\r\nmain(value:", "# 雪\r\nmain() (answer: Int) { answer = (1 +", ] { let source = Source::new(3, "eof.dever", text.into()); let error = dever::syntax::parse(&source).unwrap_err(); assert_eq!(error.code, "P001"); assert_eq!(error.span, source.span(text.len(), text.len())); assert_eq!(source.position(error.span.start).0, 2); } }