RAPL

Artifact [a90f439377]
Login

Artifact [a90f439377]

Artifact a90f439377d3511001ddd26035248669fffa042f:


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

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

/* =============================== Parser ================================== */

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

  R.Base.parser_oid++;
  parse.oid = R.Base.parser_oid;

  parse.parse_info = {
    text:       null,                   /* program being parsed */
    len:        0,                      /* Remaining length */
    start:      0,
    end:        0,                      /* Returned token is at start-end in 'text'. */
    index:      0,                      /* index of the point of the program we are parsing */
    cur:        null,                   /* current charcter handled in parsing */
    token:      parse.TOKEN_EOL,        /* Token type */
    eof:        false,                  /* Non zero if EOF condition is true. */
    state:      parse.PARSER_STATE_DEFAULT, /* Parser state */
    line:       null,                   /* Line number of the returned token */
    line_no:    null,                   /* Current line number */
    comment:    true,                   /* Non zero if the next chars may be a comment. */
  };

  parse.parse_result = {
    missing:    " ",                    /* At end of parse, ' ' if complete, '{' if braces */
                                        /* incomplete, '"' if quotes incomplete */
    missing_line: null                  /* Line number starting the missing token */
  };

  parse.debug       = 0;
  parse.escape_chars = "$[{ \t\n\r;";
  parse.expr_operator_chars = "+->=<!*/%&|()eniNI";
  parse.expr_escape_chars = "$[{ \t\n\r;"+parse.expr_operator_chars;
  parse.curr_escape_chars = parse.escape_chars;
  parse.interp      = null;
  parse.no_expr_parsing = false;

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

R.extend(Parser, R.Token, {
  my_name: "RaplParser",

  /* ==================== parserInit ===================================== */
  parserInit: function (code, len, line_no) {
    var parse = this;
    parse.parse_info.text        = code;
    parse.parse_info.len         = len + 1;
    parse.parse_info.start       = 0;
    parse.parse_info.end         = 0;
    parse.parse_info.end         = 0;
    parse.parse_info.line_no     = line_no;
    parse.parse_info.index       = -1;
    parse.parse_info.eof         = false;
    parse.parse_info.line        = 0;
    parse.parse_info.token       = parse.TOKEN_NONE;
    parse.parse_info.state       = parse.PARSER_STATE_DEFAULT;
    parse.parse_info.comment     = true;
    parse.parse_info.cur         = null;
    parse.parse_result.missing      = " ";
    parse.parse_result.missing_line = null;
    parse.feedcharstart();
  },

  /* =============================== parseScript ================================== */
  parseScript: function() {
    var parse = this;

    while(true) {              /* the while is used to reiterate with continue if needed */
      if (parse.parse_info.len <= 0) {
        parse.parse_info.start = parse.parse_info.index;
        parse.parse_info.end = parse.parse_info.index;
	parse.parse_info.line = parse.parse_info.line_no;
	parse.parse_info.token = parse.TOKEN_EOL;
	parse.parse_info.eof = true;
        return parse.OK;
      }
      switch (parse.parse_info.cur) {
      case "\\":
        if (parse.text.charAt(parse.parse_info.index + 1) == "\n" && parse.parse_info.state == parse.PARSER_STATE_DEFAULT) {
          return parse.parseWordSep();
        } else {
	  parse.parse_info.comment = false;
          return parse.parseString();
        }
        break;
      case " ":
      case "\t":
      case "\r":
        if (parse.parse_info.state == parse.PARSER_STATE_DEFAULT) {
          return parse.parseWordSep();
        } else {
	  parse.parse_info.comment = false;
          return parse.parseString();
        }
        break;
      case "\n":
      case ";":
	parse.parse_info.comment = true;
        if (parse.parse_info.state == parse.PARSER_STATE_DEFAULT) {
          return parse.parseEol();
        } else {
          return parse.parseString();
        }
        break;
      case "[":
	parse.parse_info.comment = false;
        return parse.parseCommand();
        break;
       case '$':
         parse.parse_info.comment = false;
         if (parse.parseVar() == parse.ERROR) {
           parse.parse_info.end = parse.parse_info.index - 1;
           parse.parse_info.len--;
           parse.parse_info.line = parse.parse_info.line_no;
           parse.parse_info.token = parse.TOKEN_STR;
           return parse.OK;
        } else {
           return parse.OK;
	}
        break;
      case "#":
        if (parse.parse_info.comment) {
          return parse.parseComment();
        } else {
          return parse.parseString();
        }
        break;
      default:
        parse.parse_info.comment = false;
	return parse.parseString();
        break;
      }
      return parse.OK;
    }
  },

  /* ==================== parseWordSep ===================================== */
  parseWordSep: function () {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index;
    while (" \t\r\\".indexOf(parse.parse_info.cur)>=0) {
      if (parse.parse_info.cur == '\\') {
        if (parse.parse_info.len > 0) {
          if (parse.text.charAt(parse.parse_info.index + 1) == '\n') {
            parse.feedchar();
	  } else {
            break;
	  }
        } else {
          break;
        }
      }
      parse.feedchar();
    }
    parse.parse_info.end  = parse.parse_info.index - 1;
    parse.parse_info.token = parse.TOKEN_WORD_SEP;
    R.log('  parseWordSEP!'+parse.getText()+"!", '2.debug', 'Parser', true);
    return parse.OK;
  },

  /* ==================== parseEol ===================================== */
  parseEol: function () {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
//print("parseEol!"+parse.parse_info.index+"!"+parse.parse_info.len+"!");
    while(1) {
      if (parse.parse_info.cur == "\n") {
        parse.parse_info.line_no++;
      }
      switch(parse.parse_info.cur) {
      case " ":
      case "\n":
      case "\t":
      case "\r":
      case ";":
        parse.feedchar();
	if (parse.parse_info.len == 0) {
          break;
	}
        continue;
      }
      break;
    }
    parse.parse_info.end = parse.parse_info.index - 1;
    parse.parse_info.token = parse.TOKEN_EOL;
    R.log('  parseEol!'+parse.getText()+"!"+parse.parse_info.cur+"!"+parse.parse_info.len+"!", '2.debug', 'Parser', true);
    return this.OK;
  },

  /*
  ** Here are the rules for parsing:
  ** {braced expression}
  ** - Count open and closing braces
  ** - Backslash escapes meaning of braces
  **
  ** "quoted expression"
  ** - First double quote at start of word terminates the expression
  ** - Backslash escapes quote and bracket
  ** - [commands brackets] are counted/nested
  ** - command rules apply within [brackets], not quoting rules (i.e. quotes have their own rules)
  **
  ** [command expression]
  ** - Count open and closing brackets
  ** - Backslash escapes quote, bracket and brace
  ** - [commands brackets] are counted/nested
  ** - "quoted expressions" are parsed according to quoting rules
  ** - {braced expressions} are parsed according to brace rules
  **
  ** For everything, backslash escapes the next char, newline increments current line
  */

  /* ==================== parseSubBrace ===================================== */
  /**
   * Parses a braced expression starting at parse.index.
   *
   * Positions the parser at the end of the braced expression,
   * sets parse.end and possibly parse.missing.
   */

  parseSubBrace: function() {
    var parse = this;
    var level = 1;

    /* Skip the brace */
    parse.feedchar();
    while (parse.parse_info.len) {
      switch (parse.parse_info.cur) {
      case '\\':
        if (parse.parse_info.len > 1) {
          if (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n') {
            parse.parse_info.line_no++;
          }
          parse.feedchar();
        }
        break;
      case '{':
        level++;
        break;
      case '}':
        if (--level == 0) {
          parse.parse_info.end = parse.parse_info.index - 1;
	  parse.feedchar();
          return;
        }
        break;
      case '\n':
        parse.parse_info.line_no++;
        break;
      }
      parse.feedchar();
    }
    parse.parse_result.missing = '{';
    parse.parse_result.missing_line = parse.parse_info.line;
    parse.parse_info.end = parse.parse_info.index - 1;
  },

  /* ==================== parseSubQuote ===================================== */
  /**
   * Parses a quoted expression starting at parse.index.
   *
   * Positions the parser at the end of the quoted expression,
   * sets parse.end and possibly parse.missing.
   *
   * Returns the type of the token of the string,
   * either TOKEN_ESC (if it contains values which need to be [subst]ed)
   * or TOKEN_QUOTE.
   */
  parseSubQuote: function() {
    var parse = this;
    var token = parse.TOKEN_QUOTE;
    var line = parse.parse_info.line;

    /* Skip the quote */
    parse.feedchar();
    while (parse.parse_info.len) {
      switch (parse.parse_info.cur) {
      case '\\':
        if (parse.parse_info.len > 1) {
          if (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n') {
            parse.parse_info.line_no++;
          }
          parse.feedchar();
          token = parse.TOKEN_QUOTE_ESC;
        }
        break;
      case '"':
        parse.parse_info.end = parse.parse_info.index - 1;
	parse.feedchar();
        return token;
      case '[':
        parseSubCmd();
        token = parse.TOKEN_QUOTE_ESC;
        continue;
      case '\n':
        parse.parse_info.line_no++;
        break;
      case '$':
        token = parse.TOKEN_QUOTE_ESC;
        break;
      }
      parse.feedchar();
    }
    parse.parse_result.missing = '"';
    parse.parse_result.missing_line = line;
    parse.parse_info.end = parse.parse_info.index - 1;
    return token;
  },

  /* ==================== parseSubCmd ===================================== */
  /**
   * Parses a [command] expression starting at parse.index.
   *
   * Positions the parser at the end of the command expression,
   * sets parse.end and possibly parse.missing.
   */
  parseSubCmd: function() {
    var parse = this;
    var level = 1;
    var start_of_word = 1;
    var line = parse.parse_info.line;

    /* Skip the bracket */
    parse.feedchar();
    while (parse.parse_info.len) {
    switch (parse.parse_info.cur) {
      case '\\':
        if (parse.parse_info.len > 1) {
          if (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n') {
            parse.parse_info.line_no++;
          }
          parse.feedchar();
        }
        break;
      case '[':
        level++;
        break;
      case ']':
        if (--level == 0) {
          parse.parse_info.end = parse.parse_info.index - 1;
          parse.feedchar();
          return;
        }
        break;
      case '"':
        if (start_of_word) {
          parse.parseSubQuote();
          continue;
        }
        break;
      case '{':
        parse.parseSubBrace();
        start_of_word = 0;
        continue;
      case '\n':
        parse.parse_info.line_no++;
        break;
      }
      start_of_word = parse.hasSpace.test(parse.cur);
      parse.feedchar();
    }
    parse.parse_result.missing = '[';
    parse.parse_result.missing_line = line;
    parse.parse_info.end = parse.parse_info.index - 1;
  },

  /* ==================== parseBrace ===================================== */
  parseBrace: function() {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index + 1;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.TOKEN_BRACE;
    parse.parseSubBrace();
    return parse.OK;
  },

  /* ==================== parseCommand ===================================== */
  parseCommand: function() {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index + 1;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.TOKEN_CMD;
    parse.parseSubCmd();
    return parse.OK;
  },

  /* ==================== parseQuote ===================================== */
  parseQuote: function() {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index + 1;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.parseSubQuote();
    return parse.OK;
  },

  /* ==================== parseVar ===================================== */
  parseVar: function() {
    var parse = this;
    /* skip the $ */
    parse.feedchar();

    if (parse.parse_info.cur == '[') {
        /* Parse $[...] expr shorthand syntax */
        parseCmd();
        parse.parse_info.token = parse.TOKEN_EXPRSUGAR;
        return parse.OK;
    }
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.token = parse.TOKEN_VAR;
    parse.parse_info.line = parse.parse_info.line_no;

    if (parse.parse_info.cur == '{') {
        parse.feedcharstart();

        while (parse.parse_info.len && parse.parse_info.cur != '}') {
            if (parse.parse_info.cur == '\n') {
                parse.parse_info.line_no++;
            }
	    parse.feedchar();
        }
        parse.parse_info.end = parse.parse_info.index - 1;
        if (parse.parse_info.len) {
	    parse.feedchar();
        }
    } else {
      while (1) {
        /* Skip double colon, but not single colon! */
        if (parse.parse_info.cur == ':' && parse.parse_info.text.charAt(parse.parse_info.index + 1) == ':') {
	  parse.feedchar();
	  parse.feedchar();
          continue;
        }
        if (parse.matchVarName.test(parse.parse_info.cur)) {
	  parse.feedchar();
          continue;
        }
        break;
      }
      /* Parse [dict get] syntax sugar. */
      if (parse.parse_info.cur == '(') {
        var count = 1;
        var paren = null;

        parse.parse_info.token = parse.TOKEN_VAR_ARRAY_NAME
        while (count && parse.parse_info.len) {
	  parse.feedchar();
          if (parse.parse_info.cur == '\\' && parse.parse_info.len >= 1) {
	    parse.feedchar();
          } else  {
            if (parse.parse_info.cur == '(') {
              count++;
            } else {
              if (parse.parse_info.cur == ')') {
                paren = parse.parse_info.index;
                count--;
              }
            }
	  }
          if (count == 0) {
	    parse.feedchar();
          } else {
            if (paren) {
              /* Did not find a matching paren. Back up */
              paren++;
              parse.parse_info.len += (parse.parse_info.index - paren);
              parse.parse_info.index = paren;
            }
	  }
          if (parse.parse_info.text.charAt(parse.parse_info.start) == '(') {
            parse.parse_info.token = parse.TOKEN_EXPRSUGAR;
          }
        }
      }
      parse.parse_info.end = parse.parse_info.index - 1;
    }
    /* Check if we parsed just the '$' character.
     * That's not a variable so an error is returned
     * to tell the state machine to consider this '$' just
     * a string. */
    if (parse.parse_info.start == parse.parse_info.index) {
      parse.parse_info.index--;
      parse.parse_info.len++;
      return parse.ERROR;
    }
    return parse.OK;
  },

  /* ==================== parseString ===================================== */
  parseString: function() {
    var parse = this;
//print("parseString!"+parse.parse_info.cur+"!");
    var newword = (parse.parse_info.token == parse.TOKEN_WORD_SEP ||
        parse.parse_info.token == parse.TOKEN_EOL ||
        parse.parse_info.token == parse.TOKEN_NONE ||
       	parse.parse_info.token == parse.TOKEN_STR);
    if (newword && parse.parse_info.cur == '{') {
        return parse.parseBrace();
    } else {
      if (newword && parse.parse_info.cur == '"') {
        parse.parse_info.state = parse.PARSER_STATE_QUOTE;
	parse.feedchar();
        /* In case the end quote is missing */
        parse.parse_result.missing_line = parse.parse_infoline;
      }
    }
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    while (1) {
      if (parse.parse_info.len == 0) {
        if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
          parse.parse_result.missing = '"';
        }
        parse.parse_info.end = parse.parse_info.index - 1;
        if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
          parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
	} else {
          parse.parse_info.token = parse.TOKEN_ESC;
	}
        return parse.OK;
      }
      switch (parse.parse_info.cur) {
      case '\\':
        if (parse.parse_info.state == parse.PARSER_STATE_DEFAULT && parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n') {
          parse.parse_info.end = parse.parse_info.index - 1;
          if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
            parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
	  } else {
            parse.parse_info.token = parse.TOKEN_ESC;
	  }
          return parse.OK;
        }
        if (parse.parse_info.len >= 2) {
          if (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n') {
            parse.parse_info.line_no++;
          }
	  parse.feedchar();
        }
        break;
      case '(':
        /* If the following token is not '$' just keep going */
        if (parse.parse_info.len > 1 && parse.parse_info.text.charAt(parse.parse_info.index + 1) != '$') {
          break;
        }
      case ')':
        /* Only need a separate ')' token if the previous was a var */
        if (parse.parse_info.cur == '(' || parse.parse_info.token == parse.TOKEN_VAR) {
          if (parse.parse_info.index == parse.parse_info.start) {
            /* At the start of the token, so just return this char */
	    parse.feedchar();
          }
          parse.parse_info.end = parse.parse_info.idx - 1;
          if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
            parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
	  } else {
            parse.parse_info.token = parse.TOKEN_ESC;
	  }
          return parse.OK;
        }
        break;
      case '$':
      case '[':
        parse.parse_info.end = parse.parse_info.index - 1;
        if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
          parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
        } else {
          parse.parse_info.token = parse.TOKEN_ESC;
	}
        return parse.OK;
      case ' ':
      case '\t':
      case '\n':
      case '\r':
      case ';':
        if (parse.parse_info.state == parse.PARSER_STATE_DEFAULT) {
          parse.parse_info.end = parse.parse_info.index - 1;
          if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
            parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
	  } else {
            parse.parse_info.token = parse.TOKEN_ESC;
	  }
          return parse.OK;
        } else {
          if (parse.parse_info.cur == '\n') {
            parse.parse_info.line_no++;
          }
        }
        break;
      case '"':
        if (parse.parse_info.state == parse.PARSER_STATE_QUOTE) {
          parse.parse_info.end = parse.parse_info.index - 1;
          parse.parse_info.token = parse.TOKEN_QUOTE_ESC;
	  parse.feedchar();
          parse.parse_info.state = parse.PARSER_STATE_DEFAULT;
          return parse.OK;
        }
        break;
      }
      parse.feedchar();
    }
    return parse.OK;              /* unreached */
  },

  /* ==================== parseComment ===================================== */
  parseComment: function() {
    var parse = this;
    while (parse.parse_info.len) {
      if (parse.parse_info.cur == '\n') {
        parse.parse_info.line_no++;
        if (parse.parse_info.text.charAt(parse.parse_info.index - 1) != '\\') {
          parse.feedchar();
          return parse.OK;
        }
      }
      parse.feedchar();
    }
    return parse.OK;
  },

  /* ==================== parseList ===================================== */
  parseList: function() {
    var parse = this;
    switch (parse.parse_info.cur) {
    case ' ':
    case '\n':
    case '\t':
    case '\r':
      return parse.parseListSep();
    case '"':
      return parse.parseListQuote();
    case '{':
      return parse.parseBrace();
    default:
      if (parse.parse_info.len) {
        return parse.parseListStr();
      }
      break;
    }

    parse.parse_info.end = parse.parse_info.index;
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.TOKEN_EOL;
    parse.parse_info.eof = true;
    return parse.OK;
  },

  /* ==================== parseListSep ===================================== */
  parseListSep: function() {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    while (parse.parse_info.cur == ' ' || parse.parse_info.cur == '\t' ||
        parse.parse_info.cur == '\r' || parse.parse_info.cur == '\n') {
      if (parse.parse_info.cur == '\n') {
        parse.parse_info.line_no++;
      }
      parse.feedchar();
    }
    parse.parse_info.end = parse.parse_info.index - 1;
    parse.parse_info.token = parse.TOKEN_SEP;
    return parse.OK;
  },

  /* ==================== parseListQuote ===================================== */
  parseListQuote: function() {
    var parse = this;
    parse.feedchar();

    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.TOKEN_STR;

    while (parse.parse_info.len) {
      switch (parse.parse_info.cur) {
      case '\\':
        parse.parse_info.token = parse.TOKEN_ESC;
        if (--parse.parse_info.len == 0) {
          /* Trailing backslash */
          parse.parse_info.end = parse.parse_info.index;
          return parse.OK;
        }
        parse.parse_info.index++;
        break;
      case '\n':
        parse.parse_info.line_no++;
        break;
    case '"':
        parse.parse_info.end = parse.parse_info.index - 1;
	parse.feedchar();
        return parse.OK;
      }
      parse.feedchar();
    }
    parse.parse_info.end = parse.parse_info.index - 1;
    return parse.OK;
  },

  /* ==================== parseListStr ===================================== */
  parseListStr: function() {
    var parse = this;
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    parse.parse_info.token = parse.TOKEN_STR;

    while (parse.parse_info.len) {
      switch (parse.parse_info.cur) {
      case '\\':
        if (--parse.parse_info.len == 0) {
          /* Trailing backslash */
          parse.parse_info.end = parse.parse_info.index;
          return parse.OK;
        }
        parse.parse_info.token = parse.TOKEN_ESC;
        parse.parse_info.index++;
        break;
      case ' ':
      case '\t':
      case '\n':
      case '\r':
        parse.parse_info.end = parse.parse_info.index - 1;
        return JIM_OK;
      }
      parse.feedchar();
    }
    parse.parse_info.end = parse.parse_info.index - 1;
    return parse.OK;
  },

  /* ==================== parseParen ===================================== */
  parseParen: function () {
    var parse = this;
    var level = 1;
    parse.feedcharstart();
    while (true) {
      if (parse.parse_info.len > 1 && parse.parse_info.cur == "\\") {
        parse.feedSequence();
      } else {
	if ((parse.parse_info.len == 0) || (parse.parse_info.cur == ")")) {
          level--;
          if (level == 0 || parse.parse_info.len == 0) {
            parse.parse_info.end = parse.parse_info.index - 1;
            if (parse.parse_info.len > 0) {
	      parse.feedchar();
              parse.parse_info.token = parse.TOKEN_LP;
	    }
if (parse.debug) {
print("  parseParen!"+parse.getText()+"!"+parse.parse_info.text.charAt(parse.parse_info.index)+"!"+parse.parse_info.cur+"!");
}
            return parse.OK;
          }
        } else {
	  if (parse.parse_info.cur == "(") {
	    level++;
	  }
	}
      }
      parse.feedchar();
    }
    return parse.OK; // unreached
  },

  /* ==================== parseExpression ================================== */
  parseExpression: function(exp) {
    var parse = this;
    /* Discard spaces and quoted newline */
    while (parse.hasSpace.test(parse.UCHAR(parse.parse_info.cur)) || (parse.parse_info.cur == '\\' && (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '\n'))) {
        if (parse.parse_info.cur == '\n') {
            parse.parse_info.line_no++;
        }
	parse.feedchar();
    }

    if (parse.parse_info.len == 0) {
        parse.parse_info.start = parse.parseinfo.index;
	parse.parse_info.end = parse.parseinfo.index;
        parse.parse_info.line = parse.parse_info.line_no;
        parse.parse_info.token = parse.EOL;
        parse.parse_info.eof = true;
        return parse.OK;
    }
    switch (parse.parse_info.cur) {
    case '(':
      parse.parse_info.token = parse.TOKEN_SUBEXPR_START;
      parse.parse_info.start = parse.parse_info.index
      parse.parse_info.end = parse.parse_info.index;
      parse.parse_info.line = parse.parse_info.line_no;
      parse.feedchar();
      break;
    case ')':
      parse.parse_info.token = parse.TOKEN_SUBEXPR_END;
      parse.parse_info.start = parse.parse_info.index
      parse.parse_info.end = parse.parse_info.index;
      parse.parse_info.line = parse.parse_info.line_no;
      parse.feedchar();
      break;
    case ',':
      parse.parse_info.token = parse.TOKEN_SUBEXPR_COMMA;
      parse.parse_info.start = parse.parse_info.index
      parse.parse_info.end = parse.parse_info.index;
      parse.parse_info.line = parse.parse_info.line_no;
      parse.feedchar();
      break;
    case '[':
      return parse.parseCmd();
    case '$':
      if (parse.parseVar() == parse.ERROR) {
        return parse.parseExprOperator(exp);
      } else {
        /* Don't allow expr sugar in expressions */
        if (parse.parse_info.token == parse.TOKEN_EXPRSUGAR) {
          return parse.ERROR;
        }
        return parse.OK;
      }
      break;
    case '0':
    case '1':
    case '2':
    case '3':
    case '4':
    case '5':
    case '6':
    case '7':
    case '8':
    case '9':
    case '.':
      return parse.parseExprNumber(exp);
    case '"':
      return parse.parseQuote();
    case '{':
      return parse.parseBrace();
    case 'N':
    case 'I':
    case 'n':
    case 'i':
      if (parse.parseExprIrrational() == parse.ERROR)
        return parse.parseExprOperator(exp);
      break;
    default:
      return parse.parseExprOperator(exp);
      break;
    }
    return parse.OK;
  },

  /* ==================== parseExprNumber ================================== */
  parseExprNumber: function(exp) {
    var parse = this;
    var allowdot = 1;
    var allowhex = 0;

    /* Assume an integer for now */
    parse.parse_info.token = parse.TOKEN_EXPR_INT;
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.line = parse.parse_info.line_no;
    while (parse.isInteger.test(parse.UCHAR(parse.parse_info.cur))
        || (allowhex && parse.isHex.test(parse.UCHAR(parse.parse_info.cur)))
        || (allowdot && parse.parse_info.cur == '.')
        || (parse.parse_info.index - parse.parse_info.start == 1 && parse.parse_info.text.charAt(parse.parse_info.start) == '0' && (parse.parse_info.cur == 'x' || parse.parse_info.cur == 'X'))
        ) {
        if ((parse.parse_info.cur == 'x') || (parse.parse_info.cur == 'X')) {
            allowhex = 1;
            allowdot = 0;
        }
        if (parse.parse_info.cur == '.') {
            allowdot = 0;
            parse.parse_info.token = parse.TOKEN_EXPR_DOUBLE;
        }
	parse.feedchar();
        if (!allowhex && (parse.parse_info.cur == 'e' || parse.parse_info.cur == 'E') && (parse.parse_info.text.charAt(parse.parse_info.index + 1) == '-' || parse.parse_info.text.charAt(parse.parse_info.index + 1) == '+'
                || parse.isInteger.test(parse.UCHAR(parse.parse_info.text.charAt(parse.parse_info.index + 1))))) {
	    parse.feedchar();
	    parse.feedchar();
            parse.parse_info.token = parse.TOKEN_EXPR_DOUBLE;
        }
    }
    parse.parse_info.end = parse.parse_info.index - 1;
    return parse.OK;
  },

  /* ==================== parseExprIrrational ================================== */
  parseExprIrrational: function(exp) {
    var tokens = ["NaN", "nan", "NAN", "Inf", "inf", "INF"];
    var token;

    for (var i = 0; i < tokens.length; i++) {
      len = tokens[i].length;
      if (parse.parse_info.text.substring(parse.parse_info.index, parse.parse_info.index + len) == tokens[i]){
            parse.parse_info.start = parse.parse_info.index;
            parse.parse_info.end = parse.parse_info.index + len - 1;
            parse.parse_info.index += len;
            parse.parse_info.len -= len;
            parse.parse_info.line = parse.parse_info.line_no;
            parse.parse_info.token = parse.TOKEN_EXPR_DOUBLE;
            return parse.OK;
        }
    }
    return parse.ERROR;
  },

  UCHAR: function(str) {
   //FIXME !!! need code here for handling of unicode !!!
    return str
  },

  /* ==================== parseExprOperator ================================== */
  parseExprOperator: function(exp) {
    var parse = this;
    var i;
    var bestIdx = -1;
    var bestLen = 0;
print ("E!"+exp+"!");
    var lgth = exp.exprOperators.length;
print ("L!"+lgth+"!");

    /* Try to get the longest match. */
    for (i = 0; i < lgth; i++) {
      var op_name;
      var op_len;

      opname = exp.exprOperators[i].name;
      if (op_name == null) {
        continue;
      }
      op_len = op_name.length;

      if (parse.parse_info.text.substring(parse.parse_info.index, parse.parse_info.index + op_len) == op_name && op_len > bestLen) {
        bestIdx = i + parse.TOKEN_EXPR_OP;
        bestLen = op_len;
      }
    }
    if (bestIdx == -1) {
      return parse.ERROR;
    }

    /* Validate paretheses around function arguments */
    if (bestIdx >= exp.TOKEN_EXPROP_FUNC_FIRST) {
      var idx = parse.parse_info.index + bestLen;
      var p = parse.parse_info.text.charAt(idx);
      var len = parse.parse_info.len - bestLen;

      while (len && parse.hasSpace.test(UCHAR(parse.parse_info.textcharAt(idx)))) {
        len--;
        idx++;
      }
      if (parse.parse_info.textcharAt(idx) != '(') {
        return pasre.ERROR;
      }
    }
    parse.parse_info.start = parse.parse_info.index;
    parse.parse_info.end = parse.parse_info.index + bestLen - 1;
    parse.parse_info.index += bestLen;
    parse.parse_info.len -= bestLen;
    parse.parse_info.line = parse.parse_info.line_no;

    parse.parse_info.token = bestIdx;
    return parse.OK;
  },

  /* ==================== getText ===================================== */
  getText: function () {
    var parse = this;
//print("getText!"+parse.getTokenString(parse.parse_info.token)+"!");
//print("getText text!"+parse.parse_info.text.substring(parse.parse_info.start,parse.parse_info.end+1)+"!type!"+parse.getTokenString(parse.parse_info.token)+"!");
      return parse.parse_info.text.substring(parse.parse_info.start, parse.parse_info.end + 1);
  },

  /* ==================== feedSequence ===================================== */
  feedSequence: function () {
    var parse = this;
    if (parse.parse_info.cur != "\\") {
      throw "Invalid escape sequence";
    }
    var cur = parse.steal(1);
    var specials = new Object();
    specials.a = "\a";
    specials.b = "\b";
    specials.f = "\f";
    specials.n = "\n";
    specials.r = "\r";
    specials.t = "\t";
    specials.v = "\v";
    switch (cur) {
    case 'u':
      var hex = parse.steal(4);
      if (hex != parse.isHexSeq.exec(hex)) {
        throw "Invalid unicode escape sequence: "+hex;
      }
      cur = String.fromCharCode(parseInt(hex,16));
      break;
    case 'x':
      var hex = parse.steal(2);
      if (hex != parse.isHexSeq.exec(hex)) {
        throw "Invalid unicode escape sequence: "+hex;
      }
      cur = String.fromCharCode(parseInt(hex,16));
      break;
    case "a":
    case "b":
    case "f":
    case "n":
    case "r":
    case "t":
    case "v":
      cur = specials[cur];
      break;
    case "\n":
//print("FEED escaped LF1!"+parse.text.substring(parse.index)+"!"+parse.text+"!");
      var tail = parse.text.substring(parse.index+1);
      var head = parse.text.substring(0,parse.index-1);
      parse.parse_info.text = head+" "+tail;
      parse.parse_info.len--;
      parse.parse_info.cur = parse.parse_info.text.charAt(parse.parse_info.index);
//print("FEED escaped LF2!"+parse.parse_info.cur+"!"+parse.parse_info.text.substring(parse.parse_info.index)+"!"+parse.parse_info.text+"!");
      return;
    default:
      if ("0123456789".indexOf(cur) >= 0) {
        cur = cur + parse.steal(2);
        if (cur != parse.isOctalSeq.exec(cur)) {
          throw "Invalid octal escape sequence: "+cur;
	}
        cur = String.fromCharCode(parseInt(cur, 8));
      }
      break;
    }
    var head = parse.parse_info.text.substring(0, parse.parse_info.index);
    var tail = parse.parse_info.text.substring(parse.parse_info.index + 1);
    parse.parse_info.text = head+cur+tail;
    parse.parse_info.cur = parse.parse_info.text.charAt(parse.parse_info.index);
  },

  /* ==================== steal ===================================== */
  steal: function (n) {
    var parse = this;
    var tail = parse.parse_info.text.substring(parse.parse_info.index+1);
    var word = tail.substr(0, n);
    parse.parse_info.text = parse.parse_info.text.substring(0, parse.parse_info.index) + tail.substring(n-1);
    parse.parse_info.len = parse.parse_info.len - n;
    return word;
  },

  /* ==================== feedcharstart ===================================== */
  feedcharstart: function () {
    var parse = this;
    parse.feedchar();
    parse.parse_info.start = parse.parse_info.index;
  },

  /* ==================== setPos ===================================== */
  setPos: function (index) {
    var parse = this;
    var d = index - parse.parse_info.index;
    parse.parse_info.index = index;
    parse.parse_info.len -= d;
    parse.parse_info.cur = parse.parse_info.text.charAt(parse.parse_info.index);
  },

  /* ==================== feedchar ===================================== */
  feedchar: function () {
    var parse = this;
    parse.parse_info.index++;
    parse.parse_info.len--;
    if (parse.parse_info.len < 0) {
      throw "End of file reached";
    }
    parse.parse_info.cur = parse.parse_info.text.charAt(parse.parse_info.index);
  }

});

function PP () {

  /* ==================== setExprParser ===================================== */
  this.setExprParser = function () {
    var parse = this;
    parse.curr_escape_chars = parse.expr_escape_chars;
    parse.expr_parser = true;
  }

  /* ==================== parseList ===================================== */
  this.parseList = function () {
    var parse = this;
    var level = 0;
    parse.start = parse.index;
    parse.parse_info.start = parse.parse_info.index;
    while (true) {
      if (parse.len == 0) {
        parse.end = parse.index;
        parse.token = parse.TOKEN_EOL;
        return;
      }
      switch (parse.cur) {
      case "\\":
        if (parse.len >= 2) {
          parse.feedSequence();
	}
        break;
      case " ": 
      case "\t":
      case "\n":
      case "\r":
        if (level > 0) {
          break;
	}
        parse.end  = parse.index - 1;
        parse.token = parse.TOKEN_WORD_SEP;
        parse.feedchar();
        return;
      case '{':
        level++;
        break;
      case '}':
        level--;
        break;
      }
      parse.feedchar();
    }
    if (level != 0) {
      throw "Not a list";
    }
    parse.end = parse.index;
    return parse.OK;
  }

  /* ==================== getNumLines ===================================== */
  this.getNumLines = function (str) {
    var my_parser = new R.Parser("unknown", 1, str);
    var had_eol = 0;
    my_parser.cur = my_parser.text.charAt(my_parser.index);
    while (true) {
      if (my_parser.len == 0) {
        break;
      }
      if (my_parser.cur == '\n') {
        had_eol++;
      }
      my_parser.feedchar();
    }
    return had_eol;
  }

  /* ==================== parseExprOperator ===================================== */
  this.parseExprOperator = function () {
    var parse = this;
//print("parseExprOperator!"+parse.index+"!"+parse.start+"!"+parse.text.substring(parse.index)+"!");
    if (parse.expr_parser) {
      /* FIXME !!! need to determine length of the following checked string parts !! */
      if (parse.isInteger.test(parse.text.substr(parse.index))) {
        parse.token = parse.TOKEN_INTEGER;
      } else {
        if (parse.isReal.test(parse.text.substr(parse.index))) {
          parse.token = parse.TOKEN_REAL;
        } else {
          if (parse.isHex.test(parse.text.substr(parse.index))) {
            parse.token = parse.TOKEN_HEX;
          } else {
            if (parse.isOctal.test(parse.text.substr(parse.index))) {
              parse.token = parse.TOKEN_OCTAL;
            } else {
//print("parseExprOp cur!"+this.cur+"!");
              switch (parse.cur) {
              case '+':
                parse.token = parse.TOKEN_PLUS;
                break;
              case '-':
                parse.token = parse.TOKEN_MINUS;
                break;
              case '*':
                parse.token = parse.TOKEN_MUL;
                break;
              case '/':
                parse.token = parse.TOKEN_DIV;
                break;
              case '%':
                parse.token = parse.TOKEN_MOD;
                break;
              case '!':
                if ((parse.len > 1) && (parse.text.charAt(parse.index+1) == '=')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_NE;
                } else {
                  parse.token = parse.TOKEN_NOT;
                }
                break;
              case '=':
                if ((parse.len > 1) && (parse.text.charAt(parse.index+1) == '=')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_EQ;
                }
                break;
              case '<':
                if ((parse.len > 1) && (parse.text.charAt(parse.index+1) == '=')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_LE;
                } else {
                  parse.token = parse.TOKEN_LT;
                }
                break;
              case '>':
                if ((parse.len > 1) && (parse.text.charAt(parse.index+1) == '=')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_GE;
                } else {
                  parse.token = parse.TOKEN_GT;
                }
                break;
              case '&':
                if ((parse.len > 1) && (parse.text.charAt(parse.index+1) == '&')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_AND_IF;
                } else {
                  parse.token = parse.TOKEN_AND;
                }
                break;
              case '|':
                if ((tparse.len > 1) && (parse.text.charAt(parse.index+1) == '|')) {
                  parse.feedchar();
                  parse.token = parse.TOKEN_OR_IF;
                } else {
                  parse.token = parse.TOKEN_OR;
                }
                break;
              case '^':
                parse.token = parse.TOKEN_EXOR;
                break;
              case '(':
                parse.token = parse.TOKEN_LP;
                break;
              case ')':
                parse.token = parse.TOKEN_RP;
                break;
              case 'e':
                if (parse.len > 1) {
                  if (parse.text.charAt(parse.index+1) == 'q') {
                    parse.feedchar();
                    parse.token = parse.TOKEN_STR_EQ;
                  } else {
                    return parse.parseString();
                  }
                }
                break;
              case 'n':
                if (parse.len > 1) {
                  if (parse.text.charAt(parse.index+1) == 'e') {
                    parse.feedchar();
                    parse.token = parse.TOKEN_STR_NE;
                  } else {
                    if (parse.text.charAt(parse.index+1) == 'i') {
                      parse.feedchar();
                      parse.token = parse.TOKEN_STR_NI;
                    } else {
                      return parse.parseString();
                    }
                  }
                } else {
                  return parse.parseString();
                }
                break;
              case 'i':
                if (parse.len > 1) {
                  if (parse.text.charAt(parse.index+1) == 'n') {
                    parse.feedchar();
                    parse.token = parse.TOKEN_STR_IN;
                  } else {
                    parse.no_expr_parsing = true;
                    return parse.parseString();
                  }
                } else {
                  return parse.parseString();
                }
                break;
              default: 
//print("expr op str!"+parse.text.substring(parse.index)+"!");
                if (parse.isDecimal.test(parse.text.substr(parse.index))) {
                  parse.token = parse.TOKEN_DECIMAL;
                } else {
                  return parse.parseString();
                }
              }
            }
          }
        }
      }
    }
    parse.feedchar();
    parse.end  = parse.index - 1;
if (parse.debug) {
print("  parseExprOperator!"+parse.getText()+"!"+parse.getTokenString(parse.token)+"!");
}
    return parse.OK;
  }

};

Parser.prototype.constructor = Parser;

R.Parser = Parser;

}, "0.0.1", {});