use crate::interpret::{ListConversion, PassedLimit, StepBudget, Value};
use crate::reader::Form;
use crate::scope::Scope;
#[derive(Clone)]
pub struct FormBudget {
remaining: usize,
steps: StepBudget,
}
impl FormBudget {
pub const EXPANSION_LIMIT: usize = ListConversion::FORM_LIMIT;
const NO_ENCLOSING_LISTS: usize = 0;
pub fn full() -> Self {
Self {
remaining: Self::EXPANSION_LIMIT,
steps: StepBudget::full(),
}
}
pub fn steps(&self) -> StepBudget {
self.steps.clone()
}
pub fn exhaust(&mut self) {
self.remaining = 0;
}
pub fn is_exhausted(&self) -> bool {
self.remaining == 0
}
pub fn admitted_form(&self, value: &Value) -> Result<Form, PassedLimit> {
let mut allowance = self.remaining;
value.to_form_within(&mut allowance, Self::NO_ENCLOSING_LISTS)
}
pub fn flip_scope(&mut self, form: &mut Form, scope: Scope, counted_forms: usize) -> bool {
let mut allowance = self.remaining.saturating_add(counted_forms);
let flipped = form.flip_scope_within(scope, &mut allowance);
self.remaining = if flipped {
allowance.min(self.remaining)
} else {
0
};
flipped
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interpret::CodeList;
use crate::reader::{FormKind, Symbol};
use crate::scope::ScopeAllocator;
use crate::source::{SourceId, Span};
struct BudgetFixture {
budget: FormBudget,
scopes: ScopeAllocator,
}
impl BudgetFixture {
const NOTHING_COUNTED: usize = 0;
fn new() -> Self {
Self {
budget: FormBudget::full(),
scopes: ScopeAllocator::default(),
}
}
fn admits_list_of(&mut self, forms: usize) -> bool {
let mut list = Self::list_of(forms);
let scope = self.scopes.fresh();
self.budget
.flip_scope(&mut list, scope, Self::NOTHING_COUNTED)
}
fn admits_list_counting(&mut self, forms: usize, counted_forms: usize) -> bool {
let mut list = Self::list_of(forms);
let scope = self.scopes.fresh();
self.budget.flip_scope(&mut list, scope, counted_forms)
}
fn admits_value(&self, value: &Value) -> bool {
self.budget.admitted_form(value).is_ok()
}
fn list_of(forms: usize) -> Form {
let items = vec![Self::leaf(); forms - 1];
Form::new(FormKind::List(items), Self::span())
}
fn code_list(items: Vec<Value>) -> Value {
Value::List(CodeList::new(items, Self::span()).expect("within the limit"))
}
fn leaf() -> Form {
Form::new(FormKind::Symbol(Symbol::new("leaf")), Self::span())
}
fn span() -> Span {
Span::new(SourceId::new(0), 0..0)
}
}
#[test]
fn admits_an_expansion_of_exactly_the_limit() {
let mut fixture = BudgetFixture::new();
assert!(fixture.admits_list_of(FormBudget::EXPANSION_LIMIT));
}
#[test]
fn rejects_an_expansion_one_form_past_the_limit() {
let mut fixture = BudgetFixture::new();
assert!(!fixture.admits_list_of(FormBudget::EXPANSION_LIMIT + 1));
}
#[test]
fn admits_counted_forms_beyond_the_limit() {
let mut fixture = BudgetFixture::new();
assert!(fixture.admits_list_counting(FormBudget::EXPANSION_LIMIT + 1, 1));
assert!(!fixture.admits_list_of(1));
}
#[test]
fn gains_no_budget_from_counted_forms_it_did_not_return() {
let mut fixture = BudgetFixture::new();
assert!(fixture.admits_list_counting(1, FormBudget::EXPANSION_LIMIT));
assert!(fixture.admits_list_of(FormBudget::EXPANSION_LIMIT));
assert!(!fixture.admits_list_of(1));
}
#[test]
fn spends_the_rest_of_the_budget_on_a_failed_expansion() {
let mut fixture = BudgetFixture::new();
assert!(!fixture.admits_list_counting(FormBudget::EXPANSION_LIMIT + 2, 1));
assert!(!fixture.admits_list_of(1));
}
#[test]
fn counts_the_forms_of_every_expansion_against_one_limit() {
let mut fixture = BudgetFixture::new();
let half = FormBudget::EXPANSION_LIMIT / 2;
assert!(fixture.admits_list_of(half));
assert!(fixture.admits_list_of(half));
assert!(!fixture.admits_list_of(1));
}
#[test]
fn admits_a_converted_list_of_exactly_the_limit() {
let leaves = vec![Value::Form(BudgetFixture::leaf()); FormBudget::EXPANSION_LIMIT - 1];
let list = BudgetFixture::code_list(leaves);
assert!(BudgetFixture::new().admits_value(&list));
}
#[test]
fn rejects_a_converted_list_whose_leaves_pass_the_limit() {
let leaves = vec![Value::Form(BudgetFixture::leaf()); FormBudget::EXPANSION_LIMIT];
let list = BudgetFixture::code_list(leaves);
assert!(!BudgetFixture::new().admits_value(&list));
}
#[test]
fn rejects_a_converted_list_whose_inner_lists_pass_the_limit() {
let empty_lists = vec![BudgetFixture::code_list(Vec::new()); FormBudget::EXPANSION_LIMIT];
let list = BudgetFixture::code_list(empty_lists);
assert!(!BudgetFixture::new().admits_value(&list));
}
#[test]
fn a_cloned_budget_spends_from_the_same_steps() {
let budget = FormBudget::full();
budget.steps().set_limit(1);
let restored = budget.clone();
assert!(restored.steps().spend().is_ok());
assert!(budget.steps().spend().is_err());
}
#[test]
fn a_full_budget_is_not_exhausted() {
assert!(!FormBudget::full().is_exhausted());
}
#[test]
fn spending_every_form_exhausts_the_budget() {
let mut fixture = BudgetFixture::new();
assert!(fixture.admits_list_of(FormBudget::EXPANSION_LIMIT));
assert!(fixture.budget.is_exhausted());
}
#[test]
fn an_exhausted_budget_admits_no_form() {
let mut fixture = BudgetFixture::new();
fixture.budget.exhaust();
assert!(fixture.budget.is_exhausted());
assert!(!fixture.admits_list_of(1));
}
}