git-svn-id: http://svn.berlios.de/svnroot/repos/oolite-linux/trunk@1126 127b21dd-08f5-0310-b4b7-95ae10353056
1400 lines
37 KiB
Objective-C
1400 lines
37 KiB
Objective-C
/*
|
|
|
|
OOJavaScriptEngine.h
|
|
|
|
JavaScript support for Oolite
|
|
Copyright (C) 2007 David Taylor and Jens Ayton.
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
#import "OOJavaScriptEngine.h"
|
|
#import "OOJSScript.h"
|
|
|
|
#import "OOJSVector.h"
|
|
#import "OOJSQuaternion.h"
|
|
#import "OOJSEntity.h"
|
|
#import "OOJSShip.h"
|
|
#import "OOJSStation.h"
|
|
#import "OOJSPlayer.h"
|
|
#import "jsarray.h"
|
|
|
|
#import "OOCollectionExtractors.h"
|
|
#import "Universe.h"
|
|
#import "PlanetEntity.h"
|
|
#import "NSStringOOExtensions.h"
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
|
|
extern NSString * const kOOLogDebugMessage;
|
|
|
|
|
|
static OOJavaScriptEngine *sSharedEngine = nil;
|
|
static JSObject *xglob, *systemObj, *missionObj;
|
|
|
|
|
|
extern OOJSScript *currentOOJSScript;
|
|
|
|
|
|
// For _bool scripting methods which always return @"YES" or @"NO" and nothing else.
|
|
OOINLINE jsval BooleanStringToJSVal(NSString *string) INLINE_PURE_FUNC;
|
|
OOINLINE jsval BooleanStringToJSVal(NSString *string)
|
|
{
|
|
return BOOLEAN_TO_JSVAL([string isEqualToString:@"YES"]);
|
|
}
|
|
|
|
|
|
static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report);
|
|
|
|
|
|
//===========================================================================
|
|
// MissionVars class
|
|
//===========================================================================
|
|
|
|
static JSBool MissionVarsGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool MissionVarsSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass MissionVars_class =
|
|
{
|
|
"MissionVariables",
|
|
0,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
MissionVarsGetProperty,
|
|
MissionVarsSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
static JSBool MissionVarsGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
NSDictionary *mission_variables = [OPlayerForScripting() mission_variables];
|
|
|
|
if (JSVAL_IS_STRING(name))
|
|
{
|
|
NSString *key = [@"mission_" stringByAppendingString:[NSString stringWithJavaScriptValue:name inContext:context]];
|
|
NSString *value = [mission_variables objectForKey:key];
|
|
|
|
if (value == nil)
|
|
{
|
|
*vp = JSVAL_VOID;
|
|
}
|
|
else
|
|
{
|
|
/* The point of this code is to try and tell the JS interpreter to treat numeric strings
|
|
as numbers where possible so that standard arithmetic works as you'd expect rather than
|
|
1+1 == "11". So a JSVAL_DOUBLE is returned if possible, otherwise a JSVAL_STRING is returned.
|
|
*/
|
|
|
|
BOOL isNumber = NO;
|
|
double dVal;
|
|
|
|
dVal = [value doubleValue];
|
|
if (dVal != 0) isNumber = YES;
|
|
else
|
|
{
|
|
NSCharacterSet *notZeroSet = [[NSCharacterSet characterSetWithCharactersInString:@"-0. "] invertedSet];
|
|
if ([value rangeOfCharacterFromSet:notZeroSet].location == NSNotFound) isNumber = YES;
|
|
}
|
|
if (isNumber)
|
|
{
|
|
jsdouble ds = [value doubleValue];
|
|
JSBool ok = JS_NewDoubleValue(context, ds, vp);
|
|
if (!ok) *vp = JSVAL_VOID;
|
|
}
|
|
else *vp = [value javaScriptValueInContext:context];
|
|
}
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionVarsSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
NSDictionary *mission_variables = [OPlayerForScripting() mission_variables];
|
|
|
|
if (JSVAL_IS_STRING(name))
|
|
{
|
|
NSString *key = [@"mission_" stringByAppendingString:[NSString stringWithJavaScriptValue:name inContext:context]];
|
|
NSString *value = [NSString stringWithJavaScriptValue:*vp inContext:context];
|
|
[mission_variables setValue:value forKey:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
//===========================================================================
|
|
// Global object class
|
|
//===========================================================================
|
|
|
|
static JSBool GlobalGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass global_class =
|
|
{
|
|
"Oolite",
|
|
0,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum global_propertyIDs
|
|
{
|
|
GLOBAL_GALAXY_NUMBER,
|
|
GLOBAL_PLANET_NUMBER,
|
|
GLOBAL_MISSION_VARS,
|
|
GLOBAL_GUI_SCREEN
|
|
};
|
|
|
|
|
|
static JSPropertySpec Global_props[] =
|
|
{
|
|
// JS name ID flags
|
|
{ "galaxyNumber", GLOBAL_GALAXY_NUMBER, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ "planetNumber", GLOBAL_PLANET_NUMBER, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ "missionVariables", GLOBAL_MISSION_VARS, JSPROP_PERMANENT | JSPROP_ENUMERATE, GlobalGetProperty },
|
|
{ "guiScreen", GLOBAL_GUI_SCREEN, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY, GlobalGetProperty },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool GlobalLog(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool GlobalLogWithClass(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec Global_funcs[] =
|
|
{
|
|
{ "Log", GlobalLog, 1, 0 },
|
|
{ "LogWithClass", GlobalLogWithClass, 2, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool GlobalLog(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *logString = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@", " inContext:context];
|
|
OOLog(kOOLogDebugMessage, logString);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool GlobalLogWithClass(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *logString = [NSString concatenationOfStringsFromJavaScriptValues:argv + 1 count:argc - 1 separator:@", " inContext:context];
|
|
OOLog([NSString stringWithJavaScriptValue:argv[0] inContext:context], logString);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool GlobalGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
id result = nil;
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case GLOBAL_GALAXY_NUMBER:
|
|
result = [player galaxy_number];
|
|
break;
|
|
|
|
case GLOBAL_PLANET_NUMBER:
|
|
result = [player planet_number];
|
|
break;
|
|
|
|
case GLOBAL_GUI_SCREEN:
|
|
result = [player gui_screen_string];
|
|
break;
|
|
|
|
case GLOBAL_MISSION_VARS:
|
|
{
|
|
JSObject *mv = JS_DefineObject(context, xglob, "missionVariables", &MissionVars_class, 0x00, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
|
|
*vp = OBJECT_TO_JSVAL(mv);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
OOReportJavaScriptBadPropertySelector(context, @"Global", JSVAL_TO_INT(name));
|
|
return NO;
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:context];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Universe (solar system) proxy
|
|
//===========================================================================
|
|
|
|
static JSBool SystemGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool SystemSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
static JSClass System_class =
|
|
{
|
|
"Universe",
|
|
0,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
SystemGetProperty,
|
|
SystemSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum System_propertyIDs
|
|
{
|
|
SYS_ID,
|
|
SYS_NAME,
|
|
SYS_DESCRIPTION,
|
|
SYS_GOING_NOVA,
|
|
SYS_GONE_NOVA,
|
|
SYS_GOVT_STR,
|
|
SYS_GOVT_ID,
|
|
SYS_ECONOMY_STR,
|
|
SYS_ECONOMY_ID,
|
|
SYS_TECH_LVL,
|
|
SYS_POPULATION,
|
|
SYS_PRODUCTIVITY,
|
|
SYS_INHABITANTS
|
|
};
|
|
|
|
|
|
static JSPropertySpec System_props[] =
|
|
{
|
|
// JS name ID flags
|
|
{ "ID", SYS_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "name", SYS_NAME, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "description", SYS_DESCRIPTION, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "inhabitantsDescription", SYS_INHABITANTS, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "goingNova", SYS_GOING_NOVA, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "goneNova", SYS_GONE_NOVA, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "government", SYS_GOVT_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "governmentDescription", SYS_GOVT_STR, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "economy", SYS_ECONOMY_ID, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "economyDescription", SYS_ECONOMY_STR, JSPROP_PERMANENT | JSPROP_ENUMERATE | JSPROP_READONLY },
|
|
{ "techLevel", SYS_TECH_LVL, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "population", SYS_POPULATION, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ "productivity", SYS_PRODUCTIVITY, JSPROP_PERMANENT | JSPROP_ENUMERATE },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool SystemAddPlanet(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddMoon(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSendAllShipsAway(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSetSunNova(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemCountShipsWithRole(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShips(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddSystemShips(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsAt(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsAtPrecisely(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemAddShipsWithinRadius(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSpawn(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool SystemSpawnShip(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec System_funcs[] =
|
|
{
|
|
// JS name Function min args
|
|
{ "addPlanet", SystemAddPlanet, 1 },
|
|
{ "addMoon", SystemAddMoon, 1 },
|
|
{ "sendAllShipsAway", SystemSendAllShipsAway, 1 },
|
|
{ "setSunNova", SystemSetSunNova, 1 },
|
|
{ "countShipsWithRole", SystemCountShipsWithRole, 1, 0 },
|
|
{ "legacy_addShips", SystemAddShips, 2, 0 },
|
|
{ "legacy_addSystemShips", SystemAddSystemShips, 3, 0 },
|
|
{ "legacy_addShipsAt", SystemAddShipsAt, 6, 0 },
|
|
{ "legacy_addShipsAtPrecisely", SystemAddShipsAtPrecisely, 6, 0 },
|
|
{ "legacy_addShipsWithinRadius", SystemAddShipsWithinRadius, 7, 0 },
|
|
{ "legacy_spawn", SystemSpawn, 2, 0 },
|
|
{ "legacy_spawnShip", SystemSpawnShip, 1, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool SystemAddPlanet(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0]))
|
|
{
|
|
NSString *key = JSValToNSString(context, argv[0]);
|
|
[player addPlanet:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddMoon(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0]))
|
|
{
|
|
NSString *key = JSValToNSString(context, argv[0]);
|
|
[player addMoon:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSendAllShipsAway(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
[player sendAllShipsAway];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSetSunNova(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
if (argc > 0)
|
|
{
|
|
NSString *key = JSValToNSString(context, argv[0]);
|
|
[player setSunNovaIn:key];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static Random_Seed currentSystem;
|
|
static NSDictionary *planetinfo = nil;
|
|
|
|
static JSBool SystemGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
id result = nil;
|
|
|
|
if (!equal_seeds(currentSystem, player->system_seed))
|
|
{
|
|
currentSystem = player->system_seed;
|
|
|
|
[planetinfo release];
|
|
planetinfo = [[UNIVERSE generateSystemData:currentSystem] retain];
|
|
}
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case SYS_ID:
|
|
result = [player planet_number];
|
|
break;
|
|
|
|
case SYS_NAME:
|
|
if ([UNIVERSE sun] != nil)
|
|
{
|
|
result = [planetinfo objectForKey:KEY_NAME];
|
|
if (result == nil) result = @"None"; // TODO: should this return JSVAL_VOID instead? Other cases below. -- ahruman
|
|
}
|
|
else
|
|
{
|
|
// Witchspace. (Hmm, does a system that's gone nova have a sun? If not, -[PlayerEntity planet_number] is broken, too.
|
|
result = @"Interstellar space";
|
|
}
|
|
break;
|
|
|
|
case SYS_DESCRIPTION:
|
|
result = [planetinfo objectForKey:KEY_DESCRIPTION];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
|
|
case SYS_INHABITANTS:
|
|
result = [planetinfo objectForKey:KEY_INHABITANTS];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
|
|
case SYS_GOING_NOVA:
|
|
*vp = BooleanStringToJSVal([player sunWillGoNova_bool]);
|
|
break;
|
|
|
|
case SYS_GONE_NOVA:
|
|
*vp = BooleanStringToJSVal([player sunGoneNova_bool]);
|
|
break;
|
|
|
|
case SYS_GOVT_ID:
|
|
result = [player systemGovernment_number];
|
|
break;
|
|
|
|
case SYS_GOVT_STR:
|
|
result = [player systemGovernment_string];
|
|
break;
|
|
|
|
case SYS_ECONOMY_ID:
|
|
result = [player systemEconomy_number];
|
|
break;
|
|
|
|
case SYS_ECONOMY_STR:
|
|
result = [player systemEconomy_string];
|
|
break;
|
|
|
|
case SYS_TECH_LVL:
|
|
result = [player systemTechLevel_number];
|
|
break;
|
|
|
|
case SYS_POPULATION:
|
|
result = [player systemPopulation_number];
|
|
break;
|
|
|
|
case SYS_PRODUCTIVITY:
|
|
result = [player systemProductivity_number];
|
|
break;
|
|
|
|
default:
|
|
OOReportJavaScriptBadPropertySelector(context, @"System", JSVAL_TO_INT(name));
|
|
return NO;
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:context];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
if (!equal_seeds(currentSystem, player->system_seed))
|
|
{
|
|
currentSystem = player->system_seed;
|
|
if (planetinfo) [planetinfo release];
|
|
|
|
planetinfo = [[UNIVERSE generateSystemData:currentSystem] retain];
|
|
}
|
|
int gn = [[player galaxy_number] intValue];
|
|
int pn = [[player planet_number] intValue];
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case SYS_NAME:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_NAME value:JSValToNSString(context, *vp)];
|
|
break;
|
|
|
|
case SYS_DESCRIPTION:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_DESCRIPTION value:JSValToNSString(context, *vp)];
|
|
break;
|
|
|
|
case SYS_INHABITANTS:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_INHABITANTS value:JSValToNSString(context, *vp)];
|
|
break;
|
|
|
|
case SYS_GOING_NOVA:
|
|
*vp = BOOLToJSVal([[UNIVERSE sun] willGoNova]);
|
|
break;
|
|
|
|
case SYS_GONE_NOVA:
|
|
*vp = BOOLToJSVal([[UNIVERSE sun] goneNova]);
|
|
break;
|
|
|
|
case SYS_GOVT_ID:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_GOVERNMENT value:[NSNumber numberWithInt:[JSValToNSString(context, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_ECONOMY_ID:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_ECONOMY value:[NSNumber numberWithInt:[JSValToNSString(context, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_TECH_LVL:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_TECHLEVEL value:[NSNumber numberWithInt:[JSValToNSString(context, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_POPULATION:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_POPULATION value:[NSNumber numberWithInt:[JSValToNSString(context, *vp) intValue]]];
|
|
break;
|
|
|
|
case SYS_PRODUCTIVITY:
|
|
[UNIVERSE setSystemDataForGalaxy:gn planet:pn key:KEY_PRODUCTIVITY value:[NSNumber numberWithInt:[JSValToNSString(context, *vp) intValue]]];
|
|
break;
|
|
|
|
default:
|
|
OOReportJavaScriptBadPropertySelector(context, @"System", JSVAL_TO_INT(name));
|
|
return NO;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemCountShipsWithRole(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *role = nil;
|
|
int count;
|
|
|
|
if (argc == 1)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = [UNIVERSE countShipsWithRole:role];
|
|
*rval = INT_TO_JSVAL(count);
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShips(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
NSString *role = nil;
|
|
int count;
|
|
|
|
if (argc == 2)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
|
|
while (count--) [UNIVERSE witchspaceShipWithRole:role];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddSystemShips(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
jsdouble position;
|
|
NSString *role = nil;
|
|
int count;
|
|
|
|
if (argc == 3)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
|
|
JS_ValueToNumber(context, argv[2], &position);
|
|
while (count--) [UNIVERSE addShipWithRole:role nearRouteOneAt:position];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsAt(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
jsdouble x, y, z;
|
|
NSString *role = nil;
|
|
int count;
|
|
NSString *coordScheme = nil;
|
|
NSString *arg = nil;
|
|
|
|
if (argc == 6)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
coordScheme = JSValToNSString(context, argv[2]);
|
|
|
|
JS_ValueToNumber(context, argv[3], &x);
|
|
JS_ValueToNumber(context, argv[4], &y);
|
|
JS_ValueToNumber(context, argv[5], &z);
|
|
|
|
arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, count, coordScheme, x, y, z];
|
|
[player addShipsAt:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsAtPrecisely(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
jsdouble x, y, z;
|
|
NSString *role = nil;
|
|
int count;
|
|
NSString *coordScheme = nil;
|
|
NSString *arg = nil;
|
|
|
|
if (argc == 6)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
coordScheme = JSValToNSString(context, argv[2]);
|
|
|
|
JS_ValueToNumber(context, argv[3], &x);
|
|
JS_ValueToNumber(context, argv[4], &y);
|
|
JS_ValueToNumber(context, argv[5], &z);
|
|
|
|
arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f", role, count, coordScheme, x, y, z];
|
|
[player addShipsAtPrecisely:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemAddShipsWithinRadius(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
jsdouble x, y, z, radius;
|
|
NSString *role = nil;
|
|
int count;
|
|
NSString *coordScheme = nil;
|
|
NSString *arg = nil;
|
|
|
|
if (argc == 7)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
coordScheme = JSValToNSString(context, argv[2]);
|
|
|
|
JS_ValueToNumber(context, argv[3], &x);
|
|
JS_ValueToNumber(context, argv[4], &y);
|
|
JS_ValueToNumber(context, argv[5], &z);
|
|
JS_ValueToNumber(context, argv[6], &radius);
|
|
|
|
arg = [NSString stringWithFormat:@"%@ %d %@ %f %f %f %d", role, count, coordScheme, x, y, z, radius];
|
|
[player addShipsWithinRadius:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSpawn(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
NSString *role = nil;
|
|
int count;
|
|
NSString *arg = nil;
|
|
|
|
if (argc == 2)
|
|
{
|
|
role = JSValToNSString(context, argv[0]);
|
|
count = JSVAL_TO_INT(argv[1]);
|
|
|
|
arg = [NSString stringWithFormat:@"%@ %d", role, count];
|
|
[player spawn:arg];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool SystemSpawnShip(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
if (argc == 1)
|
|
{
|
|
[player spawnShip:JSValToNSString(context, argv[0])];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// Mission class
|
|
//===========================================================================
|
|
|
|
static JSBool MissionGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
static JSBool MissionSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp);
|
|
|
|
|
|
static JSClass Mission_class =
|
|
{
|
|
"Mission",
|
|
0,
|
|
|
|
JS_PropertyStub,
|
|
JS_PropertyStub,
|
|
MissionGetProperty,
|
|
MissionSetProperty,
|
|
JS_EnumerateStub,
|
|
JS_ResolveStub,
|
|
JS_ConvertStub,
|
|
JS_FinalizeStub
|
|
};
|
|
|
|
|
|
enum Mission_propertyIDs
|
|
{
|
|
MISSION_TEXT, MISSION_MUSIC, MISSION_IMAGE, MISSION_CHOICES, MISSION_CHOICE, MISSION_INSTRUCTIONS
|
|
};
|
|
|
|
static JSPropertySpec Mission_props[] =
|
|
{
|
|
{ "missionScreenTextKey", MISSION_TEXT, JSPROP_ENUMERATE },
|
|
{ "musicFileName", MISSION_MUSIC, JSPROP_ENUMERATE },
|
|
{ "imageFileName", MISSION_IMAGE, JSPROP_ENUMERATE },
|
|
{ "choicesKey", MISSION_CHOICES, JSPROP_ENUMERATE },
|
|
{ "choice", MISSION_CHOICE, JSPROP_ENUMERATE },
|
|
{ "instructionsKey", MISSION_INSTRUCTIONS, JSPROP_ENUMERATE },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool MissionShowMissionScreen(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionShowShipModel(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionResetMissionChoice(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionMarkSystem(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
static JSBool MissionUnmarkSystem(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval);
|
|
|
|
|
|
static JSFunctionSpec Mission_funcs[] =
|
|
{
|
|
{ "showMissionScreen", MissionShowMissionScreen, 0, 0 },
|
|
{ "showShipModel", MissionShowShipModel, 1, 0 },
|
|
{ "resetMissionChoice", MissionResetMissionChoice, 0, 0 },
|
|
{ "markSystem", MissionMarkSystem, 1, 0 },
|
|
{ "unmarkSystem", MissionUnmarkSystem, 1, 0 },
|
|
{ 0 }
|
|
};
|
|
|
|
|
|
static JSBool MissionGetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
id result = nil;
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case MISSION_CHOICE:
|
|
result = [player missionChoice_string];
|
|
if (result == nil) result = @"None";
|
|
break;
|
|
|
|
default:
|
|
OOReportJavaScriptBadPropertySelector(context, @"Mission", JSVAL_TO_INT(name));
|
|
return NO;
|
|
}
|
|
|
|
if (result != nil) *vp = [result javaScriptValueInContext:context];
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionSetProperty(JSContext *context, JSObject *obj, jsval name, jsval *vp)
|
|
{
|
|
if (!JSVAL_IS_INT(name)) return JS_TRUE;
|
|
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
switch (JSVAL_TO_INT(name))
|
|
{
|
|
case MISSION_TEXT:
|
|
if (JSVAL_IS_STRING(*vp))
|
|
{
|
|
JSString *jskey = JS_ValueToString(context, *vp);
|
|
[player addMissionText: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
|
|
case MISSION_MUSIC:
|
|
if (JSVAL_IS_STRING(*vp))
|
|
{
|
|
JSString *jskey = JS_ValueToString(context, *vp);
|
|
[player setMissionMusic: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
|
|
case MISSION_IMAGE:
|
|
if (JSVAL_IS_STRING(*vp))
|
|
{
|
|
NSString *str = JSValToNSString(context, *vp);
|
|
if ([str length] == 0)
|
|
str = @"none";
|
|
[player setMissionImage:str];
|
|
}
|
|
break;
|
|
|
|
case MISSION_CHOICES:
|
|
if (JSVAL_IS_STRING(*vp))
|
|
{
|
|
JSString *jskey = JS_ValueToString(context, *vp);
|
|
[player setMissionChoices: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
break;
|
|
|
|
case MISSION_INSTRUCTIONS:
|
|
|
|
if (JSVAL_IS_STRING(*vp))
|
|
{
|
|
JSString *jskey = JS_ValueToString(context, *vp);
|
|
NSString *ins = [NSString stringWithCString:JS_GetStringBytes(jskey)];
|
|
if ([ins length])
|
|
[player setMissionDescription:ins forMission:[currentOOJSScript name]];
|
|
else
|
|
[player clearMissionDescriptionForMission:[currentOOJSScript name]];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
OOReportJavaScriptBadPropertySelector(context, @"Mission", JSVAL_TO_INT(name));
|
|
return NO;
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionShowMissionScreen(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
[player setGuiToMissionScreen];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionShowShipModel(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
JSString *jskey = NULL;
|
|
|
|
if (argc > 0 && JSVAL_IS_STRING(argv[0]))
|
|
{
|
|
jskey = JS_ValueToString(context, argv[0]);
|
|
[player showShipModel: [NSString stringWithCString:JS_GetStringBytes(jskey)]];
|
|
}
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionResetMissionChoice(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
|
|
[player resetMissionChoice];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionMarkSystem(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
NSString *params = nil;
|
|
|
|
params = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@" " inContext:context];
|
|
[player addMissionDestination:params];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static JSBool MissionUnmarkSystem(JSContext *context, JSObject *obj, uintN argc, jsval *argv, jsval *rval)
|
|
{
|
|
PlayerEntity *player = OPlayerForScripting();
|
|
NSString *params = nil;
|
|
|
|
player = [NSString concatenationOfStringsFromJavaScriptValues:argv count:argc separator:@" " inContext:context];
|
|
[player removeMissionDestination:params];
|
|
|
|
return JS_TRUE;
|
|
}
|
|
|
|
|
|
static void ReportJSError(JSContext *context, const char *message, JSErrorReport *report)
|
|
{
|
|
NSString *severity = nil;
|
|
NSString *messageText = nil;
|
|
NSString *lineBuf = nil;
|
|
NSString *messageClass = nil;
|
|
NSString *highlight = @"*****";
|
|
|
|
// Type of problem: error, warning or exception? (Strict flag wilfully ignored.)
|
|
if (report->flags & JSREPORT_EXCEPTION) severity = @"exception";
|
|
else if (report->flags & JSREPORT_WARNING)
|
|
{
|
|
severity = @"warning";
|
|
highlight = @"-----";
|
|
}
|
|
else severity = @"error";
|
|
|
|
// The error message itself
|
|
messageText = [NSString stringWithUTF8String:message];
|
|
|
|
// Get offending line, if present, and trim trailing line breaks
|
|
lineBuf = [NSString stringWithUTF16String:report->uclinebuf];
|
|
while ([lineBuf hasSuffix:@"\n"] || [lineBuf hasSuffix:@"\r"]) lineBuf = [lineBuf substringToIndex:[lineBuf length] - 1];
|
|
|
|
// Log message class
|
|
messageClass = [NSString stringWithFormat:@"script.javaScript.%@.%u", severity, report->errorNumber];
|
|
|
|
// First line: problem description
|
|
OOLog(messageClass, @"%@ JavaScript %@: %@", highlight, severity, messageText);
|
|
|
|
// Second line: where error occured, and line if provided. (The line is only provided for compile-time errors, not run-time errors.)
|
|
if ([lineBuf length] != 0)
|
|
{
|
|
OOLog(messageClass, @" %s, line %d: %@", report->filename, report->lineno, lineBuf);
|
|
}
|
|
else
|
|
{
|
|
OOLog(messageClass, @" %s, line %d.", report->filename, report->lineno);
|
|
}
|
|
}
|
|
|
|
|
|
//===========================================================================
|
|
// JavaScript engine initialisation and shutdown
|
|
//===========================================================================
|
|
|
|
@implementation OOJavaScriptEngine
|
|
|
|
+ (OOJavaScriptEngine *)sharedEngine
|
|
{
|
|
if (sSharedEngine == nil) [[self alloc] init];
|
|
|
|
return sSharedEngine;
|
|
}
|
|
|
|
|
|
- (id) init
|
|
{
|
|
assert(sSharedEngine == nil);
|
|
|
|
self = [super init];
|
|
|
|
assert(sizeof(jschar) == sizeof(unichar));
|
|
|
|
/*set up global JS variables, including global and custom objects */
|
|
|
|
/* initialize the JS run time, and return result in runtime */
|
|
runtime = JS_NewRuntime(8L * 1024L * 1024L);
|
|
|
|
/* if runtime does not have a value, end the program here */
|
|
if (!runtime)
|
|
{
|
|
OOLog(@"script.javaScript.init.error", @"FATAL ERROR: failed to create JavaScript %@.", @"runtime");
|
|
exit(1);
|
|
}
|
|
|
|
/* create a context and associate it with the JS run time */
|
|
context = JS_NewContext(runtime, 8192);
|
|
JS_SetOptions(context, JSOPTION_VAROBJFIX | JSOPTION_STRICT | JSOPTION_NATIVE_BRANCH_CALLBACK);
|
|
|
|
/* if context does not have a value, end the program here */
|
|
if (!context)
|
|
{
|
|
OOLog(@"script.javaScript.init.error", @"FATAL ERROR: failed to create JavaScript %@.", @"context");
|
|
exit(1);
|
|
}
|
|
|
|
JS_SetErrorReporter(context, ReportJSError);
|
|
|
|
/* create the global object here */
|
|
globalObject = JS_NewObject(context, &global_class, NULL, NULL);
|
|
xglob = globalObject;
|
|
|
|
/* initialize the built-in JS objects and the global object */
|
|
JS_InitStandardClasses(context, globalObject);
|
|
JS_DefineProperties(context, globalObject, Global_props);
|
|
JS_DefineFunctions(context, globalObject, Global_funcs);
|
|
|
|
systemObj = JS_DefineObject(context, globalObject, "system", &System_class, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
|
|
JS_DefineProperties(context, systemObj, System_props);
|
|
JS_DefineFunctions(context, systemObj, System_funcs);
|
|
|
|
missionObj = JS_DefineObject(context, globalObject, "mission", &Mission_class, NULL, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
|
|
JS_DefineProperties(context, missionObj, Mission_props);
|
|
JS_DefineFunctions(context, missionObj, Mission_funcs);
|
|
|
|
InitOOJSVector(context, globalObject);
|
|
InitOOJSQuaternion(context, globalObject);
|
|
InitOOJSEntity(context, globalObject);
|
|
InitOOJSShip(context, globalObject);
|
|
InitOOJSStation(context, globalObject);
|
|
InitOOJSPlayer(context, globalObject);
|
|
|
|
OOLog(@"script.javaScript.init.success", @"Set up JavaScript context.");
|
|
|
|
sSharedEngine = self;
|
|
return self;
|
|
}
|
|
|
|
|
|
- (void) dealloc
|
|
{
|
|
sSharedEngine = nil;
|
|
|
|
JS_DestroyContext(context);
|
|
JS_DestroyRuntime(runtime);
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
|
|
- (JSContext *) context
|
|
{
|
|
return context;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
void OOReportJavaScriptError(JSContext *context, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
OOReportJavaScriptErrorWithArguments(context, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void OOReportJavaScriptErrorWithArguments(JSContext *context, NSString *format, va_list args)
|
|
{
|
|
NSString *msg = nil;
|
|
|
|
msg = [[NSString alloc] initWithFormat:format arguments:args];
|
|
JS_ReportError(context, "%s", [msg UTF8String]);
|
|
[msg release];
|
|
}
|
|
|
|
|
|
void OOReportJavaScriptWarning(JSContext *context, NSString *format, ...)
|
|
{
|
|
va_list args;
|
|
|
|
va_start(args, format);
|
|
OOReportJavaScriptWarningWithArguments(context, format, args);
|
|
va_end(args);
|
|
}
|
|
|
|
|
|
void OOReportJavaScriptWarningWithArguments(JSContext *context, NSString *format, va_list args)
|
|
{
|
|
NSString *msg = nil;
|
|
|
|
msg = [[NSString alloc] initWithFormat:format arguments:args];
|
|
JS_ReportWarning(context, "%s", [msg UTF8String]);
|
|
[msg release];
|
|
}
|
|
|
|
|
|
void OOReportJavaScriptBadPropertySelector(JSContext *context, NSString *className, jsint selector)
|
|
{
|
|
OOReportJavaScriptError(context, @"Internal error: bad property identifier %i in property accessor for class %@.", selector, className);
|
|
}
|
|
|
|
|
|
BOOL NumberFromArgumentList(JSContext *context, NSString *scriptClass, NSString *function, uintN argc, jsval *argv, double *outNumber, uintN *outConsumed)
|
|
{
|
|
double value;
|
|
|
|
// Sanity checks.
|
|
if (outConsumed != NULL) *outConsumed = 0;
|
|
if (EXPECT_NOT(argc == 0 || argv == NULL || outNumber == NULL))
|
|
{
|
|
OOLogGenericParameterError();
|
|
return NO;
|
|
}
|
|
|
|
// Get value, if possible.
|
|
if (EXPECT_NOT(!JS_ValueToNumber(context, argv[0], &value) || isnan(value)))
|
|
{
|
|
// Failed; report bad parameters, if given a class and function.
|
|
if (scriptClass != nil && function != nil)
|
|
{
|
|
OOReportJavaScriptWarning(context, @"%@.%@(): expected number, got %@.", scriptClass, function, [NSString stringWithJavaScriptParameters:argv count:1 inContext:context]);
|
|
return NO;
|
|
}
|
|
}
|
|
|
|
// Success.
|
|
*outNumber = value;
|
|
if (outConsumed != NULL) *outConsumed = 1;
|
|
return YES;
|
|
}
|
|
|
|
|
|
BOOL JSArgumentsFromArray(JSContext *context, NSArray *array, uintN *outArgc, jsval **outArgv)
|
|
{
|
|
if (outArgc != NULL) *outArgc = 0;
|
|
if (outArgv != NULL) *outArgv = NULL;
|
|
|
|
if (array == nil) return YES;
|
|
|
|
// Sanity checks.
|
|
if (outArgc == NULL || outArgv == NULL)
|
|
{
|
|
OOLogGenericParameterError();
|
|
return NO;
|
|
}
|
|
if (context == NULL) context = [[OOJavaScriptEngine sharedEngine] context];
|
|
|
|
uintN i = 0, argc = [array count];
|
|
NSEnumerator *objectEnum = nil;
|
|
id object = nil;
|
|
jsval *argv = NULL;
|
|
|
|
if (argc == 0) return YES;
|
|
|
|
// Allocate result buffer
|
|
argv = malloc(sizeof *argv * argc);
|
|
if (argv == NULL)
|
|
{
|
|
OOLog(kOOLogAllocationFailure, @"Failed to allocate space for %u JavaScript parameters.", argc);
|
|
return NO;
|
|
}
|
|
|
|
// Convert objects
|
|
JSContext * volatile vCtxt = context;
|
|
for (objectEnum = [array objectEnumerator]; (object = [objectEnum nextObject]); )
|
|
{
|
|
argv[i] = JSVAL_VOID;
|
|
|
|
NS_DURING
|
|
if ([object respondsToSelector:@selector(javaScriptValueInContext:)])
|
|
{
|
|
argv[i] = [object javaScriptValueInContext:vCtxt];
|
|
}
|
|
NS_HANDLER
|
|
NS_ENDHANDLER
|
|
++i;
|
|
}
|
|
|
|
*outArgc = argc;
|
|
*outArgv = argv;
|
|
return YES;
|
|
}
|
|
|
|
|
|
JSObject *JSArrayFromNSArray(JSContext *context, NSArray *array)
|
|
{
|
|
uintN count;
|
|
jsval *values;
|
|
JSObject *result = NULL;
|
|
|
|
if (JSArgumentsFromArray(context, array, &count, &values))
|
|
{
|
|
result = JS_NewArrayObject(context, count, values);
|
|
}
|
|
if (values != NULL) free(values);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
@implementation NSObject (OOJavaScriptConversion)
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
return JSVAL_NULL;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSString (OOJavaScriptExtensions)
|
|
|
|
// Convert a JSString to an NSString.
|
|
+ (id)stringWithJavaScriptString:(JSString *)string
|
|
{
|
|
jschar *chars = NULL;
|
|
size_t length;
|
|
|
|
chars = JS_GetStringChars(string);
|
|
length = JS_GetStringLength(string);
|
|
|
|
return [NSString stringWithCharacters:chars length:length];
|
|
}
|
|
|
|
|
|
+ (id)stringWithJavaScriptValue:(jsval)value inContext:(JSContext *)context
|
|
{
|
|
JSString *string = NULL;
|
|
|
|
string = JS_ValueToString(context, value); // Calls the value's convert method if needed.
|
|
return [NSString stringWithJavaScriptString:string];
|
|
}
|
|
|
|
|
|
+ (id)stringWithJavaScriptParameters:(jsval *)params count:(uintN)count inContext:(JSContext *)context
|
|
{
|
|
if (params == nil && count != 0) return nil;
|
|
|
|
uintN i;
|
|
jsval val;
|
|
NSMutableString *result = [NSMutableString string];
|
|
NSString *valString = nil;
|
|
|
|
for (i = 0; i != count; ++i)
|
|
{
|
|
if (i != 0) [result appendString:@", "];
|
|
else [result appendString:@"("];
|
|
|
|
val = params[i];
|
|
valString = [self stringWithJavaScriptValue:val inContext:context];
|
|
if (JSVAL_IS_STRING(val))
|
|
{
|
|
[result appendFormat:@"\"%@\"", valString];
|
|
}
|
|
else
|
|
{
|
|
[result appendString:valString];
|
|
}
|
|
}
|
|
|
|
[result appendString:@")"];
|
|
return result;
|
|
}
|
|
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
size_t length;
|
|
unichar *buffer = NULL;
|
|
JSString *string = NULL;
|
|
|
|
length = [self length];
|
|
buffer = malloc(length * sizeof *buffer);
|
|
if (buffer == NULL) return JSVAL_VOID;
|
|
|
|
[self getCharacters:buffer];
|
|
|
|
string = JS_NewUCStringCopyN(context, buffer, length);
|
|
free(buffer);
|
|
|
|
return STRING_TO_JSVAL(string);
|
|
}
|
|
|
|
|
|
+ (id)concatenationOfStringsFromJavaScriptValues:(jsval *)values count:(size_t)count separator:(NSString *)separator inContext:(JSContext *)context
|
|
{
|
|
size_t i;
|
|
NSMutableString *result = nil;
|
|
NSString *element = nil;
|
|
|
|
if (count < 1) return nil;
|
|
if (values == NULL) return NULL;
|
|
|
|
for (i = 0; i != count; ++i)
|
|
{
|
|
element = [NSString stringWithJavaScriptValue:values[i] inContext:context];
|
|
if (result == nil) result = [element mutableCopy];
|
|
else
|
|
{
|
|
if (separator != nil) [result appendString:separator];
|
|
[result appendString:element];
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSArray (OOJavaScriptConversion)
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
return OBJECT_TO_JSVAL(JSArrayFromNSArray(context, self));
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSNumber (OOJavaScriptConversion)
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
jsval result;
|
|
BOOL isFloat = NO;
|
|
const char *type;
|
|
long long longLongValue;
|
|
|
|
if (self == [NSNumber numberWithBool:YES])
|
|
{
|
|
/* Under OS X, at least, numberWithBool: returns one of two singletons.
|
|
There is no other way to reliably identify a boolean NSNumber.
|
|
Fun, eh? */
|
|
result = JSVAL_TRUE;
|
|
}
|
|
else if (self == [NSNumber numberWithBool:NO])
|
|
{
|
|
result = JSVAL_FALSE;
|
|
}
|
|
else
|
|
{
|
|
longLongValue = [self longLongValue];
|
|
if (longLongValue < (long long)JSVAL_INT_MIN || (long long)JSVAL_INT_MAX < longLongValue)
|
|
{
|
|
// values outside JSVAL_INT range are returned as doubles.
|
|
isFloat = YES;
|
|
}
|
|
else
|
|
{
|
|
// Check value type.
|
|
type = [self objCType];
|
|
if (type[0] == 'f' || type[0] == 'd') isFloat = YES;
|
|
}
|
|
|
|
if (isFloat)
|
|
{
|
|
if (!JS_NewDoubleValue(context, [self doubleValue], &result)) result = JSVAL_VOID;
|
|
}
|
|
else
|
|
{
|
|
result = INT_TO_JSVAL(longLongValue);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
@implementation NSNull (OOJavaScriptConversion)
|
|
|
|
- (jsval)javaScriptValueInContext:(JSContext *)context
|
|
{
|
|
return JSVAL_NULL;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
NSString *JSPropertyAsString(JSContext *context, JSObject *object, const char *name)
|
|
{
|
|
JSBool OK;
|
|
jsval returnValue;
|
|
NSString *result = nil;
|
|
|
|
if (context == NULL || object == NULL || name == NULL) return nil;
|
|
|
|
OK = JS_GetProperty(context, object, name, &returnValue);
|
|
if (OK && !JSVAL_IS_VOID(returnValue))
|
|
{
|
|
result = [NSString stringWithJavaScriptValue:returnValue inContext:context];
|
|
}
|
|
|
|
return result;
|
|
}
|