Whitespaceのスタックマシン作成

Schemeの継続の実装について、いろいろマクロとかevalとかに悩んでで停滞してるので、
今度はWhitespaceに手を出してみました。
俺のニセSchemeいづれはスタックマシンライクなバーチャルマシンで動かすつもりなので、その実験台として、Whitespaceのインタプリタ作成です。

Whitespaceは、スペース(' '), タブ('\t'), 改行('\n')だけで記述されるプログラミング言語で、印刷しても全く読めないため、スパイなどに向いているのだそうです。(Whitespace http://ja.wikipedia.org/wiki/Whitespace)

今回出来たのはスタックマシンのみです。まだWhitespaceのコードを解釈できるようにはなっていません。アセンブリ言語のような中間言語を実行できるだけです。

下記ソースをws.jsとして保存して、コマンドプロンプトから"cscript ws.js"と入力すれば実行できます。ずらーっとログが出たあと、フィボナッチ数列の10番目の値、55がスタックに残っているのが分かるんじゃないかと思います。

作って分かったこと

CPS変換ってのは、スタックマシン用のバイトコードコンパイルできれば、十分なんじゃないかと思う。
あと、バイトコードに変換すると、末尾再帰の判定が簡単に出来そうな気がしてきた。つまり、関数内で自分と同じ関数が呼ばれた(再帰した)後、putLabel, jmpだけを通過してretに到達できていれば、末尾再帰なんだと思う。そしてその時は、再帰する場所で、実際に関数を呼び出すのではなく、今の関数の先頭にジャンプさせるっと・・・。

疑問点

store,retrieve,slide,readNumの各コマンドの実装が自信ない・・。


ソースは以下です。

// ws.js
// Whitespace用、スタックマシンのソース

//入出力関数
function p(x){
  WScript.StdOut.WriteLine(x);
}
function p_all(obj){
  for(var i in obj) p("["+i+"] = "+ obj[i]);
}
function printChar(ch){
  WScript.StdOut.Write(ch);
}
function readChar(){
  if(!WScript.StdIn.AtEndOfStream){
    return WScript.StdIn.Read(1);
  }
  return -1;
}

// 例外
function raise(str){
  throw new Exception(str);
}

// 文字列の置換
String.prototype.gsub = function (re, proc){
  if(this.match(re)){
    var result = "";
    var temp = this;
    while(temp.match(re)){
      result += RegExp.leftContext;
      result += proc(RegExp.lastMatch);
      temp = RegExp.rightContext;
    }
    return result + RegExp.rightContext;
  }else{
    return this;
  }
}

var pc=0;                        // プログラムカウンタ
var table = new Object();        // インストラクションテーブル
var alias = new Object();        // 上記の別名テーブル
var stack = new Array();        // スタック
var call_stack = new Array();    // コールスタック(returnする先)
var heap = new Array();          // ヒープ領域
var jump_table = new Array();    // ジャンプテーブル

// スタック操作
table["s"] = new Object();
table["s"]["s"]  = alias["putNum"]  = putNum;
table["s"]["ls"] = alias["dup"]     = dup;
table["s"]["ts"] = alias["copyAt"]  = copyAt;
table["s"]["lt"] = alias["swap"]    = swap;
table["s"]["ll"] = alias["discard"] = discard;
table["s"]["tl"] = alias["slide"]   = slide;

function putNum(n){
  stack.push(n);
}
function dup(){
  var x = stack.pop();
  stack.push(x);
  stack.push(x);
}
function copyAt(n){
  stack.push(stack[stack.length-1-n]);
}
function swap(n){
  var a = stack.pop();
  var b = stack.pop();
  stack.push(a);
  stack.push(b);
}
function discard(n){
  stack.pop();
}
function slide(n){
  // この処理自信ない
  var x = stack.pop();
  for(var i=0;i<stack.length;i++) stack.pop();
  stack.push(x);
}


// 数値演算
table["ts"] = new Object();
table["ts"]["ss"] = alias["add"] = add;
table["ts"]["st"] = alias["sub"] = sub;
table["ts"]["sl"] = alias["mul"] = mul;
table["ts"]["ts"] = alias["div"] = div;
table["ts"]["tt"] = alias["mod"] = mod;

function add(){
  var b = stack.pop();
  var a = stack.pop();
  stack.push(a+b);
}
function sub(){
  var b = stack.pop();
  var a = stack.pop();
  stack.push(a-b);
}
function mul(){
  var b = stack.pop();
  var a = stack.pop();
  stack.push(a*b);
}
function div(){
  var b = stack.pop();
  var a = stack.pop();
  stack.push(a/b);
}
function mod(){
  var b = stack.pop();
  var a = stack.pop();
  stack.push(a%b);
}


// ヒープアクセス
table["tt"] = new Object();  
table["tt"]["s"] = alias["store"]    = store;
table["tt"]["t"] = alias["retrieve"] = retrieve;

function store(){
  var val = stack.pop();
  var addr = stack.pop();
  heap[addr] = val;
  p(heap);
}
function retrieve(){
  var addr = stack.pop();
  if(!heap[addr]) heap[addr] = 0;
  stack.push(heap[addr]);
  p(heap);
}

// フロー制御
table["l"] = new Object();
table["l"]["ss"] = alias["putLabel"] = putLabel;
table["l"]["st"] = alias["call"] = call;
table["l"]["sl"] = alias["jmp"] = jmp;
table["l"]["ts"] = alias["jpz"] = jpz;
table["l"]["tt"] = alias["jpng"] = jpng;
table["l"]["tl"] = alias["ret"] = ret;
table["l"]["tt"] = alias["exit"] = exit;

function putLabel(label){
  // do nothing
}
function call(label){
  call_stack.push(pc);
  jmp(label);
}
function jmp(label){
  p("goto->"+label+"("+jump_table[label]+")");
  pc=jump_table[label];
}
function jpz(label){
  if( stack.pop() == 0){
    jmp(label);
  }
}
function jpng(label){
  if( stack.pop() < 0){
    jmp(label);
  }
}
function ret(){
  r = call_stack.pop();
  pc = r;
}
function exit(){
  throw new Error("exit");
}


// IO
table["tl"] = new Object();
table["tl"]["ss"] = alias["putc"] = putc;
table["tl"]["st"] = alias["puti"] = puti;
table["tl"]["ts"] = alias["readc"] = readc;
table["tl"]["tt"] = alias["readi"] = readi;

function putc(){
  var c = stack.pop();
  printChar(String.fromCharCode(c));
}
function puti(){
  var i = stack.pop();
  printNum(i);
}
function readc(){
  var c = readChar();
  stack.push(c.charCodeAt(0));
}
function readi(){
  // ここ、自信ない
  var c = readChar();
  stack.push(c-0);
}



// アセンブリ解析
function parse(src){
  // コメント削除
  var s = src.gsub(/\(.*\)/,
    function(x){return "";});
  
  var cmds = new Array();
  s.gsub(/[-_$\w:]+/, function(x){cmds.push(x);})
  
  return cmds;
}

// ジャンプテーブル初期化
function init_jump_table(cmds){
  var m;
  for(var i=0;i<cmds.length;i++){
    if(m = cmds[i].match(/putLabel:(\w+)/)){
      jump_table[m[1]] = i;
    }
  }
}

// アセンブリの命令の配列を評価
function eval(cmds){
  pc=0;
  while(pc<cmds.length){
    p("stack=["+stack.join(",")+"]"+" pc="+pc);
    p(cmds[pc]);
    var m = cmds[pc].match(/([a-z]+)(\:(\w+))?/i);
    if(m){
      var meth = alias[m[1]];
      var arg = m[3];
      if(arg){
        arg.match(/^\d+$/) ?
          meth(m[3]-0):
          meth(m[3]);
      }else{
        try{
          meth();
        }catch(e){
          if(e.message == "exit"){
            // exitコマンドで脱出してきた
            return ;
          }else{
            throw e;
          }
        }
      }
    }else{
      raise("eval");
    }
    pc++;
  }
}

prog=[
  "putNum:6",
  "call:fib",
  
//  "putNum:4",
//  "call:fact",

  "exit",

  "putLabel:fib",
  "dup",
  "putNum:2",
  "sub",
  "jpng:then_fib",
  "jmp:else_fib",
    "putLabel:then_fib",
    "jmp:endif_fib",
  "putLabel:else_fib",
    "dup",
    "putNum:1",
    "sub",
    "call:fib",
    "swap",
    "putNum:2",
    "sub",
    "call:fib",
    "add",
  "putLabel:endif_fib",
  "ret",

  "putLabel:fact",
    "dup",
    "jpz:then_fact",
    "jmp:else_fact",
    "putLabel:then_fact",
      "discard",
      "putNum:1",
      "jmp:end_fact",
    "putLabel:else_fact",
      "dup",
      "putNum:1",
      "sub",
      "call:fact",
      "mul",
    "putLabel:end_fact",
    "ret"

];

cmds = parse(prog.join("\n"));
init_jump_table(cmds);
//p_all(jump_table);
eval(cmds);
p(stack);