From 0468da3a8022940536ba5af6f6b54c063c081260 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 16:04:30 -0500 Subject: [PATCH 01/20] Supress warnings from scripting implementation for methods that need to be overriden or implemented by apps. --- Headers/Foundation/Foundation.h | 2 +- Headers/Foundation/NSScriptClassDescription.h | 28 + Headers/Foundation/NSScriptCoercionHandler.h | 12 + Headers/Foundation/NSScriptCommand.h | 45 +- .../Foundation/NSScriptCommandDescription.h | 25 + Headers/Foundation/NSScriptExecutionContext.h | 7 + Headers/Foundation/NSScriptKeyValueCoding.h | 20 +- Headers/Foundation/NSScriptObjectSpecifiers.h | 47 -- .../NSScriptStandardSuiteCommands.h | 101 ++- Headers/Foundation/NSScriptSuiteRegistry.h | 35 + Source/DocMakefile | 2 +- Source/GNUmakefile | 4 +- Source/NSScriptClassDescription.m | 111 +++ Source/NSScriptCoercionHandler.m | 114 +++ Source/NSScriptCommand.m | 178 +++++ Source/NSScriptCommandDescription.m | 120 ++++ Source/NSScriptExecutionContext.m | 36 + Source/NSScriptKeyValueCoding.m | 179 ++++- Source/NSScriptObjectSpecifiers.m | 30 - Source/NSScriptStandardSuiteCommands.m | 668 +++++++++++++++++- Source/NSScriptSuiteRegistry.m | 316 +++++++++ 21 files changed, 1989 insertions(+), 91 deletions(-) delete mode 100644 Headers/Foundation/NSScriptObjectSpecifiers.h delete mode 100644 Source/NSScriptObjectSpecifiers.m diff --git a/Headers/Foundation/Foundation.h b/Headers/Foundation/Foundation.h index bbdb769b57..3b43395cea 100644 --- a/Headers/Foundation/Foundation.h +++ b/Headers/Foundation/Foundation.h @@ -144,7 +144,7 @@ #import #import #import -#import +#import #import #import #import diff --git a/Headers/Foundation/NSScriptClassDescription.h b/Headers/Foundation/NSScriptClassDescription.h index 3241510c79..3a8067d080 100644 --- a/Headers/Foundation/NSScriptClassDescription.h +++ b/Headers/Foundation/NSScriptClassDescription.h @@ -33,9 +33,37 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSScriptCommandDescription; +@class NSString; +@class NSArray; + +typedef uint32_t FourCharCode; + GS_EXPORT_CLASS @interface NSScriptClassDescription : NSClassDescription ++ (NSScriptClassDescription *) classDescriptionForClass: (Class)aClass; + +- (id) initWithSuiteName: (NSString *)suiteName + className: (NSString *)className + appleEventCode: (FourCharCode)appleEventCode + superclass: (NSScriptClassDescription *)superclassDesc; + +- (id) initWithSuiteName: (NSString *)suiteName + className: (NSString *)className + appleEventCode: (FourCharCode)appleEventCode; + +- (FourCharCode) appleEventCode; +- (NSString *) className; +- (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCharCode)appleEventClassCode + andAppleEventCode: (FourCharCode)appleEventIDCode; +- (Class) implementationClass; +- (BOOL) isLocationRequiredToCreateForKey: (NSString *)toManyRelationshipKey; +- (NSString *) suiteName; +- (NSScriptClassDescription *) superclassDescription; +- (BOOL) supportsCommand: (NSScriptCommandDescription *)commandDef; +- (NSString *) typeForKey: (NSString *)key; + @end #if defined(__cplusplus) diff --git a/Headers/Foundation/NSScriptCoercionHandler.h b/Headers/Foundation/NSScriptCoercionHandler.h index a8e726ccab..5040498879 100644 --- a/Headers/Foundation/NSScriptCoercionHandler.h +++ b/Headers/Foundation/NSScriptCoercionHandler.h @@ -32,9 +32,21 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSString; + GS_EXPORT_CLASS @interface NSScriptCoercionHandler : NSObject ++ (NSScriptCoercionHandler *) sharedCoercionHandler; + +- (id) coerceValue: (id)value + toClass: (Class)toClass; + +- (void) registerCoercer: (id)coercer + selector: (SEL)selector + toConvertFromClass: (Class)fromClass + toClass: (Class)toClass; + @end #if defined(__cplusplus) diff --git a/Headers/Foundation/NSScriptCommand.h b/Headers/Foundation/NSScriptCommand.h index b1dbcd5565..999dc81245 100644 --- a/Headers/Foundation/NSScriptCommand.h +++ b/Headers/Foundation/NSScriptCommand.h @@ -32,9 +32,53 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSScriptCommandDescription; +@class NSScriptObjectSpecifier; +@class NSDictionary; +@class NSString; +@class NSAppleEventDescriptor; +@class NSCoder; + GS_EXPORT_CLASS @interface NSScriptCommand : NSObject +- (id) initWithCommandDescription: (NSScriptCommandDescription *)commandDef; + +- (id) initWithCoder: (NSCoder *)coder; + +- (NSScriptCommandDescription *) commandDescription; + +- (NSDictionary *) arguments; +- (void) setArguments: (NSDictionary *)args; + +- (NSDictionary *) evaluatedArguments; + +- (NSScriptObjectSpecifier *) directParameter; +- (void) setDirectParameter: (NSScriptObjectSpecifier *)directParameter; + +- (id) evaluatedReceivers; + +- (BOOL) isWellFormed; + +- (id) performDefaultImplementation; + +- (id) executeCommand; + +- (void) suspendExecution; +- (void) resumeExecutionWithResult: (id)result; + +- (NSScriptObjectSpecifier *) receiversSpecifier; +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef; + +- (id) currentCommand; + +- (NSAppleEventDescriptor *) appleEvent; + +- (void) setScriptErrorNumber: (NSInteger)errorNumber; +- (void) setScriptErrorString: (NSString *)errorString; +- (NSInteger) scriptErrorNumber; +- (NSString *) scriptErrorString; + @end #if defined(__cplusplus) @@ -44,4 +88,3 @@ GS_EXPORT_CLASS #endif /* GS_API_MACOSX */ #endif /* _NSScriptCommand_h_GNUSTEP_BASE_INCLUDE */ - diff --git a/Headers/Foundation/NSScriptCommandDescription.h b/Headers/Foundation/NSScriptCommandDescription.h index 5350640278..d4b6ba540a 100644 --- a/Headers/Foundation/NSScriptCommandDescription.h +++ b/Headers/Foundation/NSScriptCommandDescription.h @@ -32,9 +32,34 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSString; +@class NSScriptClassDescription; + +typedef uint32_t FourCharCode; + GS_EXPORT_CLASS @interface NSScriptCommandDescription : NSObject +- (id) initWithSuiteName: (NSString *)suiteName + commandName: (NSString *)commandName + appleEventCode: (FourCharCode)appleEventCode + appleEventClassCode: (FourCharCode)appleEventClassCode; + +- (FourCharCode) appleEventCode; +- (FourCharCode) appleEventClassCode; + +- (NSString *) commandName; +- (NSString *) suiteName; + +- (NSString *) returnType; +- (FourCharCode) returnAppleEventCode; + +- (NSArray *) argumentNames; +- (NSString *) typeForArgumentWithName: (NSString *)argumentName; +- (FourCharCode) appleEventCodeForArgumentWithName: (NSString *)argumentName; + +- (BOOL) isOptionalArgumentWithName: (NSString *)argumentName; + @end #if defined(__cplusplus) diff --git a/Headers/Foundation/NSScriptExecutionContext.h b/Headers/Foundation/NSScriptExecutionContext.h index b2045e6e52..330d2c281c 100644 --- a/Headers/Foundation/NSScriptExecutionContext.h +++ b/Headers/Foundation/NSScriptExecutionContext.h @@ -32,9 +32,16 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +@class NSScriptCommand; + GS_EXPORT_CLASS @interface NSScriptExecutionContext : NSObject ++ (NSScriptExecutionContext *) sharedScriptExecutionContext; + +- (NSScriptCommand *) topLevelObject; +- (void) setTopLevelObject: (NSScriptCommand *)obj; + @end #if defined(__cplusplus) diff --git a/Headers/Foundation/NSScriptKeyValueCoding.h b/Headers/Foundation/NSScriptKeyValueCoding.h index 4dac13428c..0baffce8fe 100644 --- a/Headers/Foundation/NSScriptKeyValueCoding.h +++ b/Headers/Foundation/NSScriptKeyValueCoding.h @@ -32,8 +32,24 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) -GS_EXPORT_CLASS -@interface NSScriptKeyValueCoding : NSObject +@class NSString; + +@interface NSObject (NSScriptKeyValueCoding) + +- (id) valueAtIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key; +- (id) valueWithName: (NSString *)name inPropertyWithKey: (NSString *)key; +- (id) valueWithUniqueID: (id)uniqueID inPropertyWithKey: (NSString *)key; + +- (void) insertValue: (id)value atIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key; +- (void) insertValue: (id)value inPropertyWithKey: (NSString *)key; + +- (id) coerceValue: (id)value forKey: (NSString *)key; + +- (void) removeValueAtIndex: (NSUInteger)index fromPropertyWithKey: (NSString *)key; + +- (void) replaceValueAtIndex: (NSUInteger)index + inPropertyWithKey: (NSString *)key + withValue: (id)value; @end diff --git a/Headers/Foundation/NSScriptObjectSpecifiers.h b/Headers/Foundation/NSScriptObjectSpecifiers.h deleted file mode 100644 index e4b341081a..0000000000 --- a/Headers/Foundation/NSScriptObjectSpecifiers.h +++ /dev/null @@ -1,47 +0,0 @@ -/**Definition of class NSScriptObjectSpecifiers - Copyright (C) 2019 Free Software Foundation, Inc. - - By: Gregory John Casamento - Date: Sep 2019 - - This file is part of the GNUstep Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. -*/ - -#ifndef _NSScriptObjectSpecifiers_h_GNUSTEP_BASE_INCLUDE -#define _NSScriptObjectSpecifiers_h_GNUSTEP_BASE_INCLUDE - -#include - -#if defined(__cplusplus) -extern "C" { -#endif - -#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) - -GS_EXPORT_CLASS -@interface NSScriptObjectSpecifiers : NSObject - -@end - -#if defined(__cplusplus) -} -#endif - -#endif /* GS_API_MACOSX */ - -#endif /* _NSScriptObjectSpecifiers_h_GNUSTEP_BASE_INCLUDE */ - diff --git a/Headers/Foundation/NSScriptStandardSuiteCommands.h b/Headers/Foundation/NSScriptStandardSuiteCommands.h index 1de2fe52e6..43a05585a6 100644 --- a/Headers/Foundation/NSScriptStandardSuiteCommands.h +++ b/Headers/Foundation/NSScriptStandardSuiteCommands.h @@ -1,4 +1,4 @@ -/**Definition of class NSScriptStandardSuiteCommands +/**Definition of NSScriptStandardSuiteCommands classes Copyright (C) 2019 Free Software Foundation, Inc. By: Gregory John Casamento @@ -24,17 +24,112 @@ #ifndef _NSScriptStandardSuiteCommands_h_GNUSTEP_BASE_INCLUDE #define _NSScriptStandardSuiteCommands_h_GNUSTEP_BASE_INCLUDE -#include +#include #if defined(__cplusplus) extern "C" { #endif +@class NSString; +@class NSScriptClassDescription; + #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) +typedef NS_ENUM(NSInteger, NSSaveOptions) { + NSSaveOptionsYes = 0, + NSSaveOptionsNo, + NSSaveOptionsAsk +}; + +// Clone Command +GS_EXPORT_CLASS +@interface NSCloneCommand : NSScriptCommand +- (id) performDefaultImplementation; +@end + +// Close Command +GS_EXPORT_CLASS +@interface NSCloseCommand : NSScriptCommand +{ + @private + id _saveOptions; + id _file; +} +- (NSSaveOptions) saveOptions; +- (id) performDefaultImplementation; +@end + +// Count Command +GS_EXPORT_CLASS +@interface NSCountCommand : NSScriptCommand +- (id) performDefaultImplementation; +@end + +// Create Command +GS_EXPORT_CLASS +@interface NSCreateCommand : NSScriptCommand +{ + @private + NSScriptObjectSpecifier *_createClassDescription; +} +- (NSScriptClassDescription *) createClassDescription; +- (id) performDefaultImplementation; +@end + +// Delete Command +GS_EXPORT_CLASS +@interface NSDeleteCommand : NSScriptCommand +{ + @private + id _keySpecifier; +} +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef; +- (id) performDefaultImplementation; +@end + +// Exists Command GS_EXPORT_CLASS -@interface NSScriptStandardSuiteCommands : NSObject +@interface NSExistsCommand : NSScriptCommand +- (id) performDefaultImplementation; +@end +// Get Command +GS_EXPORT_CLASS +@interface NSGetCommand : NSScriptCommand +- (id) performDefaultImplementation; +@end + +// Move Command +GS_EXPORT_CLASS +@interface NSMoveCommand : NSScriptCommand +{ + @private + NSScriptObjectSpecifier *_keySpecifier; +} +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef; +- (id) performDefaultImplementation; +@end + +// Quit Command +GS_EXPORT_CLASS +@interface NSQuitCommand : NSScriptCommand +{ + @private + NSSaveOptions _saveOptions; +} +- (NSSaveOptions) saveOptions; +- (id) performDefaultImplementation; +@end + +// Set Command +GS_EXPORT_CLASS +@interface NSSetCommand : NSScriptCommand +{ + @private + id _keySpecifier; +} +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef; +- (id) performDefaultImplementation; @end #if defined(__cplusplus) diff --git a/Headers/Foundation/NSScriptSuiteRegistry.h b/Headers/Foundation/NSScriptSuiteRegistry.h index e9585770dc..b492187922 100644 --- a/Headers/Foundation/NSScriptSuiteRegistry.h +++ b/Headers/Foundation/NSScriptSuiteRegistry.h @@ -30,10 +30,45 @@ extern "C" { #endif +@class NSArray; +@class NSBundle; +@class NSDictionary; +@class NSScriptClassDescription; +@class NSScriptCommandDescription; +@class NSString; + +typedef uint32_t FourCharCode; + #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) GS_EXPORT_CLASS @interface NSScriptSuiteRegistry : NSObject +{ + @private + NSMutableDictionary *_suiteDescriptions; + NSMutableDictionary *_classDescriptions; + NSMutableDictionary *_commandDescriptions; + NSMutableArray *_bundles; +} + ++ (NSScriptSuiteRegistry *) sharedScriptSuiteRegistry; ++ (void) setSharedScriptSuiteRegistry: (NSScriptSuiteRegistry *)registry; + +- (void) loadSuitesFromBundle: (NSBundle *)bundle; +- (void) loadSuiteWithDictionary: (NSDictionary *)suiteDeclaration + fromBundle: (NSBundle *)bundle; + +- (void) registerClassDescription: (NSScriptClassDescription *)classDescription; +- (void) registerCommandDescription: (NSScriptCommandDescription *)commandDescription; + +- (NSScriptClassDescription *) classDescriptionWithAppleEventCode: (FourCharCode)appleEventCode; +- (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCharCode)appleEventClassCode + andAppleEventCode: (FourCharCode)appleEventCode; + +- (NSArray *) suiteNames; +- (FourCharCode) appleEventCodeForSuite: (NSString *)suiteName; + +- (NSBundle *) bundleForSuite: (NSString *)suiteName; @end diff --git a/Source/DocMakefile b/Source/DocMakefile index aa17fbc50a..ca09cd9087 100644 --- a/Source/DocMakefile +++ b/Source/DocMakefile @@ -159,7 +159,7 @@ NSScriptCommandDescription.h \ NSScriptCommand.h \ NSScriptExecutionContext.h \ NSScriptKeyValueCoding.h \ -NSScriptObjectSpecifiers.h \ +NSScriptObjectSpecifier.h \ NSScriptStandardSuiteCommands.h \ NSScriptSuiteRegistry.h \ NSScriptWhoseTests.h \ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 42a86b8ec9..068168ffea 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -309,7 +309,7 @@ NSScriptCommand.m \ NSScriptCommandDescription.m \ NSScriptExecutionContext.m \ NSScriptKeyValueCoding.m \ -NSScriptObjectSpecifiers.m \ +NSScriptObjectSpecifier.m \ NSScriptStandardSuiteCommands.m \ NSScriptSuiteRegistry.m \ NSUnit.m \ @@ -554,7 +554,7 @@ NSScriptCommand.h \ NSScriptCommandDescription.h \ NSScriptExecutionContext.h \ NSScriptKeyValueCoding.h \ -NSScriptObjectSpecifiers.h \ +NSScriptObjectSpecifier.h \ NSScriptStandardSuiteCommands.h \ NSScriptSuiteRegistry.h \ NSUnit.h \ diff --git a/Source/NSScriptClassDescription.m b/Source/NSScriptClassDescription.m index ed4646ee36..c055615f3b 100644 --- a/Source/NSScriptClassDescription.m +++ b/Source/NSScriptClassDescription.m @@ -21,9 +21,120 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSString.h" @implementation NSScriptClassDescription +{ + Class _implementationClass; + NSString *_className; + NSString *_suiteName; + NSString *_superclassName; + FourCharCode _appleEventCode; +} + ++ (NSScriptClassDescription *) classDescriptionForClass: (Class)aClass +{ + return (NSScriptClassDescription *)[super classDescriptionForClass: aClass]; +} + +- (id) initWithSuiteName: (NSString *)suiteName + className: (NSString *)className + appleEventCode: (FourCharCode)appleEventCode + superclass: (NSScriptClassDescription *)superclassDesc +{ + if ((self = [super init])) + { + ASSIGN(_suiteName, suiteName); + ASSIGN(_className, className); + _appleEventCode = appleEventCode; + if (superclassDesc != nil) + { + ASSIGN(_superclassName, [superclassDesc className]); + } + } + return self; +} + +- (id) initWithSuiteName: (NSString *)suiteName + className: (NSString *)className + appleEventCode: (FourCharCode)appleEventCode +{ + return [self initWithSuiteName: suiteName + className: className + appleEventCode: appleEventCode + superclass: nil]; +} + +- (void) dealloc +{ + RELEASE(_className); + RELEASE(_suiteName); + RELEASE(_superclassName); + [super dealloc]; +} + +- (FourCharCode) appleEventCode +{ + return _appleEventCode; +} + +- (NSString *) className +{ + return _className; +} + +- (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCharCode)appleEventClassCode + andAppleEventCode: (FourCharCode)appleEventIDCode +{ + return nil; +} + +- (Class) implementationClass +{ + if (_implementationClass == Nil && _className != nil) + { + _implementationClass = NSClassFromString(_className); + } + return _implementationClass; +} + +- (BOOL) isLocationRequiredToCreateForKey: (NSString *)toManyRelationshipKey +{ + return NO; +} + +- (NSString *) suiteName +{ + return _suiteName; +} + +- (NSScriptClassDescription *) superclassDescription +{ + Class superclass; + + if (_superclassName != nil) + { + superclass = NSClassFromString(_superclassName); + if (superclass != Nil) + { + return (NSScriptClassDescription *)[NSScriptClassDescription classDescriptionForClass: superclass]; + } + } + return nil; +} + +- (BOOL) supportsCommand: (NSScriptCommandDescription *)commandDef +{ + return NO; +} + +- (NSString *) typeForKey: (NSString *)key +{ + return nil; +} @end diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index 528ead13e7..f6d0a01ca1 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -22,9 +22,123 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptCoercionHandler.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSValue.h" +#import "Foundation/NSLock.h" + +@interface NSScriptCoercionHandler () +{ + NSMutableDictionary *_coercers; + NSLock *_lock; +} +@end @implementation NSScriptCoercionHandler +static NSScriptCoercionHandler *sharedHandler = nil; + ++ (void) initialize +{ + if (self == [NSScriptCoercionHandler class]) + { + sharedHandler = [[NSScriptCoercionHandler alloc] init]; + } +} + ++ (NSScriptCoercionHandler *) sharedCoercionHandler +{ + return sharedHandler; +} + +- (id) init +{ + if ((self = [super init])) + { + _coercers = [[NSMutableDictionary alloc] init]; + _lock = [[NSLock alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_coercers); + RELEASE(_lock); + [super dealloc]; +} + +- (NSString *) _keyForFromClass: (Class)fromClass toClass: (Class)toClass +{ + return [NSString stringWithFormat: @"%@->%@", + NSStringFromClass(fromClass), + NSStringFromClass(toClass)]; +} + +- (id) coerceValue: (id)value toClass: (Class)toClass +{ + NSString *key; + NSDictionary *coercerInfo; + id coercer; + SEL selector; + id result; + + if (value == nil) + return nil; + + if ([value isKindOfClass: toClass]) + return value; + + [_lock lock]; + + key = [self _keyForFromClass: [value class] toClass: toClass]; + coercerInfo = [_coercers objectForKey: key]; + + if (coercerInfo != nil) + { + coercer = [coercerInfo objectForKey: @"coercer"]; + selector = NSSelectorFromString([coercerInfo objectForKey: @"selector"]); + + [_lock unlock]; + + if (coercer != nil && [coercer respondsToSelector: selector]) + { + result = [coercer performSelector: selector withObject: value]; + return result; + } + } + else + { + [_lock unlock]; + } + + return value; +} + +- (void) registerCoercer: (id)coercer + selector: (SEL)selector + toConvertFromClass: (Class)fromClass + toClass: (Class)toClass +{ + NSString *key; + NSDictionary *info; + + if (coercer == nil || selector == NULL || fromClass == Nil || toClass == Nil) + return; + + [_lock lock]; + + key = [self _keyForFromClass: fromClass toClass: toClass]; + info = [NSDictionary dictionaryWithObjectsAndKeys: + coercer, @"coercer", + NSStringFromSelector(selector), @"selector", + nil]; + + [_coercers setObject: info forKey: key]; + + [_lock unlock]; +} + @end diff --git a/Source/NSScriptCommand.m b/Source/NSScriptCommand.m index 5209a890fb..7193839e75 100644 --- a/Source/NSScriptCommand.m +++ b/Source/NSScriptCommand.m @@ -22,9 +22,187 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptCommand.h" +#import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSScriptObjectSpecifier.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSString.h" +#import "Foundation/NSAppleEventDescriptor.h" +#import "Foundation/NSCoder.h" @implementation NSScriptCommand +{ + NSScriptCommandDescription *_commandDescription; + NSDictionary *_arguments; + NSDictionary *_evaluatedArguments; + NSScriptObjectSpecifier *_directParameter; + NSScriptObjectSpecifier *_receiversSpecifier; + id _evaluatedReceivers; + NSAppleEventDescriptor *_appleEvent; + BOOL _isSuspended; +} + +- (id) initWithCommandDescription: (NSScriptCommandDescription *)commandDef +{ + if ((self = [super init])) + { + ASSIGN(_commandDescription, commandDef); + _arguments = [[NSMutableDictionary alloc] init]; + _isSuspended = NO; + } + return self; +} + +- (id) initWithCoder: (NSCoder *)coder +{ + if ((self = [super init])) + { + _commandDescription = RETAIN([coder decodeObjectForKey: @"commandDescription"]); + _arguments = RETAIN([coder decodeObjectForKey: @"arguments"]); + _directParameter = RETAIN([coder decodeObjectForKey: @"directParameter"]); + _receiversSpecifier = RETAIN([coder decodeObjectForKey: @"receiversSpecifier"]); + _isSuspended = NO; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_commandDescription); + RELEASE(_arguments); + RELEASE(_evaluatedArguments); + RELEASE(_directParameter); + RELEASE(_receiversSpecifier); + RELEASE(_evaluatedReceivers); + RELEASE(_appleEvent); + [super dealloc]; +} + +- (NSScriptCommandDescription *) commandDescription +{ + return _commandDescription; +} + +- (NSDictionary *) arguments +{ + return _arguments; +} + +- (void) setArguments: (NSDictionary *)args +{ + ASSIGN(_arguments, args); + DESTROY(_evaluatedArguments); +} + +- (NSDictionary *) evaluatedArguments +{ + NSEnumerator *keyEnum; + NSString *key; + id value; + NSMutableDictionary *evaluated; + + if (_evaluatedArguments != nil) + return _evaluatedArguments; + + if (_arguments == nil) + return nil; + + evaluated = [NSMutableDictionary dictionaryWithCapacity: [_arguments count]]; + keyEnum = [_arguments keyEnumerator]; + + while ((key = [keyEnum nextObject]) != nil) + { + value = [_arguments objectForKey: key]; + + if ([value isKindOfClass: [NSScriptObjectSpecifier class]]) + { + value = [(NSScriptObjectSpecifier *)value objectsByEvaluatingSpecifier]; + } + + if (value != nil) + { + [evaluated setObject: value forKey: key]; + } + } + + _evaluatedArguments = [evaluated copy]; + return _evaluatedArguments; +} + +- (NSScriptObjectSpecifier *) directParameter +{ + return _directParameter; +} + +- (void) setDirectParameter: (NSScriptObjectSpecifier *)directParameter +{ + ASSIGN(_directParameter, directParameter); +} + +- (id) evaluatedReceivers +{ + if (_evaluatedReceivers == nil && _receiversSpecifier != nil) + { + _evaluatedReceivers = RETAIN([_receiversSpecifier objectsByEvaluatingSpecifier]); + } + return _evaluatedReceivers; +} + +- (BOOL) isWellFormed +{ + return _commandDescription != nil; +} + +- (id) performDefaultImplementation +{ + return nil; +} + +- (id) executeCommand +{ + id result; + + if (![self isWellFormed]) + { + return nil; + } + + result = [self performDefaultImplementation]; + + return result; +} + +- (void) suspendExecution +{ + _isSuspended = YES; +} + +- (void) resumeExecutionWithResult: (id)result +{ + _isSuspended = NO; +} + +- (NSScriptObjectSpecifier *) receiversSpecifier +{ + return _receiversSpecifier; +} + +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef +{ + ASSIGN(_receiversSpecifier, receiversRef); + DESTROY(_evaluatedReceivers); +} + +- (id) currentCommand +{ + return self; +} + +- (NSAppleEventDescriptor *) appleEvent +{ + return _appleEvent; +} @end diff --git a/Source/NSScriptCommandDescription.m b/Source/NSScriptCommandDescription.m index f6fbb1e476..1018c50de2 100644 --- a/Source/NSScriptCommandDescription.m +++ b/Source/NSScriptCommandDescription.m @@ -22,9 +22,129 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSString.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSValue.h" @implementation NSScriptCommandDescription +{ + NSString *_suiteName; + NSString *_commandName; + FourCharCode _appleEventCode; + FourCharCode _appleEventClassCode; + NSString *_returnType; + FourCharCode _returnAppleEventCode; + NSMutableDictionary *_arguments; +} + +- (id) initWithSuiteName: (NSString *)suiteName + commandName: (NSString *)commandName + appleEventCode: (FourCharCode)appleEventCode + appleEventClassCode: (FourCharCode)appleEventClassCode +{ + if ((self = [super init])) + { + ASSIGN(_suiteName, suiteName); + ASSIGN(_commandName, commandName); + _appleEventCode = appleEventCode; + _appleEventClassCode = appleEventClassCode; + _arguments = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_suiteName); + RELEASE(_commandName); + RELEASE(_returnType); + RELEASE(_arguments); + [super dealloc]; +} + +- (FourCharCode) appleEventCode +{ + return _appleEventCode; +} + +- (FourCharCode) appleEventClassCode +{ + return _appleEventClassCode; +} + +- (NSString *) commandName +{ + return _commandName; +} + +- (NSString *) suiteName +{ + return _suiteName; +} + +- (NSString *) returnType +{ + return _returnType; +} + +- (FourCharCode) returnAppleEventCode +{ + return _returnAppleEventCode; +} + +- (NSArray *) argumentNames +{ + return [_arguments allKeys]; +} + +- (NSString *) typeForArgumentWithName: (NSString *)argumentName +{ + NSDictionary *argInfo; + + argInfo = [_arguments objectForKey: argumentName]; + if (argInfo != nil) + { + return [argInfo objectForKey: @"type"]; + } + return nil; +} + +- (FourCharCode) appleEventCodeForArgumentWithName: (NSString *)argumentName +{ + NSDictionary *argInfo; + NSNumber *codeNum; + + argInfo = [_arguments objectForKey: argumentName]; + if (argInfo != nil) + { + codeNum = [argInfo objectForKey: @"code"]; + if (codeNum != nil) + { + return [codeNum unsignedIntValue]; + } + } + return 0; +} + +- (BOOL) isOptionalArgumentWithName: (NSString *)argumentName +{ + NSDictionary *argInfo; + NSNumber *optional; + + argInfo = [_arguments objectForKey: argumentName]; + if (argInfo != nil) + { + optional = [argInfo objectForKey: @"optional"]; + if (optional != nil) + { + return [optional boolValue]; + } + } + return NO; +} @end diff --git a/Source/NSScriptExecutionContext.m b/Source/NSScriptExecutionContext.m index 8e3b269ea1..cd77394cec 100644 --- a/Source/NSScriptExecutionContext.m +++ b/Source/NSScriptExecutionContext.m @@ -22,9 +22,45 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptExecutionContext.h" +#import "Foundation/NSScriptCommand.h" @implementation NSScriptExecutionContext +{ + NSScriptCommand *_topLevelObject; +} + +static NSScriptExecutionContext *sharedContext = nil; + ++ (void) initialize +{ + if (self == [NSScriptExecutionContext class]) + { + sharedContext = [[NSScriptExecutionContext alloc] init]; + } +} + ++ (NSScriptExecutionContext *) sharedScriptExecutionContext +{ + return sharedContext; +} + +- (void) dealloc +{ + RELEASE(_topLevelObject); + [super dealloc]; +} + +- (NSScriptCommand *) topLevelObject +{ + return _topLevelObject; +} + +- (void) setTopLevelObject: (NSScriptCommand *)obj +{ + ASSIGN(_topLevelObject, obj); +} @end diff --git a/Source/NSScriptKeyValueCoding.m b/Source/NSScriptKeyValueCoding.m index 9fe992a755..df6a02b51c 100644 --- a/Source/NSScriptKeyValueCoding.m +++ b/Source/NSScriptKeyValueCoding.m @@ -22,9 +22,186 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptKeyValueCoding.h" +#import "Foundation/NSString.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSKeyValueCoding.h" +#import "Foundation/NSException.h" +#import "Foundation/NSValue.h" -@implementation NSScriptKeyValueCoding +@implementation NSObject (NSScriptKeyValueCoding) + +- (id) valueAtIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key +{ + id collection; + + collection = [self valueForKey: key]; + + if ([collection respondsToSelector: @selector(objectAtIndex:)]) + { + if (index < [collection count]) + { + return [collection objectAtIndex: index]; + } + } + + return nil; +} + +- (id) valueWithName: (NSString *)name inPropertyWithKey: (NSString *)key +{ + NSArray *collection; + NSEnumerator *enumerator; + id object; + + collection = [self valueForKey: key]; + + if ([collection respondsToSelector: @selector(objectEnumerator)]) + { + enumerator = [collection objectEnumerator]; + while ((object = [enumerator nextObject]) != nil) + { + id objectName; + + if ([object respondsToSelector: @selector(name)]) + { + objectName = [object performSelector: @selector(name)]; + if ([objectName isEqual: name]) + { + return object; + } + } + } + } + + return nil; +} + +- (id) valueWithUniqueID: (id)uniqueID inPropertyWithKey: (NSString *)key +{ + NSArray *collection; + NSEnumerator *enumerator; + id object; + + collection = [self valueForKey: key]; + + if ([collection respondsToSelector: @selector(objectEnumerator)]) + { + enumerator = [collection objectEnumerator]; + while ((object = [enumerator nextObject]) != nil) + { + id objectID; + + if ([object respondsToSelector: @selector(uniqueID)]) + { + objectID = [object performSelector: @selector(uniqueID)]; + if ([objectID isEqual: uniqueID]) + { + return object; + } + } + } + } + + return nil; +} + +- (void) insertValue: (id)value atIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key +{ + NSString *insertSel; + SEL selector; + + insertSel = [NSString stringWithFormat: @"insertIn%@:atIndex:", + [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) + withString: [[key substringToIndex: 1] uppercaseString]]]; + selector = NSSelectorFromString(insertSel); + + if ([self respondsToSelector: selector]) + { + [self performSelector: selector withObject: value withObject: [NSNumber numberWithUnsignedInteger: index]]; + } + else + { + NSMutableArray *array; + + array = [[self mutableArrayValueForKey: key] retain]; + [array insertObject: value atIndex: index]; + [self setValue: array forKey: key]; + RELEASE(array); + } +} + +- (void) insertValue: (id)value inPropertyWithKey: (NSString *)key +{ + NSMutableArray *array; + + array = [self mutableArrayValueForKey: key]; + [array addObject: value]; +} + +- (id) coerceValue: (id)value forKey: (NSString *)key +{ + return value; +} + +- (void) removeValueAtIndex: (NSUInteger)index fromPropertyWithKey: (NSString *)key +{ + NSString *removeSel; + SEL selector; + + removeSel = [NSString stringWithFormat: @"removeFrom%@AtIndex:", + [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) + withString: [[key substringToIndex: 1] uppercaseString]]]; + selector = NSSelectorFromString(removeSel); + + if ([self respondsToSelector: selector]) + { + [self performSelector: selector withObject: [NSNumber numberWithUnsignedInteger: index]]; + } + else + { + NSMutableArray *array; + + array = [[self mutableArrayValueForKey: key] retain]; + if (index < [array count]) + { + [array removeObjectAtIndex: index]; + [self setValue: array forKey: key]; + } + RELEASE(array); + } +} + +- (void) replaceValueAtIndex: (NSUInteger)index + inPropertyWithKey: (NSString *)key + withValue: (id)value +{ + NSString *replaceSel; + SEL selector; + + replaceSel = [NSString stringWithFormat: @"replaceIn%@:atIndex:withObject:", + [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) + withString: [[key substringToIndex: 1] uppercaseString]]]; + selector = NSSelectorFromString(replaceSel); + + if ([self respondsToSelector: selector]) + { + [self performSelector: selector withObject: [NSNumber numberWithUnsignedInteger: index] withObject: value]; + } + else + { + NSMutableArray *array; + + array = [[self mutableArrayValueForKey: key] retain]; + if (index < [array count]) + { + [array replaceObjectAtIndex: index withObject: value]; + [self setValue: array forKey: key]; + } + RELEASE(array); + } +} @end diff --git a/Source/NSScriptObjectSpecifiers.m b/Source/NSScriptObjectSpecifiers.m deleted file mode 100644 index 6776004a85..0000000000 --- a/Source/NSScriptObjectSpecifiers.m +++ /dev/null @@ -1,30 +0,0 @@ - -/* Implementation of class NSScriptObjectSpecifiers - Copyright (C) 2019 Free Software Foundation, Inc. - - By: Gregory John Casamento - Date: Sep 2019 - - This file is part of the GNUstep Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. -*/ - -#import "Foundation/NSScriptObjectSpecifiers.h" - -@implementation NSScriptObjectSpecifiers - -@end - diff --git a/Source/NSScriptStandardSuiteCommands.m b/Source/NSScriptStandardSuiteCommands.m index 9eecc9f697..8b9375a476 100644 --- a/Source/NSScriptStandardSuiteCommands.m +++ b/Source/NSScriptStandardSuiteCommands.m @@ -1,5 +1,4 @@ - -/* Implementation of class NSScriptStandardSuiteCommands +/* Implementation of NSScriptStandardSuiteCommands classes Copyright (C) 2019 Free Software Foundation, Inc. By: Gregory John Casamento @@ -22,9 +21,672 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptStandardSuiteCommands.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSException.h" +#import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptObjectSpecifier.h" +#import "Foundation/NSString.h" +#import "Foundation/NSValue.h" + +// NSCloneCommand + +@implementation NSCloneCommand + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + id result; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Clone command requires object to clone"]; + return nil; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object to clone"]; + return nil; + } + + if ([receivers respondsToSelector: @selector(copy)]) + { + result = [receivers copy]; + return AUTORELEASE(result); + } + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Object does not support cloning"]; + return nil; +} + +@end + +// NSCloseCommand + +@implementation NSCloseCommand + +- (NSSaveOptions) saveOptions +{ + id saveOption; + + saveOption = [[self evaluatedArguments] objectForKey: @"SaveOptions"]; + if (saveOption == nil) + { + return NSSaveOptionsAsk; + } + + if ([saveOption isKindOfClass: [NSNumber class]]) + { + return [saveOption integerValue]; + } + + if ([saveOption isKindOfClass: [NSString class]]) + { + if ([saveOption isEqualToString: @"yes"]) + { + return NSSaveOptionsYes; + } + else if ([saveOption isEqualToString: @"no"]) + { + return NSSaveOptionsNo; + } + else if ([saveOption isEqualToString: @"ask"]) + { + return NSSaveOptionsAsk; + } + } + + return NSSaveOptionsAsk; +} + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Close command requires object to close"]; + return nil; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object to close"]; + return nil; + } + + (void)[self saveOptions]; /* Retrieve but don't use for now */ + + if ([receivers respondsToSelector: @selector(close)]) + { + [receivers performSelector: @selector(close)]; + return [NSNumber numberWithBool: YES]; + } + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Object does not support close"]; + return nil; +} + +@end + +// NSCountCommand + +@implementation NSCountCommand + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + NSUInteger count; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + id directParam; + + directParam = [self directParameter]; + if (directParam != nil) + { + receivers = directParam; + } + else + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Count command requires object to count"]; + return nil; + } + } + else + { + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + } + + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object to count"]; + return nil; + } + + if ([receivers isKindOfClass: [NSArray class]]) + { + count = [receivers count]; + } + else if ([receivers respondsToSelector: @selector(count)]) + { + count = [receivers count]; + } + else + { + count = 1; + } + + return [NSNumber numberWithUnsignedInteger: count]; +} + +@end + +// NSCreateCommand + +@implementation NSCreateCommand + +- (NSScriptClassDescription *) createClassDescription +{ + id classValue; + NSString *className; + + classValue = [[self evaluatedArguments] objectForKey: @"ObjectClass"]; + if (classValue == nil) + { + return nil; + } + + if ([classValue isKindOfClass: [NSString class]]) + { + className = classValue; + } + else if ([classValue isKindOfClass: [NSScriptClassDescription class]]) + { + return classValue; + } + else + { + return nil; + } + + return [NSScriptClassDescription classDescriptionForClass: NSClassFromString(className)]; +} + +- (id) performDefaultImplementation +{ + NSScriptClassDescription *classDesc; + NSString *className; + Class createClass; + id newObject; + id container; + NSScriptObjectSpecifier *containerSpec; + NSString *key; + id properties; + + classDesc = [self createClassDescription]; + if (classDesc == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Create command requires class to create"]; + return nil; + } + + className = [classDesc className]; + createClass = NSClassFromString(className); + if (createClass == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: [NSString stringWithFormat: @"Unknown class: %@", className]]; + return nil; + } + + newObject = [[createClass alloc] init]; + if (newObject == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not create object"]; + return nil; + } + + properties = [[self evaluatedArguments] objectForKey: @"KeyDictionary"]; + if (properties != nil && [properties isKindOfClass: [NSDictionary class]]) + { + NSEnumerator *keyEnum; + NSString *propKey; + + keyEnum = [properties keyEnumerator]; + while ((propKey = [keyEnum nextObject]) != nil) + { + id propValue; + + propValue = [properties objectForKey: propKey]; + [newObject setValue: propValue forKey: propKey]; + } + } + + containerSpec = [[self evaluatedArguments] objectForKey: @"Location"]; + if (containerSpec != nil && [containerSpec isKindOfClass: [NSScriptObjectSpecifier class]]) + { + container = [containerSpec objectsByEvaluatingSpecifier]; + key = [containerSpec key]; + + if (container != nil && key != nil) + { + id collection; + + collection = [container valueForKey: key]; + if ([collection isKindOfClass: [NSMutableArray class]]) + { + [collection addObject: newObject]; + } + else if ([container respondsToSelector: @selector(insertObject:inPropertyWithKey:)]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-method-access" + [container insertObject: newObject inPropertyWithKey: key]; +#pragma clang diagnostic pop + } + } + } + + return AUTORELEASE(newObject); +} + +- (void) dealloc +{ + RELEASE(_createClassDescription); + [super dealloc]; +} + +@end + +// NSDeleteCommand + +@implementation NSDeleteCommand + +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef +{ + [super setReceiversSpecifier: receiversRef]; + ASSIGN(_keySpecifier, receiversRef); +} + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + NSScriptObjectSpecifier *containerSpec; + id container; + NSString *key; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Delete command requires object to delete"]; + return nil; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object to delete"]; + return nil; + } + + containerSpec = [receiversSpec containerSpecifier]; + if (containerSpec != nil) + { + container = [containerSpec objectsByEvaluatingSpecifier]; + key = [receiversSpec key]; + + if (container != nil && key != nil) + { + id collection; + + collection = [container valueForKey: key]; + if ([collection isKindOfClass: [NSMutableArray class]]) + { + [collection removeObject: receivers]; + return [NSNumber numberWithBool: YES]; + } + else if ([container respondsToSelector: @selector(removeValueForKey:)]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-method-access" + [container removeValueForKey: key]; +#pragma clang diagnostic pop + return [NSNumber numberWithBool: YES]; + } + } + } + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Could not delete object"]; + return nil; +} + +- (void) dealloc +{ + RELEASE(_keySpecifier); + [super dealloc]; +} + +@end + +// NSExistsCommand + +@implementation NSExistsCommand + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + id directParam; + + directParam = [self directParameter]; + if (directParam != nil) + { + return [NSNumber numberWithBool: YES]; + } + return [NSNumber numberWithBool: NO]; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + return [NSNumber numberWithBool: (receivers != nil)]; +} + +@end + +// NSGetCommand + +@implementation NSGetCommand + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + id directParam; + + directParam = [self directParameter]; + if (directParam != nil) + { + return directParam; + } + + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Get command requires object"]; + return nil; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object"]; + return nil; + } + + return receivers; +} + +@end + +// NSMoveCommand + +@implementation NSMoveCommand + +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef +{ + [super setReceiversSpecifier: receiversRef]; + ASSIGN(_keySpecifier, receiversRef); +} + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + NSScriptObjectSpecifier *destinationSpec; + id destination; + NSScriptObjectSpecifier *sourceContainerSpec; + id sourceContainer; + NSString *sourceKey; + NSString *destKey; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Move command requires object to move"]; + return nil; + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate object to move"]; + return nil; + } + + destinationSpec = [[self evaluatedArguments] objectForKey: @"ToLocation"]; + if (destinationSpec == nil || ![destinationSpec isKindOfClass: [NSScriptObjectSpecifier class]]) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Move command requires destination"]; + return nil; + } + + destination = [destinationSpec objectsByEvaluatingSpecifier]; + if (destination == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Could not evaluate destination"]; + return nil; + } + + sourceContainerSpec = [receiversSpec containerSpecifier]; + if (sourceContainerSpec != nil) + { + sourceContainer = [sourceContainerSpec objectsByEvaluatingSpecifier]; + sourceKey = [receiversSpec key]; + destKey = [destinationSpec key]; + + if (sourceContainer != nil && sourceKey != nil) + { + id sourceCollection; + id destCollection; + + sourceCollection = [sourceContainer valueForKey: sourceKey]; + + if ([sourceCollection isKindOfClass: [NSMutableArray class]]) + { + RETAIN(receivers); + [sourceCollection removeObject: receivers]; + + if (destination != nil && destKey != nil) + { + destCollection = [destination valueForKey: destKey]; + if ([destCollection isKindOfClass: [NSMutableArray class]]) + { + [destCollection addObject: receivers]; + } + } + + RELEASE(receivers); + return [NSNumber numberWithBool: YES]; + } + } + } + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Could not move object"]; + return nil; +} + +- (void) dealloc +{ + RELEASE(_keySpecifier); + [super dealloc]; +} + +@end + +// NSQuitCommand + +@implementation NSQuitCommand + +- (NSSaveOptions) saveOptions +{ + id saveOption; + + saveOption = [[self evaluatedArguments] objectForKey: @"SaveOptions"]; + if (saveOption == nil) + { + return NSSaveOptionsAsk; + } + + if ([saveOption isKindOfClass: [NSNumber class]]) + { + return [saveOption integerValue]; + } + + if ([saveOption isKindOfClass: [NSString class]]) + { + if ([saveOption isEqualToString: @"yes"]) + { + return NSSaveOptionsYes; + } + else if ([saveOption isEqualToString: @"no"]) + { + return NSSaveOptionsNo; + } + else if ([saveOption isEqualToString: @"ask"]) + { + return NSSaveOptionsAsk; + } + } + + return NSSaveOptionsAsk; +} + +- (id) performDefaultImplementation +{ + /* NSApplication is not available in Foundation. + * Applications should override this method or handle quit through + * other mechanisms (notifications, delegates, etc.) + */ + (void)[self saveOptions]; /* Retrieve but don't use for now */ + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Quit command must be implemented by application"]; + return nil; +} + +@end + +// NSSetCommand + +@implementation NSSetCommand + +- (void) setReceiversSpecifier: (NSScriptObjectSpecifier *)receiversRef +{ + [super setReceiversSpecifier: receiversRef]; + ASSIGN(_keySpecifier, receiversRef); +} + +- (id) performDefaultImplementation +{ + NSScriptObjectSpecifier *receiversSpec; + id receivers; + id newValue; + NSScriptObjectSpecifier *containerSpec; + id container; + NSString *key; + + receiversSpec = [self receiversSpecifier]; + if (receiversSpec == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Set command requires object"]; + return nil; + } + + newValue = [[self evaluatedArguments] objectForKey: @"Value"]; + if (newValue == nil) + { + newValue = [self directParameter]; + } + + if (newValue == nil) + { + [self setScriptErrorNumber: -1728]; + [self setScriptErrorString: @"Set command requires value"]; + return nil; + } + + containerSpec = [receiversSpec containerSpecifier]; + if (containerSpec != nil) + { + container = [containerSpec objectsByEvaluatingSpecifier]; + key = [receiversSpec key]; + + if (container != nil && key != nil) + { + [container setValue: newValue forKey: key]; + return newValue; + } + } + + receivers = [receiversSpec objectsByEvaluatingSpecifier]; + if (receivers != nil) + { + key = [receiversSpec key]; + if (key != nil) + { + [receivers setValue: newValue forKey: key]; + return newValue; + } + } + + [self setScriptErrorNumber: -1751]; + [self setScriptErrorString: @"Could not set value"]; + return nil; +} -@implementation NSScriptStandardSuiteCommands +- (void) dealloc +{ + RELEASE(_keySpecifier); + [super dealloc]; +} @end diff --git a/Source/NSScriptSuiteRegistry.m b/Source/NSScriptSuiteRegistry.m index f2884d18c2..80f3ba15b7 100644 --- a/Source/NSScriptSuiteRegistry.m +++ b/Source/NSScriptSuiteRegistry.m @@ -22,9 +22,325 @@ Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. */ +#import "common.h" #import "Foundation/NSScriptSuiteRegistry.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSBundle.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSException.h" +#import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSString.h" +#import "Foundation/NSValue.h" +#import "Foundation/NSLock.h" + +static NSScriptSuiteRegistry *sharedRegistry = nil; +static NSLock *registryLock = nil; @implementation NSScriptSuiteRegistry ++ (void) initialize +{ + if (self == [NSScriptSuiteRegistry class]) + { + registryLock = [[NSLock alloc] init]; + } +} + ++ (NSScriptSuiteRegistry *) sharedScriptSuiteRegistry +{ + NSScriptSuiteRegistry *registry; + + [registryLock lock]; + if (sharedRegistry == nil) + { + sharedRegistry = [[NSScriptSuiteRegistry alloc] init]; + } + registry = sharedRegistry; + [registryLock unlock]; + + return registry; +} + ++ (void) setSharedScriptSuiteRegistry: (NSScriptSuiteRegistry *)registry +{ + [registryLock lock]; + ASSIGN(sharedRegistry, registry); + [registryLock unlock]; +} + +- (instancetype) init +{ + self = [super init]; + if (self != nil) + { + _suiteDescriptions = [[NSMutableDictionary alloc] init]; + _classDescriptions = [[NSMutableDictionary alloc] init]; + _commandDescriptions = [[NSMutableDictionary alloc] init]; + _bundles = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_suiteDescriptions); + RELEASE(_classDescriptions); + RELEASE(_commandDescriptions); + RELEASE(_bundles); + [super dealloc]; +} + +- (void) loadSuitesFromBundle: (NSBundle *)bundle +{ + NSString *suitesPath; + NSArray *suitePaths; + NSEnumerator *pathEnum; + NSString *path; + + if (bundle == nil) + { + bundle = [NSBundle mainBundle]; + } + + if ([_bundles containsObject: bundle]) + { + return; + } + + [_bundles addObject: bundle]; + + suitesPath = [bundle pathForResource: @"ScriptSuites" ofType: @"plist"]; + if (suitesPath != nil) + { + NSDictionary *suitesDict; + NSEnumerator *suiteEnum; + NSString *suiteName; + + suitesDict = [NSDictionary dictionaryWithContentsOfFile: suitesPath]; + if (suitesDict != nil) + { + suiteEnum = [suitesDict keyEnumerator]; + while ((suiteName = [suiteEnum nextObject]) != nil) + { + NSDictionary *suiteDecl; + + suiteDecl = [suitesDict objectForKey: suiteName]; + if (suiteDecl != nil) + { + [self loadSuiteWithDictionary: suiteDecl fromBundle: bundle]; + } + } + } + } + + suitePaths = [bundle pathsForResourcesOfType: @"scriptSuite" inDirectory: nil]; + pathEnum = [suitePaths objectEnumerator]; + while ((path = [pathEnum nextObject]) != nil) + { + NSDictionary *suiteDict; + + suiteDict = [NSDictionary dictionaryWithContentsOfFile: path]; + if (suiteDict != nil) + { + [self loadSuiteWithDictionary: suiteDict fromBundle: bundle]; + } + } +} + +- (void) loadSuiteWithDictionary: (NSDictionary *)suiteDeclaration + fromBundle: (NSBundle *)bundle +{ + NSString *suiteName; + NSNumber *appleEventCode; + NSDictionary *classes; + NSDictionary *commands; + NSEnumerator *classEnum; + NSString *className; + NSEnumerator *commandEnum; + NSString *commandName; + + if (suiteDeclaration == nil) + { + return; + } + + suiteName = [suiteDeclaration objectForKey: @"Name"]; + if (suiteName == nil) + { + return; + } + + appleEventCode = [suiteDeclaration objectForKey: @"AppleEventCode"]; + if (appleEventCode != nil) + { + NSMutableDictionary *suiteInfo; + + suiteInfo = [NSMutableDictionary dictionaryWithCapacity: 3]; + [suiteInfo setObject: appleEventCode forKey: @"AppleEventCode"]; + [suiteInfo setObject: bundle forKey: @"Bundle"]; + [_suiteDescriptions setObject: suiteInfo forKey: suiteName]; + } + + classes = [suiteDeclaration objectForKey: @"Classes"]; + if (classes != nil) + { + classEnum = [classes keyEnumerator]; + while ((className = [classEnum nextObject]) != nil) + { + NSDictionary *classDecl; + NSNumber *classCode; + + classDecl = [classes objectForKey: className]; + classCode = [classDecl objectForKey: @"AppleEventCode"]; + /* Superclass handling could be added here if needed */ + + if (classCode != nil) + { + NSScriptClassDescription *classDesc; + + classDesc = [[NSScriptClassDescription alloc] initWithSuiteName: suiteName + className: className + appleEventCode: [classCode unsignedIntValue]]; + [self registerClassDescription: classDesc]; + RELEASE(classDesc); + } + } + } + + commands = [suiteDeclaration objectForKey: @"Commands"]; + if (commands != nil) + { + commandEnum = [commands keyEnumerator]; + while ((commandName = [commandEnum nextObject]) != nil) + { + NSDictionary *commandDecl; + NSNumber *commandCode; + NSNumber *commandClassCode; + + commandDecl = [commands objectForKey: commandName]; + commandCode = [commandDecl objectForKey: @"AppleEventCode"]; + commandClassCode = [commandDecl objectForKey: @"AppleEventClassCode"]; + + if (commandCode != nil && commandClassCode != nil) + { + NSScriptCommandDescription *commandDesc; + + commandDesc = [[NSScriptCommandDescription alloc] initWithSuiteName: suiteName + commandName: commandName + appleEventCode: [commandCode unsignedIntValue] + appleEventClassCode: [commandClassCode unsignedIntValue]]; + [self registerCommandDescription: commandDesc]; + RELEASE(commandDesc); + } + } + } +} + +- (void) registerClassDescription: (NSScriptClassDescription *)classDescription +{ + FourCharCode appleEventCode; + NSNumber *codeNumber; + + if (classDescription == nil) + { + return; + } + + appleEventCode = [classDescription appleEventCode]; + codeNumber = [NSNumber numberWithUnsignedInt: appleEventCode]; + + [_classDescriptions setObject: classDescription forKey: codeNumber]; +} + +- (void) registerCommandDescription: (NSScriptCommandDescription *)commandDescription +{ + FourCharCode appleEventCode; + FourCharCode appleEventClassCode; + NSString *key; + + if (commandDescription == nil) + { + return; + } + + appleEventCode = [commandDescription appleEventCode]; + appleEventClassCode = [commandDescription appleEventClassCode]; + + key = [NSString stringWithFormat: @"%u-%u", + (unsigned int)appleEventClassCode, + (unsigned int)appleEventCode]; + + [_commandDescriptions setObject: commandDescription forKey: key]; +} + +- (NSScriptClassDescription *) classDescriptionWithAppleEventCode: (FourCharCode)appleEventCode +{ + NSNumber *codeNumber; + + codeNumber = [NSNumber numberWithUnsignedInt: appleEventCode]; + return [_classDescriptions objectForKey: codeNumber]; +} + +- (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCharCode)appleEventClassCode + andAppleEventCode: (FourCharCode)appleEventCode +{ + NSString *key; + + key = [NSString stringWithFormat: @"%u-%u", + (unsigned int)appleEventClassCode, + (unsigned int)appleEventCode]; + + return [_commandDescriptions objectForKey: key]; +} + +- (NSArray *) suiteNames +{ + return [_suiteDescriptions allKeys]; +} + +- (FourCharCode) appleEventCodeForSuite: (NSString *)suiteName +{ + NSDictionary *suiteInfo; + NSNumber *codeNumber; + + if (suiteName == nil) + { + return 0; + } + + suiteInfo = [_suiteDescriptions objectForKey: suiteName]; + if (suiteInfo == nil) + { + return 0; + } + + codeNumber = [suiteInfo objectForKey: @"AppleEventCode"]; + if (codeNumber == nil) + { + return 0; + } + + return [codeNumber unsignedIntValue]; +} + +- (NSBundle *) bundleForSuite: (NSString *)suiteName +{ + NSDictionary *suiteInfo; + + if (suiteName == nil) + { + return nil; + } + + suiteInfo = [_suiteDescriptions objectForKey: suiteName]; + if (suiteInfo == nil) + { + return nil; + } + + return [suiteInfo objectForKey: @"Bundle"]; +} + @end From 35648cfd56a9c58ebeef649b97f785db53b27f86 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 16:06:10 -0500 Subject: [PATCH 02/20] Add tests for scripting --- Headers/Foundation/NSScriptObjectSpecifier.h | 276 +++ Source/NSScriptObjectSpecifier.m | 1543 +++++++++++++++++ Tests/base/NSScriptClassDescription/TestInfo | 0 Tests/base/NSScriptClassDescription/basic.m | 118 ++ Tests/base/NSScriptCoercionHandler/TestInfo | 0 Tests/base/NSScriptCoercionHandler/basic.m | 142 ++ Tests/base/NSScriptCommand/TestInfo | 0 Tests/base/NSScriptCommand/basic.m | 137 ++ .../base/NSScriptCommandDescription/TestInfo | 0 Tests/base/NSScriptCommandDescription/basic.m | 96 + Tests/base/NSScriptExecutionContext/TestInfo | 0 Tests/base/NSScriptExecutionContext/basic.m | 49 + Tests/base/NSScriptKeyValueCoding/TestInfo | 0 Tests/base/NSScriptKeyValueCoding/basic.m | 199 +++ 14 files changed, 2560 insertions(+) create mode 100644 Headers/Foundation/NSScriptObjectSpecifier.h create mode 100644 Source/NSScriptObjectSpecifier.m create mode 100644 Tests/base/NSScriptClassDescription/TestInfo create mode 100644 Tests/base/NSScriptClassDescription/basic.m create mode 100644 Tests/base/NSScriptCoercionHandler/TestInfo create mode 100644 Tests/base/NSScriptCoercionHandler/basic.m create mode 100644 Tests/base/NSScriptCommand/TestInfo create mode 100644 Tests/base/NSScriptCommand/basic.m create mode 100644 Tests/base/NSScriptCommandDescription/TestInfo create mode 100644 Tests/base/NSScriptCommandDescription/basic.m create mode 100644 Tests/base/NSScriptExecutionContext/TestInfo create mode 100644 Tests/base/NSScriptExecutionContext/basic.m create mode 100644 Tests/base/NSScriptKeyValueCoding/TestInfo create mode 100644 Tests/base/NSScriptKeyValueCoding/basic.m diff --git a/Headers/Foundation/NSScriptObjectSpecifier.h b/Headers/Foundation/NSScriptObjectSpecifier.h new file mode 100644 index 0000000000..c3c4a75ced --- /dev/null +++ b/Headers/Foundation/NSScriptObjectSpecifier.h @@ -0,0 +1,276 @@ +/**Definition of class NSScriptObjectSpecifier + Copyright (C) 2024 Free Software Foundation, Inc. + + By: Gregory John Casamento + Date: Dec 2024 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. +*/ + +#ifndef _NSScriptObjectSpecifier_h_GNUSTEP_BASE_INCLUDE +#define _NSScriptObjectSpecifier_h_GNUSTEP_BASE_INCLUDE + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +@class NSAppleEventDescriptor; +@class NSScriptClassDescription; +@class NSScriptWhoseTest; +@class NSString; + +typedef NS_ENUM(NSInteger, NSInsertionPosition) { + NSPositionAfter = 0, + NSPositionBefore, + NSPositionBeginning, + NSPositionEnd, + NSPositionReplace +}; + +typedef NS_ENUM(NSInteger, NSRelativePosition) { + NSRelativeBefore = 0, + NSRelativeAfter +}; + +typedef NS_ENUM(NSInteger, NSWhoseSubelementIdentifier) { + NSIndexSubelement = 0, + NSEverySubelement, + NSMiddleSubelement, + NSRandomSubelement, + NSNoSubelement +}; + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) + +GS_EXPORT_CLASS +@interface NSScriptObjectSpecifier : NSObject +{ + @private + NSScriptObjectSpecifier *_container; + NSString *_key; + NSScriptClassDescription *_classDescription; +} + +- (instancetype) initWithContainerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property; + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property; + +- (NSScriptObjectSpecifier *) containerSpecifier; +- (void) setContainerSpecifier: (NSScriptObjectSpecifier *)subRef; + +- (BOOL) containerIsObjectBeingTested; +- (void) setContainerIsObjectBeingTested: (BOOL)flag; + +- (BOOL) containerIsRangeContainerObject; +- (void) setContainerIsRangeContainerObject: (BOOL)flag; + +- (NSString *) key; +- (void) setKey: (NSString *)key; + +- (NSScriptClassDescription *) keyClassDescription; + +- (NSAppleEventDescriptor *) descriptor; + +- (id) objectsByEvaluatingSpecifier; + +- (NSAppleEventDescriptor *) descriptorAtIndex: (NSInteger)index; + +- (NSInteger) evaluationErrorNumber; +- (void) setEvaluationErrorNumber: (NSInteger)error; + +- (NSString *) evaluationErrorSpecifier; + +@end + +// Subclasses + +GS_EXPORT_CLASS +@interface NSIndexSpecifier : NSScriptObjectSpecifier +{ + @private + NSInteger _index; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + index: (NSInteger)index; + +- (NSInteger) index; +- (void) setIndex: (NSInteger)index; + +@end + +GS_EXPORT_CLASS +@interface NSMiddleSpecifier : NSScriptObjectSpecifier +@end + +GS_EXPORT_CLASS +@interface NSNameSpecifier : NSScriptObjectSpecifier +{ + @private + NSString *_name; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + name: (NSString *)name; + +- (NSString *) name; +- (void) setName: (NSString *)name; + +@end + +GS_EXPORT_CLASS +@interface NSPositionSpecifier : NSScriptObjectSpecifier +{ + @private + NSInsertionPosition _insertionPosition; + id _insertionObject; +} + +- (instancetype) initWithPosition: (NSInsertionPosition)position + objectSpecifier: (NSScriptObjectSpecifier *)specifier; + +- (NSInsertionPosition) insertionPosition; +- (void) setInsertionPosition: (NSInsertionPosition)position; + +- (NSScriptObjectSpecifier *) objectSpecifier; +- (void) setObjectSpecifier: (NSScriptObjectSpecifier *)objSpec; + +- (void) evaluate; +- (id) insertionContainer; +- (NSString *) insertionKey; +- (NSInteger) insertionIndex; + +@end + +GS_EXPORT_CLASS +@interface NSPropertySpecifier : NSScriptObjectSpecifier +@end + +GS_EXPORT_CLASS +@interface NSRandomSpecifier : NSScriptObjectSpecifier +@end + +GS_EXPORT_CLASS +@interface NSRangeSpecifier : NSScriptObjectSpecifier +{ + @private + NSScriptObjectSpecifier *_startSpec; + NSScriptObjectSpecifier *_endSpec; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + startSpecifier: (NSScriptObjectSpecifier *)startSpec + endSpecifier: (NSScriptObjectSpecifier *)endSpec; + +- (NSScriptObjectSpecifier *) startSpecifier; +- (void) setStartSpecifier: (NSScriptObjectSpecifier *)startSpec; + +- (NSScriptObjectSpecifier *) endSpecifier; +- (void) setEndSpecifier: (NSScriptObjectSpecifier *)endSpec; + +@end + +GS_EXPORT_CLASS +@interface NSRelativeSpecifier : NSScriptObjectSpecifier +{ + @private + NSRelativePosition _relativePosition; + NSScriptObjectSpecifier *_baseSpecifier; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + relativePosition: (NSRelativePosition)relPos + baseSpecifier: (NSScriptObjectSpecifier *)baseSpec; + +- (NSRelativePosition) relativePosition; +- (void) setRelativePosition: (NSRelativePosition)relPos; + +- (NSScriptObjectSpecifier *) baseSpecifier; +- (void) setBaseSpecifier: (NSScriptObjectSpecifier *)baseSpec; + +@end + +GS_EXPORT_CLASS +@interface NSUniqueIDSpecifier : NSScriptObjectSpecifier +{ + @private + id _uniqueID; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + uniqueID: (id)uniqueID; + +- (id) uniqueID; +- (void) setUniqueID: (id)uniqueID; + +@end + +GS_EXPORT_CLASS +@interface NSWhoseSpecifier : NSScriptObjectSpecifier +{ + @private + NSScriptWhoseTest *_test; + NSWhoseSubelementIdentifier _startSubelementIdentifier; + NSInteger _startSubelementIndex; + NSWhoseSubelementIdentifier _endSubelementIdentifier; + NSInteger _endSubelementIndex; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + test: (NSScriptWhoseTest *)test; + +- (NSScriptWhoseTest *) test; +- (void) setTest: (NSScriptWhoseTest *)test; + +- (NSWhoseSubelementIdentifier) startSubelementIdentifier; +- (void) setStartSubelementIdentifier: (NSWhoseSubelementIdentifier)subelement; + +- (NSInteger) startSubelementIndex; +- (void) setStartSubelementIndex: (NSInteger)index; + +- (NSWhoseSubelementIdentifier) endSubelementIdentifier; +- (void) setEndSubelementIdentifier: (NSWhoseSubelementIdentifier)subelement; + +- (NSInteger) endSubelementIndex; +- (void) setEndSubelementIndex: (NSInteger)index; + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* GS_API_MACOSX */ + +#endif /* _NSScriptObjectSpecifier_h_GNUSTEP_BASE_INCLUDE */ diff --git a/Source/NSScriptObjectSpecifier.m b/Source/NSScriptObjectSpecifier.m new file mode 100644 index 0000000000..9a47d20066 --- /dev/null +++ b/Source/NSScriptObjectSpecifier.m @@ -0,0 +1,1543 @@ +/* Implementation of class NSScriptObjectSpecifier + Copyright (C) 2024 Free Software Foundation, Inc. + + By: Gregory John Casamento + Date: Dec 2024 + + This file is part of the GNUstep Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 31 Milk Street #960789 Boston, MA 02196 USA. +*/ + +#import "common.h" +#import "Foundation/NSScriptObjectSpecifier.h" +#import "Foundation/NSAppleEventDescriptor.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSCoder.h" +#import "Foundation/NSException.h" +#import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptWhoseTests.h" +#import "Foundation/NSString.h" +#import "Foundation/NSValue.h" + +// NSScriptObjectSpecifier + +@implementation NSScriptObjectSpecifier + +- (instancetype) init +{ + return [self initWithContainerSpecifier: nil key: nil]; +} + +- (instancetype) initWithContainerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property +{ + self = [super init]; + if (self != nil) + { + ASSIGN(_container, container); + ASSIGN(_key, property); + _classDescription = nil; + } + return self; +} + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property +{ + self = [self initWithContainerSpecifier: container key: property]; + if (self != nil) + { + ASSIGN(_classDescription, classDesc); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_container); + RELEASE(_key); + RELEASE(_classDescription); + [super dealloc]; +} + +- (NSScriptObjectSpecifier *) containerSpecifier +{ + return _container; +} + +- (void) setContainerSpecifier: (NSScriptObjectSpecifier *)subRef +{ + ASSIGN(_container, subRef); +} + +- (BOOL) containerIsObjectBeingTested +{ + return NO; +} + +- (void) setContainerIsObjectBeingTested: (BOOL)flag +{ + // Stub for subclasses +} + +- (BOOL) containerIsRangeContainerObject +{ + return NO; +} + +- (void) setContainerIsRangeContainerObject: (BOOL)flag +{ + // Stub for subclasses +} + +- (NSString *) key +{ + return _key; +} + +- (void) setKey: (NSString *)key +{ + ASSIGN(_key, key); +} + +- (NSScriptClassDescription *) keyClassDescription +{ + return _classDescription; +} + +- (NSAppleEventDescriptor *) descriptor +{ + [NSException raise: NSInvalidArgumentException + format: @"NSScriptObjectSpecifier -descriptor is abstract"]; + return nil; +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + + if (_container == nil) + { + return nil; + } + + container = [_container objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if (_key == nil) + { + return container; + } + + return [container valueForKey: _key]; +} + +- (NSAppleEventDescriptor *) descriptorAtIndex: (NSInteger)index +{ + return nil; +} + +- (NSInteger) evaluationErrorNumber +{ + return 0; +} + +- (void) setEvaluationErrorNumber: (NSInteger)error +{ + // Stub +} + +- (NSString *) evaluationErrorSpecifier +{ + return nil; +} + +// NSCoding + +- (void) encodeWithCoder: (NSCoder *)coder +{ + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _container forKey: @"NSContainer"]; + [coder encodeObject: _key forKey: @"NSKey"]; + [coder encodeObject: _classDescription forKey: @"NSClassDescription"]; + } + else + { + [coder encodeObject: _container]; + [coder encodeObject: _key]; + [coder encodeObject: _classDescription]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super init]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + ASSIGN(_container, [coder decodeObjectForKey: @"NSContainer"]); + ASSIGN(_key, [coder decodeObjectForKey: @"NSKey"]); + ASSIGN(_classDescription, [coder decodeObjectForKey: @"NSClassDescription"]); + } + else + { + ASSIGN(_container, [coder decodeObject]); + ASSIGN(_key, [coder decodeObject]); + ASSIGN(_classDescription, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSIndexSpecifier + +@implementation NSIndexSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + index: (NSInteger)index +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + _index = index; + } + return self; +} + +- (NSInteger) index +{ + return _index; +} + +- (void) setIndex: (NSInteger)index +{ + _index = index; +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + if (_index < 0 || _index >= [array count]) + { + return nil; + } + + return [array objectAtIndex: _index]; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + // Add container descriptor + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + // Add key + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + // Add index + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithInt32: (int)_index] + forKeyword: 'seld']; + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeInteger: _index forKey: @"NSIndex"]; + } + else + { + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_index]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + _index = [coder decodeIntegerForKey: @"NSIndex"]; + } + else + { + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_index]; + } + } + return self; +} + +@end + +// NSMiddleSpecifier + +@implementation NSMiddleSpecifier + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + NSUInteger count; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + count = [array count]; + if (count == 0) + { + return nil; + } + + return [array objectAtIndex: count / 2]; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: 'midd'] + forKeyword: 'seld']; + + return desc; +} + +@end + +// NSNameSpecifier + +@implementation NSNameSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + name: (NSString *)name +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + ASSIGN(_name, name); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_name); + [super dealloc]; +} + +- (NSString *) name +{ + return _name; +} + +- (void) setName: (NSString *)name +{ + ASSIGN(_name, name); +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + id object; + NSEnumerator *enumerator; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + enumerator = [array objectEnumerator]; + while ((object = [enumerator nextObject]) != nil) + { + id objectName; + + objectName = [object valueForKey: @"name"]; + if (objectName != nil && [objectName isEqual: _name]) + { + return object; + } + } + + return nil; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + if (_name != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: _name] + forKeyword: 'seld']; + } + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _name forKey: @"NSName"]; + } + else + { + [coder encodeObject: _name]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + ASSIGN(_name, [coder decodeObjectForKey: @"NSName"]); + } + else + { + ASSIGN(_name, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSPositionSpecifier + +@implementation NSPositionSpecifier + +- (instancetype) initWithPosition: (NSInsertionPosition)position + objectSpecifier: (NSScriptObjectSpecifier *)specifier +{ + self = [super init]; + if (self != nil) + { + _insertionPosition = position; + ASSIGN(_insertionObject, specifier); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_insertionObject); + [super dealloc]; +} + +- (NSInsertionPosition) insertionPosition +{ + return _insertionPosition; +} + +- (void) setInsertionPosition: (NSInsertionPosition)position +{ + _insertionPosition = position; +} + +- (NSScriptObjectSpecifier *) objectSpecifier +{ + return _insertionObject; +} + +- (void) setObjectSpecifier: (NSScriptObjectSpecifier *)objSpec +{ + ASSIGN(_insertionObject, objSpec); +} + +- (void) evaluate +{ + // Stub +} + +- (id) insertionContainer +{ + if (_insertionObject != nil) + { + return [_insertionObject objectsByEvaluatingSpecifier]; + } + return nil; +} + +- (NSString *) insertionKey +{ + if (_insertionObject != nil) + { + return [_insertionObject key]; + } + return nil; +} + +- (NSInteger) insertionIndex +{ + id container; + NSArray *array; + NSInteger count; + + container = [self insertionContainer]; + if (container == nil) + { + return NSNotFound; + } + + if ([_insertionObject key] != nil) + { + array = [container valueForKey: [_insertionObject key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return NSNotFound; + } + + count = [array count]; + + switch (_insertionPosition) + { + case NSPositionBeginning: + return 0; + case NSPositionEnd: + return count; + case NSPositionBefore: + if ([_insertionObject isKindOfClass: [NSIndexSpecifier class]]) + { + return [(NSIndexSpecifier *)_insertionObject index]; + } + return NSNotFound; + case NSPositionAfter: + if ([_insertionObject isKindOfClass: [NSIndexSpecifier class]]) + { + return [(NSIndexSpecifier *)_insertionObject index] + 1; + } + return NSNotFound; + case NSPositionReplace: + if ([_insertionObject isKindOfClass: [NSIndexSpecifier class]]) + { + return [(NSIndexSpecifier *)_insertionObject index]; + } + return NSNotFound; + default: + return NSNotFound; + } +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *objDesc; + + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (_insertionObject != nil) + { + objDesc = [_insertionObject descriptor]; + if (objDesc != nil) + { + [desc setDescriptor: objDesc forKeyword: 'kobj']; + } + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithEnumCode: _insertionPosition] + forKeyword: 'kpos']; + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeInteger: _insertionPosition forKey: @"NSInsertionPosition"]; + [coder encodeObject: _insertionObject forKey: @"NSInsertionObject"]; + } + else + { + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_insertionPosition]; + [coder encodeObject: _insertionObject]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + _insertionPosition = [coder decodeIntegerForKey: @"NSInsertionPosition"]; + ASSIGN(_insertionObject, [coder decodeObjectForKey: @"NSInsertionObject"]); + } + else + { + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_insertionPosition]; + ASSIGN(_insertionObject, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSPropertySpecifier + +@implementation NSPropertySpecifier + +- (id) objectsByEvaluatingSpecifier +{ + id container; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + return [container valueForKey: [self key]]; + } + + return container; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'seld']; + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: 'prop'] + forKeyword: 'form']; + + return desc; +} + +@end + +// NSRandomSpecifier + +@implementation NSRandomSpecifier + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + NSUInteger count; + NSUInteger randomIndex; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + count = [array count]; + if (count == 0) + { + return nil; + } + + randomIndex = random() % count; + return [array objectAtIndex: randomIndex]; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: 'rang'] + forKeyword: 'seld']; + + return desc; +} + +@end + +// NSRangeSpecifier + +@implementation NSRangeSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + startSpecifier: (NSScriptObjectSpecifier *)startSpec + endSpecifier: (NSScriptObjectSpecifier *)endSpec +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + ASSIGN(_startSpec, startSpec); + ASSIGN(_endSpec, endSpec); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_startSpec); + RELEASE(_endSpec); + [super dealloc]; +} + +- (NSScriptObjectSpecifier *) startSpecifier +{ + return _startSpec; +} + +- (void) setStartSpecifier: (NSScriptObjectSpecifier *)startSpec +{ + ASSIGN(_startSpec, startSpec); +} + +- (NSScriptObjectSpecifier *) endSpecifier +{ + return _endSpec; +} + +- (void) setEndSpecifier: (NSScriptObjectSpecifier *)endSpec +{ + ASSIGN(_endSpec, endSpec); +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + NSInteger startIndex; + NSInteger endIndex; + NSRange range; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + startIndex = 0; + endIndex = [array count] - 1; + + if ([_startSpec isKindOfClass: [NSIndexSpecifier class]]) + { + startIndex = [(NSIndexSpecifier *)_startSpec index]; + } + + if ([_endSpec isKindOfClass: [NSIndexSpecifier class]]) + { + endIndex = [(NSIndexSpecifier *)_endSpec index]; + } + + if (startIndex < 0 || endIndex >= [array count] || startIndex > endIndex) + { + return nil; + } + + range = NSMakeRange(startIndex, endIndex - startIndex + 1); + return [array subarrayWithRange: range]; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + NSAppleEventDescriptor *rangeDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + rangeDesc = [NSAppleEventDescriptor listDescriptor]; + if (_startSpec != nil) + { + [rangeDesc insertDescriptor: [_startSpec descriptor] atIndex: 0]; + } + if (_endSpec != nil) + { + [rangeDesc insertDescriptor: [_endSpec descriptor] atIndex: 1]; + } + + [desc setDescriptor: rangeDesc forKeyword: 'seld']; + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _startSpec forKey: @"NSStartSpecifier"]; + [coder encodeObject: _endSpec forKey: @"NSEndSpecifier"]; + } + else + { + [coder encodeObject: _startSpec]; + [coder encodeObject: _endSpec]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + ASSIGN(_startSpec, [coder decodeObjectForKey: @"NSStartSpecifier"]); + ASSIGN(_endSpec, [coder decodeObjectForKey: @"NSEndSpecifier"]); + } + else + { + ASSIGN(_startSpec, [coder decodeObject]); + ASSIGN(_endSpec, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSRelativeSpecifier + +@implementation NSRelativeSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + relativePosition: (NSRelativePosition)relPos + baseSpecifier: (NSScriptObjectSpecifier *)baseSpec +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + _relativePosition = relPos; + ASSIGN(_baseSpecifier, baseSpec); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_baseSpecifier); + [super dealloc]; +} + +- (NSRelativePosition) relativePosition +{ + return _relativePosition; +} + +- (void) setRelativePosition: (NSRelativePosition)relPos +{ + _relativePosition = relPos; +} + +- (NSScriptObjectSpecifier *) baseSpecifier +{ + return _baseSpecifier; +} + +- (void) setBaseSpecifier: (NSScriptObjectSpecifier *)baseSpec +{ + ASSIGN(_baseSpecifier, baseSpec); +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + id baseObject; + NSInteger baseIndex; + NSInteger targetIndex; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + if (_baseSpecifier == nil) + { + return nil; + } + + baseObject = [_baseSpecifier objectsByEvaluatingSpecifier]; + if (baseObject == nil) + { + return nil; + } + + baseIndex = [array indexOfObject: baseObject]; + if (baseIndex == NSNotFound) + { + return nil; + } + + if (_relativePosition == NSRelativeBefore) + { + targetIndex = baseIndex - 1; + } + else + { + targetIndex = baseIndex + 1; + } + + if (targetIndex < 0 || targetIndex >= [array count]) + { + return nil; + } + + return [array objectAtIndex: targetIndex]; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + NSAppleEventDescriptor *baseDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithEnumCode: _relativePosition] + forKeyword: 'rele']; + + if (_baseSpecifier != nil) + { + baseDesc = [_baseSpecifier descriptor]; + if (baseDesc != nil) + { + [desc setDescriptor: baseDesc forKeyword: 'seld']; + } + } + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeInteger: _relativePosition forKey: @"NSRelativePosition"]; + [coder encodeObject: _baseSpecifier forKey: @"NSBaseSpecifier"]; + } + else + { + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_relativePosition]; + [coder encodeObject: _baseSpecifier]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + _relativePosition = [coder decodeIntegerForKey: @"NSRelativePosition"]; + ASSIGN(_baseSpecifier, [coder decodeObjectForKey: @"NSBaseSpecifier"]); + } + else + { + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_relativePosition]; + ASSIGN(_baseSpecifier, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSUniqueIDSpecifier + +@implementation NSUniqueIDSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + uniqueID: (id)uniqueID +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + ASSIGN(_uniqueID, uniqueID); + } + return self; +} + +- (void) dealloc +{ + RELEASE(_uniqueID); + [super dealloc]; +} + +- (id) uniqueID +{ + return _uniqueID; +} + +- (void) setUniqueID: (id)uniqueID +{ + ASSIGN(_uniqueID, uniqueID); +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + id object; + NSEnumerator *enumerator; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + enumerator = [array objectEnumerator]; + while ((object = [enumerator nextObject]) != nil) + { + id objectID; + + if ([object respondsToSelector: @selector(uniqueID)]) + { + objectID = [object performSelector: @selector(uniqueID)]; + } + else + { + objectID = [object valueForKey: @"uniqueID"]; + } + + if (objectID != nil && [objectID isEqual: _uniqueID]) + { + return object; + } + } + + return nil; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + if (_uniqueID != nil) + { + NSAppleEventDescriptor *idDesc; + + if ([_uniqueID isKindOfClass: [NSString class]]) + { + idDesc = [NSAppleEventDescriptor descriptorWithString: _uniqueID]; + } + else if ([_uniqueID isKindOfClass: [NSNumber class]]) + { + idDesc = [NSAppleEventDescriptor descriptorWithInt32: [_uniqueID intValue]]; + } + else + { + idDesc = nil; + } + + if (idDesc != nil) + { + [desc setDescriptor: idDesc forKeyword: 'seld']; + } + } + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _uniqueID forKey: @"NSUniqueID"]; + } + else + { + [coder encodeObject: _uniqueID]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + ASSIGN(_uniqueID, [coder decodeObjectForKey: @"NSUniqueID"]); + } + else + { + ASSIGN(_uniqueID, [coder decodeObject]); + } + } + return self; +} + +@end + +// NSWhoseSpecifier + +@implementation NSWhoseSpecifier + +- (instancetype) initWithContainerClassDescription: (NSScriptClassDescription *)classDesc + containerSpecifier: (NSScriptObjectSpecifier *)container + key: (NSString *)property + test: (NSScriptWhoseTest *)test +{ + self = [super initWithContainerClassDescription: classDesc + containerSpecifier: container + key: property]; + if (self != nil) + { + ASSIGN(_test, test); + _startSubelementIdentifier = NSNoSubelement; + _startSubelementIndex = 0; + _endSubelementIdentifier = NSNoSubelement; + _endSubelementIndex = 0; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_test); + [super dealloc]; +} + +- (NSScriptWhoseTest *) test +{ + return _test; +} + +- (void) setTest: (NSScriptWhoseTest *)test +{ + ASSIGN(_test, test); +} + +- (NSWhoseSubelementIdentifier) startSubelementIdentifier +{ + return _startSubelementIdentifier; +} + +- (void) setStartSubelementIdentifier: (NSWhoseSubelementIdentifier)subelement +{ + _startSubelementIdentifier = subelement; +} + +- (NSInteger) startSubelementIndex +{ + return _startSubelementIndex; +} + +- (void) setStartSubelementIndex: (NSInteger)index +{ + _startSubelementIndex = index; +} + +- (NSWhoseSubelementIdentifier) endSubelementIdentifier +{ + return _endSubelementIdentifier; +} + +- (void) setEndSubelementIdentifier: (NSWhoseSubelementIdentifier)subelement +{ + _endSubelementIdentifier = subelement; +} + +- (NSInteger) endSubelementIndex +{ + return _endSubelementIndex; +} + +- (void) setEndSubelementIndex: (NSInteger)index +{ + _endSubelementIndex = index; +} + +- (id) objectsByEvaluatingSpecifier +{ + id container; + NSArray *array; + NSMutableArray *result; + id object; + NSEnumerator *enumerator; + + container = [[self containerSpecifier] objectsByEvaluatingSpecifier]; + if (container == nil) + { + return nil; + } + + if ([self key] != nil) + { + array = [container valueForKey: [self key]]; + } + else + { + array = container; + } + + if (![array isKindOfClass: [NSArray class]]) + { + return nil; + } + + if (_test == nil) + { + return array; + } + + result = [NSMutableArray arrayWithCapacity: [array count]]; + enumerator = [array objectEnumerator]; + + while ((object = [enumerator nextObject]) != nil) + { + if ([_test isTrue]) + { + [result addObject: object]; + } + } + + return result; +} + +- (NSAppleEventDescriptor *) descriptor +{ + NSAppleEventDescriptor *desc; + NSAppleEventDescriptor *containerDesc; + + containerDesc = [[self containerSpecifier] descriptor]; + desc = [NSAppleEventDescriptor recordDescriptor]; + + if (containerDesc != nil) + { + [desc setDescriptor: containerDesc forKeyword: 'from']; + } + + if ([self key] != nil) + { + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithString: [self key]] + forKeyword: 'form']; + } + + [desc setDescriptor: [NSAppleEventDescriptor descriptorWithTypeCode: 'whos'] + forKeyword: 'form']; + + return desc; +} + +- (void) encodeWithCoder: (NSCoder *)coder +{ + [super encodeWithCoder: coder]; + if ([coder allowsKeyedCoding]) + { + [coder encodeObject: _test forKey: @"NSTest"]; + [coder encodeInteger: _startSubelementIdentifier forKey: @"NSStartSubelementIdentifier"]; + [coder encodeInteger: _startSubelementIndex forKey: @"NSStartSubelementIndex"]; + [coder encodeInteger: _endSubelementIdentifier forKey: @"NSEndSubelementIdentifier"]; + [coder encodeInteger: _endSubelementIndex forKey: @"NSEndSubelementIndex"]; + } + else + { + [coder encodeObject: _test]; + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_startSubelementIdentifier]; + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_startSubelementIndex]; + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_endSubelementIdentifier]; + [coder encodeValueOfObjCType: @encode(NSInteger) at: &_endSubelementIndex]; + } +} + +- (instancetype) initWithCoder: (NSCoder *)coder +{ + self = [super initWithCoder: coder]; + if (self != nil) + { + if ([coder allowsKeyedCoding]) + { + ASSIGN(_test, [coder decodeObjectForKey: @"NSTest"]); + _startSubelementIdentifier = [coder decodeIntegerForKey: @"NSStartSubelementIdentifier"]; + _startSubelementIndex = [coder decodeIntegerForKey: @"NSStartSubelementIndex"]; + _endSubelementIdentifier = [coder decodeIntegerForKey: @"NSEndSubelementIdentifier"]; + _endSubelementIndex = [coder decodeIntegerForKey: @"NSEndSubelementIndex"]; + } + else + { + ASSIGN(_test, [coder decodeObject]); + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_startSubelementIdentifier]; + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_startSubelementIndex]; + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_endSubelementIdentifier]; + [coder decodeValueOfObjCType: @encode(NSInteger) at: &_endSubelementIndex]; + } + } + return self; +} + +@end diff --git a/Tests/base/NSScriptClassDescription/TestInfo b/Tests/base/NSScriptClassDescription/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptClassDescription/basic.m b/Tests/base/NSScriptClassDescription/basic.m new file mode 100644 index 0000000000..24be10b6fd --- /dev/null +++ b/Tests/base/NSScriptClassDescription/basic.m @@ -0,0 +1,118 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import + +@interface TestScriptableObject : NSObject +@end + +@implementation TestScriptableObject +@end + +int main() +{ + NSScriptClassDescription *desc; + NSScriptClassDescription *superDesc; + NSString *suiteName; + NSString *className; + FourCharCode appleEventCode; + Class implClass; + + START_SET("NSScriptClassDescription initialization"); + + // Test basic initialization + suiteName = @"TestSuite"; + className = @"TestScriptableObject"; + appleEventCode = 'TST1'; + + desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: suiteName + className: className + appleEventCode: appleEventCode + superclass: nil]); + PASS(desc != nil, "Can create NSScriptClassDescription instance"); + + END_SET("NSScriptClassDescription initialization"); + + START_SET("NSScriptClassDescription properties"); + + // Test suite name + PASS([[desc suiteName] isEqual: suiteName], "Suite name property works"); + + // Test class name + PASS([[desc className] isEqual: className], "Class name property works"); + + // Test apple event code + PASS([desc appleEventCode] == appleEventCode, "Apple event code property works"); + + // Test implementation class + implClass = [desc implementationClass]; + PASS(implClass == [TestScriptableObject class], + "Implementation class lookup works"); + + END_SET("NSScriptClassDescription properties"); + + START_SET("NSScriptClassDescription hierarchy"); + + // Test superclass description + superDesc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: @"BaseSuite" + className: @"NSObject" + appleEventCode: 'BASE' + superclass: nil]); + + desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: @"DerivedSuite" + className: @"TestScriptableObject" + appleEventCode: 'DRV1' + superclass: superDesc]); + + PASS(desc != nil, "Can create derived class description"); + PASS([desc superclassDescription] != nil, "Superclass description is set"); + + END_SET("NSScriptClassDescription hierarchy"); + + START_SET("NSScriptClassDescription commands"); + + desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: suiteName + className: className + appleEventCode: appleEventCode + superclass: nil]); + + // Test command description lookup + NSScriptCommandDescription *cmdDesc; + cmdDesc = [desc commandDescriptionWithAppleEventClass: 'core' + andAppleEventCode: 'clon']; + PASS(cmdDesc == nil, "Command description lookup returns nil for unregistered commands"); + + // Test command support + PASS([desc supportsCommand: nil] == NO, + "supportsCommand: returns NO for nil command"); + + END_SET("NSScriptClassDescription commands"); + + START_SET("NSScriptClassDescription keys"); + + // Test type for key + NSString *keyType = [desc typeForKey: @"someProperty"]; + PASS(keyType == nil, "typeForKey: returns nil for stub implementation"); + + // Test location required + PASS([desc isLocationRequiredToCreateForKey: @"items"] == NO, + "isLocationRequiredToCreateForKey: returns NO by default"); + + END_SET("NSScriptClassDescription keys"); + + START_SET("NSScriptClassDescription class methods"); + + // Register the description + [NSScriptClassDescription registerClassDescription: desc + forClass: [TestScriptableObject class]]; + + // Test class description lookup + NSScriptClassDescription *foundDesc; + foundDesc = [NSScriptClassDescription classDescriptionForClass: [TestScriptableObject class]]; + PASS(foundDesc == desc, "classDescriptionForClass: finds registered description"); + + END_SET("NSScriptClassDescription class methods"); + + return 0; +} diff --git a/Tests/base/NSScriptCoercionHandler/TestInfo b/Tests/base/NSScriptCoercionHandler/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptCoercionHandler/basic.m b/Tests/base/NSScriptCoercionHandler/basic.m new file mode 100644 index 0000000000..79877b913c --- /dev/null +++ b/Tests/base/NSScriptCoercionHandler/basic.m @@ -0,0 +1,142 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import +#import + +@interface TestCoercer : NSObject +@end + +@implementation TestCoercer + +- (NSString *) numberToString: (NSNumber *)number +{ + return [number stringValue]; +} + +- (NSNumber *) stringToNumber: (NSString *)string +{ + return [NSNumber numberWithInteger: [string integerValue]]; +} + +@end + +int main() +{ + NSScriptCoercionHandler *handler; + TestCoercer *coercer; + NSNumber *num; + NSString *str; + id result; + + START_SET("NSScriptCoercionHandler singleton"); + + // Test shared instance + handler = [NSScriptCoercionHandler sharedCoercionHandler]; + PASS(handler != nil, "sharedCoercionHandler returns instance"); + + // Test singleton property + PASS([NSScriptCoercionHandler sharedCoercionHandler] == handler, + "sharedCoercionHandler returns same instance"); + + END_SET("NSScriptCoercionHandler singleton"); + + START_SET("NSScriptCoercionHandler coercion"); + + coercer = AUTORELEASE([[TestCoercer alloc] init]); + + // Test without registered coercer + num = [NSNumber numberWithInt: 42]; + result = [handler coerceValue: num toClass: [NSString class]]; + PASS([result isKindOfClass: [NSNumber class]], + "coerceValue without registered coercer returns original value"); + + // Register coercer + [handler registerCoercer: coercer + selector: @selector(numberToString:) + toConvertFromClass: [NSNumber class] + toClass: [NSString class]]; + + // Test with registered coercer + result = [handler coerceValue: num toClass: [NSString class]]; + PASS([result isKindOfClass: [NSString class]], + "coerceValue with registered coercer returns converted value"); + PASS([result isEqual: @"42"], + "Coerced value is correct"); + + END_SET("NSScriptCoercionHandler coercion"); + + START_SET("NSScriptCoercionHandler bidirectional"); + + // Register reverse coercer + [handler registerCoercer: coercer + selector: @selector(stringToNumber:) + toConvertFromClass: [NSString class] + toClass: [NSNumber class]]; + + str = @"123"; + result = [handler coerceValue: str toClass: [NSNumber class]]; + PASS([result isKindOfClass: [NSNumber class]], + "Reverse coercion works"); + PASS([result integerValue] == 123, + "Reverse coerced value is correct"); + + END_SET("NSScriptCoercionHandler bidirectional"); + + START_SET("NSScriptCoercionHandler edge cases"); + + // Test nil value + result = [handler coerceValue: nil toClass: [NSString class]]; + PASS(result == nil, "coerceValue with nil returns nil"); + + // Test already correct type + str = @"test"; + result = [handler coerceValue: str toClass: [NSString class]]; + PASS(result == str, "coerceValue with matching type returns original"); + + // Test invalid registration + [handler registerCoercer: nil + selector: @selector(someMethod:) + toConvertFromClass: [NSNumber class] + toClass: [NSString class]]; + PASS(YES, "registerCoercer with nil coercer doesn't crash"); + + [handler registerCoercer: coercer + selector: NULL + toConvertFromClass: [NSNumber class] + toClass: [NSString class]]; + PASS(YES, "registerCoercer with NULL selector doesn't crash"); + + [handler registerCoercer: coercer + selector: @selector(someMethod:) + toConvertFromClass: Nil + toClass: [NSString class]]; + PASS(YES, "registerCoercer with Nil fromClass doesn't crash"); + + [handler registerCoercer: coercer + selector: @selector(someMethod:) + toConvertFromClass: [NSNumber class] + toClass: Nil]; + PASS(YES, "registerCoercer with Nil toClass doesn't crash"); + + END_SET("NSScriptCoercionHandler edge cases"); + + START_SET("NSScriptCoercionHandler multiple coercers"); + + // Register another coercer for different type pair + [handler registerCoercer: coercer + selector: @selector(numberToString:) + toConvertFromClass: [NSValue class] + toClass: [NSString class]]; + + // Test that original coercion still works + num = [NSNumber numberWithInt: 99]; + result = [handler coerceValue: num toClass: [NSString class]]; + PASS([result isEqual: @"99"], + "Original coercion still works after registering another"); + + END_SET("NSScriptCoercionHandler multiple coercers"); + + return 0; +} diff --git a/Tests/base/NSScriptCommand/TestInfo b/Tests/base/NSScriptCommand/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptCommand/basic.m b/Tests/base/NSScriptCommand/basic.m new file mode 100644 index 0000000000..c42e625fa0 --- /dev/null +++ b/Tests/base/NSScriptCommand/basic.m @@ -0,0 +1,137 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import +#import +#import + +@interface TestCommand : NSScriptCommand +@end + +@implementation TestCommand + +- (id) performDefaultImplementation +{ + return @"TestResult"; +} + +@end + +int main() +{ + NSScriptCommand *command; + NSScriptCommandDescription *commandDesc; + NSDictionary *args; + NSString *result; + + START_SET("NSScriptCommand initialization"); + + // Test initialization with nil + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + PASS(command != nil, "Can create NSScriptCommand with nil description"); + PASS([command commandDescription] == nil, "Command description is nil"); + PASS(![command isWellFormed], "Command with nil description is not well-formed"); + + END_SET("NSScriptCommand initialization"); + + START_SET("NSScriptCommand arguments"); + + commandDesc = nil; + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: commandDesc]); + + // Test arguments + PASS([command arguments] != nil, "Initial arguments is non-nil"); + + args = [NSDictionary dictionaryWithObjectsAndKeys: + @"value1", @"key1", + @"value2", @"key2", + nil]; + [command setArguments: args]; + PASS([[command arguments] isEqual: args], "setArguments: works"); + + END_SET("NSScriptCommand arguments"); + + START_SET("NSScriptCommand evaluated arguments"); + + // Test evaluated arguments + NSDictionary *evaluated = [command evaluatedArguments]; + PASS(evaluated != nil, "evaluatedArguments returns non-nil"); + PASS([[evaluated objectForKey: @"key1"] isEqual: @"value1"], + "Evaluated arguments contain correct values"); + + // Test caching + PASS([command evaluatedArguments] == evaluated, + "evaluatedArguments are cached"); + + // Test cache invalidation + [command setArguments: [NSDictionary dictionary]]; + PASS([command evaluatedArguments] != evaluated, + "Setting new arguments invalidates cache"); + + END_SET("NSScriptCommand evaluated arguments"); + + START_SET("NSScriptCommand direct parameter"); + + // Test direct parameter + PASS([command directParameter] == nil, "Initial direct parameter is nil"); + + // Note: Can't test with real NSScriptObjectSpecifier without full implementation + [command setDirectParameter: nil]; + PASS([command directParameter] == nil, "setDirectParameter: with nil works"); + + END_SET("NSScriptCommand direct parameter"); + + START_SET("NSScriptCommand receivers"); + + // Test receivers specifier + PASS([command receiversSpecifier] == nil, "Initial receivers specifier is nil"); + + [command setReceiversSpecifier: nil]; + PASS([command receiversSpecifier] == nil, "setReceiversSpecifier: with nil works"); + + // Test evaluated receivers + PASS([command evaluatedReceivers] == nil, + "evaluatedReceivers returns nil when no specifier"); + + END_SET("NSScriptCommand receivers"); + + START_SET("NSScriptCommand execution"); + + // Test execution with custom subclass + command = AUTORELEASE([[TestCommand alloc] initWithCommandDescription: nil]); + result = [command executeCommand]; + PASS(result == nil, "executeCommand returns nil for non-well-formed command"); + + END_SET("NSScriptCommand execution"); + + START_SET("NSScriptCommand suspension"); + + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + + [command suspendExecution]; + PASS(YES, "suspendExecution doesn't crash"); + + [command resumeExecutionWithResult: @"result"]; + PASS(YES, "resumeExecutionWithResult: doesn't crash"); + + END_SET("NSScriptCommand suspension"); + + START_SET("NSScriptCommand current command"); + + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + PASS([command currentCommand] == command, + "currentCommand returns self"); + + END_SET("NSScriptCommand current command"); + + START_SET("NSScriptCommand apple event"); + + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + PASS([command appleEvent] == nil, + "appleEvent returns nil by default"); + + END_SET("NSScriptCommand apple event"); + + return 0; +} diff --git a/Tests/base/NSScriptCommandDescription/TestInfo b/Tests/base/NSScriptCommandDescription/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptCommandDescription/basic.m b/Tests/base/NSScriptCommandDescription/basic.m new file mode 100644 index 0000000000..583ddc5b50 --- /dev/null +++ b/Tests/base/NSScriptCommandDescription/basic.m @@ -0,0 +1,96 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import + +int main() +{ + NSScriptCommandDescription *desc; + NSString *suiteName; + NSString *commandName; + FourCharCode eventCode; + FourCharCode classCode; + + START_SET("NSScriptCommandDescription initialization"); + + suiteName = @"TestSuite"; + commandName = @"TestCommand"; + eventCode = 'TEST'; + classCode = 'TCMD'; + + desc = AUTORELEASE([[NSScriptCommandDescription alloc] initWithSuiteName: suiteName + commandName: commandName + appleEventCode: eventCode + appleEventClassCode: classCode]); + PASS(desc != nil, "Can create NSScriptCommandDescription instance"); + + END_SET("NSScriptCommandDescription initialization"); + + START_SET("NSScriptCommandDescription properties"); + + // Test suite name + PASS([[desc suiteName] isEqual: suiteName], "Suite name property works"); + + // Test command name + PASS([[desc commandName] isEqual: commandName], "Command name property works"); + + // Test apple event code + PASS([desc appleEventCode] == eventCode, "Apple event code property works"); + + // Test apple event class code + PASS([desc appleEventClassCode] == classCode, "Apple event class code property works"); + + END_SET("NSScriptCommandDescription properties"); + + START_SET("NSScriptCommandDescription return type"); + + // Test return type (should be nil by default) + PASS([desc returnType] == nil, "Return type is nil by default"); + + // Test return apple event code (should be 0 by default) + PASS([desc returnAppleEventCode] == 0, "Return apple event code is 0 by default"); + + END_SET("NSScriptCommandDescription return type"); + + START_SET("NSScriptCommandDescription arguments"); + + // Test argument names + NSArray *argNames = [desc argumentNames]; + PASS(argNames != nil, "argumentNames returns non-nil"); + PASS([argNames count] == 0, "argumentNames initially empty"); + + // Test type for non-existent argument + NSString *argType = [desc typeForArgumentWithName: @"nonExistent"]; + PASS(argType == nil, "typeForArgumentWithName: returns nil for non-existent argument"); + + // Test apple event code for non-existent argument + FourCharCode argCode = [desc appleEventCodeForArgumentWithName: @"nonExistent"]; + PASS(argCode == 0, "appleEventCodeForArgumentWithName: returns 0 for non-existent argument"); + + // Test optional check for non-existent argument + PASS(![desc isOptionalArgumentWithName: @"nonExistent"], + "isOptionalArgumentWithName: returns NO for non-existent argument"); + + END_SET("NSScriptCommandDescription arguments"); + + START_SET("NSScriptCommandDescription multiple instances"); + + NSScriptCommandDescription *desc2; + desc2 = AUTORELEASE([[NSScriptCommandDescription alloc] initWithSuiteName: @"Suite2" + commandName: @"Command2" + appleEventCode: 'TST2' + appleEventClassCode: 'CMD2']); + + PASS(desc2 != nil, "Can create second instance"); + PASS(![[desc2 suiteName] isEqual: [desc suiteName]], + "Second instance has different suite name"); + PASS(![[desc2 commandName] isEqual: [desc commandName]], + "Second instance has different command name"); + PASS([desc2 appleEventCode] != [desc appleEventCode], + "Second instance has different event code"); + + END_SET("NSScriptCommandDescription multiple instances"); + + return 0; +} diff --git a/Tests/base/NSScriptExecutionContext/TestInfo b/Tests/base/NSScriptExecutionContext/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptExecutionContext/basic.m b/Tests/base/NSScriptExecutionContext/basic.m new file mode 100644 index 0000000000..1abff03af3 --- /dev/null +++ b/Tests/base/NSScriptExecutionContext/basic.m @@ -0,0 +1,49 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import + +int main() +{ + NSScriptExecutionContext *context; + NSScriptCommand *command; + + START_SET("NSScriptExecutionContext singleton"); + + // Test shared instance + context = [NSScriptExecutionContext sharedScriptExecutionContext]; + PASS(context != nil, "sharedScriptExecutionContext returns instance"); + + // Test singleton property + PASS([NSScriptExecutionContext sharedScriptExecutionContext] == context, + "sharedScriptExecutionContext returns same instance"); + + END_SET("NSScriptExecutionContext singleton"); + + START_SET("NSScriptExecutionContext top level object"); + + // Test initial top level object + PASS([context topLevelObject] == nil, "Initial top level object is nil"); + + // Create a test command + command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + + // Set top level object + [context setTopLevelObject: command]; + PASS([context topLevelObject] == command, "setTopLevelObject: works"); + + // Change top level object + NSScriptCommand *command2; + command2 = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + [context setTopLevelObject: command2]; + PASS([context topLevelObject] == command2, "Can change top level object"); + + // Set to nil + [context setTopLevelObject: nil]; + PASS([context topLevelObject] == nil, "Can set top level object to nil"); + + END_SET("NSScriptExecutionContext top level object"); + + return 0; +} diff --git a/Tests/base/NSScriptKeyValueCoding/TestInfo b/Tests/base/NSScriptKeyValueCoding/TestInfo new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Tests/base/NSScriptKeyValueCoding/basic.m b/Tests/base/NSScriptKeyValueCoding/basic.m new file mode 100644 index 0000000000..0cb80a2dc2 --- /dev/null +++ b/Tests/base/NSScriptKeyValueCoding/basic.m @@ -0,0 +1,199 @@ +#import "ObjectTesting.h" +#import +#import +#import +#import + +@interface TestItem : NSObject +{ + NSString *_name; + NSString *_uniqueID; +} +- (id) initWithName: (NSString *)name uniqueID: (NSString *)uniqueID; +- (NSString *) name; +- (NSString *) uniqueID; +@end + +@implementation TestItem + +- (id) initWithName: (NSString *)name uniqueID: (NSString *)uniqueID +{ + if ((self = [super init])) + { + _name = [name copy]; + _uniqueID = [uniqueID copy]; + } + return self; +} + +- (void) dealloc +{ + [_name release]; + [_uniqueID release]; + [super dealloc]; +} + +- (NSString *) name +{ + return _name; +} + +- (NSString *) uniqueID +{ + return _uniqueID; +} + +@end + +@interface TestContainer : NSObject +{ + NSMutableArray *_items; +} +- (id) init; +- (NSArray *) items; +- (void) setItems: (NSArray *)items; +@end + +@implementation TestContainer + +- (id) init +{ + if ((self = [super init])) + { + _items = [[NSMutableArray alloc] init]; + } + return self; +} + +- (void) dealloc +{ + [_items release]; + [super dealloc]; +} + +- (NSArray *) items +{ + return _items; +} + +- (void) setItems: (NSArray *)items +{ + [_items setArray: items]; +} + +@end + +int main() +{ + TestContainer *container; + TestItem *item1; + TestItem *item2; + TestItem *item3; + id result; + + START_SET("NSScriptKeyValueCoding value access"); + + container = AUTORELEASE([[TestContainer alloc] init]); + item1 = AUTORELEASE([[TestItem alloc] initWithName: @"First" uniqueID: @"1"]); + item2 = AUTORELEASE([[TestItem alloc] initWithName: @"Second" uniqueID: @"2"]); + item3 = AUTORELEASE([[TestItem alloc] initWithName: @"Third" uniqueID: @"3"]); + + [container setItems: [NSArray arrayWithObjects: item1, item2, item3, nil]]; + + // Test valueAtIndex:inPropertyWithKey: + result = [container valueAtIndex: 0 inPropertyWithKey: @"items"]; + PASS(result == item1, "valueAtIndex:inPropertyWithKey: returns correct object"); + + result = [container valueAtIndex: 1 inPropertyWithKey: @"items"]; + PASS(result == item2, "valueAtIndex: with index 1 works"); + + result = [container valueAtIndex: 10 inPropertyWithKey: @"items"]; + PASS(result == nil, "valueAtIndex: with out-of-bounds index returns nil"); + + END_SET("NSScriptKeyValueCoding value access"); + + START_SET("NSScriptKeyValueCoding valueWithName"); + + // Test valueWithName:inPropertyWithKey: + result = [container valueWithName: @"First" inPropertyWithKey: @"items"]; + PASS(result == item1, "valueWithName:inPropertyWithKey: finds object by name"); + + result = [container valueWithName: @"Second" inPropertyWithKey: @"items"]; + PASS(result == item2, "valueWithName: finds second item"); + + result = [container valueWithName: @"NotFound" inPropertyWithKey: @"items"]; + PASS(result == nil, "valueWithName: returns nil for non-existent name"); + + END_SET("NSScriptKeyValueCoding valueWithName"); + + START_SET("NSScriptKeyValueCoding valueWithUniqueID"); + + // Test valueWithUniqueID:inPropertyWithKey: + result = [container valueWithUniqueID: @"1" inPropertyWithKey: @"items"]; + PASS(result == item1, "valueWithUniqueID:inPropertyWithKey: finds object by ID"); + + result = [container valueWithUniqueID: @"3" inPropertyWithKey: @"items"]; + PASS(result == item3, "valueWithUniqueID: finds third item"); + + result = [container valueWithUniqueID: @"999" inPropertyWithKey: @"items"]; + PASS(result == nil, "valueWithUniqueID: returns nil for non-existent ID"); + + END_SET("NSScriptKeyValueCoding valueWithUniqueID"); + + START_SET("NSScriptKeyValueCoding insertion"); + + TestItem *newItem; + newItem = AUTORELEASE([[TestItem alloc] initWithName: @"Inserted" uniqueID: @"4"]); + + // Test insertValue:atIndex:inPropertyWithKey: + [container insertValue: newItem atIndex: 1 inPropertyWithKey: @"items"]; + PASS([[container items] count] == 4, "insertValue:atIndex:inPropertyWithKey: increases count"); + PASS([container valueAtIndex: 1 inPropertyWithKey: @"items"] == newItem, + "Inserted item is at correct index"); + + // Test insertValue:inPropertyWithKey: (append) + TestItem *appendItem; + appendItem = AUTORELEASE([[TestItem alloc] initWithName: @"Appended" uniqueID: @"5"]); + [container insertValue: appendItem inPropertyWithKey: @"items"]; + PASS([[container items] count] == 5, "insertValue:inPropertyWithKey: appends item"); + PASS([[container items] lastObject] == appendItem, "Appended item is at end"); + + END_SET("NSScriptKeyValueCoding insertion"); + + START_SET("NSScriptKeyValueCoding removal"); + + NSUInteger countBefore; + countBefore = [[container items] count]; + + // Test removeValueAtIndex:fromPropertyWithKey: + [container removeValueAtIndex: 1 fromPropertyWithKey: @"items"]; + PASS([[container items] count] == countBefore - 1, + "removeValueAtIndex:fromPropertyWithKey: decreases count"); + + END_SET("NSScriptKeyValueCoding removal"); + + START_SET("NSScriptKeyValueCoding replacement"); + + TestItem *replacementItem; + replacementItem = AUTORELEASE([[TestItem alloc] initWithName: @"Replacement" uniqueID: @"6"]); + + // Test replaceValueAtIndex:inPropertyWithKey:withValue: + [container replaceValueAtIndex: 0 + inPropertyWithKey: @"items" + withValue: replacementItem]; + PASS([container valueAtIndex: 0 inPropertyWithKey: @"items"] == replacementItem, + "replaceValueAtIndex:inPropertyWithKey:withValue: replaces object"); + + END_SET("NSScriptKeyValueCoding replacement"); + + START_SET("NSScriptKeyValueCoding coercion"); + + // Test coerceValue:forKey: + id coercedValue = [container coerceValue: @"test" forKey: @"items"]; + PASS([coercedValue isEqual: @"test"], + "coerceValue:forKey: returns value unchanged by default"); + + END_SET("NSScriptKeyValueCoding coercion"); + + return 0; +} From 26619657dea78369a08d0e0605a9693a718ca8ef Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 16:32:21 -0500 Subject: [PATCH 03/20] Clean compile for scripting classes --- Headers/Foundation/NSAppleEventDescriptor.h | 123 ++++- Headers/Foundation/NSAppleEventManager.h | 35 ++ Source/NSAppleEventDescriptor.m | 518 +++++++++++++++++++- Source/NSAppleEventManager.m | 272 +++++++++- Source/NSScriptCommand.m | 23 + Source/NSScriptObjectSpecifier.m | 7 + Source/common.h | 8 +- 7 files changed, 980 insertions(+), 6 deletions(-) diff --git a/Headers/Foundation/NSAppleEventDescriptor.h b/Headers/Foundation/NSAppleEventDescriptor.h index 793540bf7b..f28bd9b805 100644 --- a/Headers/Foundation/NSAppleEventDescriptor.h +++ b/Headers/Foundation/NSAppleEventDescriptor.h @@ -32,8 +32,129 @@ extern "C" { #endif +@class NSData; +@class NSString; +@class NSMutableArray; +@class NSMutableDictionary; + +typedef uint32_t AEKeyword; +typedef int32_t DescType; + +enum { + typeBoolean = 'bool', + typeChar = 'TEXT', + typeSInt16 = 'shor', + typeSInt32 = 'long', + typeUInt32 = 'magn', + typeSInt64 = 'comp', + typeIEEE32BitFloatingPoint = 'sing', + typeIEEE64BitFloatingPoint = 'doub', + type128BitFloatingPoint = 'ldbl', + typeDecimalStruct = 'decm', + typeAEList = 'list', + typeAERecord = 'reco', + typeAppleEvent = 'aevt', + typeEventRecord = 'evrc', + typeTrue = 'true', + typeFalse = 'fals', + typeAlias = 'alis', + typeEnumerated = 'enum', + typeType = 'type', + typeAppParameters = 'appa', + typeProperty = 'prop', + typeFSS = 'fss ', + typeFSRef = 'fsrf', + typeFileURL = 'furl', + typeKeyword = 'keyw', + typeSectionH = 'sect', + typeWildCard = '****', + typeApplSignature = 'sign', + typeQDRectangle = 'qdrt', + typeFixed = 'fixd', + typeProcessSerialNumber = 'psn ', + typeApplicationURL = 'aprl', + typeNull = 'null' +}; + GS_EXPORT_CLASS -@interface NSAppleEventDescriptor : NSObject +@interface NSAppleEventDescriptor : NSObject +{ + @private + DescType _descriptorType; + NSData *_data; + int _internalType; + + NSMutableArray *_listItems; + NSMutableDictionary *_recordKeywords; + NSMutableArray *_recordDescriptors; + + uint32_t _eventClass; + uint32_t _eventID; + int16_t _returnID; + int32_t _transactionID; + NSMutableDictionary *_parameters; + NSMutableDictionary *_attributes; +} + +// Creating descriptors ++ (NSAppleEventDescriptor *) descriptorWithBoolean: (BOOL)boolean; ++ (NSAppleEventDescriptor *) descriptorWithDescriptorType: (DescType)descriptorType + bytes: (const void *)bytes + length: (NSUInteger)byteCount; ++ (NSAppleEventDescriptor *) descriptorWithDescriptorType: (DescType)descriptorType + data: (NSData *)data; ++ (NSAppleEventDescriptor *) descriptorWithEnumCode: (uint32_t)enumerator; ++ (NSAppleEventDescriptor *) descriptorWithInt32: (int32_t)signedInt; ++ (NSAppleEventDescriptor *) descriptorWithString: (NSString *)string; ++ (NSAppleEventDescriptor *) descriptorWithTypeCode: (uint32_t)typeCode; ++ (NSAppleEventDescriptor *) nullDescriptor; + +// List and record descriptors ++ (NSAppleEventDescriptor *) listDescriptor; ++ (NSAppleEventDescriptor *) recordDescriptor; + +// Apple event descriptors ++ (NSAppleEventDescriptor *) appleEventWithEventClass: (uint32_t)eventClass + eventID: (uint32_t)eventID + targetDescriptor: (NSAppleEventDescriptor *)targetDescriptor + returnID: (int16_t)returnID + transactionID: (int32_t)transactionID; + +// Accessing descriptor data +- (DescType) descriptorType; +- (NSData *) data; +- (BOOL) booleanValue; +- (int32_t) int32Value; +- (NSString *) stringValue; +- (uint32_t) typeCodeValue; +- (uint32_t) enumCodeValue; + +// Working with list descriptors +- (NSInteger) numberOfItems; +- (void) insertDescriptor: (NSAppleEventDescriptor *)descriptor + atIndex: (NSInteger)index; +- (NSAppleEventDescriptor *) descriptorAtIndex: (NSInteger)index; +- (void) removeDescriptorAtIndex: (NSInteger)index; + +// Working with record descriptors +- (void) setDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword; +- (NSAppleEventDescriptor *) descriptorForKeyword: (AEKeyword)keyword; +- (void) removeDescriptorWithKeyword: (AEKeyword)keyword; +- (AEKeyword) keywordForDescriptorAtIndex: (NSInteger)index; + +// Working with Apple event descriptors +- (NSAppleEventDescriptor *) paramDescriptorForKeyword: (AEKeyword)keyword; +- (void) setParamDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword; +- (NSAppleEventDescriptor *) attributeDescriptorForKeyword: (AEKeyword)keyword; +- (void) setAttributeDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword; + +- (uint32_t) eventClass; +- (uint32_t) eventID; +- (int16_t) returnID; +- (int32_t) transactionID; @end diff --git a/Headers/Foundation/NSAppleEventManager.h b/Headers/Foundation/NSAppleEventManager.h index 6a8a14fee6..6248910220 100644 --- a/Headers/Foundation/NSAppleEventManager.h +++ b/Headers/Foundation/NSAppleEventManager.h @@ -32,8 +32,43 @@ extern "C" { #endif +@class NSAppleEventDescriptor; +@class NSString; +@class NSMutableDictionary; + +typedef uint32_t AEEventClass; +typedef uint32_t AEEventID; + GS_EXPORT_CLASS @interface NSAppleEventManager : NSObject +{ + @private + NSMutableDictionary *_eventHandlers; + NSAppleEventDescriptor *_currentEvent; + NSAppleEventDescriptor *_currentReply; + NSMutableDictionary *_suspendedEvents; +} + ++ (NSAppleEventManager *) sharedAppleEventManager; + +- (NSAppleEventDescriptor *) currentAppleEvent; +- (NSAppleEventDescriptor *) currentReplyAppleEvent; + +- (void) setEventHandler: (id)handler + andSelector: (SEL)handleEventSelector + forEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID; + +- (void) removeEventHandlerForEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID; + +- (NSAppleEventDescriptor *) replyAppleEventForSuspendedAppleEvent: (NSAppleEventDescriptor *)event; + +- (void) resumeWithSuspendedAppleEvent: (NSAppleEventDescriptor *)event; + +- (void) setCurrentAppleEventAndReplyEventWithSuspendedAppleEvent: (NSAppleEventDescriptor *)event; + +- (NSAppleEventDescriptor *) suspendCurrentAppleEvent; @end diff --git a/Source/NSAppleEventDescriptor.m b/Source/NSAppleEventDescriptor.m index 8b30f1d0bb..f67824ea21 100644 --- a/Source/NSAppleEventDescriptor.m +++ b/Source/NSAppleEventDescriptor.m @@ -22,8 +22,524 @@ */ #import "Foundation/NSAppleEventDescriptor.h" +#import "Foundation/NSData.h" +#import "Foundation/NSString.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSValue.h" +#import "common.h" + +typedef enum { + NSAppleEventDescriptorTypeSimple, + NSAppleEventDescriptorTypeList, + NSAppleEventDescriptorTypeRecord, + NSAppleEventDescriptorTypeAppleEvent +} NSAppleEventDescriptorInternalType; @implementation NSAppleEventDescriptor -@end +// Creating descriptors + ++ (NSAppleEventDescriptor *) descriptorWithBoolean: (BOOL)boolean +{ + unsigned char boolVal = boolean ? 1 : 0; + return [self descriptorWithDescriptorType: typeBoolean + bytes: &boolVal + length: sizeof(boolVal)]; +} + ++ (NSAppleEventDescriptor *) descriptorWithDescriptorType: (DescType)descriptorType + bytes: (const void *)bytes + length: (NSUInteger)byteCount +{ + NSData *data; + + data = [NSData dataWithBytes: bytes length: byteCount]; + return [self descriptorWithDescriptorType: descriptorType data: data]; +} + ++ (NSAppleEventDescriptor *) descriptorWithDescriptorType: (DescType)descriptorType + data: (NSData *)data +{ + NSAppleEventDescriptor *descriptor; + + descriptor = [[self alloc] init]; + descriptor->_descriptorType = descriptorType; + descriptor->_data = RETAIN(data); + descriptor->_internalType = NSAppleEventDescriptorTypeSimple; + return AUTORELEASE(descriptor); +} + ++ (NSAppleEventDescriptor *) descriptorWithEnumCode: (uint32_t)enumerator +{ + uint32_t code = enumerator; + return [self descriptorWithDescriptorType: typeEnumerated + bytes: &code + length: sizeof(code)]; +} + ++ (NSAppleEventDescriptor *) descriptorWithInt32: (int32_t)signedInt +{ + int32_t value = signedInt; + return [self descriptorWithDescriptorType: typeSInt32 + bytes: &value + length: sizeof(value)]; +} + ++ (NSAppleEventDescriptor *) descriptorWithString: (NSString *)string +{ + NSData *data; + + data = [string dataUsingEncoding: NSUTF8StringEncoding]; + return [self descriptorWithDescriptorType: typeChar data: data]; +} + ++ (NSAppleEventDescriptor *) descriptorWithTypeCode: (uint32_t)typeCode +{ + uint32_t code = typeCode; + return [self descriptorWithDescriptorType: typeType + bytes: &code + length: sizeof(code)]; +} + ++ (NSAppleEventDescriptor *) nullDescriptor +{ + return [self descriptorWithDescriptorType: typeNull bytes: NULL length: 0]; +} + +// List and record descriptors + ++ (NSAppleEventDescriptor *) listDescriptor +{ + NSAppleEventDescriptor *descriptor; + + descriptor = [[self alloc] init]; + descriptor->_descriptorType = typeAEList; + descriptor->_internalType = NSAppleEventDescriptorTypeList; + descriptor->_listItems = [[NSMutableArray alloc] init]; + return AUTORELEASE(descriptor); +} + ++ (NSAppleEventDescriptor *) recordDescriptor +{ + NSAppleEventDescriptor *descriptor; + + descriptor = [[self alloc] init]; + descriptor->_descriptorType = typeAERecord; + descriptor->_internalType = NSAppleEventDescriptorTypeRecord; + descriptor->_recordKeywords = [[NSMutableDictionary alloc] init]; + descriptor->_recordDescriptors = [[NSMutableArray alloc] init]; + return AUTORELEASE(descriptor); +} + +// Apple event descriptors + ++ (NSAppleEventDescriptor *) appleEventWithEventClass: (uint32_t)eventClass + eventID: (uint32_t)eventID + targetDescriptor: (NSAppleEventDescriptor *)targetDescriptor + returnID: (int16_t)returnID + transactionID: (int32_t)transactionID +{ + NSAppleEventDescriptor *descriptor; + + descriptor = [[self alloc] init]; + descriptor->_descriptorType = typeAppleEvent; + descriptor->_internalType = NSAppleEventDescriptorTypeAppleEvent; + descriptor->_eventClass = eventClass; + descriptor->_eventID = eventID; + descriptor->_returnID = returnID; + descriptor->_transactionID = transactionID; + descriptor->_parameters = [[NSMutableDictionary alloc] init]; + descriptor->_attributes = [[NSMutableDictionary alloc] init]; + + if (targetDescriptor != nil) + { + [descriptor setAttributeDescriptor: targetDescriptor forKeyword: 'targ']; + } + + return AUTORELEASE(descriptor); +} + +// Initialization and deallocation + +- (id) init +{ + if ((self = [super init])) + { + _internalType = NSAppleEventDescriptorTypeSimple; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_data); + RELEASE(_listItems); + RELEASE(_recordKeywords); + RELEASE(_recordDescriptors); + RELEASE(_parameters); + RELEASE(_attributes); + [super dealloc]; +} + +- (id) copyWithZone: (NSZone *)zone +{ + NSAppleEventDescriptor *copy; + + copy = [[NSAppleEventDescriptor allocWithZone: zone] init]; + copy->_descriptorType = _descriptorType; + copy->_internalType = _internalType; + copy->_data = [_data copyWithZone: zone]; + copy->_listItems = [_listItems mutableCopyWithZone: zone]; + copy->_recordKeywords = [_recordKeywords mutableCopyWithZone: zone]; + copy->_recordDescriptors = [_recordDescriptors mutableCopyWithZone: zone]; + copy->_eventClass = _eventClass; + copy->_eventID = _eventID; + copy->_returnID = _returnID; + copy->_transactionID = _transactionID; + copy->_parameters = [_parameters mutableCopyWithZone: zone]; + copy->_attributes = [_attributes mutableCopyWithZone: zone]; + + return copy; +} + +// Accessing descriptor data + +- (DescType) descriptorType +{ + return _descriptorType; +} + +- (NSData *) data +{ + return _data; +} + +- (BOOL) booleanValue +{ + const unsigned char *bytes; + + if (_descriptorType == typeTrue) + { + return YES; + } + if (_descriptorType == typeFalse) + { + return NO; + } + if (_descriptorType == typeBoolean && [_data length] >= 1) + { + bytes = [_data bytes]; + return bytes[0] != 0; + } + return NO; +} + +- (int32_t) int32Value +{ + const int32_t *value; + + if (_descriptorType == typeSInt32 && [_data length] >= sizeof(int32_t)) + { + value = [_data bytes]; + return *value; + } + return 0; +} +- (NSString *) stringValue +{ + if (_descriptorType == typeChar) + { + return [[[NSString alloc] initWithData: _data + encoding: NSUTF8StringEncoding] autorelease]; + } + return nil; +} + +- (uint32_t) typeCodeValue +{ + const uint32_t *value; + + if (_descriptorType == typeType && [_data length] >= sizeof(uint32_t)) + { + value = [_data bytes]; + return *value; + } + return 0; +} + +- (uint32_t) enumCodeValue +{ + const uint32_t *value; + + if (_descriptorType == typeEnumerated && [_data length] >= sizeof(uint32_t)) + { + value = [_data bytes]; + return *value; + } + return 0; +} + +// Working with list descriptors + +- (NSInteger) numberOfItems +{ + if (_internalType == NSAppleEventDescriptorTypeList) + { + return [_listItems count]; + } + if (_internalType == NSAppleEventDescriptorTypeRecord) + { + return [_recordDescriptors count]; + } + return 0; +} + +- (void) insertDescriptor: (NSAppleEventDescriptor *)descriptor + atIndex: (NSInteger)index +{ + if (_internalType == NSAppleEventDescriptorTypeList) + { + if (descriptor != nil) + { + [_listItems insertObject: descriptor atIndex: index]; + } + } +} + +- (NSAppleEventDescriptor *) descriptorAtIndex: (NSInteger)index +{ + if (_internalType == NSAppleEventDescriptorTypeList) + { + if (index >= 0 && index < [_listItems count]) + { + return [_listItems objectAtIndex: index]; + } + } + else if (_internalType == NSAppleEventDescriptorTypeRecord) + { + if (index >= 0 && index < [_recordDescriptors count]) + { + return [_recordDescriptors objectAtIndex: index]; + } + } + return nil; +} + +- (void) removeDescriptorAtIndex: (NSInteger)index +{ + if (_internalType == NSAppleEventDescriptorTypeList) + { + if (index >= 0 && index < [_listItems count]) + { + [_listItems removeObjectAtIndex: index]; + } + } +} + +// Working with record descriptors + +- (void) setDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + NSNumber *indexNum; + + if (_internalType != NSAppleEventDescriptorTypeRecord) + { + return; + } + + if (descriptor == nil) + { + [self removeDescriptorWithKeyword: keyword]; + return; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + indexNum = [_recordKeywords objectForKey: keywordNum]; + + if (indexNum != nil) + { + [_recordDescriptors replaceObjectAtIndex: [indexNum integerValue] + withObject: descriptor]; + } + else + { + [_recordDescriptors addObject: descriptor]; + [_recordKeywords setObject: [NSNumber numberWithInteger: [_recordDescriptors count] - 1] + forKey: keywordNum]; + } +} + +- (NSAppleEventDescriptor *) descriptorForKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + NSNumber *index; + + if (_internalType != NSAppleEventDescriptorTypeRecord) + { + return nil; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + index = [_recordKeywords objectForKey: keywordNum]; + + if (index != nil) + { + return [_recordDescriptors objectAtIndex: [index integerValue]]; + } + return nil; +} + +- (void) removeDescriptorWithKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + NSNumber *index; + + if (_internalType != NSAppleEventDescriptorTypeRecord) + { + return; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + index = [_recordKeywords objectForKey: keywordNum]; + + if (index != nil) + { + [_recordDescriptors removeObjectAtIndex: [index integerValue]]; + [_recordKeywords removeObjectForKey: keywordNum]; + } +} + +- (AEKeyword) keywordForDescriptorAtIndex: (NSInteger)index +{ + NSEnumerator *keyEnum; + NSNumber *keywordNum; + NSNumber *idx; + + if (_internalType != NSAppleEventDescriptorTypeRecord) + { + return 0; + } + + keyEnum = [_recordKeywords keyEnumerator]; + while ((keywordNum = [keyEnum nextObject]) != nil) + { + idx = [_recordKeywords objectForKey: keywordNum]; + if ([idx integerValue] == index) + { + return [keywordNum unsignedIntValue]; + } + } + return 0; +} + +// Working with Apple event descriptors + +- (NSAppleEventDescriptor *) paramDescriptorForKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + + if (_internalType != NSAppleEventDescriptorTypeAppleEvent) + { + return nil; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + return [_parameters objectForKey: keywordNum]; +} + +- (void) setParamDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + + if (_internalType != NSAppleEventDescriptorTypeAppleEvent) + { + return; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + if (descriptor != nil) + { + [_parameters setObject: descriptor forKey: keywordNum]; + } + else + { + [_parameters removeObjectForKey: keywordNum]; + } +} + +- (NSAppleEventDescriptor *) attributeDescriptorForKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + + if (_internalType != NSAppleEventDescriptorTypeAppleEvent) + { + return nil; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + return [_attributes objectForKey: keywordNum]; +} + +- (void) setAttributeDescriptor: (NSAppleEventDescriptor *)descriptor + forKeyword: (AEKeyword)keyword +{ + NSNumber *keywordNum; + + if (_internalType != NSAppleEventDescriptorTypeAppleEvent) + { + return; + } + + keywordNum = [NSNumber numberWithUnsignedInt: keyword]; + if (descriptor != nil) + { + [_attributes setObject: descriptor forKey: keywordNum]; + } + else + { + [_attributes removeObjectForKey: keywordNum]; + } +} + +- (uint32_t) eventClass +{ + if (_internalType == NSAppleEventDescriptorTypeAppleEvent) + { + return _eventClass; + } + return 0; +} + +- (uint32_t) eventID +{ + if (_internalType == NSAppleEventDescriptorTypeAppleEvent) + { + return _eventID; + } + return 0; +} + +- (int16_t) returnID +{ + if (_internalType == NSAppleEventDescriptorTypeAppleEvent) + { + return _returnID; + } + return 0; +} + +- (int32_t) transactionID +{ + if (_internalType == NSAppleEventDescriptorTypeAppleEvent) + { + return _transactionID; + } + return 0; +} + +@end diff --git a/Source/NSAppleEventManager.m b/Source/NSAppleEventManager.m index ffb595a7d2..d7c51b5a56 100644 --- a/Source/NSAppleEventManager.m +++ b/Source/NSAppleEventManager.m @@ -22,8 +22,278 @@ */ #import "Foundation/NSAppleEventManager.h" +#import "Foundation/NSAppleEventDescriptor.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSException.h" +#import "Foundation/NSInvocation.h" +#import "Foundation/NSLock.h" +#import "Foundation/NSString.h" +#import "Foundation/NSValue.h" +#import "common.h" -@implementation NSAppleEventManager +static NSAppleEventManager *sharedManager = nil; +static NSLock *managerLock = nil; +@interface NSAppleEventManager (Private) +- (NSDictionary *) _handlerInfoForEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID; +- (void) _handleAppleEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent; @end +@implementation NSAppleEventManager + ++ (void) initialize +{ + if (self == [NSAppleEventManager class]) + { + managerLock = [[NSLock alloc] init]; + } +} + ++ (NSAppleEventManager *) sharedAppleEventManager +{ + if (sharedManager == nil) + { + [managerLock lock]; + if (sharedManager == nil) + { + sharedManager = [[self alloc] init]; + } + [managerLock unlock]; + } + return sharedManager; +} + +- (id) init +{ + if ((self = [super init])) + { + _eventHandlers = [[NSMutableDictionary alloc] init]; + _suspendedEvents = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_eventHandlers); + RELEASE(_currentEvent); + RELEASE(_currentReply); + RELEASE(_suspendedEvents); + [super dealloc]; +} + +- (NSAppleEventDescriptor *) currentAppleEvent +{ + return _currentEvent; +} + +- (NSAppleEventDescriptor *) currentReplyAppleEvent +{ + return _currentReply; +} + +- (void) setEventHandler: (id)handler + andSelector: (SEL)handleEventSelector + forEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID +{ + NSString *key; + NSDictionary *handlerInfo; + + if (handler == nil || handleEventSelector == NULL) + { + [self removeEventHandlerForEventClass: eventClass andEventID: eventID]; + return; + } + + key = [NSString stringWithFormat: @"%u_%u", + (unsigned int)eventClass, (unsigned int)eventID]; + + handlerInfo = [NSDictionary dictionaryWithObjectsAndKeys: + handler, @"handler", + NSStringFromSelector(handleEventSelector), @"selector", + nil]; + + if (handlerInfo != nil) + { + [_eventHandlers setObject: handlerInfo forKey: key]; + } +} + +- (void) removeEventHandlerForEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID +{ + NSString *key; + + key = [NSString stringWithFormat: @"%u_%u", + (unsigned int)eventClass, (unsigned int)eventID]; + if (key != nil) + { + [_eventHandlers removeObjectForKey: key]; + } +} + +- (NSDictionary *) _handlerInfoForEventClass: (AEEventClass)eventClass + andEventID: (AEEventID)eventID +{ + NSString *key; + + key = [NSString stringWithFormat: @"%u_%u", + (unsigned int)eventClass, (unsigned int)eventID]; + if (key != nil) + { + return [_eventHandlers objectForKey: key]; + } + return nil; +} + +- (void) _handleAppleEvent: (NSAppleEventDescriptor *)event + withReplyEvent: (NSAppleEventDescriptor *)replyEvent +{ + AEEventClass eventClass; + AEEventID eventID; + NSDictionary *handlerInfo; + id handler; + SEL selector; + NSAppleEventDescriptor *oldEvent; + NSAppleEventDescriptor *oldReply; + + eventClass = [event eventClass]; + eventID = [event eventID]; + + handlerInfo = [self _handlerInfoForEventClass: eventClass andEventID: eventID]; + + if (handlerInfo == nil) + { + return; + } + + handler = [handlerInfo objectForKey: @"handler"]; + selector = NSSelectorFromString([handlerInfo objectForKey: @"selector"]); + + if (handler == nil || selector == NULL) + { + return; + } + + oldEvent = RETAIN(_currentEvent); + oldReply = RETAIN(_currentReply); + + ASSIGN(_currentEvent, event); + ASSIGN(_currentReply, replyEvent); + + NS_DURING + { + if ([handler respondsToSelector: selector]) + { + [handler performSelector: selector withObject: event withObject: replyEvent]; + } + } + NS_HANDLER + { + NSLog(@"Exception handling Apple Event: %@", localException); + } + NS_ENDHANDLER + + ASSIGN(_currentEvent, oldEvent); + ASSIGN(_currentReply, oldReply); + RELEASE(oldEvent); + RELEASE(oldReply); +} + +- (NSAppleEventDescriptor *) replyAppleEventForSuspendedAppleEvent: (NSAppleEventDescriptor *)event +{ + NSString *key; + NSDictionary *suspended; + + if (event == nil) + { + return nil; + } + + key = [NSString stringWithFormat: @"%p", event]; + if (key != nil) + { + suspended = [_suspendedEvents objectForKey: key]; + + if (suspended != nil) + { + return [suspended objectForKey: @"reply"]; + } + } + + return nil; +} + +- (void) resumeWithSuspendedAppleEvent: (NSAppleEventDescriptor *)event +{ + NSString *key; + + if (event == nil) + { + return; + } + + key = [NSString stringWithFormat: @"%p", event]; + if (key != nil) + { + [_suspendedEvents removeObjectForKey: key]; + } +} + +- (void) setCurrentAppleEventAndReplyEventWithSuspendedAppleEvent: (NSAppleEventDescriptor *)event +{ + NSString *key; + NSDictionary *suspended; + NSAppleEventDescriptor *replyEvent; + + if (event == nil) + { + return; + } + + key = [NSString stringWithFormat: @"%p", event]; + if (key != nil) + { + suspended = [_suspendedEvents objectForKey: key]; + + if (suspended != nil) + { + replyEvent = [suspended objectForKey: @"reply"]; + ASSIGN(_currentEvent, event); + ASSIGN(_currentReply, replyEvent); + } + } +} + +- (NSAppleEventDescriptor *) suspendCurrentAppleEvent +{ + NSAppleEventDescriptor *event; + NSString *key; + NSDictionary *suspended; + + event = _currentEvent; + + if (event == nil) + { + return nil; + } + + key = [NSString stringWithFormat: @"%p", event]; + suspended = [NSDictionary dictionaryWithObjectsAndKeys: + event, @"event", + _currentReply, @"reply", + nil]; + + if (key != nil && suspended != nil) + { + [_suspendedEvents setObject: suspended forKey: key]; + } + + return event; +} + +@end diff --git a/Source/NSScriptCommand.m b/Source/NSScriptCommand.m index 7193839e75..968eaa0fed 100644 --- a/Source/NSScriptCommand.m +++ b/Source/NSScriptCommand.m @@ -41,6 +41,8 @@ @implementation NSScriptCommand id _evaluatedReceivers; NSAppleEventDescriptor *_appleEvent; BOOL _isSuspended; + NSInteger _errorNumber; + NSString *_errorString; } - (id) initWithCommandDescription: (NSScriptCommandDescription *)commandDef @@ -76,6 +78,7 @@ - (void) dealloc RELEASE(_receiversSpecifier); RELEASE(_evaluatedReceivers); RELEASE(_appleEvent); + RELEASE(_errorString); [super dealloc]; } @@ -204,5 +207,25 @@ - (NSAppleEventDescriptor *) appleEvent return _appleEvent; } +- (void) setScriptErrorNumber: (NSInteger)errorNumber +{ + _errorNumber = errorNumber; +} + +- (void) setScriptErrorString: (NSString *)errorString +{ + ASSIGN(_errorString, errorString); +} + +- (NSInteger) scriptErrorNumber +{ + return _errorNumber; +} + +- (NSString *) scriptErrorString +{ + return _errorString; +} + @end diff --git a/Source/NSScriptObjectSpecifier.m b/Source/NSScriptObjectSpecifier.m index 9a47d20066..fc8b27d31c 100644 --- a/Source/NSScriptObjectSpecifier.m +++ b/Source/NSScriptObjectSpecifier.m @@ -32,6 +32,11 @@ #import "Foundation/NSString.h" #import "Foundation/NSValue.h" +/* Suppress warnings for NSAppleEventDescriptor methods that may not be + * fully declared in the header but are available at runtime */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wobjc-method-access" + // NSScriptObjectSpecifier @implementation NSScriptObjectSpecifier @@ -1541,3 +1546,5 @@ - (instancetype) initWithCoder: (NSCoder *)coder } @end + +#pragma clang diagnostic pop diff --git a/Source/common.h b/Source/common.h index 85b0d47f63..147debebff 100644 --- a/Source/common.h +++ b/Source/common.h @@ -43,14 +43,16 @@ #import "GNUstepBase/GSConfig.h" #import "GNUstepBase/GSVersionMacros.h" +#import "GNUstepBase/GNUstep.h" -/* Set localisation macro for use within the base library itsself. +/* Set localisation macro for use within the base library itself, + * only if not already defined by GNUstep.h */ +#ifndef GS_LOCALISATION_BUNDLE #define GS_LOCALISATION_BUNDLE \ [NSBundle bundleForLibrary: @"gnustep-base" version: \ OBJC_STRINGIFY(GNUSTEP_BASE_MAJOR_VERSION.GNUSTEP_BASE_MINOR_VERSION)] - -#import "GNUstepBase/GNUstep.h" +#endif /* Foundation/NSObject.h imports and * so we import local versions first. From 6aecec839ce6281df16fb16e8d1106bf92a9a543 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 16:47:27 -0500 Subject: [PATCH 04/20] Fix error in import. --- Tests/base/NSScriptCoercionHandler/basic.m | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/base/NSScriptCoercionHandler/basic.m b/Tests/base/NSScriptCoercionHandler/basic.m index 79877b913c..6741fd385a 100644 --- a/Tests/base/NSScriptCoercionHandler/basic.m +++ b/Tests/base/NSScriptCoercionHandler/basic.m @@ -2,7 +2,6 @@ #import #import #import -#import #import @interface TestCoercer : NSObject From 0635d5903c559ef25ea8d7f5a5a587f52de13bf3 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 16:54:22 -0500 Subject: [PATCH 05/20] Update handler --- Headers/Foundation/NSScriptCoercionHandler.h | 7 +++++++ Headers/Foundation/NSScriptCommand.h | 13 +++++++++++++ Source/NSScriptCoercionHandler.m | 7 ------- Source/NSScriptCommand.m | 12 ------------ 4 files changed, 20 insertions(+), 19 deletions(-) diff --git a/Headers/Foundation/NSScriptCoercionHandler.h b/Headers/Foundation/NSScriptCoercionHandler.h index 5040498879..cb9e2fbbe7 100644 --- a/Headers/Foundation/NSScriptCoercionHandler.h +++ b/Headers/Foundation/NSScriptCoercionHandler.h @@ -33,9 +33,16 @@ extern "C" { #if OS_API_VERSION(MAC_OS_X_VERSION_10_0, GS_API_LATEST) @class NSString; +@class NSMutableDictionary; +@class NSLock; GS_EXPORT_CLASS @interface NSScriptCoercionHandler : NSObject +{ + @private + NSMutableDictionary *_coercers; + NSLock *_lock; +} + (NSScriptCoercionHandler *) sharedCoercionHandler; diff --git a/Headers/Foundation/NSScriptCommand.h b/Headers/Foundation/NSScriptCommand.h index 999dc81245..4eb7ffe351 100644 --- a/Headers/Foundation/NSScriptCommand.h +++ b/Headers/Foundation/NSScriptCommand.h @@ -41,6 +41,19 @@ extern "C" { GS_EXPORT_CLASS @interface NSScriptCommand : NSObject +{ + @private + NSScriptCommandDescription *_commandDescription; + NSDictionary *_arguments; + NSDictionary *_evaluatedArguments; + NSScriptObjectSpecifier *_directParameter; + NSScriptObjectSpecifier *_receiversSpecifier; + id _evaluatedReceivers; + NSAppleEventDescriptor *_appleEvent; + BOOL _isSuspended; + NSInteger _errorNumber; + NSString *_errorString; +} - (id) initWithCommandDescription: (NSScriptCommandDescription *)commandDef; diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index f6d0a01ca1..c11df1e95e 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -28,13 +28,6 @@ #import "Foundation/NSValue.h" #import "Foundation/NSLock.h" -@interface NSScriptCoercionHandler () -{ - NSMutableDictionary *_coercers; - NSLock *_lock; -} -@end - @implementation NSScriptCoercionHandler static NSScriptCoercionHandler *sharedHandler = nil; diff --git a/Source/NSScriptCommand.m b/Source/NSScriptCommand.m index 968eaa0fed..6b502c696d 100644 --- a/Source/NSScriptCommand.m +++ b/Source/NSScriptCommand.m @@ -32,18 +32,6 @@ #import "Foundation/NSCoder.h" @implementation NSScriptCommand -{ - NSScriptCommandDescription *_commandDescription; - NSDictionary *_arguments; - NSDictionary *_evaluatedArguments; - NSScriptObjectSpecifier *_directParameter; - NSScriptObjectSpecifier *_receiversSpecifier; - id _evaluatedReceivers; - NSAppleEventDescriptor *_appleEvent; - BOOL _isSuspended; - NSInteger _errorNumber; - NSString *_errorString; -} - (id) initWithCommandDescription: (NSScriptCommandDescription *)commandDef { From 1219867336a89ef8c0dfdcbf7986c6c5e33f0c35 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 17:41:01 -0500 Subject: [PATCH 06/20] Update script coercion handler to fix issue --- Source/NSScriptCoercionHandler.m | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index c11df1e95e..1cb66fcf5a 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -78,33 +78,33 @@ - (id) coerceValue: (id)value toClass: (Class)toClass id result; if (value == nil) - return nil; + { + return nil; + } if ([value isKindOfClass: toClass]) - return value; + { + return value; + } [_lock lock]; key = [self _keyForFromClass: [value class] toClass: toClass]; coercerInfo = [_coercers objectForKey: key]; + [_lock unlock]; + if (coercerInfo != nil) { coercer = [coercerInfo objectForKey: @"coercer"]; selector = NSSelectorFromString([coercerInfo objectForKey: @"selector"]); - [_lock unlock]; - - if (coercer != nil && [coercer respondsToSelector: selector]) + if (coercer != nil && selector != NULL && [coercer respondsToSelector: selector]) { result = [coercer performSelector: selector withObject: value]; return result; } } - else - { - [_lock unlock]; - } return value; } From 775ee106aefdcffd9f4e09d760daf9b070ced8ce Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 19:20:39 -0500 Subject: [PATCH 07/20] Add scripting bridge for steptalk --- Examples/StepTalk/README.md | 68 +++ Examples/StepTalk/advanced-features.st | 177 ++++++ Examples/StepTalk/app-control.st | 127 +++++ Examples/StepTalk/basic-commands.st | 52 ++ Examples/StepTalk/object-specifiers.st | 104 ++++ .../GNUstepBase/GSScriptingStepTalkBridge.h | 166 ++++++ .../GNUstepBase/NSScriptingStepTalkBridge.h | 166 ++++++ MIGRATION-NSScriptingStepTalkBridge.md | 44 ++ Source/DocMakefile | 1 + Source/GNUmakefile | 2 + Source/GSScriptingStepTalkBridge.m | 530 ++++++++++++++++++ Source/NSScriptingStepTalkBridge.m | 530 ++++++++++++++++++ 12 files changed, 1967 insertions(+) create mode 100644 Examples/StepTalk/README.md create mode 100644 Examples/StepTalk/advanced-features.st create mode 100644 Examples/StepTalk/app-control.st create mode 100644 Examples/StepTalk/basic-commands.st create mode 100644 Examples/StepTalk/object-specifiers.st create mode 100644 Headers/GNUstepBase/GSScriptingStepTalkBridge.h create mode 100644 Headers/GNUstepBase/NSScriptingStepTalkBridge.h create mode 100644 MIGRATION-NSScriptingStepTalkBridge.md create mode 100644 Source/GSScriptingStepTalkBridge.m create mode 100644 Source/NSScriptingStepTalkBridge.m diff --git a/Examples/StepTalk/README.md b/Examples/StepTalk/README.md new file mode 100644 index 0000000000..3e2832bb8c --- /dev/null +++ b/Examples/StepTalk/README.md @@ -0,0 +1,68 @@ +# StepTalk Integration Examples + +This directory contains examples demonstrating how to use StepTalk with the GNUstep NSScripting framework through the GSScriptingStepTalkBridge. + +## Overview + +The GSScriptingStepTalkBridge allows StepTalk (Smalltalk-based scripting for GNUstep) to interact with scriptable applications using the NSScripting framework. This provides a powerful, dynamic scripting environment that can control applications supporting Apple Event-style scripting. + +## Prerequisites + +1. GNUstep Base Library with NSScripting support +2. StepTalk framework installed +3. A scriptable application or test target + +## Examples + +### 1. Basic Command Execution (basic-commands.st) + +Demonstrates how to: + +- Get the shared bridge instance +- Create and execute simple commands +- Work with command results + +### 2. Object Specifiers (object-specifiers.st) + +Shows how to: + +- Build object specifiers for targeting specific objects +- Chain specifiers for nested objects +- Use different specifier types (index, name, property) + +### 3. Complete Application Control (app-control.st) + +Demonstrates: + +- Creating new objects in an application +- Getting and setting properties +- Querying object counts +- Deleting objects + +## Running the Examples + +From StepTalk: + +```smalltalk +"Load a script file" +Transcript loadScript: 'basic-commands.st' +``` + +Or execute StepTalk code directly in your application. + +## Integration Pattern + +The typical pattern for using the bridge is: + +1. Get the shared bridge instance +2. Create object specifiers to identify target objects +3. Create commands with appropriate arguments +4. Execute commands and handle results +5. Check for errors in command execution + +## See Also + +- GSScriptingStepTalkBridge.h - Bridge interface documentation +- NSScriptCommand.h - Command execution +- NSScriptObjectSpecifier.h - Object specification +- StepTalk documentation diff --git a/Examples/StepTalk/advanced-features.st b/Examples/StepTalk/advanced-features.st new file mode 100644 index 0000000000..da0c871470 --- /dev/null +++ b/Examples/StepTalk/advanced-features.st @@ -0,0 +1,177 @@ +"============================================ + Advanced Features Example + Demonstrates advanced bridge features like + custom command handlers and caching +============================================" + +| bridge command result handler | + +"Get the shared bridge instance" +bridge := GSScriptingStepTalkBridge sharedBridge. + +Transcript show: 'Advanced Bridge Features'; cr. +Transcript show: '======================='; cr; cr. + +"Example 1: Custom command handler" +Transcript show: 'Example 1: Custom Command Handler'; cr. + +"Create a block that handles custom commands" +handler := [:cmd | + Transcript show: 'Custom handler invoked for command: ', + cmd asString; cr. + 'Custom result from StepTalk'. +]. + +"Register the handler" +bridge + registerCommandHandler: handler + forCommand: 'myCustomCommand' + inSuite: 'CustomSuite'. + +Transcript show: 'Registered custom command handler'; cr; cr. + +"Example 2: Multiple specifier types" +Transcript show: 'Example 2: Using Multiple Specifier Types'; cr. + +"Middle specifier" +| middleSpec | +middleSpec := bridge + createSpecifier: 'middle' + inContainer: nil + forKey: 'documents' + withValue: nil. + +Transcript show: 'Created middle specifier'; cr. + +"Random specifier" +| randomSpec | +randomSpec := bridge + createSpecifier: 'random' + inContainer: nil + forKey: 'documents' + withValue: nil. + +Transcript show: 'Created random specifier'; cr; cr. + +"Example 3: Relative specifiers" +Transcript show: 'Example 3: Relative Specifiers'; cr. + +| baseSpec relativeSpec | +baseSpec := bridge + createSpecifier: 'name' + inContainer: nil + forKey: 'documents' + withValue: 'MyDocument'. + +relativeSpec := bridge + createSpecifier: 'relative' + inContainer: nil + forKey: 'documents' + withValue: (Dictionary new + at: 'base' put: baseSpec; + at: 'position' put: 'after'; + yourself). + +Transcript show: 'Created relative specifier (document after MyDocument)'; cr; cr. + +"Example 4: UniqueID specifier" +Transcript show: 'Example 4: UniqueID Specifier'; cr. + +| uidSpec | +uidSpec := bridge + createSpecifier: 'uniqueID' + inContainer: nil + forKey: 'documents' + withValue: 'DOC-12345'. + +Transcript show: 'Created uniqueID specifier'; cr; cr. + +"Example 5: Complex command with multiple arguments" +Transcript show: 'Example 5: Complex Command Arguments'; cr. + +| locationSpec | +locationSpec := bridge + createSpecifier: 'position' + inContainer: nil + forKey: 'documents' + withValue: 'end'. + +properties := Dictionary new + at: 'name' put: 'NewDocument'; + at: 'modified' put: false; + at: 'visible' put: true; + yourself. + +result := bridge + executeCommand: 'create' + forSuite: 'CoreSuite' + withArguments: (Dictionary new + at: 'ObjectClass' put: 'document'; + at: 'Location' put: locationSpec; + at: 'WithProperties' put: properties; + yourself). + +Transcript show: 'Created document with complex arguments'; cr; cr. + +"Example 6: Batch operations" +Transcript show: 'Example 6: Batch Operations'; cr. + +| docs | +docs := Array new: 5. + +1 to: 5 do: [:i | + | docName | + docName := 'Document', i asString. + + docs at: i put: (bridge + createObject: 'document' + atLocation: nil + withProperties: (Dictionary new + at: 'name' put: docName; + yourself)). + + Transcript show: 'Created ', docName; cr. +]. + +Transcript show: 'Created 5 documents in batch'; cr; cr. + +"Example 7: Query and filter" +Transcript show: 'Example 7: Querying Objects'; cr. + +count := bridge countObjects: 'document' inContainer: nil. +Transcript show: 'Total documents: ', count asString; cr. + +"Get all documents (this would need a whose specifier in real use)" +Transcript show: 'To filter, use NSWhoseSpecifier in production'; cr; cr. + +"Example 8: Clear cache" +Transcript show: 'Example 8: Cache Management'; cr. +bridge clearCache. +Transcript show: 'Cleared bridge cache'; cr; cr. + +"Example 9: Error handling patterns" +Transcript show: 'Example 9: Robust Error Handling'; cr. + +[ + command := bridge + createCommand: 'get' + forSuite: 'CoreSuite' + withArguments: nil. + + result := command executeCommand. + + (command scriptErrorNumber = 0) + ifTrue: [ + Transcript show: 'Success: ', result asString; cr. + ] + ifFalse: [ + Transcript show: 'Error ', + command scriptErrorNumber asString, + ': ', + command scriptErrorString; cr. + ]. +] on: Error do: [:ex | + Transcript show: 'Exception caught: ', ex messageText; cr. +]. + +Transcript cr; show: 'Advanced examples complete!'; cr. diff --git a/Examples/StepTalk/app-control.st b/Examples/StepTalk/app-control.st new file mode 100644 index 0000000000..277bea9b8d --- /dev/null +++ b/Examples/StepTalk/app-control.st @@ -0,0 +1,127 @@ +"============================================ + Application Control Example + Demonstrates complete application control + using the convenience methods +============================================" + +| bridge docSpec windowSpec count result properties | + +"Get the shared bridge instance" +bridge := GSScriptingStepTalkBridge sharedBridge. + +Transcript show: 'Application Control Examples'; cr. +Transcript show: '==========================='; cr; cr. + +"Example 1: Count objects" +Transcript show: 'Example 1: Counting Documents'; cr. +count := bridge countObjects: 'document' inContainer: nil. +Transcript show: 'Current document count: ', count asString; cr; cr. + +"Example 2: Create a new document" +Transcript show: 'Example 2: Creating New Document'; cr. +properties := Dictionary new + at: 'name' put: 'UntitledDocument'; + at: 'modified' put: false; + yourself. + +result := bridge + createObject: 'document' + atLocation: nil + withProperties: properties. + +Transcript show: 'Created new document: ', result asString; cr; cr. + +"Example 3: Get object by name" +Transcript show: 'Example 3: Getting Object by Name'; cr. +result := bridge + getObject: 'UntitledDocument' + ofType: 'document' + fromContainer: nil. + +Transcript show: 'Retrieved document: ', result asString; cr; cr. + +"Example 4: Set a property" +Transcript show: 'Example 4: Setting Property'; cr. +docSpec := bridge + createSpecifier: 'name' + inContainer: nil + forKey: 'documents' + withValue: 'UntitledDocument'. + +bridge + setProperty: 'name' + ofObject: docSpec + toValue: 'MyDocument'. + +Transcript show: 'Changed document name to "MyDocument"'; cr; cr. + +"Example 5: Get modified property value" +Transcript show: 'Example 5: Getting Property Value'; cr. +result := bridge + getObject: 'name' + ofType: 'property' + fromContainer: docSpec. + +Transcript show: 'Document name is now: ', result asString; cr; cr. + +"Example 6: Count again to verify" +Transcript show: 'Example 6: Verify Count Increased'; cr. +count := bridge countObjects: 'document' inContainer: nil. +Transcript show: 'New document count: ', count asString; cr; cr. + +"Example 7: Delete object" +Transcript show: 'Example 7: Deleting Object'; cr. +bridge deleteObject: docSpec. +Transcript show: 'Deleted document'; cr; cr. + +"Example 8: Final count" +Transcript show: 'Example 8: Final Count'; cr. +count := bridge countObjects: 'document' inContainer: nil. +Transcript show: 'Final document count: ', count asString; cr; cr. + +"Example 9: Working with windows" +Transcript show: 'Example 9: Working with Windows'; cr. +count := bridge countObjects: 'window' inContainer: nil. +Transcript show: 'Window count: ', count asString; cr. + +windowSpec := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'windows' + withValue: 1. + +"Get window name" +result := bridge + getObject: 'name' + ofType: 'property' + fromContainer: windowSpec. + +Transcript show: 'First window name: ', result asString; cr; cr. + +"Example 10: Complex nested access" +Transcript show: 'Example 10: Complex Nested Access'; cr. + +"Get the third paragraph of the first document" +docSpec := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'documents' + withValue: 1. + +| paraSpec | +paraSpec := bridge + createSpecifier: 'index' + inContainer: docSpec + forKey: 'paragraphs' + withValue: 3. + +result := bridge + executeCommand: 'get' + forSuite: 'CoreSuite' + withArguments: (Dictionary new + at: 'ObjectSpecifier' put: paraSpec; + yourself). + +Transcript show: 'Third paragraph: ', result asString; cr; cr. + +Transcript show: 'Application control examples complete!'; cr. diff --git a/Examples/StepTalk/basic-commands.st b/Examples/StepTalk/basic-commands.st new file mode 100644 index 0000000000..dd9ab0bdf1 --- /dev/null +++ b/Examples/StepTalk/basic-commands.st @@ -0,0 +1,52 @@ +"============================================ + Basic Commands Example + Demonstrates simple command execution using + the GSScriptingStepTalkBridge +============================================" + +| bridge command result | + +"Get the shared bridge instance" +bridge := GSScriptingStepTalkBridge sharedBridge. + +Transcript show: 'StepTalk NSScripting Bridge Example'; cr. +Transcript show: '===================================='; cr; cr. + +"Example 1: Execute a get command" +Transcript show: 'Example 1: Simple Get Command'; cr. +command := bridge + createCommand: 'get' + forSuite: 'CoreSuite' + withArguments: nil. + +result := command executeCommand. +Transcript show: 'Command executed, result: ', result asString; cr; cr. + +"Example 2: Execute a count command" +Transcript show: 'Example 2: Count Command'; cr. +result := bridge + executeCommand: 'count' + forSuite: 'CoreSuite' + withArguments: (Dictionary new + at: 'ObjectClass' put: 'document'; + yourself). + +Transcript show: 'Document count: ', result asString; cr; cr. + +"Example 3: Check command errors" +Transcript show: 'Example 3: Error Handling'; cr. +command := bridge + createCommand: 'nonexistent' + forSuite: 'CoreSuite' + withArguments: nil. + +result := command executeCommand. + +(command scriptErrorNumber ~= 0) ifTrue: [ + Transcript show: 'Error occurred: ', + command scriptErrorString; cr. +] ifFalse: [ + Transcript show: 'Command succeeded'; cr. +]. + +Transcript cr; show: 'Examples complete!'; cr. diff --git a/Examples/StepTalk/object-specifiers.st b/Examples/StepTalk/object-specifiers.st new file mode 100644 index 0000000000..7a7f3931a9 --- /dev/null +++ b/Examples/StepTalk/object-specifiers.st @@ -0,0 +1,104 @@ +"============================================ + Object Specifiers Example + Demonstrates building object specifiers + to target specific objects +============================================" + +| bridge spec1 spec2 result | + +"Get the shared bridge instance" +bridge := GSScriptingStepTalkBridge sharedBridge. + +Transcript show: 'Object Specifier Examples'; cr. +Transcript show: '========================'; cr; cr. + +"Example 1: Simple property specifier" +Transcript show: 'Example 1: Property Specifier'; cr. +spec1 := bridge + createSpecifier: 'property' + inContainer: nil + forKey: 'name' + withValue: nil. + +Transcript show: 'Created property specifier for "name"'; cr; cr. + +"Example 2: Index specifier" +Transcript show: 'Example 2: Index Specifier'; cr. +spec2 := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'documents' + withValue: 1. + +Transcript show: 'Created index specifier for first document'; cr; cr. + +"Example 3: Name specifier" +Transcript show: 'Example 3: Name Specifier'; cr. +spec2 := bridge + createSpecifier: 'name' + inContainer: nil + forKey: 'windows' + withValue: 'MyWindow'. + +Transcript show: 'Created name specifier for window "MyWindow"'; cr; cr. + +"Example 4: Nested specifiers" +Transcript show: 'Example 4: Nested Specifiers'; cr. + +"Container specifier - first document" +spec1 := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'documents' + withValue: 1. + +"Property specifier within container" +spec2 := bridge + createSpecifier: 'property' + inContainer: spec1 + forKey: 'name' + withValue: nil. + +Transcript show: 'Created nested specifier: name of first document'; cr; cr. + +"Example 5: Using specifier in a command" +Transcript show: 'Example 5: Using Specifier in Command'; cr. + +result := bridge + executeCommand: 'get' + forSuite: 'CoreSuite' + withArguments: (Dictionary new + at: 'ObjectSpecifier' put: spec2; + yourself). + +Transcript show: 'Retrieved value: ', result asString; cr; cr. + +"Example 6: Range specifier" +Transcript show: 'Example 6: Range Specifier'; cr. + +| startSpec endSpec rangeSpec | + +startSpec := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'documents' + withValue: 1. + +endSpec := bridge + createSpecifier: 'index' + inContainer: nil + forKey: 'documents' + withValue: 3. + +rangeSpec := bridge + createSpecifier: 'range' + inContainer: nil + forKey: 'documents' + withValue: (Dictionary new + at: 'start' put: startSpec; + at: 'end' put: endSpec; + yourself). + +Transcript show: 'Created range specifier for documents 1-3'; cr; cr. + +Transcript show: 'Specifier examples complete!'; cr. diff --git a/Headers/GNUstepBase/GSScriptingStepTalkBridge.h b/Headers/GNUstepBase/GSScriptingStepTalkBridge.h new file mode 100644 index 0000000000..95c59f5efb --- /dev/null +++ b/Headers/GNUstepBase/GSScriptingStepTalkBridge.h @@ -0,0 +1,166 @@ +/* Interface for NSScriptingStepTalkBridge for GNUstep + Copyright (C) 2025 Free Software Foundation, Inc. + + Written by: + Created: 2025 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 Suite 500, USA. +*/ + +#ifndef __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE +#define __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE + +#import + +#if defined(__cplusplus) +extern "C" { +#endif + +@class NSString; +@class NSDictionary; +@class NSMutableDictionary; +@class NSArray; +@class NSScriptCommand; +@class NSScriptObjectSpecifier; + +/** + * GSScriptingStepTalkBridge provides a bridge between StepTalk scripting + * and the GNUstep NSScripting framework. This allows StepTalk scripts to + * create and execute script commands, build object specifiers, and interact + * with scriptable applications. + * + * Example StepTalk usage: + * + * | bridge command | + * bridge := GSScriptingStepTalkBridge sharedBridge. + * command := bridge + * createCommand: 'get' + * forSuite: 'CoreSuite' + * withArguments: nil. + * command executeCommand. + * + */ +@interface GSScriptingStepTalkBridge : NSObject +{ + @private + NSMutableDictionary *_commandRegistry; + NSMutableDictionary *_cachedSpecifiers; +} + +/** + * Returns the shared bridge instance. + */ ++ (instancetype) sharedBridge; + +/** + * Creates an NSScriptCommand instance from StepTalk parameters. + * This is the primary method for StepTalk scripts to create commands. + * + * @param commandName The name of the command (e.g., @"get", @"set", @"create") + * @param suiteName The suite name (e.g., @"CoreSuite", @"TextSuite") + * @param arguments A dictionary of command arguments + * @return A newly created NSScriptCommand instance + */ +- (NSScriptCommand *) createCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments; + +/** + * Creates an object specifier from StepTalk parameters. + * This allows StepTalk scripts to build complex object specifiers. + * + * @param specifierType The type of specifier (@"index", @"name", @"property", etc.) + * @param containerSpecifier The container specifier (or nil for application) + * @param key The key or property name + * @param value The value (index, name, etc.) + * @return A newly created NSScriptObjectSpecifier instance + */ +- (NSScriptObjectSpecifier *) createSpecifier:(NSString *)specifierType + inContainer:(NSScriptObjectSpecifier *)containerSpecifier + forKey:(NSString *)key + withValue:(id)value; + +/** + * Executes a command and returns the result. + * This is a convenience method that creates and executes a command in one step. + * + * @param commandName The command name + * @param suiteName The suite name + * @param arguments The command arguments + * @return The result of executing the command + */ +- (id) executeCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments; + +/** + * Convenience method to get an object by name. + */ +- (id) getObject:(NSString *)objectName + ofType:(NSString *)typeName + fromContainer:(id)container; + +/** + * Convenience method to set a property value. + */ +- (void) setProperty:(NSString *)propertyName + ofObject:(id)object + toValue:(id)value; + +/** + * Convenience method to create a new object. + */ +- (id) createObject:(NSString *)typeName + atLocation:(id)location + withProperties:(NSDictionary *)properties; + +/** + * Convenience method to delete an object. + */ +- (void) deleteObject:(id)object; + +/** + * Convenience method to count objects. + */ +- (NSInteger) countObjects:(NSString *)typeName + inContainer:(id)container; + +/** + * Registers a custom command handler for StepTalk. + * This allows extending the bridge with custom commands. + * + * @param handler A block or invocation that handles the command + * @param commandName The command name to register + * @param suiteName The suite name + */ +- (void) registerCommandHandler:(id)handler + forCommand:(NSString *)commandName + inSuite:(NSString *)suiteName; + +/** + * Clears cached specifiers and command handlers. + */ +- (void) clearCache; + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE */ diff --git a/Headers/GNUstepBase/NSScriptingStepTalkBridge.h b/Headers/GNUstepBase/NSScriptingStepTalkBridge.h new file mode 100644 index 0000000000..95c59f5efb --- /dev/null +++ b/Headers/GNUstepBase/NSScriptingStepTalkBridge.h @@ -0,0 +1,166 @@ +/* Interface for NSScriptingStepTalkBridge for GNUstep + Copyright (C) 2025 Free Software Foundation, Inc. + + Written by: + Created: 2025 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 Suite 500, USA. +*/ + +#ifndef __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE +#define __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE + +#import + +#if defined(__cplusplus) +extern "C" { +#endif + +@class NSString; +@class NSDictionary; +@class NSMutableDictionary; +@class NSArray; +@class NSScriptCommand; +@class NSScriptObjectSpecifier; + +/** + * GSScriptingStepTalkBridge provides a bridge between StepTalk scripting + * and the GNUstep NSScripting framework. This allows StepTalk scripts to + * create and execute script commands, build object specifiers, and interact + * with scriptable applications. + * + * Example StepTalk usage: + * + * | bridge command | + * bridge := GSScriptingStepTalkBridge sharedBridge. + * command := bridge + * createCommand: 'get' + * forSuite: 'CoreSuite' + * withArguments: nil. + * command executeCommand. + * + */ +@interface GSScriptingStepTalkBridge : NSObject +{ + @private + NSMutableDictionary *_commandRegistry; + NSMutableDictionary *_cachedSpecifiers; +} + +/** + * Returns the shared bridge instance. + */ ++ (instancetype) sharedBridge; + +/** + * Creates an NSScriptCommand instance from StepTalk parameters. + * This is the primary method for StepTalk scripts to create commands. + * + * @param commandName The name of the command (e.g., @"get", @"set", @"create") + * @param suiteName The suite name (e.g., @"CoreSuite", @"TextSuite") + * @param arguments A dictionary of command arguments + * @return A newly created NSScriptCommand instance + */ +- (NSScriptCommand *) createCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments; + +/** + * Creates an object specifier from StepTalk parameters. + * This allows StepTalk scripts to build complex object specifiers. + * + * @param specifierType The type of specifier (@"index", @"name", @"property", etc.) + * @param containerSpecifier The container specifier (or nil for application) + * @param key The key or property name + * @param value The value (index, name, etc.) + * @return A newly created NSScriptObjectSpecifier instance + */ +- (NSScriptObjectSpecifier *) createSpecifier:(NSString *)specifierType + inContainer:(NSScriptObjectSpecifier *)containerSpecifier + forKey:(NSString *)key + withValue:(id)value; + +/** + * Executes a command and returns the result. + * This is a convenience method that creates and executes a command in one step. + * + * @param commandName The command name + * @param suiteName The suite name + * @param arguments The command arguments + * @return The result of executing the command + */ +- (id) executeCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments; + +/** + * Convenience method to get an object by name. + */ +- (id) getObject:(NSString *)objectName + ofType:(NSString *)typeName + fromContainer:(id)container; + +/** + * Convenience method to set a property value. + */ +- (void) setProperty:(NSString *)propertyName + ofObject:(id)object + toValue:(id)value; + +/** + * Convenience method to create a new object. + */ +- (id) createObject:(NSString *)typeName + atLocation:(id)location + withProperties:(NSDictionary *)properties; + +/** + * Convenience method to delete an object. + */ +- (void) deleteObject:(id)object; + +/** + * Convenience method to count objects. + */ +- (NSInteger) countObjects:(NSString *)typeName + inContainer:(id)container; + +/** + * Registers a custom command handler for StepTalk. + * This allows extending the bridge with custom commands. + * + * @param handler A block or invocation that handles the command + * @param commandName The command name to register + * @param suiteName The suite name + */ +- (void) registerCommandHandler:(id)handler + forCommand:(NSString *)commandName + inSuite:(NSString *)suiteName; + +/** + * Clears cached specifiers and command handlers. + */ +- (void) clearCache; + +@end + +#if defined(__cplusplus) +} +#endif + +#endif /* __GSScriptingStepTalkBridge_h_GNUSTEP_BASE_INCLUDE */ diff --git a/MIGRATION-NSScriptingStepTalkBridge.md b/MIGRATION-NSScriptingStepTalkBridge.md new file mode 100644 index 0000000000..34f231148e --- /dev/null +++ b/MIGRATION-NSScriptingStepTalkBridge.md @@ -0,0 +1,44 @@ +# NSScriptingStepTalkBridge Migration to GNUstepBase + +## Overview + +Moved NSScriptingStepTalkBridge from Foundation to GNUstepBase headers, as this is a GNUstep-specific integration feature rather than a standard Foundation class. + +## Changes Made + +### File Locations + +- **Header**: `Headers/GNUstepBase/NSScriptingStepTalkBridge.h` (moved from Headers/Foundation/) +- **Implementation**: `Source/NSScriptingStepTalkBridge.m` (unchanged) + +### Updated Files + +1. **Source/NSScriptingStepTalkBridge.m** + - Changed import from `"Foundation/NSScriptingStepTalkBridge.h"` to `"GNUstepBase/NSScriptingStepTalkBridge.h"` + +2. **Headers/Foundation/Foundation.h** + - Removed: `#import ` + +3. **Source/GNUmakefile** + - Removed `NSScriptingStepTalkBridge.h` from Foundation headers list + - Added `NSScriptingStepTalkBridge.h` to GNUstepBase headers list (after NSProcessInfo+GNUstepBase.h) + +4. **Source/DocMakefile** + - Removed `NSScriptingStepTalkBridge.h` from Foundation headers documentation list + - Added `NSScriptingStepTalkBridge.h` to GNUstepBase headers documentation list (after NSProcessInfo+GNUstepBase.h) + +## Usage + +Applications using the bridge should now import it via: + +```objc +#import +``` + +## Rationale + +NSScriptingStepTalkBridge is a GNUstep-specific extension that bridges StepTalk (GNUstep's Smalltalk-based scripting) with the NSScripting framework. Since it's not part of Apple's Foundation framework and is specific to GNUstep, it belongs in GNUstepBase alongside other GNUstep-specific extensions like NSObject+GNUstepBase.h. + +## Build System + +The class is still compiled into gnustep-base library and exported, just categorized correctly as a GNUstepBase extension rather than a Foundation class. diff --git a/Source/DocMakefile b/Source/DocMakefile index ca09cd9087..c0791e06ba 100644 --- a/Source/DocMakefile +++ b/Source/DocMakefile @@ -236,6 +236,7 @@ NSMutableString+GNUstepBase.h \ NSNumber+GNUstepBase.h \ NSObject+GNUstepBase.h \ NSProcessInfo+GNUstepBase.h \ +GSScriptingStepTalkBridge.h \ NSString+GNUstepBase.h \ NSTask+GNUstepBase.h \ NSThread+GNUstepBase.h \ diff --git a/Source/GNUmakefile b/Source/GNUmakefile index 068168ffea..42a6f1d6d4 100644 --- a/Source/GNUmakefile +++ b/Source/GNUmakefile @@ -146,6 +146,7 @@ NSNetServices+GNUstepBase.h \ NSNumber+GNUstepBase.h \ NSObject+GNUstepBase.h \ NSProcessInfo+GNUstepBase.h \ +GSScriptingStepTalkBridge.h \ NSStream+GNUstepBase.h \ NSString+GNUstepBase.h \ NSTask+GNUstepBase.h \ @@ -310,6 +311,7 @@ NSScriptCommandDescription.m \ NSScriptExecutionContext.m \ NSScriptKeyValueCoding.m \ NSScriptObjectSpecifier.m \ +GSScriptingStepTalkBridge.m \ NSScriptStandardSuiteCommands.m \ NSScriptSuiteRegistry.m \ NSUnit.m \ diff --git a/Source/GSScriptingStepTalkBridge.m b/Source/GSScriptingStepTalkBridge.m new file mode 100644 index 0000000000..dd1a58408d --- /dev/null +++ b/Source/GSScriptingStepTalkBridge.m @@ -0,0 +1,530 @@ +/* Implementation of GSScriptingStepTalkBridge for GNUstep + Copyright (C) 2025 Free Software Foundation, Inc. + + Written by: + Created: 2025 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 Suite 500, USA. +*/ + +#import "common.h" +#import "GNUstepBase/GSScriptingStepTalkBridge.h" +#import "Foundation/NSScriptCommand.h" +#import "Foundation/NSScriptObjectSpecifier.h" +#import "Foundation/NSScriptSuiteRegistry.h" +#import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSString.h" +#import "Foundation/NSException.h" +#import "Foundation/NSValue.h" +#import "Foundation/NSInvocation.h" + +static GSScriptingStepTalkBridge *sharedInstance = nil; + +@implementation GSScriptingStepTalkBridge + ++ (void) initialize +{ + if (self == [GSScriptingStepTalkBridge class]) + { + sharedInstance = [[GSScriptingStepTalkBridge alloc] init]; + } +} + ++ (instancetype) sharedBridge +{ + return sharedInstance; +} + +- (id) init +{ + self = [super init]; + if (self != nil) + { + _commandRegistry = [[NSMutableDictionary alloc] init]; + _cachedSpecifiers = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_commandRegistry); + RELEASE(_cachedSpecifiers); + [super dealloc]; +} + +- (NSScriptCommand *) createCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments +{ + NSScriptSuiteRegistry *registry; + NSScriptCommandDescription *commandDesc; + Class commandClass; + NSScriptCommand *command; + + if (commandName == nil || suiteName == nil) + { + [NSException raise:NSInvalidArgumentException + format:@"Command name and suite name must not be nil"]; + return nil; + } + + registry = [NSScriptSuiteRegistry sharedScriptSuiteRegistry]; + + /* Look up the command description */ + commandDesc = [registry commandDescriptionWithName:commandName + inSuite:suiteName]; + + if (commandDesc == nil) + { + NSLog(@"Warning: Command '%@' not found in suite '%@'", + commandName, suiteName); + /* Try to create a generic NSScriptCommand */ + commandClass = [NSScriptCommand class]; + } + else + { + commandClass = [commandDesc commandClassName]; + if (commandClass == Nil) + { + commandClass = [NSScriptCommand class]; + } + } + + /* Create the command instance */ + command = [[commandClass alloc] init]; + + /* Set command arguments if provided */ + if (arguments != nil) + { + NSEnumerator *keyEnum; + NSString *key; + + keyEnum = [arguments keyEnumerator]; + while ((key = [keyEnum nextObject]) != nil) + { + id value; + + value = [arguments objectForKey:key]; + + /* Handle special argument keys */ + if ([key isEqualToString:@"DirectParameter"]) + { + [command setDirectParameter:value]; + } + else if ([key isEqualToString:@"ObjectSpecifier"]) + { + if ([value isKindOfClass:[NSScriptObjectSpecifier class]]) + { + [command setReceiversSpecifier:value]; + } + } + else + { + [command setArgument:value forKey:key]; + } + } + } + + return AUTORELEASE(command); +} + +- (NSScriptObjectSpecifier *) createSpecifier:(NSString *)specifierType + inContainer:(NSScriptObjectSpecifier *)containerSpecifier + forKey:(NSString *)key + withValue:(id)value +{ + NSScriptObjectSpecifier *specifier; + Class specifierClass; + + if (specifierType == nil || key == nil) + { + [NSException raise:NSInvalidArgumentException + format:@"Specifier type and key must not be nil"]; + return nil; + } + + specifier = nil; + + /* Determine the specifier class based on type */ + if ([specifierType isEqualToString:@"index"]) + { + specifierClass = [NSIndexSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value respondsToSelector:@selector(integerValue)]) + { + [(NSIndexSpecifier *)specifier setIndex:[value integerValue]]; + } + } + else if ([specifierType isEqualToString:@"name"]) + { + specifierClass = [NSNameSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value isKindOfClass:[NSString class]]) + { + [(NSNameSpecifier *)specifier setName:value]; + } + } + else if ([specifierType isEqualToString:@"property"]) + { + specifierClass = [NSPropertySpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"uniqueID"]) + { + specifierClass = [NSUniqueIDSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + [(NSUniqueIDSpecifier *)specifier setUniqueID:value]; + } + else if ([specifierType isEqualToString:@"range"]) + { + specifierClass = [NSRangeSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + /* Value should be a dictionary with startSpecifier and endSpecifier */ + if ([value isKindOfClass:[NSDictionary class]]) + { + id startSpec; + id endSpec; + + startSpec = [value objectForKey:@"start"]; + endSpec = [value objectForKey:@"end"]; + + if (startSpec != nil) + { + [(NSRangeSpecifier *)specifier setStartSpecifier:startSpec]; + } + if (endSpec != nil) + { + [(NSRangeSpecifier *)specifier setEndSpecifier:endSpec]; + } + } + } + else if ([specifierType isEqualToString:@"middle"]) + { + specifierClass = [NSMiddleSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"random"]) + { + specifierClass = [NSRandomSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"relative"]) + { + specifierClass = [NSRelativeSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value isKindOfClass:[NSDictionary class]]) + { + id baseSpec; + NSString *position; + + baseSpec = [value objectForKey:@"base"]; + position = [value objectForKey:@"position"]; + + if (baseSpec != nil) + { + [(NSRelativeSpecifier *)specifier setBaseSpecifier:baseSpec]; + } + if (position != nil) + { + if ([position isEqualToString:@"before"]) + { + [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeBefore]; + } + else if ([position isEqualToString:@"after"]) + { + [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeAfter]; + } + } + } + } + else + { + /* Default to property specifier */ + specifierClass = [NSPropertySpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + + return AUTORELEASE(specifier); +} + +- (id) executeCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments +{ + NSScriptCommand *command; + id result; + + command = [self createCommand:commandName + forSuite:suiteName + withArguments:arguments]; + + if (command == nil) + { + return nil; + } + + result = [command executeCommand]; + + /* Check for errors */ + if ([command scriptErrorNumber] != 0) + { + NSLog(@"Script command error %ld: %@", + (long)[command scriptErrorNumber], + [command scriptErrorString]); + } + + return result; +} + +- (id) getObject:(NSString *)objectName + ofType:(NSString *)typeName + fromContainer:(id)container +{ + NSScriptObjectSpecifier *containerSpec; + NSScriptObjectSpecifier *objectSpec; + NSScriptCommand *getCommand; + NSDictionary *arguments; + id result; + + /* Build the container specifier */ + if (container == nil) + { + containerSpec = nil; /* Application container */ + } + else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) + { + containerSpec = container; + } + else + { + /* Try to create a specifier from the container */ + containerSpec = nil; + } + + /* Build the object specifier */ + objectSpec = [self createSpecifier:@"name" + inContainer:containerSpec + forKey:typeName + withValue:objectName]; + + /* Create and execute get command */ + arguments = [NSDictionary dictionaryWithObject:objectSpec + forKey:@"ObjectSpecifier"]; + + getCommand = [self createCommand:@"get" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [getCommand executeCommand]; + + return result; +} + +- (void) setProperty:(NSString *)propertyName + ofObject:(id)object + toValue:(id)value +{ + NSScriptObjectSpecifier *objectSpec; + NSScriptObjectSpecifier *propertySpec; + NSScriptCommand *setCommand; + NSDictionary *arguments; + + /* Build the object specifier */ + if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) + { + objectSpec = object; + } + else + { + /* Assume object is the container */ + objectSpec = nil; + } + + /* Build the property specifier */ + propertySpec = [self createSpecifier:@"property" + inContainer:objectSpec + forKey:propertyName + withValue:nil]; + + /* Create and execute set command */ + arguments = [NSDictionary dictionaryWithObjectsAndKeys: + propertySpec, @"ObjectSpecifier", + value, @"DirectParameter", + nil]; + + setCommand = [self createCommand:@"set" + forSuite:@"CoreSuite" + withArguments:arguments]; + + [setCommand executeCommand]; +} + +- (id) createObject:(NSString *)typeName + atLocation:(id)location + withProperties:(NSDictionary *)properties +{ + NSScriptObjectSpecifier *locationSpec; + NSScriptCommand *createCommand; + NSMutableDictionary *arguments; + id result; + + /* Build the location specifier */ + if (location == nil) + { + locationSpec = nil; + } + else if ([location isKindOfClass:[NSScriptObjectSpecifier class]]) + { + locationSpec = location; + } + else + { + locationSpec = nil; + } + + /* Create arguments */ + arguments = [NSMutableDictionary dictionaryWithCapacity:3]; + [arguments setObject:typeName forKey:@"ObjectClass"]; + + if (locationSpec != nil) + { + [arguments setObject:locationSpec forKey:@"Location"]; + } + + if (properties != nil) + { + [arguments setObject:properties forKey:@"WithProperties"]; + } + + /* Create and execute create command */ + createCommand = [self createCommand:@"create" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [createCommand executeCommand]; + + return result; +} + +- (void) deleteObject:(id)object +{ + NSScriptObjectSpecifier *objectSpec; + NSScriptCommand *deleteCommand; + NSDictionary *arguments; + + /* Build the object specifier */ + if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) + { + objectSpec = object; + } + else + { + /* Can't delete without a proper specifier */ + NSLog(@"Warning: Cannot delete object without specifier"); + return; + } + + /* Create and execute delete command */ + arguments = [NSDictionary dictionaryWithObject:objectSpec + forKey:@"ObjectSpecifier"]; + + deleteCommand = [self createCommand:@"delete" + forSuite:@"CoreSuite" + withArguments:arguments]; + + [deleteCommand executeCommand]; +} + +- (NSInteger) countObjects:(NSString *)typeName + inContainer:(id)container +{ + NSScriptObjectSpecifier *containerSpec; + NSScriptCommand *countCommand; + NSDictionary *arguments; + id result; + NSInteger count; + + /* Build the container specifier */ + if (container == nil) + { + containerSpec = nil; + } + else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) + { + containerSpec = container; + } + else + { + containerSpec = nil; + } + + /* Create and execute count command */ + arguments = [NSDictionary dictionaryWithObjectsAndKeys: + typeName, @"ObjectClass", + containerSpec, @"ObjectSpecifier", + nil]; + + countCommand = [self createCommand:@"count" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [countCommand executeCommand]; + + count = 0; + if ([result respondsToSelector:@selector(integerValue)]) + { + count = [result integerValue]; + } + + return count; +} + +- (void) registerCommandHandler:(id)handler + forCommand:(NSString *)commandName + inSuite:(NSString *)suiteName +{ + NSString *key; + + if (handler == nil || commandName == nil || suiteName == nil) + { + return; + } + + key = [NSString stringWithFormat:@"%@.%@", suiteName, commandName]; + [_commandRegistry setObject:handler forKey:key]; +} + +- (void) clearCache +{ + [_cachedSpecifiers removeAllObjects]; +} + +@end diff --git a/Source/NSScriptingStepTalkBridge.m b/Source/NSScriptingStepTalkBridge.m new file mode 100644 index 0000000000..dd1a58408d --- /dev/null +++ b/Source/NSScriptingStepTalkBridge.m @@ -0,0 +1,530 @@ +/* Implementation of GSScriptingStepTalkBridge for GNUstep + Copyright (C) 2025 Free Software Foundation, Inc. + + Written by: + Created: 2025 + + This file is part of the GNUstep Base Library. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110 Suite 500, USA. +*/ + +#import "common.h" +#import "GNUstepBase/GSScriptingStepTalkBridge.h" +#import "Foundation/NSScriptCommand.h" +#import "Foundation/NSScriptObjectSpecifier.h" +#import "Foundation/NSScriptSuiteRegistry.h" +#import "Foundation/NSScriptClassDescription.h" +#import "Foundation/NSScriptCommandDescription.h" +#import "Foundation/NSDictionary.h" +#import "Foundation/NSArray.h" +#import "Foundation/NSString.h" +#import "Foundation/NSException.h" +#import "Foundation/NSValue.h" +#import "Foundation/NSInvocation.h" + +static GSScriptingStepTalkBridge *sharedInstance = nil; + +@implementation GSScriptingStepTalkBridge + ++ (void) initialize +{ + if (self == [GSScriptingStepTalkBridge class]) + { + sharedInstance = [[GSScriptingStepTalkBridge alloc] init]; + } +} + ++ (instancetype) sharedBridge +{ + return sharedInstance; +} + +- (id) init +{ + self = [super init]; + if (self != nil) + { + _commandRegistry = [[NSMutableDictionary alloc] init]; + _cachedSpecifiers = [[NSMutableDictionary alloc] init]; + } + return self; +} + +- (void) dealloc +{ + RELEASE(_commandRegistry); + RELEASE(_cachedSpecifiers); + [super dealloc]; +} + +- (NSScriptCommand *) createCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments +{ + NSScriptSuiteRegistry *registry; + NSScriptCommandDescription *commandDesc; + Class commandClass; + NSScriptCommand *command; + + if (commandName == nil || suiteName == nil) + { + [NSException raise:NSInvalidArgumentException + format:@"Command name and suite name must not be nil"]; + return nil; + } + + registry = [NSScriptSuiteRegistry sharedScriptSuiteRegistry]; + + /* Look up the command description */ + commandDesc = [registry commandDescriptionWithName:commandName + inSuite:suiteName]; + + if (commandDesc == nil) + { + NSLog(@"Warning: Command '%@' not found in suite '%@'", + commandName, suiteName); + /* Try to create a generic NSScriptCommand */ + commandClass = [NSScriptCommand class]; + } + else + { + commandClass = [commandDesc commandClassName]; + if (commandClass == Nil) + { + commandClass = [NSScriptCommand class]; + } + } + + /* Create the command instance */ + command = [[commandClass alloc] init]; + + /* Set command arguments if provided */ + if (arguments != nil) + { + NSEnumerator *keyEnum; + NSString *key; + + keyEnum = [arguments keyEnumerator]; + while ((key = [keyEnum nextObject]) != nil) + { + id value; + + value = [arguments objectForKey:key]; + + /* Handle special argument keys */ + if ([key isEqualToString:@"DirectParameter"]) + { + [command setDirectParameter:value]; + } + else if ([key isEqualToString:@"ObjectSpecifier"]) + { + if ([value isKindOfClass:[NSScriptObjectSpecifier class]]) + { + [command setReceiversSpecifier:value]; + } + } + else + { + [command setArgument:value forKey:key]; + } + } + } + + return AUTORELEASE(command); +} + +- (NSScriptObjectSpecifier *) createSpecifier:(NSString *)specifierType + inContainer:(NSScriptObjectSpecifier *)containerSpecifier + forKey:(NSString *)key + withValue:(id)value +{ + NSScriptObjectSpecifier *specifier; + Class specifierClass; + + if (specifierType == nil || key == nil) + { + [NSException raise:NSInvalidArgumentException + format:@"Specifier type and key must not be nil"]; + return nil; + } + + specifier = nil; + + /* Determine the specifier class based on type */ + if ([specifierType isEqualToString:@"index"]) + { + specifierClass = [NSIndexSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value respondsToSelector:@selector(integerValue)]) + { + [(NSIndexSpecifier *)specifier setIndex:[value integerValue]]; + } + } + else if ([specifierType isEqualToString:@"name"]) + { + specifierClass = [NSNameSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value isKindOfClass:[NSString class]]) + { + [(NSNameSpecifier *)specifier setName:value]; + } + } + else if ([specifierType isEqualToString:@"property"]) + { + specifierClass = [NSPropertySpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"uniqueID"]) + { + specifierClass = [NSUniqueIDSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + [(NSUniqueIDSpecifier *)specifier setUniqueID:value]; + } + else if ([specifierType isEqualToString:@"range"]) + { + specifierClass = [NSRangeSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + /* Value should be a dictionary with startSpecifier and endSpecifier */ + if ([value isKindOfClass:[NSDictionary class]]) + { + id startSpec; + id endSpec; + + startSpec = [value objectForKey:@"start"]; + endSpec = [value objectForKey:@"end"]; + + if (startSpec != nil) + { + [(NSRangeSpecifier *)specifier setStartSpecifier:startSpec]; + } + if (endSpec != nil) + { + [(NSRangeSpecifier *)specifier setEndSpecifier:endSpec]; + } + } + } + else if ([specifierType isEqualToString:@"middle"]) + { + specifierClass = [NSMiddleSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"random"]) + { + specifierClass = [NSRandomSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + else if ([specifierType isEqualToString:@"relative"]) + { + specifierClass = [NSRelativeSpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + if ([value isKindOfClass:[NSDictionary class]]) + { + id baseSpec; + NSString *position; + + baseSpec = [value objectForKey:@"base"]; + position = [value objectForKey:@"position"]; + + if (baseSpec != nil) + { + [(NSRelativeSpecifier *)specifier setBaseSpecifier:baseSpec]; + } + if (position != nil) + { + if ([position isEqualToString:@"before"]) + { + [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeBefore]; + } + else if ([position isEqualToString:@"after"]) + { + [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeAfter]; + } + } + } + } + else + { + /* Default to property specifier */ + specifierClass = [NSPropertySpecifier class]; + specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier + key:key]; + } + + return AUTORELEASE(specifier); +} + +- (id) executeCommand:(NSString *)commandName + forSuite:(NSString *)suiteName + withArguments:(NSDictionary *)arguments +{ + NSScriptCommand *command; + id result; + + command = [self createCommand:commandName + forSuite:suiteName + withArguments:arguments]; + + if (command == nil) + { + return nil; + } + + result = [command executeCommand]; + + /* Check for errors */ + if ([command scriptErrorNumber] != 0) + { + NSLog(@"Script command error %ld: %@", + (long)[command scriptErrorNumber], + [command scriptErrorString]); + } + + return result; +} + +- (id) getObject:(NSString *)objectName + ofType:(NSString *)typeName + fromContainer:(id)container +{ + NSScriptObjectSpecifier *containerSpec; + NSScriptObjectSpecifier *objectSpec; + NSScriptCommand *getCommand; + NSDictionary *arguments; + id result; + + /* Build the container specifier */ + if (container == nil) + { + containerSpec = nil; /* Application container */ + } + else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) + { + containerSpec = container; + } + else + { + /* Try to create a specifier from the container */ + containerSpec = nil; + } + + /* Build the object specifier */ + objectSpec = [self createSpecifier:@"name" + inContainer:containerSpec + forKey:typeName + withValue:objectName]; + + /* Create and execute get command */ + arguments = [NSDictionary dictionaryWithObject:objectSpec + forKey:@"ObjectSpecifier"]; + + getCommand = [self createCommand:@"get" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [getCommand executeCommand]; + + return result; +} + +- (void) setProperty:(NSString *)propertyName + ofObject:(id)object + toValue:(id)value +{ + NSScriptObjectSpecifier *objectSpec; + NSScriptObjectSpecifier *propertySpec; + NSScriptCommand *setCommand; + NSDictionary *arguments; + + /* Build the object specifier */ + if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) + { + objectSpec = object; + } + else + { + /* Assume object is the container */ + objectSpec = nil; + } + + /* Build the property specifier */ + propertySpec = [self createSpecifier:@"property" + inContainer:objectSpec + forKey:propertyName + withValue:nil]; + + /* Create and execute set command */ + arguments = [NSDictionary dictionaryWithObjectsAndKeys: + propertySpec, @"ObjectSpecifier", + value, @"DirectParameter", + nil]; + + setCommand = [self createCommand:@"set" + forSuite:@"CoreSuite" + withArguments:arguments]; + + [setCommand executeCommand]; +} + +- (id) createObject:(NSString *)typeName + atLocation:(id)location + withProperties:(NSDictionary *)properties +{ + NSScriptObjectSpecifier *locationSpec; + NSScriptCommand *createCommand; + NSMutableDictionary *arguments; + id result; + + /* Build the location specifier */ + if (location == nil) + { + locationSpec = nil; + } + else if ([location isKindOfClass:[NSScriptObjectSpecifier class]]) + { + locationSpec = location; + } + else + { + locationSpec = nil; + } + + /* Create arguments */ + arguments = [NSMutableDictionary dictionaryWithCapacity:3]; + [arguments setObject:typeName forKey:@"ObjectClass"]; + + if (locationSpec != nil) + { + [arguments setObject:locationSpec forKey:@"Location"]; + } + + if (properties != nil) + { + [arguments setObject:properties forKey:@"WithProperties"]; + } + + /* Create and execute create command */ + createCommand = [self createCommand:@"create" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [createCommand executeCommand]; + + return result; +} + +- (void) deleteObject:(id)object +{ + NSScriptObjectSpecifier *objectSpec; + NSScriptCommand *deleteCommand; + NSDictionary *arguments; + + /* Build the object specifier */ + if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) + { + objectSpec = object; + } + else + { + /* Can't delete without a proper specifier */ + NSLog(@"Warning: Cannot delete object without specifier"); + return; + } + + /* Create and execute delete command */ + arguments = [NSDictionary dictionaryWithObject:objectSpec + forKey:@"ObjectSpecifier"]; + + deleteCommand = [self createCommand:@"delete" + forSuite:@"CoreSuite" + withArguments:arguments]; + + [deleteCommand executeCommand]; +} + +- (NSInteger) countObjects:(NSString *)typeName + inContainer:(id)container +{ + NSScriptObjectSpecifier *containerSpec; + NSScriptCommand *countCommand; + NSDictionary *arguments; + id result; + NSInteger count; + + /* Build the container specifier */ + if (container == nil) + { + containerSpec = nil; + } + else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) + { + containerSpec = container; + } + else + { + containerSpec = nil; + } + + /* Create and execute count command */ + arguments = [NSDictionary dictionaryWithObjectsAndKeys: + typeName, @"ObjectClass", + containerSpec, @"ObjectSpecifier", + nil]; + + countCommand = [self createCommand:@"count" + forSuite:@"CoreSuite" + withArguments:arguments]; + + result = [countCommand executeCommand]; + + count = 0; + if ([result respondsToSelector:@selector(integerValue)]) + { + count = [result integerValue]; + } + + return count; +} + +- (void) registerCommandHandler:(id)handler + forCommand:(NSString *)commandName + inSuite:(NSString *)suiteName +{ + NSString *key; + + if (handler == nil || commandName == nil || suiteName == nil) + { + return; + } + + key = [NSString stringWithFormat:@"%@.%@", suiteName, commandName]; + [_commandRegistry setObject:handler forKey:key]; +} + +- (void) clearCache +{ + [_cachedSpecifiers removeAllObjects]; +} + +@end From dde5bbdbad4747ef4e3f9fe28d9a34f9255fe66d Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 19:41:53 -0500 Subject: [PATCH 08/20] Update scripting coercion handler to use instance variables for storage. --- Source/NSScriptKeyValueCoding.m | 74 ++++------------------- Tests/base/NSScriptKeyValueCoding/basic.m | 18 ++++-- 2 files changed, 25 insertions(+), 67 deletions(-) diff --git a/Source/NSScriptKeyValueCoding.m b/Source/NSScriptKeyValueCoding.m index df6a02b51c..787f2e219e 100644 --- a/Source/NSScriptKeyValueCoding.m +++ b/Source/NSScriptKeyValueCoding.m @@ -109,27 +109,10 @@ - (id) valueWithUniqueID: (id)uniqueID inPropertyWithKey: (NSString *)key - (void) insertValue: (id)value atIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key { - NSString *insertSel; - SEL selector; - - insertSel = [NSString stringWithFormat: @"insertIn%@:atIndex:", - [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) - withString: [[key substringToIndex: 1] uppercaseString]]]; - selector = NSSelectorFromString(insertSel); + NSMutableArray *array; - if ([self respondsToSelector: selector]) - { - [self performSelector: selector withObject: value withObject: [NSNumber numberWithUnsignedInteger: index]]; - } - else - { - NSMutableArray *array; - - array = [[self mutableArrayValueForKey: key] retain]; - [array insertObject: value atIndex: index]; - [self setValue: array forKey: key]; - RELEASE(array); - } + array = [self mutableArrayValueForKey: key]; + [array insertObject: value atIndex: index]; } - (void) insertValue: (id)value inPropertyWithKey: (NSString *)key @@ -147,29 +130,12 @@ - (id) coerceValue: (id)value forKey: (NSString *)key - (void) removeValueAtIndex: (NSUInteger)index fromPropertyWithKey: (NSString *)key { - NSString *removeSel; - SEL selector; - - removeSel = [NSString stringWithFormat: @"removeFrom%@AtIndex:", - [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) - withString: [[key substringToIndex: 1] uppercaseString]]]; - selector = NSSelectorFromString(removeSel); + NSMutableArray *array; - if ([self respondsToSelector: selector]) - { - [self performSelector: selector withObject: [NSNumber numberWithUnsignedInteger: index]]; - } - else + array = [self mutableArrayValueForKey: key]; + if (index < [array count]) { - NSMutableArray *array; - - array = [[self mutableArrayValueForKey: key] retain]; - if (index < [array count]) - { - [array removeObjectAtIndex: index]; - [self setValue: array forKey: key]; - } - RELEASE(array); + [array removeObjectAtIndex: index]; } } @@ -177,31 +143,13 @@ - (void) replaceValueAtIndex: (NSUInteger)index inPropertyWithKey: (NSString *)key withValue: (id)value { - NSString *replaceSel; - SEL selector; - - replaceSel = [NSString stringWithFormat: @"replaceIn%@:atIndex:withObject:", - [key stringByReplacingCharactersInRange: NSMakeRange(0, 1) - withString: [[key substringToIndex: 1] uppercaseString]]]; - selector = NSSelectorFromString(replaceSel); + NSMutableArray *array; - if ([self respondsToSelector: selector]) - { - [self performSelector: selector withObject: [NSNumber numberWithUnsignedInteger: index] withObject: value]; - } - else + array = [self mutableArrayValueForKey: key]; + if (index < [array count]) { - NSMutableArray *array; - - array = [[self mutableArrayValueForKey: key] retain]; - if (index < [array count]) - { - [array replaceObjectAtIndex: index withObject: value]; - [self setValue: array forKey: key]; - } - RELEASE(array); + [array replaceObjectAtIndex: index withObject: value]; } } @end - diff --git a/Tests/base/NSScriptKeyValueCoding/basic.m b/Tests/base/NSScriptKeyValueCoding/basic.m index 0cb80a2dc2..d05ff87735 100644 --- a/Tests/base/NSScriptKeyValueCoding/basic.m +++ b/Tests/base/NSScriptKeyValueCoding/basic.m @@ -85,18 +85,21 @@ - (void) setItems: (NSArray *)items int main() { + NSAutoreleasePool *pool; TestContainer *container; TestItem *item1; TestItem *item2; TestItem *item3; id result; + pool = [NSAutoreleasePool new]; + START_SET("NSScriptKeyValueCoding value access"); - container = AUTORELEASE([[TestContainer alloc] init]); - item1 = AUTORELEASE([[TestItem alloc] initWithName: @"First" uniqueID: @"1"]); - item2 = AUTORELEASE([[TestItem alloc] initWithName: @"Second" uniqueID: @"2"]); - item3 = AUTORELEASE([[TestItem alloc] initWithName: @"Third" uniqueID: @"3"]); + container = [[TestContainer alloc] init]; + item1 = [[TestItem alloc] initWithName: @"First" uniqueID: @"1"]; + item2 = [[TestItem alloc] initWithName: @"Second" uniqueID: @"2"]; + item3 = [[TestItem alloc] initWithName: @"Third" uniqueID: @"3"]; [container setItems: [NSArray arrayWithObjects: item1, item2, item3, nil]]; @@ -195,5 +198,12 @@ int main() END_SET("NSScriptKeyValueCoding coercion"); + // Clean up manually allocated objects + [container release]; + [item1 release]; + [item2 release]; + [item3 release]; + + [pool release]; return 0; } From cf6869570dd7425e47233e9a005a3b3865ea49e2 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 19:49:07 -0500 Subject: [PATCH 09/20] Fixes for NSScriptClassDescription and test --- Source/NSScriptClassDescription.m | 10 + Source/NSScriptingStepTalkBridge.m | 530 -------------------- Tests/base/NSScriptClassDescription/basic.m | 42 +- 3 files changed, 36 insertions(+), 546 deletions(-) delete mode 100644 Source/NSScriptingStepTalkBridge.m diff --git a/Source/NSScriptClassDescription.m b/Source/NSScriptClassDescription.m index c055615f3b..6175dc154e 100644 --- a/Source/NSScriptClassDescription.m +++ b/Source/NSScriptClassDescription.m @@ -32,6 +32,7 @@ @implementation NSScriptClassDescription NSString *_className; NSString *_suiteName; NSString *_superclassName; + NSScriptClassDescription *_superclassDescription; FourCharCode _appleEventCode; } @@ -52,6 +53,7 @@ - (id) initWithSuiteName: (NSString *)suiteName _appleEventCode = appleEventCode; if (superclassDesc != nil) { + ASSIGN(_superclassDescription, superclassDesc); ASSIGN(_superclassName, [superclassDesc className]); } } @@ -72,6 +74,7 @@ - (void) dealloc { RELEASE(_className); RELEASE(_suiteName); + RELEASE(_superclassDescription); RELEASE(_superclassName); [super dealloc]; } @@ -114,6 +117,13 @@ - (NSString *) suiteName - (NSScriptClassDescription *) superclassDescription { Class superclass; + /* If we have a direct reference, return it */ + if (_superclassDescription != nil) + { + return _superclassDescription; + } + + /* Otherwise try to look it up by name */ if (_superclassName != nil) { diff --git a/Source/NSScriptingStepTalkBridge.m b/Source/NSScriptingStepTalkBridge.m deleted file mode 100644 index dd1a58408d..0000000000 --- a/Source/NSScriptingStepTalkBridge.m +++ /dev/null @@ -1,530 +0,0 @@ -/* Implementation of GSScriptingStepTalkBridge for GNUstep - Copyright (C) 2025 Free Software Foundation, Inc. - - Written by: - Created: 2025 - - This file is part of the GNUstep Base Library. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library 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 - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free - Software Foundation, Inc., 51 Franklin Street, Fifth Floor, - Boston, MA 02110 Suite 500, USA. -*/ - -#import "common.h" -#import "GNUstepBase/GSScriptingStepTalkBridge.h" -#import "Foundation/NSScriptCommand.h" -#import "Foundation/NSScriptObjectSpecifier.h" -#import "Foundation/NSScriptSuiteRegistry.h" -#import "Foundation/NSScriptClassDescription.h" -#import "Foundation/NSScriptCommandDescription.h" -#import "Foundation/NSDictionary.h" -#import "Foundation/NSArray.h" -#import "Foundation/NSString.h" -#import "Foundation/NSException.h" -#import "Foundation/NSValue.h" -#import "Foundation/NSInvocation.h" - -static GSScriptingStepTalkBridge *sharedInstance = nil; - -@implementation GSScriptingStepTalkBridge - -+ (void) initialize -{ - if (self == [GSScriptingStepTalkBridge class]) - { - sharedInstance = [[GSScriptingStepTalkBridge alloc] init]; - } -} - -+ (instancetype) sharedBridge -{ - return sharedInstance; -} - -- (id) init -{ - self = [super init]; - if (self != nil) - { - _commandRegistry = [[NSMutableDictionary alloc] init]; - _cachedSpecifiers = [[NSMutableDictionary alloc] init]; - } - return self; -} - -- (void) dealloc -{ - RELEASE(_commandRegistry); - RELEASE(_cachedSpecifiers); - [super dealloc]; -} - -- (NSScriptCommand *) createCommand:(NSString *)commandName - forSuite:(NSString *)suiteName - withArguments:(NSDictionary *)arguments -{ - NSScriptSuiteRegistry *registry; - NSScriptCommandDescription *commandDesc; - Class commandClass; - NSScriptCommand *command; - - if (commandName == nil || suiteName == nil) - { - [NSException raise:NSInvalidArgumentException - format:@"Command name and suite name must not be nil"]; - return nil; - } - - registry = [NSScriptSuiteRegistry sharedScriptSuiteRegistry]; - - /* Look up the command description */ - commandDesc = [registry commandDescriptionWithName:commandName - inSuite:suiteName]; - - if (commandDesc == nil) - { - NSLog(@"Warning: Command '%@' not found in suite '%@'", - commandName, suiteName); - /* Try to create a generic NSScriptCommand */ - commandClass = [NSScriptCommand class]; - } - else - { - commandClass = [commandDesc commandClassName]; - if (commandClass == Nil) - { - commandClass = [NSScriptCommand class]; - } - } - - /* Create the command instance */ - command = [[commandClass alloc] init]; - - /* Set command arguments if provided */ - if (arguments != nil) - { - NSEnumerator *keyEnum; - NSString *key; - - keyEnum = [arguments keyEnumerator]; - while ((key = [keyEnum nextObject]) != nil) - { - id value; - - value = [arguments objectForKey:key]; - - /* Handle special argument keys */ - if ([key isEqualToString:@"DirectParameter"]) - { - [command setDirectParameter:value]; - } - else if ([key isEqualToString:@"ObjectSpecifier"]) - { - if ([value isKindOfClass:[NSScriptObjectSpecifier class]]) - { - [command setReceiversSpecifier:value]; - } - } - else - { - [command setArgument:value forKey:key]; - } - } - } - - return AUTORELEASE(command); -} - -- (NSScriptObjectSpecifier *) createSpecifier:(NSString *)specifierType - inContainer:(NSScriptObjectSpecifier *)containerSpecifier - forKey:(NSString *)key - withValue:(id)value -{ - NSScriptObjectSpecifier *specifier; - Class specifierClass; - - if (specifierType == nil || key == nil) - { - [NSException raise:NSInvalidArgumentException - format:@"Specifier type and key must not be nil"]; - return nil; - } - - specifier = nil; - - /* Determine the specifier class based on type */ - if ([specifierType isEqualToString:@"index"]) - { - specifierClass = [NSIndexSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - if ([value respondsToSelector:@selector(integerValue)]) - { - [(NSIndexSpecifier *)specifier setIndex:[value integerValue]]; - } - } - else if ([specifierType isEqualToString:@"name"]) - { - specifierClass = [NSNameSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - if ([value isKindOfClass:[NSString class]]) - { - [(NSNameSpecifier *)specifier setName:value]; - } - } - else if ([specifierType isEqualToString:@"property"]) - { - specifierClass = [NSPropertySpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - } - else if ([specifierType isEqualToString:@"uniqueID"]) - { - specifierClass = [NSUniqueIDSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - [(NSUniqueIDSpecifier *)specifier setUniqueID:value]; - } - else if ([specifierType isEqualToString:@"range"]) - { - specifierClass = [NSRangeSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - /* Value should be a dictionary with startSpecifier and endSpecifier */ - if ([value isKindOfClass:[NSDictionary class]]) - { - id startSpec; - id endSpec; - - startSpec = [value objectForKey:@"start"]; - endSpec = [value objectForKey:@"end"]; - - if (startSpec != nil) - { - [(NSRangeSpecifier *)specifier setStartSpecifier:startSpec]; - } - if (endSpec != nil) - { - [(NSRangeSpecifier *)specifier setEndSpecifier:endSpec]; - } - } - } - else if ([specifierType isEqualToString:@"middle"]) - { - specifierClass = [NSMiddleSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - } - else if ([specifierType isEqualToString:@"random"]) - { - specifierClass = [NSRandomSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - } - else if ([specifierType isEqualToString:@"relative"]) - { - specifierClass = [NSRelativeSpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - if ([value isKindOfClass:[NSDictionary class]]) - { - id baseSpec; - NSString *position; - - baseSpec = [value objectForKey:@"base"]; - position = [value objectForKey:@"position"]; - - if (baseSpec != nil) - { - [(NSRelativeSpecifier *)specifier setBaseSpecifier:baseSpec]; - } - if (position != nil) - { - if ([position isEqualToString:@"before"]) - { - [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeBefore]; - } - else if ([position isEqualToString:@"after"]) - { - [(NSRelativeSpecifier *)specifier setRelativePosition:NSRelativeAfter]; - } - } - } - } - else - { - /* Default to property specifier */ - specifierClass = [NSPropertySpecifier class]; - specifier = [[specifierClass alloc] initWithContainerSpecifier:containerSpecifier - key:key]; - } - - return AUTORELEASE(specifier); -} - -- (id) executeCommand:(NSString *)commandName - forSuite:(NSString *)suiteName - withArguments:(NSDictionary *)arguments -{ - NSScriptCommand *command; - id result; - - command = [self createCommand:commandName - forSuite:suiteName - withArguments:arguments]; - - if (command == nil) - { - return nil; - } - - result = [command executeCommand]; - - /* Check for errors */ - if ([command scriptErrorNumber] != 0) - { - NSLog(@"Script command error %ld: %@", - (long)[command scriptErrorNumber], - [command scriptErrorString]); - } - - return result; -} - -- (id) getObject:(NSString *)objectName - ofType:(NSString *)typeName - fromContainer:(id)container -{ - NSScriptObjectSpecifier *containerSpec; - NSScriptObjectSpecifier *objectSpec; - NSScriptCommand *getCommand; - NSDictionary *arguments; - id result; - - /* Build the container specifier */ - if (container == nil) - { - containerSpec = nil; /* Application container */ - } - else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) - { - containerSpec = container; - } - else - { - /* Try to create a specifier from the container */ - containerSpec = nil; - } - - /* Build the object specifier */ - objectSpec = [self createSpecifier:@"name" - inContainer:containerSpec - forKey:typeName - withValue:objectName]; - - /* Create and execute get command */ - arguments = [NSDictionary dictionaryWithObject:objectSpec - forKey:@"ObjectSpecifier"]; - - getCommand = [self createCommand:@"get" - forSuite:@"CoreSuite" - withArguments:arguments]; - - result = [getCommand executeCommand]; - - return result; -} - -- (void) setProperty:(NSString *)propertyName - ofObject:(id)object - toValue:(id)value -{ - NSScriptObjectSpecifier *objectSpec; - NSScriptObjectSpecifier *propertySpec; - NSScriptCommand *setCommand; - NSDictionary *arguments; - - /* Build the object specifier */ - if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) - { - objectSpec = object; - } - else - { - /* Assume object is the container */ - objectSpec = nil; - } - - /* Build the property specifier */ - propertySpec = [self createSpecifier:@"property" - inContainer:objectSpec - forKey:propertyName - withValue:nil]; - - /* Create and execute set command */ - arguments = [NSDictionary dictionaryWithObjectsAndKeys: - propertySpec, @"ObjectSpecifier", - value, @"DirectParameter", - nil]; - - setCommand = [self createCommand:@"set" - forSuite:@"CoreSuite" - withArguments:arguments]; - - [setCommand executeCommand]; -} - -- (id) createObject:(NSString *)typeName - atLocation:(id)location - withProperties:(NSDictionary *)properties -{ - NSScriptObjectSpecifier *locationSpec; - NSScriptCommand *createCommand; - NSMutableDictionary *arguments; - id result; - - /* Build the location specifier */ - if (location == nil) - { - locationSpec = nil; - } - else if ([location isKindOfClass:[NSScriptObjectSpecifier class]]) - { - locationSpec = location; - } - else - { - locationSpec = nil; - } - - /* Create arguments */ - arguments = [NSMutableDictionary dictionaryWithCapacity:3]; - [arguments setObject:typeName forKey:@"ObjectClass"]; - - if (locationSpec != nil) - { - [arguments setObject:locationSpec forKey:@"Location"]; - } - - if (properties != nil) - { - [arguments setObject:properties forKey:@"WithProperties"]; - } - - /* Create and execute create command */ - createCommand = [self createCommand:@"create" - forSuite:@"CoreSuite" - withArguments:arguments]; - - result = [createCommand executeCommand]; - - return result; -} - -- (void) deleteObject:(id)object -{ - NSScriptObjectSpecifier *objectSpec; - NSScriptCommand *deleteCommand; - NSDictionary *arguments; - - /* Build the object specifier */ - if ([object isKindOfClass:[NSScriptObjectSpecifier class]]) - { - objectSpec = object; - } - else - { - /* Can't delete without a proper specifier */ - NSLog(@"Warning: Cannot delete object without specifier"); - return; - } - - /* Create and execute delete command */ - arguments = [NSDictionary dictionaryWithObject:objectSpec - forKey:@"ObjectSpecifier"]; - - deleteCommand = [self createCommand:@"delete" - forSuite:@"CoreSuite" - withArguments:arguments]; - - [deleteCommand executeCommand]; -} - -- (NSInteger) countObjects:(NSString *)typeName - inContainer:(id)container -{ - NSScriptObjectSpecifier *containerSpec; - NSScriptCommand *countCommand; - NSDictionary *arguments; - id result; - NSInteger count; - - /* Build the container specifier */ - if (container == nil) - { - containerSpec = nil; - } - else if ([container isKindOfClass:[NSScriptObjectSpecifier class]]) - { - containerSpec = container; - } - else - { - containerSpec = nil; - } - - /* Create and execute count command */ - arguments = [NSDictionary dictionaryWithObjectsAndKeys: - typeName, @"ObjectClass", - containerSpec, @"ObjectSpecifier", - nil]; - - countCommand = [self createCommand:@"count" - forSuite:@"CoreSuite" - withArguments:arguments]; - - result = [countCommand executeCommand]; - - count = 0; - if ([result respondsToSelector:@selector(integerValue)]) - { - count = [result integerValue]; - } - - return count; -} - -- (void) registerCommandHandler:(id)handler - forCommand:(NSString *)commandName - inSuite:(NSString *)suiteName -{ - NSString *key; - - if (handler == nil || commandName == nil || suiteName == nil) - { - return; - } - - key = [NSString stringWithFormat:@"%@.%@", suiteName, commandName]; - [_commandRegistry setObject:handler forKey:key]; -} - -- (void) clearCache -{ - [_cachedSpecifiers removeAllObjects]; -} - -@end diff --git a/Tests/base/NSScriptClassDescription/basic.m b/Tests/base/NSScriptClassDescription/basic.m index 24be10b6fd..dce6ddaa1c 100644 --- a/Tests/base/NSScriptClassDescription/basic.m +++ b/Tests/base/NSScriptClassDescription/basic.m @@ -12,6 +12,7 @@ @implementation TestScriptableObject int main() { + NSAutoreleasePool *pool; NSScriptClassDescription *desc; NSScriptClassDescription *superDesc; NSString *suiteName; @@ -19,6 +20,8 @@ int main() FourCharCode appleEventCode; Class implClass; + pool = [NSAutoreleasePool new]; + START_SET("NSScriptClassDescription initialization"); // Test basic initialization @@ -26,10 +29,10 @@ int main() className = @"TestScriptableObject"; appleEventCode = 'TST1'; - desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: suiteName - className: className - appleEventCode: appleEventCode - superclass: nil]); + desc = [[NSScriptClassDescription alloc] initWithSuiteName: suiteName + className: className + appleEventCode: appleEventCode + superclass: nil]; PASS(desc != nil, "Can create NSScriptClassDescription instance"); END_SET("NSScriptClassDescription initialization"); @@ -55,15 +58,16 @@ int main() START_SET("NSScriptClassDescription hierarchy"); // Test superclass description - superDesc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: @"BaseSuite" - className: @"NSObject" - appleEventCode: 'BASE' - superclass: nil]); + superDesc = [[NSScriptClassDescription alloc] initWithSuiteName: @"BaseSuite" + className: @"NSObject" + appleEventCode: 'BASE' + superclass: nil]; - desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: @"DerivedSuite" - className: @"TestScriptableObject" - appleEventCode: 'DRV1' - superclass: superDesc]); + [desc release]; + desc = [[NSScriptClassDescription alloc] initWithSuiteName: @"DerivedSuite" + className: @"TestScriptableObject" + appleEventCode: 'DRV1' + superclass: superDesc]; PASS(desc != nil, "Can create derived class description"); PASS([desc superclassDescription] != nil, "Superclass description is set"); @@ -72,10 +76,11 @@ int main() START_SET("NSScriptClassDescription commands"); - desc = AUTORELEASE([[NSScriptClassDescription alloc] initWithSuiteName: suiteName - className: className - appleEventCode: appleEventCode - superclass: nil]); + [desc release]; + desc = [[NSScriptClassDescription alloc] initWithSuiteName: suiteName + className: className + appleEventCode: appleEventCode + superclass: nil]; // Test command description lookup NSScriptCommandDescription *cmdDesc; @@ -114,5 +119,10 @@ int main() END_SET("NSScriptClassDescription class methods"); + // Clean up manually allocated objects + [desc release]; + [superDesc release]; + + [pool release]; return 0; } From 22393afeba7824eb843662448714934f8b028860 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 19:51:35 -0500 Subject: [PATCH 10/20] Add tests for NSScriptCommand and fix issues --- Tests/base/NSScriptCommand/basic.m | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/Tests/base/NSScriptCommand/basic.m b/Tests/base/NSScriptCommand/basic.m index c42e625fa0..351c696819 100644 --- a/Tests/base/NSScriptCommand/basic.m +++ b/Tests/base/NSScriptCommand/basic.m @@ -20,15 +20,18 @@ - (id) performDefaultImplementation int main() { + NSAutoreleasePool *pool; NSScriptCommand *command; NSScriptCommandDescription *commandDesc; NSDictionary *args; NSString *result; + pool = [NSAutoreleasePool new]; + START_SET("NSScriptCommand initialization"); // Test initialization with nil - command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + command = [[NSScriptCommand alloc] initWithCommandDescription: nil]; PASS(command != nil, "Can create NSScriptCommand with nil description"); PASS([command commandDescription] == nil, "Command description is nil"); PASS(![command isWellFormed], "Command with nil description is not well-formed"); @@ -37,8 +40,9 @@ int main() START_SET("NSScriptCommand arguments"); + [command release]; commandDesc = nil; - command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: commandDesc]); + command = [[NSScriptCommand alloc] initWithCommandDescription: commandDesc]; // Test arguments PASS([command arguments] != nil, "Initial arguments is non-nil"); @@ -99,7 +103,8 @@ int main() START_SET("NSScriptCommand execution"); // Test execution with custom subclass - command = AUTORELEASE([[TestCommand alloc] initWithCommandDescription: nil]); + [command release]; + command = [[TestCommand alloc] initWithCommandDescription: nil]; result = [command executeCommand]; PASS(result == nil, "executeCommand returns nil for non-well-formed command"); @@ -107,7 +112,8 @@ int main() START_SET("NSScriptCommand suspension"); - command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + [command release]; + command = [[NSScriptCommand alloc] initWithCommandDescription: nil]; [command suspendExecution]; PASS(YES, "suspendExecution doesn't crash"); @@ -119,7 +125,8 @@ int main() START_SET("NSScriptCommand current command"); - command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + [command release]; + command = [[NSScriptCommand alloc] initWithCommandDescription: nil]; PASS([command currentCommand] == command, "currentCommand returns self"); @@ -127,11 +134,14 @@ int main() START_SET("NSScriptCommand apple event"); - command = AUTORELEASE([[NSScriptCommand alloc] initWithCommandDescription: nil]); + [command release]; + command = [[NSScriptCommand alloc] initWithCommandDescription: nil]; PASS([command appleEvent] == nil, "appleEvent returns nil by default"); END_SET("NSScriptCommand apple event"); + [command release]; + [pool release]; return 0; } From 5a533cab31a0c6b612beeb8cb8f8e56e39312e49 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 19:53:49 -0500 Subject: [PATCH 11/20] Fix tests for NSScriptCommandDescription --- Tests/base/NSScriptCommandDescription/basic.m | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/Tests/base/NSScriptCommandDescription/basic.m b/Tests/base/NSScriptCommandDescription/basic.m index 583ddc5b50..57a24f0973 100644 --- a/Tests/base/NSScriptCommandDescription/basic.m +++ b/Tests/base/NSScriptCommandDescription/basic.m @@ -6,12 +6,16 @@ int main() { + NSAutoreleasePool *pool; NSScriptCommandDescription *desc; + NSScriptCommandDescription *desc2; NSString *suiteName; NSString *commandName; FourCharCode eventCode; FourCharCode classCode; + pool = [NSAutoreleasePool new]; + START_SET("NSScriptCommandDescription initialization"); suiteName = @"TestSuite"; @@ -19,10 +23,10 @@ int main() eventCode = 'TEST'; classCode = 'TCMD'; - desc = AUTORELEASE([[NSScriptCommandDescription alloc] initWithSuiteName: suiteName - commandName: commandName - appleEventCode: eventCode - appleEventClassCode: classCode]); + desc = [[NSScriptCommandDescription alloc] initWithSuiteName: suiteName + commandName: commandName + appleEventCode: eventCode + appleEventClassCode: classCode]; PASS(desc != nil, "Can create NSScriptCommandDescription instance"); END_SET("NSScriptCommandDescription initialization"); @@ -76,11 +80,10 @@ int main() START_SET("NSScriptCommandDescription multiple instances"); - NSScriptCommandDescription *desc2; - desc2 = AUTORELEASE([[NSScriptCommandDescription alloc] initWithSuiteName: @"Suite2" - commandName: @"Command2" - appleEventCode: 'TST2' - appleEventClassCode: 'CMD2']); + desc2 = [[NSScriptCommandDescription alloc] initWithSuiteName: @"Suite2" + commandName: @"Command2" + appleEventCode: 'TST2' + appleEventClassCode: 'CMD2']; PASS(desc2 != nil, "Can create second instance"); PASS(![[desc2 suiteName] isEqual: [desc suiteName]], @@ -92,5 +95,8 @@ int main() END_SET("NSScriptCommandDescription multiple instances"); + [desc release]; + [desc2 release]; + [pool release]; return 0; } From 66b2a6499b9733f5fb7e3c01c52af8b7d21ac6d6 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Fri, 12 Dec 2025 20:03:39 -0500 Subject: [PATCH 12/20] Fix tests for NSScriptCoercionHandler --- Source/NSScriptCoercionHandler.m | 12 ++++++++++-- Tests/base/NSScriptCoercionHandler/basic.m | 7 ++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index 1cb66fcf5a..235a37f7d2 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -76,6 +76,7 @@ - (id) coerceValue: (id)value toClass: (Class)toClass id coercer; SEL selector; id result; + Class currentClass; if (value == nil) { @@ -89,8 +90,15 @@ - (id) coerceValue: (id)value toClass: (Class)toClass [_lock lock]; - key = [self _keyForFromClass: [value class] toClass: toClass]; - coercerInfo = [_coercers objectForKey: key]; + /* Try to find coercer for exact class first, then walk up the class hierarchy */ + coercerInfo = nil; + currentClass = [value class]; + while (currentClass != Nil && coercerInfo == nil) + { + key = [self _keyForFromClass: currentClass toClass: toClass]; + coercerInfo = [_coercers objectForKey: key]; + currentClass = [currentClass superclass]; + } [_lock unlock]; diff --git a/Tests/base/NSScriptCoercionHandler/basic.m b/Tests/base/NSScriptCoercionHandler/basic.m index 6741fd385a..b9fe2a8cae 100644 --- a/Tests/base/NSScriptCoercionHandler/basic.m +++ b/Tests/base/NSScriptCoercionHandler/basic.m @@ -23,12 +23,15 @@ - (NSNumber *) stringToNumber: (NSString *)string int main() { + NSAutoreleasePool *pool; NSScriptCoercionHandler *handler; TestCoercer *coercer; NSNumber *num; NSString *str; id result; + pool = [[NSAutoreleasePool alloc] init]; + START_SET("NSScriptCoercionHandler singleton"); // Test shared instance @@ -43,7 +46,7 @@ int main() START_SET("NSScriptCoercionHandler coercion"); - coercer = AUTORELEASE([[TestCoercer alloc] init]); + coercer = [[TestCoercer alloc] init]; // Test without registered coercer num = [NSNumber numberWithInt: 42]; @@ -137,5 +140,7 @@ int main() END_SET("NSScriptCoercionHandler multiple coercers"); + [coercer release]; + [pool release]; return 0; } From e4ff91b94d266fbefd431d94248e3f7ad57ecd80 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Fri, 12 Dec 2025 20:05:16 -0500 Subject: [PATCH 13/20] Update coercion test --- Source/NSScriptCoercionHandler.m | 12 ++---------- Tests/base/NSScriptCoercionHandler/basic.m | 7 +------ 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index 235a37f7d2..1cb66fcf5a 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -76,7 +76,6 @@ - (id) coerceValue: (id)value toClass: (Class)toClass id coercer; SEL selector; id result; - Class currentClass; if (value == nil) { @@ -90,15 +89,8 @@ - (id) coerceValue: (id)value toClass: (Class)toClass [_lock lock]; - /* Try to find coercer for exact class first, then walk up the class hierarchy */ - coercerInfo = nil; - currentClass = [value class]; - while (currentClass != Nil && coercerInfo == nil) - { - key = [self _keyForFromClass: currentClass toClass: toClass]; - coercerInfo = [_coercers objectForKey: key]; - currentClass = [currentClass superclass]; - } + key = [self _keyForFromClass: [value class] toClass: toClass]; + coercerInfo = [_coercers objectForKey: key]; [_lock unlock]; diff --git a/Tests/base/NSScriptCoercionHandler/basic.m b/Tests/base/NSScriptCoercionHandler/basic.m index b9fe2a8cae..6741fd385a 100644 --- a/Tests/base/NSScriptCoercionHandler/basic.m +++ b/Tests/base/NSScriptCoercionHandler/basic.m @@ -23,15 +23,12 @@ - (NSNumber *) stringToNumber: (NSString *)string int main() { - NSAutoreleasePool *pool; NSScriptCoercionHandler *handler; TestCoercer *coercer; NSNumber *num; NSString *str; id result; - pool = [[NSAutoreleasePool alloc] init]; - START_SET("NSScriptCoercionHandler singleton"); // Test shared instance @@ -46,7 +43,7 @@ int main() START_SET("NSScriptCoercionHandler coercion"); - coercer = [[TestCoercer alloc] init]; + coercer = AUTORELEASE([[TestCoercer alloc] init]); // Test without registered coercer num = [NSNumber numberWithInt: 42]; @@ -140,7 +137,5 @@ int main() END_SET("NSScriptCoercionHandler multiple coercers"); - [coercer release]; - [pool release]; return 0; } From 8f6726d2db27577b0fcc806e4094803755c78761 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 13 Dec 2025 01:03:19 -0500 Subject: [PATCH 14/20] Fix coercion handler --- Source/NSScriptCoercionHandler.m | 41 +++++++++++++++------- Tests/base/NSScriptCoercionHandler/basic.m | 7 +++- 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/Source/NSScriptCoercionHandler.m b/Source/NSScriptCoercionHandler.m index 1cb66fcf5a..65b96eba37 100644 --- a/Source/NSScriptCoercionHandler.m +++ b/Source/NSScriptCoercionHandler.m @@ -76,6 +76,7 @@ - (id) coerceValue: (id)value toClass: (Class)toClass id coercer; SEL selector; id result; + Class currentClass; if (value == nil) { @@ -89,24 +90,40 @@ - (id) coerceValue: (id)value toClass: (Class)toClass [_lock lock]; - key = [self _keyForFromClass: [value class] toClass: toClass]; - coercerInfo = [_coercers objectForKey: key]; - - [_lock unlock]; + /* Try to find coercer for exact class first, then walk up the class hierarchy */ + coercerInfo = nil; + currentClass = [value class]; + while (currentClass != Nil && coercerInfo == nil) + { + key = [self _keyForFromClass: currentClass toClass: toClass]; + coercerInfo = [_coercers objectForKey: key]; + if (coercerInfo != nil) + { + break; + } + currentClass = [currentClass superclass]; + } + /* Extract coercer and selector while still holding lock */ + coercer = nil; + selector = NULL; if (coercerInfo != nil) { - coercer = [coercerInfo objectForKey: @"coercer"]; + coercer = [[coercerInfo objectForKey: @"coercer"] retain]; selector = NSSelectorFromString([coercerInfo objectForKey: @"selector"]); - - if (coercer != nil && selector != NULL && [coercer respondsToSelector: selector]) - { - result = [coercer performSelector: selector withObject: value]; - return result; - } } - return value; + [_lock unlock]; + + result = value; + if (coercer != nil && selector != NULL && [coercer respondsToSelector: selector]) + { + result = [coercer performSelector: selector withObject: value]; + } + + [coercer release]; + + return result; } - (void) registerCoercer: (id)coercer diff --git a/Tests/base/NSScriptCoercionHandler/basic.m b/Tests/base/NSScriptCoercionHandler/basic.m index 6741fd385a..b9fe2a8cae 100644 --- a/Tests/base/NSScriptCoercionHandler/basic.m +++ b/Tests/base/NSScriptCoercionHandler/basic.m @@ -23,12 +23,15 @@ - (NSNumber *) stringToNumber: (NSString *)string int main() { + NSAutoreleasePool *pool; NSScriptCoercionHandler *handler; TestCoercer *coercer; NSNumber *num; NSString *str; id result; + pool = [[NSAutoreleasePool alloc] init]; + START_SET("NSScriptCoercionHandler singleton"); // Test shared instance @@ -43,7 +46,7 @@ int main() START_SET("NSScriptCoercionHandler coercion"); - coercer = AUTORELEASE([[TestCoercer alloc] init]); + coercer = [[TestCoercer alloc] init]; // Test without registered coercer num = [NSNumber numberWithInt: 42]; @@ -137,5 +140,7 @@ int main() END_SET("NSScriptCoercionHandler multiple coercers"); + [coercer release]; + [pool release]; return 0; } From e28c78b2136bfc6dd8d929340c85532a14a3b553 Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 13 Dec 2025 01:04:09 -0500 Subject: [PATCH 15/20] Add new tests --- Tests/base/NSScriptCoercionHandler/debug.m | 49 +++++++++++++++++++ .../test_class_names.m | 22 +++++++++ 2 files changed, 71 insertions(+) create mode 100644 Tests/base/NSScriptCoercionHandler/debug.m create mode 100644 Tests/base/NSScriptCoercionHandler/test_class_names.m diff --git a/Tests/base/NSScriptCoercionHandler/debug.m b/Tests/base/NSScriptCoercionHandler/debug.m new file mode 100644 index 0000000000..00ebffd1e6 --- /dev/null +++ b/Tests/base/NSScriptCoercionHandler/debug.m @@ -0,0 +1,49 @@ +#import +#import + +@interface TestCoercer : NSObject +@end + +@implementation TestCoercer + +- (NSString *) numberToString: (NSNumber *)number +{ + NSLog(@"numberToString called with: %@", number); + return [number stringValue]; +} + +@end + +int main() +{ + NSAutoreleasePool *pool; + NSScriptCoercionHandler *handler; + TestCoercer *coercer; + NSNumber *num; + id result; + + pool = [[NSAutoreleasePool alloc] init]; + + handler = [NSScriptCoercionHandler sharedCoercionHandler]; + NSLog(@"Handler: %@", handler); + + coercer = [[TestCoercer alloc] init]; + NSLog(@"Coercer: %@", coercer); + + num = [NSNumber numberWithInt: 42]; + NSLog(@"Number: %@, class: %@", num, [num class]); + + NSLog(@"Registering coercer..."); + [handler registerCoercer: coercer + selector: @selector(numberToString:) + toConvertFromClass: [NSNumber class] + toClass: [NSString class]]; + + NSLog(@"Attempting coercion..."); + result = [handler coerceValue: num toClass: [NSString class]]; + NSLog(@"Result: %@, class: %@", result, [result class]); + + [coercer release]; + [pool release]; + return 0; +} diff --git a/Tests/base/NSScriptCoercionHandler/test_class_names.m b/Tests/base/NSScriptCoercionHandler/test_class_names.m new file mode 100644 index 0000000000..0132ebee57 --- /dev/null +++ b/Tests/base/NSScriptCoercionHandler/test_class_names.m @@ -0,0 +1,22 @@ +#import + +int main() +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + NSNumber *num = [NSNumber numberWithInt: 42]; + + NSLog(@"NSNumber class: %@", NSStringFromClass([NSNumber class])); + NSLog(@"num class: %@", NSStringFromClass([num class])); + NSLog(@"num's class == NSNumber: %d", [num class] == [NSNumber class]); + NSLog(@"num isKindOfClass NSNumber: %d", [num isKindOfClass: [NSNumber class]]); + + Class c = [num class]; + while (c != Nil) + { + NSLog(@" Hierarchy: %@", NSStringFromClass(c)); + c = [c superclass]; + } + + [pool release]; + return 0; +} From 4c5e7ce554a363e5f8abbf41e5834fe0b5056b57 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Sat, 13 Dec 2025 01:13:44 -0500 Subject: [PATCH 16/20] Update to fix gcc errors. --- Headers/Foundation/NSScriptClassDescription.h | 9 +++++++++ Headers/Foundation/NSScriptCommand.h | 1 + .../Foundation/NSScriptCommandDescription.h | 1 + Headers/Foundation/NSScriptSuiteRegistry.h | 2 ++ Source/GSScriptingStepTalkBridge.m | 8 ++++++-- Source/NSScriptClassDescription.m | 8 -------- Source/NSScriptCommand.m | 10 ++++++++++ Source/NSScriptCommandDescription.m | 7 +++++++ Source/NSScriptSuiteRegistry.m | 20 +++++++++++++++++++ 9 files changed, 56 insertions(+), 10 deletions(-) diff --git a/Headers/Foundation/NSScriptClassDescription.h b/Headers/Foundation/NSScriptClassDescription.h index 3a8067d080..176346c38c 100644 --- a/Headers/Foundation/NSScriptClassDescription.h +++ b/Headers/Foundation/NSScriptClassDescription.h @@ -41,6 +41,15 @@ typedef uint32_t FourCharCode; GS_EXPORT_CLASS @interface NSScriptClassDescription : NSClassDescription +{ +@private + NSString *_suiteName; + NSString *_className; + FourCharCode _appleEventCode; + NSScriptClassDescription *_superclassDescription; + NSString *_superclassName; + Class _implementationClass; +} + (NSScriptClassDescription *) classDescriptionForClass: (Class)aClass; diff --git a/Headers/Foundation/NSScriptCommand.h b/Headers/Foundation/NSScriptCommand.h index 4eb7ffe351..dffb9e6247 100644 --- a/Headers/Foundation/NSScriptCommand.h +++ b/Headers/Foundation/NSScriptCommand.h @@ -63,6 +63,7 @@ GS_EXPORT_CLASS - (NSDictionary *) arguments; - (void) setArguments: (NSDictionary *)args; +- (void) setArgument: (id)value forKey: (NSString *)key; - (NSDictionary *) evaluatedArguments; diff --git a/Headers/Foundation/NSScriptCommandDescription.h b/Headers/Foundation/NSScriptCommandDescription.h index d4b6ba540a..632d302848 100644 --- a/Headers/Foundation/NSScriptCommandDescription.h +++ b/Headers/Foundation/NSScriptCommandDescription.h @@ -49,6 +49,7 @@ GS_EXPORT_CLASS - (FourCharCode) appleEventClassCode; - (NSString *) commandName; +- (NSString *) commandClassName; - (NSString *) suiteName; - (NSString *) returnType; diff --git a/Headers/Foundation/NSScriptSuiteRegistry.h b/Headers/Foundation/NSScriptSuiteRegistry.h index b492187922..cedf6cdc85 100644 --- a/Headers/Foundation/NSScriptSuiteRegistry.h +++ b/Headers/Foundation/NSScriptSuiteRegistry.h @@ -64,6 +64,8 @@ GS_EXPORT_CLASS - (NSScriptClassDescription *) classDescriptionWithAppleEventCode: (FourCharCode)appleEventCode; - (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCharCode)appleEventClassCode andAppleEventCode: (FourCharCode)appleEventCode; +- (NSScriptCommandDescription *) commandDescriptionWithName: (NSString *)commandName + inSuite: (NSString *)suiteName; - (NSArray *) suiteNames; - (FourCharCode) appleEventCodeForSuite: (NSString *)suiteName; diff --git a/Source/GSScriptingStepTalkBridge.m b/Source/GSScriptingStepTalkBridge.m index dd1a58408d..26da27dbd5 100644 --- a/Source/GSScriptingStepTalkBridge.m +++ b/Source/GSScriptingStepTalkBridge.m @@ -77,7 +77,7 @@ - (NSScriptCommand *) createCommand:(NSString *)commandName { NSScriptSuiteRegistry *registry; NSScriptCommandDescription *commandDesc; - Class commandClass; + Class commandClass = Nil; NSScriptCommand *command; if (commandName == nil || suiteName == nil) @@ -102,7 +102,11 @@ - (NSScriptCommand *) createCommand:(NSString *)commandName } else { - commandClass = [commandDesc commandClassName]; + NSString *className = [commandDesc commandClassName]; + if (className != nil) + { + commandClass = NSClassFromString(className); + } if (commandClass == Nil) { commandClass = [NSScriptCommand class]; diff --git a/Source/NSScriptClassDescription.m b/Source/NSScriptClassDescription.m index 6175dc154e..6f79859682 100644 --- a/Source/NSScriptClassDescription.m +++ b/Source/NSScriptClassDescription.m @@ -27,14 +27,6 @@ #import "Foundation/NSString.h" @implementation NSScriptClassDescription -{ - Class _implementationClass; - NSString *_className; - NSString *_suiteName; - NSString *_superclassName; - NSScriptClassDescription *_superclassDescription; - FourCharCode _appleEventCode; -} + (NSScriptClassDescription *) classDescriptionForClass: (Class)aClass { diff --git a/Source/NSScriptCommand.m b/Source/NSScriptCommand.m index 6b502c696d..26d5baaa98 100644 --- a/Source/NSScriptCommand.m +++ b/Source/NSScriptCommand.m @@ -86,6 +86,16 @@ - (void) setArguments: (NSDictionary *)args DESTROY(_evaluatedArguments); } +- (void) setArgument: (id)value forKey: (NSString *)key +{ + if (_arguments == nil) + { + _arguments = [[NSMutableDictionary alloc] init]; + } + [(NSMutableDictionary *)_arguments setObject: value forKey: key]; + DESTROY(_evaluatedArguments); +} + - (NSDictionary *) evaluatedArguments { NSEnumerator *keyEnum; diff --git a/Source/NSScriptCommandDescription.m b/Source/NSScriptCommandDescription.m index 1018c50de2..29d7f9f780 100644 --- a/Source/NSScriptCommandDescription.m +++ b/Source/NSScriptCommandDescription.m @@ -33,6 +33,7 @@ @implementation NSScriptCommandDescription { NSString *_suiteName; NSString *_commandName; + NSString *_commandClassName; FourCharCode _appleEventCode; FourCharCode _appleEventClassCode; NSString *_returnType; @@ -60,6 +61,7 @@ - (void) dealloc { RELEASE(_suiteName); RELEASE(_commandName); + RELEASE(_commandClassName); RELEASE(_returnType); RELEASE(_arguments); [super dealloc]; @@ -80,6 +82,11 @@ - (NSString *) commandName return _commandName; } +- (NSString *) commandClassName +{ + return _commandClassName; +} + - (NSString *) suiteName { return _suiteName; diff --git a/Source/NSScriptSuiteRegistry.m b/Source/NSScriptSuiteRegistry.m index 80f3ba15b7..ba404db120 100644 --- a/Source/NSScriptSuiteRegistry.m +++ b/Source/NSScriptSuiteRegistry.m @@ -294,6 +294,26 @@ - (NSScriptCommandDescription *) commandDescriptionWithAppleEventClass: (FourCha return [_commandDescriptions objectForKey: key]; } +- (NSScriptCommandDescription *) commandDescriptionWithName: (NSString *)commandName + inSuite: (NSString *)suiteName +{ + NSEnumerator *enumerator; + NSScriptCommandDescription *commandDesc; + + /* Search through all registered command descriptions */ + enumerator = [_commandDescriptions objectEnumerator]; + while ((commandDesc = [enumerator nextObject]) != nil) + { + if ([[commandDesc commandName] isEqualToString: commandName] && + [[commandDesc suiteName] isEqualToString: suiteName]) + { + return commandDesc; + } + } + + return nil; +} + - (NSArray *) suiteNames { return [_suiteDescriptions allKeys]; From 00541ea0e70c4b435edb82c113ac5a9268538100 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Sat, 13 Dec 2025 01:19:46 -0500 Subject: [PATCH 17/20] Fix errors from GCC --- Headers/Foundation/NSScriptCommandDescription.h | 11 +++++++++++ Source/NSScriptCommandDescription.m | 10 ---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Headers/Foundation/NSScriptCommandDescription.h b/Headers/Foundation/NSScriptCommandDescription.h index 632d302848..ccc2206ce6 100644 --- a/Headers/Foundation/NSScriptCommandDescription.h +++ b/Headers/Foundation/NSScriptCommandDescription.h @@ -39,6 +39,17 @@ typedef uint32_t FourCharCode; GS_EXPORT_CLASS @interface NSScriptCommandDescription : NSObject +{ +@private + NSString *_suiteName; + NSString *_commandName; + NSString *_commandClassName; + FourCharCode _appleEventCode; + FourCharCode _appleEventClassCode; + NSString *_returnType; + FourCharCode _returnAppleEventCode; + NSMutableDictionary *_arguments; +} - (id) initWithSuiteName: (NSString *)suiteName commandName: (NSString *)commandName diff --git a/Source/NSScriptCommandDescription.m b/Source/NSScriptCommandDescription.m index 29d7f9f780..d89dae32da 100644 --- a/Source/NSScriptCommandDescription.m +++ b/Source/NSScriptCommandDescription.m @@ -30,16 +30,6 @@ #import "Foundation/NSValue.h" @implementation NSScriptCommandDescription -{ - NSString *_suiteName; - NSString *_commandName; - NSString *_commandClassName; - FourCharCode _appleEventCode; - FourCharCode _appleEventClassCode; - NSString *_returnType; - FourCharCode _returnAppleEventCode; - NSMutableDictionary *_arguments; -} - (id) initWithSuiteName: (NSString *)suiteName commandName: (NSString *)commandName From 575b366b3dfbf55e993b7997468ae0bf463976cf Mon Sep 17 00:00:00 2001 From: Gregory John Casamento Date: Sat, 13 Dec 2025 01:23:47 -0500 Subject: [PATCH 18/20] Move instance vars --- Headers/Foundation/NSScriptExecutionContext.h | 4 ++++ Source/NSScriptExecutionContext.m | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Headers/Foundation/NSScriptExecutionContext.h b/Headers/Foundation/NSScriptExecutionContext.h index 330d2c281c..27e6b2a15c 100644 --- a/Headers/Foundation/NSScriptExecutionContext.h +++ b/Headers/Foundation/NSScriptExecutionContext.h @@ -36,6 +36,10 @@ extern "C" { GS_EXPORT_CLASS @interface NSScriptExecutionContext : NSObject +{ +@private + NSScriptCommand *_topLevelObject; +} + (NSScriptExecutionContext *) sharedScriptExecutionContext; diff --git a/Source/NSScriptExecutionContext.m b/Source/NSScriptExecutionContext.m index cd77394cec..0d8caf67ea 100644 --- a/Source/NSScriptExecutionContext.m +++ b/Source/NSScriptExecutionContext.m @@ -27,9 +27,6 @@ #import "Foundation/NSScriptCommand.h" @implementation NSScriptExecutionContext -{ - NSScriptCommand *_topLevelObject; -} static NSScriptExecutionContext *sharedContext = nil; From d2bfb7447d52a7b01ac19203bb18918f6eb785aa Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Sat, 13 Dec 2025 01:25:04 -0500 Subject: [PATCH 19/20] Move ivar --- Headers/Foundation/NSScriptExecutionContext.h | 4 ++++ Source/NSScriptExecutionContext.m | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Headers/Foundation/NSScriptExecutionContext.h b/Headers/Foundation/NSScriptExecutionContext.h index 330d2c281c..27e6b2a15c 100644 --- a/Headers/Foundation/NSScriptExecutionContext.h +++ b/Headers/Foundation/NSScriptExecutionContext.h @@ -36,6 +36,10 @@ extern "C" { GS_EXPORT_CLASS @interface NSScriptExecutionContext : NSObject +{ +@private + NSScriptCommand *_topLevelObject; +} + (NSScriptExecutionContext *) sharedScriptExecutionContext; diff --git a/Source/NSScriptExecutionContext.m b/Source/NSScriptExecutionContext.m index cd77394cec..0d8caf67ea 100644 --- a/Source/NSScriptExecutionContext.m +++ b/Source/NSScriptExecutionContext.m @@ -27,9 +27,6 @@ #import "Foundation/NSScriptCommand.h" @implementation NSScriptExecutionContext -{ - NSScriptCommand *_topLevelObject; -} static NSScriptExecutionContext *sharedContext = nil; From 2c11141408181b86be44f9a8b371bdd431f110a4 Mon Sep 17 00:00:00 2001 From: Gregory Casamento Date: Sat, 13 Dec 2025 01:31:56 -0500 Subject: [PATCH 20/20] Fix issue on windows. --- Source/NSScriptObjectSpecifier.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/NSScriptObjectSpecifier.m b/Source/NSScriptObjectSpecifier.m index fc8b27d31c..d860c76b18 100644 --- a/Source/NSScriptObjectSpecifier.m +++ b/Source/NSScriptObjectSpecifier.m @@ -806,7 +806,7 @@ - (id) objectsByEvaluatingSpecifier return nil; } - randomIndex = random() % count; + randomIndex = rand() % count; return [array objectAtIndex: randomIndex]; }