#include <iostream>
#include <stack>
#include <array>
#include <map>
#include <string>
#include <vector>
#include <memory>
#include <exception>

class interpreter {
public:
	interpreter(std::istream& input, std::ostream& output) : input_(input), output_(output) {};
	void load_commands(); // Nacte vsechny instrukce v souboru (streamu). Inicializuje objekt.
	void execute_commands(); // Vykona vsechny instrukce.
private: 

	#pragma region Instruction Classes
	class instruction {
	public:
		virtual void execute(interpreter& interpret) = 0;
	};

	class ldconst : public instruction {
	public:
		ldconst(int con) : const_(con) {};
		virtual void execute(interpreter& interpret) override {
			interpret.stack_.push(const_);
		}
	private:
		int const_;
	};

	class stloc : public instruction {
	public:
		stloc(std::size_t local) : local_(local) {};
		virtual void execute(interpreter& interpret) override {
			interpret.locals_.at(local_) = interpret.stack_.top();
			interpret.stack_.pop();
		}
	private:
		std::size_t local_;
	};

	class ldloc : public instruction {
	public:
		ldloc(std::size_t local) : local_(local) {};
		virtual void execute(interpreter& interpret) override {
			interpret.stack_.push(interpret.locals_.at(local_));
		}
	private:
		std::size_t local_;
	};

	class pop : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			interpret.stack_.pop();
		}
	};

	class out : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			interpret.output_ << interpret.stack_.top() << " ";
		}
	};

	class add : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op + right_op);
		}
	};

	class sub : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op - right_op);
		}
	};

	class mul : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op * right_op);
		}
	};

	class div : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op / right_op);
		}
	};

	class lt : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op < right_op ? 1 : 0);
		}
	};

	class gt : public instruction {
	public:
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.size() < 2) throw std::runtime_error("Too few operands on the stack.");
			int right_op = interpret.stack_.top(); interpret.stack_.pop();
			int left_op = interpret.stack_.top(); interpret.stack_.pop();
			interpret.stack_.push(left_op > right_op ? 1 : 0);
		}
	};

	class gotoi : public instruction {
	public:
		gotoi(const std::string& label) : label_(label + ":") {};
		virtual void execute(interpreter& interpret) override {
			auto inst = interpret.labels_.find(label_);
			if (inst == interpret.labels_.end())
				throw std::runtime_error("The label " + label_ + " has not been defined.");
			else
				interpret.current_instruction_ = inst->second;
		}
	private:
		std::string label_;
	};

	class brt : public instruction {
	public:
		brt(const std::string& label) : label_(label + ":") {};
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.top() != 0) {
				auto inst = interpret.labels_.find(label_);
				if (inst == interpret.labels_.cend())
					throw std::runtime_error("The label " + label_ + "has not been defined.");
				else
					interpret.current_instruction_ = inst->second;
			}
		}
	private:
		std::string label_;
	};

	class brf : public instruction {
	public:
		brf(const std::string& label) : label_(label + ":") {};
		virtual void execute(interpreter& interpret) override {
			if (interpret.stack_.top() == 0) {
				auto inst = interpret.labels_.find(label_);
				if (inst == interpret.labels_.cend())
					throw std::runtime_error("The label " + label_ + "has not been defined.");
				else
					interpret.current_instruction_ = inst->second;
			}
		}
	private:
		std::string label_;
	};
#pragma endregion

	std::vector<std::string> get_tokens_(const std::string& line); // Rozdeli radku na jednotlive tokeny.
	void process_line_(const std::string& line); // Zpracuje jednu radku na vstupu dle zadanych pravidel.

	std::size_t current_instruction_; // Index prave vykonavane instrukce (viz. nasledujici vektor).
	std::vector<std::unique_ptr<instruction>> instructions_; // Zde je ulozena posloupnost vsech instrukci.
	std::map<std::string, std::size_t> labels_; // Prvni je jmeno navesti, druhy je cislo radku (instrukce) v kodu (viz. predchozi vektor), indexace od nuly.
	std::array<int, 10> locals_; // Lokalni promenne.
	std::stack<int> stack_; // Vyhodnocovaci zasobnik.

	std::istream& input_;
	std::ostream& output_;
};