knave-expand 0.9.3

The reading, scoping and expansion stages of knave
Documentation
use super::Forms;
use crate::diagnostic::Diagnostic;
use crate::interpret::arguments::Arguments;
use crate::interpret::problems::InterpretProblems;
use crate::interpret::{CodeList, Value};
use crate::reader::{Form, FormKind};
use crate::source::Span;

pub struct StringOperations<'a> {
    arguments: &'a Arguments,
    call_site: Span,
}

impl<'a> StringOperations<'a> {
    const LINE_BREAK: char = '\n';

    pub fn new(arguments: &'a Arguments, call_site: Span) -> Self {
        Self {
            arguments,
            call_site,
        }
    }

    pub fn lines(&self) -> Result<Value, Diagnostic> {
        self.arguments.expect_count(1)?;
        let lines = self
            .arguments
            .string(0)?
            .split(Self::LINE_BREAK)
            .map(|line| self.string_value(line.to_string()))
            .collect();
        Ok(Value::List(CodeList::new(lines, self.call_site)))
    }

    pub fn trim_start(&self) -> Result<Value, Diagnostic> {
        self.arguments.expect_count(1)?;
        let text = self.arguments.string(0)?;
        Ok(self.string_value(text.trim_start().to_string()))
    }

    pub fn length(&self) -> Result<Value, Diagnostic> {
        self.arguments.expect_count(1)?;
        let count = self.arguments.string(0)?.chars().count();
        Ok(Forms::count(count, self.call_site))
    }

    pub fn drop(&self) -> Result<Value, Diagnostic> {
        self.arguments.expect_count(2)?;
        let count = self.arguments.index(0)?;
        let text = self.arguments.string(1)?;
        let start = self.char_boundary(text, count)?;
        Ok(self.string_value(text[start..].to_string()))
    }

    fn char_boundary(&self, text: &str, count: usize) -> Result<usize, Diagnostic> {
        text.char_indices()
            .map(|(boundary, _)| boundary)
            .chain(std::iter::once(text.len()))
            .nth(count)
            .ok_or_else(|| {
                InterpretProblems::past_string_end(self.arguments.function(), self.arguments.span())
            })
    }

    fn string_value(&self, text: String) -> Value {
        Value::Form(Form::new(FormKind::String(text.into()), self.call_site))
    }
}