using System;
using System.Collections.Generic;
using System.Linq;
using Jint.Native.Object;
using Jint.Native.String;
using Jint.Parser;
using Jint.Parser.Ast;
using Jint.Runtime;
using Jint.Runtime.Environments;
namespace Jint.Native.Function
{
public sealed class FunctionConstructor : FunctionInstance, IConstructor
{
private FunctionConstructor(Engine engine):base(engine, null, null, false)
{
}
public static FunctionConstructor CreateFunctionConstructor(Engine engine)
{
var obj = new FunctionConstructor(engine);
obj.Extensible = true;
// The initial value of Function.prototype is the standard built-in Function prototype object
obj.PrototypeObject = FunctionPrototype.CreatePrototypeObject(engine);
// The value of the [[Prototype]] internal property of the Function constructor is the standard built-in Function prototype object
obj.Prototype = obj.PrototypeObject;
obj.FastAddProperty("prototype", obj.PrototypeObject, false, false, false);
obj.FastAddProperty("length", 1, false, false, false);
return obj;
}
public void Configure()
{
}
public FunctionPrototype PrototypeObject { get; private set; }
public override JsValue Call(JsValue thisObject, JsValue[] arguments)
{
return Construct(arguments);
}
private string[] ParseArgumentNames(string parameterDeclaration)
{
string[] values = parameterDeclaration.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var newValues = new string[values.Length];
for (var i = 0; i < values.Length; i++)
{
newValues[i] = StringPrototype.TrimEx(values[i]);
}
return newValues;
}
public ObjectInstance Construct(JsValue[] arguments)
{
var argCount = arguments.Length;
string p = "";
string body = "";
if (argCount == 1)
{
body = TypeConverter.ToString(arguments[0]);
}
else if (argCount > 1)
{
var firstArg = arguments[0];
p = TypeConverter.ToString(firstArg);
for (var k = 1; k < argCount - 1; k++)
{
var nextArg = arguments[k];
p += "," + TypeConverter.ToString(nextArg);
}
body = TypeConverter.ToString(arguments[argCount-1]);
}
var parameters = this.ParseArgumentNames(p);
var parser = new JavaScriptParser();
FunctionExpression function;
try
{
var functionExpression = "function(" + p + ") { " + body + "}";
function = parser.ParseFunctionExpression(functionExpression);
}
catch (ParserException)
{
throw new JavaScriptException(Engine.SyntaxError);
}
// todo: check if there is not a way to use the FunctionExpression directly instead of creating a FunctionDeclaration
var functionObject = new ScriptFunctionInstance(
Engine,
new FunctionDeclaration
{
Type = SyntaxNodes.FunctionDeclaration,
Body = new BlockStatement
{
Type = SyntaxNodes.BlockStatement,
Body = new [] { function.Body }
},
Parameters = parameters.Select(x => new Identifier
{
Type = SyntaxNodes.Identifier,
Name = x
}).ToArray(),
FunctionDeclarations = function.FunctionDeclarations,
VariableDeclarations = function.VariableDeclarations
},
LexicalEnvironment.NewDeclarativeEnvironment(Engine, Engine.ExecutionContext.LexicalEnvironment),
function.Strict
) { Extensible = true };
return functionObject;
}
///
/// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
///
///
///
public FunctionInstance CreateFunctionObject(FunctionDeclaration functionDeclaration)
{
var functionObject = new ScriptFunctionInstance(
Engine,
functionDeclaration,
LexicalEnvironment.NewDeclarativeEnvironment(Engine, Engine.ExecutionContext.LexicalEnvironment),
functionDeclaration.Strict
) { Extensible = true };
return functionObject;
}
private FunctionInstance _throwTypeError;
public FunctionInstance ThrowTypeError
{
get
{
if (_throwTypeError != null)
{
return _throwTypeError;
}
_throwTypeError = new ThrowTypeError(Engine);
return _throwTypeError;
}
}
public object Apply(JsValue thisObject, JsValue[] arguments)
{
if (arguments.Length != 2)
{
throw new ArgumentException("Apply has to be called with two arguments.");
}
var func = thisObject.TryCast();
var thisArg = arguments[0];
var argArray = arguments[1];
if (func == null)
{
throw new JavaScriptException(Engine.TypeError);
}
if (argArray == Null.Instance || argArray == Undefined.Instance)
{
return func.Call(thisArg, Arguments.Empty);
}
var argArrayObj = argArray.TryCast();
if (argArrayObj == null)
{
throw new JavaScriptException(Engine.TypeError);
}
var len = argArrayObj.Get("length");
var n = TypeConverter.ToUint32(len);
var argList = new List();
for (var index = 0; index < n; index++)
{
var indexName = index.ToString();
var nextArg = argArrayObj.Get(indexName);
argList.Add(nextArg);
}
return func.Call(thisArg, argList.ToArray());
}
}
}