class Spitewaste::CodegenEmitter

Despite my best efforts to torture it into something vaguely resembling a high-level language, Whitespace is quite simple at bottom; so much so that mapping almost all of its instructions to equivalent C++ code is a relatively straightforward endeavor, so long as we don't mind using goto.

The only interesting aspect of this approach is how we go about calling into and returning from subroutines. A call instruction generates a goto to the label in its argument and a label immediately after that goto, whose address gets pushed onto a stack of “call sites”. Naturally, a return instruction simply pops the address to jump to by way of a computed goto.

Public Instance Methods

emit(io: io.puts <<CPP) click to toggle source
# File lib/spitewaste/emitters/codegen.rb, line 13
    def emit io:
      io.puts <<CPP
#include <algorithm>
#include <iostream>
#include <map>
#include <stack>
#include <vector>
#include <gmpxx.h>

using namespace std;
typedef mpz_class num;

vector<num> S;
map<num, num> H;
stack<void *> C;
void *cs;
num n;

num pop() { num c = S.back(); S.pop_back(); return c; }

int main(void) {
CPP
      cs = -1 # call site
      instructions.each do |op, arg|
        io.puts case op
          when :push  ; "S.push_back(#{arg});"
          when :pop   ; "pop();"
          when :dup   ; "S.push_back(S.back());"
          when :swap  ; "reverse(S.end() - 2, S.end());"
          when :copy  ; "S.push_back(S[S.size() - 1 - #{arg}]);"
          when :slide ; "S.erase(S.end() - #{arg} - 1, S.end() - 1);"

          when :add   ; "S[S.size() - 2] += S.back(); S.pop_back();"
          when :sub   ; "S[S.size() - 2] -= S.back(); S.pop_back();"
          when :mul   ; "S[S.size() - 2] *= S.back(); S.pop_back();"
          when :div   ; "{ auto d = pop(); auto n = S.back().get_mpz_t();" +
                        "mpz_fdiv_q(n, n, d.get_mpz_t()); }"
          when :mod   ; "{ auto d = pop(); auto n = S.back().get_mpz_t();" +
                        "mpz_fdiv_r(n, n, d.get_mpz_t()); }"

          when :label ; "L#{arg}:"
          when :jump  ; "goto L#{arg};"
          when :jz    ; "if (!pop()) goto L#{arg};"
          when :jn    ; "if (pop() < 0) goto L#{arg};"
          when :call  ; "C.push(&&C#{cs += 1}); goto L#{arg}; C#{cs}:"
          when :ret   ; "cs = C.top(); C.pop(); goto *cs;"
          when :exit  ; "goto done;"

          when :ichr  ; "H[pop()] = cin.get();"
          when :inum  ; "cin >> n; cin.ignore(); H[pop()] = n;"
          when :ochr  ; "cout << (char) pop().get_ui();"
          when :onum  ; "cout << pop();"
          when :store ; "n = pop(); H[pop()] = n;"
          when :load  ; "S.push_back(H[pop()]);"
        end
      end
      io.puts 'done: return S.size(); }' # punish dirty exit
    end