use super::leading_lines::DistinctLeadingLines;
use super::lists::ListOperations;
use super::strings::StringOperations;
use super::text::TextOperations;
use super::warning::WarnCall;
use crate::diagnostic::Diagnostic;
use crate::interpret::arguments::{Arguments, BuiltinSite};
use crate::interpret::{CodeList, StepBudget, Value, Warnings, WorkMeter};
use crate::reader::{Form, FormKind, Symbol};
use crate::source::{SourceId, Span};
struct WorkFixture;
struct IgnoredWarnings;
impl Warnings for IgnoredWarnings {
fn report_warning(&mut self, _warning: Diagnostic) {}
}
impl WorkFixture {
const MOST_WORK: u64 = 4_096;
const FUNCTION: &'static str = "built-in";
const SIXTEEN_BYTES: &'static str = "abcdefghijklmnop";
const BYTES_BUILT_PER_STEP: usize = 64;
const GROWING_TEXT: usize = 64;
fn span() -> Span {
Span::new(SourceId::new(0), 0..0)
}
fn symbol(name: &str) -> Value {
Value::Form(Form::new(FormKind::Symbol(Symbol::new(name)), Self::span()))
}
fn string(text: &str) -> Value {
Value::Form(Form::new(FormKind::String(text.into()), Self::span()))
}
fn built_list(items: Vec<Value>) -> Value {
Value::List(CodeList::new(items, Self::span()).expect("within the limit"))
}
fn read_list(count: usize) -> Value {
let leaf = Form::new(FormKind::Symbol(Symbol::new("s")), Self::span());
Value::Form(Form::new(FormKind::List(vec![leaf; count]), Self::span()))
}
fn arguments(values: &[Value], limit: u64) -> Arguments<'static> {
let site = BuiltinSite {
function: Self::FUNCTION,
span: Self::span(),
};
let budget = StepBudget::full();
budget.set_limit(limit);
Arguments::new(
site,
values.to_vec(),
Box::leak(Box::new(WorkMeter::new(budget, Self::span()))),
)
}
fn work(values: &[Value], run: impl Fn(&Arguments) -> Result<Value, Diagnostic>) -> u64 {
(0..=Self::MOST_WORK)
.find(|limit| run(&Self::arguments(values, *limit)).is_ok())
.expect("the work fits the largest limit")
}
fn list_work(
values: &[Value],
run: impl for<'a> Fn(&ListOperations<'a>) -> Result<Value, Diagnostic>,
) -> u64 {
Self::work(values, |arguments| {
run(&ListOperations::new(arguments, Self::span()))
})
}
fn text_work(
values: &[Value],
run: impl for<'a> Fn(&TextOperations<'a>) -> Result<Value, Diagnostic>,
) -> u64 {
Self::work(values, |arguments| {
run(&TextOperations::new(arguments, Self::span()))
})
}
fn string_work(
values: &[Value],
run: impl for<'a> Fn(&StringOperations<'a>) -> Result<Value, Diagnostic>,
) -> u64 {
Self::work(values, |arguments| {
run(&StringOperations::new(arguments, Self::span()))
})
}
fn builds_within_bound(
values: &[Value],
run: impl Fn(&Arguments) -> Result<Value, Diagnostic>,
) -> bool {
let work = Self::work(values, &run) as usize;
let built = run(&Self::arguments(values, Self::MOST_WORK)).expect("it builds text");
match built.as_form().map(Form::kind) {
Some(FormKind::String(text)) => text.len() <= Self::BYTES_BUILT_PER_STEP * work,
_ => panic!("the built-in builds text"),
}
}
fn text_within_bound(
values: &[Value],
run: impl for<'a> Fn(&TextOperations<'a>) -> Result<Value, Diagnostic>,
) -> bool {
Self::builds_within_bound(values, |arguments| {
run(&TextOperations::new(arguments, Self::span()))
})
}
fn strings_within_bound(
values: &[Value],
run: impl for<'a> Fn(&StringOperations<'a>) -> Result<Value, Diagnostic>,
) -> bool {
Self::builds_within_bound(values, |arguments| {
run(&StringOperations::new(arguments, Self::span()))
})
}
fn repeated(text: &str) -> Value {
Self::string(&text.repeat(Self::GROWING_TEXT))
}
fn leaves(count: usize) -> Vec<Value> {
vec![Self::symbol("s"); count]
}
}
#[test]
fn list_charges_one_step_per_argument() {
assert_eq!(
WorkFixture::list_work(&WorkFixture::leaves(3), |lists| lists.list()),
3
);
}
#[test]
fn prepend_charges_every_copied_item_and_the_new_one() {
let values = [WorkFixture::symbol("s"), WorkFixture::read_list(4)];
assert_eq!(WorkFixture::list_work(&values, |lists| lists.prepend()), 5);
}
#[test]
fn concat_at_the_end_of_a_buffer_charges_only_the_added_items() {
let head = WorkFixture::built_list(WorkFixture::leaves(10));
let values = [head, WorkFixture::read_list(3)];
assert_eq!(WorkFixture::list_work(&values, |lists| lists.concat()), 3);
}
#[test]
fn concat_that_copies_its_head_charges_the_copy() {
let values = [WorkFixture::read_list(10), WorkFixture::read_list(3)];
assert_eq!(WorkFixture::list_work(&values, |lists| lists.concat()), 13);
}
#[test]
fn splitting_lines_charges_each_line_it_builds() {
let text = "a\nb\nc\nd";
assert_eq!(
WorkFixture::string_work(&[WorkFixture::string(text)], |strings| strings.lines()),
1 + 4 * StepBudget::STEPS_PER_BUILT_FORM as u64
);
}
#[test]
fn a_string_argument_charges_one_step_per_sixteen_bytes() {
let text = WorkFixture::SIXTEEN_BYTES.repeat(2);
assert_eq!(
WorkFixture::string_work(&[WorkFixture::string(&text)], |strings| strings.length()),
2
);
}
#[test]
fn a_string_of_one_byte_more_than_a_step_charges_another() {
let text = format!("{}x", WorkFixture::SIXTEEN_BYTES);
assert_eq!(
WorkFixture::string_work(&[WorkFixture::string(&text)], |strings| strings.length()),
2
);
}
#[test]
fn a_symbol_name_charges_by_its_bytes() {
let name = WorkFixture::SIXTEEN_BYTES.repeat(3);
assert_eq!(
WorkFixture::text_work(&[WorkFixture::symbol(&name)], |text| text.symbol_name()),
3
);
}
#[test]
fn a_keyword_name_charges_by_its_bytes() {
let keyword = format!(":{}", WorkFixture::SIXTEEN_BYTES.repeat(2));
assert_eq!(
WorkFixture::text_work(&[WorkFixture::symbol(&keyword)], |text| text.keyword_name()),
2
);
}
#[test]
fn a_keyword_field_charges_by_its_bytes() {
let keyword = format!(":{}", WorkFixture::SIXTEEN_BYTES.repeat(2));
assert_eq!(
WorkFixture::text_work(&[WorkFixture::symbol(&keyword)], |text| text
.keyword_field()),
2
);
}
#[test]
fn a_field_keyword_charges_by_its_bytes() {
let field = WorkFixture::SIXTEEN_BYTES.repeat(2);
let work = WorkFixture::text_work(&[WorkFixture::symbol(&field)], |text| text.field_keyword());
assert_eq!(work, 3);
}
#[test]
fn joining_parts_charges_each_part() {
let part = WorkFixture::string(&WorkFixture::SIXTEEN_BYTES.repeat(2));
let values = [part.clone(), part];
assert_eq!(
WorkFixture::text_work(&values, |text| text.make_string()),
4
);
}
#[test]
fn value_eq_charges_one_step_per_form_and_the_bytes_of_each_symbol() {
let values = [WorkFixture::read_list(4), WorkFixture::read_list(4)];
assert_eq!(
WorkFixture::text_work(&values, |text| text.value_eq()),
1 + 4 * 2
);
}
#[test]
fn value_eq_charges_the_bytes_of_equal_strings() {
let text = WorkFixture::string(&WorkFixture::SIXTEEN_BYTES.repeat(4));
let values = [text.clone(), text];
assert_eq!(
WorkFixture::text_work(&values, |text| text.value_eq()),
1 + 4
);
}
#[test]
fn value_eq_charges_the_forms_of_a_built_list() {
let values = [
WorkFixture::built_list(WorkFixture::leaves(3)),
WorkFixture::symbol("s"),
];
assert_eq!(
WorkFixture::text_work(&values, |text| text.value_eq()),
4 + 1
);
}
#[test]
fn value_eq_of_two_built_lists_charges_both_conversions() {
let list = WorkFixture::built_list(WorkFixture::leaves(2));
let values = [list.clone(), list];
assert_eq!(
WorkFixture::text_work(&values, |text| text.value_eq()),
3 + 3 + 1 + 2 * 2
);
}
#[test]
fn a_form_argument_charges_only_when_converted_from_a_built_list() {
let read = WorkFixture::read_list(4);
assert_eq!(
WorkFixture::work(&[read], |args| args.form(0).map(Value::Form)),
0
);
let built = WorkFixture::built_list(WorkFixture::leaves(4));
assert_eq!(
WorkFixture::work(&[built], |args| args.form(0).map(Value::Form)),
5
);
}
#[test]
fn sorted_names_charges_forms_bytes_and_the_new_list() {
let values = [WorkFixture::read_list(3)];
assert_eq!(
WorkFixture::text_work(&values, |text| text.sorted_names()),
3 + 1 + 1
);
}
#[test]
fn closest_name_charges_the_product_of_the_name_and_candidate_bytes() {
let values = [WorkFixture::symbol("ab"), WorkFixture::read_list(3)];
assert_eq!(
WorkFixture::text_work(&values, |text| text.closest_name()),
1 + 3 + 2 * 3
);
}
#[test]
fn distinct_leading_lines_charges_each_text() {
let text = WorkFixture::string(&format!("{}\nx", WorkFixture::SIXTEEN_BYTES));
let forms = Value::Form(Form::new(
FormKind::List(vec![text.as_form().cloned().expect("a form")]),
WorkFixture::span(),
));
let work = WorkFixture::work(&[forms], |arguments| {
DistinctLeadingLines::new(arguments, WorkFixture::span()).kept_texts()
});
assert_eq!(work, 1 + 3);
}
#[test]
fn escaping_a_string_builds_within_the_bound() {
let values = [WorkFixture::repeated("\u{1}")];
assert!(WorkFixture::text_within_bound(&values, |text| text.escape_string()));
}
#[test]
fn escaping_a_character_builds_within_the_bound() {
let values = [WorkFixture::repeated("\u{1}")];
assert!(WorkFixture::text_within_bound(&values, |text| text.escape_char()));
}
#[test]
fn escaping_a_format_builds_within_the_bound() {
let values = [WorkFixture::repeated("{\u{1}")];
assert!(WorkFixture::text_within_bound(&values, |text| text.escape_format()));
}
#[test]
fn stripping_a_suffix_builds_within_the_bound() {
let values = [WorkFixture::repeated("ab"), WorkFixture::string("b")];
assert!(WorkFixture::text_within_bound(&values, |text| text.string_strip_suffix()));
}
#[test]
fn stripping_a_prefix_builds_within_the_bound() {
let values = [WorkFixture::repeated("ab"), WorkFixture::string("a")];
assert!(WorkFixture::text_within_bound(&values, |text| text.string_strip_prefix()));
}
#[test]
fn naming_a_symbol_builds_within_the_bound() {
let values = [WorkFixture::symbol(&"s".repeat(WorkFixture::GROWING_TEXT))];
assert!(WorkFixture::text_within_bound(&values, |text| text.symbol_name()));
}
#[test]
fn naming_a_keyword_builds_within_the_bound() {
let keyword = format!(":{}", "k".repeat(WorkFixture::GROWING_TEXT));
let values = [WorkFixture::symbol(&keyword)];
assert!(WorkFixture::text_within_bound(&values, |text| text.keyword_name()));
}
#[test]
fn lowering_a_string_stays_within_the_bound() {
let values = [WorkFixture::repeated("\u{130}")];
assert!(WorkFixture::strings_within_bound(&values, |strings| {
strings.lowercase()
}));
}
#[test]
fn trimming_a_string_builds_within_the_bound() {
let values = [WorkFixture::repeated(" a")];
assert!(WorkFixture::strings_within_bound(&values, |strings| {
strings.trim_start()
}));
assert!(WorkFixture::strings_within_bound(&values, |strings| {
strings.trim_end()
}));
}
#[test]
fn dropping_from_a_string_builds_within_the_bound() {
let count = Value::Form(Form::new(
FormKind::Number(crate::reader::Number::new(
"1",
crate::reader::NumberValue::Integer(1),
)),
WorkFixture::span(),
));
let values = [WorkFixture::repeated("ab"), count];
assert!(WorkFixture::strings_within_bound(&values, |strings| {
strings.drop()
}));
}
#[test]
fn warn_charges_a_built_description_once() {
let description = WorkFixture::built_list(vec![
WorkFixture::symbol(":code"),
WorkFixture::symbol("unmatched-link"),
]);
let values = [
WorkFixture::string("parked"),
WorkFixture::symbol("van"),
description,
];
let reading = WorkFixture::work(&values, |arguments| {
arguments.string(0)?;
arguments.list_forms(2).map(|_| Value::Unit)
});
let warning = WorkFixture::work(&values, |arguments| {
WarnCall::new(arguments).report(&mut IgnoredWarnings)
});
assert_eq!(warning, reading);
}