roost-lang 2.0.4

Interpreter library for the roost language
Documentation
#[macro_use]
mod built_in;
mod runtime_result;
pub mod value;

use rust_decimal::{prelude::ToPrimitive, Decimal};

#[cfg(feature = "no_std_io")]
use crate::io::Write;
use crate::{
    error::{Result, Span},
    nodes::*,
    tokens::TokenKind,
};
#[cfg(not(feature = "no_std_io"))]
use std::io::Write;
use std::{collections::HashMap, mem, rc::Rc};

use self::{
    runtime_result::RuntimeResult,
    value::{
        members::BuiltInMethods,
        types::{self, Type},
        BuiltIn, ToValue, Value, WrappedValue,
    },
};

macro_rules! try_visit {
    ($call:expr) => {{
        let res: RuntimeResult = $call;
        if res.should_return() {
            return Ok(res);
        }
        res.take_value()
    }};
}

macro_rules! simple_expr {
    ($name:ident: $type:ident, $next:ident; $($tt:tt)+) => {
        fn $name(&mut self, node: &'tree $type) -> Result<RuntimeResult<'tree>> {
            let mut base = try_visit!(self.$next(&node.base)?);
            simple_expr!(@kind self, node, base, $next, $($tt)+);
            Ok(RuntimeResult::new(Some(base)))
        }
    };
    (@kind $self:ident, $node:ident, $base:ident, $next:ident, $method:ident) => {
        for other in &$node.following {
            let other = try_visit!($self.$next(other)?);
            let out = $base
                .borrow()
                .$method(&other.borrow(), &$node.span)?
                .wrapped();
            $base = out;
        }
    };
    (@kind $self:ident, $node:ident, $base:ident, $next:ident, $($tok:ident => $method:ident),+ $(,)?) => {
        for (tok, other) in &$node.following {
            let other = try_visit!($self.$next(other)?);
            let out = match tok {
                $(TokenKind::$tok => $base.borrow().$method(&other.borrow(), &$node.span),)+
                _ => unreachable!(),
            }?
            .wrapped();
            $base = out;
        }
    };
}

macro_rules! built_in {
    ($name:literal, $($built_in:tt)*) => {
        ($name, Value::BuiltIn(BuiltIn::$($built_in)*).wrapped())
    };
}

pub struct Interpreter<'tree, StdOut, StdErr, Exit>
where
    StdOut: Write,
    StdErr: Write,
    Exit: FnOnce(i32),
{
    program: &'tree Program,
    pub scopes: Vec<HashMap<&'tree str, WrappedValue<'tree>>>,
    pub scope_idx: usize,
    stdout: StdOut,
    stderr: StdErr,
    exit_callback: Option<Exit>,
    built_in_methods: BuiltInMethods<'tree>,
}

impl<'tree, StdOut, StdErr, Exit> Interpreter<'tree, StdOut, StdErr, Exit>
where
    StdOut: Write,
    StdErr: Write,
    Exit: FnOnce(i32),
{
    pub fn new(
        program: &'tree Program,
        stdout: StdOut,
        stderr: StdErr,
        exit_callback: Exit,
    ) -> Self {
        Self {
            program,
            scopes: vec![HashMap::from([
                built_in!(
                    "print",
                    Print {
                        newline: false,
                        stderr: false
                    },
                ),
                built_in!(
                    "printl",
                    Print {
                        newline: true,
                        stderr: false
                    },
                ),
                built_in!(
                    "eprint",
                    Print {
                        newline: false,
                        stderr: true
                    },
                ),
                built_in!(
                    "eprintl",
                    Print {
                        newline: true,
                        stderr: true
                    },
                ),
                built_in!("typeOf", Function(built_in::type_of)),
                built_in!("assert", Function(built_in::assert)),
                built_in!("throw", Function(built_in::throw)),
                built_in!("exit", Exit),
                built_in!("debug", Debug),
                ("answer", Value::Number(42.into()).wrapped()),
            ])],
            scope_idx: 0,
            stdout,
            stderr,
            exit_callback: Some(exit_callback),
            built_in_methods: BuiltInMethods::new(),
        }
    }

    #[inline]
    pub fn run(&mut self, new_scope: bool) -> Result<RuntimeResult<'tree>> {
        self.visit_program(self.program, new_scope)
    }

    fn push_scope(&mut self) {
        self.scopes.push(HashMap::new());
        self.scope_idx += 1;
    }

    fn pop_scope(&mut self) {
        self.scopes.pop();
        self.scope_idx -= 1;
    }

    fn add_var(&mut self, name: &'tree str, value: WrappedValue<'tree>) {
        self.scopes.last_mut().unwrap().insert(name, value);
    }

    fn get_var(&self, name: &str, span: &Span) -> Result<(&WrappedValue<'tree>, usize)> {
        for (idx, scope) in self.scopes.iter().enumerate().rev() {
            if let Some(var) = scope.get(name) {
                return Ok((var, idx));
            }
        }
        error!(
            ReferenceError,
            *span, "Variable with name '{}' not found", name,
        );
    }

    ////////////////////////////////////////

    #[inline]
    fn visit_program(
        &mut self,
        node: &'tree Program,
        new_scope: bool,
    ) -> Result<RuntimeResult<'tree>> {
        self.visit_statements(node, new_scope)
    }

    fn visit_statements(
        &mut self,
        node: &'tree Statements,
        new_scope: bool,
    ) -> Result<RuntimeResult<'tree>> {
        if new_scope {
            self.push_scope();
        }
        let mut result = RuntimeResult::new(None);
        for stmt in node {
            result = self.visit_statement(stmt)?;
            if result.should_return() {
                break;
            }
        }
        if new_scope {
            self.pop_scope();
        }
        Ok(if result.value.is_none() && !result.should_return() {
            RuntimeResult::new(Some(Value::Null.wrapped()))
        } else {
            result
        })
    }

    #[inline]
    fn visit_block(&mut self, node: &'tree Block, new_scope: bool) -> Result<RuntimeResult<'tree>> {
        self.visit_statements(node, new_scope)
    }

    fn visit_statement(&mut self, node: &'tree Statement) -> Result<RuntimeResult<'tree>> {
        match node {
            Statement::Var(node) => self.visit_var_stmt(node),
            Statement::Function(node) => self.visit_function_decl(node),
            Statement::Class(node) => self.visit_class_decl(node),
            Statement::Break(node) => self.visit_break_stmt(node),
            Statement::Continue(node) => self.visit_continue_stmt(node),
            Statement::Return(node) => self.visit_return_stmt(node),
            Statement::Expr(node) => self.visit_expression(node),
        }
    }

    fn visit_var_stmt(&mut self, node: &'tree VarStmt) -> Result<RuntimeResult<'tree>> {
        let val = match &node.expr {
            Some(node) => try_visit!(self.visit_expression(node)?),
            None => Value::Null.wrapped(),
        };
        self.add_var(&node.ident, val);
        Ok(RuntimeResult::new(None))
    }

    fn visit_function_decl(&mut self, node: &'tree FunctionDecl) -> Result<RuntimeResult<'tree>> {
        self.add_var(
            &node.ident,
            Value::Function {
                args: &node.args,
                block: &node.block,
            }
            .wrapped(),
        );
        Ok(RuntimeResult::new(None))
    }

    fn visit_class_decl(&mut self, node: &'tree ClassDecl) -> Result<RuntimeResult<'tree>> {
        let class = Value::Null.wrapped();
        let mut statics: HashMap<&str, _> = HashMap::new();
        let mut non_statics = vec![];
        for member in &node.block.members {
            match (member.is_static, &member.kind) {
                (true, MemberKind::Attribute(node)) => {
                    statics.insert(
                        &node.ident,
                        match &node.expr {
                            Some(node) => try_visit!(self.visit_expression(node)?),
                            None => Value::Null.wrapped(),
                        },
                    );
                }
                (true, MemberKind::Method(node)) => {
                    statics.insert(
                        &node.ident,
                        Value::Function {
                            args: &node.args,
                            block: &node.block,
                        }
                        .wrapped(),
                    );
                }
                (false, member) => {
                    non_statics.push(member);
                }
            }
        }
        *class.borrow_mut() = Value::Class {
            statics,
            non_statics,
        };
        self.add_var(&node.ident, class);
        Ok(RuntimeResult::new(None))
    }

    fn visit_break_stmt(&mut self, node: &'tree BreakStmt) -> Result<RuntimeResult<'tree>> {
        let val = match &node.expr {
            Some(node) => try_visit!(self.visit_expression(node)?),
            None => Value::Null.wrapped(),
        };
        Ok(RuntimeResult::success_break(val))
    }

    fn visit_continue_stmt(&mut self, _: &'tree ContinueStmt) -> Result<RuntimeResult<'tree>> {
        Ok(RuntimeResult::success_continue())
    }

    fn visit_return_stmt(&mut self, node: &'tree ReturnStmt) -> Result<RuntimeResult<'tree>> {
        let val = match &node.expr {
            Some(node) => try_visit!(self.visit_expression(node)?),
            None => Value::Null.wrapped(),
        };
        Ok(RuntimeResult::success_return(val))
    }

    #[inline]
    fn visit_expression(&mut self, node: &'tree Expression) -> Result<RuntimeResult<'tree>> {
        self.visit_range_expr(node)
    }

    fn visit_range_expr(&mut self, node: &'tree RangeExpr) -> Result<RuntimeResult<'tree>> {
        match node {
            RangeExpr::None(node) => self.visit_or_expr(node),
            RangeExpr::Closed(left, tok, right, span) => {
                let left = try_visit!(self.visit_or_expr(left)?);
                let right = try_visit!(self.visit_or_expr(right)?);
                let inclusive = tok == &TokenKind::DotsInclusive;
                let range = match (&*left.borrow(), &*right.borrow()) {
                    (Value::Number(start), Value::Number(end)) => {
                        if !start.fract().is_zero() || !end.fract().is_zero() {
                            error!(ValueError, *span, "Range bounds have to be integers");
                        }
                        let start = start.to_i128().unwrap();
                        let end = end.to_i128().unwrap();
                        let end = end - !inclusive as i128;
                        Value::Range {
                            start: Some(start),
                            end: Some(end),
                        }
                    }
                    _ => error!(TypeError, *span, "Range bounds have to be of type 'number'"),
                };
                Ok(RuntimeResult::new(Some(range.wrapped())))
            }
            RangeExpr::OpenEnd(left, span) => {
                let left = try_visit!(self.visit_or_expr(left)?);
                let range = match &*left.borrow() {
                    Value::Number(start) => {
                        if !start.fract().is_zero() {
                            error!(ValueError, *span, "Range bounds have to be integers");
                        }
                        let start = start.to_i128().unwrap();
                        Value::Range {
                            start: Some(start),
                            end: None,
                        }
                    }
                    _ => error!(TypeError, *span, "Range bounds have to be of type 'number'"),
                };
                Ok(RuntimeResult::new(Some(range.wrapped())))
            }
            RangeExpr::OpenStart(tok, right, span) => {
                let right = try_visit!(self.visit_or_expr(right)?);
                let inclusive = tok == &TokenKind::DotsInclusive;
                let range = match &*right.borrow() {
                    Value::Number(end) => {
                        if !end.fract().is_zero() {
                            error!(ValueError, *span, "Range bounds have to be integers");
                        }
                        let end = end.to_i128().unwrap();
                        let end = end - !inclusive as i128;
                        Value::Range {
                            start: None,
                            end: Some(end),
                        }
                    }
                    _ => error!(TypeError, *span, "Range bounds have to be of type 'number'"),
                };
                Ok(RuntimeResult::new(Some(range.wrapped())))
            }
            RangeExpr::Open => Ok(RuntimeResult::new(Some(
                Value::Range {
                    start: None,
                    end: None,
                }
                .wrapped(),
            ))),
        }
    }

    fn visit_or_expr(&mut self, node: &'tree OrExpr) -> Result<RuntimeResult<'tree>> {
        let base = try_visit!(self.visit_and_expr(&node.base)?);
        if !node.following.is_empty() {
            if base.borrow().is_false() {
                return Ok(RuntimeResult::new(Some(Value::Bool(false).wrapped())));
            }
            for other in &node.following {
                if try_visit!(self.visit_and_expr(other)?).borrow().is_false() {
                    return Ok(RuntimeResult::new(Some(Value::Bool(false).wrapped())));
                }
            }
            return Ok(RuntimeResult::new(Some(Value::Bool(true).wrapped())));
        }
        Ok(RuntimeResult::new(Some(base)))
    }

    fn visit_and_expr(&mut self, node: &'tree AndExpr) -> Result<RuntimeResult<'tree>> {
        let base = try_visit!(self.visit_bit_or_expr(&node.base)?);
        if !node.following.is_empty() {
            if base.borrow().is_true() {
                return Ok(RuntimeResult::new(Some(Value::Bool(true).wrapped())));
            }
            for other in &node.following {
                if try_visit!(self.visit_bit_or_expr(other)?)
                    .borrow()
                    .is_true()
                {
                    return Ok(RuntimeResult::new(Some(Value::Bool(true).wrapped())));
                }
            }
            return Ok(RuntimeResult::new(Some(Value::Bool(false).wrapped())));
        }
        Ok(RuntimeResult::new(Some(base)))
    }

    simple_expr!(visit_bit_or_expr: BitOrExpr, visit_bit_xor_expr; or);
    simple_expr!(visit_bit_xor_expr: BitXorExpr, visit_bit_and_expr; xor);
    simple_expr!(visit_bit_and_expr: BitAndExpr, visit_eq_expr; and);

    fn visit_eq_expr(&mut self, node: &'tree EqExpr) -> Result<RuntimeResult<'tree>> {
        let left = try_visit!(self.visit_rel_expr(&node.left)?);
        let out = if let Some((tok, right)) = &node.right {
            let right = try_visit!(self.visit_rel_expr(right)?);
            Value::Bool(match tok {
                TokenKind::Equal => left == right,
                TokenKind::NotEqual => left != right,
                _ => unreachable!(),
            })
            .wrapped()
        } else {
            left
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_rel_expr(&mut self, node: &'tree RelExpr) -> Result<RuntimeResult<'tree>> {
        let left = try_visit!(self.visit_shift_expr(&node.left)?);
        let out = if let Some((tok, right)) = &node.right {
            let right = try_visit!(self.visit_shift_expr(right)?);
            match tok {
                TokenKind::LessThan => left.borrow().lt(&right.borrow(), &node.span),
                TokenKind::LessThanOrEqual => left.borrow().le(&right.borrow(), &node.span),
                TokenKind::GreaterThan => left.borrow().gt(&right.borrow(), &node.span),
                TokenKind::GreaterThanOrEqual => left.borrow().ge(&right.borrow(), &node.span),
                _ => unreachable!(),
            }?
            .wrapped()
        } else {
            left
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    simple_expr!(
        visit_shift_expr: ShiftExpr, visit_add_expr;
        ShiftLeft => shl,
        ShiftRight => shr,
    );

    simple_expr!(
        visit_add_expr: AddExpr, visit_mul_expr;
        Plus => add,
        Minus => sub,
    );

    simple_expr!(
        visit_mul_expr: MulExpr, visit_unary_expr;
        Star => mul,
        Slash => div,
        Rem => rem,
        Backslash => div_floor,
    );

    fn visit_unary_expr(&mut self, node: &'tree UnaryExpr) -> Result<RuntimeResult<'tree>> {
        match node {
            UnaryExpr::Unary {
                span,
                operator,
                expr,
            } => {
                let base = try_visit!(self.visit_unary_expr(expr)?);
                let out = match operator {
                    TokenKind::Plus => Value::Number(Decimal::ZERO).add(&base.borrow(), span)?,
                    TokenKind::Minus => Value::Number(Decimal::ZERO).sub(&base.borrow(), span)?,
                    TokenKind::Not => Value::Bool(base.borrow().is_false()),
                    _ => unreachable!(),
                }
                .wrapped();
                Ok(RuntimeResult::new(Some(out)))
            }
            UnaryExpr::Done(node) => self.visit_exp_expr(node),
        }
    }

    fn visit_exp_expr(&mut self, node: &'tree ExpExpr) -> Result<RuntimeResult<'tree>> {
        let mut base = try_visit!(self.visit_assign_expr(&node.base)?);
        if let Some(exponent) = &node.exponent {
            let exponent = try_visit!(self.visit_unary_expr(exponent)?);
            let out = base.borrow().pow(&exponent.borrow(), &node.span)?.wrapped();
            base = out;
        }
        Ok(RuntimeResult::new(Some(base)))
    }

    fn visit_assign_expr(&mut self, node: &'tree AssignExpr) -> Result<RuntimeResult<'tree>> {
        let left = try_visit!(self.visit_call_expr(&node.left)?);
        if let Some((tok, right)) = &node.right {
            let left_type = types::type_of(&left.borrow());
            if let Type::Class | Type::Object | Type::Range = left_type {
                error!(TypeError, node.span, "Cannot reassign type '{}'", left_type,);
            }
            let right = try_visit!(self.visit_expression(right)?);
            let new_value = match tok {
                TokenKind::Assign => right.borrow().clone(),
                TokenKind::StarAssign => left.borrow().mul(&right.borrow(), &node.span)?,
                TokenKind::SlashAssign => left.borrow().div(&right.borrow(), &node.span)?,
                TokenKind::BackslashAssign => {
                    left.borrow().div_floor(&right.borrow(), &node.span)?
                }
                TokenKind::RemAssign => left.borrow().rem(&right.borrow(), &node.span)?,
                TokenKind::PlusAssign => left.borrow().add(&right.borrow(), &node.span)?,
                TokenKind::MinusAssign => left.borrow().sub(&right.borrow(), &node.span)?,
                TokenKind::ShiftLeftAssign => left.borrow().shl(&right.borrow(), &node.span)?,
                TokenKind::ShiftRightAssign => left.borrow().shr(&right.borrow(), &node.span)?,
                TokenKind::BitAndAssign => left.borrow().and(&right.borrow(), &node.span)?,
                TokenKind::BitXorAssign => left.borrow().xor(&right.borrow(), &node.span)?,
                TokenKind::BitOrAssign => left.borrow().or(&right.borrow(), &node.span)?,
                _ => unreachable!(),
            };
            let new_type = types::type_of(&new_value);
            if left_type != new_type && left_type != Type::Null && new_type != Type::Null {
                error!(
                    TypeError,
                    node.span, "Cannot change type by reassigning, create a new variable instead",
                );
            }
            *left.borrow_mut() = new_value;
        }
        Ok(RuntimeResult::new(Some(left)))
    }

    fn visit_call_expr(&mut self, node: &'tree CallExpr) -> Result<RuntimeResult<'tree>> {
        let (mut parent, result) = self.visit_member_expr(&node.base)?;
        if result.should_return() {
            return Ok(result);
        }
        let mut base = result.take_value();
        for part in &node.following {
            let out = match part {
                CallPart::Args(args) => self.call_value(&base, args, &parent, &node.span)?,
                CallPart::Member(MemberPart::Field(ident)) => {
                    Value::get_field(&base, ident, &self.built_in_methods, &node.span)?
                }
                CallPart::Member(MemberPart::Index(expr)) => {
                    Value::index(&base, &try_visit!(self.visit_expression(expr)?), &node.span)?
                }
            };
            mem::swap(&mut base, &mut parent);
            base = out;
        }
        Ok(RuntimeResult::new(Some(base)))
    }

    pub fn call_value(
        &mut self,
        value: &WrappedValue<'tree>,
        call_args: &'tree Args,
        parent: &WrappedValue<'tree>,
        span: &Span,
    ) -> Result<WrappedValue<'tree>> {
        match &*value.borrow() {
            Value::Function { args, block } => {
                if args.len() != call_args.len() {
                    error!(
                        TypeError,
                        *span,
                        "Function takes {} arguments, however {} were supllied",
                        args.len(),
                        call_args.len(),
                    );
                }
                self.push_scope();
                if *parent.borrow() != Value::Null {
                    self.add_var("this", Rc::clone(parent));
                }
                for (idx, arg) in args.iter().enumerate() {
                    let val = self.visit_expression(&call_args[idx])?.take_value();
                    self.add_var(arg, val);
                }
                let res = self.visit_block(block, false)?;
                self.pop_scope();
                Ok(if let Some(val) = res.return_value {
                    val
                } else {
                    res.take_value()
                })
            }
            Value::BuiltIn(func) => {
                let mut args = vec![];
                for arg in call_args {
                    args.push(self.visit_expression(arg)?.take_value());
                }

                let out = match func {
                    BuiltIn::Function(func) => func(args, span)?,
                    BuiltIn::Method(func) => func(parent, args, span)?,
                    BuiltIn::Print {
                        newline,
                        stderr: false,
                    } => built_in::print(args, &mut self.stdout, span, *newline)?,
                    BuiltIn::Print {
                        newline,
                        stderr: true,
                    } => built_in::print(args, &mut self.stderr, span, *newline)?,
                    BuiltIn::Exit => {
                        built_in::exit(args, self.exit_callback.take().unwrap(), span)?
                    }
                    BuiltIn::Debug => built_in::debug(args, &mut self.stderr, span)?,
                };
                Ok(out)
            }
            Value::Class { non_statics, .. } => {
                if !call_args.is_empty() {
                    error!(
                        TypeError,
                        *span,
                        "Class constructors take no arguments, however {} were supplied",
                        call_args.len(),
                    );
                }
                let object = Value::Null.wrapped();
                let mut fields: HashMap<&str, _> = HashMap::new();
                for member in non_statics {
                    match member {
                        MemberKind::Attribute(node) => {
                            fields.insert(
                                &node.ident,
                                match &node.expr {
                                    Some(node) => self.visit_expression(node)?.take_value(),
                                    None => Value::Null.wrapped(),
                                },
                            );
                        }
                        MemberKind::Method(node) => {
                            fields.insert(
                                &node.ident,
                                Value::Function {
                                    args: &node.args,
                                    block: &node.block,
                                }
                                .wrapped(),
                            );
                        }
                    }
                }
                *object.borrow_mut() = Value::Object(fields);
                Ok(object)
            }
            _ => error!(
                TypeError,
                *span,
                "Type '{}' is not callable",
                types::type_of(&value.borrow()),
            ),
        }
    }

    fn visit_member_expr(
        &mut self,
        node: &'tree MemberExpr,
    ) -> Result<(WrappedValue<'tree>, RuntimeResult<'tree>)> {
        let res = self.visit_atom(&node.base)?;
        if res.should_return() {
            return Ok((Value::Null.wrapped(), res));
        }
        let mut base = res.take_value();
        let mut parent = Value::Null.wrapped();
        for part in &node.following {
            let out = match part {
                MemberPart::Field(ident) => {
                    Value::get_field(&base, ident, &self.built_in_methods, &node.span)?
                }
                MemberPart::Index(expr) => {
                    let res = self.visit_expression(expr)?;
                    if res.should_return() {
                        return Ok((Value::Null.wrapped(), res));
                    }
                    Value::index(&base, &res.take_value(), &node.span)?
                }
            };
            mem::swap(&mut parent, &mut base);
            base = out;
        }
        Ok((parent, RuntimeResult::new(Some(base))))
    }

    fn visit_atom(&mut self, node: &'tree Atom) -> Result<RuntimeResult<'tree>> {
        let out = match node {
            Atom::Number(val) => Value::Number(*val).wrapped(),
            Atom::Bool(val) => Value::Bool(*val).wrapped(),
            Atom::String(val) => Value::String(val.clone()).wrapped(),
            Atom::Null => Value::Null.wrapped(),
            Atom::Identifier { span, name } => Rc::clone(self.get_var(name, span)?.0),
            Atom::Expr(node) => try_visit!(self.visit_expression(node)?),
            Atom::List(nodes) => {
                let results = nodes
                    .iter()
                    .map(|node| self.visit_expression(node))
                    .collect::<Result<Vec<_>>>()?;
                let mut values = vec![];
                for res in results {
                    if res.should_return() {
                        return Ok(res);
                    }
                    values.push(res.take_value());
                }
                Value::List(values).wrapped()
            }
            Atom::IfExpr(node) => try_visit!(self.visit_if_expr(node)?),
            Atom::ForExpr(node) => try_visit!(self.visit_for_expr(node)?),
            Atom::WhileExpr(node) => try_visit!(self.visit_while_expr(node)?),
            Atom::LoopExpr(node) => try_visit!(self.visit_loop_expr(node)?),
            Atom::FunExpr(node) => try_visit!(self.visit_fun_expr(node)?),
            Atom::ClassExpr(node) => try_visit!(self.visit_class_expr(node)?),
            Atom::TryExpr(node) => try_visit!(self.visit_try_expr(node)?),
            Atom::BlockExpr(node) => try_visit!(self.visit_block_expr(node)?),
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_if_expr(&mut self, node: &'tree IfExpr) -> Result<RuntimeResult<'tree>> {
        let cond = try_visit!(self.visit_expression(&node.cond)?);
        let out = if cond.borrow().is_true() {
            try_visit!(self.visit_block(&node.block, true)?)
        } else if let Some(block) = &node.else_block {
            try_visit!(self.visit_block(block, true)?)
        } else {
            Value::Null.wrapped()
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_for_expr(&mut self, node: &'tree ForExpr) -> Result<RuntimeResult<'tree>> {
        let iter = try_visit!(self.visit_expression(&node.iter)?);
        let iter = iter.borrow();
        let iter = iter.to_iter(&node.span)?;
        let mut out = Value::Null.wrapped();
        for item in iter {
            self.push_scope();
            self.add_var(&node.ident, item);
            let res = self.visit_block(&node.block, false)?;
            if res.should_continue {
                continue;
            } else if let Some(val) = res.break_value {
                out = val;
            } else if res.return_value.is_some() {
                return Ok(res);
            }
        }
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_while_expr(&mut self, node: &'tree WhileExpr) -> Result<RuntimeResult<'tree>> {
        let out = loop {
            let cond = try_visit!(self.visit_expression(&node.cond)?);
            if cond.borrow().is_false() {
                break Value::Null.wrapped();
            }

            let res = self.visit_block(&node.block, true)?;
            if res.should_continue {
                continue;
            } else if let Some(val) = res.break_value {
                break val;
            } else if res.return_value.is_some() {
                return Ok(res);
            }
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_loop_expr(&mut self, node: &'tree LoopExpr) -> Result<RuntimeResult<'tree>> {
        let out = loop {
            let res = self.visit_block(&node.block, true)?;
            if res.should_continue {
                continue;
            } else if let Some(val) = res.break_value {
                break val;
            } else if res.return_value.is_some() {
                return Ok(res);
            }
        };
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_fun_expr(&mut self, node: &'tree FunExpr) -> Result<RuntimeResult<'tree>> {
        let out = Value::Function {
            args: &node.args,
            block: &node.block,
        }
        .wrapped();
        Ok(RuntimeResult::new(Some(out)))
    }

    fn visit_class_expr(&mut self, node: &'tree ClassExpr) -> Result<RuntimeResult<'tree>> {
        let class = Value::Null.wrapped();
        let mut statics: HashMap<&str, _> = HashMap::new();
        let mut non_statics = vec![];
        for member in &node.block.members {
            match (member.is_static, &member.kind) {
                (true, MemberKind::Attribute(node)) => {
                    statics.insert(
                        &node.ident,
                        match &node.expr {
                            Some(node) => try_visit!(self.visit_expression(node)?),
                            None => Value::Null.wrapped(),
                        },
                    );
                }
                (true, MemberKind::Method(node)) => {
                    statics.insert(
                        &node.ident,
                        Value::Function {
                            args: &node.args,
                            block: &node.block,
                        }
                        .wrapped(),
                    );
                }
                (false, member) => {
                    non_statics.push(member);
                }
            }
        }
        *class.borrow_mut() = Value::Class {
            statics,
            non_statics,
        };
        Ok(RuntimeResult::new(Some(class)))
    }

    fn visit_try_expr(&mut self, node: &'tree TryExpr) -> Result<RuntimeResult<'tree>> {
        let res = self.visit_block(&node.try_block, true);
        if let Err(e) = res {
            self.push_scope();
            self.add_var(&node.ident, e.to_value().wrapped());
            let out = try_visit!(self.visit_block(&node.catch_block, false)?);
            self.pop_scope();
            Ok(RuntimeResult::new(Some(out)))
        } else {
            res
        }
    }

    #[inline]
    fn visit_block_expr(&mut self, node: &'tree BlockExpr) -> Result<RuntimeResult<'tree>> {
        self.visit_block(node, true)
    }
}