clover 0.1.3

A scripting language.
Documentation
mod frontend;
mod intermediate;
mod backend;
mod runtime;
mod version;

pub use runtime::program::Program;
pub use runtime::state::State;
pub use runtime::object::Object;
pub use runtime::object::NativeModel;
pub use runtime::object::NativeModelInstance;
pub use runtime::object::Reference;

use backend::compiler::DefaultStorage;
use backend::compiler::compile_file;
use std::ops::{Deref, DerefMut};

pub mod helper {
    pub use crate::runtime::object::make_reference;
    pub use crate::backend::compiler::Storage;
}

pub mod debug {
    pub use crate::intermediate::CompileErrorList;
    pub use crate::runtime::program::RuntimeError;
    pub use crate::intermediate::Position;
}

pub struct Clover {
    storage: Box<dyn helper::Storage>
}

impl Clover {
    pub fn new_with_file_loader(storage: Box<dyn helper::Storage>) -> Clover {
        Clover {
            storage
        }
    }

    pub fn new() -> Clover {
        Clover {
            storage: Box::new(DefaultStorage::new())
        }
    }

    pub fn compile_file(&self, filename: &str) -> Result<Program, debug::CompileErrorList> {
        compile_file(filename, self.storage.deref())
    }

    pub fn save_program(&self, filename: &str, program: &Program) -> Result<(), debug::CompileErrorList> {

        let mut writer = self.storage.get_writer(filename)?;

        program.serialize(writer.deref_mut()).unwrap();

        Ok(())
    }

    pub fn load_program(&self, filename: &str) -> Result<Program, debug::CompileErrorList> {
        let mut reader = self.storage.get_reader(filename)?;

        Ok(Program::deserialize(&mut reader).unwrap())
    }

    pub fn create_state_by_filename(&self, filename: &str) -> Result<State, debug::CompileErrorList> {
        let program = self.compile_file(filename)?;

        Ok(program.into())
    }

    pub fn run(&self, program: Program) -> Result<Object, debug::RuntimeError> {
        let mut state: State = program.into();

        state.execute()
    }

}


#[cfg(test)]
mod tests {
    use crate::{Clover, State, Object};

    fn execute_function(state: &mut State, function_name: &str) {
        let mut function_index = None;

        for (i, name) in state.get_program().file_info.as_ref().unwrap().function_names.iter().enumerate() {
            if name != function_name {
                continue;
            };

            function_index = Some(i);
            break;
        };

        assert!(function_index.is_some(), "can not found function [{}] in [{}]", function_name, &state.get_program().file_info.as_ref().unwrap().filenames[0]);

        let result = state.execute_by_function_index(function_index.unwrap(), &[]);

        assert!(result.is_ok(), "error occur when executing function [{}] in [{}]", function_name, &state.get_program().file_info.as_ref().unwrap().filenames[0]);

        let object = result.unwrap();

        if let Object::Boolean(value) = object{
            assert!(value, "result is not bool when executing function [{}] in [{}]", function_name, &state.get_program().file_info.as_ref().unwrap().filenames[0]);
        } else {
            panic!("result is not bool when executing function [{}] in [{}]", function_name, &state.get_program().file_info.as_ref().unwrap().filenames[0]);
        };
    }

    fn execute(filename: &str, function_names: &[ &str ]) {
        let clover = Clover::new();

        let result = clover.create_state_by_filename(filename);

        assert!(result.is_ok(), "create state with with file [{}]", filename);

        let mut state = result.unwrap();

        for function_name in function_names {
            execute_function(&mut state, *function_name)
        };
    }

    #[test]
    fn integer_operations() {
        execute("tests/integer_operations.luck", &[ "add", "sub", "multiply", "divide" ]);
    }

    #[test]
    fn for_loop() {
        execute("tests/for_loop.luck", &[ "simple", "nests", "break_loop", "array", "for_model" ]);
    }

    #[test]
    fn error_handling() {
        execute("tests/error_handling.luck", &[ "in_same_function", "in_child_function" ]);
    }

    #[test]
    fn function() {
        execute("tests/function.luck", &[ "recursive", "with_return", "first_class_function", "instance_first_class_function" ]);
    }

    #[test]
    fn include() {
        execute("tests/include.luck", &[ "include_function", "include_with_nickname", "include_model" ]);
    }

    #[test]
    fn model() {
        execute("tests/model.luck", &[ "regular", "with_apply" ]);
    }

    #[test]
    fn local() {
        execute("tests/local.luck", &[ "in_file", "in_file_again", "in_function", "in_scope" ]);
    }
}