This was inspired by the question Passing Programs To A Stack Machine Implemented In C++.
I wanted to make it a bit smarter by adding control functions:
| instruction | op1 | op2 | op3 | description |
|---|---|---|---|---|
jmp |
a | unconditional jump to a |
||
jgt |
a | b | c | jump to c if a > b |
jlt |
a | b | c | jump to c if a < b |
get |
a | push the element at a to the top of the stack |
||
set |
a | b | set the element at b to a |
|
wrt |
a | writes a to the console |
#include <charconv>
#include <functional>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <variant>
#include <vector>
template <class T>
class stackprog
{
public:
static T run(const std::vector<std::string>& arguments)
{
auto instructions = parse(arguments);
return process(instructions);
}
static T run(const std::string& prog)
{
std::vector<std::string> arguments;
std::stringstream ss(prog);
std::string item;
while (getline(ss, item, ' '))
arguments.push_back(item);
return run(arguments);
}
private:
static T pop(std::vector<T>& vec)
{
T retval = vec.back();
vec.pop_back();
return retval;
}
static void jump(std::vector<T>& stack, std::size_t& index, bool gt)
{
std::size_t pos = static_cast<std::size_t>(std::lround(pop(stack)));
T b = pop(stack);
T a = stack.back();
if ((gt && a > b) || (!gt && a < b))
index = pos - 1;
}
typedef std::function<T(T)> func1arg;
typedef std::function<T(T, T)> func2arg;
typedef std::function<void(std::vector<T>&, std::size_t&)> funcctrl;
typedef std::variant<T, func1arg, func2arg, funcctrl> instruction;
static std::vector<instruction> parse(const std::vector<std::string>& args)
{
static std::map<std::string, func1arg> functions1{
{ "abs", [](T a) { return (T)std::fabs(a); } },
{ "cos", [](T a) { return (T)std::cos(a); } },
{ "exp", [](T a) { return (T)std::exp(a); } },
{ "log", [](T a) { return (T)std::log(a); } },
{ "sin", [](T a) { return (T)std::sin(a); } },
{ "tan", [](T a) { return (T)std::tan(a); } },
{ "flr", [](T a) { return (T)std::floor(a); } },
{ "sqrt", [](T a) { return (T)std::sqrt(a); } }
};
static std::map<std::string, func2arg> functions2{
{ "add", [](T a, T b) { return a + b; } },
{ "div", [](T a, T b) { return a / b; } },
{ "mul", [](T a, T b) { return a * b; } },
{ "sub", [](T a, T b) { return a - b; } },
{ "pow", [](T a, T b) { return (T)std::pow(a, b); } },
{ "mod", [](T a, T b) { return (T)std::fmod(a, b); } }
};
static std::map<std::string, funcctrl> functionsc{
{ "jmp", [](std::vector<T>& stack, std::size_t& index) { index = static_cast<std::size_t>(std::lround(pop(stack))) - 1; } },
{ "jgt", [](std::vector<T>& stack, std::size_t& index) { jump(stack, index, true); } },
{ "jlt", [](std::vector<T>& stack, std::size_t& index) { jump(stack, index, false); } },
{ "get", [](std::vector<T>& stack, std::size_t& index) { std::size_t pos = static_cast<std::size_t>(std::lround(pop(stack))); stack.emplace_back(stack[pos]); } },
{ "set", [](std::vector<T>& stack, std::size_t& index) { std::size_t pos = static_cast<std::size_t>(std::lround(pop(stack))); stack[pos] = pop(stack); } },
{ "wrt", [](std::vector<T>& stack, std::size_t& index) { std::cout << pop(stack); } },
};
std::vector<instruction> instructions;
for (auto arg : args)
{
T number;
auto ret = std::from_chars(arg.data(), arg.data() + arg.size(), number);
if (ret.ec == std::errc() && ret.ptr == arg.data() + arg.size())
{
instructions.emplace_back(number);
continue;
}
auto func1 = functions1.find(arg);
if (func1 != functions1.end())
{
instructions.emplace_back(func1->second);
continue;
}
auto func2 = functions2.find(arg);
if (func2 != functions2.end())
{
instructions.emplace_back(func2->second);
continue;
}
auto funcc = functionsc.find(arg);
if (funcc != functionsc.end())
{
instructions.emplace_back(funcc->second);
continue;
}
std::ostringstream oss;
oss << "Error: invalid instruction: " << arg;
throw std::runtime_error(oss.str());
}
return instructions;
}
static T process(const std::vector<instruction>& instructions)
{
std::vector<T> stack;
stack.reserve(instructions.size());
for (size_t i = 0; i < instructions.size(); i++)
{
const auto& inst = instructions[i];
switch (inst.index())
{
case 0:
stack.emplace_back(std::get<T>(inst));
break;
case 1:
stack.emplace_back(std::get<func1arg>(inst)(pop(stack)));
break;
case 2:
stack.emplace_back(std::get<func2arg>(inst)(pop(stack), pop(stack)));
break;
case 3:
std::get<funcctrl>(inst)(stack, i);
break;
}
}
return stack.empty() ? 0 : stack.back();
}
};
int main(int argc, char* argv[])
{
if (argc == 1)
{
// simple loop, expected result: 10000000
std::cout << stackprog<int>::run("0 1 add 1000000 1 jlt") << '\n';
// hypot, expected result: 5
std::cout << stackprog<double>::run("3 2 pow 4 2 pow add sqrt") << '\n';
// calculating pi, expected result: pi to the fifth digit
std::cout << stackprog<double>::run("0 -1 1 get 2 add 1 set 4 1 get div 0 get add 0 set 0 get 1 get 2 add 1 set 4 1 get div sub 0 set 1 get 1e6 4 jlt 0 get") << '\n';
// Hello World, expected result: Hello, World!
std::cout << stackprog<char>::run("72 wrt 101 wrt 108 wrt 108 wrt 111 wrt 44 wrt 32 wrt 87 wrt 111 wrt 114 wrt 108 wrt 100 wrt 33");
}
else
{
// execute from command line arguments
try
{
std::cout << stackprog<double>::run(std::vector<std::string>(argv + 1, argv + argc));
}
catch (std::exception err)
{
std::cerr << err.what();
return 1;
}
}
}