/* Ook!/Brainfuck interpreter. 
 * User JavaScript implementation for Opera
 * 
 * Author: Arve Bersvendsen
 * Info:   http://virtuelvis.com/archives/2006/04/ookjaxfuck
 *
 * Loosely based on a Ook! implementation written in Python by
 * Øyvind Grønnesby ( http://www.pvv.ntnu.no/~oyving/code/python/pook.py )
 * 
 */
window.opera.addEventListener('BeforeScript', function (ev){ 
  if (
      (ev.element.hasAttribute("type")) && (
      (ev.element.getAttribute("type") == "text/x-ook")
  ||  (ev.element.getAttribute("type") == "text/x-brainfuck"))){
    var OokJaXFuck = function(source_text,source_type){
      var memory = [];
      var mem_pointer = 0;
      var call_stack = [];
      var run_index = 0;
      var self = this;
      output = "";
      this.parse = function(){
        // reinitialize call_stack, memory, pointers and run_index;
        call_stack = [];
        memory = [];
        run_index = 0;
        mem_pointer = 0;
        output = "";
        if (!tokenMap[source_type]){
          throw source_type+": UNRECOGNIZED_LANGUAGE";
        }
        switch (source_type){
          case "ook":
            var retval = [];
            var tokenList = source_text.split(/\n|\s|;/);
            var tl = tokenList.length;
            for (var i=0; i < tl;i++ ){
              var token = tokenList[i];
              if (token.match(/Ook[.!?]{1}/)){
                retval[retval.length] = token;
              }
            }
            if (retval.length%2 != 0){
              throw source_type+": SYNTAX_ERR_ODD_NUM_TOKENS";
            } else {
              // Normalize the token_list into actual tokens
              for (var i = 0; i < retval.length-1;i+=2){
                call_stack[call_stack.length] = retval[i]+" "+retval[i+1];
              }
            }
            break;
          case "brainfuck":
            for (var i=0,token; token = source_text[i++];){
              if (token.match(/[+\-\[\]<>.,]{1}/)){ 
                call_stack[call_stack.length] = token;
              }
            }
            break;
          default:
            break;
        }
      }
    
      this.run = function(){
        run_index = 0;
        while (run_index < call_stack.length){
          tokenMap[source_type][call_stack[run_index]]();
          run_index++
        }
        eval(output);
      }
      
      this.pointerUp = function(){
        mem_pointer++;
      }
    
      this.pointerDown = function(){
        mem_pointer--;
      }
      
      this.incPointer = function(){
        if (!memory[mem_pointer]) memory[mem_pointer]=0;
        memory[mem_pointer]++;
      }
    
      this.decPointer = function(){
        if (!memory[mem_pointer]) memory[mem_pointer]=0;
        memory[mem_pointer]--;
      }
    
      this.getChar = function(){
        var v = window.prompt("Input char","");
        memory[mem_pointer] = v?v.charCodeAt(0):10;
      }
    
      this.putChar = function(){
        output += String.fromCharCode((memory[mem_pointer]));
      }
    
      this.startConditional = function(){
        var nestCount = 0;
        var i = run_index;
        
        if (memory[mem_pointer] != 0){
          return;
        }
    
        while (true){
          i++;
          if (tokenMap[call_stack[i]] == self.startConditional){
              nestCount++;
          }
          if (tokenMap[call_stack[i]] == self.endConditional){
            if (nestCount == 0) {
              run_index++;
              break;
            } else {       
              nestCount--;
            }
          }
          if (i >= call_stack.length){
            throw source_type+": SYNTAX_ERR_UNMATCHED_START_BLOCK"
          }
        }
      }
    
      this.endConditional = function(){
        var nestCount = 0;
        var i = run_index;
        if (memory[mem_pointer] == 0 || memory[mem_pointer] == undefined) {
          return;
        }
        while (true){
          i--;
          if (tokenMap[source_type][call_stack[i]] == self.endConditional){
              nestCount++;
          }
          if (tokenMap[source_type][call_stack[i]] == self.startConditional){
            if (nestCount == 0) {
              run_index = i;
              break;
            } else {       
              nestCount--;
            }
          }
          if (i >= call_stack.length){
            throw source_type+": SYNTAX_ERR_UNMATCHED_END_BLOCK"
          }
        }
        
      }
    
      // Map the right tokens to the right methods.
      var tokenMap = [];
      tokenMap["brainfuck"]  = {
        '>' : this.pointerUp,
        '<' : this.pointerDown,
        '+' : this.incPointer,
        '-' : this.decPointer,
        ',' : this.getChar,
        '.' : this.putChar,
        '[' : this.startConditional,
        ']' : this.endConditional
      }
      tokenMap["ook"]  = {
        'Ook. Ook?' : this.pointerUp,
        'Ook? Ook.' : this.pointerDown,
        'Ook. Ook.' : this.incPointer,
        'Ook! Ook!' : this.decPointer,
        'Ook. Ook!' : this.getChar,
        'Ook! Ook.' : this.putChar,
        'Ook! Ook?' : this.startConditional,
        'Ook? Ook!' : this.endConditional
      }
      this.parse();
    }
    
    if (ev.element.getAttribute("type") == "text/x-ook"){
      var program = new OokJaXFuck(ev.element.text,"ook").run();
    } else {
      var program = new OokJaXFuck(ev.element.text,"brainfuck").run();
    }
  ev.preventDefault();   
  }
},false);