knave-expand 0.13.2

The reading, scoping and expansion stages of knave
Documentation
mod failure;
mod leading_lines;
mod lists;
mod numbers;
mod program;
mod step_limit;
mod strings;
mod text;

pub use failure::FailureDescription;

use std::cmp::Ordering;

use super::arguments::Arguments;
use super::{CompileTimeHost, ListNesting, Value};
use crate::core::{Builtin, Declaration};
use crate::diagnostic::Diagnostic;
use crate::reader::{Form, FormKind, Number, NumberValue};
use crate::source::Span;
use leading_lines::DistinctLeadingLines;
use lists::ListOperations;
use numbers::NumberOperations;
use program::{ModuleOperations, ProgramOperations};
use step_limit::StepLimitSetting;
use strings::StringOperations;
use text::TextOperations;

pub struct BuiltinCall {
    builtin: Builtin,
    arguments: Arguments,
    call_site: Span,
}

impl BuiltinCall {
    const IMPLEMENTATIONS: [(Builtin, Implementation); 72] = [
        (Builtin::First, |operations, _| operations.lists.first()),
        (Builtin::Rest, |operations, _| operations.lists.rest()),
        (Builtin::List, |operations, _| operations.lists.list()),
        (Builtin::Prepend, |operations, _| operations.lists.prepend()),
        (Builtin::Concat, |operations, _| operations.lists.concat()),
        (Builtin::Len, |operations, _| operations.lists.len()),
        (Builtin::IsEmpty, |operations, _| {
            operations.lists.is_empty()
        }),
        (Builtin::Nth, |operations, _| operations.lists.nth()),
        (Builtin::Take, |operations, _| operations.lists.take()),
        (Builtin::Drop, |operations, _| operations.lists.drop()),
        (Builtin::IsSymbol, |operations, _| {
            operations
                .text
                .is_kind(|kind| matches!(kind, FormKind::Symbol(_)))
        }),
        (Builtin::IsKeyword, |operations, _| {
            operations.text.is_keyword()
        }),
        (Builtin::IsList, |operations, _| operations.text.is_list()),
        (Builtin::IsString, |operations, _| {
            operations
                .text
                .is_kind(|kind| matches!(kind, FormKind::String(_)))
        }),
        (Builtin::IsNumber, |operations, _| {
            operations
                .text
                .is_kind(|kind| matches!(kind, FormKind::Number(_)))
        }),
        (Builtin::SymbolName, |operations, _| {
            operations.text.symbol_name()
        }),
        (Builtin::KeywordName, |operations, _| {
            operations.text.keyword_name()
        }),
        (Builtin::KeywordField, |operations, _| {
            operations.text.keyword_field()
        }),
        (Builtin::FieldKeyword, |operations, _| {
            operations.text.field_keyword()
        }),
        (Builtin::KeywordTrimEnd, |operations, _| {
            operations.text.keyword_trim_end()
        }),
        (Builtin::MakeSymbol, |operations, _| {
            operations.text.make_symbol()
        }),
        (Builtin::MakeString, |operations, _| {
            operations.text.make_string()
        }),
        (Builtin::SameBinding, |operations, host| {
            operations.text.same_binding(host)
        }),
        (Builtin::IsPatternHead, |operations, host| {
            operations.text.is_pattern_head(host)
        }),
        (Builtin::DefinitionSymbol, |operations, host| {
            operations.text.definition_symbol(host)
        }),
        (Builtin::TypeDefinitionSymbol, |operations, host| {
            operations.text.type_definition_symbol(host)
        }),
        (Builtin::DefinitionCall, |operations, host| {
            operations.text.definition_call(host)
        }),
        (Builtin::TypeDefinitionCall, |operations, host| {
            operations.text.type_definition_call(host)
        }),
        (Builtin::ExpandCall, |operations, host| {
            operations.text.expand_call(host)
        }),
        (Builtin::ClosestName, |operations, _| {
            operations.text.closest_name()
        }),
        (Builtin::SortedNames, |operations, _| {
            operations.text.sorted_names()
        }),
        (Builtin::DistinctLeadingLines, |operations, _| {
            operations.leading_lines.kept_texts()
        }),
        (Builtin::Fail, |operations, _| operations.text.fail()),
        (Builtin::NumberAdd, |operations, _| operations.numbers.add()),
        (Builtin::NumberSub, |operations, _| {
            operations.numbers.subtract()
        }),
        (Builtin::NumberLt, |operations, _| {
            operations.numbers.compare(Ordering::is_lt)
        }),
        (Builtin::NumberLe, |operations, _| {
            operations.numbers.compare(Ordering::is_le)
        }),
        (Builtin::NumberGt, |operations, _| {
            operations.numbers.compare(Ordering::is_gt)
        }),
        (Builtin::NumberGe, |operations, _| {
            operations.numbers.compare(Ordering::is_ge)
        }),
        (Builtin::ValueEq, |operations, _| operations.text.value_eq()),
        (Builtin::BoolNot, |operations, _| operations.text.not()),
        (Builtin::EscapeString, |operations, _| {
            operations.text.escape_string()
        }),
        (Builtin::EscapeChar, |operations, _| {
            operations.text.escape_char()
        }),
        (Builtin::EscapeFormat, |operations, _| {
            operations.text.escape_format()
        }),
        (Builtin::StringStripSuffix, |operations, _| {
            operations.text.string_strip_suffix()
        }),
        (Builtin::StringStripPrefix, |operations, _| {
            operations.text.string_strip_prefix()
        }),
        (Builtin::StringLines, |operations, _| {
            operations.strings.lines()
        }),
        (Builtin::StringTrimStart, |operations, _| {
            operations.strings.trim_start()
        }),
        (Builtin::StringTrimEnd, |operations, _| {
            operations.strings.trim_end()
        }),
        (Builtin::StringLength, |operations, _| {
            operations.strings.length()
        }),
        (Builtin::StringDrop, |operations, _| {
            operations.strings.drop()
        }),
        (Builtin::StringLowercase, |operations, _| {
            operations.strings.lowercase()
        }),
        (Builtin::StringContains, |operations, _| {
            operations.strings.contains()
        }),
        (Builtin::AddModuleNote, |operations, host| {
            operations.text.add_module_note(host)
        }),
        (Builtin::ModuleNotes, |operations, host| {
            operations.text.module_notes(host)
        }),
        (Builtin::DeclareDefinition, |operations, host| {
            operations.text.declare_definition(host, Declaration::Value)
        }),
        (Builtin::DeclareTypeDefinition, |operations, host| {
            operations.text.declare_definition(host, Declaration::Type)
        }),
        (Builtin::DeclareMacroDefinition, |operations, host| {
            operations.text.declare_definition(host, Declaration::Macro)
        }),
        (Builtin::FormLocation, |operations, host| {
            operations.program.form_location(host)
        }),
        (Builtin::InTasksDirectory, |operations, host| {
            operations.program.in_tasks_directory(host)
        }),
        (Builtin::PinnedAt, |operations, _| {
            operations.program.pinned_at()
        }),
        (Builtin::ProgramModules, |operations, host| {
            operations.modules.program_modules(host)
        }),
        (Builtin::ModuleExports, |operations, host| {
            operations.modules.module_exports(host)
        }),
        (Builtin::ModuleDefinitions, |operations, host| {
            operations.modules.module_definitions(host)
        }),
        (Builtin::CurrentModule, |operations, host| {
            operations.modules.current_module(host)
        }),
        (Builtin::ModuleSymbol, |operations, host| {
            operations
                .modules
                .named(|path, span| host.module_symbol(path, span))
        }),
        (Builtin::RequireModule, |operations, host| {
            operations
                .modules
                .named(|path, span| host.require_module(path, span))
        }),
        (Builtin::SetMacroStepLimit, |operations, host| {
            operations
                .step_limit
                .apply(|limit| host.set_macro_step_limit(limit))
        }),
        (Builtin::ProjectFileName, |operations, _| {
            operations.program.project_file_name()
        }),
        (Builtin::ReadForm, |operations, host| {
            operations.program.read_form(host)
        }),
        (Builtin::Located, |operations, _| {
            operations.program.located()
        }),
        (Builtin::ProjectModules, |operations, host| {
            operations.modules.project_modules(host)
        }),
    ];

    pub fn new(builtin: Builtin, values: Vec<Value>, span: Span, call_site: Span) -> Self {
        Self {
            builtin,
            arguments: Arguments::new(builtin.name(), values, span),
            call_site,
        }
    }

    pub fn run(self, host: &mut dyn CompileTimeHost) -> Result<Value, Diagnostic> {
        let operations = Operations::new(&self.arguments, self.call_site);
        Self::implementation(self.builtin)(&operations, host)
    }

    fn implementation(builtin: Builtin) -> Implementation {
        let (_, implementation) = Self::IMPLEMENTATIONS[builtin.position()];
        implementation
    }
}

type Implementation =
    for<'a> fn(&Operations<'a>, &mut dyn CompileTimeHost) -> Result<Value, Diagnostic>;

struct Operations<'a> {
    lists: ListOperations<'a>,
    modules: ModuleOperations<'a>,
    numbers: NumberOperations<'a>,
    program: ProgramOperations<'a>,
    step_limit: StepLimitSetting<'a>,
    strings: StringOperations<'a>,
    leading_lines: DistinctLeadingLines<'a>,
    text: TextOperations<'a>,
}

impl<'a> Operations<'a> {
    fn new(arguments: &'a Arguments, call_site: Span) -> Self {
        Self {
            lists: ListOperations::new(arguments, call_site),
            modules: ModuleOperations::new(arguments, call_site),
            numbers: NumberOperations::new(arguments, call_site),
            program: ProgramOperations::new(arguments, call_site),
            step_limit: StepLimitSetting::new(arguments),
            strings: StringOperations::new(arguments, call_site),
            leading_lines: DistinctLeadingLines::new(arguments, call_site),
            text: TextOperations::new(arguments, call_site),
        }
    }
}

struct Forms;

impl Forms {
    fn list(items: Vec<Form>, span: Span, called_at: Span) -> Result<Value, Diagnostic> {
        ListNesting::form_list(items, span).map_err(|deep| deep.at(called_at))
    }

    fn count(count: usize, span: Span) -> Value {
        let number = Number::new(count.to_string(), NumberValue::Integer(count as i128));
        Value::Form(Form::new(FormKind::Number(number), span))
    }
}

#[cfg(test)]
mod tests {
    use super::BuiltinCall;
    use crate::core::Builtin;

    #[test]
    fn each_builtin_finds_its_own_implementation_row() {
        for builtin in Builtin::all() {
            let (implemented, _) = BuiltinCall::IMPLEMENTATIONS[builtin.position()];
            assert_eq!(implemented, builtin);
        }
    }
}