1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
//! ErgoScript compiler

use super::binder::BinderError;
use super::hir::HirLoweringError;
use crate::ast;
use crate::binder::Binder;
use crate::hir;
use crate::mir;
use crate::parser::parse_error::ParseError;
use crate::script_env::ScriptEnv;
use crate::type_infer::assign_type;
use crate::type_infer::TypeInferenceError;

extern crate derive_more;
use derive_more::From;
use ergotree_ir::ergo_tree::ErgoTree;
use ergotree_ir::type_check::TypeCheckError;
use mir::lower::MirLoweringError;

/// Compilation errors
#[derive(Debug, PartialEq, From)]
pub enum CompileError {
    /// Parser error
    ParseError(Vec<ParseError>),
    /// Error on AST to HIR lowering
    HirLoweringError(HirLoweringError),
    /// Error on binder pass
    BinderError(BinderError),
    /// Error on type inference pass
    TypeInferenceError(TypeInferenceError),
    /// Error on HIT to MIR lowering
    MirLoweringError(MirLoweringError),
    /// Error on type checking
    TypeCheckError(TypeCheckError),
}

impl CompileError {
    /// Pretty formatted error with CST/AST/IR, etc.
    pub fn pretty_desc(&self, source: &str) -> String {
        match self {
            CompileError::ParseError(errors) => {
                errors.iter().map(|e| e.pretty_desc(source)).collect()
            }
            CompileError::HirLoweringError(e) => e.pretty_desc(source),
            CompileError::BinderError(e) => e.pretty_desc(source),
            CompileError::TypeInferenceError(e) => e.pretty_desc(source),
            CompileError::MirLoweringError(e) => e.pretty_desc(source),
            CompileError::TypeCheckError(e) => e.pretty_desc(),
        }
    }
}

/// Compiles given source code to [`ergotree_ir::mir::expr::Expr`], or returns an error
pub fn compile_expr(
    source: &str,
    env: ScriptEnv,
) -> Result<ergotree_ir::mir::expr::Expr, CompileError> {
    let hir = compile_hir(source)?;
    let binder = Binder::new(env);
    let bind = binder.bind(hir)?;
    let typed = assign_type(bind)?;
    let mir = mir::lower::lower(typed)?;
    let res = ergotree_ir::type_check::type_check(mir)?;
    Ok(res)
}

/// Compiles given source code to [`ErgoTree`], or returns an error
pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
    let expr = compile_expr(source, env)?;
    Ok(expr.into())
}

pub(crate) fn compile_hir(source: &str) -> Result<hir::Expr, CompileError> {
    let parse = super::parser::parse(&source);
    if !parse.errors.is_empty() {
        return Err(CompileError::ParseError(parse.errors));
    }
    let syntax = parse.syntax();
    let root = ast::Root::cast(syntax).unwrap();
    let hir = hir::lower(root)?;
    Ok(hir)
}

#[cfg(test)]
fn check(input: &str, expected_tree: expect_test::Expect) {
    let res = compile_expr(input, ScriptEnv::new());

    let expected_out = res
        .map(|tree| tree.debug_tree())
        .unwrap_or_else(|e| e.pretty_desc(input));
    expected_tree.assert_eq(&expected_out);
}

#[cfg(test)]
mod tests {
    use super::*;
    use expect_test::expect;

    #[test]
    fn test_height() {
        check(
            "HEIGHT",
            expect![[r#"
                GlobalVars(
                    Height,
                )"#]],
        );
    }

    #[test]
    fn test_parser_error() {
        check(
            "HSB.HEIGHT",
            expect![[r#"
                error: expected ‘+’, ‘-’, ‘*’, ‘/’, ‘val’, number, number, identifier, ‘-’ or ‘(’, but found an unrecognized token
                line: 1
                HSB.HEIGHT
                  ^^"#]],
        );
    }
}