ergoscript_compiler/
compiler.rs

1//! ErgoScript compiler
2
3use super::binder::BinderError;
4use super::hir::HirLoweringError;
5use crate::ast;
6use crate::binder::Binder;
7use crate::hir;
8use crate::mir;
9use crate::parser::parse_error::ParseError;
10use crate::script_env::ScriptEnv;
11use crate::type_infer::assign_type;
12use crate::type_infer::TypeInferenceError;
13use std::convert::TryInto;
14
15extern crate derive_more;
16use derive_more::From;
17use ergotree_ir::ergo_tree::ErgoTree;
18use ergotree_ir::ergo_tree::ErgoTreeError;
19use ergotree_ir::type_check::TypeCheckError;
20use mir::lower::MirLoweringError;
21
22/// Compilation errors
23#[derive(Debug, PartialEq, Eq, From)]
24pub enum CompileError {
25    /// Parser error
26    ParseError(Vec<ParseError>),
27    /// Error on AST to HIR lowering
28    HirLoweringError(HirLoweringError),
29    /// Error on binder pass
30    BinderError(BinderError),
31    /// Error on type inference pass
32    TypeInferenceError(TypeInferenceError),
33    /// Error on HIT to MIR lowering
34    MirLoweringError(MirLoweringError),
35    /// Error on type checking
36    TypeCheckError(TypeCheckError),
37    /// ErgoTree error
38    ErgoTreeError(ErgoTreeError),
39}
40
41impl CompileError {
42    /// Pretty formatted error with CST/AST/IR, etc.
43    pub fn pretty_desc(&self, source: &str) -> String {
44        match self {
45            CompileError::ParseError(errors) => {
46                errors.iter().map(|e| e.pretty_desc(source)).collect()
47            }
48            CompileError::HirLoweringError(e) => e.pretty_desc(source),
49            CompileError::BinderError(e) => e.pretty_desc(source),
50            CompileError::TypeInferenceError(e) => e.pretty_desc(source),
51            CompileError::MirLoweringError(e) => e.pretty_desc(source),
52            CompileError::TypeCheckError(e) => e.pretty_desc(),
53            CompileError::ErgoTreeError(e) => format!("{:?}", e),
54        }
55    }
56}
57
58/// Compiles given source code to [`ergotree_ir::mir::expr::Expr`], or returns an error
59pub fn compile_expr(
60    source: &str,
61    env: ScriptEnv,
62) -> Result<ergotree_ir::mir::expr::Expr, CompileError> {
63    let hir = compile_hir(source)?;
64    let binder = Binder::new(env);
65    let bind = binder.bind(hir)?;
66    let typed = assign_type(bind)?;
67    let mir = mir::lower::lower(typed)?;
68    let res = ergotree_ir::type_check::type_check(mir)?;
69    Ok(res)
70}
71
72/// Compiles given source code to [`ErgoTree`], or returns an error
73pub fn compile(source: &str, env: ScriptEnv) -> Result<ErgoTree, CompileError> {
74    let expr = compile_expr(source, env)?;
75    Ok(expr.try_into()?)
76}
77
78pub(crate) fn compile_hir(source: &str) -> Result<hir::Expr, CompileError> {
79    let parse = super::parser::parse(source);
80    if !parse.errors.is_empty() {
81        return Err(CompileError::ParseError(parse.errors));
82    }
83    let syntax = parse.syntax();
84    let root = ast::Root::cast(syntax).unwrap();
85    let hir = hir::lower(root)?;
86    Ok(hir)
87}
88
89#[cfg(test)]
90fn check(input: &str, expected_tree: expect_test::Expect) {
91    let res = compile_expr(input, ScriptEnv::new());
92
93    let expected_out = res
94        .map(|tree| tree.debug_tree())
95        .unwrap_or_else(|e| e.pretty_desc(input));
96    expected_tree.assert_eq(&expected_out);
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102    use expect_test::expect;
103
104    #[test]
105    fn test_height() {
106        check(
107            "HEIGHT",
108            expect![[r#"
109                GlobalVars(
110                    Height,
111                )"#]],
112        );
113    }
114
115    #[test]
116    fn test_parser_error() {
117        check(
118            "HSB.HEIGHT",
119            expect![[r#"
120                error: expected ‘+’, ‘-’, ‘*’, ‘/’, ‘val’, number, number, identifier, ‘-’ or ‘(’, but found an unrecognized token
121                line: 1
122                HSB.HEIGHT
123                  ^^"#]],
124        );
125    }
126}