melorun 0.5.1

Melodeon interactive repl and command-line evaluator
Documentation
use derivative::Derivative;
use melodeon::typesys::Type;
use mil::compiler::{BinCode, Compile};
use std::path::{Path, PathBuf};
use themelio_stf::melvm::{self, CovenantEnv, Executor};
use themelio_structs::{Address, CoinData, CoinDataHeight, CoinID, Header, Transaction};
use thiserror::Error;

/// A high-level runner for Melodeon files.
pub struct Runner {
    /// Contents of a valid Melodeon program.
    src_contents: String,
    /// Path to that valid Melodeon program.
    src_path: PathBuf,
    /// Covenant environment.
    env: CovenantEnv,
    /// Transaction.
    txn: Transaction,
}

#[derive(Error, Derivative)]
#[derivative(Debug)]
pub enum LoadFileError {
    #[error("could not read file: {0:?}")]
    IoError(std::io::Error),
    #[error("cannot compile Melodeon covenant: {0:?}")]
    MeloError(melodeon::context::CtxErr),
    #[error("MelVM execution failed")]
    VmError(#[derivative(Debug = "ignore")] Executor),
}

#[derive(Error, Derivative)]
#[derivative(Debug)]
pub enum ReplError {
    #[error("cannot compile Melodeon covenant: {0:?}")]
    MeloError(melodeon::context::CtxErr),
    #[error("MelVM execution failed")]
    VmError(#[derivative(Debug = "ignore")] Executor),
}

impl Runner {
    /// Create a new Runner with nothing loaded.
    pub fn new(env: Option<CovenantEnv>, txn: Option<Transaction>) -> Self {
        Self {
            src_contents:
                "def z8990eb86ebbfda6ffb26010466539320a33a165388b1d0d028dff489841952d9() = 0".into(), // complete garbage
            src_path: PathBuf::from("."),
            env: env.unwrap_or(CovenantEnv {
                parent_coinid: CoinID::zero_zero(),
                parent_cdh: CoinDataHeight {
                    height: 0.into(),
                    coin_data: CoinData {
                        covhash: Address(Default::default()),
                        value: 0.into(),
                        denom: themelio_structs::Denom::Mel,
                        additional_data: vec![],
                    },
                },
                spender_index: 0,
                last_header: Header {
                    network: themelio_structs::NetID::Mainnet,
                    previous: Default::default(),
                    height: 0.into(),
                    history_hash: Default::default(),
                    coins_hash: Default::default(),
                    transactions_hash: Default::default(),
                    fee_pool: Default::default(),
                    fee_multiplier: Default::default(),
                    dosc_speed: Default::default(),
                    pools_hash: Default::default(),
                    stakes_hash: Default::default(),
                },
            }),
            txn: txn.unwrap_or(Transaction {
                kind: themelio_structs::TxKind::Normal,
                inputs: Default::default(),
                outputs: Default::default(),
                fee: Default::default(),
                covenants: Default::default(),
                data: Default::default(),
                sigs: Default::default(),
            }),
        }
    }

    /// Loads a file containing a Melodeon program, returning the  
    pub fn load_file(&mut self, path: &Path) -> Result<(melvm::Value, Type), LoadFileError> {
        let melo_str = std::fs::read_to_string(path).map_err(LoadFileError::IoError)?;

        self.load_str(path, &melo_str)
    }

    /// Load a string, read from a given path.
    pub fn load_str(
        &mut self,
        path: &Path,
        melo_str: &str,
    ) -> Result<(melvm::Value, Type), LoadFileError> {
        let (s, t) = melodeon::compile(melo_str, path).map_err(LoadFileError::MeloError)?;
        let parsed = mil::parser::parse_no_optimize(&s).expect("BUG: mil compilation failed");
        let melvm_ops = parsed.compile_onto(BinCode::default()).0;
        let mut executor =
            Executor::new_from_env(melvm_ops, self.txn.clone(), Some(self.env.clone()));
        if executor.run_discerning_to_end_preserve_stack().is_none() {
            return Err(LoadFileError::VmError(executor));
        }
        let val = executor.stack.pop().unwrap();
        self.src_contents = melo_str.to_owned();
        self.src_path = path.to_owned();
        Ok((val, t))
    }

    /// Runs a REPL line, returning the execution result.
    pub fn run_repl_line(&mut self, line: &str) -> Result<(melvm::Value, Type), ReplError> {
        let line = line.trim();
        let s = if self.src_contents.contains("---") {
            self.src_contents.split("---").next().unwrap()
        } else {
            &self.src_contents
        };
        let s = format!("{}\n---\n{}\n\n", s, line);
        // eprintln!("{}", s);
        let (s, t) = melodeon::compile(&s, &self.src_path).map_err(ReplError::MeloError)?;
        let parsed = mil::parser::parse_no_optimize(&s).expect("BUG: mil compilation failed");
        let melvm_ops = parsed.compile_onto(BinCode::default()).0;
        let mut executor =
            Executor::new_from_env(melvm_ops, self.txn.clone(), Some(self.env.clone()));
        if executor.run_discerning_to_end_preserve_stack().is_none() {
            return Err(ReplError::VmError(executor));
        }
        let val = executor.stack.pop().unwrap();
        Ok((val, t))
    }
}