oolite/src/Core/Debug/OOJSConsole.m

488 lines
14 KiB
Objective-C

/*
OOJSConsole.m
Oolite Debug OXP
Copyright (C) 2007 Jens Ayton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
#ifndef OO_EXCLUDE_DEBUG_SUPPORT
#import "OOJSConsole.h"
#import "OODebugMonitor.h"
#import <stdint.h>
#import "OOJavaScriptEngine.h"
#import "OOJSScript.h"
#import "OOJSVector.h"
#import "OOJSEntity.h"
#import "OOJSCall.h"
#import "OOLoggingExtended.h"
@interface Entity (OODebugInspector)
// Method added by inspector in Debug OXP under OS X only.
- (void) inspect;
@end
static JSObject *sConsolePrototype = NULL;
static JSObject *sConsoleSettingsPrototype = NULL;
static JSBool ConsoleGetProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue);
static JSBool ConsoleSetProperty(JSContext *context, JSObject *this, jsval name, jsval *value);
static void ConsoleFinalize(JSContext *context, JSObject *this);
// Methods
static JSBool ConsoleConsoleMessage(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleClearConsole(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleScriptStack(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleInspectEntity(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleCallObjCMethod(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleIsExecutableJavaScript(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleSetDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult);
static JSBool ConsoleSettingsDeleteProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue);
static JSBool ConsoleSettingsGetProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue);
static JSBool ConsoleSettingsSetProperty(JSContext *context, JSObject *this, jsval name, jsval *value);
static JSClass sConsoleClass =
{
"Console",
JSCLASS_HAS_PRIVATE | JSCLASS_IS_ANONYMOUS,
JS_PropertyStub, // addProperty
JS_PropertyStub, // delProperty
ConsoleGetProperty, // getProperty
ConsoleSetProperty, // setProperty
JS_EnumerateStub, // enumerate
JS_ResolveStub, // resolve
JS_ConvertStub, // convert
ConsoleFinalize, // finalize
JSCLASS_NO_OPTIONAL_MEMBERS
};
enum
{
// Property IDs
kConsole_debugFlags // debug flags, integer, read/write
};
static JSPropertySpec sConsoleProperties[] =
{
// JS name ID flags
{ "debugFlags", kConsole_debugFlags, JSPROP_PERMANENT | JSPROP_ENUMERATE },
{ 0 }
};
static JSFunctionSpec sConsoleMethods[] =
{
// JS name Function min args
{ "consoleMessage", ConsoleConsoleMessage, 2 },
{ "clearConsole", ConsoleClearConsole, 0 },
{ "scriptStack", ConsoleScriptStack, 0 },
{ "inspectEntity", ConsoleInspectEntity, 1 },
{ "__callObjCMethod", ConsoleCallObjCMethod, 1 },
{ "isExecutableJavaScript", ConsoleIsExecutableJavaScript, 2 },
{ "displayMessagesInClass", ConsoleDisplayMessagesInClass, 1 },
{ "setDisplayMessagesInClass", ConsoleSetDisplayMessagesInClass, 2 },
{ 0 }
};
static JSClass sConsoleSettingsClass =
{
"ConsoleSettings",
JSCLASS_HAS_PRIVATE,
JS_PropertyStub, // addProperty
ConsoleSettingsDeleteProperty, // delProperty
ConsoleSettingsGetProperty, // getProperty
ConsoleSettingsSetProperty, // setProperty
JS_EnumerateStub, // enumerate. FIXME: this should work.
JS_ResolveStub, // resolve
JS_ConvertStub, // convert
ConsoleFinalize, // finalize (same as Console)
JSCLASS_NO_OPTIONAL_MEMBERS
};
static void InitOOJSConsole(JSContext *context, JSObject *global)
{
sConsolePrototype = JS_InitClass(context, global, NULL, &sConsoleClass, NULL, 0, sConsoleProperties, sConsoleMethods, NULL, NULL);
JSRegisterObjectConverter(&sConsoleClass, JSBasicPrivateObjectConverter);
sConsoleSettingsPrototype = JS_InitClass(context, global, NULL, &sConsoleSettingsClass, NULL, 0, NULL, NULL, NULL, NULL);
JSRegisterObjectConverter(&sConsoleSettingsClass, JSBasicPrivateObjectConverter);
}
JSObject *DebugMonitorToJSConsole(JSContext *context, OODebugMonitor *monitor)
{
OOJavaScriptEngine *engine = nil;
JSObject *object = NULL;
JSObject *settingsObject = NULL;
jsval value;
engine = [OOJavaScriptEngine sharedEngine];
if (sConsolePrototype == NULL)
{
InitOOJSConsole(context, [engine globalObject]);
}
// Create Console object
object = JS_NewObject(context, &sConsoleClass, sConsolePrototype, NULL);
if (object != NULL)
{
if (!JS_SetPrivate(context, object, [monitor weakRetain])) object = NULL;
}
if (object != NULL)
{
// Create ConsoleSettings object
settingsObject = JS_NewObject(context, &sConsoleSettingsClass, sConsoleSettingsPrototype, NULL);
if (settingsObject != NULL)
{
if (!JS_SetPrivate(context, settingsObject, [monitor weakRetain])) settingsObject = NULL;
}
if (settingsObject != NULL)
{
value = OBJECT_TO_JSVAL(settingsObject);
if (!JS_SetProperty(context, object, "settings", &value))
{
settingsObject = NULL;
}
}
if (settingsObject == NULL) object = NULL;
}
return object;
}
static JSBool ConsoleGetProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue)
{
if (!JSVAL_IS_INT(name)) return YES;
switch (JSVAL_TO_INT(name))
{
#ifndef NDEBUG
case kConsole_debugFlags:
*outValue = INT_TO_JSVAL(gDebugFlags);
break;
#endif
default:
OOReportJSBadPropertySelector(context, @"Console", JSVAL_TO_INT(name));
return NO;
}
return YES;
}
static JSBool ConsoleSetProperty(JSContext *context, JSObject *this, jsval name, jsval *value)
{
int32 iValue;
if (!JSVAL_IS_INT(name)) return YES;
switch (JSVAL_TO_INT(name))
{
#ifndef NDEBUG
case kConsole_debugFlags:
if (JS_ValueToInt32(context, *value, &iValue))
{
gDebugFlags = iValue;
}
break;
#endif
default:
OOReportJSBadPropertySelector(context, @"Console", JSVAL_TO_INT(name));
return NO;
}
return YES;
}
static void ConsoleFinalize(JSContext *context, JSObject *this)
{
[(id)JS_GetPrivate(context, this) release];
JS_SetPrivate(context, this, nil);
}
static JSBool ConsoleSettingsDeleteProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue)
{
NSString *key = nil;
id monitor = nil;
if (!JSVAL_IS_STRING(name)) return NO;
key = [NSString stringWithJavaScriptValue:name inContext:context];
monitor = JSObjectToObject(context, this);
if (![monitor isKindOfClass:[OODebugMonitor class]])
{
OOReportJSError(context, @"Expected OODebugMonitor, got %@ in %s. This is an internal error, please report it.", [monitor class], __PRETTY_FUNCTION__);
return NO;
}
[monitor setConfigurationValue:nil forKey:key];
*outValue = JSVAL_TRUE;
return YES;
}
static JSBool ConsoleSettingsGetProperty(JSContext *context, JSObject *this, jsval name, jsval *outValue)
{
NSString *key = nil;
id value = nil;
id monitor = nil;
if (!JSVAL_IS_STRING(name)) return YES;
key = [NSString stringWithJavaScriptValue:name inContext:context];
monitor = JSObjectToObject(context, this);
if (![monitor isKindOfClass:[OODebugMonitor class]])
{
OOReportJSError(context, @"Expected OODebugMonitor, got %@ in %s. This is an internal error, please report it.", [monitor class], __PRETTY_FUNCTION__);
return NO;
}
value = [monitor configurationValueForKey:key];
*outValue = [value javaScriptValueInContext:context];
return YES;
}
static JSBool ConsoleSettingsSetProperty(JSContext *context, JSObject *this, jsval name, jsval *inValue)
{
NSString *key = nil;
id value = nil;
id monitor = nil;
if (!JSVAL_IS_STRING(name)) return YES;
key = [NSString stringWithJavaScriptValue:name inContext:context];
monitor = JSObjectToObject(context, this);
if (![monitor isKindOfClass:[OODebugMonitor class]])
{
OOReportJSError(context, @"Expected OODebugMonitor, got %@ in %s. This is an internal error, please report it.", [monitor class], __PRETTY_FUNCTION__);
return NO;
}
if (JSVAL_IS_NULL(*inValue) || JSVAL_IS_VOID(*inValue))
{
[monitor setConfigurationValue:nil forKey:key];
}
else
{
value = JSValueToObject(context, *inValue);
if (value != nil)
{
[monitor setConfigurationValue:value forKey:key];
}
else
{
OOReportJSWarning(context, @"debugConsole.settings: could not convert %@ to native object.", [NSString stringWithJavaScriptValue:*inValue inContext:context]);
}
}
return YES;
}
// *** Methods ***
// function consoleMessage(message : String) : void
static JSBool ConsoleConsoleMessage(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
id monitor = nil;
NSString *colorKey = nil,
*message = nil;
NSRange emphasisRange = {0, 0};
jsdouble location, length;
monitor = JSObjectToObject(context, this);
if (![monitor isKindOfClass:[OODebugMonitor class]])
{
OOReportJSError(context, @"Expected OODebugMonitor, got %@ in %s. This is an internal error, please report it.", [monitor class], __PRETTY_FUNCTION__);
return NO;
}
colorKey = JSValToNSString(context,argv[0]);
message = JSValToNSString(context,argv[1]);
if (4 <= argc)
{
// Attempt to get two numbers, specifying an emphasis range.
if (JS_ValueToNumber(context, argv[2], &location) &&
JS_ValueToNumber(context, argv[3], &length))
{
emphasisRange = NSMakeRange(location, length);
}
}
if (message == nil)
{
if (colorKey == nil)
{
OOReportJSWarning(context, @"Console.consoleMessage() called with no parameters.");
}
else
{
message = colorKey;
colorKey = @"command-result";
}
}
if (message != nil)
{
[monitor appendJSConsoleLine:message
colorKey:colorKey
emphasisRange:emphasisRange];
}
return YES;
}
// function clearConsole() : void
static JSBool ConsoleClearConsole(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
id monitor = nil;
monitor = JSObjectToObject(context, this);
if (![monitor isKindOfClass:[OODebugMonitor class]])
{
OOReportJSError(context, @"Expected OODebugMonitor, got %@ in %s. This is an internal error, please report it.", [monitor class], __PRETTY_FUNCTION__);
return NO;
}
[monitor clearJSConsole];
return YES;
}
// function scriptStack() : Array
static JSBool ConsoleScriptStack(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
NSArray *result = nil;
result = [OOJSScript scriptStack];
*outResult = [result javaScriptValueInContext:context];
return YES;
}
// function inspectEntity(entity : Entity) : void
static JSBool ConsoleInspectEntity(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
Entity *entity = nil;
if (JSValueToEntity(context, argv[0], &entity))
{
if ([entity respondsToSelector:@selector(inspect)])
{
[entity inspect];
}
}
return YES;
}
// function __callObjCMethod(selector : String [, ...]) : Object
static JSBool ConsoleCallObjCMethod(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
id object = nil;
object = JSObjectToObject(context, this);
if (object == nil)
{
OOReportJSError(context, @"Attempt to call __callObjCMethod() for non-Objective-C object %@.", [NSString stringWithJavaScriptValue:OBJECT_TO_JSVAL(this) inContext:context]);
return NO;
}
return OOJSCallObjCObjectMethod(context, object, [object jsClassName], argc, argv, outResult);
}
// function isExecutableJavaScript(this : Object, string : String) : Boolean
static JSBool ConsoleIsExecutableJavaScript(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
JSObject *target = NULL;
JSString *string = NULL;
*outResult = JSVAL_FALSE;
if (argc < 2) return YES;
if (!JS_ValueToObject(context, argv[0], &target) || !JSVAL_IS_STRING(argv[1])) return YES; // Fail silently
string = JSVAL_TO_STRING(argv[1]);
*outResult = BOOLEAN_TO_JSVAL(JS_BufferIsCompilableUnit(context, target, JS_GetStringBytes(string), JS_GetStringLength(string)));
return YES;
}
// function displayMessagesInClass(class : String) : Boolean
static JSBool ConsoleDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
NSString *messageClass = nil;
messageClass = [NSString stringWithJavaScriptValue:argv[0] inContext:context];
*outResult = BOOLEAN_TO_JSVAL(OOLogWillDisplayMessagesInClass(messageClass));
return YES;
}
// function setDisplayMessagesInClass(class : String, flag : Boolean) : void
static JSBool ConsoleSetDisplayMessagesInClass(JSContext *context, JSObject *this, uintN argc, jsval *argv, jsval *outResult)
{
NSString *messageClass = nil;
JSBool flag;
messageClass = JSValToNSString(context, argv[0]);
if (messageClass != nil && JS_ValueToBoolean(context, argv[1], &flag))
{
OOLogSetDisplayMessagesInClass(messageClass, flag);
}
return YES;
}
#endif /* OO_EXCLUDE_DEBUG_SUPPORT */