/** JavaScript -- DOT NET Javascript Library
* Copyright (C) 2005 John Garrison
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace JavaScript
{
public class ParseError : Exception
{
public ParseError( string msg ) : base( msg )
{
}
}
/// <summary>
/// Javascript recursive descent interpreter
/// </summary>
public class Parser
{
public Parser(bool strict)
{
m_strict = strict;
}
public ExecList Parse(string source)
{
Debug.Assert(null != source);
return Parse(source, 1);
}
public ExecList Parse( string source, int startline )
{
m_emit = new ExecList(null, source, 0);
StringReader reader = new StringReader( source );
m_lex = new Yylex( reader );
m_lex.LineNumber = startline;
m_token = new Yytoken( JsToken.EOF, "", 0 );
Match( JsToken.EOF );
CompileUnit();
return m_emit;
}
/// <summary>
/// CompileUnit
/// : Stmts
/// | e
/// ;
/// </summary>
/// <returns></returns>
protected void CompileUnit()
{
while ( ! m_lex.EOF )
{
Stmts( );
}
Emit( Op.NOP, Mode.IM, TypeNull.Instance, "EOF" ); // pad for jump exits
}
/// <summary>
///VarDef
/// : VAR MoreVarDef
/// ;
/// </summary>
protected void VarDef()
{
Match ( JsToken.VAR );
MoreVarDef();
}
/// <summary>
///MoreVarDef
/// : ID AssignStmt COMMA MoreVarDef
/// | ID AssignStmt SEMI
/// ;
/// </summary>
protected void MoreVarDef()
{
TypeString varname = new TypeString( m_token.m_text );
Emit( Op.NEWPROP, Mode.IM, varname, "VAR" );
Match( JsToken.ID );
if ( m_token.m_token == JsToken.ASSIGN )
{
Match( JsToken.ASSIGN );
Expr();
Emit( Op.ASSIGN, Mode.STK_IM );
}
else
{
Emit( Op.POP, Mode.IM, varname, "" );
}
if ( m_token.m_token == JsToken.COMMA )
{
Match( JsToken.COMMA );
MoreVarDef();
}
else
{
//Emit(Op.CLEAR_STK, Mode.IM, "Clear stack after VAR stmt");
return;
}
}
/// <summary>
///FunctionDef
/// : FUNCTION ID LPARA IdList RPARA Block
/// ;
/// </summary>
protected void FunctionDef()
{
Match ( JsToken.FUNCTION );
if (m_token.m_token == JsToken.LPARA)
{
AnonFunctionDef(false);
return;
}
string fnname = m_token.m_text;
Emit( Op.DEFTYPE, Mode.IM, new TypeString( fnname ), "FUNCTION " + fnname );
Match( JsToken.ID );
Match ( JsToken.LPARA );
if ( m_token.m_token != JsToken.RPARA )
{
IdList( fnname );
}
Match ( JsToken.RPARA );
int fnpos = Emit( Op.DEFBODY, Mode.IM, TypeNull.Instance, "BODY" );
int charpos = m_lex.CharNumber;
Block();
UpdateDebugInfo(fnpos, m_emit.SourceCode.Substring(charpos, m_lex.CharNumber - charpos));
// force return;
int end = Emit( Op.RETURN, Mode.IM, TypeNull.Instance, fnname + " RETURN" );
Update( fnpos, new TypeInt( end-fnpos ) );
}
/// <summary>
///AnonFunctionDef
/// : FUNCTION ID LPARA IdList RPARA Block
/// ;
/// </summary>
protected void AnonFunctionDef(bool isNew)
{
string fnname = ((isNew)?"new":"") + TypeFunction.AnonFunctionNameMangle(m_lex.LineNumber.ToString());
// note: DEFTYPE leaves the function object on the stack
Emit(Op.DEFTYPE, Mode.IM, new TypeString(fnname), fnname);
Match(JsToken.LPARA);
if (m_token.m_token != JsToken.RPARA)
{
IdList(fnname);
}
Match(JsToken.RPARA);
if (isNew)
{
Emit(Op.PUSH_CONTEXT, Mode.INDIR, new TypeString(fnname), "ANON FN");
Block();
Emit(Op.LEAVE, Mode.IM);
}
else
{
int fnpos = Emit(Op.DEFBODY, Mode.IM, TypeNull.Instance, "BODY");
int charpos = m_lex.CharNumber;
Block();
UpdateDebugInfo(fnpos, m_emit.SourceCode.Substring(charpos, m_lex.CharNumber - charpos));
// force return;
int end = Emit(Op.RETURN, Mode.IM, TypeNull.Instance, fnname + " RETURN");
Update(fnpos, new TypeInt(end-fnpos));
Emit(Op.PUSH, Mode.INDIR, new TypeString(fnname), "ANON FN");
}
}
protected void AnonObjectDef()
{
// anon object definition: var x = {Id1:'bob', "Id2":6};
Match(JsToken.LBRACE);
string fnname = "@@" + m_lex.LineNumber.ToString();
Emit(Op.DEFTYPE, Mode.IM, new TypeString(fnname), "ANON OBJECT " + fnname);
PropDefList();
Match(JsToken.RBRACE);
}
/// <summary>
///IdList
/// : ID MoreIdList
/// ;
/// </summary>
protected void IdList( string fnname )
{
Emit( Op.DEFARG, Mode.IM, new TypeString( m_token.m_text ), m_token.m_text );
Match( JsToken.ID );
MoreIdList( fnname );
}
/// <summary>
///MoreIdList
/// : COMMA ID MoreIdList
/// | e
/// ;
/// </summary>
protected void MoreIdList( string fnname )
{
if ( m_token.m_token == JsToken.COMMA )
{
Match( JsToken.COMMA );
Emit( Op.DEFARG, Mode.IM, new TypeString( m_token.m_text ), m_token.m_text );
Match ( JsToken.ID );
MoreIdList( fnname );
}
}
///<summary>
/// PropDefList's job is to parse the properties for an anon object
///
/// prop_def_list
/// : STRING ':' expr more_prop_def_list
/// | ID ':' expr more_prop_def_list
/// ;
/// </summary>
protected void PropDefList()
{
if (m_token.m_token == JsToken.RBRACE)
{
return;
}
Emit(Op.DUP, Mode.IM, "Anon Object");
Emit(Op.PUSH_CONTEXT, Mode.STK_IM, "Anon Object Prop Def");
AnonObjProp();
MorePropDefList();
Emit(Op.LEAVE, Mode.IM, "Anon Object Prop Def");
}
protected void AnonObjProp()
{
string propname;
if (m_token.m_token == JsToken.ID)
{
propname = m_token.m_text;
Match(JsToken.ID);
}
else if (m_token.m_token == JsToken.QUOTE || m_token.m_token == JsToken.TIC)
{
propname = ReadString();
}
else
{
throw new ParseError("Expected anon object property list on line " + m_lex.LineNumber.ToString());
}
Emit(Op.NEWPROP, Mode.IM, new TypeString(propname), "Anon Object Prop Def");
Match(JsToken.COLON);
if (m_token.m_token == JsToken.QUOTE || m_token.m_token == JsToken.TIC)
{
Emit(Op.PUSH, Mode.IM, new TypeString(ReadString()), "Anon prop value");
}
else if (m_token.m_token == JsToken.DOUBLE)
{
Emit(Op.PUSH, Mode.IM, new TypeDouble(Double.Parse(m_token.m_text)), "Anon prop val");
Match(JsToken.DOUBLE);
}
else if (m_token.m_token == JsToken.INT)
{
Emit(Op.PUSH, Mode.IM, new TypeInt(Int32.Parse(m_token.m_text)), "Anon prop val");
Match(JsToken.INT);
}
else if (m_token.m_token == JsToken.REGEX)
{
TypeRegExp regex = new TypeRegExp(m_token.m_text.Substring(1, m_token.m_text.Length - 2));
Emit(Op.PUSH, Mode.IM, regex, "Anon prop value");
Match(JsToken.REGEX);
if (m_token.m_token == JsToken.ID)
{
regex._Compile(regex.Source, m_token.m_text);
Match(JsToken.ID);
}
}
else if (m_token.m_token == JsToken.ID)
{
Emit(Op.DEREF_PROP, Mode.IM, new TypeString(m_token.m_text), "Anon prop ID assigment");
Match(JsToken.ID);
}
else if (m_token.m_token == JsToken.TRUE)
{
Emit(Op.PUSH, Mode.IM, TypeBool.True, "Anon prop value");
Match(JsToken.TRUE);
}
else if (m_token.m_token == JsToken.FALSE)
{
Emit(Op.PUSH, Mode.IM, TypeBool.False, "Anon prop value");
Match(JsToken.FALSE);
}
else
{
throw new ParseError("Expected anonymous object property value on line " + m_lex.LineNumber.ToString());
}
Emit(Op.STORE, Mode.STK_IM, "Anon prop assign");
}
///<summary>
/// more_prop_def_list
/// : , prop_def_list
/// |
/// ;
///</summary>
protected void MorePropDefList()
{
if (m_token.m_token == JsToken.COMMA)
{
Match(JsToken.COMMA);
AnonObjProp();
MorePropDefList();
}
}
/// <summary>
///Block
/// : LBRACE Stmts RBRACE
/// ;
/// </summary>
protected void Block()
{
Match ( JsToken.LBRACE );
Emit( Op.ENTER, Mode.IM, TypeNull.Instance, "{" );
Stmts( );
Emit( Op.LEAVE, Mode.IM, TypeNull.Instance, "}" );
Match ( JsToken.RBRACE );
Emit( Op.NOP, Mode.IM, TypeNull.Instance, "}" );
}
/// <summary>
///OptionalBlock
/// : LBRACE Stmts RBRACE
/// | Stmt
/// ;
/// </summary>
protected void OptionalBlock()
{
if ( m_token.m_token == JsToken.LBRACE )
{
Block();
return;
}
Stmt( );
}
/// <summary>
///Stmts
/// : Stmt Stmts
/// |
/// ;
/// </summary>
protected void Stmts( )
{
while ( ! m_lex.EOF && m_token.m_token != JsToken.RBRACE && m_token.m_token != JsToken.CASE && m_token.m_token != JsToken.DEFAULT )
{
Stmt( );
}
}
/// <summary>
///Stmt
/// : VarDef
/// | Block
/// | IfStmt
/// | SwitchStmt
/// | DoStmt
/// | ForStmt
/// | WhileStmt
/// | ReturnStmt
/// | ThrowStmt
/// | TryStmt
/// | FunctionDef
/// | Expr Conditional SEMI
/// | SEMI
/// ;
/// </summary>
protected void Stmt( )
{
switch ( m_token.m_token )
{
case JsToken.VAR:
VarDef();
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
break;
case JsToken.LBRACE:
Block();
break;
case JsToken.IF:
IfStmt( );
break;
case JsToken.SWITCH:
SwitchStmt();
break;
case JsToken.DO:
DoStmt();
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
break;
case JsToken.FOR:
ForStmt();
break;
case JsToken.WHILE:
WhileStmt();
break;
case JsToken.THROW:
ThrowStmt();
break;
case JsToken.TRY:
TryStmt();
break;
case JsToken.FUNCTION:
FunctionDef();
break;
case JsToken.SEMI:
Match ( JsToken.SEMI );
break;
case JsToken.BREAK:
Match ( JsToken.BREAK );
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
Emit( Op.BREAK, Mode.IM );
break;
case JsToken.CONTINUE:
Match ( JsToken.CONTINUE );
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
break;
case JsToken.RETURN:
ReturnStmt();
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
break;
case JsToken.DEBUGGER:
Emit(Op.DEBUGGER, Mode.IM);
Match(JsToken.DEBUGGER);
break;
default:
int charpos = m_lex.CharNumber;
m_requirePop = false;
Expr( );
if (m_requirePop)
{
Emit(Op.POP, Mode.STK_IM);
}
if (m_strict || m_token.m_token == JsToken.SEMI)
{
if (m_token.m_token != JsToken.RBRACE)
{
Match(JsToken.SEMI);
}
}
if (!m_strict && charpos == m_lex.CharNumber)
{
throw new ParseError("Unexpected " + m_lex.yytext());
}
break;
}
}
protected void Conditional()
{
Match( JsToken.CONDITIONAL );
int pos = Emit( Op.JMPZ, Mode.IM, TypeNull.Instance, "?" );
Expr();
Match( JsToken.COLON );
int pos2 = Emit( Op.JMP, Mode.IM, TypeNull.Instance, "?" );
Update(pos, new TypeInt( Position-pos ) );
Expr();
Update(pos2, new TypeInt( Position-pos2 ) );
}
/// <summary>
///IfStmt
/// : IF LPARA Expr RPARA OptionalBlock
/// ;
/// </summary>
protected void IfStmt( )
{
Match( JsToken.IF );
Match( JsToken.LPARA );
Expr();
Match( JsToken.RPARA );
int pos = Emit( Op.JMPZ, Mode.IM, TypeNull.Instance, "IF" );
OptionalBlock();
int pos2 = Emit( Op.JMP, Mode.IM, TypeNull.Instance, "IF" );
Update(pos, new TypeInt( Position-pos ) );
ElseStmt();
Update(pos2, new TypeInt( Position-pos2 ) );
}
/// <summary>
///ElseStmt
/// : ELSE OptionalBlock
/// | ELSE IfStmt
/// |
/// ;
/// </summary>
protected void ElseStmt()
{
if ( !m_lex.EOF && m_token.m_token == JsToken.ELSE )
{
Match( JsToken.ELSE );
if ( m_token.m_token == JsToken.IF )
{
IfStmt( );
}
else
{
OptionalBlock();
}
}
}
/// <summary>
///SwitchStmt
/// : SWITCH LPARA Expr RPARA LBRACE SwitchBlock RBRACE
/// ;
/// </summary>
protected void SwitchStmt()
{
Match( JsToken.SWITCH );
Match( JsToken.LPARA );
Expr();
Match ( JsToken.RPARA );
Match ( JsToken.LBRACE );
int breaktarget = Emit(Op.SETBREAK, Mode.IM, "Switch break target");
Emit(Op.ENTER, Mode.IM, "{");
TypeDispatch disp = new TypeDispatch();
int dispos = Emit(Op.DISPATCH, Mode.IM, disp, "switch");
disp.SetOffset(dispos);
SwitchBlock(disp);
Match ( JsToken.RBRACE );
Update(breaktarget, new TypeInt(Position - breaktarget));
disp.SetBreakTarget(Position);
Emit(Op.LEAVE, Mode.IM, "}");
Emit(Op.NOP, Mode.IM);
}
/// <summary>
///SwitchBlock
/// : CaseList Stmts SwitchBlock
/// |
/// ;
/// </summary>
protected void SwitchBlock(TypeDispatch disp)
{
if ( m_token.m_token == JsToken.DEFAULT )
{
DefaultCase(disp);
}
else if ( m_token.m_token == JsToken.CASE )
{
CaseList(disp);
}
else
{
return;
}
Stmts( );
SwitchBlock(disp);
}
/// <summary>
///CaseList
/// : CASE STRING COLON CaseList
/// | CASE STRING COLON
/// | CASE INT COLON CaseList
/// | CASE INT COLON
/// ;
/// </summary>
protected void CaseList(TypeDispatch disp)
{
Match( JsToken.CASE );
if (m_token.m_token == JsToken.QUOTE || m_token.m_token == JsToken.TIC)
{
disp.AddTarget(new TypeString(ReadString()), Position);
}
else if (m_token.m_token == JsToken.DOUBLE)
{
disp.AddTarget(new TypeDouble(m_token.m_text), Position);
Match(JsToken.DOUBLE);
}
else if (m_token.m_token == JsToken.INT)
{
disp.AddTarget(new TypeInt(m_token.m_text), Position);
Match(JsToken.INT);
}
else
{
throw new ParseError("Unexpected case type of " + m_token.m_text);
}
Match ( JsToken.COLON );
if ( m_token.m_token == JsToken.CASE )
{
CaseList(disp);
}
}
protected void DefaultCase(TypeDispatch disp)
{
Match( JsToken.DEFAULT );
Match ( JsToken.COLON );
disp.SetDefaultTarget(Position);
}
/// <summary>
///DoStmt
/// : DO OptionalBlock WHILE LPARA Expr RPARA SEMI
/// ;
/// </summary>
protected void DoStmt()
{
Match( JsToken.DO );
OptionalBlock();
Match ( JsToken.WHILE );
Match ( JsToken.LPARA );
Expr();
Match ( JsToken.RPARA );
}
/// <summary>
///ForStmt
/// : FOR LPARA AssignStmt SEMI Expr SEMI Expr RPARA OptionalBlock
/// : FOR LPARA VarDef SEMI Expr SEMI Expr RPARA OptionalBlock
/// ;
/// </summary>
protected void ForStmt()
{
bool itermode = false;
TypeString itername = null;
Match ( JsToken.FOR );
Match ( JsToken.LPARA );
//
// initialize loop. initializer can have local vars, so enter
//
Emit( Op.ENTER, Mode.IM );
ForInit();
if ( JsToken.IN == m_token.m_token )
{
Match( JsToken.IN );
TypeString varname = new TypeString( m_token.m_text );
//Emit( Op.PUSH, Mode.IM, varname, "ITER VAR NAME" );
//Emit(Op.REF_PROP, Mode.INDIR, varname, "ITER VAR");
itername = new TypeString(varname.ToString() + "_iter");
Reference();
Emit( Op.NEWITER, Mode.IM, itername, "for-in" );
itermode = true;
}
else
{
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
}
// create a pseudo var for break
int breaktarget = Emit( Op.SETBREAK, Mode.IM, "for-break_target" );
//
// start of loop
//
int top = Position;
if ( itermode )
{
Emit(Op.ITERNEXT, Mode.INDIR, itername, "NEXT");
}
else
{
// condition
Expr();
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
}
// check result of condition
int exitpos = Emit( Op.JMPZ, Mode.IM, "for-cond" );
// jump over the increment block
int incpos = Emit( Op.JMP, Mode.IM, "for-skip-inc" ) + 1;
if ( itermode )
{
}
else
{
//
// increment
// NOTE: expr i++ leaves the value on the stack
//
Expr();
}
Match ( JsToken.RPARA );
// after increment, jump to the top of the loop (re-eval condition)
Emit( Op.JMP, Mode.IM, new TypeInt(-(Position-top)), "for-goto top" );
//
// loop body
//
int body = Position;
// update jump over increment stmt to the body location
Update( incpos-1, new TypeInt( body - incpos + 1 ) );
OptionalBlock();
// jump to the increment block
Emit( Op.JMP, Mode.IM, new TypeInt( - (Position-incpos) ), "for-goto inc");
// update the loop exit location
Update( exitpos, new TypeInt( Position-exitpos ) );
Update( breaktarget, new TypeInt( Position - breaktarget ) );
Emit( Op.LEAVE, Mode.IM, "for-end loop" );
}
/// <summary>
/// for_init
/// : for_init_part , for_init
/// | for_init_part
/// |
/// ;
/// </summary>
protected void ForInit()
{
if ( m_token.m_token == JsToken.SEMI )
{
return;
}
ForInitPart();
if ( m_token.m_token == JsToken.COMMA )
{
Match( JsToken.COMMA );
ForInit();
}
}
/// <summary>
/// for_init_part
/// : VAR ID = expr
/// | ID = expr
/// ;
/// </summary>
protected void ForInitPart()
{
if ( m_token.m_token == JsToken.VAR )
{
Match( JsToken.VAR );
TypeString varname = new TypeString( m_token.m_text );
Emit( Op.NEWPROP, Mode.IM, varname, "for-init" );
Match( JsToken.ID );
if (m_token.m_token == JsToken.IN)
{
return;
}
Match( JsToken.ASSIGN );
Expr();
Emit( Op.ASSIGN, Mode.STK_IM, "for-init" );
}
else
{
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), "for-init" );
Match( JsToken.ID );
if (m_token.m_token == JsToken.IN)
{
return;
}
Match( JsToken.ASSIGN );
Expr();
Emit( Op.ASSIGN, Mode.STK_IM, "for-init" );
}
}
/// <summary>
///WhileStmt
/// : WHILE LPARA Expr RPARA OptionalBlock
/// ;
/// </summary>
protected void WhileStmt()
{
Match ( JsToken.WHILE );
Match ( JsToken.LPARA );
int start = Position;
Expr();
Match ( JsToken.RPARA );
int jmpend = Emit( Op.JMPZ, Mode.IM, TypeNull.Instance, "while body" );
OptionalBlock();
int end = Emit( Op.JMP, Mode.IM, new TypeInt( -(Position - start) ), "while loop" );
Update( jmpend, new TypeInt( end+1 - jmpend ) );
}
/// <summary>
///ReturnStmt
/// : RETURN LPARA Expr RPARA SEMI
/// | RETURN Expr SEMI
/// | RETURN SEMI
/// ;
/// </summary>
protected void ReturnStmt()
{
Match( JsToken.RETURN );
//if ( m_token.m_token == JsToken.LPARA )
//{
// Match ( JsToken.LPARA );
// Expr();
// Match ( JsToken.RPARA );
//}
/*else*/ if ( m_token.m_token != JsToken.SEMI )
{
Expr();
}
Emit( Op.RETURN, Mode.IM, TypeNull.Instance, "" );
}
/// <summary>
///ThrowStmt
/// : THROW ObjOp SEMI
/// ;
/// </summary>
protected void ThrowStmt()
{
Match( JsToken.THROW );
Reference();
if (m_strict || m_token.m_token == JsToken.SEMI)
{
Match(JsToken.SEMI);
}
}
/// <summary>
///TryStmt
/// : TRY Block CatchBlock
/// ;
/// </summary>
protected void TryStmt()
{
Match ( JsToken.TRY );
Block();
CatchBlock();
}
/// <summary>
///CatchBlock
/// : CATCH LPARA ID RPARA Block CatchBlock
/// | FINALLY Block
/// |
/// ;
/// </summary>
protected void CatchBlock()
{
if ( m_token.m_token == JsToken.CATCH )
{
Match ( JsToken.CATCH );
Match ( JsToken.LPARA );
Match ( JsToken.ID );
Match ( JsToken.RPARA );
Block();
CatchBlock();
}
else if ( m_token.m_token == JsToken.FINALLY )
{
Match ( JsToken.FINALLY );
Block();
}
}
/// <summary>
///Expr
/// : LogTerm MoreLogTerms
/// ;
/// </summary>
protected void Expr()
{
AssignTerm();
MoreAssignTerms();
}
protected void AssignTerm()
{
LogTerm();
MoreLogTerms();
}
/// <summary>
///
/// | = expr
/// | += expr
/// | -= expr
/// | *= expr
/// | \= expr
/// | |= expr
/// | &= expr
/// | ^= expr
/// | %= expr
/// </summary>
protected void MoreAssignTerms()
{
switch ( m_token.m_token )
{
case JsToken.ASSIGN:
//
// Stack <val>,<lvalue> ==> .
//
Match ( JsToken.ASSIGN );
Expr();
//if (m_token.m_token != JsToken.SEMI)
//{
// Emit(Op.DUP, Mode.IM);
// Emit(Op.ROT3, Mode.IM);
// Emit(Op.ROT, Mode.IM);
//}
Emit( Op.ASSIGN, Mode.STK_IM, TypeUndefined.Instance, "" );
m_requirePop = true;
break;
case JsToken.PLUSEQ:
//
// Stack: <val>,<lvalue> ==> .
// BUG: this should leave an rvalue on the stack
//
Match ( JsToken.PLUSEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "+=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "+=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "+=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "+=" );
// sum the values
Emit( Op.ADD, Mode.STK_IM, "+=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "+=" );
break;
case JsToken.MINEQ:
Match ( JsToken.MINEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "-=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "-=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "-=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "-=" );
// sum the values
Emit( Op.SUB, Mode.STK_IM, "-=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "-=" );
break;
case JsToken.TIMESEQ:
Match ( JsToken.TIMESEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "*=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "*=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "*=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "*=" );
// sum the values
Emit( Op.MULT, Mode.STK_IM, "*=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "*=" );
break;
case JsToken.DIVEQ:
Match ( JsToken.DIVEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "/=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "/=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "/=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "/=" );
// sum the values
Emit( Op.DIV, Mode.STK_IM, "/=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "/=" );
break;
case JsToken.OREQ:
Match ( JsToken.OREQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "|=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "|=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "|=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "|=" );
// sum the values
Emit( Op.BINOR, Mode.STK_IM, "|=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "|=" );
break;
case JsToken.ANDEQ:
Match ( JsToken.ANDEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "&=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "&=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "&=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "&=" );
// sum the values
Emit( Op.BINAND, Mode.STK_IM, "&=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "&=" );
break;
case JsToken.XOREQ:
Match ( JsToken.XOREQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "^=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "^=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "^=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "^=" );
// sum the values
Emit( Op.XOR, Mode.STK_IM, "^=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "^=" );
break;
case JsToken.MODEQ:
Match ( JsToken.MODEQ );
Expr();
// put the lvalue on top
Emit( Op.ROT, Mode.STK_IM, "%=" );
// copy the lvalue (pushes an rvalue)
Emit( Op.DUP, Mode.STK_IM, "%=" );
// put the lvalue back on top
Emit( Op.ROT, Mode.STK_IM, "%=" );
// put the left-hand-side on the bottom
Emit( Op.ROT3, Mode.STK_IM, "%=" );
// sum the values
Emit( Op.MOD, Mode.STK_IM, "%=" );
// assign the result
Emit( Op.ASSIGN, Mode.STK_IM, "%=" );
break;
default:
return;
}
}
/// <summary>
///LogTerm
/// : RelTerm MoreRelTerms
/// ;
/// </summary>
protected void LogTerm()
{
RelTerm();
MoreRelTerms();
}
protected void PushExprShortCutStart(Op jmpop)
{
Emit(Op.DUP, Mode.IM);
PushExprFixup(Emit(jmpop, Mode.IM, TypeNull.Instance, "shortcut eval"));
}
protected Stack<int> m_exprFixup = new Stack<int>();
protected void PushExprFixup(int pos)
{
m_exprFixup.Push(pos);
}
protected void FixupExpr()
{
int pos = m_exprFixup.Pop();
Update( pos, new TypeInt(Position-pos) );
}
/// <summary>
///MoreLogTerms
/// : AND LogTerm MorLogTerms
/// | LOG_AND LogTerm MorLogTerms
/// | OR LogTerm MorLogTerms
/// | LOG_OR LogTerm MorLogTerms
/// | XOR LogTerm MorLogTerms
/// | COMP LogTerm MoreLogTerms
/// | CONDITIONAL expr COLON expr
/// ;
/// </summary>
protected void MoreLogTerms()
{
switch ( m_token.m_token )
{
case JsToken.AND:
Match ( JsToken.AND );
LogTerm();
Emit( Op.BINAND, Mode.STK_IM, TypeNull.Instance, "&" );
break;
case JsToken.LOG_AND:
PushExprShortCutStart(Op.JMPZ);
Match ( JsToken.LOG_AND );
LogTerm();
Emit(Op.AND, Mode.STK_IM, TypeNull.Instance, "&&");
FixupExpr();
break;
case JsToken.OR:
Match ( JsToken.OR );
LogTerm();
Emit( Op.BINOR, Mode.STK_IM, TypeNull.Instance, "|" );
break;
case JsToken.LOG_OR:
PushExprShortCutStart(Op.JMPNZ);
Match(JsToken.LOG_OR);
LogTerm();
Emit(Op.OR, Mode.STK_IM, TypeNull.Instance, "||");
FixupExpr();
break;
case JsToken.XOR:
Match ( JsToken.XOR );
LogTerm();
Emit( Op.XOR, Mode.STK_IM, TypeNull.Instance, "^" );
break;
case JsToken.COMP:
Match ( JsToken.COMP );
LogTerm();
Emit( Op.COMPL, Mode.STK_IM, TypeNull.Instance, "~" );
break;
case JsToken.CONDITIONAL:
Match(JsToken.CONDITIONAL);
int pos = Emit(Op.JMPZ, Mode.IM, TypeNull.Instance, "?");
Expr();
Match(JsToken.COLON);
int pos2 = Emit(Op.JMP, Mode.IM, TypeNull.Instance, "?:");
Update(pos, new TypeInt(Position - pos));
Expr();
Update(pos2, new TypeInt(Position - pos2));
break;
default:
return;
}
MoreLogTerms();
}
/// <summary>
///RelTerm
/// : Term MoreTerms
/// ;
/// </summary>
protected void RelTerm()
{
Term();
MoreTerms();
}
/// <summary>
///MoreRelTerms
/// : LT RelTerm MoreRelTerms
/// | GT RelTerm MoreRelTerms
/// | LTEQ RelTerm MoreRelTerms
/// | GTEQ RelTerm MoreRelTerms
/// | EQEQ RelTerm MoreRelTerms
/// | NEQ RelTerm MoreRelTerms
/// ;
/// </summary>
protected void MoreRelTerms()
{
switch ( m_token.m_token )
{
case JsToken.LT:
Match ( JsToken.LT );
RelTerm();
Emit( Op.LT, Mode.STK_IM, TypeNull.Instance, "<" );
break;
case JsToken.GT:
Match ( JsToken.GT );
RelTerm();
Emit( Op.GT, Mode.STK_IM, TypeNull.Instance, ">" );
break;
case JsToken.LTEQ:
Match ( JsToken.LTEQ );
RelTerm();
Emit( Op.LTEQ, Mode.STK_IM, TypeNull.Instance, "<=" );
break;
case JsToken.GTEQ:
Match ( JsToken.GTEQ );
RelTerm();
Emit( Op.GTEQ, Mode.STK_IM, TypeNull.Instance, ">=" );
break;
case JsToken.EQEQ:
Match ( JsToken.EQEQ );
RelTerm();
Emit( Op.EQ, Mode.STK_IM, TypeNull.Instance, "==" );
break;
case JsToken.NEQ:
Match ( JsToken.NEQ );
RelTerm();
Emit( Op.NEQ, Mode.STK_IM, TypeNull.Instance, "!=" );
break;
default:
return;
}
MoreRelTerms();
}
/// <summary>
///Term
/// : Factor MoreFactors
/// ;
/// </summary>
protected void Term()
{
Factor();
MoreFactors();
}
/// <summary>
///MoreTerms
/// : PLUS Term MoreTerms
/// | MINUS Term MoreTerms
/// ;
/// </summary>
protected void MoreTerms()
{
switch ( m_token.m_token )
{
case JsToken.PLUS:
Match( JsToken.PLUS );
Term();
Emit( Op.ADD, Mode.STK_IM, TypeNull.Instance, "+" );
break;
case JsToken.MINUS:
Match( JsToken.MINUS );
Term();
Emit( Op.SUB, Mode.STK_IM, TypeNull.Instance, "-" );
break;
default:
return;
}
MoreTerms();
}
/// <summary>
///Factor
/// : ( Expr )
/// | ++ ID deref
/// | -- ID deref
/// | PLUS Factor
/// | MINUS Factor
/// | NOT Factor
/// | INT
/// | DOUBLE
/// | STRING
/// | FALSE
/// | TRUE
/// | NULL
/// | [ value_list ]
/// | INC reference
/// | DEC reference
/// | NEW FunctionDef
/// | NEW FunctionCall
/// | reference
/// ;
/// </summary>
protected void Factor()
{
switch ( m_token.m_token )
{
case JsToken.LPARA:
Match ( JsToken.LPARA );
Expr();
Match ( JsToken.RPARA );
if ( m_token.m_token == JsToken.CONDITIONAL )
{
Conditional();
}
if ( m_token.m_token == JsToken.DOT )
{
Deref("(expr)");
}
break;
case JsToken.MINUS:
Match ( JsToken.MINUS );
Factor();
Emit( Op.NEGATE, Mode.STK_IM, TypeNull.Instance, "-" );
break;
case JsToken.PLUS:
Match ( JsToken.PLUS );
Factor();
// Do Nothing
break;
case JsToken.NOT:
Match ( JsToken.NOT );
Factor();
Emit( Op.NOT, Mode.STK_IM, TypeNull.Instance, "!" );
break;
case JsToken.COMP:
Match ( JsToken.COMP );
Factor();
Emit( Op.COMPL, Mode.STK_IM, TypeNull.Instance, "~" );
break;
case JsToken.INT:
if ( m_token.m_text.Length > 7 )
{
Emit( Op.PUSH, Mode.IM, new TypeDouble( m_token.m_text ), m_token.m_text );
}
else
{
Emit(Op.PUSH, Mode.IM, new TypeInt(m_token.m_text), m_token.m_text);
}
Match ( JsToken.INT );
break;
case JsToken.DOUBLE:
Emit( Op.PUSH, Mode.IM, new TypeDouble( m_token.m_text ), m_token.m_text );
Match ( JsToken.DOUBLE );
break;
case JsToken.QUOTE:
case JsToken.TIC:
Emit( Op.PUSH, Mode.IM, new TypeString( ReadString() ), "STRING" );
if (m_token.m_token == JsToken.DOT)
{
Deref("'string'");
}
break;
case JsToken.REGEX:
string pattern = m_token.m_text;
Match( JsToken.REGEX );
if ( m_token.m_token == JsToken.ID )
{
Emit( Op.PUSH, Mode.IM, new TypeRegExp( pattern.Substring(1, pattern.Length-2), m_token.m_text ), "RegExp" );
Match( JsToken.ID );
}
else
{
Emit(Op.PUSH, Mode.IM, new TypeRegExp(pattern.Substring(1, pattern.Length - 2)), "RegExp");
}
break;
case JsToken.FALSE:
Emit( Op.PUSH, Mode.IM, new TypeBool( false ), m_token.m_text );
Match ( JsToken.FALSE );
break;
case JsToken.TRUE:
Emit( Op.PUSH, Mode.IM, new TypeBool( true ), m_token.m_text );
Match ( JsToken.TRUE );
break;
case JsToken.FUNCTION:
// anon function definition
Match(JsToken.FUNCTION);
AnonFunctionDef(false);
break;
case JsToken.NEW:
Match ( JsToken.NEW );
if ( m_token.m_token == JsToken.ID )
{
string idname = m_token.m_text;
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( idname ), "expr" );
Emit( Op.MARK, Mode.IM );
Match ( JsToken.ID );
if (m_token.m_token == JsToken.LPARA)
{
Match(JsToken.LPARA);
ValueList();
Match(JsToken.RPARA);
}
Emit( Op.NEW, Mode.STK_IM, TypeUndefined.Instance, m_token.m_text );
Emit( Op.UNMARK, Mode.IM );
// pop the function's return value
Emit( Op.POP, Mode.IM );
}
else
{
Match(JsToken.FUNCTION);
AnonFunctionDef(true);
}
break;
case JsToken.NULL:
Emit( Op.PUSH, Mode.IM, TypeNull.Instance, "null" );
Match ( JsToken.NULL );
break;
case JsToken.LBRACE:
// anon object definition: var x = {Id1:'bob', "Id2":6};
AnonObjectDef();
break;
case JsToken.LBRACK:
// array
Match(JsToken.LBRACK);
Emit(Op.PUSH, Mode.INDIR, new TypeString("Array"), "inline array");
Emit(Op.MARK, Mode.IM);
ValueList();
Match(JsToken.RBRACK);
Emit(Op.NEW, Mode.STK_IM, "inline array");
Emit(Op.UNMARK, Mode.IM);
Emit(Op.POP, Mode.STK_IM);
break;
default:
Reference();
break;
}
}
/// <summary>
///Reference
/// : ID deref
/// | THIS deref
/// | ID ++
/// | ID --
/// ;
/// </summary>
protected void Reference( )
{
switch ( m_token.m_token )
{
case JsToken.INC:
// ++ID
Match ( JsToken.INC );
string idname = m_token.m_text;
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), m_token.m_text );
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), m_token.m_text );
Emit( Op.PUSH, Mode.IM, TypeInt.One, m_token.m_text );
Emit( Op.ADD, Mode.STK_IM, TypeUndefined.Instance, "+" );
Emit( Op.STORE, Mode.STK_IM, TypeUndefined.Instance, "=" );
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), m_token.m_text );
Match ( JsToken.ID );
break;
case JsToken.DEC:
// --ID
Match ( JsToken.DEC );
idname = m_token.m_text;
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), m_token.m_text );
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), "--" );
Emit( Op.PUSH, Mode.IM, TypeInt.NegOne, m_token.m_text );
Emit( Op.ADD, Mode.STK_IM, TypeUndefined.Instance, "+" );
Emit( Op.STORE, Mode.STK_IM, TypeUndefined.Instance, "=" );
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( m_token.m_text ), m_token.m_text );
Match ( JsToken.ID );
break;
case JsToken.THIS:
Match( JsToken.THIS );
Emit( Op.THIS, Mode.IM, TypeUndefined.Instance, "" );
Deref("this");
break;
case JsToken.TYPEOF:
idname = m_token.m_text;
Match ( JsToken.TYPEOF );
Emit( Op.REF_PROP, Mode.INDIR, new TypeString( idname ), "expr" );
Deref("typeof(" + idname + ")");
//Reference();
break;
case JsToken.ID:
idname = m_token.m_text;
Match ( JsToken.ID );
Emit(Op.REF_PROP, Mode.INDIR, new TypeString(idname), "ID");
if (!PostIncDec(idname))
{
Deref(idname);
Reference(); // this.array[0]++
}
break;
default:
//throw new ParseError( "Exected identifier" );
break;
}
}
/// <summary>
///
/// Deref
/// : . ID deref
/// | ID ++
/// | ID --
/// | [ expr ] deref
/// | ( valuelist ) deref
/// |
/// ;
/// </summary>
protected void Deref(string hint)
{
// LValue is on the stack
switch ( m_token.m_token )
{
case JsToken.LBRACK:
Match ( JsToken.LBRACK );
Expr();
// load the LValue's property
Emit( Op.DEREF_PROP, Mode.STK_INDIR, TypeUndefined.Instance, hint + "[" );
Match ( JsToken.RBRACK );
Deref("[expr]");
break;
case JsToken.DOT:
Match ( JsToken.DOT );
string idname = m_token.m_text;
Emit(Op.PUSH, Mode.IM, new TypeString(idname), hint + "." + idname);
Emit(Op.DEREF_PROP, Mode.STK_IM, TypeUndefined.Instance, hint + "." + idname);
Match(JsToken.ID);
Deref(idname);
PostIncDec(idname);
break;
case JsToken.LPARA:
Match ( JsToken.LPARA );
// function call
Emit(Op.MARK, Mode.IM, TypeUndefined.Instance, "call " + hint);
ValueList();
Match ( JsToken.RPARA );
// function is already on the stack
Emit( Op.CALL, Mode.STK_IM, TypeUndefined.Instance, "call " + hint );
Emit( Op.UNMARK, Mode.IM, TypeUndefined.Instance, "call " + hint );
if ( m_token.m_token == JsToken.DOT )
{
Deref(hint + "()");
}
break;
default:
return;
}
}
protected bool PostIncDec(string idname)
{
/*
* At this point, an lvalue that we want to increment is on the stack.
*/
if (m_token.m_token == JsToken.INC)
{
Match(JsToken.INC);
//Emit(Op.RVALUE, Mode.INDIR, new TypeString(idname), idname);
//Emit(Op.REF_PROP, Mode.INDIR, new TypeString(idname), idname);
//Emit(Op.REF_PROP, Mode.INDIR, new TypeString(idname), idname);
//Emit(Op.PUSH, Mode.IM, TypeInt.One, idname);
//Emit(Op.ADD, Mode.STK_IM, TypeUndefined.Instance, idname);
//Emit(Op.ASSIGN, Mode.STK_IM, TypeUndefined.Instance, idname);
Emit(Op.CLONE, Mode.STK_IM, idname + "++");
Emit(Op.RVALUE, Mode.STK_IM);
Emit(Op.ROT, Mode.STK_IM);
Emit(Op.CLONE, Mode.STK_IM, idname + "++");
Emit(Op.PUSH, Mode.IM, TypeInt.One, idname + "++");
Emit(Op.ADD, Mode.STK_IM, idname + "++");
Emit(Op.STORE, Mode.STK_IM, idname + "++");
return true;
}
else if (m_token.m_token == JsToken.DEC)
{
Match(JsToken.DEC);
//Emit( Op.RVALUE, Mode.INDIR, new TypeString( idname ), idname );
//Emit(Op.REF_PROP, Mode.INDIR, new TypeString(idname), idname);
//Emit(Op.REF_PROP, Mode.INDIR, new TypeString(idname), idname);
//Emit(Op.PUSH, Mode.IM, TypeInt.NegOne, idname);
//Emit(Op.ADD, Mode.STK_IM, TypeUndefined.Instance, idname);
//Emit(Op.ASSIGN, Mode.STK_IM, TypeUndefined.Instance, idname);
Emit(Op.CLONE, Mode.STK_IM, idname + "--");
Emit(Op.RVALUE, Mode.STK_IM);
Emit(Op.ROT, Mode.STK_IM);
Emit(Op.CLONE, Mode.STK_IM, idname + "--");
Emit(Op.PUSH, Mode.IM, TypeInt.One, idname + "--");
Emit(Op.SUB, Mode.STK_IM, idname + "--");
Emit(Op.STORE, Mode.STK_IM, idname + "--");
return true;
}
return false;
}
//protected void MoreDots()
//{
// if (m_token.m_token == JsToken.ID)
// {
// Emit(Op.PUSH, Mode.IM, new TypeString(m_token.m_text), "expr");
// Emit(Op.DEREF_PROP, Mode.STK_IM, TypeUndefined.Instance, "dot");
// Match(JsToken.ID);
// Deref();
// }
//}
/// <summary>
///MoreFactors
/// : STAR Factor MoreFactors
/// | DIV Factor MoreFactors
/// | MOD Factor MoreFactors
/// |
/// ;
/// </summary>
protected void MoreFactors()
{
if ( m_token == null )
{
throw new ParseError("Unexpected EOF on line " + m_lex.LineNumber);
}
switch ( m_token.m_token )
{
case JsToken.STAR:
Match ( JsToken.STAR );
Factor();
Emit( Op.MULT, Mode.STK_IM, TypeUndefined.Instance, "" );
break;
case JsToken.DIV:
Match( JsToken.DIV );
Factor();
Emit( Op.DIV, Mode.STK_IM, TypeUndefined.Instance, "" );
break;
case JsToken.MOD:
Match ( JsToken.MOD );
Factor();
Emit( Op.MOD, Mode.STK_IM, TypeUndefined.Instance, "" );
break;
//case JsToken.CONDITIONAL:
// Match(JsToken.CONDITIONAL);
// int pos = Emit(Op.JMPZ, Mode.IM, TypeNull.Instance, "?");
// Expr();
// Match(JsToken.COLON);
// int pos2 = Emit(Op.JMP, Mode.IM, TypeNull.Instance, "?:");
// Update(pos, new TypeInt(Position - pos));
// Expr();
// Update(pos2, new TypeInt(Position - pos2));
// break;
default:
return;
}
MoreFactors();
}
/// <summary>
///ValueList
/// : Expr COMMA ValueList
/// | Expr
/// |
/// ;
/// </summary>
protected void ValueList()
{
if ( m_token.m_token == JsToken.RPARA )
{
return;
}
Expr();
// put the function back on top of the stack
Emit( Op.ROT, Mode.STK_IM, TypeUndefined.Instance, "" );
if ( m_token.m_token == JsToken.COMMA )
{
Match ( JsToken.COMMA );
ValueList();
}
}
protected void Match( JsToken expt )
{
if ( m_token.m_token != expt )
{
throw new ParseError( "Expected " + expt.ToString() + ", found " + m_token.m_token.ToString() + " (" + m_token.m_text + ") on line " + m_token.m_line );
}
if (null == (m_token = m_lex.yylex()))
{
m_token = new Yytoken(JsToken.EOF, "", m_lex.LineNumber);
}
}
protected int Emit( Op op, Mode mode, string debughint )
{
return Emit( op, mode, TypeUndefined.Instance, debughint );
}
protected int Emit( Op op, Mode mode )
{
return Emit( op, mode, TypeUndefined.Instance, "" );
}
protected int Emit( Op op, Mode mode, JsObject arg, string debughint )
{
return m_emit.Add( new Command( op, mode, arg, m_lex.LineNumber, debughint) );
}
protected void Update(int pos, JsObject arg)
{
m_emit[pos].Arg = arg;
}
protected void UpdateDebugInfo(int pos, string debughint)
{
m_emit[pos].DebugHint = debughint;
}
protected int Position
{
get
{
return m_emit.Count;
}
}
protected string ReadString()
{
if (m_token.m_token == JsToken.TIC)
{
return ReadStringTo(JsToken.TIC, JsToken.ESCTIC);
}
else if (m_token.m_token == JsToken.QUOTE)
{
return ReadStringTo(JsToken.QUOTE, JsToken.ESCQUOTE);
}
else
{
throw new ParseError("Expected string on line " + m_lex.LineNumber.ToString());
}
}
protected string ReadStringTo(JsToken endtok, JsToken esctok)
{
StringBuilder buf = new StringBuilder();
Match(endtok);
while (m_token.m_token != endtok)
{
if (m_token.m_token == JsToken.STRTXT)
{
buf.Append(m_token.m_text);
Match(JsToken.STRTXT);
}
else if (m_token.m_token == esctok)
{
Debug.Assert(m_token.m_text.Length == 2);
Debug.Assert(m_token.m_text[1] == '"' || m_token.m_text[1] == '\'');
buf.Append(m_token.m_text[1]);
Match(esctok);
}
else if (m_token.m_token == JsToken.ESCQUOTE)
{
buf.Append('"');
Match(JsToken.ESCQUOTE);
}
else if (m_token.m_token == JsToken.ESCTIC)
{
buf.Append('\'');
Match(JsToken.ESCTIC);
}
else if (m_token.m_token == JsToken.ESCCHAR)
{
Debug.Assert(m_token.m_text.Length == 2);
Debug.Assert(m_token.m_text[0] == '\\');
switch (m_token.m_text[1])
{
case '0':
buf.Append('\0');
break;
case '\\':
buf.Append('\\');
break;
case '!':
buf.Append('!');
break;
case 'b':
buf.Append('\b');
break;
case 'f':
buf.Append('\f');
break;
case 'n':
buf.Append('\n');
break;
case 'r':
buf.Append('\r');
break;
case 't':
buf.Append('\t');
break;
case 'v':
buf.Append('\v');
break;
case 'u':
// unicode \u000A
throw new ParseError("UNICODE escape sequences not supported");
default:
throw new ParseError("Unknown escape sequence " + m_token.m_text);
}
Match(JsToken.ESCCHAR);
}
else
{
throw new ParseError("Unexpected text in string '" + m_token.m_text + "'");
}
}
Match(endtok);
return buf.ToString();
}
/// <summary>
/// When false, attempt to infer missing semi colons
/// </summary>
private bool m_strict;
/// <summary>
/// Current token
/// </summary>
private Yytoken m_token;
/// <summary>
/// The lexical analyzer (created from javascript.lex)
/// </summary>
private Yylex m_lex;
/// <summary>
/// Code generation helper
/// </summary>
private ExecList m_emit;
/// <summary>
/// F
/// </summary>
private bool m_requirePop;
}
}