#include <string>
#include <stack>
#include <map>
#include <iostream>

typedef long val_t;
typedef std::map<std::string, val_t> frame_t;
typedef std::stack<frame_t> stack_t;

stack_t stack;
frame_t globals;

val_t NOWHERE, arg1_tmp, arg2_tmp, arg3_tmp, result_tmp;

#define BEGIN_FUNCTION(name) \
  goto end##name; \
  name: ;

#define END_FUNCTION(name) \
  RETURN(0); \
  end##name: ;

#define FRAME (stack.top())
#define LOCAL(name) (FRAME[#name])
#define GLOBAL(name) (globals[#name])
#define SPEC(name) (FRAME[":" #name])
#define ARG1 SPEC(ARG1)
#define ARG2 SPEC(ARG2)
#define ARG3 SPEC(ARG3)
#define _RET SPEC(RET)
#define _RES SPEC(RES)

#define RETURN(val) \
  do { \
    _RES = val; \
    goto *(void*)_RET; \
  } while(0);

#define CALL_START stack.push(frame_t())

#define CONCAT(x,y) x##y

#define CALL_END(name, uniqueid) \
  _RET = (val_t) &&CONCAT(here, uniqueid); \
  goto name; \
  CONCAT(here, uniqueid): ;

#define PUT_RESULT(where) \
  result_tmp = _RES; \
  stack.pop(); \
  where = result_tmp;

#define CALL0(func, out) \
  CALL_START; \
  CALL_END(func, __LINE__); \
  PUT_RESULT(out);

#define CALL1(func, arg, out) \
  arg1_tmp = arg; \
  CALL_START; \
  ARG1 = arg1_tmp; \
  CALL_END(func, __LINE__); \
  PUT_RESULT(out);

#define CALL2(func, arg1, arg2, out) \
  arg1_tmp = arg1; \
  arg2_tmp = arg2; \
  CALL_START; \
  ARG1 = arg1_tmp; \
  ARG2 = arg2_tmp; \
  CALL_END(func, __LINE__); \
  PUT_RESULT(out);

#define CALL3(func, arg1, arg2, arg3, out) \
  arg1_tmp = arg1; \
  arg2_tmp = arg2; \
  arg3_tmp = arg3; \
  CALL_START; \
  ARG1 = arg1_tmp; \
  ARG2 = arg2_tmp; \
  ARG3 = arg3_tmp; \
  CALL_END(func, __LINE__); \
  PUT_RESULT(out);

int main() {

  BEGIN_FUNCTION(fac);
    LOCAL(f) = 1;
    while(ARG1 > 0) {
      LOCAL(f) *= ARG1--;
    }
    RETURN(LOCAL(f));
  END_FUNCTION(fac);
  
  BEGIN_FUNCTION(fibs);
    if(ARG1 <= 0) RETURN(0);
    if(ARG1 == 1) RETURN(1);
    
    CALL1(fibs, ARG1 - 1, LOCAL(n1));
    CALL1(fibs, ARG1 - 2, LOCAL(n2));
    RETURN(LOCAL(n1) + LOCAL(n2));
  END_FUNCTION(fibs);
  
  CALL1(fac, 5, GLOBAL(fac5));
  CALL1(fibs, 10, GLOBAL(fibs10));
  
  BEGIN_FUNCTION(print);
    std::cout << "VALUE: " << ARG1 << std::endl;
  END_FUNCTION(print);

  BEGIN_FUNCTION(print_a);
    std::cout << "A: " << GLOBAL(a) << std::endl;
  END_FUNCTION(print_a);
  
  CALL1(print, GLOBAL(fac5), NOWHERE); // 120
  GLOBAL(a) = GLOBAL(fibs10); // 55
  CALL0(print_a, NOWHERE);
  
}
