Skip to content

File ProgramExecutor.hpp

File List > source > Worlds > ProgramExecutor.hpp

Go to the documentation of this file


#pragma once

#include "Language.hpp"

#include "core/WorldBase.hpp"
#include "core/EasyLogging.hpp"

#include <algorithm>
#include <functional>
#include <map>
#include <stack>
#include <variant>

#include "core/AgentBase.hpp"
#include "Interfaces/TrashInterface.hpp"

#ifdef USE_SFML_INTERFACE
#include "Interfaces/MainInterface.hpp"
#endif

using cse491::AgentBase;
using cse491::Entity;
using cse491::CellType;
using clogged::Logger;
using clogged::Team;
using clogged::LogLevel;

namespace worldlang {

    class ProgramExecutor {
    // Internal types
    public:
        using Callable = std::function<void(ProgramExecutor&)>;

        struct Identifier : std::string {};

        using Value = std::variant < size_t, double, std::string, Callable, Identifier >;

    // Execution state
    private:
        std::map<std::string, std::vector<Unit>> scripts;

        std::vector<Unit>* code = nullptr;

        size_t index = 0;

        std::map < std::string, Value > variables{};

        std::stack < Value > stack{};

        std::stack < std::vector< Value > > call_stack{};

        std::string error_message{};

    // Public methods
    public:
        ProgramExecutor(){
            // if function
            // skips a block if value is false
            registerFunction("if", [this](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 1) { error("Wrong number of arguments!"); return; }
                // jump to end of block if false

                if (pe.as<double>(args.at(0)) == 0){
                    skipBlock();
                } else {
                    // advance to start of block automatically
                }
                // mark type of block entered
                call_stack.push({"__IF_BLOCK"});
            });
            // for function
            registerFunction("for", [this](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 3 && args.size() != 4) { error("Wrong number of arguments!"); return; }
                auto var = pe.as<Identifier>(args.at(0));
                auto start = pe.as<double>(args.at(1));
                auto end = pe.as<double>(args.at(2));
                auto step = args.size() == 4 ? pe.as<double>(args.at(3)) : 1.0;

                variables.insert_or_assign(static_cast<std::string>(var), start);

                auto value = pe.as<double>(args.at(0));
                if ((start < end && step > 0 && value <= end)
                    || (start >= end && step < 0 && value >= end)){
                    // enter the loop if conditions are met
                    call_stack.push({"__FOR_BLOCK", var, start, end, step, static_cast<double>(index)});
                    index++; // skip start_block which checks condition + increments
                } else {
                    // skip loop entirely
                    skipBlock();
                    index++; // skip end_block or else it tries to jump to beginning
                }
            });
            // Log some values
            registerFunction("print", [](ProgramExecutor& pe){
                auto args = pe.popArgs();
                for (auto a : args){
                    if (pe.has<double>(a)){
                        std::cout << pe.as<double>(a);
                    } else if (pe.has<size_t>(a)){
                        std::cout << pe.as<size_t>(a);
                    } else if (pe.has<std::string>(a)){
                        std::cout << pe.as<std::string>(a);
                    } else if (pe.has<Callable>(a)){
                        std::cout << "<Callable>";
                    }
                }
                std::cout << std::endl;
            });
        }

        ProgramExecutor(cse491::WorldBase& world) : ProgramExecutor(){
            // Load world grid from file
            registerFunction("loadWorld", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 1) { error("Wrong number of arguments!"); return; }
                world.GetGrid().Read(as<std::string>(args.at(0)), world.GetCellTypes());
            });
            // Get the size of the world
            registerFunction("getWorldSize", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 0) { error("Wrong number of arguments!"); return; }
                pe.pushStack(static_cast<double>(world.GetGrid().GetWidth()));
                pe.pushStack(static_cast<double>(world.GetGrid().GetHeight()));
            });
            // Create an agent
            registerFunction("addAgent", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() < 5) { error("Wrong number of arguments!"); return; }
                // type, name, symbol, x, y
                auto type = pe.as<std::string>(args[0]);
                auto name = pe.as<std::string>(args[1]);
                auto symbol = pe.as<std::string>(args[2]);
                auto x = pe.as<double>(args[3]);
                auto y = pe.as<double>(args[4]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }
                if (!symbol.size()) { error("Symbol cannot be empty!"); return; }

                //TODO:Use the agent Factory class here, see if that works better.
                AgentBase* agent;
                if (type == "Player"){
                    agent = &world.AddAgent<cse491::TrashInterface>(name, "symbol", symbol[0]);
                    agent->SetPosition(x, y);
                } 
#ifdef USE_SFML_INTERFACE
        else if (type == "Player2D") {
                    agent = &world.AddAgent<i_2D::MainInterface>(name, "symbol", symbol[0]);
                    agent->SetPosition(x, y);
        } 
#endif 
        else {
                    error("Unknown agent type!"); return;
                }

                pe.pushStack(agent->GetID());
            });
            // Set agent property
            registerFunction("setProperty", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 3) { error("Wrong number of arguments!"); return; }
                auto id = pe.as<size_t>(args[0]);
                auto prop = pe.as<std::string>(args[1]);
                auto value = args[2];

                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                Entity& agent = world.HasAgent(id) ? static_cast<Entity&>(world.GetAgent(id)) : world.GetItem(id);
                if (pe.has<double>(value)){
                    agent.SetProperty(prop, as<double>(value));
                } else if (pe.has<std::string>(value)){
                    auto s = as<std::string>(value);
                    // silly hack
                    if (s.size() == 1){
                        agent.SetProperty(prop, s[0]);
                    } else {
                        agent.SetProperty(prop, s);
                    }
                } else {
                    error("Unsupported property type!");
                }
            });
            // Get agent property
            registerFunction("getProperty", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto id = pe.as<size_t>(args[0]);
                auto prop = pe.as<std::string>(args[1]);

                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                Entity& agent = world.HasAgent(id) ? static_cast<Entity&>(world.GetAgent(id)) : world.GetItem(id);
                if(agent.HasProperty(prop)){
                    // property exists but type is unknown
                    // assume double, as there is no way to determine the type currently
                    // TODO: Fix this if types are ever stored
                    pushStack(agent.GetProperty<double>(prop));
                } else {
                    error("Undefined property:" + prop);
                }
            });
            // Check if property exists
            registerFunction("hasProperty", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto id = pe.as<size_t>(args[0]);
                auto prop = pe.as<std::string>(args[1]);

                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                Entity& agent = world.HasAgent(id) ? static_cast<Entity&>(world.GetAgent(id)) : world.GetItem(id);
                if(agent.HasProperty(prop)){
                    pushStack(1.0);
                } else {
                    pushStack(0.0);
                }
            });
            // Get agent position
            registerFunction("getAgentPosition", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 1) { error("Wrong number of arguments!"); return; }
                auto id = pe.as<size_t>(args[0]);

                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                AgentBase& agent = world.GetAgent(id);
                auto x = agent.GetPosition().GetX();
                auto y = agent.GetPosition().GetY();
                pushStack(x);
                pushStack(y);
            });
            // Set agent position
            registerFunction("setAgentPosition", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 3) { error("Wrong number of arguments!"); return; }
                auto agent_id = pe.as<size_t>(args[0]);
                auto x = pe.as<double>(args[1]);
                auto y = pe.as<double>(args[2]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                AgentBase& agent = world.GetAgent(agent_id);
                agent.SetPosition(x, y);
            });
            // Get agent at this position
            registerFunction("findAgentAt", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto x = pe.as<double>(args[0]);
                auto y = pe.as<double>(args[1]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                auto res = world.FindAgentsAt({x, y});
                if (res.size()){
                    pe.pushStack(res[0]);
                } else {
                    pe.pushStack(0u);
                }
            });
            // Get item at this position
            registerFunction("findItemAt", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto x = pe.as<double>(args[0]);
                auto y = pe.as<double>(args[1]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                auto res = world.FindItemsAt({x, y});
                if (res.size()){
                    pe.pushStack(res[0]);
                } else {
                    pe.pushStack(0u);
                }
            });
            // Get size of the agent's inventory
            registerFunction("getInventorySize", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 1) { error("Wrong number of arguments!"); return; }
                auto agent_id = pe.as<size_t>(args[0]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                AgentBase& agent = world.GetAgent(agent_id);
                pe.pushStack(static_cast<double>(agent.GetInventory().size()));
            });
            // Gets an item from the inventory
            registerFunction("getInventoryItem", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto agent_id = pe.as<size_t>(args[0]);
                auto item_index = pe.as<double>(args[1]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                AgentBase& agent = world.GetAgent(agent_id);
                if (item_index >= agent.GetInventory().size()) {
                    error("Item index out of range!"); return;
                }

                pe.pushStack(agent.GetInventory()[item_index]);
            });
            // Add an item to the world
            registerFunction("addItem", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                // name,symbol,x,y,prop1,val1,prop2,val2...
                if (args.size() < 4) { error("Wrong number of arguments!"); return; }
                auto name = pe.as<std::string>(args[0]);
                auto symbol = pe.as<std::string>(args[1]);
                auto x = pe.as<double>(args[2]);
                auto y = pe.as<double>(args[3]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }
                if (symbol.empty()) { error("Symbol can't be empty!"); return; }

                auto item = std::make_unique<cse491::ItemBase>(world.NextEntityID(), name);
                item->SetProperties("symbol",symbol.at(0));
                item->SetPosition(x,y);
                item->SetGrid();
                for (size_t i = 4; i < args.size(); i += 2){
                    item->SetProperty(pe.as<std::string>(args[i]), pe.as<double>(args[i+1]));
                }
                pe.pushStack(item->GetID());

                world.AddItem(std::move(item));
            });
            // Gets an item from the inventory
            registerFunction("addInventoryItem", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() != 2) { error("Wrong number of arguments!"); return; }
                auto owner_id = pe.as<size_t>(args[0]);
                auto item_id = pe.as<size_t>(args[1]);
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }

                world.GetItem(item_id).SetPosition(-1,-1);
                Entity& entity = world.HasAgent(owner_id) ? static_cast<Entity&>(world.GetAgent(owner_id)) : world.GetItem(owner_id);
                entity.AddItem(item_id);
            });
            // Create a new cell type
            registerFunction("addCellType", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() < 3) { error("Wrong number of arguments!"); return; }
                // name, desc, symbol, props (ignored: TODO later)
                auto name = pe.as<std::string>(args[0]);
                auto desc = pe.as<std::string>(args[1]);
                auto symbol = pe.as<std::string>(args[2]);
                std::cout << desc << "," << name << "," << symbol << ",";
                if (!symbol.size()) { error("Symbol cannot be empty!"); return; }
                std::cout << (int)symbol[0] << "\n";

                auto id = world.AddCellType(name, desc, symbol[0]);
                for (size_t i = 3; i < args.size(); ++i){
                    world.type_options[id].SetProperty(pe.as<std::string>(args[i]));
                }

                pe.pushStack(id);
            });
            // Get a random number
            registerFunction("rand", [this, &world](ProgramExecutor& pe){
                auto args = pe.popArgs();
                if (args.size() > 2) { error("Wrong number of arguments!"); return; }
                if (args.size() == 0){
                    // random double in [0,1]
                    pushStack(world.GetRandom());
                } else if (args.size() == 1){
                    // random int in [0,max]
                    auto max = pe.as<double>(args[0]) + 1;
                    pushStack((double)(int)world.GetRandom(max)); // casts for rounding
                } else if (args.size() == 2){
                    // random int in [min,max]
                    auto min = pe.as<double>(args[0]);
                    auto max = pe.as<double>(args[1]) + 1;
                    pushStack((double)(int)world.GetRandom(min,max)); // casts for rounding
                }
                // check for argument errors
                if (!pe.getErrorMessage().empty()){ return; }
            });
            // Set constants
            setVariable("ID_NONE",0u);
            setVariable("CELL_WALL", CellType::CELL_WALL);
            setVariable("CELL_WATER", CellType::CELL_WATER);
        }

        virtual ~ProgramExecutor() = default;

        void skipBlock(int nest = 0){
            do {
                auto& unit = code->at(++index);
                if (unit.type == Unit::Type::operation && unit.value == "start_block") nest++;
                if (unit.type == Unit::Type::operation && unit.value == "end_block") nest--;
            } while(nest);
            // points to one past end_block
            index--;
            // points to end_block
        }

        void registerFunction(std::string name, Callable callable){
            variables.insert_or_assign(name, callable);
        }

        Value popStack(){
            auto v = stack.top();
            stack.pop();
            return v;
        }

        std::vector<Value> popArgs(){
            std::vector< Value > values;
            do {
                values.push_back(popStack());
            } while (!(has<Identifier>(values.back()) 
                    && static_cast<std::string>(as<Identifier>(values.back())) == "__INTERNAL_ENDARGS"));
            values.pop_back(); // don't keep that one
            std::reverse(values.begin(), values.end());
            return values;
        }

        void pushStack(Value value){
            stack.push(value);
        }

        template <typename T>
        bool has(const Value& a){
            if (std::holds_alternative<T>(a)){
                return true;
            } else if (std::holds_alternative<Identifier>(a)){
                try {
                    auto val = variables.at(static_cast<std::string>(std::get<Identifier>(a)));
                    return std::holds_alternative<T>(val);
                } catch(const std::out_of_range& e) {
                    error("Variable does not exist!");
                }
            }
            return false;
        }

        template <typename T>
        T as(const Value& a){
            if (std::holds_alternative<T>(a)){
                return std::get<T>(a);
            } else if (std::holds_alternative<Identifier>(a)){
                auto val = variables.at(static_cast<std::string>(std::get<Identifier>(a)));
                if (std::holds_alternative<T>(val)){
                    return std::get<T>(val);
                }
            }
            // error if conversion fails
            error(std::string{"Type error in as()! Expected "}+typeid(T).name());
            return T{};
        }

        template <typename T>
        T var(const std::string& name){
            auto val = variables.at(name);
            return std::get<T>(val);
        }

        void setVariable(const std::string& name, Value value){
            variables.insert_or_assign(name, value);
        }

        void error(const std::string& error){
            if (error_message.empty()){
                error_message = error;
            }
        }

        std::string getErrorMessage(){
            return error_message;
        }

        bool runFile(const std::string& filename){
            //TODO: program preprocessing (add newline to end, remove spaces)
            if (!scripts.count(filename)){
                std::ifstream in{filename};
                std::string s;
                std::string filedata;
                while (getline(in, s))
                    filedata += s + '\n';

                scripts[filename] = parse_to_code(filedata);
                if (scripts[filename].empty()){
                    // implies a parse error
                    error("Error parsing program from file");
                    return false;
                }
            }
            code = &scripts[filename];
            return run();
        }

        bool run(const std::string& program){
            scripts["__STRING_PROGRAM"] = parse_to_code(program);
            code = &scripts["__STRING_PROGRAM"];
            if (code->empty()){
                error("Error parsing program from string");
                return false;
            }
            return run();
        }

        bool run(){
            auto log = Logger::Log();
            log << Team::TEAM_4 << LogLevel::INFO << "Entering program execution" << std::endl;

            error_message = "";

            log << LogLevel::DEBUG;

            index = 0;
            while (error_message.empty() && index < code->size()){
                auto& unit = code->at(index);
                switch (unit.type){
                    case Unit::Type::number:
                        log << "Push number " << unit.value << std::endl;
                        try {
                            pushStack(std::stod(unit.value));
                        } catch (const std::invalid_argument& e) {
                            error("Failed to convert number!");
                        } catch (const std::out_of_range& e){
                            error("Number too big!");
                        }
                        break;

                    case Unit::Type::string:
                        log << "Push string " << unit.value << std::endl;
                        pushStack(unit.value);
                        break;

                    case Unit::Type::identifier:
                        log << "Push identifier " << unit.value << std::endl;
                        pushStack(Identifier{unit.value});
                        break;

                    case Unit::Type::operation:
                        // perform operation!
                        log << "Perform operation " << unit.value << std::endl;
                        if (unit.value == "="){
                            // values to assign
                            std::vector< Value > values = popArgs();
                            // identifiers to assign to
                            auto identifier_values = popArgs();
                            if (values.size() > identifier_values.size()){
                                error("Too many values!");
                                break;
                            } else if (values.size() < identifier_values.size()){
                                error("Not enough values!");
                                break;
                            }
                            // Convert to identifiers specifically
                            std::vector< Identifier > identifiers;
                            std::transform(
                identifier_values.begin(), identifier_values.end(),
                std::back_inserter(identifiers),
                [this](const Value& v){ return as<Identifier>(v); }
              );
                            //ex. a,b,c=1,2,3 becomes the following units
                            // . a b c . 1 2 3 =
                            // v: 3 2 1
                            // i: c b a
                            if (!getErrorMessage().empty()){
                                // transform failed in some way 
                                break;
                            }

                            // upon reaching this point, values and identifiers
                            // are the same length and contain valid items.
                            for (size_t i = 0; i < identifiers.size(); ++i){
                                auto a = identifiers[i];
                                auto b = values[i];

                                if (!std::holds_alternative<Identifier>(b)){
                                    setVariable(static_cast<std::string>(a), b);
                                } else {
                                    // exception if variable b does not exist
                                    try {
                                        auto& b_var = variables.at(static_cast<std::string>(as<Identifier>(b)));
                                        setVariable(static_cast<std::string>(a), b_var);
                                    } catch (const std::out_of_range& e){
                                        error("Variable did not exist!");
                                    }
                                }
                            }
                        } else if (std::string{"+ - * / == != <= >= < >"}.find(unit.value) != std::string::npos){
                            // binary expressions
                            auto b = popStack();
                            auto a = popStack();
                            if (unit.value == "+"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(as<double>(a) + as<double>(b));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(as<std::string>(a) + as<std::string>(b));
                                } else {
                                    error("Runtime type error (plus)");
                                }
                            } else if (unit.value == "-"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(as<double>(a) - as<double>(b));
                                } else {
                                    error("Runtime type error (minus)");
                                }
                            } else if (unit.value == "*"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(as<double>(a) * as<double>(b));
                                } else if (has<std::string>(a) && has<double>(b)){
                                    std::string n;
                                    double c = as<double>(b);
                                    for (int i = 0; i < c; ++i){
                                        n += as<std::string>(a);
                                    }
                                    pushStack(n);
                                } else {
                                    error("Runtime type error (times)");
                                }
                            } else if (unit.value == "/"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(as<double>(a) / as<double>(b));
                                } else {
                                    error("Runtime type error (divide)");
                                }
                            } else if (unit.value == "=="){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) == as<double>(b)));
                                } else if (has<size_t>(a) && has<size_t>(b)){
                                    pushStack(static_cast<double>(as<size_t>(a) == as<size_t>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) == as<std::string>(b)));
                                } else {
                                    error("Runtime type error (==)");
                                }
                            } else if (unit.value == "!="){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) != as<double>(b)));
                                } else if (has<size_t>(a) && has<size_t>(b)){
                                    pushStack(static_cast<double>(as<size_t>(a) != as<size_t>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) != as<std::string>(b)));
                                } else {
                                    error("Runtime type error (!=)");
                                }
                            } else if (unit.value == "<"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) < as<double>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) < as<std::string>(b)));
                                } else {
                                    error("Runtime type error (<)");
                                }
                            } else if (unit.value == ">"){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) > as<double>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) > as<std::string>(b)));
                                } else {
                                    error("Runtime type error (>)");
                                }
                            } else if (unit.value == "<="){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) <= as<double>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) <= as<std::string>(b)));
                                } else {
                                    error("Runtime type error (<=)");
                                }
                            } else if (unit.value == ">="){
                                if (has<double>(a) && has<double>(b)){
                                    pushStack(static_cast<double>(as<double>(a) >= as<double>(b)));
                                } else if (has<std::string>(a) && has<std::string>(b)){
                                    pushStack(static_cast<double>(as<std::string>(a) >= as<std::string>(b)));
                                } else {
                                    error("Runtime type error (>=)");
                                }
                            }
                        } else if (unit.value == "endline"){
                            // clear stack on end of line
                            while (stack.size())
                                popStack();
                        } else if (unit.value == "endargs"){
                            // this could absolutely be broken but that's OK
                            pushStack(Identifier{"__INTERNAL_ENDARGS"});
                        } else if (unit.value == "start_block"){
                            auto type = as<std::string>(call_stack.top().at(0));
                            if (type == "__FOR_BLOCK"){
                                auto info = call_stack.top();

                                double value = as<double>(info.at(1));
                                double start = as<double>(info.at(2));
                                double end = as<double>(info.at(3));
                                double step = as<double>(info.at(4));

                                if ((start < end && step > 0 && value + step <= end)
                                    || (start >= end && step < 0 && value + step >= end)){
                                    // continue
                                    variables.insert_or_assign(static_cast<std::string>(as<Identifier>(info.at(1))), value + step);
                                    // jump back to the beginning to check the condiiton
//                                  index = static_cast<int>(as<double>(call_stack.top().at(5)));
                                } else {
                                    // end loop
                                    call_stack.pop();
                                    skipBlock(1);
                                    index++; // skip end_block or else it tries to jump to beginning
                                    break;
                                }
                            }
                        } else if (unit.value == "end_block"){
                            // check for for loop if needed 
                            auto type = as<std::string>(call_stack.top().at(0));
                            if (type == "__FOR_BLOCK"){
                                // jump back to the beginning to check the condiiton
                                index = static_cast<int>(as<double>(call_stack.top().at(5)));
                            } else if (type == "__IF_BLOCK"){
                                // this only runs once, don't really need to save this
                                call_stack.pop();
                            } else if (type == "__FUNCTION_BLOCK"){
                                // return from function
                                index = static_cast<int>(as<double>(call_stack.top().at(1)));
                                call_stack.pop();
                            }
                        } else {
                            error("Unknown operation '" + unit.value + "'");
                        }
                        break;

                    case Unit::Type::function:
                        log << "Perform function " << unit.value << std::endl;
                        if (variables.count(unit.value)){
                            auto& func = variables.at(unit.value);
                            if (std::holds_alternative<Callable>(func)){
                                std::get<Callable>(func)(*this);
                            } else {
                                error(unit.value + " is not a callable object!");
                            }
                            break;
                        } else {
                            error("Function " + unit.value + " does not exist!");
                        }
                        break;

                    case Unit::Type::function_decl:
                        {
                        log << "Create function " << unit.value << std::endl;
                        auto vars = popArgs(); // should consist of variable names only
                        for (auto& v : vars){
                            if (!std::holds_alternative<Identifier>(v)){
                                error("Function definition only accepts identifiers!");
                            }
                        }

                        if (!getErrorMessage().empty()) break;
                        // args are all vars
                        auto func_body_index = index;
                        auto func = [this, vars, func_body_index](ProgramExecutor& pe){
                            // first: assign to all vars the values on the stack
                            auto values = pe.popArgs();
                            if (values.size() != vars.size()){
                                error("Invalid number of arguments (to user-defined function)!");
                                return;
                            }

                            for (size_t i = 0; i < vars.size(); ++i){
                                auto a = static_cast<std::string>(std::get<Identifier>(vars[i]));
                                auto b = values[i];

                                if (!std::holds_alternative<Identifier>(b)){
                                    setVariable(a, b);
                                } else {
                                    // exception if variable b does not exist
                                    try {
                                        auto& b_var = variables.at(static_cast<std::string>(as<Identifier>(b)));
                                        setVariable(a, b_var);
                                    } catch (const std::out_of_range& e){
                                        error("Variable did not exist!");
                                    }
                                }
                            }
                            // Assigned stack values to vars (as best as possible)
                            // Store return index
                            call_stack.push({"__FUNCTION_BLOCK", static_cast<double>(this->index)});
                            // Assign PC to start of code block
                            this->index = func_body_index;
                        };

                        // Assign this created function to this name
                        setVariable(unit.value, func);
                        skipBlock(); // skip past the function definition
                        ++index;
                        }
                        break;

                    default:
                        error("Unknown code unit '" + unit.value +"'!");
                }
                index++;
            }

            log << "Program execution ends" << std::endl;
            if (!error_message.empty()){
                log << LogLevel::WARNING << "With error: " << error_message << std::endl;
            }

            return error_message.empty();
        }
    };
} //worldlang