/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source 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.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
* Free SoftwareFoundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.es;
import com.caucho.es.parser.Parser;
import com.caucho.vfs.ReadStream;
import com.caucho.vfs.Vfs;
import java.io.IOException;
import java.util.ArrayList;
/**
* JavaScript object
*/
class NativeFunction extends Native {
static final int NEW = 1;
static final int TO_STRING = NEW + 1;
static final int CALL = TO_STRING + 1;
static final int APPLY = CALL + 1;
static ESId LENGTH = ESId.intern("length");
/**
* Create a new object based on a prototype
*/
private NativeFunction(String name, int n, int len)
{
super(name, len);
this.n = n;
}
/**
* Creates the native Function object
*
* XXX: incomplete
*/
static NativeWrapper create(Global resin)
{
ESBase []scope = new ESBase[] { resin.getGlobalProto() };
ESClosure funProto = new ESClosure(scope, 1);
funProto.name = ESId.intern("Function");
Native natFunction = new NativeFunction("Function", NEW, 1);
NativeWrapper function = new NativeWrapper(resin, natFunction,
funProto, ESThunk.FUN_THUNK);
resin.funProto = funProto;
put(funProto, "toString", TO_STRING, 0);
put(funProto, "call", CALL, 1);
put(funProto, "apply", APPLY, 2);
funProto.prototype = resin.objProto;
funProto.setClean();
function.setClean();
return function;
}
private static void put(ESObject proto, String name, int n, int len)
{
ESId id = ESId.intern(name);
proto.put(id, new NativeFunction(name, n, len), DONT_ENUM);
}
public ESBase call(Call eval, int length) throws Throwable
{
switch (n) {
case NEW:
return createAnonymous(eval, length);
// Object prototype stuff
case TO_STRING:
// XXX: Is this correct? Test.
if (eval.getThis() instanceof ESClosure) {
ESClosure closure = (ESClosure) eval.getThis();
return ESString.create(closure.decompile());
} else if (eval.getThis() instanceof NativeWrapper) {
NativeWrapper wrapper = (NativeWrapper) eval.getThis();
return wrapper.fun.toStr();
} else
throw new ESException("to string bound to function: " +
eval.getThis().getClass());
case CALL:
int oldTop = eval.top;
ESBase fun = eval.getArg(-1);
ESBase callThis = null;
try {
if (length > 0) {
callThis = eval.getArg(0);
} else
callThis = esNull;
if (callThis == esNull || callThis == esUndefined ||
callThis == esEmpty)
eval.setArg(0, eval.getGlobal());
else
eval.setArg(0, callThis.toObject());
eval.top++;
return fun.call(eval, length > 0 ? length - 1 : 0);
} finally {
eval.top = oldTop;
}
case APPLY:
return apply(eval, length);
default:
throw new ESException("Unknown object function");
}
}
/**
* Create a new parser instance.
*/
private ESClosure parseFunction(Call eval, int length) throws Throwable
{
StringBuffer sbuf = new StringBuffer();
sbuf.append("function anonymous(");
ArrayList argList = new ArrayList();
for (int i = 0; i < length - 1; i++) {
if (i != 0)
sbuf.append(",");
String str = eval.getArg(i).toString();
int j = 0;
int p = 0;
while ((p = str.indexOf(',', j)) >= 0 ||
(p = str.indexOf(' ', j)) >= 0) {
if (j < p)
argList.add(ESId.intern(str.substring(j, p)));
j = p + 1;
}
if (j < str.length())
argList.add(ESId.intern(str.substring(j)));
sbuf.append(str);
}
ESId []args = new ESId[argList.size()];
argList.toArray(args);
sbuf.append("){");
if (length > 0)
sbuf.append(eval.getArg(length - 1).toString());
sbuf.append("}\n");
sbuf.append("return anonymous();");
Global resin = Global.getGlobalProto();
Script script = null;
try {
Parser parser = new Parser();
ReadStream is = Vfs.openString(sbuf.toString());
script = parser.parse(is, "anonymous", 1);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
ESCallable jsClass = script.initClass(resin, eval.getGlobal());
// It's known that the function will be #2.
ESClosure fun = new ESClosure(ESId.intern("anonymous"), jsClass,
null, 2, args, eval.getGlobal());
return fun;
}
private ESBase createAnonymous(Call eval, int length) throws Throwable
{
return parseFunction(eval, length);
}
private ESBase apply(Call eval, int length) throws Throwable
{
Global resin = Global.getGlobalProto();
Call call = eval.getCall();
call.top = 1;
call.global = eval.global;
call.caller = eval;
ESBase fun = eval.getArg(-1);
ESBase callThis = null;
if (length > 0) {
callThis = eval.getArg(0);
} else
callThis = esNull;
if (callThis == esNull || callThis == esUndefined ||
callThis == esEmpty)
call.setArg(-1, eval.getGlobal());
else
call.setArg(-1, callThis.toObject());
int j = 0;
for (int i = 1; i < length; i++) {
ESBase arg = eval.getArg(i);
if (arg == esNull || arg == esUndefined || arg == esEmpty)
continue;
ESBase arglen = arg.hasProperty(LENGTH);
if (arglen == null)
call.setArg(j++, arg);
else {
int len = arglen.toInt32();
if (j + len > call.stack.length - 2)
throw new ESException("stack overflow");
for (int k = 0; k < len; k++)
call.setArg(j++, arg.getProperty(ESString.create(k)));
if (len < 0)
call.setArg(j++, arg);
}
}
ESBase value = fun.call(call, j);
resin.freeCall(call);
return value;
}
}