wlambda 0.8.1

WLambda is an embeddable scripting language for Rust
Documentation
// Copyright (c) 2020-2022 Weird Constructor <weirdconstructor@gmail.com>
// This is a part of WLambda. See README.md and COPYING for details.

/*!

  Implements all the semantics of WLambda. It takes an AST
  that was generated by the parser and compiles it to a
  tree of closures.

  The compiler and the runtime require multiple
  other data structures, such as `GlobalEnv` with a
  `SymbolTable` for global functions,
  a `ModuleResolver` and a `ThreadCreator`.

  An to execute the closures that the compiler generated
  you need an `Env` instance (which itself requires a reference
  to the `GlobalEnv`.

  To orchestrate all this the `EvalContext` exists.
  You can customize:

  - The `GlobalEnv` with custom
  globals and even with your own core and standard library functions
  - You can provide a custom `ModuleResolver`, which loads the
  modules from other sources (eg. memory or network).
  - You can make your own `ThreadCreator`, to customize thread
  creation.

  However, to get some WLambda code running quickly there is
  `EvalContext::new_default()` which creates a ready made context for
  evaluating WLambda code:


```
use wlambda::*;

let mut ctx = EvalContext::new_default();
let res = ctx.eval("$[10, 20 + 30]").unwrap();

assert_eq!(res.s(), "$[10,50]");
```

*/

use crate::parser::{self};
use crate::prelude::*;
use crate::vval::VVal;
use crate::vval::Syntax;
use crate::vval::Env;
use crate::vval::VValFun;
use crate::vval::StackAction;
use crate::vval::CompileError;
use crate::vval::VarPos;
use crate::vval::EvalNode;
use crate::vval::SynPos;
use crate::vval::CollectionAdd;
use crate::nvec::NVec;
use crate::str_int::*;
use crate::threads::*;

use crate::pw_null;
use crate::pw;
use crate::pw_provides_result_pos;
use crate::pw_store_if_needed;
use crate::pw_needs_storage;
use crate::prog_writer::*;
use crate::ops::*;

use crate::io::debug_print_value;

use crate::selector;
use crate::struct_pattern;
use crate::formatter;

use std::rc::Rc;
use std::sync::Arc;
use std::sync::Mutex;
use std::cell::RefCell;
use std::fmt::{Display, Formatter};

use fnv::FnvHashMap;

#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
struct CompileLocal {
}

#[derive(Debug)]
/// Error for the `ModuleResolver` trait, to implement
/// custom module loading behaviour.
#[allow(dead_code)]
pub enum ModuleLoadError<'a> {
    NoSuchModule(String),
    ModuleEvalError(EvalError),
    Other(&'a str),
}

/// This trait is responsible for loading modules
/// and returning a collection of name->value mappings for a module
/// name.
///
/// See also GlobalEnv::set_resolver() about how to configure the
/// global environment to use you trait implementation.
///
/// There is a default implementation named LocalFileModuleResolver,
/// which loads the modules from files.
pub trait ModuleResolver {
    /// Resolves the path to a HashMap of names -> VVal.
    /// Where you obtain this mapping from is completely up to you.
    /// You can statically define these, load them from a JSON file,
    /// load them by executing another WLambda script or whatever you fancy.
    ///
    /// See LocalFileModuleResolver as example on how to implement this.
    fn resolve(&self, global: GlobalEnvRef, path: &[String], import_file_path: Option<&str>) -> Result<SymbolTable, ModuleLoadError>;
}

/// This structure implements the ModuleResolver trait and is
/// responsible for loading modules on `!@import` for WLambda.
#[derive(Debug, Clone, Default)]
pub struct LocalFileModuleResolver {
    loaded_modules: Rc<RefCell<std::collections::HashMap<String, Rc<SymbolTable>>>>,
}

#[allow(dead_code)]
impl LocalFileModuleResolver {
    pub fn new() -> LocalFileModuleResolver {
        LocalFileModuleResolver {
            loaded_modules:
                Rc::new(RefCell::new(std::collections::HashMap::new())),
        }
    }
}

/// Stores symbols and values for a WLambda module that can be added to a `GlobalEnv` with `set_module`.
///
///```
/// use wlambda::{SymbolTable, GlobalEnv, EvalContext, Env};
///
/// let mut st = SymbolTable::new();
///
/// let outbuf = std::rc::Rc::new(std::cell::RefCell::new(String::from("")));
///
/// let captured_outbuf = outbuf.clone();
///
/// st.fun("print", move |e: &mut Env, _argc: usize| {
///     std::mem::replace(&mut *captured_outbuf.borrow_mut(), e.arg(0).s());
///     println!("MY PRINT: {}", e.arg(0).s());
///     Ok(e.arg(0).clone())
/// }, Some(1), Some(1), false);
///
/// let global_env = GlobalEnv::new_default();
/// global_env.borrow_mut().set_module("my", st);
///
/// let mut ctx = EvalContext::new(global_env);
/// ctx.eval("!@import my my; my:print 1337");
///
/// assert_eq!(outbuf.borrow().clone(), "1337");
///```
#[derive(Default, Debug, Clone)]
pub struct SymbolTable {
    symbols: FnvHashMap<Symbol, VVal>,
}

impl SymbolTable {
    pub fn new() -> Self {
        SymbolTable {
            symbols: FnvHashMap::with_capacity_and_hasher(10, Default::default()),
        }
    }

    /// This function returns all symbols defined in this SymbolTable.
    /// It's mainly used for tests.
    #[allow(dead_code)]
    pub fn list(&self) -> std::vec::Vec<String> {
        let mut v = vec![];
        for (s, _) in self.symbols.iter() {
            v.push(s.to_string());
        }
        v
    }

    /// Sets the entry `name` to the `value`. So that the
    /// value can be imported.
    #[allow(dead_code)]
    pub fn set(&mut self, name: &str, value: VVal) {
        self.symbols.insert(s2sym(name), value);
    }

    /// Retrieves a value from the SymbolTable, if present.
    /// ```
    /// use wlambda::{VVal, EvalContext};
    /// let mut ctx = EvalContext::new_default();
    /// ctx.eval("!@export to_rust = 42.0;");
    ///
    /// assert_eq!(ctx.get_exports().get("to_rust").cloned(), Some(VVal::Flt(42.0)));
    /// ```
    #[allow(dead_code)]
    pub fn get(&self, name: &str) -> Option<&VVal> {
        self.symbols.get(&s2sym(name))
    }

    /// Helper function for building symbol tables with functions in them.
    ///
    /// See also `VValFun::new_fun` for more details.
    ///
    ///```
    /// use wlambda::VVal;
    /// let mut st = wlambda::compiler::SymbolTable::new();
    /// st.fun("nothing",
    ///        |e: &mut wlambda::vval::Env, _argc: usize| Ok(VVal::None),
    ///        None, None, false);
    ///```
    pub fn fun<T>(
        &mut self, fnname: &str, fun: T,
        min_args: Option<usize>,
        max_args: Option<usize>,
        err_arg_ok: bool)
        where T: 'static + Fn(&mut Env, usize) -> Result<VVal,StackAction> {

        self.symbols.insert(
            s2sym(fnname),
            VValFun::new_fun(fun, min_args, max_args, err_arg_ok));
    }
}

impl ModuleResolver for LocalFileModuleResolver {
    fn resolve(&self, global: GlobalEnvRef, path: &[String], import_file_path: Option<&str>)
        -> Result<SymbolTable, ModuleLoadError>
    {
        let genv = GlobalEnv::new_empty_default();
        genv.borrow_mut().set_thread_creator(
            global.borrow().get_thread_creator());
        genv.borrow_mut().import_modules_from(&*global.borrow());

        let mut ctx = EvalContext::new(genv);
        let pth = format!("{}.wl", path.join("/"));

        if let Some(st) = self.loaded_modules.borrow().get(&pth) {
            return Ok((**st).clone());
        }

        let mut check_paths = vec![pth.clone()];

        if let Some(ifp) = import_file_path {
            let impfp = std::path::Path::new(ifp);

            let import_dir_path =
                if impfp.is_file() {
                    impfp.parent()
                } else {
                    Some(impfp)
                };

            if let Some(idp) = import_dir_path {
                let mut pb = idp.to_path_buf();
                for p in path { pb.push(p); }

                if let Some(p) = pb.as_path().to_str() {
                    check_paths.push(format!("{}.wl", p));
                }
            }
        }

        for p in check_paths.iter() {
            if std::path::Path::new(p).exists() {
                return match ctx.eval_file(p) {
                    Err(e) => Err(ModuleLoadError::ModuleEvalError(e)),
                    Ok(_v) => {
                        let exports = Rc::new(ctx.get_exports());
                        self.loaded_modules
                            .borrow_mut()
                            .insert(pth, exports);
                        Ok(ctx.get_exports())
                    },
                }
            }
        }

        Err(ModuleLoadError::NoSuchModule(check_paths.join(";")))
    }
}

/// Holds global environment variables.
///
/// This data structure is part of the API. It's there
/// to make functions or values available to a WLambda program.
///
/// This environment structure is usually wrapped inside an [EvalContext](struct.EvalContext.html)
/// which augments it for calling the compiler and allows evaluation
/// of the code.
///
/// **See also:**
/// - [GlobalEnv::add_func()](#method.add_func) of how to create a function and put it
/// into the global variable.
/// - And [GlobalEnv::set_module()](#method.set_module) of how to supply your own importable
/// modules. See also [SymbolTable](struct.SymbolTable.html) has a good example how that could work.
/// - And [GlobalEnv::set_var()](#method.set_var).
/// - And [GlobalEnv::get_var()](#method.get_var).
#[derive(Clone)]
pub struct GlobalEnv {
    env: std::collections::HashMap<String, VVal>,
    mem_modules:
        std::rc::Rc<std::cell::RefCell<std::collections::HashMap<String, SymbolTable>>>,

    /// Holds the default module resolver for this global environment.
    /// If some code executed with this global environment uses `!@import`
    /// this resolver will be used to resolve the given module name.
    pub resolver: Option<Rc<RefCell<dyn ModuleResolver>>>,
    /// Holds the default thread creator for this global environment.
    /// If some code executed with this global environment spawns a thread,
    /// then this thread creator will create the thread handle, which is passed
    /// back to WLambda.
    pub thread_creator: Option<Arc<Mutex<dyn ThreadCreator>>>,
}

impl std::fmt::Debug for GlobalEnv {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "<<GlobalEnv>>")
    }
}

/// Reference type of `GlobalEnv`.
pub type GlobalEnvRef = Rc<RefCell<GlobalEnv>>;

impl GlobalEnv {
    /// Adds a function to a `GlobalEnv`.
    ///
    /// This is an example of how to add a function:
    ///
    /// ```
    /// use wlambda::compiler::GlobalEnv;
    /// use wlambda::vval::{Env, VVal};
    ///
    /// let g = GlobalEnv::new();
    /// g.borrow_mut().add_func(
    ///     "push",
    ///     |env: &mut Env, argc: usize| {
    ///         if argc < 2 { return Ok(VVal::None); }
    ///         let v = env.arg(0);
    ///         v.push(env.arg(1).clone());
    ///         Ok(v.clone())
    ///     }, Some(2), Some(2));
    /// ```
    pub fn add_func<T>(&mut self, fnname: &str, fun: T, min_args: Option<usize>, max_args: Option<usize>)
        where T: 'static + Fn(&mut Env, usize) -> Result<VVal,StackAction> {
        self.env.insert(
            String::from(fnname),
            VValFun::new_fun(fun, min_args, max_args, false));
    }

    /// Sets a global variable to a value.
    ///
    /// See also [EvalContext::set_global_var()](struct.EvalContext.html#method.set_global_var)
    #[allow(dead_code)]
    pub fn set_var(&mut self, var: &str, val: &VVal) {
        match self.env.get(var) {
            Some(v) => { v.set_ref(val.clone()); }
            None    => { self.env.insert(String::from(var), val.to_ref()); }
        }
    }

    /// Returns the value of a global variable.
    ///
    /// See also [EvalContext::get_global_var()](struct.EvalContext.html#method.get_global_var)
    #[allow(dead_code)]
    pub fn get_var(&self, var: &str) -> Option<VVal> {
        self.env.get(var).map(|v| v.deref())
    }

    /// Returns the reference to the value of a global variable.
    /// This does not dereference the VVal::Ref that usually holds the
    /// global variable.
    ///
    /// See also [EvalContext::get_global_var()](struct.EvalContext.html#method.get_global_var)
    #[allow(dead_code)]
    pub fn get_var_ref(&self, var: &str) -> Option<VVal> {
        self.env.get(var).cloned()
    }

    /// Sets a symbol table for a module before a module asks for it.
    /// Modules set via this function have precedence over resolved modules
    /// via set_resolver().
    ///
    /// Here is an example how to setup your own module:
    ///```
    /// use wlambda::{VVal, EvalContext, GlobalEnv, SymbolTable};
    ///
    /// let my_mod = SymbolTable::new();
    ///
    /// let genv = GlobalEnv::new_default();
    /// genv.borrow_mut().set_module("test", my_mod);
    ///```
    #[allow(dead_code)]
    pub fn set_module(&mut self, mod_name: &str, symtbl: SymbolTable) {
        self.mem_modules.borrow_mut().insert(mod_name.to_string(), symtbl);
    }

    /// Imports all symbols from the designated module with the specified
    /// prefix applied. This does not call out to the resolver and
    /// only works on previously `set_module` modules.
    /// Returns true if the module was found.
    pub fn import_module_as(&mut self, mod_name: &str, prefix: &str) -> bool {
        let prefix =
            if mod_name == "wlambda" && prefix == "wlambda" { String::from("") }
            else if !prefix.is_empty() { prefix.to_string() + ":" }
            else { String::from("") };

        if let Some(st) = self.mem_modules.borrow_mut().get(mod_name) {
            for (k, v) in &st.symbols {
                self.env.insert(prefix.clone() + k, v.clone());
            }
            true
        } else {
            false
        }
    }

    /// Sets the module resolver. There is a LocalFileModuleResolver available
    /// which loads the modules relative to the current working directory.
    ///
    /// Please note that modules made available using `set_module` have priority
    /// over modules that are provided by the resolver.
    ///
    ///```
    /// use std::rc::Rc;
    /// use std::cell::RefCell;
    ///
    /// let global = wlambda::compiler::GlobalEnv::new_default();
    ///
    /// let lfmr = Rc::new(RefCell::new(
    ///     wlambda::compiler::LocalFileModuleResolver::new()));
    ///
    /// global.borrow_mut().set_resolver(lfmr);
    ///```
    pub fn set_resolver(&mut self, res: Rc<RefCell<dyn ModuleResolver>>) {
        self.resolver = Some(res.clone());
    }

    /// Creates a new completely empty GlobalEnv.
    ///
    /// There is no core language, no std lib. You have
    /// to add all that on your own via `set_var` and `set_module`.
    pub fn new() -> GlobalEnvRef {
        Rc::new(RefCell::new(GlobalEnv {
            env: std::collections::HashMap::new(),
            mem_modules:
                std::rc::Rc::new(std::cell::RefCell::new(
                    std::collections::HashMap::new())),
            resolver: None,
            thread_creator: None,
        }))
    }

    /// Returns a default global environment. Where default means what
    /// the author of WLambda decided what is default at the moment.
    /// For more precise global environment, that is not a completely
    /// moving target consider the alternate constructor variants.
    ///
    /// Global environments constructed with this typically contain:
    ///
    /// - `set_module("wlambda", wlambda::prelude::core_symbol_table())`
    /// - `set_module("std",     wlambda::prelude::std_symbol_table())`
    /// - `set_resolver(Rc::new(RefCell::new(wlambda::compiler::LocalFileModuleResolver::new()))`
    /// - `set_thread_creator(Some(Arc::new(Mutex::new(DefaultThreadCreator::new()))))`
    /// - `import_module_as("wlambda", "")`
    /// - `import_module_as("std", "std")`
    ///
    /// On top of that, the `WLambda` module has been imported without a prefix
    /// and the `std` module has been loaded with an `std:` prefix.
    /// This means you can load and eval scripts you see all over this documentation.
    pub fn new_default() -> GlobalEnvRef {
        let g = Self::new_empty_default();
        g.borrow_mut().import_module_as("wlambda", "");
        g.borrow_mut().import_module_as("std",     "std");
        g
    }

    /// This function adds the modules that were loaded into memory
    /// from the given `parent_global_env` to the current environment.
    pub fn import_modules_from(&mut self, parent_global_env: &GlobalEnv) {
        if parent_global_env.resolver.is_some() {
            self.set_resolver(
                parent_global_env.resolver.as_ref().unwrap().clone());
        }

        for (mod_name, symtbl) in parent_global_env.mem_modules.borrow().iter() {
            self.set_module(mod_name, symtbl.clone());
        }
    }

    /// Imports all functions from the given SymbolTable with the
    /// given prefix to the GlobalEnv.
    pub fn import_from_symtbl(&mut self, prefix: &str, symtbl: SymbolTable) {
        for (k, v) in symtbl.symbols {
            self.env.insert(prefix.to_string() + &k, v.clone());
        }
    }

    /// This is like `new_default` but does not import anything, neither the
    /// core language nor the std module.
    pub fn new_empty_default() -> GlobalEnvRef {
        let g = GlobalEnv::new();
        g.borrow_mut().set_module("wlambda", core_symbol_table());
        g.borrow_mut().set_module("std",     std_symbol_table());
        g.borrow_mut().set_thread_creator(
            Some(Arc::new(Mutex::new(
                DefaultThreadCreator::new(
                    FunctionGlobalEnvCreator::from(
                        Box::new(GlobalEnv::new_default)))))));
        g.borrow_mut().set_resolver(
            Rc::new(RefCell::new(LocalFileModuleResolver::new())));
        g.borrow_mut().set_var("\\", &VVal::None);
        g
    }

    /// Assigns a new thread creator to this GlobalEnv.
    /// It will be used to spawn new threads if `std:thread:spawn` from
    /// WLambda's standard library is called.
    pub fn set_thread_creator(&mut self,
        tc: Option<Arc<Mutex<dyn ThreadCreator>>>)
    {
        self.thread_creator = tc.clone();
    }

    /// Returns the thread creator for this GlobalEnv if one is set.
    pub fn get_thread_creator(&self)
        -> Option<Arc<Mutex<dyn ThreadCreator>>>
    {
        self.thread_creator.clone()
    }
}

#[derive(Debug)]
/// Errors that can occur when evaluating a piece of WLambda code.
/// Usually created by methods of `EvalContext`.
pub enum EvalError {
    /// Errors regarding file I/O when parsing files
    IOError(String, std::io::Error),
    /// Syntax errors
    ParseError(String),
    /// Grammar/Compilation time errors
    CompileError(CompileError),
    /// Special kinds of runtime errors
    ExecError(StackAction),
}

impl Display for EvalError {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        match self {
            EvalError::IOError(file, e) => write!(f, "IO error: file '{}': {} ", file, e),
            EvalError::ParseError(e)    => write!(f, "Parse error: {}", e),
            EvalError::CompileError(e)  => write!(f, "Compile error: {}", e),
            EvalError::ExecError(s)     => write!(f, "Runtime error: {}", s),
        }
    }
}

/// This context holds all the data to compile and execute a piece of WLambda code.
/// You can either use the default environment, or customize the EvalContext
/// to your application's needs. You can provide custom:
///
/// - Global environemnt
/// - Module resolver
/// - Custom preset modules
/// - Thread creator
///
/// It can be this easy to create a context:
///
///```
/// let mut ctx = wlambda::EvalContext::new_default();
/// let ret = ctx.eval("10 + 20").unwrap().i();
///
/// assert_eq!(ret, 30);
///
/// // Also works beyond differnt eval() calls:
/// ctx.eval("!:global X = 10").unwrap();
///
/// let ret = ctx.eval("X").unwrap().i();
/// assert_eq!(ret, 10);
///
/// // You can access the global environment later too:
/// assert_eq!(ctx.get_global_var("X").unwrap().i(),
///            10);
///
/// // You can even store top level local variables beyond one eval():
/// ctx.eval("!toplevel_var = { _ + 20 }").unwrap();
/// let ret = ctx.eval("toplevel_var 11").unwrap().i();
///
/// assert_eq!(ret, 31);
///```
///
/// You can also explicitly setup a global environment:
///
///```
/// use wlambda::{GlobalEnv, EvalContext, VVal};
///
/// let genv = GlobalEnv::new_default();
///
/// genv.borrow_mut().set_var("xyz", &VVal::Int(31347));
///
/// let mut ctx = EvalContext::new(genv);
/// let ret = ctx.eval("xyz - 10").unwrap().i();
///
/// assert_eq!(ret, 31337);
///```
#[derive(Debug, Clone)]
pub struct EvalContext {
    /// Holds the reference to the supplied or internally created
    /// GlobalEnv.
    pub global:         GlobalEnvRef,
    local_compile:      Rc<RefCell<CompileEnv>>,
    user:               Rc<RefCell<dyn std::any::Any>>,
    /// Holds the top level environment data accross multiple eval()
    /// invocations.
    pub local:          Rc<RefCell<Env>>,
}

impl EvalContext {
    /// Creates an EvalContext from a given GlobalEnv.
    ///
    ///```
    /// use wlambda::{GlobalEnv, EvalContext, VVal};
    ///
    /// let genv = GlobalEnv::new_default();
    /// let ctx = EvalContext::new(genv);
    ///```
    pub fn new(global: GlobalEnvRef) -> EvalContext {
        (Self::new_with_user_impl(global, Rc::new(RefCell::new(VVal::vec()))))
        .register_self_eval()
    }

    /// Creates a new EvalContext with an empty GlobalEnv.
    /// This means the EvalContext has none of the core or std library
    /// functions in it's global environment and you have to provide
    /// them yourself. Either by loading them from WLambda by `!@import std`
    /// or `!@wlambda`. Or by manually binding in the corresponding
    /// SymbolTable(s).
    ///
    ///```rust
    /// use wlambda::compiler::EvalContext;
    /// use wlambda::vval::{VVal, VValFun, Env};
    ///
    /// let mut ctx = EvalContext::new_empty_global_env();
    ///
    /// {
    ///     let mut genv = ctx.global.borrow_mut();
    ///     genv.set_module("wlambda", wlambda::prelude::core_symbol_table());
    ///     genv.import_module_as("wlambda", "");
    /// }
    ///
    /// assert_eq!(ctx.eval("type $true").unwrap().s_raw(), "bool")
    ///```
    #[allow(dead_code)]
    pub fn new_empty_global_env() -> EvalContext {
        Self::new_with_user_impl(
            GlobalEnv::new(),
            Rc::new(RefCell::new(VVal::vec())))
    }

    /// A shortcut: This creates a new EvalContext with a GlobalEnv::new_default()
    /// global environment.
    ///
    /// This is a shorthand for:
    ///```
    /// wlambda::compiler::EvalContext::new(
    ///     wlambda::compiler::GlobalEnv::new_default());
    ///```
    #[allow(dead_code)]
    pub fn new_default() -> EvalContext {
        Self::new(GlobalEnv::new_default())
    }

    fn register_self_eval(self) -> Self {
        let ctx_clone =
            Self::new_with_user_impl(
                self.global.clone(),
                self.local.borrow().get_user());

        self.global.borrow_mut().add_func("std:eval", move |env: &mut Env, _argc: usize| {
            let code    = env.arg(0).s_raw();
            let ctx     = ctx_clone.clone();
            let mut ctx = ctx.register_self_eval();
            match ctx.eval(&code) {
                Ok(v)  => Ok(v),
                Err(e) => Ok(env.new_err(format!("{}", e))),
            }
        }, Some(1), Some(2));

        self
    }

    /// Returns a SymbolTable of the locally exported symbols.
    /// This is useful, if you want to execute WLambda code yourself
    /// and use the local environment to export symbols.
    ///
    ///```
    /// use wlambda::{EvalContext};
    ///
    /// let mut ctx = EvalContext::new_default();
    ///
    /// ctx.eval("!@export foo = 10").unwrap();
    ///
    /// let mut sym_tbl = ctx.get_exports();
    /// assert_eq!(sym_tbl.get("foo").unwrap().i(), 10);
    ///```
    pub fn get_exports(&self) -> SymbolTable {
        SymbolTable { symbols: self.local.borrow_mut().exports.clone() }
    }

    #[allow(dead_code)]
    fn new_with_user_impl(
        global: GlobalEnvRef, user: Rc<RefCell<dyn std::any::Any>>) -> EvalContext {

        EvalContext {
            global: global.clone(),
            user:   user.clone(),
            local_compile: Rc::new(RefCell::new(CompileEnv {
                parent:         None,
                global:         global.clone(),
                block_env:      BlockEnv::new(),
                upvals:         Vec::new(),
                locals_space:   0,
                recent_var:     String::new(),
                recent_sym:     String::new(),
                implicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
                explicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
                quote_func:     false,
            })),
            local: Rc::new(RefCell::new(Env::new_with_user(global, user))),
        }
    }

    #[allow(dead_code)]
    pub fn new_with_user(global: GlobalEnvRef, user: Rc<RefCell<dyn std::any::Any>>) -> EvalContext {
        (Self::new_with_user_impl(global, user)).register_self_eval()
    }

    /// Evaluates an AST of WLambda code and executes it with the given `EvalContext`.
    ///
    /// ```
    /// use wlambda::parser;
    /// let mut ctx = wlambda::EvalContext::new_default();
    ///
    /// let s = "$[1,2,3]";
    /// let ast = parser::parse(s, "somefilename").unwrap();
    /// let r = &mut ctx.eval_ast(&ast).unwrap();
    ///
    /// println!("Res: {}", r.s());
    /// ```
    pub fn eval_ast(&mut self, ast: &VVal) -> Result<VVal, EvalError>  {
        let prog = compile_vm_fun(ast, &mut self.local_compile);
        let locals_size = self.local_compile.borrow().get_local_space();

        let mut temporary_env = None;

        let env =
            match self.local.try_borrow_mut() {
                Ok(env) => env,
                Err(_) => {
                    temporary_env =
                        Some(Rc::new(RefCell::new(
                            Env::new_with_user(
                                self.global.clone(), self.user.clone()))));
                    temporary_env.as_ref().unwrap().borrow_mut()
                },
            };

        let mut res = Ok(VVal::None);

        std::cell::RefMut::map(env, |l_env| {
            res = match prog {
                Ok(prog_closures) => {
                    l_env.sp = 0;
                    l_env.set_bp(locals_size);
                    match l_env.with_restore_sp(|e: &mut Env| { prog_closures(e) }) {
                        Ok(v)   => Ok(v),
                        Err(je) => Err(EvalError::ExecError(je)),
                    }
                },
                Err(e) => Err(EvalError::CompileError(e)),
            };
            l_env
        });

        if let Some(te) = temporary_env {
            self.local = te;
        }

        res
    }

    /// Evaluates a WLambda code in a file with the given `EvalContext`.
    ///
    /// ```
    /// let mut ctx = wlambda::EvalContext::new_default();
    ///
    /// let r = &mut ctx.eval_file("examples/read_test.wl").unwrap();
    /// assert_eq!(r.i(), 403, "matches contents!");
    /// ```
    #[allow(dead_code)]
    pub fn eval_file(&mut self, filename: &str) -> Result<VVal, EvalError> {
        let contents = std::fs::read_to_string(filename);
        if let Err(err) = contents {
            Err(EvalError::IOError(filename.to_string(), err))
        } else {
            let contents = contents.unwrap();
            match parser::parse(&contents, filename) {
                Ok(ast) => self.eval_ast(&ast),
                Err(e)  => Err(EvalError::ParseError(e)),
            }
        }
    }

    /// Evaluates a piece of WLambda code with the given `EvalContext`.
    ///
    /// ```
    /// let mut ctx = wlambda::EvalContext::new_default();
    ///
    /// let r = &mut ctx.eval("$[1,2,3]").unwrap();
    /// println!("Res: {}", r.s());
    /// ```
    #[allow(dead_code)]
    pub fn eval(&mut self, s: &str) -> Result<VVal, EvalError>  {
        match parser::parse(s, "<wlambda::eval>") {
            Ok(ast) => self.eval_ast(&ast),
            Err(e)  => Err(EvalError::ParseError(e)),
        }
    }

    /// Evaluates a WLambda code with the corresponding filename
    /// in the given `EvalContext`.
    ///
    /// ```
    /// let mut ctx = wlambda::EvalContext::new_default();
    ///
    /// let r = &mut ctx.eval_string("403", "examples/read_test.wl").unwrap();
    /// assert_eq!(r.i(), 403, "matches contents!");
    /// ```
    #[allow(dead_code)]
    pub fn eval_string(&mut self, code: &str, filename: &str)
        -> Result<VVal, EvalError>
    {
        match parser::parse(code, filename) {
            Ok(ast) => self.eval_ast(&ast),
            Err(e)  => Err(EvalError::ParseError(e)),
        }
    }

    /// Calls a wlambda function with the given `EvalContext`.
    ///
    /// ```
    /// use wlambda::{VVal, EvalContext};
    /// let mut ctx = EvalContext::new_default();
    ///
    /// let returned_func = &mut ctx.eval("{ _ + _1 }").unwrap();
    /// assert_eq!(
    ///     ctx.call(returned_func,
    ///              &vec![VVal::Int(10), VVal::Int(11)]).unwrap().i(),
    ///     21);
    /// ```
    #[allow(dead_code)]
    pub fn call(&mut self, f: &VVal, args: &[VVal]) -> Result<VVal, StackAction>  {
        let mut env = self.local.borrow_mut();
        f.call(&mut env, args)
    }

    /// Sets a global variable for the scripts to access.
    ///
    /// ```
    /// use wlambda::{VVal, EvalContext};
    /// let mut ctx = EvalContext::new_default();
    ///
    /// ctx.set_global_var("XXX", &VVal::Int(200));
    ///
    /// assert_eq!(ctx.eval("XXX * 2").unwrap().i(), 400);
    /// ```
    #[allow(dead_code)]
    pub fn set_global_var(&mut self, var: &str, val: &VVal) {
        self.global.borrow_mut().set_var(var, val);
    }

    /// Gets the value of a global variable from the script:
    ///
    /// ```
    /// use wlambda::{VVal, EvalContext};
    /// let mut ctx = EvalContext::new_default();
    ///
    /// ctx.eval("!:global XXX = 22 * 2;");
    ///
    /// assert_eq!(ctx.get_global_var("XXX").unwrap().i(), 44);
    /// ```
    #[allow(dead_code)]
    pub fn get_global_var(&mut self, var: &str) -> Option<VVal> {
        self.global.borrow_mut().get_var(var)
    }
}

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ArityParam {
    Undefined,
    Limit(usize),
    Infinite,
}

#[derive(Debug, Clone)]
#[allow(clippy::box_collection)]
struct BlockEnv {
    local_map_stack: std::vec::Vec<(usize, Box<std::collections::HashMap<String, VarPos>>)>,
    locals:          std::vec::Vec<(String, CompileLocal)>,
}

#[derive(Debug,Clone,Copy,PartialEq)]
pub(crate) enum ResValue {
    None,
    OptNone,
    Ret,
    AccumVal,
    AccumFun,
    SelfObj,
    SelfData,
}

#[derive(Debug,Clone,Copy,PartialEq)]
pub(crate) enum ResPos {
    Local(u16),
    LocalRef(u16),
    Arg(u16),
    Up(u16),
    UpRef(u16),
    Global(u16),
    GlobalRef(u16),
    Data(u16),
    Stack(u16),
    Value(ResValue),
}

impl BlockEnv {
    fn new() -> Self {
        Self {
            local_map_stack: vec![(0, Box::new(std::collections::HashMap::new()))],
            locals:          vec![],
        }
    }

    #[allow(dead_code)]
    fn env_size(&self) -> usize {
        self.locals.len()
    }

    fn push_env(&mut self) {
        self.local_map_stack.push((0, Box::new(std::collections::HashMap::new())));
    }

    fn pop_env(&mut self) -> (usize, usize) {
        let block_env_vars = self.local_map_stack.pop().unwrap();

        let local_count = block_env_vars.0;
        for _ in 0..local_count {
            self.locals.pop();
        }

        (self.locals.len(), self.locals.len() + local_count)
    }

    fn set_upvalue(&mut self, var: &str, idx: usize) -> VarPos {
        let last_idx = self.local_map_stack.len() - 1;
        self.local_map_stack[last_idx].1
            .insert(String::from(var), VarPos::UpValue(idx));
        VarPos::UpValue(idx)
    }

    fn next_local(&mut self) -> usize {
        let next_index = self.locals.len();
        self.locals.push((String::from(""), CompileLocal { }));
        next_index
    }

    fn find_local_in_current_block(&self, var: &str) -> Option<usize> {
        let last_idx = self.local_map_stack.len() - 1;
        let v = self.local_map_stack[last_idx].1.get(var)?;
        if let VarPos::Local(idx) = v {
            Some(*idx)
        } else {
            None
        }
    }

    fn def_local(&mut self, var: &str, idx: usize) {
        self.locals[idx].0 = String::from(var);
        let last_idx = self.local_map_stack.len() - 1;
        self.local_map_stack[last_idx].1
            .insert(String::from(var),
                    VarPos::Local(idx));
        self.local_map_stack[last_idx].0 += 1;
    }

    fn get(&self, var: &str) -> VarPos {
        for (_locals, map) in self.local_map_stack.iter().rev() {
            if let Some(pos) = map.get(var) {
                return pos.clone();
            }
        }

        VarPos::NoPos
    }
}

/// Compile time environment for allocating and
/// storing variables inside a function scope.
///
/// Also handles upvalues. Upvalues in WLambda are copied to every
/// scope that they are passed through until they are needed.
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub(crate) struct CompileEnv {
    /// Reference to the global environment
    pub global:    GlobalEnvRef,
    /// Reference to the environment of the _parent_ function.
    parent:    Option<Rc<RefCell<CompileEnv>>>,
    /// Holds all function local variables and manages nesting of blocks.
    block_env: BlockEnv,
    /// Holds the maximum number of locals ever used:
    locals_space: usize,
    /// Stores position of the upvalues for copying the upvalues at runtime.
    upvals:    std::vec::Vec<VarPos>,
    /// Stores the implicitly calculated arity of this function.
    pub implicit_arity: (ArityParam, ArityParam),
    /// Stores the explicitly defined arity of this function.
    pub explicit_arity: (ArityParam, ArityParam),
    /// Recently accessed variable name:
    pub recent_var: String,
    /// Recently compiled symbol:
    pub recent_sym: String,
    /// If set, the next compile() is compiling a function as block.
    pub quote_func: bool,
}

/// Reference type to a `CompileEnv`.
type CompileEnvRef = Rc<RefCell<CompileEnv>>;

impl CompileEnv {
    /// Creates a new compilation environment that references symbols in the
    /// given [GlobalEnv]. 
    pub fn new(g: GlobalEnvRef) -> Rc<RefCell<Self>> {
        Rc::new(RefCell::new(CompileEnv {
            parent:         None,
            global:         g,
            block_env:      BlockEnv::new(),
            upvals:         Vec::new(),
            locals_space:   0,
            quote_func:     false,
            recent_var:     String::new(),
            recent_sym:     String::new(),
            implicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
            explicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
        }))
    }

    pub fn create_env(parent: Option<CompileEnvRef>) -> Rc<RefCell<CompileEnv>> {
        let global = if let Some(p) = &parent {
            p.borrow_mut().global.clone()
        } else {
            GlobalEnv::new()
        };
        Rc::new(RefCell::new(CompileEnv {
            parent,
            global,
            block_env:      BlockEnv::new(),
            upvals:         Vec::new(),
            locals_space:   0,
            recent_var:     String::new(),
            recent_sym:     String::new(),
            implicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
            explicit_arity: (ArityParam::Undefined, ArityParam::Undefined),
            quote_func:     false,
        }))
    }

    fn def_up(&mut self, s: &str, parent_local_var: VarPos) -> VarPos {
        let next_index = self.upvals.len();
        self.upvals.push(parent_local_var);
        self.block_env.set_upvalue(s, next_index)
    }

    pub fn def_const(&mut self, s: &str, val: VVal) {
        self.global.borrow_mut().env.insert(String::from(s), val);
    }

    pub fn def_local(&mut self, s: &str, idx: usize) {
        self.block_env.def_local(s, idx);
    }

    pub fn find_or_new_local(&mut self, var: &str) -> usize {
        let idx =
            if let Some(idx) = self.block_env.find_local_in_current_block(var) {
                idx
            } else {
                self.block_env.next_local()
            };
        if (idx + 1) > self.locals_space {
            self.locals_space = idx + 1;
        }
        idx
    }

    pub fn next_local(&mut self) -> usize {
        let idx = self.block_env.next_local();
        if (idx + 1) > self.locals_space {
            self.locals_space = idx + 1;
        }
        idx
    }

    pub fn get_local_space(&self) -> usize { self.locals_space }

    pub fn def(&mut self, s: &str, is_global: bool) -> VarPos {
        if is_global {
            let v = VVal::None;
            let r = v.to_ref();
            self.global.borrow_mut().env.insert(String::from(s), r.clone());
            VarPos::Global(r)
        } else {
            let idx = self.find_or_new_local(s);
            self.block_env.def_local(s, idx);
            VarPos::Local(idx)
        }
    }

    pub fn get_upval_pos(&self) -> std::vec::Vec<VarPos> {
        let mut poses = vec![];
        for p in self.upvals.iter() {
            match p {
                VarPos::UpValue(_) => poses.push(p.clone()),
                VarPos::Local(_)   => poses.push(p.clone()),
                VarPos::Global(_) => {
                    panic!("Globals can't be captured as upvalues!");
                },
                VarPos::Const(_) => {
                    panic!("Consts can't be captured as upvalues!");
                },
                VarPos::NoPos => poses.push(p.clone()),
            }
        }
        poses
    }

    #[allow(dead_code)]
    pub fn local_env_size(&self) -> usize {
        self.block_env.env_size()
    }

    pub fn push_block_env(&mut self) {
        self.block_env.push_env();
    }

    pub fn pop_block_env(&mut self) -> (usize, usize) {
        self.block_env.pop_env()
    }

    pub fn get(&mut self, s: &str) -> VarPos {
        let pos = self.block_env.get(s);
        match pos {
            VarPos::NoPos => {
                let opt_p = self.parent.as_mut();
                if opt_p.is_none() {
                    if let Some(v) = self.global.borrow().env.get(s){
                        if v.is_ref() {
                            return VarPos::Global(v.clone());
                        } else {
                            return VarPos::Const(v.clone());
                        }
                    } else {
                        return VarPos::NoPos;
                    }
                }
                let parent = opt_p.unwrap().clone();
                let mut par_mut = parent.borrow_mut();

                let par_var_pos = par_mut.block_env.get(s);
                let par_var_pos = match par_var_pos {
                    VarPos::NoPos => par_mut.get(s),
                    _             => par_var_pos,
                };
                match par_var_pos {
                    VarPos::Local(_)   => self.def_up(s, par_var_pos),
                    VarPos::UpValue(_) => self.def_up(s, par_var_pos),
                    VarPos::Global(g)  => VarPos::Global(g),
                    VarPos::Const(c)   => VarPos::Const(c),
                    VarPos::NoPos      => VarPos::NoPos
                }
            }
            _ => pos,
        }
    }
}

fn set_impl_arity(i: usize, ce: &mut Rc<RefCell<CompileEnv>>) {
    let min = ce.borrow().implicit_arity.0.clone();
    match min {
        ArityParam::Undefined => { ce.borrow_mut().implicit_arity.0 = ArityParam::Limit(i); },
        ArityParam::Limit(j) => { if j < i { ce.borrow_mut().implicit_arity.0 = ArityParam::Limit(i); }; },
        _ => (),
    }

    let max = ce.borrow_mut().implicit_arity.1.clone();
    match max {
        ArityParam::Undefined => {
            ce.borrow_mut().implicit_arity.1 = ArityParam::Limit(i);
        },
        ArityParam::Limit(j) => {
            if j < i { ce.borrow_mut().implicit_arity.1 = ArityParam::Limit(i); }
        },
        _ => (),
    }
}

fn check_for_at_arity(prev_arity: (ArityParam, ArityParam), ast: &VVal,
                          ce: &mut Rc<RefCell<CompileEnv>>, vars: &VVal)
{
    // If we have an destructuring assignment directly from "@", then we conclude
    // the implicit max arity to be minimum of number of vars:
    if ast.at(2).unwrap_or(VVal::None)
          .at(0).unwrap_or(VVal::None).get_syn() == Syntax::Var {

        if let VVal::Lst(l) = vars {
            let llen = l.borrow().len();

            let var = ast.at(2).unwrap().at(1).unwrap();
            if var.with_s_ref(|var: &str| var == "@") {
                ce.borrow_mut().implicit_arity = prev_arity;
                set_impl_arity(llen, ce);
            }
        }
    }
}

fn fetch_object_key_access(ast: &VVal) -> Option<(Syntax, VVal, VVal)> {
    let syn = ast.v_(0).get_syn();
    match syn {
        Syntax::GetKey => {
            Some((Syntax::GetKey, ast.v_(1), ast.v_(2)))
        },
        Syntax::GetKey2 => {
            let get_obj = ast.shallow_clone();
            get_obj.set_syn_at(0, Syntax::GetKey);
            let key = get_obj.pop();
            Some((Syntax::GetKey, get_obj, key))
        },
        Syntax::GetKey3 => {
            let get_obj = ast.shallow_clone();
            get_obj.set_syn_at(0, Syntax::GetKey2);
            let key = get_obj.pop();
            Some((Syntax::GetKey, get_obj, key))
        },
        Syntax::GetSym => {
            Some((Syntax::GetSym, ast.v_(1), ast.v_(2)))
        },
        Syntax::GetSym2 => {
            let get_obj = ast.shallow_clone();
            get_obj.set_syn_at(0, Syntax::GetSym);
            let key = get_obj.pop();
            Some((Syntax::GetSym, get_obj, key))
        },
        Syntax::GetSym3 => {
            let get_obj = ast.shallow_clone();
            get_obj.set_syn_at(0, Syntax::GetSym2);
            let key = get_obj.pop();
            Some((Syntax::GetSym, get_obj, key))
        },
        _ => None,
    }
}

pub(crate) fn copy_upvs(upvs: &[VarPos], e: &mut Env, upvalues: &mut std::vec::Vec<VVal>) {
    for u in upvs.iter() {
        match u {
            VarPos::UpValue(i) => upvalues.push(e.get_up_raw(*i)),
            VarPos::Local(i)   => upvalues.push(e.get_local_up_promotion(*i)),
            VarPos::NoPos      => upvalues.push(VVal::None.to_ref()),
            VarPos::Global(_)  => (),
            VarPos::Const(_)   => (),
        }
    }
}

fn compile_def(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>, is_global: bool) -> Result<ProgWriter, CompileError> {
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    let prev_max_arity = ce.borrow().implicit_arity.clone();

    let vars    = ast.at(1).unwrap();
    let value   = ast.at(2).unwrap();
    let destr   = ast.at(3).unwrap_or(VVal::None);

    //d// println!("COMP DEF: {:?} global={}, destr={}", vars, is_global, destr.b());

    if destr.b() {
        let val_pw = compile(&value, ce)?;

        check_for_at_arity(prev_max_arity, ast, ce, &vars);

        let poses =
            vars.map_ok_skip(
                |v| ce.borrow_mut().def(&v.s_raw(), is_global),
                0);

        pw_null!(prog, {
            let vp = val_pw.eval(prog);
            prog.op_destr(&spos, vp, DestructureInfo {
                vars:   vars.clone(),
                poses:  poses.clone(),
                is_ref: false,
            });
        })
    } else {
        let varname = vars.at(0).unwrap().s_raw();
        ce.borrow_mut().recent_var = varname.clone();

        if is_global {
            let val_pw = compile(&value, ce)?;

            if let VarPos::Global(r) = ce.borrow_mut().def(&varname, true) {
                pw_null!(prog, {
                    let gp = prog.global_pos(r.clone());
                    val_pw.eval_to(prog, gp);
                })
            } else {
                panic!("Defining global did not return a global!");
            }
        } else {
            let next_local = ce.borrow_mut().find_or_new_local(&varname);
            let val_pw = compile(&value, ce)?;
            ce.borrow_mut().def_local(&varname, next_local);

            pw_null!(prog, {
                val_pw.eval_to(prog, ResPos::Local(next_local as u16));
            })
        }
    }
}

pub(crate) fn pw_arg(arg_idx: usize, to_ref: bool) -> Result<ProgWriter, CompileError> {
    let arg_pos = ResPos::Arg(arg_idx as u16);

    if to_ref {
        pw!(prog, store, {
            store.if_must_store(|store_pos| {
                prog.op_mov(&SynPos::empty(), arg_pos, store_pos);
                prog.op_to_ref(&SynPos::empty(), store_pos, store_pos, ToRefType::CaptureRef);
            })
        })
    } else {
        pw_provides_result_pos!(prog, {
            arg_pos
        })
    }
}

fn compile_var(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>, capt_ref: bool) -> Result<ProgWriter, CompileError> {
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    let var = ast.at(1).unwrap();
    var.with_s_ref(|var_s: &str| -> Result<ProgWriter, CompileError> {
        match var_s {
            "_"  => { set_impl_arity(1,  ce); pw_arg(0, capt_ref) },
            "_1" => { set_impl_arity(2,  ce); pw_arg(1, capt_ref) },
            "_2" => { set_impl_arity(3,  ce); pw_arg(2, capt_ref) },
            "_3" => { set_impl_arity(4,  ce); pw_arg(3, capt_ref) },
            "_4" => { set_impl_arity(5,  ce); pw_arg(4, capt_ref) },
            "_5" => { set_impl_arity(6,  ce); pw_arg(5, capt_ref) },
            "_6" => { set_impl_arity(7,  ce); pw_arg(6, capt_ref) },
            "_7" => { set_impl_arity(8,  ce); pw_arg(7, capt_ref) },
            "_8" => { set_impl_arity(9,  ce); pw_arg(8, capt_ref) },
            "_9" => { set_impl_arity(10, ce); pw_arg(9, capt_ref) },
            "@"  => {
                ce.borrow_mut().implicit_arity.1 = ArityParam::Infinite;
                pw!(prog, store, {
                    store.if_must_store(|store_pos| {
                        prog.op_argv(&spos, store_pos);
                        if capt_ref {
                            prog.op_to_ref(&spos, store_pos, store_pos, ToRefType::CaptureRef);
                        }
                    })
                })
            },
            _ => {
                let pos = ce.borrow_mut().get(var_s);
                let mk_respos : Result<Box<dyn Fn(&mut Prog) -> ResPos>, CompileError> =
                    match pos {
                        VarPos::UpValue(i) =>
                            Ok(Box::new(move |_prog: &mut Prog| ResPos::Up(i as u16))),
                        VarPos::Local(i) =>
                            Ok(Box::new(move |_prog: &mut Prog| ResPos::Local(i as u16))),
                        VarPos::Global(v) =>
                            Ok(Box::new(move |prog: &mut Prog| prog.global_pos(v.clone()))),
                        VarPos::Const(v) =>
                            Ok(Box::new(move |prog: &mut Prog| prog.data_pos(v.clone()))),
                        VarPos::NoPos => {
                            Err(ast.compile_err(
                                format!("Variable '{}' undefined", var_s)))
                        }
                    };
                let mk_respos = mk_respos?;
                pw_provides_result_pos!(prog, {
                    let var_respos = mk_respos(prog);
                    if capt_ref {
                        let rp = ResPos::Stack(0);
                        prog.op_to_ref(&spos, var_respos, rp, ToRefType::CaptureRef);
                        rp
                    } else {
                        var_respos
                    }
                })
            }
        }
    })
}

fn compile_assign(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>, is_ref: bool)
    -> Result<ProgWriter, CompileError>
{
    let prev_max_arity = ce.borrow().implicit_arity.clone();

    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    let vars          = ast.at(1).unwrap();
    let value         = ast.at(2).unwrap();
    let destr         = ast.at(3).unwrap_or(VVal::None);

    if destr.b() {
        let val_pw = compile(&value, ce)?;

        check_for_at_arity(prev_max_arity, ast, ce, &vars);

        let poses = vars.map_ok_skip(|v| ce.borrow_mut().get(&v.s_raw()), 0);

        pw_null!(prog, {
            let vp = val_pw.eval(prog);
            prog.op_destr(&spos, vp, DestructureInfo {
                vars:   vars.clone(),
                poses:  poses.clone(),
                is_ref,
            });
        })
    } else {
        let varname = &vars.at(0).unwrap().s_raw();
        let pos     = ce.borrow_mut().get(varname);

        let val_pw = compile(&value, ce)?;

        match pos {
            VarPos::Const(_) =>
                return Err(ast.compile_err(
                    format!("Can't assign to constant '{}'", varname))),
            VarPos::NoPos =>
                return Err(ast.compile_err(
                    format!("Can't assign to undefined local variable '{}'", varname))),
            _ => (),
        }

        pw_null!(prog, {
            let rp =
                match pos.clone() {
                    VarPos::Local(vip) => {
                        if is_ref {
                            ResPos::LocalRef(vip as u16)
                        } else {
                            ResPos::Local(vip as u16)
                        }
                    },
                    VarPos::Global(r) => {
                        if is_ref {
                            prog.global_ref_pos(r)
                        } else {
                            prog.global_pos(r)
                        }
                    },
                    VarPos::UpValue(vip) => {
                        if is_ref {
                            ResPos::UpRef(vip as u16)
                        } else {
                            ResPos::Up(vip as u16)
                        }
                    },
                    _ => ResPos::Value(ResValue::None)
                };

            val_pw.eval_to(prog, rp)
        })
    }
}

fn compile_stmts(ast: &VVal, skip_cnt: usize, ce: &mut Rc<RefCell<CompileEnv>>) -> Result<ProgWriter, CompileError> {
    let exprs : Vec<ProgWriter> =
        ast.map_skip( |e| { compile(e, ce) }, skip_cnt)?;

    pw!(prog, store, {
        let expr_count = exprs.len();
        let mut res    = ResPos::Value(ResValue::None);

        for (i, e) in exprs.iter().enumerate() {
            if i == expr_count - 1 {
                res = e.eval_proxy(prog, store.clone());
            } else {
                e.eval_nul(prog);
            }
        }

        res
    })
}

macro_rules! var_env_clear_locals {
    ($prog: ident, $from: ident, $to: ident, $spos: ident, $block: block) => {
        if $from != $to {
            $prog.op_clear_locals(&$spos, $from as u16, $to as u16);

            let res = $block;

            $prog.op_unwind(&$spos);
            res
        } else {
            $block
        }
    }
}

fn compile_block(ast: &VVal, skip_cnt: usize, ce: &mut Rc<RefCell<CompileEnv>>) -> Result<ProgWriter, CompileError> {
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    ce.borrow_mut().push_block_env();
    let stmts = compile_stmts(ast, skip_cnt, ce)?;
    let (from_local_idx, to_local_idx) = ce.borrow_mut().pop_block_env();

    pw!(prog, store, {
        match store {
            ResultSink::WantResult => {
                var_env_clear_locals!(prog, from_local_idx, to_local_idx, spos, {
                    stmts.eval_to(prog, ResPos::Stack(0))
                });
                ResPos::Stack(0)
            },
            _ => {
                var_env_clear_locals!(prog, from_local_idx, to_local_idx, spos, {
                    stmts.eval_proxy(prog, store)
                })
            }
        }
    })
}

fn compile_direct_block(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    match ast {
        VVal::Lst(_) => {
            let syn  = ast.at(0).unwrap_or(VVal::None);
            let syn  = syn.get_syn();

            match syn {
                Syntax::Func => {
                    let label          = ast.at(1).unwrap();
                    let explicit_arity = ast.at(2).unwrap();

                    if !label.is_none() {
                        return Err(
                            ast.compile_err(
                                format!("direct blocks don't support labels: {}",
                                        ast.s())));
                    }

                    if !explicit_arity.is_none() {
                        return Err(
                            ast.compile_err(
                                format!("direct blocks don't support arity: {}",
                                        ast.s())));
                    }

                    compile_block(ast, 3, ce)
                },
                _ => compile(ast, ce),
            }
        },
        _ => compile(ast, ce),
    }
}


fn compile_binop(ast: &VVal, op: BinOp, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    let a_pw = compile(&ast.at(1).unwrap(), ce)?;
    let b_pw = compile(&ast.at(2).unwrap(), ce)?;

    pw_needs_storage!(prog, store, {
        let ap = a_pw.eval(prog);
        let bp = b_pw.eval(prog);
        prog.op_binop(&spos, op, bp, ap, store);
    })
}

#[allow(clippy::many_single_char_names)]
fn compile_const_value(val: &VVal) -> Result<VVal, CompileError> {
    match val {
        VVal::Lst(l) => {
            let l = l.borrow();
            match l[0].get_syn() {
                Syntax::Key => Ok(l[1].clone()),
                Syntax::Str => Ok(l[1].clone()),
                Syntax::Lst => {
                    let v = VVal::vec();
                    for i in l.iter().skip(1) {
                        v.push(compile_const_value(i)?);
                    }
                    Ok(v)
                },
                Syntax::Map => {
                    let m = VVal::map();
                    for i in l.iter().skip(1) {
                        let key = compile_const_value(&i.at(0).unwrap_or(VVal::None))?;
                        let val = compile_const_value(&i.at(1).unwrap_or(VVal::None))?;
                        m.set_key_sym(key.to_sym(), val)
                         .expect("Const map not used more than once");
                    }
                    Ok(m)
                },
                Syntax::IVec => {
                    let a = compile_const_value(&l[1])?;
                    let b = compile_const_value(&l[2])?;
                    if l.len() > 3 {
                        let c = compile_const_value(&l[3])?;
                        if l.len() == 5 {
                            let d = compile_const_value(&l[4])?;
                            Ok(VVal::IVec(Box::new(NVec::Vec4(a.i(), b.i(), c.i(), d.i()))))
                        } else {
                            Ok(VVal::IVec(Box::new(NVec::Vec3(a.i(), b.i(), c.i()))))
                        }
                    } else {
                        Ok(VVal::IVec(Box::new(NVec::Vec2(a.i(), b.i()))))
                    }
                },
                Syntax::FVec => {
                    let a = compile_const_value(&l[1])?;
                    let b = compile_const_value(&l[2])?;
                    if l.len() > 3 {
                        let c = compile_const_value(&l[3])?;
                        if l.len() == 5 {
                            let d = compile_const_value(&l[4])?;
                            Ok(VVal::FVec(Box::new(NVec::Vec4(a.f(), b.f(), c.f(), d.f()))))
                        } else {
                            Ok(VVal::FVec(Box::new(NVec::Vec3(a.f(), b.f(), c.f()))))
                        }
                    } else {
                        Ok(VVal::FVec(Box::new(NVec::Vec2(a.f(), b.f()))))
                    }
                },
                _ => Err(val.to_compile_err(
                    format!(
                        "Invalid literal in constant definition: {}",
                        val.s())).err().unwrap()),
            }
        },
        VVal::Pair(bx) => {
            let a = compile_const_value(&bx.0)?;
            let b = compile_const_value(&bx.1)?;
            Ok(VVal::pair(a, b))
        },
        _ => Ok(val.clone()),
    }
}

fn compile_const(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let vars    = ast.at(1).unwrap();
    let value   = ast.at(2).unwrap();
    let destr   = ast.at(3).unwrap_or(VVal::None);

    if destr.b() {
        for (i, (v, _)) in vars.iter().enumerate() {
            let varname = v.s_raw();
            let val = compile_const_value(&value)?;
            let val =
                match val {
                    VVal::Lst(_) => val.at(i).unwrap_or(VVal::None),
                    VVal::Map(_) => val.get_key(&varname).unwrap_or(VVal::None),
                    _ => val,
                };

            ce.borrow_mut().def_const(&varname, val);
        }
    } else {
        let varname = vars.at(0).unwrap().s_raw();
        let const_val = compile_const_value(&value)?;
        ce.borrow_mut().def_const(&varname, const_val);
    }

    pw_null!(prog, { })
}

pub(crate) fn compile_break(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    if ast.len() > 3 {
        return Err(ast.compile_err("break takes 0 or 1 arguments".to_string()))
    }

    if let Some(expr) = ast.at(2) {
        let expr = compile(&expr, ce)?;
        pw_null!(prog, {
            let ep = expr.eval(prog);
            prog.op_ctrl_flow_break(&spos, ep);
        })
    } else {
        pw_null!(prog, {
            prog.op_ctrl_flow_break(
                &spos, ResPos::Value(ResValue::None));
        })
    }
}

pub(crate) fn compile_next(ast: &VVal, _ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    if ast.len() > 2 {
        return Err(ast.compile_err("next takes no arguments".to_string()))
    }

    pw_null!(prog, {
        prog.op_ctrl_flow_next(&spos);
    })
}

pub(crate) fn compile_if(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    if ast.len() != 4 && ast.len() != 5 {
        return Err(ast.compile_err(
            "?/if takes 1 or 2 arguments (condition and expression)".to_string()))
    }

    let cond =
        compile_direct_block(
            &ast.at(2).unwrap_or(VVal::None), ce)?;

    let then_body =
        compile_direct_block(
            &ast.at(3).unwrap_or(VVal::None), ce)?;

    if let Some(else_body) = ast.at(4) {
        let else_body = compile_direct_block(&else_body, ce)?;

        pw!(prog, store, {

            let needs_store = store.if_null(|_| {
                let mut then_body_prog = Prog::new();
                let mut else_body_prog = Prog::new();
                then_body.eval_nul(&mut then_body_prog);
                else_body.eval_nul(&mut else_body_prog);

                then_body_prog.op_jmp(&spos, else_body_prog.op_count() as i32);

                let condval = cond.eval(prog);
                prog.op_jmp_ifn(
                    &spos, condval, then_body_prog.op_count() as i32);
                prog.append(then_body_prog);
                prog.append(else_body_prog);
            });

            if needs_store {
                store.if_must_store(|store_pos| {
                    let mut then_body_prog = Prog::new();
                    let mut else_body_prog = Prog::new();
                    then_body.eval_to(&mut then_body_prog, store_pos);
                    else_body.eval_to(&mut else_body_prog, store_pos);
                    then_body_prog.op_jmp(&spos, else_body_prog.op_count() as i32);

                    let condval = cond.eval(prog);
                    prog.op_jmp_ifn(
                        &spos, condval, then_body_prog.op_count() as i32);
                    prog.append(then_body_prog);
                    prog.append(else_body_prog);
                })
            } else {
                ResPos::Value(ResValue::None)
            }
        })
    } else {
        pw!(prog, store, {
            let needs_store = store.if_null(|_| {
                let mut then_body_prog = Prog::new();
                then_body.eval_nul(&mut then_body_prog);

                let condval = cond.eval(prog);
                prog.op_jmp_ifn(
                    &spos, condval, then_body_prog.op_count() as i32);
                prog.append(then_body_prog);
            });

            if needs_store {
                store.if_must_store(|store_pos| {
                    let mut then_body_prog = Prog::new();
                    then_body.eval_to(&mut then_body_prog, store_pos);
                    then_body_prog.op_jmp(&spos, 1);

                    let condval = cond.eval(prog);
                    prog.op_jmp_ifn(
                        &spos, condval, then_body_prog.op_count() as i32);
                    prog.append(then_body_prog);
                    prog.op_mov(&spos, ResPos::Value(ResValue::None), store_pos);
                })
            } else {
                ResPos::Value(ResValue::None)
            }
        })
    }
}

pub(crate) fn compile_while(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    if ast.len() != 4 {
        return Err(ast.compile_err(
            "while takes exactly 2 arguments (condition and expression)"
            .to_string()));
    }

    let cond =
        compile_direct_block(
            &ast.at(2).unwrap_or(VVal::None), ce)?;

    let body =
        compile_direct_block(
            &ast.at(3).unwrap_or(VVal::None), ce)?;

    return pw_null!(prog, {
        // Create the OPs for the body:
        let mut body_prog = Prog::new();
        body.eval_nul(&mut body_prog);
        let body_op_count = body_prog.op_count();

        let mut cond_prog = Prog::new();
        let cond_val = cond.eval(&mut cond_prog);

        prog.op_push_loop_info(
            &spos, (cond_prog.op_count() + body_op_count + 2) as u16);

        let cond_op_count1 = prog.op_count();
        cond_prog.op_jmp_ifn(
            &spos, cond_val, body_op_count as i32 + 1);
        prog.append(cond_prog);

        let cond_offs =
            body_op_count + (prog.op_count() - cond_op_count1);
        body_prog.op_jmp(&spos, -(cond_offs as i32 + 1));
        prog.append(body_prog);
        prog.op_unwind(&spos);
    });
}

pub(crate) fn compile_iter(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.at(0).unwrap_or(VVal::None);
    let spos = syn.get_syn_pos();

    if ast.len() != 5 {
        return Err(ast.compile_err(
            "iter takes exactly 3 arguments \
             (variable identifier, iterable expression and \
             iteration expression)".to_string()));
    }

    let var = ast.at(2).unwrap_or(VVal::None);

    let varname =
        match var.at(0).unwrap().get_syn() {
            Syntax::Var => var.at(1).unwrap_or(VVal::None).s_raw(),
            _ => {
                return Err(ast.compile_err(
                    "iter takes an identifier as first argument".to_string()));
            }
        };

    ce.borrow_mut().push_block_env();
    let iter_var = ce.borrow_mut().next_local();

    let iterable =
        compile_direct_block(
            &ast.at(3).unwrap_or(VVal::None), ce)?;

    ce.borrow_mut().def_local(&varname, iter_var);

    let expr =
        compile_direct_block(
            &ast.at(4).unwrap_or(VVal::None), ce)?;

    let (from_local_idx, to_local_idx) = ce.borrow_mut().pop_block_env();

    pw_null!(prog, {
        let iter_var = ResPos::Local(iter_var as u16);

        let mut body = Prog::new();
        expr.eval_nul(&mut body);
        body.op_jmp(&spos, -(body.op_count() as i32 + 2));

        var_env_clear_locals!(prog, from_local_idx, to_local_idx, spos, {
            let ip = iterable.eval(prog);

            // + 1 for the op_iter_next:
            prog.op_iter_init(&spos, ip, (body.op_count() + 1) as i32);
            prog.op_iter_next(&spos, iter_var);

            prog.append(body);

            prog.op_unwind(&spos);
            prog.op_unwind(&spos);
        });
    })
}

pub(crate) fn generate_jump_table(spos: SynPos, value: ProgWriter, blocks: Vec<ProgWriter>)
    -> Result<ProgWriter, CompileError>
{
    pw!(prog, store, {
        let mut block_progs = Vec::new();
        let mut end_offs : i32 = 0;

        let needs_store = store.if_null(|_| {
            end_offs = 0;
            for b in blocks.iter().rev() {
                let mut b_prog = Prog::new();

                b.eval_nul(&mut b_prog);

                if end_offs > 0 {
                    b_prog.op_jmp(&spos, end_offs);
                }
                end_offs += b_prog.op_count() as i32;

                block_progs.push(b_prog);
            }
        });

        let res =
            if needs_store {
                store.if_must_store(|store_pos| {
                    end_offs = 0;
                    for b in blocks.iter().rev() {
                        let mut b_prog = Prog::new();

                        b.eval_to(&mut b_prog, store_pos);
                        if end_offs > 0 {
                            b_prog.op_jmp(&spos, end_offs);
                        }
                        end_offs += b_prog.op_count() as i32;

                        block_progs.push(b_prog);
                    }
                })
            } else {
                ResPos::Value(ResValue::None)
            };

        let mut val_prog = Prog::new();
        let val = value.eval(&mut val_prog);

        let mut tbl : Vec<i32> = vec![];
        let mut offs = 0;
        for b in block_progs.iter().rev() {
            tbl.push(offs);
            offs += b.op_count() as i32;
        }

        val_prog.op_jmp_tbl(&spos, val, tbl);

        prog.append(val_prog);
        for b in block_progs.into_iter().rev() {
            prog.append(b);
        }

        res
    })
}

pub(crate) fn compile_jump(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.v_(0);
    let spos = syn.get_syn_pos();

    if ast.len() < 4 {
        return Err(ast.compile_err(
            "jump takes at least 2 arguments".to_string()));
    }

    let value = compile(&ast.v_(2), ce)?;

    let mut blocks = Vec::new();
    for (block, _) in ast.iter().skip(3) {
        blocks.push(compile_direct_block(&block, ce)?);
    }

    generate_jump_table(spos, value, blocks)
}

// 0. Write op that takes a jump table. Stores at idx 0 the "end",
//    starting at idx 1 the offsets to the blocks. The op_jmp_tbl
//    pops off an index from a location and sets the pc.
// 0b. Write a vm_compile_jmp_tbl that takes a list of code blocks and
//     arranges them. and uses the op_jmp_tbl then on the given value.
// 0c. Write an op_call_direct(A, R, DCall) that calls boxed DCall directly
//     in the op and writes the single return value out. and takes 1 argument
//     value.
// 1. compile struct patterns into single function, that returns the
//    code block index. and use the op_call_direct()
// 2. use op_jmp_tbl next to jump to the destinations
// 3. benchmark the VM speed changes (if any, still fear it might slow down
//    OP dispatch).
pub(crate) fn compile_match(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    let syn  = ast.v_(0);
    let spos = syn.get_syn_pos();

    if ast.len() < 4 {
        return Err(ast.compile_err(
            "match takes at least 2 arguments".to_string()));
    }

    let value = compile(&ast.v_(2), ce)?;

    let mut patterns = Vec::new();
    let mut blocks = Vec::new();

    let len = ast.len();
    for (i, (struct_pat, _)) in ast.iter().enumerate().skip(3) {
        if (i + 1) < len {
            if !struct_pat.is_pair() {
                return Err(ast.compile_err(
                    format!("match argument {} is not a pair: {}",
                            i - 1, struct_pat.s())));
            }

            patterns.push(struct_pat.v_(0));
            blocks.push(compile_direct_block(&struct_pat.v_(1), ce)?);

        } else if struct_pat.is_pair() {
            patterns.push(struct_pat.v_(0));
            blocks.push(compile_direct_block(&struct_pat.v_(1), ce)?);
            blocks.push(compile(&VVal::None, ce)?);

        } else {
            blocks.push(compile_direct_block(&struct_pat, ce)?);
        }
    }

    let map = VVal::map();

    let dfun_constr =
        struct_pattern::create_struct_patterns_direct_fun(
            &patterns, &map)?;

    let res_ref =
        ce.borrow_mut().global.borrow_mut()
          .get_var_ref("\\")
          .unwrap_or(VVal::None);

    let dfun =
        if map.is_empty() {
            (dfun_constr)(
                Box::new(|_sym, _val| ()),
                Box::new(|| ()))
        } else {
            let set_res_ref = res_ref.clone();
            (dfun_constr)(
                Box::new(move |sym, val| set_res_ref.set_key_sym(sym.clone(), val.clone()).unwrap()),
                Box::new(move ||
                    if let VVal::Map(m) = res_ref.deref() {
                        m.borrow_mut().clear();
                    } else {
                        res_ref.set_ref(VVal::map());
                    }))
        };

    let spos_jmp = spos.clone();
    let jump_value =
        pw_provides_result_pos!(prog, {
            let v = value.eval(prog);
            prog.op_call_direct(&spos_jmp, v, dfun.clone(), ResPos::Stack(0));
            ResPos::Stack(0)
        })?;

    generate_jump_table(spos, jump_value, blocks)
}

pub(crate) fn collection_add(env: &mut Env, argc: usize, col_add: CollectionAdd) -> Result<VVal, StackAction> {
    let args = env.argv_ref();
    if args[0].is_fun() {
        let fun = args[0].clone();
        let mut ret = VVal::None;

        for i in 1..argc {
            let v = env.arg(i);
            let mut retf = Ok(VVal::None);
            v.with_value_or_iter_values(|v, _k| {
                env.push(v);
                match fun.call_internal(env, 1) {
                    Err(StackAction::Break(v)) => {
                        retf = Ok(*v);
                        env.popn(1);
                        return false;
                    },
                    r => { retf = r; }
                }
                env.popn(1);
                true
            });

            match retf {
                Ok(v)                      => { ret = v; },
                Err(StackAction::Break(v)) => { return Ok(*v); },
                Err(StackAction::Next)     => { },
                Err(e)                     => { return Err(e); }
            }
        }

        Ok(ret)
    } else {
        let col = args[0].clone();
        Ok(col.add(&args[1..], col_add))
    }
}

#[allow(clippy::cognitive_complexity)]
pub(crate) fn compile(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<ProgWriter, CompileError>
{
    match ast {
        VVal::Lst(_) => {
            let syn  = ast.at(0).unwrap_or(VVal::None);
            let spos = syn.get_syn_pos();
            let syn  = syn.get_syn();

            match syn {
                Syntax::Block       => compile_block(ast, 1, ce),
                Syntax::Assign      => compile_assign(ast, ce, false),
                Syntax::AssignRef   => compile_assign(ast, ce, true),
                Syntax::Var         => compile_var(ast, ce, false),
                Syntax::CaptureRef  => compile_var(ast, ce, true),
                Syntax::Def         => compile_def(ast, ce, false),
                Syntax::DefGlobRef  => compile_def(ast, ce, true),
                Syntax::DefConst    => compile_const(ast, ce),
                Syntax::BinOpAdd    => compile_binop(ast, BinOp::Add, ce),
                Syntax::BinOpSub    => compile_binop(ast, BinOp::Sub, ce),
                Syntax::BinOpDiv    => compile_binop(ast, BinOp::Div, ce),
                Syntax::BinOpMod    => compile_binop(ast, BinOp::Mod, ce),
                Syntax::BinOpMul    => compile_binop(ast, BinOp::Mul, ce),
                Syntax::BinOpGe     => compile_binop(ast, BinOp::Ge,  ce),
                Syntax::BinOpGt     => compile_binop(ast, BinOp::Gt,  ce),
                Syntax::BinOpLe     => compile_binop(ast, BinOp::Le,  ce),
                Syntax::BinOpLt     => compile_binop(ast, BinOp::Lt,  ce),
                Syntax::BinOpEq     => compile_binop(ast, BinOp::Eq,  ce),
                Syntax::Ref => {
                    let ref_pw = compile(&ast.at(1).unwrap(), ce)?;
                    pw_needs_storage!(prog, store, {
                        let ref_rp = ref_pw.eval(prog);
                        prog.op_to_ref(&spos, ref_rp, store, ToRefType::ToRef);
                    })
                },
                Syntax::HRef => {
                    let ref_pw = compile(&ast.at(1).unwrap(), ce)?;
                    pw_needs_storage!(prog, store, {
                        let ref_rp = ref_pw.eval(prog);
                        prog.op_to_ref(&spos, ref_rp, store, ToRefType::Hidden);
                    })
                },
                Syntax::WRef => {
                    let ref_pw = compile(&ast.at(1).unwrap(), ce)?;
                    pw_needs_storage!(prog, store, {
                        let ref_rp = ref_pw.eval(prog);
                        prog.op_to_ref(&spos, ref_rp, store, ToRefType::Weak);
                    })
                },
                Syntax::Deref => {
                    let ref_pw = compile(&ast.at(1).unwrap(), ce)?;
                    pw_needs_storage!(prog, store, {
                        let ref_rp = ref_pw.eval(prog);
                        prog.op_to_ref(&spos, ref_rp, store, ToRefType::Deref);
                    })
                },
                Syntax::Lst => {
                    let mut pws : std::vec::Vec<(bool, ProgWriter)> = vec![];
                    for (a, _) in ast.iter().skip(1) {
                        if a.is_vec()
                           && a.at(0).unwrap_or(VVal::None).syn().unwrap()
                              == Syntax::VecSplice
                        {

                            let splice_pw = compile(&a.at(1).unwrap(), ce)?;
                            pws.push((true, splice_pw));
                            continue;
                        }

                        let val_pw = compile(&a, ce)?;
                        pws.push((false, val_pw));
                    }

                    pw_provides_result_pos!(prog, {
                        prog.op_new_list(&spos, ResPos::Stack(0));

                        for (is_splice, pw) in pws.iter() {
                            pw.eval_to(prog, ResPos::Stack(0));
                            if *is_splice {
                                prog.op_list_splice(
                                    &spos,
                                    ResPos::Stack(0),
                                    ResPos::Stack(0),
                                    ResPos::Stack(0));
                            } else {
                                prog.op_list_push(
                                    &spos,
                                    ResPos::Stack(0),
                                    ResPos::Stack(0),
                                    ResPos::Stack(0));
                            }
                        }

                        ResPos::Stack(0)
                    })
                },
                Syntax::Iter => {
                    let val_pw = compile(&ast.at(1).unwrap(), ce)?;

                    pw_store_if_needed!(prog, store, {
                        let vp = val_pw.eval(prog);
                        prog.op_new_iter(&spos, vp, store);
                    })
                },
                Syntax::Opt => {
                    if let Some(v) = ast.at(1) {
                        let val_pw = compile(&v, ce)?;

                        pw_store_if_needed!(prog, store, {
                            let vp = val_pw.eval(prog);
                            prog.op_new_opt(&spos, vp, store);
                        })
                    } else {
                        pw_store_if_needed!(prog, store, {
                            prog.op_new_opt(
                                &spos,
                                ResPos::Value(ResValue::OptNone),
                                store);
                        })
                    }
                },
                Syntax::Map => {
                    let mut pws : std::vec::Vec<(ProgWriter, Option<ProgWriter>)> =
                        vec![];

                    for (e, _) in ast.iter().skip(1) {
                        let k = e.at(0).unwrap();
                        let v = e.at(1).unwrap();

                        if let Some(Syntax::MapSplice) = k.syn() {
                            let sc_pw = compile(&v, ce)?;
                            pws.push((sc_pw, None));
                            continue;
                        }

                        let kc_pw = compile(&k, ce)?;
                        if let VVal::Sym(y) = k {
                            ce.borrow_mut().recent_var = String::from(y.as_ref());
                        } else {
                            let recent_sym = ce.borrow().recent_sym.clone();
                            ce.borrow_mut().recent_var = recent_sym;
                        }

                        let vc_pw = compile(&v, ce)?;
                        pws.push((kc_pw, Some(vc_pw)));
                    }

                    pw_provides_result_pos!(prog, {
                        prog.op_new_map(&spos, ResPos::Stack(0));

                        for (kc_pw, vc_pw) in pws.iter() {
                            if let Some(vc_pw) = vc_pw {
                                kc_pw.eval_to(prog, ResPos::Stack(0));
                                vc_pw.eval_to(prog, ResPos::Stack(0));

                                prog.op_map_set_key(
                                    &spos,
                                    ResPos::Stack(0),
                                    ResPos::Stack(0),
                                    ResPos::Stack(0),
                                    ResPos::Stack(0));
                            } else {
                                kc_pw.eval_to(prog, ResPos::Stack(0));

                                prog.op_map_splice(
                                    &spos,
                                    ResPos::Stack(0),
                                    ResPos::Stack(0),
                                    ResPos::Stack(0));
                            }
                        }

                        ResPos::Stack(0)
                    })
                },
                Syntax::Func => {
                    let mut fun_spos = spos.clone();
                    fun_spos.set_name(&ce.borrow().recent_var);

                    let mut func_ce = CompileEnv::create_env(Some(ce.clone()));
                    let ce_sub = func_ce.clone();

                    let label          = ast.at(1).unwrap();
                    let explicit_arity = ast.at(2).unwrap();

                    let mut func_prog = Prog::new();

                    let func_pw = compile_stmts(ast, 3, &mut func_ce)?;
                    func_pw.eval_to(&mut func_prog, ResPos::Value(ResValue::Ret));
                    func_prog.op_end();

                    let func_prog = Rc::new(func_prog);

                    ce_sub.borrow_mut().explicit_arity.0 =
                        match explicit_arity.at(0).unwrap_or(VVal::None) {
                            VVal::Int(i) => ArityParam::Limit(i as usize),
                            VVal::Bol(true) => ArityParam::Limit(0),
                            _ => ArityParam::Undefined,
                        };

                    ce_sub.borrow_mut().explicit_arity.1 =
                        match explicit_arity.at(1).unwrap_or(VVal::None) {
                            VVal::Int(i) => ArityParam::Limit(i as usize),
                            VVal::Bol(true) => ArityParam::Infinite,
                            _ => ArityParam::Undefined,
                        };

                    let deciding_min_arity = if ce_sub.borrow().explicit_arity.0 != ArityParam::Undefined {
                        ce_sub.borrow().explicit_arity.0.clone()
                    } else {
                        ce_sub.borrow().implicit_arity.0.clone()
                    };

                    let deciding_max_arity = if ce_sub.borrow().explicit_arity.1 != ArityParam::Undefined {
                        ce_sub.borrow().explicit_arity.1.clone()
                    } else {
                        ce_sub.borrow().implicit_arity.1.clone()
                    };

                    let min_args : Option<usize> = match deciding_min_arity {
                        ArityParam::Infinite  => None,
                        ArityParam::Undefined => Some(0),
                        ArityParam::Limit(i)  => Some(i),
                    };

                    let max_args : Option<usize> = match deciding_max_arity {
                        ArityParam::Infinite  => None,
                        ArityParam::Undefined => Some(0),
                        ArityParam::Limit(i)  => Some(i),
                    };

                    let env_size = ce_sub.borrow().get_local_space();
                    let upvs     = ce_sub.borrow_mut().get_upval_pos();
                    let upvalues = vec![];

                    let fun_template =
                        VValFun::new_prog(
                            func_prog,
                            upvalues, env_size, min_args, max_args, false,
                            Some(fun_spos),
                            Rc::new(upvs),
                            label);

                    pw_needs_storage!(prog, store, {
                        let fp = prog.data_pos(fun_template.clone());
                        prog.op_new_clos(&spos, fp, store)
                    })
                },
                Syntax::Call => {
                    if let Some((syntax, object, key)) =
                        fetch_object_key_access(&ast.at(1).unwrap()) {

                        let obj = compile(&object, ce)?;

                        let mut args = vec![];
                        for (e, _) in ast.iter().skip(2) {
                            args.push(e);
                        }

                        let mut compiled_args = vec![];
                        let mut argc = 0;
                        for e in args.iter() {
                            compiled_args.push(compile(e, ce)?);
                            argc += 1;
                        }

                        match syntax {
                            Syntax::GetKey => {
                                let key = compile(&key, ce)?;
                                pw_store_if_needed!(prog, store, {
                                    for ca in compiled_args.iter() {
                                        ca.eval_to(prog, ResPos::Stack(0));
                                    }
                                    let obj_p = obj.eval(prog);
                                    let key_p = key.eval(prog);
                                    prog.op_call_method_key(
                                        &spos,
                                        obj_p,
                                        key_p,
                                        argc as u16,
                                        store);
                                })
                            },
                            Syntax::GetSym => {
                                let key = key.s_raw();
                                pw_store_if_needed!(prog, store, {
                                    for ca in compiled_args.iter() {
                                        ca.eval_to(prog, ResPos::Stack(0));
                                    }
                                    let obj_p = obj.eval(prog);
                                    prog.op_call_method_sym(
                                        &spos,
                                        obj_p,
                                        key.clone(),
                                        argc as u16,
                                        store);
                                })
                            },
                            _ => {
                                Err(ast.compile_err(
                                    format!("fetch_object_key_access failed: {}",
                                            ast.s())))
                            },
                        }
                    } else {
                        let symbol =
                            if let Syntax::Var = ast.at(1).unwrap_or(VVal::None).at(0).unwrap_or(VVal::None).get_syn() {
                                let var = ast.at(1).unwrap().at(1).unwrap();
                                Some(var.s_raw())
                            } else {
                                None
                            };

                        if let Some(sym) = symbol {
                            match &sym[..] {
                                "?"     => return compile_if(ast, ce),
                                "if"    => return compile_if(ast, ce),
                                "while" => return compile_while(ast, ce),
                                "iter"  => return compile_iter(ast, ce),
                                "next"  => return compile_next(ast, ce),
                                "break" => return compile_break(ast, ce),
                                "match" => return compile_match(ast, ce),
                                "jump"  => return compile_jump(ast, ce),
                                _ => (),
                            }
                        }

                        let mut args = vec![];
                        for (e, _) in ast.iter().skip(1) {
                            args.push(e);
                        }

                        let mut compiled_args = vec![];
                        let mut argc = 0;
                        for e in args.iter() {
                            compiled_args.push(compile(e, ce)?);
                            argc += 1;
                        }

                        pw_store_if_needed!(prog, store, {
                            for ca in compiled_args.iter() {
                                ca.eval_to(prog, ResPos::Stack(0));
                            }
                            prog.op_call(&spos, argc as u16 - 1, store);
                        })
                    }
                },
                Syntax::Err => {
                    let err_pw = compile(&ast.at(1).unwrap(), ce)?;

                    pw_needs_storage!(prog, store, {
                        let err_val_p = err_pw.eval(prog);
                        prog.op_new_err(&spos, err_val_p, store);
                    })
                },
                Syntax::Key => {
                    let sym = ast.at(1).unwrap();
                    ce.borrow_mut().recent_sym = sym.s_raw();
                    pw_provides_result_pos!(prog, {
                        prog.data_pos(sym.clone())
                    })
                },
                Syntax::Str => {
                    let string = ast.at(1).unwrap();
                    ce.borrow_mut().recent_sym = string.s_raw();
                    pw_provides_result_pos!(prog, {
                        prog.data_pos(string.clone())
                    })
                },
                Syntax::IVec => {
                    let lc : Vec<ProgWriter> =
                        ast.map_skip(|e| { compile(e, ce) }, 1)?;

                    match lc.len() {
                        2 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                prog.op_new_ivec2(&spos, lc0p, lc1p, store);
                            })
                        },
                        3 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                let lc2p = lc[2].eval(prog);
                                prog.op_new_ivec3(
                                    &spos, lc0p, lc1p, lc2p, store);
                            })
                        },
                        4 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                let lc2p = lc[2].eval(prog);
                                let lc3p = lc[3].eval(prog);
                                prog.op_new_ivec4(
                                    &spos, lc0p, lc1p, lc2p, lc3p, store);
                            })
                        },
                        ecount => {
                            Err(ast.compile_err(
                                format!(
                                    "Can only create an IVector with 2, 3 or 4 elements, but got {}.", ecount)))
                        }
                    }
                },
                Syntax::FVec => {
                    let lc : Vec<ProgWriter> =
                        ast.map_skip(|e| { compile(e, ce) }, 1)?;

                    match lc.len() {
                        2 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                prog.op_new_fvec2(&spos, lc0p, lc1p, store);
                            })
                        },
                        3 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                let lc2p = lc[2].eval(prog);
                                prog.op_new_fvec3(
                                    &spos, lc0p, lc1p, lc2p, store);
                            })
                        },
                        4 => {
                            pw_store_if_needed!(prog, store, {
                                let lc0p = lc[0].eval(prog);
                                let lc1p = lc[1].eval(prog);
                                let lc2p = lc[2].eval(prog);
                                let lc3p = lc[3].eval(prog);
                                prog.op_new_fvec4(
                                    &spos, lc0p, lc1p, lc2p, lc3p, store);
                            })
                        },
                        ecount => {
                            Err(ast.compile_err(
                                format!(
                                    "Can only create an FVector with 2, 3 or 4 elements, but got {}.", ecount)))
                        }
                    }
                },
                Syntax::SelfObj => {
                    pw_provides_result_pos!(prog,
                        { ResPos::Value(ResValue::SelfObj) })
                },
                Syntax::SelfData => {
                    pw_provides_result_pos!(prog,
                        { ResPos::Value(ResValue::SelfData) })
                },
                Syntax::DebugPrint => {
                    pw_provides_result_pos!(prog, {
                        let pos_str = spos.s_only_pos();
                        let print_fun =
                            VValFun::new_fun(
                                move |env: &mut Env, argc: usize| {
                                    debug_print_value(env, argc, &pos_str)
                                }, None, None, true);
                        prog.data_pos(print_fun)
                    })
                },
                Syntax::Accum => {
                    match ast.at(1) {
                        Some(s) => {
                            if s.s_raw() == "@" {
                                pw_provides_result_pos!(prog, {
                                    ResPos::Value(ResValue::AccumVal)
                                })
                            } else {
                                let accum_type =
                                    match &s.s_raw()[..] {
                                        "string" => AccumType::String,
                                        "bytes"  => AccumType::Bytes,
                                        "float"  => AccumType::Float,
                                        "int"    => AccumType::Int,
                                        "map"    => AccumType::Map,
                                        "vec"    => AccumType::Vec,
                                        _ => {
                                            panic!("COMPILER ERROR: BAD ACCUM SYM");
                                        }
                                    };

                                let acc_pw =
                                    compile(&ast.at(2).unwrap(), ce)?;
                                pw_store_if_needed!(prog, store, {
                                    prog.op_accumulator(&spos, accum_type);
                                    acc_pw.eval_nul(prog);
                                    prog.op_mov(&spos,
                                        ResPos::Value(ResValue::AccumVal),
                                        store);
                                    prog.op_unwind(&spos);
                                })
                            }
                        },
                        None => {
                            pw_provides_result_pos!(prog, {
                                ResPos::Value(ResValue::AccumFun)
                            })
                        }
                    }
                },
                Syntax::GetIdx => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let idx = ast.at(2).unwrap().i() as u32;

                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_idx(&spos, opos, idx, store);
                    })
                },
                Syntax::GetIdx2 => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let idx  = ast.at(2).unwrap().i() as u32;
                    let idx2 = ast.at(3).unwrap().i() as u32;
                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_idx2(
                            &spos, opos, idx, idx2, store);
                    })
                },
                Syntax::GetIdx3 => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let idx  = ast.at(2).unwrap().i() as u32;
                    let idx2 = ast.at(3).unwrap().i() as u32;
                    let idx3 = ast.at(4).unwrap().i() as u32;
                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_idx3(
                            &spos, opos, idx, idx2, idx3, store);
                    })
                },
                Syntax::GetSym => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let sym = ast.at(2).unwrap().to_sym();
                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_sym(
                            &spos, opos, sym.clone(), store);
                    })
                },
                Syntax::GetSym2 => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let sym  = ast.at(2).unwrap().to_sym();
                    let sym2 = ast.at(3).unwrap().to_sym();
                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_sym2(
                            &spos, opos, sym.clone(), sym2.clone(), store);
                    })
                },
                Syntax::GetSym3 => {
                    let o_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let sym  = ast.at(2).unwrap().to_sym();
                    let sym2 = ast.at(3).unwrap().to_sym();
                    let sym3 = ast.at(4).unwrap().to_sym();
                    pw_store_if_needed!(prog, store, {
                        let opos = o_pw.eval(prog);
                        prog.op_get_sym3(
                            &spos, opos, sym.clone(), sym2.clone(), sym3.clone(), store);
                    })
                },
                Syntax::GetKey => {
                    let map_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let idx_pw = compile(&ast.at(2).unwrap(), ce)?;

                    pw_store_if_needed!(prog, store, {
                        let mp = map_pw.eval(prog);
                        let ip = idx_pw.eval(prog);
                        prog.op_get_key(&spos, mp, ip, store);
                    })
                },
                Syntax::SetKey => {
                    let map_pw = compile(&ast.at(1).unwrap(), ce)?;
                    let sym_pw = compile(&ast.at(2).unwrap(), ce)?;

                    let recent_sym = ce.borrow().recent_sym.clone();
                    ce.borrow_mut().recent_var = recent_sym;

                    let val_pw = compile(&ast.at(3).unwrap(), ce)?;

                    pw_store_if_needed!(prog, store, {
                        let map = map_pw.eval(prog);
                        let sym = sym_pw.eval(prog);
                        let val = val_pw.eval(prog);
                        prog.op_map_set_key(&spos, val, sym, map, store);
                    })
                },
                  Syntax::Or
                | Syntax::BinOpSomeOr
                | Syntax::BinOpNoneOr
                | Syntax::BinOpErrOr
                | Syntax::BinOpOptOr
                | Syntax::BinOpExtSomeOr => {

                    let a = compile(&ast.at(1).unwrap(), ce)?;
                    let b = compile(&ast.at(2).unwrap(), ce)?;

                    let mode =
                        match syn {
                            Syntax::BinOpSomeOr    => OrMode::SomeOp,
                            Syntax::BinOpNoneOr    => OrMode::NoneOp,
                            Syntax::BinOpErrOr     => OrMode::ErrOp,
                            Syntax::BinOpOptOr     => OrMode::OptOp,
                            Syntax::BinOpExtSomeOr => OrMode::ExtSomeOp,
                            _                      => OrMode::Bool,
                        };

                    pw_needs_storage!(prog, store, {
                        let mut aprog = Prog::new();
                        let ap = a.eval(&mut aprog);

                        let mut bprog = Prog::new();
                        let bp = b.eval(&mut bprog);

                        bprog.op_mov(&spos, bp, store);
                        aprog.op_or_jmp_mode(
                            &spos, ap, bprog.op_count() as i32, store, mode);
                        prog.append(aprog);
                        prog.append(bprog);
                    })
                },
                Syntax::And => {
                    let a = compile(&ast.at(1).unwrap(), ce)?;
                    let b = compile(&ast.at(2).unwrap(), ce)?;

                    pw_needs_storage!(prog, store, {
                        let mut aprog = Prog::new();
                        let ap = a.eval(&mut aprog);

                        let mut bprog = Prog::new();
                        let bp = b.eval(&mut bprog);

                        bprog.op_mov(&spos, bp, store);
                        aprog.op_and_jmp(&spos, ap, bprog.op_count() as i32, store);
                        prog.append(aprog);
                        prog.append(bprog);
                    })
                },
                Syntax::Apply => {
                    let call_argv_pw = compile(&ast.at(2).unwrap(), ce)?;
                    let func_pw      = compile(&ast.at(1).unwrap(), ce)?;

                    pw_store_if_needed!(prog, store, {
                        let f_rp    = func_pw.eval(prog);
                        let argv_rp = call_argv_pw.eval(prog);

                        prog.op_apply(&spos, argv_rp, f_rp, store);
                    })
                },
                Syntax::DumpVM => {
                    pw_null!(prog, {
                        prog.op_dump_vm(&spos);
                    })
                },
                Syntax::DumpStack => {
                    pw_null!(prog, {
                        prog.op_dump_stack(&spos);
                    })
                },
                Syntax::Export => {
                    let name = ast.at(1).unwrap();
                    let val_pw = compile(&ast.at(2).unwrap(), ce)?;

                    pw_null!(prog, {
                        let vp = val_pw.eval(prog);
                        prog.op_export(&spos, vp, name.s_raw());
                    })
                },
                Syntax::Import => {
                    let prefix = ast.at(1).unwrap();
                    let name   = ast.at(2).unwrap();
                    let s_prefix = if prefix.is_none() { String::from("") }
                                   else { prefix.s_raw() + ":" };

                    let glob_ref = ce.borrow_mut().global.clone();
                    if glob_ref.borrow_mut().import_module_as(
                         &name.s_raw(), &prefix.s_raw())
                    {
                        return pw_null!(prog, { });
                    }

                    let resolver : Option<Rc<RefCell<dyn ModuleResolver>>> =
                        glob_ref.borrow_mut().resolver.clone();

                    let path : Vec<String> =
                        (&name.s_raw())
                            .split(':')
                            .map(String::from)
                            .collect();

                    let import_file_path =
                        if spos.filename() == "?" {
                            None
                        } else {
                            Some(spos.filename())
                        };

                    if let Some(resolver) = resolver {

                        let r = resolver.borrow();
                        let exports = r.resolve(glob_ref.clone(), &path, import_file_path);
                        match exports {
                            Err(ModuleLoadError::NoSuchModule(p)) => {
                                Err(ast.compile_err(
                                    format!("Couldn't find module '{}' in paths: {}", name.s_raw(), p)))
                            },
                            Err(ModuleLoadError::ModuleEvalError(e)) => {
                                Err(ast.compile_err(
                                    format!("Error on evaluating module '{}': {}", name.s_raw(), e)))
                            },
                            Err(ModuleLoadError::Other(s)) => {
                                Err(ast.compile_err(
                                    format!("Error on resolving module '{}': {}", name.s_raw(), s)))
                            },
                            Ok(symtbl) => {
                                glob_ref.borrow_mut().import_from_symtbl(
                                    &s_prefix, symtbl);

                                pw_null!(prog, { })
                            },
                        }
                    } else {
                        Err(ast.compile_err(
                            format!("Couldn't resolve module '{}'", name.s_raw())))
                    }
                },
                Syntax::Pattern => {
                    let res_ref =
                        ce.borrow_mut().global.borrow_mut()
                          .get_var_ref("\\")
                          .unwrap_or(VVal::None);

                    let regex_mode =
                        ast.at(2)
                           .unwrap()
                           .with_s_ref(
                               selector::RegexMode::from_str);

                    ast.at(1).unwrap().with_s_ref(|pat_src| {
                        match selector::create_regex_find_function(
                                pat_src, res_ref, regex_mode)
                        {
                            Ok(fun) => {
                                pw_provides_result_pos!(prog, {
                                    prog.data_pos(fun.clone())
                                })
                            },
                            Err(e) => {
                                Err(ast.compile_err(
                                    format!("bad pattern: {}", e)))
                            }
                        }
                    })
                },
                Syntax::Selector => {
                    let res_ref =
                        ce.borrow_mut().global.borrow_mut()
                          .get_var_ref("\\")
                          .unwrap_or(VVal::None);

                    ast.at(1).unwrap().with_s_ref(|sel_src| {
                        match selector::create_selector_function(sel_src, res_ref) {
                            Ok(fun) => {
                                pw_provides_result_pos!(prog, {
                                    prog.data_pos(fun.clone())
                                })
                            },
                            Err(e) => {
                                Err(ast.compile_err(
                                    format!("bad selector: {}", e)))
                            }
                        }
                    })
                },
                Syntax::StructPattern => {
                    let res_ref =
                        ce.borrow_mut().global.borrow_mut()
                          .get_var_ref("\\")
                          .unwrap_or(VVal::None);

                    let variable_map = VVal::map();

                    let fun =
                        struct_pattern::create_struct_pattern_function(
                            &ast.at(1).unwrap(), &variable_map, res_ref)?;
                    pw_provides_result_pos!(prog, {
                        prog.data_pos(fun.clone())
                    })
                },
                Syntax::Formatter => {
                    let fun =
                        match formatter::create_formatter_fun(
                                &ast.at(1).unwrap().at(1).unwrap())
                        {
                            Ok(fun) => fun,
                            Err(e) => {
                                return Err(ast.compile_err(
                                    format!("bad formatter: {}", e)))
                            },
                        };

                    pw_provides_result_pos!(prog, {
                        prog.data_pos(fun.clone())
                    })
                },
                _ => { Err(ast.compile_err(format!("bad input: {}", ast.s()))) },
            }
        },
        VVal::Syn(s)
            if s.syn() == Syntax::OpColAddL
            || s.syn() == Syntax::OpColAddR => {

            let col_add =
                if s.syn() == Syntax::OpColAddL {
                    CollectionAdd::Unshift
                } else {
                    CollectionAdd::Push
                };

            pw_provides_result_pos!(prog, {
                let add_fun =
                    VValFun::new_fun(
                        move |env, argc|
                            collection_add(env, argc, col_add),
                        None, None, true);
                prog.data_pos(add_fun)
            })
        },
        VVal::Pair(bx) => {
            let a = compile(&bx.0, ce)?;
            let b = compile(&bx.1, ce)?;

            pw_store_if_needed!(prog, store, {
                let ar = a.eval(prog);
                let br = b.eval(prog);
                prog.op_new_pair(&SynPos::empty(), br, ar, store);
            })
        },
        _ => {
            let ast = ast.clone();
            pw_provides_result_pos!(prog, { prog.data_pos(ast.clone()) })
        },
    }
}

/// Compiles a WLambda AST into an [EvalNode] in the given CompileEnv.
/// This is an internal function.
fn compile_vm_fun(ast: &VVal, ce: &mut Rc<RefCell<CompileEnv>>)
    -> Result<EvalNode, CompileError>
{
    let prog = compile_stmts(ast, 1, ce)?;

    let mut p = Prog::new();
    prog.eval_to(&mut p, ResPos::Value(ResValue::Ret));
    p.op_end();

    Ok(Box::new(move |e: &mut Env| { crate::vm::vm(&p, e) }))
}

/// This is a function to help evaluating a piece of WLambda code and
/// receive a text representation of the result. It's primarily used
/// by the WLambda test suite.
///
///```
/// assert_eq!(wlambda::compiler::test_eval_to_string("1 + 2"), "3");
///```
pub fn test_eval_to_string(s: &str) -> String {
    let global = GlobalEnv::new_default();
    match parser::parse(s, "<compiler:s_eval>") {
        Ok(ast) => {
            let mut ce = CompileEnv::new(global.clone());
            match compile(&ast, &mut ce) {
                Ok(prog) => {
                    let local_space = ce.borrow().get_local_space();

                    let mut p = Prog::new();
                    prog.eval_to(&mut p, ResPos::Value(ResValue::Ret));
                    p.op_end();

                    let mut e = Env::new(global);
                    e.push(VVal::Int(10));
                    e.push(VVal::Flt(14.4));
                    e.argc = 2;
                    e.set_bp(0);
                    e.push_sp(local_space);

                    match crate::vm::vm(&p, &mut e) {
                        Ok(v) => v.s(),
                        Err(je) => {
                            format!("EXEC ERR: Caught {:?}", je)
                        }
                    }
                },
                Err(re) => format!("COMPILE ERROR: {}", re),
            }
        }
        Err(e)  => format!("PARSE ERROR: {}", e),
    }
}