var globalVars,globalFuncs,globalClasses,curClass,curFunc,instrCnt,instrRevCnt,helperFunc,warning;

var convOptions = {
  openbrNewLine:false,
  phpVer:       '4',       // target PHP version
  returnVar:    '_retval', // return variable name in functions
  withVar:      '_withval',// used to emulate vb with statements
  tabSize:      2,         // tab size for indenting result
  cookieSep:    '__',      // used to separate cookies with keys
  libDir:       '/dbprod/rico-test8/asp/',  // used to read in #include files
  showWarnings: true       // show warnings?
}

function convert(txt,level,isasp) {
  globalVars=new SymbolTable();
  globalFuncs=new SymbolTable();
  globalClasses=new SymbolTable();
  var pass1=aspConvPass1.convert(txt,isasp);
  if (typeof pass1=='string') return 'Error in pass 1\n'+pass1;
  if (level=='1') return pass1.toString();

  var pass2=aspConvPass2.convert(pass1);
  if (typeof pass2=='string') return 'Error in pass 2\n'+pass2;
  if (level=='2') return pass2.toString();

  warning='';
  convOptions.phpVer=level;
  curClass=null;
  curFunc=null;
  instrCnt=0;
  instrRevCnt=0;
  var retval=pass2.convert2php(0);
  // collapse %><%
  retval=retval.replace(/^\?>\s*<\?\s+/img, "");
  // remove any initial newlines
  retval=retval.replace(/$\s+</m, "<");
  return retval
}

String.prototype.removeNonPrintable = function() {
  return this.replace(/[\x00-\x08\x0a-\x1f]/,'');
}

String.prototype.repeat = function(cnt) {
  var arrTmp = new Array(cnt+1);
  return arrTmp.join(this);
}

function spaces(lvl) {
  if (lvl<=0) return '';
  return ' '.repeat(lvl*convOptions.tabSize);
}

function startBlock(lvl) {
  if (convOptions.openbrNewLine)
    return '\n'+spaces(lvl)+'{';
  else
    return ' {';
}

Array.prototype.toString = function() {
  return this.join('\n');
}

// zero-fill
Number.prototype.zf = function(slen) {
    var s=this.toString();
    while (s.length<slen) s='0'+s;
    return s;
}

function parseNode1(id,type,value) {
  this.section=id;
  this.toktyp=type;
  this.tokval=value;
}

parseNode1.prototype.toString = function() {
  return this.section+' '+this.toktyp+'\n'+this.tokval+'\n';
}

function parseNode2(id,type) {
  this.section=id;
  this.toktyp=type;
}

parseNode2.prototype.toString = function(lvl) {
  if (typeof lvl=='undefined') lvl=0;
  var retval=spaces(lvl)+this.section+' '+this.toktyp
  if (this.tokval) retval+=' '+this.tokval;
  if (this.children) {
    for (var i=0; i<this.children.length; i++) {
      retval+='\n'+lvl.zf(2)+'.'+i.zf(2)+' '+this.children[i].toString(lvl+1);
    }
  }
  return retval;
}

Array.prototype.convert2php = function(lvl) {
  var retval='';
  if (lvl<0) {
    for (var i=0; i<this.length; i++)
      retval+=this[i].convert2php(lvl);
  } else {
    for (var i=0; i<this.length; i++) {
      //warning='';
      retval+='\n'+this[i].convert2php(lvl);
      if (warning && convOptions.showWarnings) retval+=' // WARNING:'+warning;
      warning='';
    }
  }
  return retval;
}

parseNode2.prototype.convert2php = function(lvl) {
  //if (warning) return '';
  switch (this.toktyp) {
    case 'html':
      return this.tokval;
    case 'vbscript':
      if (this.children.length==0) return '';
      helperFunc='';
      var retval=this.children.convert2php(0);
      // optimize
      // transform places where +=, -=, or .= can be used
      retval=retval.replace(/^(\s*)(\$[a-z_][a-z_0-9\[\]'->]*)\s*=\s*\2\s*([+-.\*/])\s*/img, "$1$2$3=");
      // transform places where ++ or -- can be used
      retval=retval.replace(/^(\s*)(\$[a-z_][a-z_0-9\[\]'->]*)([+-])=1;/img, "$1$2$3$3;");
      // transform: var1=xyz; return var1; to return xyz
      retval=retval.replace(/^\s*(\$[a-z_][a-z_0-9]*)\s*=\s*(.*);\n(\s*return\s+)\1;/img, "$3$2;");
      // transform: if|when (abc-1 >= xyz) to if|when (abc > xyz)
      retval=retval.replace(/^(\s*)(if|when)(\s*)\((.*)-1\s*>=(.*)\)/img, "$1$2$3($4 > $5)");
      // transform: if|when (abc >= xyz-1) to if|when (abc > xyz)
      retval=retval.replace(/^(\s*)(if|when)(\s*)\((.*)>=(.*)-1\)/img, "$1$2$3($4>$5)");
      // collapse "str1"."str2"
      for (var i=0; i<2; i++)
        retval=retval.replace(/([^.\\]".*[^.\\])"\s*\.\s*"(.*[^.\\]")/mg, "$1$2");
      if (helperFunc.length>0) retval+='\n\n// Helper Functions\n'+helperFunc;
      helperFunc='';
      return '<? '+retval+'\n?>';
    case 'includefile':
      var retval='<?\nrequire '+this.tokval.replace(/\.(asp|vbs)\b/im,'.php')+';';
      var url=convOptions.libDir+this.tokval.replace(/^"(.*)"$/,"$1");  // remove quotes
      var oReq=new Ajax.Request(url, {method:'get',asynchronous:false});
      if (oReq.responseIsSuccess()) {
        alert('Reading: '+url);
        var pass1=aspConvPass1.convert(oReq.transport.responseText,true);
        if (typeof pass1!='string')
          aspConvPass2.convert(pass1);
      } else {
        retval+=' // WARNING: could not load '+this.tokval;
      }
      retval+='\n?>';
      return retval;
    case 'let':
      var n=(curFunc && this.tokval.toLowerCase()==curFunc.tokval.toLowerCase()) ? convOptions.returnVar : convertVar(this.tokval);
      return spaces(lvl)+'$'+n+'='+this.children.convert2php(-1)+';';
    case 'ref':
      // need to add test for inside "with"
      return '->'+this.tokval;
    case 'exit':
      switch (this.tokval) {
        case 'for': return spaces(lvl)+'break;';
        case 'sub': return spaces(lvl)+'return;';
        case 'function': return spaces(lvl)+'return $'+convOptions.returnVar+';';
      }
      return '?';
    case 'index':    return '['+this.children.convert2php(-1)+']';
    case 'pexpr':    return '('+this.children.convert2php(-1)+')';
    case 'class':    return convertClass(this,lvl);
    case 'property':
    case 'sub':      return convertFunc(this,lvl,false);
    case 'function': return convertFunc(this,lvl,true);
    case 'qname1':
      var retval=convertQName(this);
      if (retval) return spaces(lvl)+retval+';';
      return '';
    case 'qname':    return spaces(lvl)+convertQName(this);
    case 'for':      return convertFor(this,lvl);
    case 'parm':     return this.children.convert2php(-1);
    case 'comment':  return spaces(lvl)+'//'+this.tokval;
    case 'caseSel':  return spaces(lvl)+'case '+this.children.convert2php(-1)+':';
    case 'case':
      var retval=(this.Selectors.length==0) ? '\n'+spaces(lvl)+'default:' : this.Selectors.convert2php(lvl);
      retval+=this.children.convert2php(lvl+1);
      retval+='\n'+spaces(lvl+1)+'break;';
      return retval;
    case 'assign':
      return this.children.convert2php(-1);
    case 'while':
    case 'switch':
    case 'if':
    case 'else':
    case 'elseif':
      var retval=spaces(lvl)+this.toktyp;
      if (this.Condition) retval+=' ('+this.Condition.convert2php(-1)+')'
      retval+=startBlock(lvl);
      retval+=this.children.convert2php(lvl+1);
      retval+='\n'+spaces(lvl)+'}';
      return retval;
    case 'paren':
    case 'lit-str':
    case 'lit-num-dec': return this.tokval;
    case 'lit-dat':     return 'strtotime("'+this.tokval+'")';
    case 'op':          return convertOp(this.tokval);
    default:
      warning+=' cannot convert '+this.toktyp;
      return '';
  }
}

function convertOp(op) {
  switch (op) {
    case 'not': return '!';
    case 'or':  return ' || ';
    case 'and': return ' && ';
    case 'is':
    case '=':  return ' == ';
    case '<>':  return ' != ';
    case 'mod': return ' % ';
    case '&': return '.';
    case '-': return '-';  // no spaces, so that unary minus works correctly
  }
  return ' '+op+' ';
}

function convertFuncDefParms(parms) {
  var retval='';
  for (var i=0; i<parms.length; i++) {
    if (i>0) retval+=', ';
    if (parms[i].toktyp=='parm-byref') retval+='&';
    retval+='$'+parms[i].tokval;
  }
  return retval;
}

function adjustExpr(node,offset) {
  var retval=node.convert2php(-1);
  if (retval.match(/^\d+$/))
    return parseInt(retval)+parseInt(offset);
  else if (retval.match(/(\s*[+-]\s*)(\d+)$/)) {
    offset=eval(RegExp.$2+RegExp.$1+offset);
    retval=RegExp.leftContext;
    if (offset!=0) retval+=RegExp.$1+offset;
    return retval;
  }
  return retval+offset;
}

function convertFor(node,lvl) {
  var qtype=nameType(node.tokval);
  if ((curFunc && qtype!='vf') || (!curFunc && qtype!='vg'))
    pass3AddVar(node.tokval);
  if (node.loopStart) {
    var retval=spaces(lvl)+'for ($'+node.tokval+'=';
    retval+=node.loopStart.convert2php(-1)+'; ';
    retval+='$'+node.tokval+'<='+node.loopEnd.convert2php(-1)+'; ';
    retval+='$'+node.tokval+'++)';
  } else {
    var retval=spaces(lvl)+'foreach (';
    retval+=node.loopEach.convert2php(-1);
    retval+=' as $'+node.tokval+')';
  }
  retval+=startBlock(lvl);
  retval+=node.children.convert2php(lvl+1);
  retval+='\n'+spaces(lvl)+'}';
  return retval;
}

function convertClass(node,lvl) {
  var retval='\n'+spaces(lvl)+'class '+node.tokval
  retval+=startBlock(lvl);
  var symtab=node.varList.symtab;
  for (var i=0; i<symtab.length; i++) {
    var decl=symtab[i].scope && convOptions.phpVer>='5' ? symtab[i].scope : 'var';
    retval+='\n'+spaces(lvl+1)+decl+' $'+symtab[i].symbol;
    if (symtab[i].isArray) retval+='=array()';
    retval+=';'
  }
  curClass=node;
  retval+=node.children.convert2php(lvl+1);
  curClass=null;
  retval+='\n'+spaces(lvl)+'}';
  return retval;
}

function convertFunc(node,lvl,retFlag) {
  var n=node.tokval;
  var retval='';
  if (curClass!=null) {
    switch (n.toLowerCase()) {
      case "class_initialize":
        if (convOptions.phpVer=='4')
          n=curClass.tokval;
        else
          n="__construct";
        break;
      case "class_terminate":
        if (convOptions.phpVer=='4')
          warning+=' class_terminate will not be automatically called';
        else
          n="__destruct";
        break;
    }
  }
  retval+='\n'+spaces(lvl)+'function '+n+'('
  retval+=convertFuncDefParms(node.parms);
  curFunc=node;
  retval+=')'
  retval+=startBlock(lvl);
  retval+=node.children.convert2php(lvl+1);
  if (retFlag) retval+='\n'+spaces(lvl+1)+'return $'+convOptions.returnVar+';';
  retval+='\n'+spaces(lvl)+'}';
  curFunc=null;
  return retval;
}

// node should have toktyp 'parms'
function convertParms(node,brackets) {
  var retval=brackets.charAt(0);
  if (node) {
    for (var i=0; i<node.children.length; i++) {
      if (i>0) retval+=', ';
      retval+=node.children[i].convert2php(-1);
    }
  }
  return retval+brackets.substr(1);
}

function nameType(qname) {
  // is it a variable?
  if (curFunc && curFunc.varList.exists(qname)) return 'vf';
  if (curClass && curClass.varList.exists(qname)) return 'vc';
  if (globalVars.exists(qname)) return 'vg';
  // is it a function?
  if (curClass && curClass.funcList.exists(qname)) return 'fc';
  if (globalFuncs.exists(qname)) return 'fg';
  // don't know -- assume it is an external function
  return 'fe';
}

function convertVar(varname,type) {
  if (type.charAt(1)=='c') return '$this->'+varname;
  if (type.charAt(0)=='v') return '$'+varname;
  return varname;
}

function pass3AddVar (varname,scope) {
  if (curFunc!=null) {
    if (!curFunc.varList.exists(varname)) curFunc.varList.add(varname,scope);
  } else {
    if (!globalVars.exists(varname)) globalVars.add(varname,scope);
  }
}

function convertQName(node) {
  if (node.children.length==1)
    var parms=node.children[0];
  var lcname=node.tokval.toLowerCase();
  switch (lcname) {
    case 'lbound': parms.children[0].convert2php(-1); return '0';
    case 'ubound': return 'count('+parms.children[0].convert2php(-1)+')-1';
    case 'cstr':   return '('+parms.children[0].convert2php(-1)+')';
    case 'left':   return 'substr('+parms.children[0].convert2php(-1)+',0,'+parms.children[1].convert2php(-1)+')';
    case 'right':  return 'substr('+parms.children[0].convert2php(-1)+',-'+parms.children[1].convert2php(-1)+')';
    case 'mid':
      retval='substr('+parms.children[0].convert2php(-1)+','+adjustExpr(parms.children[1],'-1');
      if (parms.children.length>2) retval+=','+parms.children[2].convert2php(-1);
      return retval+')';
    case 'ucase':  return 'strtoupper('+parms.children[0].convert2php(-1)+')';
    case 'lcase':  return 'strtolower('+parms.children[0].convert2php(-1)+')';
    case 'len':    return 'strlen('+parms.children[0].convert2php(-1)+')';
    case 'clng':
    case 'cint':   return 'intval('+parms.children[0].convert2php(-1)+')';
    case 'cdbl':   return 'doubleval('+parms.children[0].convert2php(-1)+')';
    case 'atn':    return 'atan('+parms.children[0].convert2php(-1)+')';
    case 'split':
      var delim=parms.children.length>1 ? parms.children[1].convert2php(-1) : " ";
      return 'explode('+delim+','+parms.children[0].convert2php(-1)+')';
    case 'join':
      var delim=parms.children.length>1 ? parms.children[1].convert2php(-1) : " ";
      return 'implode('+delim+','+parms.children[0].convert2php(-1)+')';
    case 'instr':
      if (instrCnt==0) {
        helperFunc+="\nfunction _instr($start,$str1,$str2,$mode) {";
        helperFunc+="\nif ($mode) { $str1=strtolower($str1); $str2=strtolower($str2); }";
        helperFunc+="\n$retval=strpos($str1,$str2,$start);";
        helperFunc+="\nreturn ($retval===false) ? 0 : $retval+1;";
        helperFunc+="\n}";
      }
      var start=0,idx=0;
      instrCnt++;
      var mode=parms.children.length>3 ? parms.children[3].convert2php(-1) : "0";
      if (parms.children.length > 2) {
        start=parms.children[0].convert2php(-1);
        idx=1;
      }
      return '_instr('+start+','+parms.children[idx].convert2php(-1)+','+parms.children[idx+1].convert2php(-1)+','+mode+')';
    case 'instrrev':
      if (instrRevCnt==0) {
        helperFunc+="\nfunction _instrrev($str1,$str2,$mode) {";
        helperFunc+="\nif ($mode) { $str1=strtolower($str1); $str2=strtolower($str2); }";
        helperFunc+="\n$retval=strrpos($str1,$str2);";
        helperFunc+="\nreturn ($retval===false) ? 0 : $retval+1;";
        helperFunc+="\n}";
      }
      instrRevCnt++;
      var mode=parms.children.length>3 ? parms.children[3].convert2php(-1) : "0";
      // ignore start for now
      return '_instrrev('+parms.children[0].convert2php(-1)+','+parms.children[1].convert2php(-1)+','+mode+')';
    case 'replace':
      return 'str_replace('+parms.children[1].convert2php(-1)+','+parms.children[2].convert2php(-1)+','+parms.children[0].convert2php(-1)+')';
    case 'strreverse': return 'strrev('+parms.children[0].convert2php(-1)+')';
    case 'vartype':    return 'gettype('+parms.children[0].convert2php(-1)+')';
    case 'cbool':      return '('+parms.children[0].convert2php(-1)+')';
    case 'asc':        return 'ord('+parms.children[0].convert2php(-1)+')';
    case 'date':
    case 'time':
    case 'now':        return 'time()';
    
    case 'array': return 'array'+this.convertParms(node.children[0],'()');
    case 'createobject':
      var parm=parms.children.convert2php(-1);
      parm=parm.replace(/^"(.*)"$/,"$1");  // remove quotes
      switch (parm.toLowerCase()) {
        case 'scripting.dictionary': return 'array()';
      }
      warning+=' unable to convert CreateObject('+parm+')';
      return '';
    
    // is... functions
    
    case 'isdate':     warning+=' isDate is not supported'; return '';
    case 'isnumeric':  return 'is_numeric('+parms.children[0].convert2php(-1)+')';
    case 'isnull':     return '!isset('+parms.children[0].convert2php(-1)+')';
    case 'isempty':    return 'empty('+parms.children[0].convert2php(-1)+')';
    case 'isarray':    return 'is_array('+parms.children[0].convert2php(-1)+')';
    case 'isobject':
      if (parms.children[0].children.length==1 && parms.children[0].children[0].toktyp=='qname' && parms.children[0].children[0].children.length==0) {
        var varname=parms.children[0].children[0].tokval;
        if (curFunc==null || !curFunc.varList.exists(varname))
          if (curClass==null || !curClass.varList.exists(varname))
            if (!globalVars.exists(varname)) globalVars.add(varname);  // if varname hasn't been defined anywhere, assume it is an external global
      }
      var parm1=parms.children[0].convert2php(-1);
      switch (parm1.toLowerCase()) {
        case '$response': return 'true';
        case '$wscript':  return 'false';
        default: return 'is_object('+parm1+')';
      }
    
    // single-parameter functions with the same name in asp & php
    
    case 'abs':
    case 'chr':
    case 'trim':
    case 'rtrim':
    case 'ltrim':
    case 'sin':
    case 'cos':
    case 'tan':
    case 'exp':
    case 'log':
      return lcname+'('+parms.children[0].convert2php(-1)+')';
    
    // constants
    
    case "empty":
    case "nothing":      return 'NULL';
    case "vbcr":         return '"\\r"';
    case "vbcrlf":       return '"\\r\\n"';
    case "vbnewline":    return '"\\n"';
    case "vblf":         return '"\\n"';
    case "vbnullchar":   return '"\\0"';
    case "vbnullstring": return "NULL";
    case "vbtab":        return '"\\t"';
    case "vbsunday":     return "1";
    case "vbmonday":     return "2";
    case "vbtuesday":    return "3";
    case "vbwednesday":  return "4";
    case "vbthursday":   return "5";
    case "vbfriday":     return "6";
    case "vbsaturday":   return "7";
    case "vbblack":      return "0x000000";
    case "vbred":        return "0xff0000";
    case "vbgreen":      return "0x00ff00";
    case "vbyellow":     return "0xffff00";
    case "vbblue":       return "0x0000ff";
    case "vbmagenta":    return "0xff00ff";
    case "vbcyan":       return "0xffff00";
    case "vbwhite":      return "0xffffff";
    case 'vbbinarycompare': return "0";
    case 'vbtextcompare':   return "1";
    case 'true':
    case 'false': return lcname;
    
    // built-in object references
    
    case 'application':
      warning+=' the ASP application object is not supported';
      return '';
    case 'session':  return convertSession(node);
    case 'server':   return convertServer(node);
    case 'request':  return convertRequest(node);
    case 'response': return convertResponse(node);

    // handle user-defined function calls & array references
    
    default: 
      var qtype=nameType(node.tokval);
      var qwarn='';
      //alert(node.tokval+': '+qtype);
      if (curFunc && lcname==curFunc.tokval.toLowerCase()) {
        var retval='$'+convOptions.returnVar
      } else {
        var retval=this.convertVar(node.tokval,qtype);
        if (curFunc && qtype=='vg') {
          retval="$GLOBALS['"+node.tokval+"']";
        } else if (node.children.length==0) {
          if (qtype.charAt(0)=='f') retval+='()';
          if (qtype=='fe') warning+=' assuming '+node.tokval+' is an external function';
        } else if (node.children[node.children.length-1].toktyp=='assign') {
          if (qtype=='fe')
            pass3AddVar(node.tokval);
        } else if (qtype=='fe') {
          qwarn=' assuming '+node.tokval+' is an external function';
        }
      }
      if (retval.indexOf('->')>0 && retval.charAt(0)!='$') retval='$'+retval;
      convObj=convertQNameParms(node,qtype,0,qwarn,retval);
      //alert(qtype+'\n'+retval);
      retval=convObj.convResult;
      if (convObj.foundAssignment && retval.charAt(0)!='$') retval='$'+retval;
      if (convObj.assumedArray && qwarn) qwarn=' assuming '+node.tokval+' is an external array';
      warning+=qwarn;
      return retval;
  }
}

function convertServer(node) {
  if (node.children[0].toktyp!='ref') {
    warning+=' unable to convert '+node.tokval;
    return '';
  }
  var lcname=node.children[0].tokval.toLowerCase();
  switch (lcname) {
    case 'htmlencode':  return 'htmlspecialchars'+convertQNameParms(node,'f',1).convResult;
    case '': return convertQS(node,'querystring','_GET');
  }
  warning+=' unable to convert '+node.tokval;
  return '';
}

function convertRequest(node) {
  if (node.children[0].toktyp!='ref') {
    warning+=' unable to convert '+node.tokval;
    return '';
  }
  var lcname=node.children[0].tokval.toLowerCase();
  switch (lcname) {
    case 'querystring': return convertQS(node,'querystring','_GET');
    case 'form':        return convertQS(node,'form','_POST');
    case 'cookies':     return '$_COOKIE'+convertQNameParms(node,'vf',1).convResult;
    case 'servervariables': return '$_SERVER'+convertQNameParms(node,'vf',1).convResult;
  }
  warning+=' unable to convert '+node.tokval;
  return '';
}

function convertQS(node,aspName,phpName) {
  var retval='';
  if (node.children && node.children.length>1) {
    retval='$'+phpName+convertParms(node.children[1],'[]');
    if (node.children.length==3 && node.children[2].toktyp=='parms')
      retval+=convertParms(node.children[2],'[-1]');
    else if (node.children.length==3 && node.children[2].tokval.toLowerCase()=='count')
      retval='(int)isset('+retval+')';
  }
  if (!retval) warning+=' cannot convert request.'+aspName;
  return retval;
}

function convertSession(node) {
  if (node.children[0].toktyp!='ref') {
    warning+=' unable to convert '+node.tokval;
    return '';
  }
  var lcname=node.children[0].tokval.toLowerCase();
  switch (lcname) {
    case 'contents': return '$_SESSION'+convertQNameParms(node,'vf',1).convResult;
    case 'timeout':  return (node.toktyp=='qname1') ? 'session_set_cookie_params('+node.children[1].convert2php(-1)+'*60)' : '(array_shift(session_get_cookie_params())/60)';
  }
  warning+=' unable to convert '+node.tokval;
  return '';
}

function convertResponse(node) {
  if (node.children[0].toktyp!='ref') {
    warning+=' unable to convert '+node.tokval;
    return '';
  }
  var lcname=node.children[0].tokval.toLowerCase();
  switch (lcname) {
    case 'binarywrite':
    case 'write':        return 'echo'+convertParms(node.children[1],' ');
    case 'redirect':     return 'header("Location: ".'+node.children[1].children.convert2php(-1)+')';
    case 'cachecontrol': return 'header("Cache-Control: ".'+node.children[1].convert2php(-1)+')';
    case 'cookies':
      if (node.children && node.children.length==3) return 'setcookie('+node.children[1].children.convert2php(-1)+','+node.children[2].children.convert2php(-1)+')';
      warning+=' cannot convert response.cookies';
      return '';
    case 'status':
      if (node.children && node.children.length==1) return 'header("Status: ".'+node.children[1].convert2php(-1)+')';
      warning+=' cannot convert response.status';
      return '';
    case 'contenttype':  return 'header("Content-type: ".'+node.children[1].convert2php(-1)+')';
    case 'end':          return 'exit()';
    case 'clear':        return 'ob_clean()';
    case 'flush':        return 'flush()';
    case 'buffer':
      var parm=node.children[1].convert2php(-1);
      switch (parm.toLowerCase()) {
        case 'true':
        case '1': return "ob_start()"
        case 'false':
        case '0': return "ob_end_flush()"
      }
      warning+=' unable to convert response.buffer';
      return '';
    case 'addheader':    return 'header('+node.children[1].children[0].convert2php(-1)+'.": ".'+node.children[1].children[1].convert2php(-1)+')';
    case 'expires':      return 'header("Expires: ".gmdate("D, d M Y H:i:s",time()+('+node.children[1].convert2php(-1)+'*60))." GMT")';
  }
  warning+=' unable to convert '+node.tokval;
  return '';
}

function convertQNameParms(node,qtype,startIdx,qwarn,initVal) {
  var retval=(typeof initVal=='string') ? initVal : '';
  var assumedArray=false, foundAssignment=false;
  for (var i=startIdx; i<node.children.length; i++) {
    switch (node.children[i].toktyp) {
      case 'parms':
        var parens='()';
        if (qtype.charAt(0)=='v' || (qtype=='fe' && node.children[i].children.length==1 && !node.children[i].noParens)) {
          parens='[]';
          assumedArray=true;
        }
        retval+=convertParms(node.children[i],parens);
        break;
      case 'ref':
        if (node.children[i].tokval.toLowerCase()=='exists') {
          retval='array_key_exists('+convertParms(node.children[i+1],'')+','+retval+')';
          return { convResult:retval, assumedArray:assumedArray, foundAssignment:foundAssignment };
        }
        retval+='->'+node.children[i].tokval;
        qtype='fe';
        break;
      case 'assign':
        retval+='='+node.children[i].children.convert2php(-1);
        foundAssignment=true;
        break;
    }
  }
  return { convResult:retval, assumedArray:assumedArray, foundAssignment:foundAssignment };
}

function SymbolObject(symbol,scope) {
  this.symbol=symbol;
  this.scope=scope;
}

function SymbolTable() {
  this.symtab=[];
}

SymbolTable.prototype.add = function(symbol,scope) {
  var newsym=new SymbolObject(symbol,scope);
  this.symtab.push(newsym);
  return newsym;
}

SymbolTable.prototype.index = function(symbol) {
  var lcSymbol=symbol.toLowerCase();
  for(var i=0; i<this.symtab.length; i++)
    if (this.symtab[i].symbol.toLowerCase()==lcSymbol) return i;
  return -1;
}

SymbolTable.prototype.exists = function(symbol) {
  return this.index(symbol)!=-1;
}

var aspConvPass2 = {
  convert: function(pass1) {
    var s=0,newnode;
    this.errmsg='';
    this.pass2=new Array();
    this.curClass=null;
    this.curFunc=null;
    while (s<pass1.length && !this.errmsg) {
      if (pass1[s].toktyp=='vbscript') {
        this.pass2idx=0;
        this.pass2inputs=pass1[s].tokval;
        newnode=this.newP2node(this.pass2,pass1[s].toktyp);
        this.parseStatments(newnode);
      } else {
        this.newP2node(this.pass2,pass1[s].toktyp,pass1[s].tokval,pass1[s].section);
      }
      s++;
    }
    if (this.errmsg!='') return this.errmsg;
    return this.pass2;
  },

  newP2node: function(nodeList,tokentype,tokenvalue,section) {
    if (!nodeList) {
      this.pass2error(tokenvalue,"Internal error in newP2node: tokentype="+tokentype);
      return;
    }
    if (typeof section=='undefined') section=this.pass2inputs[this.pass2idx].section;
    this.curNode=new parseNode2(section,tokentype);
    nodeList.push(this.curNode);
    if (typeof tokenvalue=='undefined')
      this.curNode.children=new Array();
    else
      this.curNode.tokval=tokenvalue;
    return this.curNode.children;
  },

  parseStatments: function(toknode) {
    var tokval,toktyp;
    while (this.pass2idx<this.pass2inputs.length) {
      if (this.errmsg) return 'error';
      tokval=this.pass2inputs[this.pass2idx].tokval;
      toktyp=this.pass2inputs[this.pass2idx].toktyp;
      //alert('parseStatments:'+toktyp+' '+tokval);
      for (var i=1; i<arguments.length; i++) {
        if (arguments[i].charAt(0)=='*') {
          if (toktyp==arguments[i].substr(1)) {
            this.pass2idx++;
            return arguments[i];
          }
        } else {
          if (tokval.toLowerCase()==arguments[i]) {
            this.pass2idx++;
            return arguments[i];
          }
        }
      }
      switch (toktyp) {
        case 'resword':
          this.parseResWord(toknode,tokval);
          break;
        case 'word':
          if (tokval.toLowerCase()=='property') {
            this.parseProperty(toknode);
            break;
          }
          this.pass2idx++;
          var qnode=this.parseQName(toknode,tokval,'1');
          if (this.pass2inputs[this.pass2idx].tokval=='=') {
            this.pass2idx++;
            this.parseExpr(this.newP2node(qnode,'assign'));
          } else if (!this.eol()) {
            this.parseSubParms(qnode);
          }
          break;
        case 'ref':
          var qnode=this.parseQName(toknode,convOptions.withVar,'1');
          if (this.pass2inputs[this.pass2idx].tokval=='=') {
            this.pass2idx++;
            this.parseExpr(this.newP2node(qnode,'assign'));
          } else {
            this.parseSubParms(qnode);
          }
          break;
        case 'eos':
        case 'eol':
          this.pass2idx++;
          break;
        case 'comment':
          this.newP2node(toknode,'comment',tokval);
          this.pass2idx++;
          break;
        default:
          if (tokval=='@') {
            this.parseAt(toknode);
          } else {
            this.errmsg='Syntax error near "'+tokval+'" in section '+this.pass2inputs[this.pass2idx].section;
          }
      }
    }
    return 'eof';
  },

  parseResWord: function(toknode,resword) {
    this.pass2idx++;
    switch (resword) {
      case 'redim':
      case 'dim':    this.parseDim(toknode,resword); break;
      case 'const':  this.parseLet(toknode,resword,true); break;
      case 'for':    this.parseFor(toknode); break;
      case 'do':     this.parseDo(toknode); break;
      case 'while':  this.parseWhile(toknode); break;
      case 'select': this.parseSelect(toknode); break;
      case 'let':
      case 'set':    this.parseLet(toknode,resword,false); break;
      case 'if':     this.parseIf(toknode); break;
      case 'call':   
        this.newP2node(toknode,'qname');
        this.curNode.tokval=this.pass2inputs[this.pass2idx++].tokval;
        this.parseSubParms(toknode);
        break;
      case 'class':  this.parseClass(toknode); break;
      case 'with':   this.parseWith(toknode); break;
      case 'endwith': break;
      case 'sub':
      case 'function': this.parseFunction(toknode,resword); break;
      case 'public':
      case 'private':  this.parsePublic(toknode,resword); break;
      case 'exitdo':
      case 'exitfor':
      case 'exitsub':
      case 'exitfunction':
      case 'exitproperty':
        this.newP2node(toknode,'exit',resword.substr(4).toLowerCase());
        break;
      case 'onerrorresumenext':
        this.newP2node(toknode,'onerrorresumenext')
        break;
      default:
        this.pass2error(resword,'Do not know how to process this reserved word ('+resword+')');
    }
  },

  parsePublic: function(toknode,nodename) {
    var toktyp=this.pass2inputs[this.pass2idx].toktyp;
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (tokval=='sub' || tokval=='function') {
      this.pass2idx++;
      this.parseFunction(toknode,tokval,nodename);
    } else if (tokval.toLowerCase()=='property') {
      this.parseProperty(toknode,nodename);
    } else if (toktyp=='word') {
      this.parseDim(toknode,nodename,nodename);
    } else {
      this.pass2error(nodename);
    }
  },

  parseProperty: function(toknode,scope) {
    var tokval=this.pass2inputs[++this.pass2idx].tokval;
    //alert('prop:'+tokval);
    if (tokval=='let' || tokval=='set' || tokval=='get') {
      this.pass2idx++;
      this.parseFunction(toknode,'property',scope,tokval);
    } else {
      this.pass2error(tokval,"Expected let, set, or get");
    }
  },

  parseAt: function(toknode) {
    var tokval=this.pass2inputs[++this.pass2idx].tokval;
    if (tokval.toLowerCase()=='language') {
      tokval=this.pass2inputs[++this.pass2idx].tokval;
      if (tokval=='=') {
        tokval=this.pass2inputs[++this.pass2idx].tokval;
        if (tokval.toLowerCase()!='"vbscript"')
          this.pass2error(tokval,'Can only convert vbscript!');
      } else {
        this.pass2error('language','Expected "="');
      }
    }
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg)
      if (this.pass2inputs[++this.pass2idx]=='eol') return;
  },

  pass2error: function(loc,submsg) {
    if (loc) loc=' near "'+loc+'"';
    this.errmsg='Error'+loc+' at '+this.pass2inputs[this.pass2idx].section;
    if (typeof submsg=='string') this.errmsg+='\n'+submsg;
  },

  addVar: function(varname,scope) {
    if (this.curFunc!=null)
      this.curSym=this.curFunc.varList.add(varname,scope);
    else if (this.curClass!=null)
      this.curSym=this.curClass.varList.add(varname,scope);
    else
      this.curSym=globalVars.add(varname,scope);
  },

  parseDim: function(toknode,nodename,scope) {
    var state=0;
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      var toktyp=this.pass2inputs[this.pass2idx].toktyp;
      var tokval=this.pass2inputs[this.pass2idx].tokval;
      if (this.eol()) break;
      switch (state) {
        case 0:
          if (toktyp=='word') {
            if (nodename!='redim') this.addVar(tokval,scope);
            var curvar=tokval;
            state=1;
          } else if (tokval=='preserve') {
            this.newP2node(toknode,'comment',nodename+' '+tokval);
            state=4;
          } else {
            this.pass2error(nodename,'Expected variable name (found '+tokval+')');
          }
          break;
        case 1:
          if (tokval==',') {
            state=0;
          } else if (tokval=='(') {
            this.curSym.isArray=true;
            this.pass2idx++;
            this.parseExpr(this.newP2node([],'temp'));  // ignore whatever is inside the parens
            if (this.curFunc || !this.curClass) {
              var qnode=this.newP2node(toknode,'qname1');
              this.curNode.tokval=curvar;
              var anode=this.newP2node(qnode,'assign');
              this.newP2node(anode,'qname');
              this.curNode.tokval='array';
            }
          } else {
            this.pass2error(nodename,'parseDim state='+state);
          }
          break;
        case 2:
          if (toktyp.substr(0,7)=='lit-num')
            state=3;
          else if (tokval==')')
            state=0;
          else
            this.pass2error(nodename,'parseDim state='+state);
          break;
        case 3:
          if (tokval==')')
            state=1;
          else
            this.pass2error(nodename,'Expected ")"');
          break;
      }
      this.pass2idx++;
    }
  },
  
  // returns true if there is no more code on the current line
  eol: function() {
    var toktyp=this.pass2inputs[this.pass2idx].toktyp;
    return (toktyp=='comment' || toktyp=='eol');
  },

  parseSubParms: function(toknode) {
    var parms=this.newP2node(toknode,'parms');
    this.curNode.noParens=true;
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      if (this.eol()) break;
      this.parseExpr(this.newP2node(parms,'parm'));
      var tokval=this.pass2inputs[this.pass2idx].tokval;
      if (tokval==',')
        this.pass2idx++;
      else
        return;
    }
  },

  parseFuncParms: function(toknode) {
    var parms=this.newP2node(toknode,'parms');
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      this.parseExpr(this.newP2node(parms,'parm'));
      var tokval=this.pass2inputs[this.pass2idx].tokval;
      if (tokval==')') {
        this.pass2idx++;
        break;
      } else if (tokval==',') {
        this.pass2idx++;
      } else {
        this.pass2error('function parameter or array index','Found unexpected value "'+tokval+'"');
      }
    }
  },
  
  parseQName: function(toknode,name,suffix) {
    if (!toknode) {
      this.pass2error(name,'Internal error in parseQName');
      return;
    }
    var qnode=this.newP2node(toknode,'qname'+(suffix || ''));
    this.curNode.tokval=name;
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      var toktyp=this.pass2inputs[this.pass2idx].toktyp;
      var tokval=this.pass2inputs[this.pass2idx].tokval;
      //alert('parseQName: '+toktyp+' '+tokval);
      if (tokval=='(') {
        this.pass2idx++;
        this.parseFuncParms(qnode);
      } else if (toktyp=='ref') {
        this.newP2node(qnode,toktyp,tokval);
        this.pass2idx++;
      } else {
        break;
      }
    }
    return qnode;
  },

  parseExpr: function(toknode) {
    if (!toknode) {
      this.pass2error('','Internal error in parseExpr');
      return;
    }
    var state=0;
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      var toktyp=this.pass2inputs[this.pass2idx].toktyp;
      var tokval=this.pass2inputs[this.pass2idx].tokval;
      //alert('parseExpr: state='+state+' toktyp='+toktyp+' tokval='+tokval);
      switch (state) {
        case 0:
          switch (toktyp) {
            case 'word':
              this.pass2idx++;
              this.parseQName(toknode,tokval);
              state=1;
              break;
            case 'ref':
              this.parseQName(toknode,convOptions.withVar);
              state=1;
              break;
            case 'paren':
              if (tokval==')') return;
              this.pass2idx++;
              this.parseExpr(this.newP2node(toknode,'pexpr'));
              if (this.pass2inputs[this.pass2idx].tokval!=')') {
                this.pass2error('expression','Expected ")"');
                return;
              }
              this.pass2idx++;
              state=1;
              break;
            case 'op':
              if (tokval=='-' || tokval=='new' || tokval=='not') {
                this.newP2node(toknode,toktyp,tokval);
                this.pass2idx++;
              } else {
                this.pass2error(tokval,'Invalid expression');
                return;
              }
              break;
            case 'lit-num-dec':
            case 'lit-num-oct':
            case 'lit-num-hex':
            case 'lit-str':
            case 'lit-dat':
              this.newP2node(toknode,toktyp,tokval);
              this.pass2idx++;
              state=1;
              break;
            strtotime
            default:
              this.pass2error(tokval,'Invalid expression');
              return;
          }
          break;
        case 1:
          switch (toktyp) {
            case 'op':
              this.newP2node(toknode,toktyp,tokval);
              this.pass2idx++;
              state=0;
              break;
            default: return;
          }
          break;
      }
    }
  },

  parseFor: function(toknode) {
    var newnode=this.newP2node(toknode,'for');
    var fornode=this.curNode;
    var toktyp=this.pass2inputs[this.pass2idx].toktyp;
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (tokval=='each') {
      toktyp=this.pass2inputs[++this.pass2idx].toktyp;
      tokval=this.pass2inputs[this.pass2idx].tokval;
      if (toktyp!='word') {
        this.pass2error('for','Expected loop variable');
        return;
      }
      fornode.tokval=tokval;
      tokval=this.pass2inputs[++this.pass2idx].tokval;
      if (tokval!='in') {
        this.pass2error('for','Expected "in"');
        return;
      }
      fornode.loopEach=[];
      this.pass2idx++;
      this.parseExpr(fornode.loopEach);
    } else if (toktyp=='word') {
      fornode.tokval=tokval;
      tokval=this.pass2inputs[++this.pass2idx].tokval;
      if (tokval!='=') {
        this.pass2error('for','Expected "="');
        return;
      }
      fornode.loopStart=[];
      this.pass2idx++;
      this.parseExpr(fornode.loopStart);
      tokval=this.pass2inputs[this.pass2idx].tokval;
      if (tokval.toLowerCase()!='to') {
        this.pass2error('for','Expected "to"');
        return;
      }
      fornode.loopEnd=[];
      this.pass2idx++;
      this.parseExpr(fornode.loopEnd);
    } else {
      this.pass2error('for');
      return;
    }
    this.parseStatments(newnode,'next');
  },

  parseDo: function(toknode) {
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (tokval=='while' || tokval=='until') {
      var newnode=this.newP2node(toknode,tokval);
      this.pass2idx++;
      this.curNode.Condition=[];
      this.parseExpr(this.curNode.Condition);
      this.parseStatments(newnode,'loop');
    } else {
      var newnode=this.newP2node(toknode,'do');
      this.parseStatments(newnode,'loop');
      tokval=this.pass2inputs[this.pass2idx].tokval;
      if (tokval=='while' || tokval=='until') {
        this.pass2idx++;
        newnode.Condition=[];
        newnode.CondType=tokval;
        this.parseExpr(newnode.Condition);
      } else {
        this.pass2error(tokval,'Expected "loop"');
      }
    }
  },

  parseWhile: function(toknode) {
    var newnode=this.newP2node(toknode,'while');
    this.curNode.Condition=[];
    this.parseExpr(this.curNode.Condition);
    this.parseStatments(newnode,'wend');
  },

  parseWith: function(toknode) {
    var qnode=this.newP2node(toknode,'qname1');
    this.curNode.tokval=convOptions.withVar;
    this.parseExpr(this.newP2node(qnode,'assign'));
  },

  parseSelect: function(toknode) {
    var tokval,toktyp,casenode,casechildren;
    var selnode=this.newP2node(toknode,'switch');
    tokval=this.pass2inputs[this.pass2idx].tokval;
    if (tokval.toLowerCase()!='case') {
      this.pass2error('select','Expected "case" ('+tokval+')');
      return;
    }
    this.pass2idx++;
    this.curNode.Condition=[];
    this.parseExpr(this.curNode.Condition);
    tokval=this.parseStatments(selnode,'case','endselect');
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg && tokval!='endselect') {
      if (tokval!='case') {
        this.pass2error('select','Expected "case" ('+tokval+')');
        return;
      }

      casechildren=this.newP2node(selnode,'case');
      casenode=this.curNode;
      casenode.Selectors=[];
      if (this.pass2inputs[this.pass2idx].tokval=='else') {
        this.pass2idx++;
      } else {
        // get list of literals
        while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
          this.parseExpr(this.newP2node(casenode.Selectors,'caseSel'));
          if (this.pass2inputs[this.pass2idx].tokval!=',') break;
          this.pass2idx++;
        }
      }
      //alert('case next token:'+tokval);
      tokval=this.parseStatments(casechildren,'case','endselect');
    }
  },

  parseLet: function(toknode,nodename,addvar) {
    var tokval=this.pass2inputs[this.pass2idx++].tokval;
    //alert('parseLet: '+tokval);
    var qnode=this.parseQName(toknode,tokval,'1');
    //if (addvar) this.addVar(tokval);
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (tokval!='=') {
      this.pass2error(nodename,'Expected "=" (found '+tokval+')');
      return;
    }
    this.pass2idx++;
    this.parseExpr(this.newP2node(qnode,'assign'));
  },

  parseIf: function(toknode) {
    var nodename='if',tokval,toktyp,newnode;
    while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
      //alert('parseIf:'+nodename);
      switch (nodename) {
        case 'elseif':
        case 'if':
          newnode=this.newP2node(toknode,nodename);
          this.curNode.Condition=[];
          this.parseExpr(this.curNode.Condition);
          tokval=this.pass2inputs[this.pass2idx].tokval;
          if (tokval.toLowerCase()!='then') {
            this.pass2error('if','Expected "then" (found '+tokval+')');
            return;
          }
          this.pass2idx++;
          toktyp=this.pass2inputs[this.pass2idx].toktyp;
          if (!this.eol()) {
            // single-line if
            nodename=this.parseStatments(newnode,'else','*eol');
            if (nodename=='else')
              this.parseStatments(this.newP2node(toknode,'else'),'*eol');
            return;
          }
          // multi-line if
          nodename=this.parseStatments(newnode,'else','elseif','endif');
          break;
        case 'else':
          nodename=this.parseStatments(this.newP2node(toknode,nodename),'endif');
          return;
        default:
          return;
      }
    }
  },

  parseClass: function(toknode) {
    var classnode=this.newP2node(toknode,'class');
    this.curClass=this.curNode;
    this.curClass.varList=new SymbolTable();
    this.curClass.funcList=new SymbolTable();
    var toktyp=this.pass2inputs[this.pass2idx].toktyp;
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (toktyp!='word') {
      this.pass2error('class','Expected class name');
      return;
    }
    this.curNode.tokval=tokval;
    globalClasses.add(tokval);
    this.pass2idx++;
    this.parseStatments(classnode,'endclass');
    this.curClass=null;
  },

  parseFunction: function(toknode,nodename,scope,attr) {
    var funcnode=this.newP2node(toknode,nodename);
    this.curFunc=this.curNode;
    this.curFunc.varList=new SymbolTable();
    this.curFunc.scope=scope;
    this.curFunc.attr=attr;
    var toktyp=this.pass2inputs[this.pass2idx].toktyp;
    var tokval=this.pass2inputs[this.pass2idx].tokval;
    if (toktyp!='word') {
      this.pass2error(nodename,'Expected '+nodename+' name');
      return;
    }
    this.curNode.tokval=tokval;
    if (this.curClass!=null)
      this.curClass.funcList.add(tokval,scope);
    else
      globalFuncs.add(tokval,scope);
    var parms=this.curNode.parms=[];
    tokval=this.pass2inputs[++this.pass2idx].tokval;
    if (tokval=='(') {
      this.pass2idx++;
      while (this.pass2idx<this.pass2inputs.length && !this.errmsg) {
        tokval=this.pass2inputs[this.pass2idx].tokval;
        //alert('parseFunction parms:'+tokval);
        var passBy='';
        if (tokval=='byval' || tokval=='byref') {
          passBy='-'+tokval;
          tokval=this.pass2inputs[++this.pass2idx].tokval;
        }
        if (this.pass2inputs[this.pass2idx].toktyp!='word') break;
        this.newP2node(parms,'parm'+passBy,tokval);
        this.addVar(tokval);
        if (this.pass2inputs[++this.pass2idx].tokval==',') this.pass2idx++;
      }
      tokval=this.pass2inputs[this.pass2idx].tokval;
      if (tokval!=')') {
        this.pass2error(nodename,'Expected ")"');
        return;
      }
      this.pass2idx++;
    }
    this.parseStatments(funcnode,'end'+nodename);
    this.curFunc=null;
  }
}

var aspConvPass1 = {
  convert: function(txt,isasp) {
    this.pass1=new Array();
    this.section=1;
    this.errmsg='';
    if (isasp)
      this.convertASP(txt);
    else
      this.convertVB(txt);
    if (this.errmsg!='') return this.errmsg;
    return this.pass1;
  },

  pass1error: function(msg) {
    this.errmsg=msg+' at '+this.section+'.'+this.linenum;
  },

  convertASP: function(txt) {
    while (txt.match(/<%/)) {
      txt=RegExp.rightContext;
      this.parseHTML(RegExp.leftContext);
      if (txt.match(/%>/)) {
        txt=RegExp.leftContext;
        nexttxt=RegExp.rightContext;
      } else {
        nexttxt='';
      }
      if (txt.substr(0,1)=='=')
        this.convertVB('response.write '+txt.substr(1));
      else
        this.convertVB(txt);
      txt=nexttxt;
    }
    this.parseHTML(txt);
  },

  // just look for includes
  parseHTML: function(txt) {
    if (txt.length==0) return;
    while (txt.match(/<!--\s+#include\s+(virtual|file)\s*=\s*("[^"]+")\s+-->/i)) {
      var htmlLeft=RegExp.leftContext;
      var htmlRight=RegExp.rightContext;
      var includeType=RegExp.$1;
      var includeFile=RegExp.$2;
      this.newP1node('html',htmlLeft);
      this.newP1node('include'+includeType.toLowerCase(),includeFile);
      txt=htmlRight;
    }
    this.newP1node('html',txt);
  },

  newP1node: function(tokentype,tokenvalue) {
    if (tokenvalue.length==0) return;
    return this.pass1.push(new parseNode1(this.section++,tokentype,tokenvalue));
  },

  convertVB: function(txt) {
    this.parseTree=new Array();
    this.asplines=txt.split(/\n/);
    this.linenum=0;
    while (this.linenum<this.asplines.length && !this.errmsg) {
      this.curline=this.asplines[this.linenum++].removeNonPrintable();
      while (this.curline.slice(-1)=='_') {
        this.curline=this.curline.slice(0,-1)+' ';
        this.curline+=this.asplines[this.linenum++].removeNonPrintable();
      }
      this.curline=this.curline.replace(/\t/g,' ');
      this.curline+='\n';
      this.tokenizeLine();
    }
    //alert('section: '+this.section+' lines='+this.parseTree.length+' result type='+(typeof this.pass1)+'\n'+txt);
    this.newP1node('vbscript',this.parseTree);
  },

  tokenizeLine: function() {
    this.tokidx=0;
    do {
      this.getToken();
      var lctok=this.token.toLowerCase();
      if (this.toktype=='word' && this.reservedWords[lctok]==1)
        this.toktype='resword';
      if (lctok=='rem') {
        this.token=this.curline.slice(this.tokidx,-1);
        this.toktype='comment';
        this.tokidx=this.curline.length-1;
      } else if (lctok=='stop') {
        this.token=this.curline.slice(this.tokidx-lctok.length,-1);
        this.toktype='comment';
        this.tokidx=this.curline.length-1;
      } else if (this.toktype=='resword')
        this.token=lctok;
      else if (this.operators[lctok]==1) {
        this.toktype='op';
        this.token=lctok;
      } else if (this.token==':')
        this.toktype='eos';
      else if (this.token=='(' || this.token==')')
        this.toktype='paren';
      this.parseTree.push(new parseNode1(this.section+'.'+this.linenum,this.toktype,this.token));
    } while (this.toktype!='eol' && !this.errmsg);
  },

  getToken: function() {
    var state=0,nextch;
    this.toktype='';
    this.token='';
    var ch=this.curline.charAt(this.tokidx);
    while (ch!='\n') {
      if (ch=="'" && state==0) {
        this.token=this.curline.slice(this.tokidx+1,-1);
        this.toktype='comment';
        this.tokidx=this.curline.length-1;
        return;
      }
      nextch=this.curline.charAt(++this.tokidx);
      switch (state) {
        case 0:
          if (ch=='"') {
            this.token=ch;
            this.toktype='lit-str';
            state=2;
          } else if (ch.match(/[a-zA-Z_]/)) {
            this.token+=ch;
            this.toktype='word';
            state=1;
          } else if (ch.charAt(0)=='.') {
            this.toktype='ref';
            state=3;
          } else if (ch.match(/[0-9]/)) {
            this.token+=ch;
            this.toktype='lit-num-dec';
            state=4;
          } else if (ch=='&' && nextch.match(/[o0-7]/i)) {
            this.token+=ch+nextch;
            this.toktype='lit-num-oct';
            nextch=this.curline.charAt(++this.tokidx);
            state=5;
          } else if (ch=='&' && nextch.match(/h/i)) {
            this.token+=ch+nextch;
            this.toktype='lit-num-hex';
            nextch=this.curline.charAt(++this.tokidx);
            state=6;
          } else if (ch=='#') {
            this.toktype='lit-dat';
            state=7;
          } else if (ch!=' ') {
            this.token=ch;
            this.toktype='symbol';
            if ((ch+nextch).match(/<=|>=|<>/)) {
              this.token+=nextch;
              nextch=this.curline.charAt(++this.tokidx);
            }
            return;
          }
          break;
        case 1:
          // process word or reserved word
          var lctok=this.token.toLowerCase();
          if (ch.match(/[a-zA-Z0-9_]/))
            this.token+=ch;
          else if (lctok=='end' || lctok=='exit' || lctok=='onerrorresume') {
            this.toktype='resword';
            state=3;
          } else if (lctok!='on' && lctok!='onerror') {
            this.tokidx--;
            return;
          }
          break;
        case 2:
          // process string
          if (ch=='"') {
            if (nextch=='"') {
              this.token+='\\';
              //alert('quotes: idx='+this.tokidx+' rest='+this.curline.substr(this.tokidx));
              nextch=this.curline.charAt(++this.tokidx);
            } else {
              this.token+=ch;
              return;
            }
          }
          this.token+=ch;
          break;
        case 3:
          // finish word or ref token
          if (ch.match(/[a-zA-Z0-9_]/)) {
            this.token+=ch;
          } else {
            this.tokidx--;
            return;
          }
          break;
        case 4:
          // decimal number
          if (ch.match(/[0-9.]/)) {
            this.token+=ch;
          } else if (ch.match(/[a-z]/i)) {
            this.pass1error('Invalid number');
            return;
          } else {
            this.tokidx--;
            return;
          }
          break;
        case 5:
          // octal number
          if (ch.match(/[0-7]/)) {
            this.token+=ch;
          } else if (ch=='&') {
            this.token+=ch;
            return;
          } else {
            this.tokidx--;
            return;
          }
          break;
        case 6:
          // hex number
          if (ch.match(/[0-9a-f]/i)) {
            this.token+=ch;
          } else if (ch=='&') {
            this.token+=ch;
            return;
          } else {
            this.tokidx--;
            return;
          }
          break;
        case 7:
          // date literal
          if (ch=='#') return;
          this.token+=ch;
          break;
      }
      ch=nextch;
    }
    if (this.toktype.length==0)
      this.toktype='eol';
  },

  // these words are reserved, but may appear in an expression
  exprReserved : {
    'nothing':1,'empty':1,'false':1,'true':1,'null':1
  },

  operators : {
    'and':1,'not':1,'or':1,'xor':1,'eqv':1,'imp':1,'new':1,
    'mod':1,'&':1,'+':1,'-':1,'*':1,'/':1,'\\':1,
    '=':1,'<':1,'<>':1,'<=':1,'>':1,'>=':1,'is':1
  },

  // these words are reserved, and may not appear in an expression
  reservedWords : {
    'as':1,'boolean':1,'byref':1,'byte':1,'byval':1,'call':1,'case':1,'class':1,
    'const':1,'currency':1,'debug':1,'dim':1,'do':1,'double':1,'each':1,'else':1,'elseif':1,
    'end':1,'endif':1,'enum':1,'event':1,'exit':1,'for':1,
    'function':1,'get':1,'goto':1,'if':1,'implements':1,'in':1,'integer':1,
    'let':1,'like':1,'long':1,'loop':1,'lset':1,'me':1,'next':1,
    'on':1,'option':1,'optional':1,'paramarray':1,'preserve':1,
    'private':1,'public':1,'raiseevent':1,'redim':1,'rem':1,'resume':1,'rset':1,'select':1,
    'set':1,'shared':1,'single':1,'static':1,'stop':1,'sub':1,'then':1,'to':1,
    'type':1,'typeof':1,'until':1,'variant':1,'wend':1,'while':1,'with':1
  }
}
