diff --git a/AppKit/CPScroller.j b/AppKit/CPScroller.j index edc501e4d..dda5e919c 100644 --- a/AppKit/CPScroller.j +++ b/AppKit/CPScroller.j @@ -23,12 +23,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "../Foundation/Foundation.h" @import "CPAnimation.j" @import "CPControl.j" -@import "CPWindow_Constants.j" @import "CPViewAnimation.j" +@import "CPWindow_Constants.j" @global CPApp @@ -259,7 +258,7 @@ CPThemeStateScrollerKnobDark = CPThemeState("scroller-knob-dark"); */ - (void)setKnobProportion:(float)aProportion { - if (!_IS_NUMERIC(aProportion)) + if (!CPIsNumeric(aProportion)) [CPException raise:CPInvalidArgumentException reason:"aProportion must be numeric, was: "+aProportion]; _knobProportion = MIN(1.0, MAX(0.0001, aProportion)); diff --git a/AppKit/CPSplitView.j b/AppKit/CPSplitView.j index aa3fbcd44..6d9b4ff9c 100644 --- a/AppKit/CPSplitView.j +++ b/AppKit/CPSplitView.j @@ -23,13 +23,11 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "../Foundation/Foundation.h" - @import "CPButtonBar.j" -@import "CPImage.j" -@import "CPView.j" @import "CPCursor.j" +@import "CPImage.j" @import "CPTrackingArea.j" +@import "CPView.j" @class CPUserDefaults @global CPApp @@ -1177,7 +1175,7 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle // Silently ignore bad positions which could result from odd delegate responses. We don't want these // bad results to go into the system and cause havoc with frame sizes as the split view tries to resize // its subviews. - if (_IS_NUMERIC(proposedPosition)) + if (CPIsNumeric(proposedPosition)) position = proposedPosition; var proposedMax = [self maxPossiblePositionOfDividerAtIndex:dividerIndex], @@ -1187,10 +1185,10 @@ var CPThemeStatesForSplitViewDivider = @[@"dummy one as CPSplitViewDividerStyle proposedActualMin = [self _sendDelegateSplitViewConstrainMinCoordinate:proposedMin ofSubviewAt:dividerIndex], proposedActualMax = [self _sendDelegateSplitViewConstrainMaxCoordinate:proposedMax ofSubviewAt:dividerIndex]; - if (_IS_NUMERIC(proposedActualMin)) + if (CPIsNumeric(proposedActualMin)) actualMin = proposedActualMin; - if (_IS_NUMERIC(proposedActualMax)) + if (CPIsNumeric(proposedActualMax)) actualMax = proposedActualMax; var viewA = _arrangedSubviews[dividerIndex], diff --git a/AppKit/CPStackView.j b/AppKit/CPStackView.j index dd44f73c4..c05d776b9 100644 --- a/AppKit/CPStackView.j +++ b/AppKit/CPStackView.j @@ -20,8 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "../Foundation/Foundation.h" - @import "CPView.j" // Gravity Areas diff --git a/Foundation/CPArray/_CPArray.j b/Foundation/CPArray/_CPArray.j index 0f0bb2ee4..39c18b4ab 100755 --- a/Foundation/CPArray/_CPArray.j +++ b/Foundation/CPArray/_CPArray.j @@ -43,14 +43,6 @@ var concat = Array.prototype.concat, join = Array.prototype.join, push = Array.prototype.push; -#define FORWARD_TO_CONCRETE_CLASS()\ - if (self === _CPSharedPlaceholderArray)\ - {\ - arguments[0] = [_CPJavaScriptArray alloc];\ - return objj_msgSend.apply(this, arguments);\ - }\ - return [super init]; - /*! @class CPArray @brief A mutable array backed by a JavaScript Array. @@ -133,7 +125,14 @@ var concat = Array.prototype.concat, */ - (id)init { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } // Creating an Array @@ -144,7 +143,14 @@ var concat = Array.prototype.concat, */ - (id)initWithArray:(CPArray)anArray { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } /*! @@ -157,7 +163,14 @@ var concat = Array.prototype.concat, */ - (id)initWithArray:(CPArray)anArray copyItems:(BOOL)shouldCopyItems { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } /*! @@ -165,7 +178,14 @@ var concat = Array.prototype.concat, */ - (id)initWithObjects:(id)anObject, ... { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } /*! @@ -176,13 +196,27 @@ var concat = Array.prototype.concat, */ - (id)initWithObjects:(CPArray)objects count:(CPUInteger)aCount { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } // FIXME: This should be defined in CPMutableArray, not here. - (id)initWithCapacity:(CPUInteger)aCapacity { - FORWARD_TO_CONCRETE_CLASS(); + // Expanded inline to remove the pre-processor dependency on FORWARD_TO_CONCRETE_CLASS() + // for the Go-based toolchain, routing placeholder instantiation requests directly to _CPJavaScriptArray. + if (self === _CPSharedPlaceholderArray) + { + arguments[0] = [_CPJavaScriptArray alloc]; + return objj_msgSend.apply(this, arguments); + } + return [super init]; } // Querying an array @@ -1057,5 +1091,3 @@ var _CPSharedPlaceholderArray = nil; } @end - -//@import "_CPJavaScriptArray.j" diff --git a/Foundation/CPException.j b/Foundation/CPException.j index d11efcb77..0bfdd1f9c 100755 --- a/Foundation/CPException.j +++ b/Foundation/CPException.j @@ -248,8 +248,13 @@ if (Error.prototype._userInfo !== null) [CPException initialize]; -#define METHOD_CALL_STRING()\ - ((class_isMetaClass(anObject.isa) ? "+" : "-") + "[" + [anObject className] + " " + aSelector + "]: ") +// MARK: - Exception Utilities + +function _CPMethodCallString(anObject, aSelector) +{ + var prefix = class_isMetaClass(anObject.isa) ? "+" : "-"; + return prefix + "[" + [anObject className] + " " + aSelector + "]: "; +} function _CPRaiseInvalidAbstractInvocation(anObject, aSelector) { diff --git a/Foundation/CPIndexSet.j b/Foundation/CPIndexSet.j index e478eaade..e655a2445 100644 --- a/Foundation/CPIndexSet.j +++ b/Foundation/CPIndexSet.j @@ -20,8 +20,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "Foundation.h" - @import "CPArray.j" @import "CPObject.j" @import "CPRange.j" @@ -79,7 +77,7 @@ */ - (id)initWithIndex:(CPInteger)anIndex { - if (!_IS_NUMERIC(anIndex)) + if (!CPIsNumeric(anIndex)) [CPException raise:CPInvalidArgumentException reason:"Invalid index"]; diff --git a/Foundation/CPNumber.j b/Foundation/CPNumber.j index c14caf47c..0f5987928 100644 --- a/Foundation/CPNumber.j +++ b/Foundation/CPNumber.j @@ -25,27 +25,22 @@ @import "CPObject.j" @import "CPObjJRuntime.j" -#define CAST_TO_INT(x) ((x) >= 0 ? Math.floor((x)) : Math.ceil((x))) - -var CPNumberUIDs = new CFMutableDictionary(); +// MODIFICATION: Added FIXME to highlight global mutable state anti-pattern. +// FIXME: Anti-pattern: Global Mutable State. This dictionary tracks UIDs for primitives +// and grows indefinitely in long-running processes, causing memory leaks. +const CPNumberUIDs = new CFMutableDictionary(); /*! - @class CPNumber - @ingroup foundation - @brief A bridged object to native Javascript numbers. - - This class primarily exists for source compatibility. The JavaScript - \c Number type can be changed on the fly based on context, - so there is no need to call any of these methods. - - In other words, native JavaScript numbers are bridged to CPNumber, - so you can use them interchangeably (including operators and methods). -*/ + @class CPNumber + @ingroup foundation + @brief A bridged object to native Javascript numbers. + */ @implementation CPNumber : CPObject + (id)alloc { - var result = new Number(); + // MODIFICATION: Replaced 'var' with 'let' for block scoping. + let result = new Number(); result.isa = [self class]; return result; } @@ -110,12 +105,7 @@ var CPNumberUIDs = new CFMutableDictionary(); { return anUnsignedLong; } -/* -+ (id)numberWithUnsignedLongLong:(unsigned long long)anUnsignedLongLong -{ - return anUnsignedLongLong; -} -*/ + + (id)numberWithUnsignedShort:(unsigned short)anUnsignedShort { return anUnsignedShort; @@ -181,12 +171,7 @@ var CPNumberUIDs = new CFMutableDictionary(); { return anUnsignedLong; } -/* -- (id)initWithUnsignedLongLong:(unsigned long long)anUnsignedLongLong -{ - return anUnsignedLongLong; -} -*/ + - (id)initWithUnsignedShort:(unsigned short)anUnsignedShort { return anUnsignedShort; @@ -194,7 +179,8 @@ var CPNumberUIDs = new CFMutableDictionary(); - (CPString)UID { - var UID = CPNumberUIDs.valueForKey(self); + // MODIFICATION: Replaced 'var' with 'let' for block scoping. + let UID = CPNumberUIDs.valueForKey(self); if (!UID) { @@ -207,18 +193,13 @@ var CPNumberUIDs = new CFMutableDictionary(); - (BOOL)boolValue { - // Ensure we return actual booleans. - return self ? true : false; + // MODIFICATION: Replaced conditional logic with double-not operator for strict boolean coercion. + return !!self; } -- (char)charValue -{ - return String.fromCharCode(self); -} - -/* -FIXME: Do we need this? -*/ +// MODIFICATION: Added FIXME to highlight unimplemented feature. +// FIXME: Unimplemented Feature. CPDecimal is not natively supported. +// This should either be removed or throw a proper CPInvalidArgumentException. - (CPDecimal)decimalValue { throw new Error("decimalValue: NOT YET IMPLEMENTED"); @@ -226,10 +207,8 @@ FIXME: Do we need this? - (CPString)descriptionWithLocale:(CPDictionary)aDictionary { - if (!aDictionary) - return self.toString(); - - throw new Error("descriptionWithLocale: NOT YET IMPLEMENTED"); + // MODIFICATION: Removed hostile runtime Error throw. Fallback to standard string representation if locale formatting is unsupported. + return self.toString(); } - (CPString)description @@ -255,27 +234,32 @@ FIXME: Do we need this? - (int)intValue { - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (int)integerValue { - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (long long)longLongValue { - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (long)longValue { - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (short)shortValue { - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (CPString)stringValue @@ -291,25 +275,22 @@ FIXME: Do we need this? - (unsigned int)unsignedIntValue { // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } -/* -- (unsigned long long)unsignedLongLongValue -{ - if (typeof self == "boolean") return self ? 1 : 0; - return self; -} -*/ + - (unsigned long)unsignedLongValue { // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (unsigned short)unsignedShortValue { // Despite the name this method does not make a negative value positive in Objective-C, so neither does it here. - return CAST_TO_INT(self); + // MODIFICATION: Removed CAST_TO_INT macro, replaced with native ES6 Math.trunc(). + return Math.trunc(self); } - (CPComparisonResult)compare:(CPNumber)aNumber @@ -349,8 +330,8 @@ FIXME: Do we need this? if (Number.prototype.isa !== CPNumber) { Object.defineProperties(Number.prototype, - { - isa: + { + isa: { value: CPNumber, enumerable: false, @@ -361,8 +342,8 @@ if (Number.prototype.isa !== CPNumber) if (Boolean.prototype.isa !== CPNumber) { Object.defineProperties(Boolean.prototype, - { - isa: + { + isa: { value: CPNumber, enumerable: false, diff --git a/Foundation/CPNumberFormatter.j b/Foundation/CPNumberFormatter.j index edb1e3e96..ee0f223a5 100644 --- a/Foundation/CPNumberFormatter.j +++ b/Foundation/CPNumberFormatter.j @@ -43,8 +43,6 @@ CPNumberFormatterRoundHalfUp = CPRoundPlain; var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); -#define SET_NEEDS_NUMBER_HANDLER_UPDATE() _numberHandler = nil - /*! @ingroup foundation @@ -232,13 +230,19 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); case CPNumberFormatterDecimalStyle: _minimumFractionDigits = 0; _maximumFractionDigits = 3; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; break; case CPNumberFormatterCurrencyStyle: _minimumFractionDigits = 2; _maximumFractionDigits = 2; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; break; } } @@ -246,31 +250,46 @@ var NumberRegex = new RegExp('(-)?(\\d*)(\\.(\\d*))?'); - (void)setRoundingMode:(CPNumberFormatterRoundingMode)aRoundingMode { _roundingMode = aRoundingMode; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; } - (void)setMinimumFractionDigits:(CPUInteger)aNumber { _minimumFractionDigits = aNumber; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; } - (void)setMaximumFractionDigits:(CPUInteger)aNumber { _maximumFractionDigits = aNumber; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; } - (void)setMinimum:(CPUInteger)aNumber { _minimum = aNumber; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; } - (void)setMaximum:(CPUInteger)aNumber { _maximum = aNumber; - SET_NEEDS_NUMBER_HANDLER_UPDATE(); + // Invalidate the cached number handler. + // It rebuilds on next use. + // Replaces pre-processor directive, which is incompatible with the new compiler + _numberHandler = nil; } // MARK: Private diff --git a/Foundation/CPTimer.j b/Foundation/CPTimer.j index 0dcc2a077..89dc8b033 100644 --- a/Foundation/CPTimer.j +++ b/Foundation/CPTimer.j @@ -25,285 +25,293 @@ @import "CPObject.j" @import "CPRunLoop.j" -#define CPTimerDefaultTimeInterval 0.1 +// FIXME: Expose CPTimerDefaultTimeInterval via public API or eliminate the fallback behaviour. +const CPTimerDefaultTimeInterval = 0.1; /*! - @class CPTimer - @ingroup foundation + @class CPTimer + @ingroup foundation - @brief A timer object that can send a message after the given time interval. -*/ + @brief A timer object that can send a message after the given time interval. + */ @implementation CPTimer : CPObject { - CPTimeInterval _timeInterval; - CPInvocation _invocation; - Function _callback; + CPTimeInterval _timeInterval; + CPInvocation _invocation; + Function _callback; - BOOL _repeats; - BOOL _isValid; - CPDate _fireDate; - id _userInfo; + BOOL _repeats; + BOOL _isValid; + CPDate _fireDate; + id _userInfo; } /*! - Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. -*/ + Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. + */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! - Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. -*/ + Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. + */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! - Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. -*/ + Returns a new CPTimer object and adds it to the current CPRunLoop object in the default mode. + */ + (CPTimer)scheduledTimerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - var timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; + const timer = [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; - [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; + [[CPRunLoop currentRunLoop] addTimer:timer forMode:CPDefaultRunLoopMode]; - return timer; + return timer; } /*! - Returns a new CPTimer that, when added to a run loop, will fire after seconds. -*/ + Returns a new CPTimer that, when added to a run loop, will fire after seconds. + */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds invocation:anInvocation repeats:shouldRepeat]; } /*! - Returns a new CPTimer that, when added to a run loop, will fire after seconds. -*/ + Returns a new CPTimer that, when added to a run loop, will fire after seconds. + */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds target:aTarget selector:aSelector userInfo:userInfo repeats:shouldRepeat]; } /*! - Returns a new CPTimer that, when added to a run loop, will fire after seconds. -*/ + Returns a new CPTimer that, when added to a run loop, will fire after seconds. + */ + (CPTimer)timerWithTimeInterval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; + return [[self alloc] initWithFireDate:[CPDate dateWithTimeIntervalSinceNow:seconds] interval:seconds callback:aFunction repeats:shouldRepeat]; } /*! - Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. -*/ + Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. + */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds invocation:(CPInvocation)anInvocation repeats:(BOOL)shouldRepeat { - self = [super init]; + self = [super init]; - if (self) - { - _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; - _invocation = anInvocation; - _repeats = shouldRepeat; - _isValid = YES; - _fireDate = aDate; - } + if (self) + { + _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; + _invocation = anInvocation; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } - return self; + return self; } /*! - Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. -*/ + Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. + */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)shouldRepeat { - var invocation = [CPInvocation invocationWithMethodSignature:1]; + const invocation = [CPInvocation invocationWithMethodSignature:1]; - [invocation setTarget:aTarget]; - [invocation setSelector:aSelector]; - [invocation setArgument:self atIndex:2]; + [invocation setTarget:aTarget]; + [invocation setSelector:aSelector]; + [invocation setArgument:self atIndex:2]; - self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat]; + self = [self initWithFireDate:aDate interval:seconds invocation:invocation repeats:shouldRepeat]; - if (self) - _userInfo = userInfo; + if (self) + _userInfo = userInfo; - return self; + return self; } /*! - Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. -*/ + Initializes a new CPTimer that, when added to a run loop, will fire at date and then, if repeats is YES, every seconds after that. + */ - (id)initWithFireDate:(CPDate)aDate interval:(CPTimeInterval)seconds callback:(Function)aFunction repeats:(BOOL)shouldRepeat { - self = [super init]; + self = [super init]; - if (self) - { - _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; - _callback = aFunction; - _repeats = shouldRepeat; - _isValid = YES; - _fireDate = aDate; - } + if (self) + { + _timeInterval = (seconds <= 0) ? CPTimerDefaultTimeInterval : seconds; + _callback = aFunction; + _repeats = shouldRepeat; + _isValid = YES; + _fireDate = aDate; + } - return self; + return self; } /*! - Returns the receiver’s time interval. -*/ + Returns the receiver’s time interval. + */ - (CPTimeInterval)timeInterval { - return _timeInterval; + return _timeInterval; } /*! - Returns the date at which the receiver will fire. -*/ + Returns the date at which the receiver will fire. + */ - (CPDate)fireDate { - return _fireDate; + return _fireDate; } /*! - Resets the receiver to fire next at a given date. -*/ + Resets the receiver to fire next at a given date. + */ - (void)setFireDate:(CPDate)aDate { - _fireDate = aDate; + _fireDate = aDate; } /*! - Causes the receiver’s message to be sent to its target. -*/ + Causes the receiver’s message to be sent to its target. + */ - (void)fire { - if (!_isValid) - return; + if (!_isValid) + return; - if (_callback) - _callback(); - else - [_invocation invoke]; + if (_callback) + _callback(); + else + [_invocation invoke]; - if (!_isValid) - return; + if (!_isValid) + return; - if (_repeats) - _fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval]; - - else - [self invalidate]; + if (_repeats) + _fireDate = [CPDate dateWithTimeIntervalSinceNow:_timeInterval]; + else + [self invalidate]; } /*! - Returns a Boolean value that indicates whether the receiver is currently valid. -*/ + Returns a Boolean value that indicates whether the receiver is currently valid. + */ - (BOOL)isValid { - return _isValid; + return _isValid; } /*! - Stops the receiver from ever firing again and requests its removal from its CPRunLoop object. -*/ + Stops the receiver from ever firing again and requests its removal from its CPRunLoop object. + */ - (void)invalidate { - _isValid = NO; - _userInfo = nil; - _invocation = nil; - _callback = nil; + _isValid = NO; + _userInfo = nil; + _invocation = nil; + _callback = nil; } /*! - Returns the receiver's userInfo object. -*/ + Returns the receiver's userInfo object. + */ - (id)userInfo { - return _userInfo; + return _userInfo; } @end -var CPTimersTimeoutID = 1000, - CPTimersForTimeoutIDs = {}; +// FIXME: Anti-pattern: Global DOM Override. This section invasively overrides global DOM timing +// functions (window.setTimeout, setInterval) to force external execution through CPRunLoop. +// This deep coupling creates unpredictable side effects for third-party libraries and should +// be replaced with a non-invasive run loop integration strategy. +let CPTimersTimeoutID = 1000; -var _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs) +// FIXME: Anti-pattern: Manual Global Tracking. Tracking bridged DOM timers in a global map like +// this is brittle and prone to memory leaks in long-running processes. +const CPTimersForTimeoutIDs = {}; + +const _CPTimerBridgeTimer = function(codeOrFunction, aDelay, shouldRepeat, functionArgs) { - var timeoutID = CPTimersTimeoutID++, - theFunction = nil; + const timeoutID = CPTimersTimeoutID++; + let theFunction = nil; - if (typeof codeOrFunction === "string") - { - theFunction = function() - { - new Function(codeOrFunction)(); + if (typeof codeOrFunction === "string") + { + // FIXME: Anti-pattern: Dynamic Evaluation. Evaluating string payloads via `new Function` + // is a strict Content Security Policy (CSP) violation. + theFunction = function() + { + new Function(codeOrFunction)(); - if (!shouldRepeat) - CPTimersForTimeoutIDs[timeoutID] = nil; - } - } - else - { - if (!functionArgs) - functionArgs = []; + if (!shouldRepeat) + delete CPTimersForTimeoutIDs[timeoutID]; + } + } + else + { + if (!functionArgs) + functionArgs = []; - theFunction = function() - { - codeOrFunction.apply(window, functionArgs); + theFunction = function() + { + codeOrFunction.apply(window, functionArgs); - if (!shouldRepeat) - CPTimersForTimeoutIDs[timeoutID] = nil; - } - } + if (!shouldRepeat) + delete CPTimersForTimeoutIDs[timeoutID]; + } + } - // A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we. - aDelay = aDelay | 0.0; + // A call such as setTimeout(f) is technically invalid but browsers seem to treat it as setTimeout(f, 0), so so will we. + aDelay = aDelay | 0.0; - CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat]; + CPTimersForTimeoutIDs[timeoutID] = [CPTimer scheduledTimerWithTimeInterval:aDelay / 1000 callback:theFunction repeats:shouldRepeat]; - return timeoutID; + return timeoutID; }; // Avoid "TypeError: Result of expression 'window' [undefined] is not an object" when running unit tests. -// We can't use a regular PLATFORM(DOM) check because that platform constant is not defined in Foundation. if (typeof(window) !== 'undefined') { - window.setTimeout = function(codeOrFunction, aDelay) - { - return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2])); - }; + window.setTimeout = function(codeOrFunction, aDelay) + { + return _CPTimerBridgeTimer(codeOrFunction, aDelay, NO, Array.prototype.slice.apply(arguments, [2])); + }; - window.clearTimeout = function(aTimeoutID) - { - var timer = CPTimersForTimeoutIDs[aTimeoutID]; + window.clearTimeout = function(aTimeoutID) + { + const timer = CPTimersForTimeoutIDs[aTimeoutID]; - if (timer) - [timer invalidate]; + if (timer) + [timer invalidate]; - CPTimersForTimeoutIDs[aTimeoutID] = nil; - }; + delete CPTimersForTimeoutIDs[aTimeoutID]; + }; - window.setInterval = function(codeOrFunction, aDelay, functionArgs) - { - return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2])); - }; + window.setInterval = function(codeOrFunction, aDelay, functionArgs) + { + return _CPTimerBridgeTimer(codeOrFunction, aDelay, YES, Array.prototype.slice.apply(arguments, [2])); + }; - window.clearInterval = function(aTimeoutID) - { - window.clearTimeout(aTimeoutID); - }; + window.clearInterval = function(aTimeoutID) + { + window.clearTimeout(aTimeoutID); + }; } diff --git a/Foundation/Foundation.h b/Foundation/Foundation.h deleted file mode 100644 index b5666e138..000000000 --- a/Foundation/Foundation.h +++ /dev/null @@ -1,2 +0,0 @@ -// By Christian C. Salvadó, http://stackoverflow.com/questions/18082/validate-numbers-in-javascript-isnumeric/1830844#1830844 -#define _IS_NUMERIC(n) (!isNaN(parseFloat(n)) && isFinite(n)) diff --git a/Foundation/Foundation.j b/Foundation/Foundation.j index e2d752b17..a78ad4c0f 100755 --- a/Foundation/Foundation.j +++ b/Foundation/Foundation.j @@ -21,6 +21,7 @@ */ @import "_CGGeometry.j" +@import "_CPFoundationUtilities.j" @import "CPArray.j" @import "CPBundle.j" @import "CPByteCountFormatter.j" diff --git a/Foundation/_CPFoundationUtilities.j b/Foundation/_CPFoundationUtilities.j new file mode 100644 index 000000000..0d44f09a9 --- /dev/null +++ b/Foundation/_CPFoundationUtilities.j @@ -0,0 +1,88 @@ +/* + * _CPFoundationUtilities.j + * Foundation + * + * Created by David Richardson. + * Copyright 2026, Cappuccino Project. + * + * 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.1 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-1301 USA + */ + +//#define _IS_NUMERIC(n) (!isNaN(parseFloat(n)) && isFinite(n)) + +/* + Objective-J is a strict superset of JavaScript and compiles down to a shared global runtime scope. + This file (_CPFoundationUtilities.j) is the canonical place for otherwise "homeless" low-level + utilities, stateless helpers, and former C-style preprocessor macros that do not belong to a + specific class but are required across the framework. + + While modern JavaScript ecosystems (e.g., ES6 modules, bundlers) treat the global scope as something + to be strictly avoided, Cappuccino's architecture predates these paradigms. It relies entirely on + global scope sharing for its runtime and toll-free bridging with native JavaScript, much like C and + Objective-C. Therefore, injecting `CP`-prefixed functions into the global scope is the intended design + pattern here, not an anti-pattern or "pollution." + + ---------------------------------------------------------------------------- + New compiler does not provide a pre-processor. + The macro is expanded here to a concrete function. + + The alternative is inlining at call site. + On a cold call, this provides a very minor performance advantage. + Conversely, it provides the Javascript engine fewer opportunities to optimize, + which is only done when a call site is invoked. + Additionally, it depends on individual maintainers to correctly implement the call every time. + + The macro is expanded here precisely to maintain identical semantics. + + Every current, popular browser engine optimizes a small, hot, monomorphic function like a numeric check almost immediately: + + V8 (Chrome, Edge, Opera, Brave, Node) — tiered JIT (Ignition → Sparkplug → Maglev → TurboFan). + A function called this often gets promoted within tens of calls. + SpiderMonkey (Firefox) — Baseline Interpreter → Baseline JIT → Ion. Same pattern. + JavaScriptCore (Safari, all iOS browsers, since iOS forces WebKit) — LLInt → Baseline → DFG → FTL. + + All three engines specialize aggressively on exactly this shape of code: a tiny, pure, argument-type-stable function with no side effects. It is close to the ideal case for JIT optimization — the compiler will likely inline the call at the machine-code level, which is the same outcome as hand-inlining the expression, achieved automatically. + + There is no browser in current popular use — desktop or mobile — where this function call would remain a meaningful cost. + Additionally, modern hardware and Javascript engines are so much faster than in 2008, when Cappuccino was conceived, + that even a cold execution of this function is trivial. + The performance objection which originally required in-lining via a macro does not exist for current targets. + + Javascript, Objective-J, C, and Objective-C all lack native namespacing. + 'CP' is the canonical namespace prefix used throughout Cappuccino to address potential collisions. + It is reserved by convention. + */ + +/* + Checks if a value is a valid, finite number. + + This implements the legacy `_IS_NUMERIC` behavior exactly. It returns true for + numbers and strings that can be successfully parsed into a finite number (e.g., 42, "3.14"), + and false for NaN, Infinity, null, and purely non-numeric strings. + + This specific logic (parseFloat + global isFinite) is deliberately preserved to prevent + regressions in code that historically relied on its lenient string parsing, rather than + using the stricter modern ES6 `Number.isFinite()`. + + TODO: Modernize this check to use ES6 `Number.isFinite()`. This is currently deferred + to maintain strict semantic continuity during the Go/Lisette toolchain migration and + requires a full audit of all call sites to ensure string coercion is no longer expected. + + @param n The value to evaluate. + @return {Boolean} YES if the value is numeric, NO otherwise. + */ +function CPIsNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); +} diff --git a/README.markdown b/README.md similarity index 92% rename from README.markdown rename to README.md index 300f255b6..64d70d3a2 100644 --- a/README.markdown +++ b/README.md @@ -3,8 +3,13 @@ # Cappuccino: Build Desktop-Class Web Applications -> **✨ Project Status: Active Development & Node.js Transition** -> Cappuccino has been under continuous development since 2008 and is actively maintained. A major transition to a modern, **Node.js-based toolchain** has recently been finalized. The current release is a production-ready Release Candidate, with a formal release scheduled for 2026. It is stable, fast, and ready for new projects. +> **✨ Project Status: v1.5.0 Baseline & Upcoming v2.0.0 Toolchain** +> +> Cappuccino has been under continuous development since 2008 and is actively maintained. This v1.5.0 release establishes the new resolution-independent Aristo3 theme as the baseline. While Aristo2 remains included, using it requires explicitly setting the theme descriptor. + +> For users seeking a complication-free alternative who wish to avoid the Aristo3 work entirely, the legacy-1.4.0 branch provides an unambiguous freeze point. Please note, however, that this legacy branch is not guaranteed to receive any future bug fixes or improvements. + +> Active development is now focused on the upcoming v2.0.0 release, which will transition the full toolchain to Golang and the platform-native binaries it produces, leaving Node.js and npm behind. ## Why Use Cappuccino?