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

+258
View File
@@ -0,0 +1,258 @@
use dever::compiler::{check, model::Program};
use dever::syntax::{diagnostic::Diagnostic, source::Source};
fn linked(text: &str) -> Result<Program, Diagnostic> {
let sources = sources(text);
let modules = sources
.iter()
.map(dever::syntax::parse)
.collect::<Result<Vec<_>, _>>()?;
check::check(
modules,
&["main".into(), "dever.bytes".into(), "text".into()],
)
}
fn sources(text: &str) -> [Source; 3] {
[
Source::new(0, "main.dever", text.into()),
Source::new(
1,
"lib/dever/bytes.dever",
include_str!("../../../lib/dever/bytes.dever").into(),
),
Source::new(
2,
"lib/text.dever",
include_str!("../../../lib/text.dever").into(),
),
]
}
fn generated_entry(text: &str) -> String {
let mut program = linked(text).unwrap();
let entry = program
.functions
.iter()
.position(|function| function.signature.name == "main")
.unwrap();
// 改名不改变内建操作身份,防止优化依赖 concat/from_text 的函数名称。
for (index, function) in program.functions.iter_mut().enumerate() {
function.signature.name = format!("renamed_{index}");
}
let generated = dever::compiler::emit::generate(&program, &sources(text), &[entry].into());
let start = generated.find(&format!("pub fn function{entry}(")).unwrap();
generated[start..]
.split("\npub ")
.next()
.unwrap()
.to_owned()
}
#[test]
fn borrowed_bytes_keep_their_shared_owner() {
let generated = generated_entry("main(bytes: Bytes) (answer: Bytes) { answer = bytes }");
assert!(
generated.contains("arg0: &dever_runtime::Bytes"),
"{generated}"
);
assert!(generated.contains(".to_owned()"), "{generated}");
assert!(!generated.contains("copy_from_slice"), "{generated}");
}
#[test]
fn text_byte_views_are_limited_to_non_escaping_iteration() {
let generated = generated_entry(
"add(item: Int, total: Int) (next: Int) { next = total + item }\nmain(text: Text) (answer: Int) { bytes = dever.bytes.fromText(text)\nanswer = reduce(add, bytes, 0) }",
);
assert!(generated.contains(".as_bytes()"), "{generated}");
assert!(!generated.contains("copy_from_slice"), "{generated}");
let generated = generated_entry(
"add(item: Int, total: Int) (next: Int) { next = total + item }\nmain(text: Text) (answer: Int) { bytes = dever.bytes.fromText(text)\nanswer = reduce(add, bytes, 0) + length(bytes) }",
);
assert!(!generated.contains("copy_from_slice"), "{generated}");
for body in [
"main(text: Text) (answer: Bytes) { bytes = dever.bytes.fromText(text)\nanswer = bytes }",
"identity(bytes: Bytes) (answer: Bytes) { answer = bytes }\nmain(text: Text) (answer: Bytes) { bytes = dever.bytes.fromText(text)\nanswer = identity(bytes) }",
] {
let generated = generated_entry(body);
assert!(generated.contains("copy_from_slice"), "{generated}");
}
}
#[test]
fn empty_separator_parts_are_fused_only_when_fully_concatenated() {
let join = "joinPart(item: Bytes, state: Bytes) (answer: Bytes) { answer = dever.bytes.concat(state, item) }\n";
let generated = generated_entry(
&(join.to_owned()
+ "main(text: Text, seed: Bytes) (answer: Bytes) { parts = each(dever.bytes.fromText, text.split(text, \"\"))\nanswer = reduce(joinPart, parts, seed) }"),
);
assert!(
generated.contains("state.extend_from_slice(sequence)"),
"{generated}"
);
assert!(!generated.contains("copy_from_slice"), "{generated}");
assert!(!generated.contains("collect::<Vec<_>>()"), "{generated}");
for body in [
"main(text: Text) (answer: List<Bytes>) { parts = each(dever.bytes.fromText, text.split(text, \"\"))\nanswer = parts }",
"main(text: Text, seed: Bytes) (answer: Bytes, count: Int) { parts = each(dever.bytes.fromText, text.split(text, \"\"))\ncount = length(parts)\nanswer = reduce(joinPart, parts, seed) }",
"main(text: Text, seed: Bytes) (answer: Bytes) { parts = each(dever.bytes.fromText, text.split(text, \",\"))\nanswer = reduce(joinPart, parts, seed) }",
"main(text: Text, separator: Text, seed: Bytes) (answer: Bytes) { parts = each(dever.bytes.fromText, text.split(text, separator))\nanswer = reduce(joinPart, parts, seed) }",
] {
let generated = generated_entry(&(join.to_owned() + body));
assert!(generated.contains("copy_from_slice"), "{generated}");
}
}
#[test]
fn literal_text_concat_allocates_only_the_final_buffer() {
for literal in [r#""""#, r#""prefix""#, r#""雪\u{0}🙂""#] {
let generated = generated_entry(&format!(
"main(suffix: Bytes) (answer: Bytes) {{ answer = dever.bytes.concat(dever.bytes.fromText({literal}), suffix) }}"
));
assert!(
generated.contains("value::bytes::concat(prefix, suffix)"),
"{generated}"
);
assert!(!generated.contains(".to_vec()"), "{generated}");
assert!(!generated.contains("extend_from_slice"), "{generated}");
}
}
#[test]
fn dynamic_text_and_owned_byte_accumulators_keep_their_existing_paths() {
for (source, operation) in [
(
"main(text: Text, suffix: Bytes) (answer: Bytes) { answer = dever.bytes.concat(dever.bytes.fromText(text), suffix) }",
"Bytes::copy_from_slice",
),
(
"main(suffix: Bytes) (answer: Bytes) { text = \"owned\"\n answer = dever.bytes.concat(dever.bytes.fromText(text), suffix) }",
".into_bytes()",
),
(
"main(suffix: Bytes) (answer: Bytes) { bytes = dever.bytes.fromText(\"owned\")\n answer = dever.bytes.concat(bytes, suffix) }",
"bytes.extend_from_slice(suffix)",
),
] {
let generated = generated_entry(source);
assert!(generated.contains(operation), "{generated}");
assert!(
!generated.contains("value::bytes::concat(prefix, suffix)"),
"{generated}"
);
}
}
#[test]
fn bytes_keep_their_declared_collection_contract() {
for source in [
"identity(item: Int) (answer: Int) { answer = item }\nmain(bytes: Bytes) (answer: List<Int>) { answer = each(identity, bytes) }",
"add(item: Int, state: Int) (next: Int) { next = item + state }\nmain(bytes: Bytes) (answer: Int) { answer = reduce(add, bytes, 0) }",
"main(values: List<Text>) (answer: List<Bytes>) { answer = each(dever.bytes.fromText, values) }",
"main(bytes: Bytes?) (answer: Bytes?) { answer = bytes }",
] {
let result = linked(source);
assert!(result.is_ok(), "{source}: {result:?}");
}
for source in [
"main(bytes: Bytes) (answer: Int) { answer = sum(bytes) }",
"identity(item: Int) (answer: Int) { answer = item }\nmain(bytes: Bytes) (answer: Int) { answer = sum(identity, bytes) }",
"yes(item: Int) (answer: Bool) { answer = true }\nmain(bytes: Bytes) (answer: List<Int>) { answer = filter(yes, bytes) }",
"yes(item: Int) (answer: Bool) { answer = true }\nmain(bytes: Bytes) (answer: Int?) { answer = find(yes, bytes) }",
"identity(item: Text) (answer: Text) { answer = item }\nmain(bytes: Bytes) (answer: List<Text>) { answer = each(identity, bytes) }",
"main(bytes: Bytes) (answer: List<Int>) { answer = bytes }",
"main(values: List<Int>) (answer: Bytes) { answer = values }",
"main(values: Map<Bytes, Int>) () {}",
] {
assert_eq!(linked(source).unwrap_err().code, "C005", "{source}");
}
}
#[test]
fn buffer_reductions_append_without_copying_the_accumulator() {
for (kind, body, method) in [
("Text", "state + item", "push_str"),
(
"Bytes",
"dever.bytes.concat(state, item)",
"extend_from_slice",
),
] {
let source = format!(
"appendPiece(item: {kind}, state: {kind}) (answer: {kind}) {{ answer = {body} }}\nmain(values: List<{kind}>, initial: {kind}) (answer: {kind}) {{ answer = reduce(appendPiece, values, initial) }}"
);
let generated = generated_entry(&source);
let iteration = generated.split("for item in sequence").nth(1).unwrap();
assert!(
iteration.contains(&format!("state.{method}(item)")),
"{generated}"
);
for copy in [".clone()", ".to_owned()", ".to_vec()"] {
assert!(!iteration.contains(copy), "{generated}");
}
}
}
#[test]
fn literal_buffer_seeds_do_not_roundtrip_through_immutable_bytes() {
for literal in [r#""""#, r#""seed""#, r#""雪\u{0}🙂""#] {
let generated = generated_entry(&format!(
"appendPiece(item: Bytes, state: Bytes) (answer: Bytes) {{ answer = dever.bytes.concat(state, item) }}\nmain(values: List<Bytes>) (answer: Bytes) {{ answer = reduce(appendPiece, values, dever.bytes.fromText({literal})) }}"
));
assert!(generated.contains("let mut state: Vec<u8>"), "{generated}");
assert!(!generated.contains("Bytes::from_static"), "{generated}");
assert!(!generated.contains("Vec::<u8>::from"), "{generated}");
}
}
#[test]
fn library_helpers_and_intrinsics_are_private() {
for source in [
"main() (answer: Bytes) { answer = dever.system.bytesFromText(\"x\") }",
"main() (answer: Bytes) { answer = dever.bytes.ready(dever.bytes.BufferResult.Ready(dever.bytes.fromText(\"x\"))) }",
"main() (answer: Text) { answer = dever.bytes.decoded(dever.bytes.DecodeResult.Decoded(\"x\")) }",
] {
assert_eq!(linked(source).unwrap_err().code, "C006", "{source}");
}
}
#[test]
fn byte_errors_keep_nominal_identity_and_obligations() {
assert_eq!(
linked("main(bytes: Bytes) () { captured = result(dever.bytes.toText(bytes)) }")
.unwrap_err()
.code,
"C012"
);
assert_eq!(linked("type Result { Decoded(text: Text)\n error Failed(message: Text) }\nmain(bytes: Bytes) (answer: Result) { answer = result(dever.bytes.toText(bytes)) }").unwrap_err().code, "C005");
assert!(linked("main(bytes: Bytes) (answer: dever.bytes.DecodeResult) { answer = result(dever.bytes.toText(bytes)) }").is_ok());
assert!(linked("type Result { Ready(text: Text)\n error Failed(message: Text) }\nidentity() (text: Text) { text = \"ok\" }\nmain() (answer: Result) { captured = result(identity())\n answer = captured }").is_ok());
}
#[test]
fn package_names_resolve_without_exposing_private_types() {
let library = Source::new(1, "sample.dever", "type Hidden { value: Int }\npublic type Visible { value: Int }\npublic read(value: Int) (answer: Int) { answer = value }".into());
for (source, accepted) in [
(
"main() (answer: sample.Visible) { answer = sample.Visible { value = sample.read(1) } }",
true,
),
("main(value: sample.Hidden) () {}", false),
("main() () { value = sample.Hidden { value = 1 } }", false),
(
"main() (answer: Visible) { answer = sample.Visible { value = 1 } }",
false,
),
] {
let source = Source::new(0, "main.dever", source.into());
let result = check::check(
vec![
dever::syntax::parse(&source).unwrap(),
dever::syntax::parse(&library).unwrap(),
],
&["main".into(), "sample".into()],
);
assert_eq!(result.is_ok(), accepted, "{result:?}");
}
}