spwn 0.0.6

A language for Geometry Dash triggers
Documentation
//! Tools for compiling SPWN into GD object strings

use crate::ast;
use crate::builtin::*;
use crate::compiler_info::CodeArea;
use crate::compiler_info::CompilerInfo;
use crate::context::*;

use crate::globals::Globals;
use crate::levelstring::*;
use crate::value::*;
use crate::value_storage::*;
use crate::STD_PATH;
use std::collections::HashMap;
use std::mem;

use crate::parser::{ParseNotes, SyntaxError};
use std::fs;
use std::path::PathBuf;

use crate::compiler_types::*;
use crate::print_with_color;

use ariadne::Fmt;
use internment::Intern;
use termcolor::Color as TColor;

pub enum RuntimeError {
    UndefinedErr {
        undefined: String,
        desc: String,
        info: CompilerInfo,
    },

    PackageSyntaxError {
        err: SyntaxError,
        info: CompilerInfo,
    },

    PackageError {
        err: Box<RuntimeError>,
        info: CompilerInfo,
    },

    TypeError {
        expected: String,
        found: String,
        val_def: CodeArea,
        info: CompilerInfo,
    },

    PatternMismatchError {
        pattern: String,
        val: String,
        pat_def: CodeArea,
        val_def: CodeArea,
        info: CompilerInfo,
    },

    CustomError(ErrorReport),

    BuiltinError {
        message: String,
        info: CompilerInfo,
    },

    MutabilityError {
        val_def: CodeArea,
        info: CompilerInfo,
    },
    ContextChangeMutateError {
        val_def: CodeArea,
        info: CompilerInfo,
        context_changes: Vec<CodeArea>,
    },
    ContextChangeError {
        message: String,
        info: CompilerInfo,
        context_changes: Vec<CodeArea>,
    },

    BreakNeverUsedError {
        breaktype: BreakType,
        info: CompilerInfo,
        broke: CodeArea,
        dropped: CodeArea,
        reason: String,
    },
}

#[derive(Debug, Clone, Copy)]
pub struct RainbowColorGenerator {
    h: f64,
    s: f64,
    b: f64,
}

impl RainbowColorGenerator {
    pub fn new(h: f64, s: f64, b: f64) -> Self {
        Self { h, s, b }
    }

    pub fn next(&mut self) -> ariadne::Color {
        self.h += 20.0;
        self.h %= 360.0;

        let hsl = *self;

        let c = (1.0 - (hsl.b * 2.0 - 1.0).abs()) * hsl.s;
        let h = hsl.h / 60.0;
        let x = c * (1.0 - (h % 2.0 - 1.0).abs());
        let m = hsl.b - c * 0.5;

        let (red, green, blue) = if h >= 0.0 && h < 0.0 {
            (c, x, 0.0)
        } else if (1.0..2.0).contains(&h) {
            (x, c, 0.0)
        } else if (2.0..3.0).contains(&h) {
            (0.0, c, x)
        } else if (3.0..4.0).contains(&h) {
            (0.0, x, c)
        } else if (4.0..5.0).contains(&h) {
            (x, 0.0, c)
        } else {
            (c, 0.0, x)
        };

        ariadne::Color::RGB(
            ((red + m) * 255.0) as u8,
            ((green + m) * 255.0) as u8,
            ((blue + m) * 255.0) as u8,
        )
    }
}
pub fn create_report(rep: ErrorReport) -> ariadne::Report<CodeArea> {
    use ariadne::{Config, Label, Report, ReportKind};

    let info = rep.info;
    let message = rep.message;
    let labels = rep.labels;
    let note = rep.note;
    let position = info.position;

    let mut colors = RainbowColorGenerator::new(0.0, 1.5, 0.8);

    let mut report = Report::build(ReportKind::Error, position.file.as_ref(), position.pos.0)
        .with_config(Config::default().with_cross_gap(true))
        .with_message(message.clone());

    let mut i = 1;
    for area in info.call_stack {
        let color = colors.next();
        report = report.with_label(
            Label::new(area)
                .with_order(i)
                .with_message(&format!(
                    "{}: Error comes from this macro call",
                    i.to_string().fg(color)
                ))
                .with_color(color)
                .with_priority(1),
        );
        i += 1;
    }

    if labels.is_empty() || !labels.iter().any(|(a, _)| a == &position) {
        let color = colors.next();
        report = report.with_label(
            Label::new(position)
                .with_order(i)
                .with_color(color)
                .with_message(message)
                .with_priority(2),
        );
    }
    if i == 1 && labels.len() == 1 {
        let color = colors.next();
        report = report.with_label(
            Label::new(labels[0].0)
                .with_message(labels[0].1.clone())
                .with_order(i)
                .with_color(color)
                .with_priority(2),
        );
    } else if !labels.is_empty() {
        for (area, label) in labels {
            let color = colors.next();
            report = report.with_label(
                Label::new(area)
                    .with_message(&format!("{}: {}", i.to_string().fg(color), label))
                    .with_order(i)
                    .with_color(color)
                    .with_priority(2),
            );
            i += 1;
        }
    }
    if let Some(note) = note {
        report = report.with_note(note);
    }
    report.finish()
}

pub fn create_error(
    info: CompilerInfo,
    message: &str,
    labels: &[(CodeArea, &str)],
    note: Option<&str>,
) -> ErrorReport {
    ErrorReport {
        info: info.clone(),
        message: message.to_string(),
        labels: labels
            .iter()
            .map(|(a, s)| {
                if !a.file.as_ref().exists() {
                    (
                        CodeArea {
                            pos: (0, 0),
                            ..info.position
                        },
                        s.to_string(),
                    )
                } else {
                    (*a, s.to_string())
                }
            })
            .collect(),
        note: note.map(|s| s.to_string()),
    }
}

pub struct ErrorReport {
    pub info: CompilerInfo,
    pub message: String,
    pub labels: Vec<(CodeArea, String)>,
    pub note: Option<String>,
}

impl From<RuntimeError> for ErrorReport {
    fn from(err: RuntimeError) -> ErrorReport {
        let mut colors = RainbowColorGenerator::new(120.0, 1.5, 0.8);
        let a = colors.next();
        let b = colors.next();

        match err {
            RuntimeError::UndefinedErr {
                undefined,
                desc,
                info,
            } => create_error(
                info.clone(),
                &format!("Use of undefined {}", desc),
                &[(
                    info.position,
                    &format!("'{}' is undefined", undefined.fg(b)),
                )],
                None,
            ),
            RuntimeError::PackageSyntaxError { err, info } => {
                let syntax_error = ErrorReport::from(err);
                let mut labels = vec![(info.position, "Error when parsing this library/module")];
                labels.extend(syntax_error.labels.iter().map(|(a, b)| (*a, b.as_str())));
                create_error(info, &syntax_error.message, &labels, None)
            }

            RuntimeError::PackageError { err, info } => {
                let syntax_error = ErrorReport::from(*err);
                let mut labels = vec![(info.position, "Error when running this library/module")];
                labels.extend(syntax_error.labels.iter().map(|(a, b)| (*a, b.as_str())));
                create_error(info, &syntax_error.message, &labels, None)
            }

            RuntimeError::TypeError {
                expected,
                found,
                info,
                val_def,
            } => create_error(
                info.clone(),
                "Type mismatch",
                &[
                    (
                        val_def,
                        &format!("Value defined as {} here", found.clone().fg(b)),
                    ),
                    (
                        info.position,
                        &format!("Expected {}, found {}", expected.fg(a), found.fg(b)),
                    ),
                ],
                None,
            ),

            RuntimeError::PatternMismatchError {
                pattern,
                val,
                info,
                val_def,
                pat_def,
            } => create_error(
                info.clone(),
                "Pattern mismatch",
                &[
                    (
                        val_def,
                        &format!("Value defined as {} here", val.clone().fg(b)),
                    ),
                    (
                        pat_def,
                        &format!("Pattern defined as {} here", pattern.clone().fg(b)),
                    ),
                    (
                        info.position,
                        &format!("Expected {}, found {}", pattern.fg(a), val.fg(b)),
                    ),
                ],
                None,
            ),

            RuntimeError::CustomError(report) => report,

            RuntimeError::BuiltinError { message, info } => create_error(
                info.clone(),
                "Error when using built-in function",
                &[(info.position, &message)],
                None,
            ),

            RuntimeError::MutabilityError { val_def, info } => create_error(
                info.clone(),
                "Attempted to change immutable variable",
                &[
                    (val_def, "Value was defined as immutable here"),
                    (info.position, "This tries to change the value"),
                ],
                None,
            ),

            RuntimeError::ContextChangeMutateError {
                val_def,
                info,
                context_changes,
            } => {
                let mut labels = vec![(val_def, "Value was defined here")];

                #[allow(clippy::comparison_chain)]
                if context_changes.len() == 1 {
                    labels.push((
                        context_changes[0],
                        "New trigger function context was defined here",
                    ));
                } else if context_changes.len() > 1 {
                    labels.push((*context_changes.last().unwrap(), "Context was changed here"));

                    for change in context_changes[1..(context_changes.len() - 1)].iter().rev() {
                        labels.push((*change, "This changes the context inside the macro"));
                    }

                    labels.push((
                        context_changes[0],
                        "New trigger function context was defined here",
                    ));
                }

                labels.push((info.position, "Attempted to change value here"));

                create_error(
                    info,
                    "Attempted to change a variable defined in a different trigger function context",
                    &labels,
                    Some("Consider using a counter"),
                )
            }

            RuntimeError::ContextChangeError {
                message,
                info,
                context_changes,
            } => {
                let mut labels = Vec::new();

                #[allow(clippy::comparison_chain)]
                if context_changes.len() == 1 {
                    labels.push((
                        context_changes[0],
                        "New trigger function context was defined here",
                    ));
                } else if context_changes.len() > 1 {
                    labels.push((*context_changes.last().unwrap(), "Context was changed here"));

                    for change in context_changes[1..(context_changes.len() - 1)].iter().rev() {
                        labels.push((*change, "This changes the context inside the macro"));
                    }

                    labels.push((
                        context_changes[0],
                        "New trigger function context was defined here",
                    ));
                }

                create_error(info, &message, &labels, None)
            }

            RuntimeError::BreakNeverUsedError {
                info,
                breaktype,
                broke,
                dropped,
                reason,
            } => create_error(
                info,
                &format!(
                    "{} statement never used",
                    match breaktype {
                        BreakType::ContinueLoop => "Continue",
                        BreakType::Loop => "Break",
                        BreakType::Macro(_, _) => "Return",
                        BreakType::Switch(_) => unreachable!("Switch break in the wild"),
                    }
                ),
                &[
                    (broke, "Declared here"),
                    (
                        dropped,
                        &format!("Can't reach past here because {}", reason),
                    ),
                ],
                None,
            ),
        }
    }
}

pub const NULL_STORAGE: StoredValue = 1;
pub const BUILTIN_STORAGE: StoredValue = 0;

pub fn compile_spwn(
    statements: Vec<ast::Statement>,
    path: PathBuf,
    included_paths: Vec<PathBuf>,
    notes: ParseNotes,
) -> Result<Globals, RuntimeError> {
    //variables that get changed throughout the compiling
    let mut globals = Globals::new(path.clone());
    globals.includes = included_paths;
    if statements.is_empty() {
        return Err(RuntimeError::CustomError(create_error(
            CompilerInfo::from_area(crate::compiler_info::CodeArea {
                file: Intern::new(path),
                pos: (0, 0),
            }),
            "this script is empty",
            &[],
            None,
        )));
    }
    let mut start_context = FullContext::new();
    //store at pos 0
    // store_value(Value::Builtins, 1, &mut globals, &start_context);
    // store_value(Value::Null, 1, &mut globals, &start_context);

    let start_info = CompilerInfo {
        ..CompilerInfo::from_area(crate::compiler_info::CodeArea {
            file: Intern::new(path.clone()),
            pos: statements[0].pos,
        })
    };
    use std::time::Instant;

    //println!("Importing standard library...");
    print_with_color("Building script ...", TColor::Cyan);
    print_with_color("———————————————————————————\n", TColor::White);
    let start_time = Instant::now();

    if !notes.tag.tags.iter().any(|x| x.0 == "no_std") {
        import_module(
            &ImportType::Lib(STD_PATH.to_string()),
            &mut start_context,
            &mut globals,
            start_info.clone(),
            false,
        )?;

        if let FullContext::Split(_, _) = start_context {
            return Err(RuntimeError::CustomError(create_error(
                start_info,
                "The standard library can not split the context",
                &[],
                None,
            )));
        }

        if let Value::Dict(d) = &globals.stored_values[start_context.inner().return_value] {
            for (a, b, c) in d.iter().map(|(k, v)| (*k, *v, -1)) {
                start_context.inner().new_variable(a, b, c)
            }
        } else {
            return Err(RuntimeError::CustomError(create_error(
                start_info,
                "The standard library must return a dictionary",
                &[],
                None,
            )));
        }
    }

    compile_scope(&statements, &mut start_context, &mut globals, start_info)?;

    for fc in start_context.with_breaks() {
        let c = fc.inner();
        let end_pos = statements.last().unwrap().pos.1;
        if let Some((r, i)) = c.broken {
            return Err(RuntimeError::BreakNeverUsedError {
                breaktype: r,
                info: CompilerInfo::from_area(i),
                broke: i,
                dropped: CodeArea {
                    pos: (end_pos, end_pos),
                    file: Intern::new(path),
                },
                reason: "the program ended".to_string(),
            });
        }
    }

    print_with_color("———————————————————————————\n", TColor::White);

    /*  Build Timing ----------------------------------------------------- **
        New build timing changes the unit form milliseconds, to seconds,
        to minutes depending on the time building took.
    */

    // Define the different units
    let build_time_secs = start_time.elapsed().as_secs();
    let build_time_millis = start_time.elapsed().as_millis();
    let build_time_mins = build_time_secs / 60;

    // Check which unit to unit to use
    if build_time_millis < 1000 {
        print_with_color(
            &format!("Built in {} milliseconds!", build_time_millis),
            TColor::Green,
        );
    } else if build_time_millis < 60000 {
        print_with_color(
            &format!("Built in {} seconds!", build_time_secs),
            TColor::Green,
        );
    } else {
        print_with_color(
            &format!("Built in {} minutes!", build_time_mins),
            TColor::Green,
        );
    }

    //----------------------------------------------------------------------- **

    Ok(globals)
}

pub fn compile_scope(
    statements: &[ast::Statement],
    contexts: &mut FullContext,
    globals: &mut Globals,
    mut info: CompilerInfo,
) -> Result<(), RuntimeError> {
    if contexts.iter().next().is_none() {
        return Ok(());
    }
    contexts.enter_scope();

    for statement in statements.iter() {
        contexts.reset_return_vals();
        //find out what kind of statement this is
        //let start_time = Instant::now();

        //print_error_intro(info.pos, &info.current_file);

        //println!("{}", statement.fmt(0));

        // println!(
        //     "{} -> Compiling a statement in {} contexts",
        //     info.path.join(">"),
        //     contexts.len()
        // );
        info.position.pos = statement.pos;

        // println!(
        //     "{}:0:{}",
        //     info.position.file.to_string_lossy(),
        //     info.position.pos.0
        // );
        use ast::StatementBody::*;

        let stored_context = if statement.arrow {
            Some(contexts.iter().map(|a| a.clone()).collect::<Vec<_>>())
        } else {
            None
        };

        //println!("{}:{}:{}", info.current_file.to_string_lossy(), info.pos.0.0, info.pos.0.1);
        //use crate::fmt::SpwnFmt;
        match &statement.body {
            Expr(expr) => {
                let is_assign = !expr.operators.is_empty()
                    && expr.operators[0] == ast::Operator::Assign
                    && !expr.values[0].is_undefinable(
                        contexts.iter().next().unwrap().inner(),
                        globals,
                        true,
                    );

                //println!("{:?}, {}", expr, is_assign);

                if is_assign {
                    let mut new_expr = expr.clone();
                    let symbol = new_expr.values.remove(0);
                    //use crate::fmt::SpwnFmt;
                    new_expr.operators.remove(0); //assign operator
                    let mutable = symbol.operator == Some(ast::UnaryOperator::Let);

                    //let mut new_context = context.clone();

                    match symbol.value.body {
                        ast::ValueBody::Array(var_a) => {
                            new_expr.eval(contexts, globals, info.clone(), true)?;

                            for ctx in contexts.iter() {
                                match globals.stored_values[ctx.inner().return_value].clone() {
                                    Value::Array(val_a) => {
                                        let ranges = var_a
                                            .iter()
                                            .filter(|x| {
                                                x.values[0].operator
                                                    == Some(ast::UnaryOperator::Range)
                                            })
                                            .collect::<Vec<&ast::Expression>>();

                                        if ranges.len() > 1 {
                                            let mut why_sput = info.position;
                                            why_sput.pos = ranges[0].values[0].pos;
                                            info.position.pos = ranges[1].values[0].pos;

                                            return Err(RuntimeError::CustomError(create_error(
                                                info.clone(),
                                                "Cannot spread a destructure multiple times",
                                                &[
                                                    (why_sput, "First spread is here"),
                                                    (info.position, "Attempted to spread again"),
                                                ],
                                                None,
                                            )));
                                        }

                                        if (var_a.len() < val_a.len() && ranges.is_empty())
                                            || var_a.len() > val_a.len()
                                        {
                                            return Err(RuntimeError::CustomError(create_error(
                                                info,
                                                &format!(
                                                    "Expected {} items to destructure, found {}",
                                                    var_a.len(),
                                                    val_a.len()
                                                ),
                                                &[],
                                                None,
                                            )));
                                        } else {
                                            let mut idx: usize = 0;
                                            let mut var_idx: usize = 0;
                                            loop {
                                                let mut idx_step = 1;
                                                for expr_ctx in ctx.iter() {
                                                    if var_a[var_idx].operators.is_empty()
                                                        || var_a[var_idx].values.is_empty()
                                                    {
                                                        use crate::fmt::_format2;

                                                        return Err(RuntimeError::CustomError(create_error(
                                                            info,
                                                            &format!("Cannot destructure value into expression '{}'", _format2(&ast::ValueBody::Expression(var_a[idx].clone()))),
                                                            &[],
                                                            None,
                                                        )));
                                                    }
                                                    let var_val = &var_a[var_idx].values[0];
                                                    match var_val.operator {
                                                        Some(ast::UnaryOperator::Range) => {
                                                            let mut without_op = var_val.clone();
                                                            without_op.operator = None;

                                                            idx_step =
                                                                1 + val_a.len() - var_a.len();
                                                            let mut packed =
                                                                Vec::<StoredValue>::new();

                                                            let storage = without_op.define(
                                                                expr_ctx.inner(),
                                                                globals,
                                                                &info,
                                                            )?;

                                                            for tmp_idx in idx..(idx + idx_step) {
                                                                let cloned = clone_value(
                                                                    val_a[tmp_idx],
                                                                    globals,
                                                                    expr_ctx.inner().start_group,
                                                                    !mutable,
                                                                    globals.get_area(storage),
                                                                );
                                                                packed.push(cloned);
                                                            }
                                                            //println!("collecting {} items", val_a.len()-var_a.len());
                                                            globals.stored_values[storage] =
                                                                Value::Array(packed);
                                                        }
                                                        _ => {
                                                            let storage = var_val.define(
                                                                expr_ctx.inner(),
                                                                globals,
                                                                &info,
                                                            )?;
                                                            //clone the value so as to not share the reference

                                                            let cloned = clone_and_get_value(
                                                                val_a[idx],
                                                                globals,
                                                                expr_ctx.inner().start_group,
                                                                !mutable,
                                                            );

                                                            globals.stored_values[storage] = cloned;
                                                        }
                                                    }
                                                }

                                                idx += idx_step;
                                                var_idx += 1;
                                                if idx >= val_a.len() {
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                    b => {
                                        return Err(RuntimeError::TypeError {
                                            expected: "array".to_string(),
                                            found: b.get_type_str(globals),
                                            val_def: globals.get_area(ctx.inner().return_value),
                                            info,
                                        })
                                    }
                                }
                            }
                        }
                        _ => {
                            match (
                                new_expr.values.len() == 1
                                    && new_expr.values[0].path.is_empty()
                                    && new_expr.values[0].operator.is_none(),
                                &new_expr.values[0].value.body,
                            ) {
                                (true, ast::ValueBody::CmpStmt(f)) => {
                                    //to account for recursion

                                    //create the function context
                                    for full_context in contexts.iter() {
                                        let storage =
                                            symbol.define(full_context.inner(), globals, &info)?;

                                        //pick a start group
                                        let start_group =
                                            Group::next_free(&mut globals.closed_groups);
                                        //store value
                                        globals.stored_values[storage] =
                                            Value::TriggerFunc(TriggerFunction { start_group });

                                        full_context.inner().fn_context_change_stack =
                                            vec![info.position];
                                        //new_info.last_context_change_stack = vec![info.position.clone()];

                                        f.to_trigger_func(
                                            full_context,
                                            globals,
                                            info.clone(),
                                            Some(start_group),
                                        )?;
                                    }
                                }

                                _ => {
                                    new_expr.eval(contexts, globals, info.clone(), !mutable)?;

                                    for full_context in contexts.iter() {
                                        let (context, val) = full_context.inner_value();
                                        let storage = symbol.define(context, globals, &info)?;
                                        //clone the value so as to not share the reference

                                        let cloned = clone_and_get_value(
                                            val,
                                            globals,
                                            context.start_group,
                                            !mutable,
                                        );

                                        globals.stored_values[storage] = cloned;
                                    }
                                }
                            }
                        }
                    }
                } else {
                    expr.eval(contexts, globals, info.clone(), true)?;
                }
            }

            Extract(val) => {
                val.eval(contexts, globals, info.clone(), true)?;
                for full_context in contexts.iter() {
                    let (context, val) = full_context.inner_value();
                    let fn_context = context.start_group;
                    match globals.stored_values[val].clone() {
                        Value::Dict(d) => {
                            let iter = d.iter().map(|(k, v)| {
                                (
                                    *k,
                                    clone_value(
                                        *v,
                                        globals,
                                        fn_context,
                                        !globals.is_mutable(*v),
                                        globals.get_area(*v),
                                    ),
                                    0,
                                )
                            });
                            for (a, b, c) in iter {
                                context.new_variable(a, b, c);
                            }
                        }
                        Value::Builtins => {
                            for name in BUILTIN_LIST.iter() {
                                let p = store_const_value(
                                    Value::BuiltinFunction(*name),
                                    globals,
                                    fn_context,
                                    info.position,
                                );

                                context.new_variable(Intern::new(String::from(*name)), p, 0);
                            }
                        }
                        a => {
                            return Err(RuntimeError::TypeError {
                                expected: "dictionary or $".to_string(),
                                found: a.get_type_str(globals),
                                val_def: globals.get_area(val),
                                info,
                            })
                        }
                    }
                }
            }

            TypeDef(name) => {
                //initialize type
                let already = globals.type_ids.get(name);
                if let Some(t) = already {
                    if t.1 != info.position {
                        return Err(RuntimeError::CustomError(create_error(
                            info.clone(),
                            &format!("the type '{}' is already defined", name),
                            &[
                                (t.1, "The type was first defined here"),
                                (info.position, "Attempted to redefine here"),
                            ],
                            None,
                        )));
                    }
                } else {
                    (*globals).type_id_count += 1;
                    (*globals)
                        .type_ids
                        .insert(name.clone(), (globals.type_id_count, info.position));
                }
                //Value::TypeIndicator(globals.type_id_count)
            }

            If(if_stmt) => {
                if_stmt
                    .condition
                    .eval(contexts, globals, info.clone(), true)?;
                for full_context in contexts.iter() {
                    let (_, val) = full_context.inner_value();
                    match &globals.stored_values[val] {
                        Value::Bool(b) => {
                            //internal if statement
                            if *b {
                                compile_scope(
                                    &if_stmt.if_body,
                                    full_context,
                                    globals,
                                    info.clone(),
                                )?;
                            } else {
                                match &if_stmt.else_body {
                                    Some(body) => {
                                        compile_scope(body, full_context, globals, info.clone())?;
                                    }
                                    None => (),
                                };
                            }
                        }
                        a => {
                            return Err(RuntimeError::TypeError {
                                expected: "boolean".to_string(),
                                found: a.get_type_str(globals),
                                val_def: globals.get_area(val),
                                info,
                            })
                        }
                    }
                }
            }

            Impl(imp) => {
                let message = "cannot run impl statement in a trigger function context, consider moving it to the start of your script.".to_string();

                if let FullContext::Single(c) = &contexts {
                    if c.start_group.id != Id::Specific(0) {
                        return Err(RuntimeError::ContextChangeError {
                            message,
                            info,
                            context_changes: c.fn_context_change_stack.clone(),
                        });
                    }
                } else {
                    return Err(RuntimeError::CustomError(create_error(
                        info,
                        "impl cannot run in a split context",
                        &[],
                        None,
                    )));
                }

                imp.symbol.to_value(contexts, globals, info.clone(), true)?;

                if let FullContext::Split(_, _) = contexts {
                    return Err(RuntimeError::CustomError(create_error(
                        info,
                        "impl statements with context-splitting values are not allowed",
                        &[],
                        None,
                    )));
                }

                let (c, typ) = contexts.inner_value();

                if c.start_group.id != Id::Specific(0) {
                    return Err(RuntimeError::ContextChangeError {
                        message: "impl type changes the context".to_string(),
                        info,
                        context_changes: c.fn_context_change_stack.clone(),
                    });
                }
                match globals.stored_values[typ].clone() {
                    Value::TypeIndicator(s) => {
                        eval_dict(imp.members.clone(), contexts, globals, info.clone(), true)?;
                        if let FullContext::Split(_, _) = contexts {
                            return Err(RuntimeError::CustomError(create_error(
                                info,
                                "impl statements with context-splitting values are not allowed",
                                &[],
                                None,
                            )));
                        }
                        //Returns inside impl values dont really make sense do they
                        if contexts.inner().broken.is_some() {
                            return Err(RuntimeError::CustomError(create_error(
                                info,
                                "you can't use return from inside an impl statement value",
                                &[],
                                None,
                            )));
                        }
                        let (_, val) = contexts.inner_value();

                        // make this not ugly, future me

                        if let Value::Dict(d) = &globals.stored_values[val] {
                            match globals.implementations.get_mut(&s) {
                                Some(implementation) => {
                                    for (key, val) in d.iter() {
                                        (*implementation).insert(*key, (*val, true));
                                    }
                                }
                                None => {
                                    globals.implementations.insert(
                                        s,
                                        d.iter()
                                            .map(|(key, value)| (*key, (*value, true)))
                                            .collect(),
                                    );
                                }
                            }
                        } else {
                            unreachable!();
                        }
                    }
                    a => {
                        return Err(RuntimeError::TypeError {
                            expected: "type indicator".to_string(),
                            found: a.get_type_str(globals),
                            val_def: globals.get_area(typ),
                            info,
                        })
                    }
                }

                //println!("{:?}", new_contexts[0].implementations);
            }
            Call(call) => {
                /*for context in &mut contexts {
                    context.x += 1;
                }*/
                call.function
                    .to_value(contexts, globals, info.clone(), true)?;

                //let mut obj_list = Vec::<GDObj>::new();
                for full_context in contexts.iter() {
                    let (context, func) = full_context.inner_value();
                    let mut params = HashMap::new();
                    params.insert(
                        51,
                        match &globals.stored_values[func] {
                            Value::TriggerFunc(g) => ObjParam::Group(g.start_group),
                            Value::Group(g) => ObjParam::Group(*g),
                            a => {
                                return Err(RuntimeError::TypeError {
                                    expected: "trigger function or group".to_string(),
                                    found: a.get_type_str(globals),
                                    val_def: globals.get_area(func),
                                    info,
                                })
                            }
                        },
                    );
                    params.insert(1, ObjParam::Number(1268.0));
                    (*globals).trigger_order += 1;

                    (*globals).func_ids[context.func_id].obj_list.push((
                        GdObj {
                            params,

                            ..context_trigger(context, &mut globals.uid_counter)
                        }
                        .context_parameters(context),
                        globals.trigger_order,
                    ))
                }
            }

            While(w) => {
                for full_context in contexts.iter() {
                    let fn_context = full_context.inner().start_group;
                    loop {
                        full_context.disable_breaks(BreakType::ContinueLoop);

                        w.condition
                            .eval(full_context, globals, info.clone(), true)?;

                        if let FullContext::Split(_, _) = full_context {
                            return Err(RuntimeError::CustomError(create_error(
                                info,
                                "While loop condition can not split the context",
                                &[],
                                Some("Consider using a runtime while loop"),
                            )));
                        }

                        if full_context.inner().start_group != fn_context {
                            return Err(RuntimeError::ContextChangeError {
                                message: "While loop condition can not change the trigger function context (consider using a runtime while loop)".to_string(),
                                info,
                                context_changes: full_context.inner().fn_context_change_stack.clone()
                            });
                        }

                        if let Value::Bool(b) =
                            globals.stored_values[full_context.inner().return_value]
                        {
                            if b {
                                compile_scope(&w.body, full_context, globals, info.clone())?;
                                if let FullContext::Split(_, _) = full_context {
                                    return Err(RuntimeError::CustomError(create_error(
                                        info,
                                        "While loop body can not split the context (consider using a runtime while loop)",
                                        &[],
                                        Some("Consider using a runtime while loop"),
                                    )));
                                }
                                if full_context.inner().start_group != fn_context {
                                    return Err(RuntimeError::ContextChangeError {
                                        message: "While loop body can not change the trigger function context".to_string(),
                                        info,
                                        context_changes: full_context.inner().fn_context_change_stack.clone()
                                    });
                                }
                            } else {
                                full_context.inner().broken =
                                    Some((BreakType::Loop, CodeArea::new()));
                            }
                        } else {
                            return Err(RuntimeError::TypeError {
                                expected: "boolean".to_string(),
                                found: globals.get_type_str(full_context.inner().return_value),
                                val_def: globals.get_area(full_context.inner().return_value),
                                info,
                            });
                        }

                        let all_breaks = full_context.with_breaks().all(|fc| {
                            !matches!(fc.inner().broken, Some((BreakType::ContinueLoop, _)) | None)
                        });
                        if all_breaks {
                            break;
                        }
                    }
                    full_context.disable_breaks(BreakType::Loop);
                    full_context.disable_breaks(BreakType::ContinueLoop);
                }
            }

            For(f) => {
                f.array.eval(contexts, globals, info.clone(), true)?;
                /*
                Before going further you should probably understand what contexts mean.
                A "context", in SPWN, is like a parallel universe. Each context is nearly
                identical to a code block, except it expands a runtime value that is meant to be
                converted into a compile time item. Every time you want to do something like
                convert a counter to a number, SPWN will branch the current code block into a number
                of contexts, one for each possible value from the conversion. All of the branched contexts
                will be evaluated in isolation to each other.
                */
                let i_name = f.symbol;
                // skips all broken contexts, so as to not interfere with potential breaks in the following loops

                for full_context in contexts.iter() {
                    let (_, val) = full_context.inner_value();
                    globals.push_new_preserved();
                    globals.push_preserved_val(val);

                    match globals.stored_values[val].clone() {
                        // what are we iterating
                        Value::Array(arr) => {
                            // its an array!for

                            for element in arr {
                                // going through the array items
                                full_context.disable_breaks(BreakType::ContinueLoop);

                                full_context.set_variable_and_clone(
                                    i_name,
                                    element,
                                    -1, // so that it gets removed at the end of the scope
                                    true,
                                    globals,
                                    globals.get_area(element),
                                );

                                compile_scope(&f.body, full_context, globals, info.clone())?; // eval the stuff

                                let all_breaks = full_context.with_breaks().all(|fc| {
                                    !matches!(
                                        fc.inner().broken,
                                        Some((BreakType::ContinueLoop, _)) | None
                                    )
                                });

                                if all_breaks {
                                    break;
                                }
                            }

                            // finally append all newly created ones to the global count
                        }
                        Value::Dict(d) => {
                            // its a dict!

                            for (k, v) in d {
                                // going through the dict items
                                full_context.disable_breaks(BreakType::ContinueLoop);

                                for c in full_context.iter() {
                                    let fn_context = c.inner().start_group;
                                    let key = store_val_m(
                                        Value::Str(k.as_ref().clone()),
                                        globals,
                                        fn_context,
                                        true,
                                        globals.get_area(v),
                                    );
                                    let val = clone_value(
                                        v,
                                        globals,
                                        fn_context,
                                        true,
                                        globals.get_area(v),
                                    );
                                    // reset all variables per context
                                    (*c.inner()).new_variable(
                                        i_name,
                                        store_const_value(
                                            Value::Array(vec![key, val]),
                                            globals,
                                            fn_context,
                                            globals.get_area(v),
                                        ),
                                        -1,
                                    );
                                }

                                compile_scope(&f.body, full_context, globals, info.clone())?; // eval the stuff

                                let all_breaks = full_context.with_breaks().all(|fc| {
                                    !matches!(
                                        fc.inner().broken,
                                        Some((BreakType::ContinueLoop, _)) | None
                                    )
                                });

                                if all_breaks {
                                    break;
                                }
                            }
                        }
                        Value::Str(s) => {
                            for ch in s.chars() {
                                // going through the array items
                                full_context.disable_breaks(BreakType::ContinueLoop);

                                full_context.set_variable_and_store(
                                    i_name,
                                    Value::Str(ch.to_string()),
                                    -1, // so that it gets removed at the end of the scope
                                    true,
                                    globals,
                                    info.position,
                                );

                                compile_scope(&f.body, full_context, globals, info.clone())?; // eval the stuff

                                let all_breaks = full_context.with_breaks().all(|fc| {
                                    !matches!(
                                        fc.inner().broken,
                                        Some((BreakType::ContinueLoop, _)) | None
                                    )
                                });

                                if all_breaks {
                                    break;
                                }
                            }
                        }

                        Value::Range(start, end, step) => {
                            let mut normal = (start..end).step_by(step);
                            let mut rev = (end..start).step_by(step).rev();
                            let range: &mut dyn Iterator<Item = i32> =
                                if start < end { &mut normal } else { &mut rev };

                            for num in range {
                                // going through the array items
                                full_context.disable_breaks(BreakType::ContinueLoop);

                                full_context.set_variable_and_store(
                                    i_name,
                                    Value::Number(num as f64),
                                    -1, // so that it gets removed at the end of the scope
                                    true,
                                    globals,
                                    info.position,
                                );

                                //dbg!(full_context.with_breaks().count());

                                compile_scope(&f.body, full_context, globals, info.clone())?; // eval the stuff

                                //dbg!(full_context.with_breaks().count());

                                let all_breaks = full_context.with_breaks().all(|fc| {
                                    !matches!(
                                        fc.inner().broken,
                                        Some((BreakType::ContinueLoop, _)) | None
                                    )
                                });

                                if all_breaks {
                                    break;
                                }
                            }
                        }

                        a => {
                            return Err(RuntimeError::TypeError {
                                expected: "array, dictionary, string or range".to_string(),
                                found: a.get_type_str(globals),
                                val_def: globals.get_area(val),
                                info,
                            })
                        }
                    }
                    full_context.disable_breaks(BreakType::Loop);
                    full_context.disable_breaks(BreakType::ContinueLoop);
                    globals.pop_preserved();
                }
            }
            Break => {
                //set all contexts to broken
                for c in contexts.iter() {
                    (*c.inner()).broken = Some((BreakType::Loop, info.position));
                }
                break;
            }

            Continue => {
                //set all contexts to broken
                for c in contexts.iter() {
                    (*c.inner()).broken = Some((BreakType::ContinueLoop, info.position));
                }
                break;
            }

            Return(return_val) => {
                for full_context in contexts.iter() {
                    // let full_context = if statement.arrow {
                    //     *full_context = FullContext::Split(
                    //         full_context.clone().into(),
                    //         full_context.clone().into(),
                    //     );
                    //     if let FullContext::Split(_, c) = full_context {
                    //         &mut **c
                    //     } else {
                    //         unreachable!()
                    //     }
                    // } else {
                    //     full_context
                    // };
                    match return_val {
                        Some(val) => {
                            val.eval(full_context, globals, info.clone(), true)?;
                            for context in full_context.iter() {
                                let return_val = context.inner().return_value;
                                context.inner().broken = Some((
                                    BreakType::Macro(
                                        Some(clone_value(
                                            return_val,
                                            globals,
                                            context.inner().start_group,
                                            true,
                                            globals.get_area(return_val),
                                        )),
                                        statement.arrow,
                                    ),
                                    info.position,
                                ));
                            }
                        }

                        None => {
                            full_context.inner().broken =
                                Some((BreakType::Macro(None, statement.arrow), info.position));
                        }
                    };
                }
                if !statement.arrow {
                    break;
                }
            }

            Error(e) => {
                let mut errors = Vec::new();

                e.message.eval(contexts, globals, info.clone(), true)?;
                for c in contexts.iter() {
                    let err = globals.stored_values[c.inner().return_value].to_str(globals);
                    errors.push((info.position, err))
                }

                let mut new_errors = Vec::new();
                for (area, msg) in errors.iter() {
                    new_errors.push((*area, msg.as_str()))
                }

                let err = create_error(info, "Runtime Error", &new_errors, None);

                return Err(RuntimeError::CustomError(err));
            }
        }

        if let Some(c) = stored_context {
            //resetting the context if async
            let mut list = c;

            for context in contexts.with_breaks() {
                if let Some((r, i)) = context.inner().broken {
                    if let BreakType::Macro(_, true) = r {
                        list.push(context.clone());
                    } else {
                        return Err(RuntimeError::BreakNeverUsedError {
                            breaktype: r,
                            info: CompilerInfo::from_area(i),
                            broke: i,
                            dropped: info.position,
                            reason: "it's inside an arrow statement".to_string(),
                        });
                    }
                }
            }
            *contexts = FullContext::stack(&mut list.into_iter()).unwrap();
        }

        //try to merge contexts

        if let FullContext::Split(_, _) = contexts {
            let mut broken = Vec::new();
            let mut not_broken = Vec::new();
            for c in contexts.with_breaks() {
                if c.inner().broken.is_some() {
                    broken.push(c.inner().clone())
                } else {
                    not_broken.push(c.inner().clone())
                }
            }

            if not_broken.len() > 1 {
                loop {
                    if !merge_contexts(&mut not_broken, globals) {
                        break;
                    }
                }

                broken.extend(not_broken);

                *contexts =
                    FullContext::stack(&mut broken.into_iter().map(FullContext::Single)).unwrap();
            } else if not_broken.is_empty() {
                break;
            }
        }

        if contexts.iter().next().is_none() {
            break;
        }

        /*println!(
            "{} -> Compiled '{}' in {} milliseconds!",
            path,
            statement_type,
            start_time.elapsed().as_millis(),
        );*/

        let increase =
            globals.stored_values.map.len() as i32 - globals.stored_values.prev_value_count as i32;

        if increase > 5000 {
            globals.collect_garbage(contexts);
        }
    }

    // TODO: get rid of lifetimes

    contexts.exit_scope();

    Ok(())
}

fn merge_impl(target: &mut Implementations, source: &Implementations) {
    for (key, imp) in source.iter() {
        match target.get_mut(key) {
            Some(target_imp) => (*target_imp).extend(imp.iter().map(|x| (*x.0, *x.1))),
            None => {
                (*target).insert(*key, imp.clone());
            }
        }
    }
}

pub fn get_import_path(
    path: &ImportType,
    globals: &mut Globals,
    info: CompilerInfo,
) -> Result<PathBuf, RuntimeError> {
    Ok(match path {
        ImportType::Script(p) => globals
            .path
            .clone()
            .parent()
            .expect("Your file must be in a folder to import modules!")
            .join(&p),

        ImportType::Lib(name) => {
            let mut outpath = globals.includes[0].clone();
            let mut found = false;
            for path in &globals.includes {
                if path.join("libraries").join(name).exists() {
                    outpath = path.to_path_buf();
                    found = true;
                    break;
                }
            }
            if found {
                outpath
            } else {
                let labels = globals
                    .includes
                    .iter()
                    .map(|p| {
                        (
                            info.position,
                            format!("Not found in {}", p.to_string_lossy()),
                        )
                    })
                    .collect::<Vec<_>>();
                let mut new_labels = Vec::new();

                for (area, text) in labels.iter() {
                    new_labels.push((*area, text.as_str()));
                }
                return Err(RuntimeError::CustomError(create_error(
                    info,
                    "Unable to find library folder",
                    &new_labels,
                    None,
                )));
            }
        }
        // .parent()
        // .unwrap()
        .join("libraries")
        .join(name),
    })
}

pub fn import_module(
    path: &ImportType,
    contexts: &mut FullContext,
    globals: &mut Globals,
    info: CompilerInfo,
    forced: bool,
) -> Result<(), RuntimeError> {
    //println!("importing: {:?}", path);
    if !forced {
        if let Some(ret) = globals.prev_imports.get(path).cloned() {
            merge_impl(&mut globals.implementations, &ret.1);
            for c in contexts.iter() {
                c.inner().return_value = ret.0;
            }
            return Ok(());
        }
    }

    let mut module_path = get_import_path(path, globals, info.clone())?;

    if module_path.is_dir() {
        module_path = module_path.join("lib.spwn");
    } else if module_path.is_file() && module_path.extension().is_none() {
        module_path.set_extension("spwn");
    } else if !module_path.is_file() {
        return Err(RuntimeError::CustomError(create_error(
            info,
            &format!(
                "Couldn't find library file ({})",
                module_path.to_string_lossy()
            ),
            &[],
            None,
        )));
    }

    let module_path = Intern::new(module_path);

    let unparsed = match fs::read_to_string(module_path.as_ref()) {
        Ok(content) => content,
        Err(e) => {
            return Err(RuntimeError::CustomError(create_error(
                info.clone(),
                &format!(
                    "Something went wrong when opening library file ({})",
                    module_path.to_string_lossy(),
                ),
                &[(info.position, &format!("{}", e))],
                None,
            )));
        }
    };
    let (parsed, notes) = match crate::parse_spwn(unparsed, module_path.as_ref().clone()) {
        Ok(p) => p,
        Err(err) => return Err(RuntimeError::PackageSyntaxError { err, info }),
    };

    let mut start_context = FullContext::new();

    globals.push_new_preserved();
    for c in contexts.with_breaks() {
        for stack in c.inner().get_variables().values() {
            for (v, _) in stack.iter() {
                globals.push_preserved_val(*v);
            }
        }
    }

    let mut stored_impl = None;
    if let ImportType::Lib(_) = path {
        let mut impl_vals = Vec::new();
        for imp in globals.implementations.values() {
            for (v, _) in imp.values() {
                impl_vals.push(*v);
            }
        }
        for v in impl_vals {
            globals.push_preserved_val(v);
        }
        let mut stored = HashMap::new();

        mem::swap(&mut stored, &mut globals.implementations);
        stored_impl = Some(stored);
    }

    if !notes.tag.tags.iter().any(|x| x.0 == "no_std") {
        import_module(
            &ImportType::Lib(STD_PATH.to_string()),
            &mut start_context,
            globals,
            info.clone(),
            false,
        )?;

        if let FullContext::Split(_, _) = start_context {
            return Err(RuntimeError::CustomError(create_error(
                info,
                "The standard library can not split the context",
                &[],
                None,
            )));
        }

        if let Value::Dict(d) = &globals.stored_values[start_context.inner().return_value] {
            for (a, b, c) in d.iter().map(|(k, v)| (*k, *v, -1)) {
                start_context.inner().new_variable(a, b, c)
            }
        } else {
            return Err(RuntimeError::CustomError(create_error(
                info,
                "The standard library must return a dictionary",
                &[],
                None,
            )));
        }
    }

    let stored_path = globals.path;
    (*globals).path = module_path;

    let mut new_info = info.clone();

    new_info.position.file = module_path;
    new_info.position.pos = (0, 0);

    if let ImportType::Lib(l) = path {
        new_info.current_module = l.clone();
    }

    match compile_scope(&parsed, &mut start_context, globals, new_info) {
        Ok(_) => (),
        Err(err) => {
            return Err(RuntimeError::PackageError {
                err: Box::new(err),
                info,
            })
        }
    };

    globals.pop_preserved();

    let save_value = notes.tag.tags.iter().any(|x| x.0 == "cache_output");
    let mut out_values = 0;
    let mut output_saved = None;
    let mut impl_saved = None;

    for fc in start_context.with_breaks() {
        let c = fc.inner();
        if let Some((r, i)) = c.broken {
            if let BreakType::Macro(v, _) = r {
                for full_context in contexts.iter() {
                    let fn_context = full_context.inner().start_group;
                    (*full_context).inner().return_value = match v {
                        Some(v) => {
                            if save_value {
                                if out_values > 0 {
                                    return Err(RuntimeError::CustomError(create_error(
                                        info,
                                        "Cannot cache a context splitting library",
                                        &[],
                                        None,
                                    )));
                                }
                                output_saved = Some(v);
                            }
                            out_values += 1;
                            clone_value(v, globals, fn_context, true, info.position)
                        }
                        None => NULL_STORAGE,
                    };
                }
            } else {
                return Err(RuntimeError::BreakNeverUsedError {
                    breaktype: r,
                    info: CompilerInfo::from_area(i),
                    broke: i,
                    dropped: info.position,
                    reason: "the file ended".to_string(),
                });
            }
        }
    }
    (*globals).path = stored_path;

    if let Some(stored_impl) = stored_impl {
        //change and delete from impls
        let mut to_be_deleted = Vec::new();
        for (k1, imp) in &mut globals.implementations {
            for (k2, (_, in_scope)) in imp {
                if *in_scope {
                    (*in_scope) = false;
                } else {
                    to_be_deleted.push((*k1, *k2));
                }
                // globals
                //     .stored_values
                //     .increment_single_lifetime(*val, 1, &mut HashSet::new());
            }
        }
        for (k1, k2) in to_be_deleted {
            (*globals).implementations.get_mut(&k1).unwrap().remove(&k2);
        }
        if save_value {
            impl_saved = Some(globals.implementations.clone());
        }

        //merge impls
        merge_impl(&mut globals.implementations, &stored_impl);
    } else if save_value {
        impl_saved = Some(globals.implementations.clone());
    }

    if save_value {
        globals.prev_imports.insert(
            path.clone(),
            (
                output_saved.unwrap_or(NULL_STORAGE),
                impl_saved.unwrap_or_else(HashMap::new),
            ),
        );
    }

    Ok(())
}

// const ID_MAX: u16 = 999;

// pub fn next_free(
//     ids: &mut Vec<u16>,
//     id_class: ast::IDClass,
//     info: CompilerInfo,
// ) -> Result<ID, RuntimeError> {
//     for i in 1..ID_MAX {
//         if !ids.contains(&i) {
//             (*ids).push(i);
//             return Ok(i);
//         }
//     }

//     Err(RuntimeError::IDError { id_class, info })
//     //panic!("All ids of this type are used up!");
// }