knave-expand 0.14.2

The reading, scoping and expansion stages of knave
Documentation
use super::leading_lines::DistinctLeadingLines;
use super::lists::ListOperations;
use super::strings::StringOperations;
use super::text::TextOperations;
use crate::diagnostic::Diagnostic;
use crate::interpret::arguments::{Arguments, BuiltinSite};
use crate::interpret::{CodeList, StepBudget, Value, WorkMeter};
use crate::reader::{Form, FormKind, Symbol};
use crate::source::{SourceId, Span};

struct WorkFixture;

impl WorkFixture {
    const MOST_WORK: u64 = 4_096;
    const FUNCTION: &'static str = "built-in";
    const SIXTEEN_BYTES: &'static str = "abcdefghijklmnop";

    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 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 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);
}