/*====================================================
 * rapl_script.js "A Tcl like language implementation in Javascript named WebRAPL 
 * (Web Rapid Application Programming Language)"
 *
 * RAPL script implementation
 *
 * Released under BSD license.
 * (BSD license found at <http://www.tcl.tk/software/tcltk/license.html>)
 *
 * Arnulf Wiedemann    2011
 */

/* The token_info container below is the script object internal representation. An array of
 * token_info structures, including a pre-computed representation of the
 * command length and arguments.
 *
 * For example the script:
 *
 * puts hello
 * set $i $x$y [foo]BAR
 *
 * will produce a ScriptObj with the following Tokens:
 *
 * LIN 2
 * ESC puts
 * ESC hello
 * LIN 4
 * ESC set
 * VAR i
 * WRD 2
 * VAR x
 * VAR y
 * WRD 2
 * CMD foo
 * ESC BAR
 *
 * "puts hello" has two args (LIN 2), composed of single tokens.
 * (Note that the WRD token is omitted for the common case of a single token.)
 *
 * "set $i $x$y [foo]BAR" has four (LIN 4) args, the first word
 * has 1 token (ESC SET), and the last has two tokens (WRD 2 CMD foo ESC BAR)
 *
 * The precomputation of the command structure makes Eval() faster,
 * and simpler because there aren't dynamic lengths / allocations.
 *
 * -- {expand}/{*} handling --
 *
 * Expand is handled in a special way.
 *
 *   If a "word" begins with {*}, the word token count is -ve.
 *
 * For example the command:
 *
 * list {*}{a b}
 *
 * Will produce the following cmdstruct array:
 *
 * LIN 2
 * ESC list
 * WRD -1
 * STR a b
 *
 * Note that the 'LIN' token also contains the source information for the
 * first word of the line for error reporting purposes
 *
 * -- the substFlags field of the structure --
 *
 * The scriptObj structure is used to represent both "script" objects
 * and "subst" objects. In the second case, the there are no LIN and WRD
 * tokens. Instead SEP and EOL tokens are added as-is.
 * In addition, the field 'substFlags' is used to represent the flags used to turn
 * the string into the internal representation used to perform the
 * substitution. If this flags are not what the application requires
 * the scriptObj is created again. For example the script:
 *
 * subst -nocommands $string
 * subst -novariables $string
 *
 * Will recreate the internal representation of the $string object
 * two times.
 */

RP.add("rapl-script", function(R, name) {

function RaplScript(interp) {
  R.log('constructor called', '2.life', 'RaplScript', true);
  // kweight
  var script = this;
  var constructor = script.constructor;
  RaplScript.superclass.constructor.apply(script, arguments);

  R.Base.script_oid++;
  script.oid = R.Base.script_oid;

  script.interp = interp;
  script.parse_token_list = null;
  script.result = null;
  script.token_idx = null;
  script.text = null;
  script.text_len = null;
  script.script_object = {
    len: null,	              /* Length as number of tokens. */
    tokens: null,             /* Tokens array. */
    subst_flags: null,        /* flags used for the compilation of "subst" objects */
    in_use: null,             /* Used to share a ScriptObj. Currently
                                 only used by evalObj() as protection against
                                 shimmering of the currently evaluated object. */
    file_name_obj: null,
    line: null                /* Line number of the first line */
   };

  R.log('constructor end', '2.life', 'RaplScript', true);
}

R.extend(RaplScript, R.RaplObject, {
  my_name: "RaplScript",

  /* ==================== TOKEN_IS_SEP ================================== */
  TOKEN_IS_SEP: function(token) {
    var script = this;
    return (token >= script.TOKEN_WORD_SEP && token <= script.TOKEN_EOF);
  },

  /* ==================== toString ================================== */
  toString: function() {
    var script = this;

    return script.text;
  },

  /* ==================== scriptTokenListInit ================================== */
  scriptTokenListInit: function() {
    var script = this;
    script.parse_token_list = new Array();
  },

  /* ==================== scriptTokenListFree ================================== */
  scriptTokenListFree: function() {
    var script = this;
    script.parse_token_list = null;
  },

  /* ==================== scriptAddToken ================================== */
  scriptAddToken: function(start, len, token, line) {
    var script = this;
//var str = script.text.substring(start, start +len);
//print("Add!"+start+"!"+len+"!"+script.getTokenString(token)+"!"+line+"!str!"+str+"!");
    var token_info = {
      start: start,
      len: len,
      token: token,
      line: line
    };
//print("To!"+script.getTokenString(token_info.token)+"!"+token_info.start+"!");
    script.parse_token_list.push(token_info);
  },

  /* ==================== scriptObjAddTokens ================================== */
  /**
   * Takes a tokenlist and creates the allocated list of script tokens
   * in script.script_object.tokens, of length script.script_object.len.
   *
   * Unnecessary tokens are discarded, and LINE and WORD tokens are inserted
   * as required.
   *
   * Also sets script.script_object.line to the line number of the first token
   */
  scriptObjAddTokens: function() {
    var script = this;
    var line_args = 0;  /* Number of tokens so far for the current command */
    var line_first_idx; /* This is the first token for the current command */
    var count;
    var line_no;
    var debug_script_tokens = 0;
    var parse_token;
    var token_list_idx;
    var token_info;

    if (debug_script_tokens) {
      print("======== Tokens ======");
      for (var i = 0; i < script.parse_token_list.length; i++) {
        parse_token = script.parse_token_list[i];
        print("["+i+"]@"+parse_token.line+" "+script.getTokenString(parse_token.token)+" '"+script.text.substring(parse_token.start, parse_token.start + parse_token.len)+"'");

      }
      print("======== Tokens END ======");
    }
    /* May need up to one extra script token for each EOL in the worst case */
    count = script.parse_token_list.length;
    for (var i = 0; i < script.parse_token_list.length; i++) {
      parse_token = script.parse_token_list[i];
      if (parse_token.token == script.TOKEN_EOL) {
        count++;
      }
    }

    line_no = script.parse_token_list[0].line;
    script.script_object.line = line_no;

    script.script_object.tokens = new Array();
    for (var i = 0; i < count; i++) {
      token_info = {
        token: null,
        obj_ptr: null
      };
      script.script_object.tokens.push(token_info);
    }
    script.token_idx = 0;

    /* This is the first token for the current command */
    token_list_idx = 0;
    line_first_idx = script.token_idx;
    script.token_idx++;

    while (token_list_idx < script.parse_token_list.length) {
      /* Look ahead to find out how many tokens make up the next word */
      var word_tokens;
      parse_token = script.parse_token_list[token_list_idx];

//print("11["+token_list_idx+"]@"+parse_token.line+" "+script.getTokenString(parse_token.token)+" '"+script.text.substring(parse_token.start, (parse_token.start + parse_token.len))+"'");
      /* Skip any leading separators */
      while (parse_token.token == script.TOKEN_WORD_SEP) {
        token_list_idx++;
	parse_token = script.parse_token_list[token_list_idx];
      }

      word_tokens = script.countWordTokens(token_list_idx);

      if (word_tokens == 0) {
        /* None, so at end of line */
        if (line_args) {
	  var my_token_info = script.script_object.tokens[line_first_idx];
          my_token_info.token = script.TOKEN_LINE;
	  my_token_info.obj_ptr = script.interp.script_line_obj_type.newScriptLineObject(line_args, line_no);
	  my_token_info.obj_ptr.incrRefCount();

	  /* Reset for new line */
	  line_args = 0;
	  line_first_idx = script.token_idx;
	  script.token_idx++;
	}
	token_list_idx++;
	continue;
      } else {
        if (word_tokens != 1) {
          /* More than 1, or {expand}, so insert a WORD token */
          token_info = script.script_object.tokens[script.token_idx];
	  token_info.token = script.TOKEN_WORD;
	  token_info.obj_ptr = script.interp.int_obj_type.newIntObj(word_tokens);
	  token_info.obj_ptr.incrRefCount();
	  script.token_idx++;
	  if (word_tokens < 0) {
            /* Skip the expand token */
	    token_list_idx++;
	    word_tokens = -word_tokens - 1;
	    line_args--;
	  }
	}

	if (line_args == 0) {
          /* First real token on the line, so record the line number */
          line_no = script.parse_token_list[token_list_idx].line;
	}
	line_args++;

	/* Add each non-separator word token to the line */
	while (word_tokens--) {
          parse_token = script.parse_token_list[token_list_idx];
	  token_list_idx++;
	  token_info = script.script_object.tokens[script.token_idx];
	  token_info.token = parse_token.token;
	  /* put the token info into a new string obj in th ebytes part */
	  token_info.obj_ptr = script.interp.script_obj_type.makeScriptObj(script.interp, parse_token, script.text);
	  token_info.obj_ptr.incrRefCount();

	  /* Every object is initially a string, but the
	   * internal type may be specialized during execution of the
	   * script.
	   * The bytes part still contains the token info!!
	   */
	  script.interp.source_obj_type.setSourceInfo(token_info.obj_ptr, script.script_object.file_name_obj, parse_token.line);
          script.token_idx++;
        }
      }
    }

    if (line_args == 0) {
      script.token_idx--;
    }

    script.len = script.token_idx;

    if (debug_script_tokens) {
print("scriptObjAddTokens!"+script.script_object.file_name_obj+"!");
      var fname = script.interp.string_obj_type.getString(script.script_object.file_name_obj);
      print("======== Script ("+fname+") ======");
      for (var i = 0; i < script.token_idx; i++) {
        var token_info = script.script_object.tokens[i];
        print("["+i+"]@"+script.getTokenString(token_info.token)+"!"+script.interp.string_obj_type.getString(token_info.obj_ptr)+"!");
      }
      print("======== Script END ======");
    }
    script.panic(script.len > count, "scriptObjAddTokens: script.len > count: "+script.len+"!"+count);
  },

  /* ==================== getText ================================== */
  getText: function(start_idx, len) {
    var script = this;

    return script.text.substring(start_idx, start_idx + len);
  },

  /* ==================== countWordTokens ================================== */
    /* Counts the number of adjoining non-separator.
     *
     * Returns -ve if the first token is the expansion
     * operator (in which case the count doesn't include
     * that token).
     */
  countWordTokens: function(idx) {
    var script = this;
    var expand = 1;
    var count = 0;

    /* Is the first word {*} or {expand}? */
    var parse_token = script.parse_token_list[idx];
    if (parse_token.token == script.TOKEN_BRACE && !script.TOKEN_IS_SEP(script.parse_token_list[idx + 1].token)) {
      if ((parse_token.len == 1 && script.text.charAt(parse_token.start) == '*') 
          || (parse_token.len == 6 && script.text.substring(parse_token.start, parse_token.start + 6) == "expand")) {
        /* Create an expand token */
        expand = -1;
	idx++;
        parse_token = script.parse_token_list[idx];
      }
    }
    /* Now count non-separator words */
    while (!script.TOKEN_IS_SEP(parse_token.token)) {
      idx++;
      parse_token = script.parse_token_list[idx];
      count++;
    }
    return count * expand;
  },

  /* ==================== substObjAddTokens ================================== */
  substObjAddTokens: function(idx, token_info) {
    var script = this;
    var i;
    var token;
    var count;
    var debug_script_tokens = 1;
    var token_list_idx = 0;

    if (debug_script_tokens) {
      print("======== Subst ======");
      for (var i = 0; i < script.parse_token_list.length; i++) {
        parse_token = script.parse_token_list[i];
        print("["+i+"]@"+parse_token.line+" "+script.getTokenString(parse_token.token)+" '"+script.text.substring(parse_token.start, parse_token.start + parse_token.len)+"'");

      }
      print("======== Subst END ======");
    }
    script.script_object.tokens = new Array();
    count = script.parse_token_list.length;
    for (var i = 0; i < count; i++) {
      token_info = {
        token: null,
        obj_ptr: null
      };
      script.script_object.tokens.push(token_info);
    }
    script.token_idx = 0;

    while (token_list_idx < script.parse_token_list.length) {
      parse_token = script.parse_token_list[token_list_idx];
      token_list_idx++;
      token_info = script.script_object.tokens[script.token_idx];
      token_info.token = parse_token.token;
      token_info.obj_ptr = script.interp.script_obj_type.makeScriptObj(script.interp, parse_token, script.text);
      token_info.obj_ptr.incrRefCount();
      script.token_idx++;
    }
    script.len = script.token_idx;
    if (debug_script_tokens) {
print("scriptObjAddTokens!"+script.script_object.file_name_obj+"!");
      var fname = script.interp.string_obj_type.getString(script.script_object.file_name_obj);
      print("======== Script ("+fname+") ======");
      for (var i = 0; i < script.token_idx; i++) {
        var token_info = script.script_object.tokens[i];
        print("["+i+"]@"+script.getTokenString(token_info.token)+"!"+script.interp.string_obj_type.getString(token_info.obj_ptr)+"!");
      }
      print("======== Script END ======");
    }
  },

  /* ==================== dumpToken ================================== */
  dumpToken: function(idx, token_info) {
    var script = this;

print("dumpToken["+idx+"]"+script.getTokenString(token_info.token)+"!");
    print("["+idx+"]@"+script.getTokenString(token_info.token)+"!"+script.interp.string_obj_type.getString(token_info.obj_ptr)+"!");
   },

});

RaplScript.prototype.constructor = RaplScript;

R.RaplScript = RaplScript;

}, "0.0.1", {});
