Merge remote-tracking branch 'upstream/main' into Aristo3

This commit is contained in:
daboe01
2026-08-10 18:21:37 +02:00
13 changed files with 392 additions and 262 deletions
+2 -3
View File
@@ -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));
+5 -7
View File
@@ -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],
-2
View File
@@ -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
+48 -16
View File
@@ -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"
+7 -2
View File
@@ -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)
{
+1 -3
View File
@@ -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"];
+42 -61
View File
@@ -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,
+28 -9
View File
@@ -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
+163 -155
View File
@@ -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);
};
}
-2
View File
@@ -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))
+1
View File
@@ -21,6 +21,7 @@
*/
@import "_CGGeometry.j"
@import "_CPFoundationUtilities.j"
@import "CPArray.j"
@import "CPBundle.j"
@import "CPByteCountFormatter.j"
+88
View File
@@ -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);
}
+7 -2
View File
@@ -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?