knave-expand 0.14.21

The reading, scoping and expansion stages of knave
Documentation
pub use super::builtin_site::BuiltinSite;
use super::problems::InterpretProblems;
use super::work_meter::WorkMeter;
use super::{CodeList, Value};
use crate::diagnostic::Diagnostic;
use crate::reader::{Form, FormKind, NumberValue, Symbol};
use crate::source::Span;

pub struct Arguments<'m> {
    site: BuiltinSite,
    values: Vec<Value>,
    meter: &'m WorkMeter,
}

impl<'m> Arguments<'m> {
    pub fn new(site: BuiltinSite, values: Vec<Value>, meter: &'m WorkMeter) -> Self {
        Self {
            site,
            values,
            meter,
        }
    }

    pub fn charge(&self, work: usize) -> Result<(), Diagnostic> {
        self.meter.charge(work)
    }

    pub fn span(&self) -> Span {
        self.site.span
    }

    pub fn function(&self) -> &'static str {
        self.site.function
    }

    pub fn values(&self) -> &[Value] {
        &self.values
    }

    pub fn expect_count(&self, expected: usize) -> Result<(), Diagnostic> {
        if self.values.len() == expected {
            Ok(())
        } else {
            Err(InterpretProblems::builtin_argument_count(
                self.site.function,
                expected,
                self.values.len(),
                self.site.span,
            ))
        }
    }

    pub fn value(&self, index: usize) -> &Value {
        &self.values[index]
    }

    pub fn code(&self, index: usize) -> Result<Value, Diagnostic> {
        let value = self.value(index);
        if value.is_code() {
            Ok(value.clone())
        } else {
            Err(self.wrong(index, "code"))
        }
    }

    pub fn form(&self, index: usize) -> Result<Form, Diagnostic> {
        let form = self.values[index]
            .to_form()
            .map_err(|large| large.at(self.site.span))?
            .ok_or_else(|| self.wrong(index, "code"))?;
        if matches!(self.values[index], Value::List(_)) {
            self.charge(form.form_count())?;
        }
        Ok(form)
    }

    pub fn list(&self, index: usize) -> Result<CodeList, Diagnostic> {
        self.code(index)?
            .as_code_list()
            .ok_or_else(|| self.wrong(index, "a list"))
    }

    pub fn list_forms(&self, index: usize) -> Result<Vec<Form>, Diagnostic> {
        let forms = self
            .list(index)?
            .forms()
            .map_err(|large| large.at(self.site.span))?;
        self.charge(forms.iter().map(Form::form_count).sum())?;
        Ok(forms)
    }

    pub fn symbol(&self, index: usize) -> Result<&Symbol, Diagnostic> {
        let symbol = self
            .atom(index, "a symbol")?
            .as_symbol()
            .ok_or_else(|| self.wrong(index, "a symbol"))?;
        self.charge(WorkMeter::text_work(symbol.name().len()))?;
        Ok(symbol)
    }

    pub fn symbol_forms(&self, index: usize) -> Result<Vec<Form>, Diagnostic> {
        let forms = self.list_forms(index)?;
        if forms.iter().all(|form| form.as_symbol().is_some()) {
            Ok(forms)
        } else {
            Err(self.wrong(index, "a list of symbols"))
        }
    }

    pub fn keyword_name(&self, index: usize) -> Result<&str, Diagnostic> {
        let name = self.values[index]
            .keyword_name()
            .ok_or_else(|| self.wrong(index, "a keyword"))?;
        self.charge(WorkMeter::text_work(name.len()))?;
        Ok(name)
    }

    pub fn keyword_part(
        &self,
        index: usize,
        read: fn(&Value) -> Option<Form>,
    ) -> Result<Form, Diagnostic> {
        let part = read(&self.values[index]).ok_or_else(|| self.wrong(index, "a keyword"))?;
        self.charge(WorkMeter::text_work(
            part.as_symbol().map_or(0, |symbol| symbol.name().len()),
        ))?;
        Ok(part)
    }

    pub fn field_keyword(&self, index: usize) -> Result<Form, Diagnostic> {
        let keyword = self.values[index]
            .field_keyword()
            .ok_or_else(|| self.wrong(index, "a field name"))?;
        self.charge(WorkMeter::text_work(
            keyword.as_symbol().map_or(0, |symbol| symbol.name().len()),
        ))?;
        Ok(keyword)
    }

    pub fn string(&self, index: usize) -> Result<&str, Diagnostic> {
        match self.atom(index, "a string")?.kind() {
            FormKind::String(text) => {
                self.charge(WorkMeter::text_work(text.len()))?;
                Ok(text)
            }
            _ => Err(self.wrong(index, "a string")),
        }
    }

    pub fn integer(&self, index: usize) -> Result<i128, Diagnostic> {
        match self.atom(index, "an integer")?.kind() {
            FormKind::Number(number) => match number.value() {
                NumberValue::Integer(value) => Ok(*value),
                NumberValue::Float(_) => Err(self.wrong(index, "an integer")),
            },
            _ => Err(self.wrong(index, "an integer")),
        }
    }

    pub fn index(&self, index: usize) -> Result<usize, Diagnostic> {
        usize::try_from(self.integer(index)?).map_err(|_| self.wrong(index, "a count"))
    }

    pub fn boolean(&self, index: usize) -> Result<bool, Diagnostic> {
        match self.values[index] {
            Value::Bool(value) => Ok(value),
            _ => Err(self.wrong(index, "a boolean")),
        }
    }

    fn atom(&self, index: usize, expected: &str) -> Result<&Form, Diagnostic> {
        match &self.values[index] {
            Value::Form(form) => Ok(form),
            Value::List(_) => Err(self.wrong(index, expected)),
            _ => Err(self.wrong(index, "code")),
        }
    }

    fn wrong(&self, index: usize, expected: &str) -> Diagnostic {
        let position = index + 1;
        InterpretProblems::wrong_argument(
            self.site.function,
            position,
            expected,
            &self.values[index],
            self.site.span,
        )
    }
}