Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,127 @@
|
||||
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<Int?> = [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<Int?>) (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::<Vec<_>>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user