/** 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.Collections.Generic;
using System.Diagnostics;
namespace JavaScript
{
/// <summary>
/// Summary description for Interp.
/// </summary>
public class JsInterp
{
public JsInterp( JsObject global, ExecList execlist, bool strictTyping )
{
m_strictTyping = strictTyping;
execlist.Reset();
m_prog.Push( execlist );
m_context.Push( new StackFrame(m_context, global ) );
}
public StackFrame Context
{
get { return (StackFrame)m_context.Peek(); }
}
public JsObject ReturnValue()
{
if (Context.Stack.Count == 0)
{
return TypeUndefined.Instance;
}
return LValue.CheckRValue(Context.Stack.Peek());
}
public JsObject Run(Stack<StackFrame> ctx)
{
Stack<StackFrame> oldctx = m_context;
m_context = ctx;
JsObject ret = Run();
m_context = oldctx;
return ret;
}
public JsObject Run(StackFrame frame)
{
m_context.Push(frame);
JsObject ret = Run();
m_context.Pop();
return ret;
}
public JsObject Run( )
{
int stackstart = Context.Stack.Count;
PrepareToRun();
try
{
while (m_prog.Count > 0 && !((ExecList)m_prog.Peek()).EOF)
{
Step();
}
}
catch (EndCurrentProgramException)
{
}
if ( Context.Stack.Count == stackstart )
{
return TypeUndefined.Instance;
}
m_prepared = false;
return LValue.CheckRValue(Context.Stack.Peek());
}
public void PrepareToRun()
{
((ExecList)m_prog.Peek()).Reset();
((ExecList)m_prog.Peek()).Activate(Context);
m_prepared = true;
}
public bool Step()
{
JsObject a = null, b = null;
LValue lvalue = null;
int markstackpos;
Debug.Assert(m_prepared);
Command cmd = ((ExecList)m_prog.Peek()).Fetch();
switch ( cmd.OpCode )
{
case Op.POP:
Debug.Assert( Context.Stack.Count > 0, "Stack empty" );
Context.Stack.Pop();
break;
case Op.PUSH:
Context.Stack.Push( cmd.Deref( Context ) );
break;
case Op.CLEAR_STK:
Context.Stack.Clear();
break;
case Op.ROT:
// Rotate
// Stack before: a
// b
//
// Stack after: b
// a
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
object oa = Context.Stack.Pop();
object ob = Context.Stack.Pop();
Debug.Assert(null != oa && null != ob);
Context.Stack.Push( oa );
Context.Stack.Push( ob );
break;
case Op.ROT3:
// Rotate
// Stack before: a
// b
// c
//
// Stack after: c
// b
// a
Debug.Assert( Context.Stack.Count > 2, "Stack empty" );
oa = Context.Stack.Pop();
ob = Context.Stack.Pop();
object oc = Context.Stack.Pop();
Context.Stack.Push( oa );
Context.Stack.Push( ob );
Context.Stack.Push( oc );
break;
case Op.ADD:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push( b.PlusOperator(a) );
break;
case Op.SUB:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.MinusOperator(a));
break;
case Op.NEGATE:
Debug.Assert( Context.Stack.Count > 0, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = new TypeInt(-1);
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(a.TimesOperator(b));
break;
case Op.MULT:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.TimesOperator(a));
break;
case Op.DIV:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.DivOperator(a));
break;
case Op.MOD:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.ModOperator(a));
break;
case Op.AND:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
//CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.ToBool().AndOperator(a.ToBool()));
break;
case Op.BINAND:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.BitAndOperator(a));
break;
case Op.OR:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
//CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.ToBool().OrOperator(a.ToBool()));
break;
case Op.BINOR:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.BitOrOperator(a));
break;
case Op.XOR:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableOp(ref b, ref a);
Context.Stack.Push(b.XorOperator(a));
break;
case Op.COMPL:
Debug.Assert( Context.Stack.Count > 0, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
Context.Stack.Push( a.OnesComplOperator() );
break;
case Op.NOT:
Debug.Assert( Context.Stack.Count > 0, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
Context.Stack.Push( a.NotOperator() );
break;
case Op.LT:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.LessThanOperator(a));
break;
case Op.GT:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare( ref b, ref a );
Context.Stack.Push( b.GreaterThanOperator(a) );
break;
case Op.LTEQ:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.LessThanEqOperator(a));
break;
case Op.GTEQ:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.GreaterThanEqOperator(a));
break;
case Op.EQ:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.EqualOperator(a));
break;
case Op.NEQ:
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context));
b = LValue.CheckRValue(cmd.Deref(Context));
CovertCompatableCompare(ref b, ref a);
Context.Stack.Push(b.EqualOperator(a).Not());
break;
case Op.NEWPROP:
//
// Add a property to the current context
// Stack before: <string>
// Stack after: LVALUE
//
Context.Stack.Push( Context.NewLValue( cmd.Arg.ToString() ) );
break;
case Op.NEWITER:
//
// Create a new interator, store it in prog cmd.arg
// Stack before: <collection, iter var lvalue>
// Stack after: .
//
a = LValue.CheckRValue(Context.Stack.Pop());
lvalue = (LValue)Context.Stack.Pop();
if (a is TypeArray)
{
b = new TypeIter(((TypeArray)a).Indexes, lvalue);
}
else
{
b = new TypeIter(a._Properties, lvalue);
}
Context.NewLValue(cmd.Arg.ToString()).Set(b);
break;
case Op.ITERNEXT:
//
// Push 1 on the stack, or 0 if finished
// Stack before: .
// Stack after: rvalue
//
a = (JsObject)LValue.CheckRValue(cmd.Deref(Context));
Debug.Assert(a is TypeIter);
Context.Stack.Push(((TypeIter)a).Next);
break;
case Op.ASSIGN:
//
// Put a value in a variable
// Stack before: <type>
// LVALUE
// Stack after: LVALUE
//
Debug.Assert( Context.Stack.Count > 1, "Stack empty" );
a = LValue.CheckRValue(cmd.Deref(Context)); // Rvalue
lvalue = (LValue)Context.Stack.Pop(); // Lvalue
Debug.Assert(null != a);
lvalue.Set( a );
Context.Stack.Push(lvalue);
break;
case Op.STORE:
//
// Put a value in a variable
// Stack before: <type>
// LVALUE
// Stack after: .
//
Debug.Assert(Context.Stack.Count > 1, "Stack empty");
a = LValue.CheckRValue(cmd.Deref(Context)); // Rvalue
lvalue = (LValue)Context.Stack.Pop(); // Lvalue
Debug.Assert(null != a);
lvalue.Set(a);
break;
case Op.REF_PROP:
//
// Push the rvalue of the property named on the stack
// Stack before: <string>
// Stack after: LVALUE
//
Context.Stack.Push( cmd.Deref(Context) );
break;
case Op.DEREF_PROP:
//
// Load a named property from the LVALUE
// Stack: <value>,LVALUE1 ==> LVALUE2
//
a = LValue.CheckRValue( Context.Stack.Pop() );
b = LValue.CheckRValue( Context.Stack.Pop() );
Debug.Assert(a != null && b != null);
Debug.Assert(null != b.GetPropertyLValue(a.ToString()));
lvalue = b.GetPropertyLValue( a.ToString() );
if ( lvalue.Get() is TypeFunction && ((TypeFunction)lvalue.Get()).IsAnonFunction )
{
((TypeFunction)lvalue.Get()).OuterObject = b;
}
Context.Stack.Push( lvalue );
break;
case Op.CLONE:
//
// Copy the LValue on the stack
//
//
if (cmd.AddressMode == Mode.STK_IM)
{
Debug.Assert(Context.Stack.Count > 0, "Stack empty");
lvalue = (LValue)Context.Stack.Peek();
}
else
{
lvalue = (LValue)cmd.Deref(Context);
}
Context.Stack.Push( lvalue );
break;
case Op.DUP:
//
// Stack: <any> ==> <any_rvalue>,<any>
//
a = LValue.CheckRValue( Context.Stack.Peek() );
Context.Stack.Push( a );
break;
case Op.RVALUE:
a = LValue.CheckRValue( cmd.Deref(Context) );
Context.Stack.Push( a.Dup() );
break;
case Op.THIS:
Debug.Assert(Context.IsInObjectContext, "No global this");
Context.Stack.Push( Context.This );
break;
case Op.CALL:
//
// Function call
// Stack before: <function>
// <arg1>
// <argN>
// Stack after: .
//
// Get the function
a = LValue.CheckRValue( Context.Stack.Pop() );
Debug.Assert( a is TypeFunction );
// pull the arguments
markstackpos = Context.Stack.Count - m_markstackpos.Pop();
Debug.Assert(markstackpos <= ((TypeFunction)a).ArgCount);
a.PrepareCall(Context.Stack, markstackpos);
m_markstackpos.Push(markstackpos);
// establish the new stack frame ( context )
m_context.Push(new StackFrame(m_context, Context, (TypeFunction)a));
if (a is TypeFunction && ((TypeFunction)a).IsAnonFunction)
{
Context.EnterContext(((TypeFunction)a).OuterObject);
}
else
{
Context.EnterContext(a);
}
if (a is TypeFunctionObject)
{
Context.Stack.Push(((TypeFunctionObject)a).Execute(Context));
}
else
{
// push the function's code
m_prog.Push(((TypeFunction)a).Body);
((ExecList)m_prog.Peek()).Reset();
}
break;
case Op.NEW:
//
// leave the function on the stack
//
// Stack before: <function>
// <arg1>
// <argN>
//
// Stack after: <function:2>
//
a = LValue.CheckRValue( Context.Stack.Pop() );
// create a new instance of the function
a = a.Dup();
if( a is TypeFunction && !(a is TypeFunctionObject) )
{
// set up the call to the new function
markstackpos = Context.Stack.Count - m_markstackpos.Pop();
//Debug.Assert(((TypeFunction)a).Name == "Array" || markstackpos <= ((TypeFunction)a).ArgCount);
Debug.Assert(markstackpos <= ((TypeFunction)a).ArgCount);
a.PrepareCall(Context.Stack, markstackpos);
m_markstackpos.Push(markstackpos);
// save the function
Context.Stack.Push( a );
// create the stack frame ( context ) for the call
m_context.Push( new StackFrame(m_context, (JsObject)Context.Global ) );
Context.EnterContext( a );
// push the function's code
m_prog.Push( ((TypeFunction)a).Body );
((ExecList)m_prog.Peek()).Reset();
}
else
{
// a must be a type
markstackpos = Context.Stack.Count - m_markstackpos.Pop();
a.Construct(Context.Stack, markstackpos);
m_markstackpos.Push(markstackpos);
Context.Stack.Push(a);
Context.Stack.Push(TypeUndefined.Instance);
}
break;
case Op.RETURN:
if ( Context.Stack.Count > 0 )
{
a = LValue.CheckRValue( Context.Stack.Pop() );
}
else
{
a = TypeUndefined.Instance;
}
Debug.Assert(a != null);
if (m_context.Count > 1)
{
// RETURN is legal in the global context, so don't pop
// in that case.
m_context.Pop();
}
m_prog.Peek().End();
m_prog.Pop();
Context.Stack.Push( a );
break;
case Op.MARK:
Context.Mark();
m_markstackpos.Push( Context.Stack.Count-1);
break;
case Op.UNMARK:
Context.UnMark();
m_markstackpos.Pop();
break;
case Op.ENTER:
if (cmd.AddressMode == Mode.IM)
{
Context.EnterContext(new JsObject());
}
else if (cmd.AddressMode == Mode.STK_IM)
{
Context.EnterContext(LValue.CheckRValue(Context.Stack.Pop()));
}
else
{
throw new ArgumentException("Invalid address mode for ENTER");
}
break;
case Op.LEAVE:
Context.LeaveContext();
break;
case Op.PUSH_CONTEXT:
//
// Load a property as the current context
// Stack before: .
// Stack after: .
//
a = LValue.CheckRValue( cmd.Deref( Context ) );
Context.EnterContext( a );
break;
case Op.NOP:
break;
case Op.JMPZ:
//
// Jump absolute if the top of the stack is zero
// Stack before: <val>
// Stack after: .
//
a = LValue.CheckRValue( Context.Stack.Pop() );
if ( a is TypeNull )
{
((ExecList)m_prog.Peek()).Jump( ((TypeInt)cmd.Deref(Context)).IntValue() );
}
else if ( a is TypeUndefined )
{
((ExecList)m_prog.Peek()).Jump( ((TypeInt)cmd.Deref(Context)).IntValue() );
}
else if ( a is TypeInt || a is TypeBool || a is TypeDouble )
{
if ( ((TypeInt)a.ToInt()).IntValue() == 0 )
{
((ExecList)m_prog.Peek()).Jump( ((TypeInt)cmd.Deref(Context)).IntValue() );
}
}
else if (a is TypeString)
{
if (a.ToString().Length == 0)
{
((ExecList)m_prog.Peek()).Jump(((TypeInt)cmd.Deref(Context)).IntValue());
}
}
break;
case Op.JMPNZ:
//
// Jump absolute if the top of the stack is NOT zero
// Stack before: <val>
// Stack after: .
//
a = LValue.CheckRValue(Context.Stack.Pop());
if (a is TypeInt || a is TypeBool || a is TypeDouble)
{
if (((TypeInt)a.ToInt()).IntValue() != 0)
{
((ExecList)m_prog.Peek()).Jump(((TypeInt)cmd.Deref(Context)).IntValue());
}
}
else if (a is TypeString)
{
if (a.ToString().Length != 0)
{
((ExecList)m_prog.Peek()).Jump(((TypeInt)cmd.Deref(Context)).IntValue());
}
}
else if (a is TypeUndefined)
{
// don't jump
}
else
{
((ExecList)m_prog.Peek()).Jump(((TypeInt)cmd.Deref(Context)).IntValue());
}
break;
case Op.JMP:
//
// Jump
// Stack before: .
// Stack after: .
//
((ExecList)m_prog.Peek()).Jump( ((TypeInt)LValue.CheckRValue(cmd.Deref(Context))).IntValue() );
break;
case Op.BREAK:
((ExecList)m_prog.Peek()).Break( Context );
break;
case Op.SETBREAK:
Context.LValue( "__breaktarget" ).Set(cmd.Arg);
break;
case Op.DISPATCH:
a = LValue.CheckRValue(Context.Stack.Pop());
((ExecList)m_prog.Peek()).Jump(((TypeDispatch)LValue.CheckRValue(cmd.Deref(Context))).GetTarget(a));
break;
case Op.DEFTYPE:
//
// Define function
// Stack before: .
// Stack after: <function>.
//
//TypeFunction fn;
//if (cmd.DebugHint.StartsWith("FUNCTION"))
//{
TypeFunction fn = new TypeFunction(cmd.Arg.ToString(), ((ExecList)m_prog.Peek()).SourceCode);
//}
//else
//{
// fn = new TypeFunction(cmd.Arg.ToString(), ((ExecList)m_prog.Peek()).SourceCode/*cmd.DebugHint*/);
//}
Context.LValue(cmd.Arg.ToString().ToString()).Set(fn);
Context.Stack.Push( Context.RValue( cmd.Arg.ToString() ) );
break;
case Op.DEFARG:
//
// Add an argurment to function
// Stack before: <function>
// Stack after: <function>
//
((TypeFunction)Context.Stack.Peek()).AddArgument( cmd.Arg.ToString() );
break;
case Op.DEFBODY:
//
// Set a function's body
// Stack before: <function>
// Stack after: .
//
fn = (TypeFunction)Context.Stack.Pop();
fn.SetBody( m_prog.Peek(), ((TypeInt)cmd.Arg).IntValue() );
break;
case Op.NATIVE:
//
// Call a native function delegate
//
TypeNativeFunction nfn = (TypeNativeFunction)cmd.Arg;
a = nfn.Function( Context, m_markstackpos.Peek() );
if (! (a is TypeUndefined) )
{
Context.Stack.Push( a );
}
break;
case Op.DEBUGGER:
throw new DebuggerException(this);
default:
throw new ApplicationException( "Internal error: Unknown op code of " + cmd.OpCode );
}
Stack<object> stk = DebugGetStack();
object[] stkitms = stk.ToArray();
for (int x = 0; x < stkitms.Length; x++)
{
Debug.Assert(stkitms[x] != null);
}
if (m_prog.Count > 1 && ((ExecList)m_prog.Peek()).EOF)
{
m_prog.Pop();
}
return m_prog.Count == 0 || ((ExecList)m_prog.Peek()).EOF;
}
public bool EOF
{
get { return ((ExecList)m_prog.Peek()).EOF; }
}
protected void CovertCompatableOp(ref JsObject a, ref JsObject b)
{
if (a.GetType() == b.GetType())
{
return;
}
if (a is TypeNull || b is TypeNull)
{
return;
}
if (a is TypeString || b is TypeString)
{
if (!(a is TypeString))
{
a = a.ToJsString();
return;
}
b = b.ToJsString();
return;
}
if (a is TypeUndefined || b is TypeUndefined)
{
// symatically an error
if (m_strictTyping)
{
throw new TypeMismatchException(a.GetType().Name + " is not compatable with " + b.GetType().Name);
}
a = TypeUndefined.Instance;
b = TypeUndefined.Instance;
return;
}
if (a is TypeBool)
{
b = b.ToBool();
return;
}
if (b is TypeBool)
{
a = a.ToBool();
return;
}
if (a is TypeDouble)
{
b = b.ToDouble();
return;
}
if (b is TypeDouble)
{
a = a.ToDouble();
return;
}
if (a is TypeInt)
{
b = b.ToInt();
return;
}
if (b is TypeInt)
{
a = a.ToInt();
return;
}
throw new TypeMismatchException();
}
protected void CovertCompatableCompare(ref JsObject left, ref JsObject right)
{
if (left.GetType() == right.GetType())
{
return;
}
if (left is TypeUndefined || left is TypeNull)
{
return;
}
if (right is TypeUndefined || right is TypeNull)
{
JsObject v = right;
right = left;
left = v;
return;
}
if (left is TypeString)
{
right = right.ToJsString();
return;
}
if (right is TypeString)
{
left = left.ToJsString();
return;
}
if (left is TypeBool)
{
right = right.ToBool();
return;
}
if (right is TypeBool)
{
left = left.ToBool();
return;
}
if (left is TypeDouble)
{
right = right.ToDouble();
return;
}
if (right is TypeDouble)
{
left = left.ToDouble();
return;
}
if (left is TypeInt)
{
right = right.ToInt();
return;
}
if (right is TypeInt)
{
left = left.ToInt();
return;
}
if (right is TypeFunction || left is TypeFunction)
{
return;
}
throw new TypeMismatchException( );
}
public int Position
{
get
{
return ((ExecList)m_prog.Peek()).Position;
}
}
public Stack<object> DebugGetStack()
{
return Context.Stack;
}
public Command DebugGetCurrentCommand()
{
return m_prog.Peek().Current();
}
public ExecList DebugGetCurrentProg()
{
return m_prog.Peek();
}
public int DebugGetCurrentPC()
{
return m_prog.Peek().Position;
}
protected Stack<ExecList> m_prog = new Stack<ExecList>();
protected Stack<StackFrame> m_context = new Stack<StackFrame>();
protected Stack<int> m_markstackpos = new Stack<int>();
protected bool m_strictTyping;
protected bool m_prepared;
}
public class EndCurrentProgramException : Exception
{
public EndCurrentProgramException()
: base()
{
}
}
}