Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+127
View File
@@ -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);
}
}