no more js
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
function mainThread() {
|
||||
var maxDepth = Math.max(6, Number(arg[0] || 16));
|
||||
var maxDepth = number.max(6, Number(arg[0] || 16));
|
||||
|
||||
var stretchDepth = maxDepth + 1;
|
||||
var check = itemCheck(bottomUpTree(stretchDepth));
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
var blob = use('blob')
|
||||
var math = use('math/radians')
|
||||
|
||||
function eratosthenes (n) {
|
||||
var sieve = new blob(n, true)
|
||||
var sqrtN = Math.trunc(Math.sqrt(n));
|
||||
var sqrtN = number.whole(math.sqrt(n));
|
||||
|
||||
for (i = 2; i <= sqrtN; i++)
|
||||
if (sieve.read_logical(i))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
var time = use('time')
|
||||
var math = use('math/radians')
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// JavaScript Performance Benchmark Suite
|
||||
@@ -171,7 +172,7 @@ function benchObjectCreation() {
|
||||
|
||||
var prototypeTime = measureTime(function() {
|
||||
for (var i = 0; i < iterations.medium; i++) {
|
||||
var obj = Object.create(protoObj);
|
||||
var obj = meme(protoObj);
|
||||
obj.x = i;
|
||||
obj.y = i * 2;
|
||||
}
|
||||
@@ -237,8 +238,8 @@ function benchArithmetic() {
|
||||
var floatMathTime = measureTime(function() {
|
||||
var result = 1.5;
|
||||
for (var i = 0; i < iterations.simple; i++) {
|
||||
result = Math.sin(result) + Math.cos(i * 0.01);
|
||||
result = Math.sqrt(Math.abs(result)) + 0.1;
|
||||
result = math.sine(result) + math.cosine(i * 0.01);
|
||||
result = math.sqrt(number.abs(result)) + 0.1;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
var math = use('math/radians')
|
||||
var N = 1000000;
|
||||
var num = 0;
|
||||
for (var i = 0; i < N; i ++) {
|
||||
var x = 2 * $_.random();
|
||||
var y = $_.random();
|
||||
if (y < Math.sin(x * x))
|
||||
if (y < math.sine(x * x))
|
||||
num++;
|
||||
}
|
||||
log.console(2 * num / N);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var PI = Math.PI;
|
||||
var SOLAR_MASS = 4 * PI * PI;
|
||||
var math = use('math/radians')
|
||||
var SOLAR_MASS = 4 * pi * pi;
|
||||
var DAYS_PER_YEAR = 365.24;
|
||||
|
||||
function Body(x, y, z, vx, vy, vz, mass) {
|
||||
@@ -100,7 +100,7 @@ function advance(dt) {
|
||||
var dz = bodyi.z - bodyj.z;
|
||||
|
||||
var d2 = dx * dx + dy * dy + dz * dz;
|
||||
var mag = dt / (d2 * Math.sqrt(d2));
|
||||
var mag = dt / (d2 * math.sqrt(d2));
|
||||
|
||||
var massj = bodyj.mass;
|
||||
vxi -= dx * massj * mag;
|
||||
@@ -141,7 +141,7 @@ function energy() {
|
||||
var dy = bodyi.y - bodyj.y;
|
||||
var dz = bodyi.z - bodyj.z;
|
||||
|
||||
var distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
var distance = math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
e -= (bodyi.mass * bodyj.mass) / distance;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ for (let i = 0; i < 100; i++) {
|
||||
// Calculate statistics
|
||||
function getStats(arr) {
|
||||
def avg = arr.reduce((a, b) => a + b) / arr.length;
|
||||
def min = Math.min(...arr);
|
||||
def max = Math.max(...arr);
|
||||
def min = number.min(...arr);
|
||||
def max = number.max(...arr);
|
||||
return { avg, min, max };
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
const math = require('math/radians');
|
||||
|
||||
function A(i,j) {
|
||||
return 1/((i+j)*(i+j+1)/2+i+1);
|
||||
}
|
||||
@@ -42,7 +44,7 @@ function spectralnorm(n) {
|
||||
vv += v[i]*v[i];
|
||||
}
|
||||
|
||||
return Math.sqrt(vBv/vv);
|
||||
return math.sqrt(vBv/vv);
|
||||
}
|
||||
|
||||
log.console(spectralnorm(arg[0]).toFixed(9));
|
||||
|
||||
2
build.cm
2
build.cm
@@ -47,7 +47,7 @@ Build.get_local_dir = get_local_dir
|
||||
// ============================================================================
|
||||
|
||||
Build.list_targets = function() {
|
||||
return Object.keys(toolchains)
|
||||
return array(toolchains)
|
||||
}
|
||||
|
||||
Build.has_target = function(target) {
|
||||
|
||||
@@ -89,11 +89,10 @@ function print_config(obj, prefix = '') {
|
||||
var val = obj[key]
|
||||
var full_key = prefix ? prefix + '.' + key : key
|
||||
|
||||
if (val && typeof val == 'object' && !Array.isArray(val)) {
|
||||
if (isa(val, object))
|
||||
print_config(val, full_key)
|
||||
} else {
|
||||
else
|
||||
log.console(full_key + ' = ' + format_value(val))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +137,7 @@ switch (command) {
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found: " + key)
|
||||
} else if (value && typeof value == 'object' && !Array.isArray(value)) {
|
||||
} else if (isa(value, object)) {
|
||||
// Print all nested values
|
||||
print_config(value, key)
|
||||
} else {
|
||||
@@ -192,7 +191,7 @@ switch (command) {
|
||||
|
||||
switch (actor_cmd) {
|
||||
case 'list':
|
||||
if (Object.keys(config.actors[actor_name]).length == 0) {
|
||||
if (array(config.actors[actor_name]).length == 0) {
|
||||
log.console("No configuration for actor: " + actor_name)
|
||||
} else {
|
||||
log.console("# Configuration for actor: " + actor_name)
|
||||
|
||||
@@ -2,174 +2,625 @@
|
||||
|
||||
Cell is a JavaScript variant used in cell. While very similar to JavaScript, it has several important differences that make it more suitable for actor-based programming.
|
||||
|
||||
## Key Differences from JavaScript
|
||||
Variables are delcared with 'var'. Var behaves like let.
|
||||
Constants are declared with 'def'.
|
||||
!= and == are strict, there is no !== or ===.
|
||||
There is no undefined, only null.
|
||||
There are no classes, only objects and prototypes.
|
||||
There are no arraybuffers.
|
||||
There are no automatic conversions for types.
|
||||
No proxy.
|
||||
No JS modules.
|
||||
No bigint.
|
||||
No reflect.
|
||||
No weakmap, weakset, weakref.
|
||||
No with.
|
||||
No Number, Date, Array, Math, Function
|
||||
No setters or getters or any other property descriptor things
|
||||
No eval.
|
||||
No new keyword.
|
||||
No instanceof.
|
||||
|
||||
### Null vs Undefined
|
||||
- Cell has only `null`, no `undefined`
|
||||
- Idiomatic null checking: `if (object.x == null)`
|
||||
- Uninitialized variables and missing properties return `null`
|
||||
The point of cell is to merge the prototype system and actor systems, by giving ergonomic ways to go from prototypical intherited objects to pure data records suitable for sending over the wire.
|
||||
|
||||
### Equality Operators
|
||||
- Only `==` operator exists (no `===`)
|
||||
- `==` is always strict (no type coercion)
|
||||
- `!=` for inequality (no `!==`)
|
||||
length(value)
|
||||
Length. Find the length of an array in elements, a blob in bits, or a text in codepoints. For functions, it is the arity (or number of inputs).
|
||||
|
||||
### Variable Declarations
|
||||
- `def` keyword for constants (replaces `const`)
|
||||
- `var` works like `let` (block-scoped)
|
||||
- No `let` keyword
|
||||
If the value is a record containing a length field
|
||||
|
||||
### Compilation
|
||||
- All code is compiled in strict mode
|
||||
- No need for `"use strict"` directive
|
||||
If the length field contains a function, then length(my_record) has the same effect as my_record.length().
|
||||
|
||||
### Removed Features
|
||||
Cell removes several JavaScript features for simplicity and security:
|
||||
- No `Proxy` objects
|
||||
- No ES6 module syntax (use `use()` function instead)
|
||||
- No `class` syntax (use prototypes and closures)
|
||||
- No `Reflect` API
|
||||
- No `BigInt`
|
||||
- No `WeakMap`, `WeakSet`, `WeakRef`
|
||||
- No `document.all` (HTMLAllCollection)
|
||||
- No `with` statement
|
||||
- No `Date` intrinsic (use `time` module instead)
|
||||
If the length field contains a number, return the number.
|
||||
|
||||
## Language Features
|
||||
All other values produce null.
|
||||
|
||||
### Constants
|
||||
```javascript
|
||||
def PI = 3.14159
|
||||
def MAX_PLAYERS = 4
|
||||
// PI = 3.14 // Error: cannot reassign constant
|
||||
```
|
||||
## statements
|
||||
throw
|
||||
Throw an error. If an actor has an uncaught error, it crashes.
|
||||
|
||||
### Variables
|
||||
```javascript
|
||||
var x = 10
|
||||
{
|
||||
var y = 20 // Block-scoped like let
|
||||
x = 15 // Can access outer scope
|
||||
## Native constants and functions
|
||||
|
||||
false
|
||||
true
|
||||
null
|
||||
|
||||
use(text)
|
||||
Use a module. Returns the module. Modules are a script which return a single value. The value is cached, and the module is only reloaded when the file changes. The value is stone, so it cannot be modified.
|
||||
|
||||
The returned value is actually a cell runtime owned object, which may be modified for hot reloading purposes.
|
||||
|
||||
stone(value)
|
||||
Petrify the value, turning it into stone. Its contents are preserved, but it can no longer be modified by the assign statement or the blob.write functions. This is usually performed on arrays, records, and blobs. All other types are already stone. Attempting to turn a value that is stone to stone will have no effect.
|
||||
|
||||
The stone operation is deep. Any mutable objects in the value will also be turned to stone.
|
||||
|
||||
This can not be reversed. Immutable objects can never become mutable. A mutable copy can be made of an immutable object, but the original object remains immutable.
|
||||
|
||||
The stone function returns the value.
|
||||
|
||||
isa(object, master_object)
|
||||
Return true if master is in the value's prototype chain.
|
||||
|
||||
isa(object, function)
|
||||
isa(value, function)
|
||||
Return true if function.proto is in the value's prototype chain.
|
||||
|
||||
isa(1, stone) true
|
||||
isa(1, number) true
|
||||
isa("1", stone) true
|
||||
isa("1", text) true
|
||||
isa("1", number) false
|
||||
isa($me, actor) true
|
||||
isa($me, object) false
|
||||
isa($me, stone) true
|
||||
|
||||
isa(object, array)
|
||||
Return true if the object has keys for each value in the array.
|
||||
|
||||
reverse(array|blob)
|
||||
The reverse function makes a new array or blob with the elements or bits in the opposite order.
|
||||
|
||||
It returns a new, reversed array or blob.
|
||||
|
||||
fn
|
||||
The function object; not a function.
|
||||
|
||||
fn.apply(function, array)
|
||||
Apply. Execute the function and return its return value. Pass the elements of the array as input values. See proxy.
|
||||
|
||||
If the first input value is not a function, apply returns its first input value.
|
||||
|
||||
If length(array) is greater than length(function), it disrupts.
|
||||
|
||||
If the second argument is not an array, it is used as a single input value.
|
||||
|
||||
### log
|
||||
log(*name*, *options*)
|
||||
Establish a logging channel.
|
||||
|
||||
log.*channel*(*text)
|
||||
Write to a channel. There are a few already defined:
|
||||
|
||||
log.console
|
||||
log.error
|
||||
|
||||
### text
|
||||
text(*array*, *separator*)
|
||||
Convert an array to text. All are concatenated to create a single text, separated by the separator. The default separator is a space.
|
||||
|
||||
text(*number*, *radix*)
|
||||
Convert a number to text. The radix is 2 through 37.The default radix is 10.
|
||||
|
||||
text(*text*)
|
||||
Return the text.
|
||||
|
||||
text(*text*, *from*, *to*)
|
||||
Return the text from the from index to the to index.
|
||||
|
||||
text.lower(text)
|
||||
The lower function returns a text in which all uppercase characters are converted to lowercase.
|
||||
|
||||
text.normalize(text)
|
||||
Unicode normalize. Return a text whose textual value is the same as text, but whose binary representation is in the specified Unicode normalization form. The two texts will display the same, but might not be equal.
|
||||
|
||||
text.extract(text, pattern, from, to)
|
||||
The text is matched to the pattern. If it does not match, the result is null. If the pattern does match, then the result is a record containing the saved fields.
|
||||
|
||||
text.replace(text, target, replacement, limit)
|
||||
Return a new text in which the target is replaced by the replacement.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be replaced in the source.
|
||||
|
||||
replacement: text to replace the matched text, or a function that takes the matched text and the starting position, and returns the replacement text or null if it should be left alone.
|
||||
|
||||
limit: The maximum number of replacements. The default is all possible replacements. The limit includes null matches.
|
||||
|
||||
text.format(text, collection, transformer)
|
||||
The format function makes a new text with substitutions in an original text. A collection is either an array of texts or a record of texts.
|
||||
|
||||
A search is made for {left brace and }right brace in the text. If they are found, the middle text between them is examined. If the collection is an array, the middle text is used as a number, and then the matched {left brace and middle and }right brace are replaced with the text at that subscript in the array. If the collection is a record, and if the middle text is the key of a member of the collection with a text value, then the value of the member is used in the substitution. Unmatched text is not altered.
|
||||
|
||||
The text between {left brace and }right brace is broken on the :colon character. The left text will be used as a number or name to select a value from the collection. (The value need not be a text.)
|
||||
|
||||
If a transformer input is a function, then it is called with the collection[left text] and right text as inputs. If it returns a text, then the substitution is made.
|
||||
|
||||
If a transformer input is a record, then the right text is used to select a function from the transformer. That function is passed the value from the collection. If the return value is a text, that text will substitute. If there is no colon, then the empty text is used to select the function from the transformer. If the transformer does not produce a function, or if the function does not return a text, then no replacement occurs. If transformer[right text](collection[left text]) produces a text, then the substitution is made.
|
||||
|
||||
If the substitution is not made, and if collection[left text] is a number, then the right text is used as a format input in calling collection[left text].text(right text). If it returns a text, then the substitution is made.
|
||||
|
||||
text.codepoint(text)
|
||||
The codepoint function returns the codepoint number of the first character of the text. If the input value is not a text, or if it is the empty text, then it returns null.
|
||||
|
||||
text.search(text, target, from)
|
||||
Search the text for the target. If the target is found, return the character position of the left-most part of the match. If the search fails, return null.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be found in the source.
|
||||
|
||||
from: The starting position for the search. The default is 0, the beginning of the text. If from is negative, it is added to length(text).
|
||||
|
||||
text.trim(text, reject)
|
||||
The trim function removes selected characters from the ends of a text. The default is to remove control characters and spaces.
|
||||
|
||||
text.upper(text)
|
||||
The upper function returns a text in which all lowercase characters are converted to uppercase.
|
||||
|
||||
### logical
|
||||
logical(value)
|
||||
Convert a value to a logical (boolean).
|
||||
If value is 0, false, "false", or null, return false.
|
||||
If value is 1, true, or "true", return true.
|
||||
Otherwise, return null.
|
||||
|
||||
### number
|
||||
number(logical)
|
||||
Result is 1 or 0.
|
||||
|
||||
number(number)
|
||||
Return the number.
|
||||
|
||||
number(text, radix)
|
||||
Convert a text to a number. The radix is 2 through 37. The default radix is 10.
|
||||
|
||||
number(text, format)
|
||||
The number function converts a text into a number.
|
||||
|
||||
If it is unable to (possibly because of a formatting error), it returns null. The format character determines how the text is interpreted. If the format is not one of those listed, then null is returned.
|
||||
|
||||
format radix separator decimal point
|
||||
"" 10 .period
|
||||
"n"
|
||||
"u" _underbar
|
||||
"d" ,comma
|
||||
"s" space
|
||||
"v" .period ,comma
|
||||
"l" dependent on locale
|
||||
"i" _underbar
|
||||
"b" 2
|
||||
"o" 8
|
||||
"h" 16
|
||||
"t" 32
|
||||
"j" 0x- base 16
|
||||
0o- base 8
|
||||
0b- base 2
|
||||
otherwise base 10
|
||||
|
||||
number.whole(number)
|
||||
Return the whole part of a number.
|
||||
4.9 => 4
|
||||
4.2 => 4
|
||||
|
||||
number.fraction(number)
|
||||
Return the fractional part of a number
|
||||
|
||||
number.floor(number, place)
|
||||
If place is 0 or null, the number is rounded down to the greatest integer that is less than or equal to the number. If place is a small positive integer, then the number is rounded down to that many decimal places. For positive numbers, this is like discarding decimal places.
|
||||
|
||||
number.ceiling(number, place)
|
||||
If place is 0 or null, the number is rounded up to the smallest integer that is greater than or equal to the number. If place is a small positive integer, then the number is rounded up to that decimal place.
|
||||
|
||||
number.abs(number)
|
||||
Absolute value. Return the positive form of the number. If the input value is not a number, the result is null.
|
||||
|
||||
number.round(number, place)
|
||||
If place is 0 or null, the number is rounded to the nearest integer. If place is a small integer, then the number is rounded to that many decimal places.
|
||||
|
||||
number.sign(number)
|
||||
The sign function returns -1 if the number is negative, 0 if the number is exactly 0, 1 if the number is positive, and null if it is not a number.
|
||||
|
||||
number.trunc(number, place)
|
||||
The number is truncated toward zero. If the number is positive, the result is the same as floor(place). If the number is negative, the result is the same as ceiling(place).
|
||||
|
||||
If place is a small integer, then the number is rounded down to that many decimal places. This is like discarding decimal places.
|
||||
|
||||
number.min(...vals)
|
||||
Returns the smallest of all vals.
|
||||
|
||||
number.max(...vals)
|
||||
Returns the largest of all vals.
|
||||
|
||||
number.remainder(dividend, divisor)
|
||||
Remainder. For fit integers, the remainder is dividend - ((dividend // divisor) * divisor).
|
||||
|
||||
### array
|
||||
array(number)
|
||||
Create an array of the specified size. All elements are null.
|
||||
|
||||
array(number, initial_value)
|
||||
Make an array. All of the elements are initialized to initial_value.
|
||||
|
||||
number is a non-negative integer, the intended length of the new array.
|
||||
|
||||
If initial_value is a function, then the function is called for each element to produce initialization values. If the function has an arity of 1 or more, it is passed the element number.
|
||||
|
||||
array(array)
|
||||
Copy.
|
||||
|
||||
array(array, function, reverse, exit)
|
||||
Map. Call the function with each element of the array, collecting the return values in a new array. The function is passed each element and its element number.
|
||||
|
||||
function (element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
If exit is not null, then when the function returns the exit value, then the array function returns early. The exit value will not be stored into the new array. If the array was processed normally, then the returned array will be shorter than the input array. If the array was processed in reverse, then the returned array will have the same length as the input array, and the first elements will be null. The elements in the new array that were not set due to an early exit will be set to null.
|
||||
|
||||
array(array, another_array)
|
||||
Concat. Produce a new array that concatenates the array and another_array.
|
||||
|
||||
array(array, from, to)
|
||||
Slice. Make a mutable copy of all or part of an array.
|
||||
|
||||
array: the array to copy
|
||||
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(array).
|
||||
|
||||
to: the position at which to stop copying. Default: length(array), the end. If negative, add length(array).
|
||||
|
||||
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(array).
|
||||
|
||||
array(object)
|
||||
Keys. Make an array containing all of the text keys in the object. The keys are not guaranteed to be in any particular order.
|
||||
|
||||
array(text)
|
||||
Split the text into grapheme clusters. A grapheme cluster is a Unicode codepoint and its contributing combining characters, if any.
|
||||
|
||||
array(text, separator)
|
||||
Split the text into an array of subtexts. The separator can be a text or pattern.
|
||||
|
||||
array(text, length)
|
||||
Dice the text into an array of subtexts of a given length.
|
||||
|
||||
array.reduce(array, function, initial, reverse)
|
||||
Reduce. The reduce function takes a function that takes two input values and returns a value.
|
||||
|
||||
function (first, second) {
|
||||
return ...
|
||||
}
|
||||
// y is not accessible here
|
||||
```
|
||||
The function is called for each element of the array, passing the result of the previous iteration to the next iteration.
|
||||
|
||||
### Null Checking
|
||||
```javascript
|
||||
var obj = {name: "player"}
|
||||
if (obj.score == null) {
|
||||
obj.score = 0
|
||||
}
|
||||
```
|
||||
The initial value is optional. If present, the function is called for every element of the array.
|
||||
|
||||
### Functions
|
||||
```javascript
|
||||
// Function declaration
|
||||
function add(a, b) {
|
||||
return a + b
|
||||
}
|
||||
If initial is null:
|
||||
|
||||
// Function expression
|
||||
var multiply = function(a, b) {
|
||||
return a * b
|
||||
}
|
||||
If length(array) is 0, then it returns null.
|
||||
If length(array) is 1, then it returns array[0].
|
||||
If length(array) is 2, it returns function(array[0], array[1]).
|
||||
If length(array) is 3, it returns function(function(array[0], array[1]), array[2]).
|
||||
And so on.
|
||||
If initial is not null:
|
||||
|
||||
// Arrow functions work normally
|
||||
var square = x => x * x
|
||||
```
|
||||
If length(array) is 0, then it returns initial.
|
||||
If length(array) is 1, then it returns function(initial, array[0]).
|
||||
If length(array) is 2, it returns function(function(initial, array[0]), array[1]).
|
||||
If length(array) is 3, it returns function(function(function(initial, array[0]), array[1]), array[2]).
|
||||
And so on.
|
||||
If reverse is true, then the work begins at the end of the array and works backward.
|
||||
|
||||
### Objects and Prototypes
|
||||
```javascript
|
||||
// Object creation
|
||||
var player = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
move: function(dx, dy) {
|
||||
this.x += dx
|
||||
this.y += dy
|
||||
}
|
||||
}
|
||||
array.for(array, function, reverse, exit)
|
||||
For each. Call the function with each element of the array. The function is passed each element and its element number.
|
||||
|
||||
// Prototype-based inheritance
|
||||
function Enemy(x, y) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
Enemy.prototype.attack = function() {
|
||||
// Attack logic
|
||||
}
|
||||
```
|
||||
(element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
### Module System
|
||||
Cell uses a custom module system with the `use()` function:
|
||||
```javascript
|
||||
var math = use('math')
|
||||
var draw2d = use('prosperon/draw2d')
|
||||
```
|
||||
If exit is not null, then when the function returns the exit value, then the for function returns early. The exit value is usually true or false, but it may be anything. If exit is null, then every element is processed.
|
||||
|
||||
### Time Handling
|
||||
Since there's no `Date` object, use the `time` module:
|
||||
```javascript
|
||||
var time = use('time')
|
||||
var now = time.number() // Numeric timestamp
|
||||
var record = time.record() // Structured time
|
||||
var text = time.text() // Human-readable time
|
||||
```
|
||||
The for function returns null unless it did an early exit, when it returns the exit value.
|
||||
|
||||
## Best Practices
|
||||
array.find(array, function, reverse, from)
|
||||
Call the function for each element of the array, passing each element and its element number.
|
||||
|
||||
1. **Prefer `def` for values that won't change**
|
||||
```javascript
|
||||
def TILE_SIZE = 32
|
||||
var playerPos = {x: 0, y: 0}
|
||||
```
|
||||
(element, element_nr)
|
||||
If the function returns true, then find returns the element number of the current element.
|
||||
|
||||
2. **Always check for null explicitly**
|
||||
```javascript
|
||||
if (player.weapon == null) {
|
||||
player.weapon = createDefaultWeapon()
|
||||
}
|
||||
```
|
||||
If the second input value is not a function, then it is compared exactly to the elements.
|
||||
|
||||
3. **Use prototype patterns instead of classes**
|
||||
```javascript
|
||||
function GameObject(x, y) {
|
||||
this.x = x
|
||||
this.y = y
|
||||
}
|
||||
GameObject.prototype.update = function(dt) {
|
||||
// Update logic
|
||||
}
|
||||
```
|
||||
If the reverse input value is true, then search begins at the end of the array and works backward.
|
||||
|
||||
4. **Leverage closures for encapsulation**
|
||||
```javascript
|
||||
function createCounter() {
|
||||
var count = 0
|
||||
return {
|
||||
increment: function() { count++ },
|
||||
getValue: function() { return count }
|
||||
}
|
||||
}
|
||||
```
|
||||
The from input value gives the element number to search first. The default is 0 unless reverse is true, when the default is length(array) - 1.
|
||||
|
||||
## Common Gotchas
|
||||
find returns the element number of the found value. If nothing is found, find returns null.
|
||||
|
||||
1. **No undefined means different behavior**
|
||||
```javascript
|
||||
var obj = {}
|
||||
console.log(obj.missing) // null, not undefined
|
||||
```
|
||||
array.filter(array, function)
|
||||
The filter function calls a function for every element in the array, passing each element and its element number.
|
||||
|
||||
2. **Strict equality by default**
|
||||
```javascript
|
||||
"5" == 5 // false (no coercion)
|
||||
null == 0 // false
|
||||
```
|
||||
(element, element_nr)
|
||||
When the function's return value is true, then the element is copied into a new array. If the function's return value is false, then the element is not copied into the new array. If the return value is not a logical, then the filter returns null.
|
||||
|
||||
3. **Block-scoped var**
|
||||
```javascript
|
||||
for (var i = 0; i < 10; i++) {
|
||||
setTimeout(() => console.log(i), 100) // Works as expected
|
||||
}
|
||||
```
|
||||
It returns a new array. The length of the new array is between 0 thru length(array). It returns null if the function input is not a function.
|
||||
|
||||
array.sort(array, select)
|
||||
The sort function produces a new array in which the values are sorted. Sort keys must be either all numbers or all texts. Any other type of key or any error in the key calculation will cause the sort to fail, returning null. The sort is ascending. The sort is stable, so the relative order of equal keys is preserved.
|
||||
|
||||
The optional select input determines how the sort key for each element is selected.
|
||||
|
||||
select type Sort key for array[index] Description
|
||||
null array[index] The sort key is the element itself. This is useful for sorting simple arrays of numbers or texts.
|
||||
text array[index][select] The sort key is the select field of each record element. This is useful for sorting arrays of records.
|
||||
number array[index][select] The sort key is the select element of each array element. This is useful for sorting arrays of arrays.
|
||||
array select[index] select is an array of the same length containing the sort keys.
|
||||
It returns a new, sorted array.
|
||||
|
||||
### object
|
||||
object(object)
|
||||
Shallow mutable copy. Text keys
|
||||
|
||||
splat(object)
|
||||
Create a new object with the same fields as the object, compressed down its prototype chain, for all keys.
|
||||
|
||||
The only types in a splat will be objects, arrays, numbers, boolean, and text.
|
||||
|
||||
When sending an object with $send, it is automatically splat'd.
|
||||
|
||||
meme(record, record)
|
||||
create a new record with the first as its prototype, and add the second's fields. prototypes cannot be changed.
|
||||
|
||||
proto(object)
|
||||
Return the object's prototype.
|
||||
|
||||
object(object, another_object)
|
||||
Combine. Make a copy of a object, and then put all the fields of another_object into the copy.
|
||||
|
||||
object(object, array_of_keys)
|
||||
Select. Make a new object containing only the fields that are named by the array_of_keys.
|
||||
|
||||
object(array_of_keys)
|
||||
Set. Make a object using the array as the source of the keys. Each field value is true.
|
||||
|
||||
object(array_of_keys, value)
|
||||
Value Set. Make a object using the array as the source of the keys. Each field value is value.
|
||||
|
||||
object(array_of_keys, function)
|
||||
Functional Value Set. Make a object using the array as the source of the keys. The function is called for each key, yielding the field values.
|
||||
|
||||
### parallelism
|
||||
Callbacks
|
||||
A callback function is a function that is used to deliver functional results from the future. A callback function has this signature:
|
||||
|
||||
function (value, reason)
|
||||
The value is the value of the operation if it was successful. The value is null if it is unsuccessful.
|
||||
|
||||
If the value is null, then the optional reason may include an error message or indication of the cause of the unsuccess.
|
||||
|
||||
Requestor functions take a callback.
|
||||
|
||||
Requestors
|
||||
A requestor function encapsulates a unit of work, communicating the result thru a callback, allowing work to progress over many turns. A requestor function has this signature:
|
||||
|
||||
function requestor(callback, value)
|
||||
When the requestor function is finished, it calls the callback function with the result.
|
||||
|
||||
The optional value is some value that is used to determine the result.
|
||||
|
||||
A common usage is to send a message to some actor. When the reply eventually arrives, extract a result from the reply and pass it to the callback.
|
||||
|
||||
A requestor function can optionally return a cancel function.
|
||||
|
||||
Cancels
|
||||
A requestor function may optionally return a cancel function. When the cancel function is called, it attempts to stop the work of the requestor. A cancel function try to send a message to some actor informing it that the result is no longer needed. The purpose of cancel is to stop work that is no longer required. It is advisory. It is not an undo. It is not guaranteed, particularly in the case where cancel is called after some actor has completed its work.
|
||||
|
||||
Cancel is most effective with the requestor factories that are organizing the work.
|
||||
|
||||
A cancel function has this signature:
|
||||
|
||||
function cancel(reason, abandon | true)
|
||||
The reason is optional, but if included might be logged or propagated. If abandon is true, then simply stop the requested work. If abandon is false, then if possible, complete the assignment, perhaps by calling the callback with the reason.
|
||||
|
||||
parallel(requestor_array, throttle, need)
|
||||
Parallel. The parallel requestor factory returns a resquestor function. When the requestor function is called with a callback function and a value, every requestor in the requestor_array is called with the value, and the results are placed in corresponding elements of the results array. This all happens in parallel. The value produced by the first element of the requestor_array provides the first element of the result. The completed results array is passed to the callback function.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand all at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. The throttle is optional. If provided, the throttle is a number: the maximum number of requestors to have running at any time.
|
||||
|
||||
Ordinarily, the number of successes must be the same as the number of requestors in the requestor_array. If you need few successes, specify your need with the need argument. The need could be 1, meaning that 1 or more successes are needed. The need could be 0, meaning that no successes are needed. If the number of successes is greater than or equal to need, then the whole operation succeeds. The need must be between 0 and requestor_array.length.
|
||||
|
||||
The requestor function itself returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
race(requestor_array, throttle, need)
|
||||
Race. The race function is a requestor factory that takes an array of requestor functions and returns a requestor function that starts all of the requestors in the requestor_array at once.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started.
|
||||
|
||||
By default, a single result is produced. If an array of results is need, specify the needed number of results in the need parameter. When the needed number of successful results is obtained, the operation ends. The results go into a sparce array aligned with the requestor_array, and unfinished requestors are cancelled. The need argument must be between 1 and requestor_array.length.
|
||||
|
||||
The returned requestor function returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
sequence(requestor_array)
|
||||
Sequence. The sequence requestor factory that takes a requestor_array and returns a requestor function that starts all of the requestors in the requestor_array one at a time. The result of each becomes the input to the next. The last result is the result of the sequence.
|
||||
|
||||
sequence returns a requestor that returns a cancel function and processes each requestor in requestor_array one at a time. Each of those requestors is passed the result of the previous requestor as its value argument. If all succeed, then the sequence succeeds, giving the result of the last of the requestors. If any fail, then the sequence fails.The sequence succeeds if every requestor in the requestor_array succeeds
|
||||
|
||||
fallback(requestor_array)
|
||||
The fallback requestor factory returns a requestor function that tries each of the requestors in the requstor_array until it gets a success. When the requestor is called, it calls the first requestor in requestor_array. If that is eventually successful, its value is passed to the callback. But if that requestor fails, the next requestor is called, and so on. If none of the requestors is successful, then the fallback fails. If any one succeeds, then the fallback succeeds.
|
||||
|
||||
The fallback requestor returns a cancel function that can be called when the result is no longer needed.
|
||||
|
||||
## Standard library
|
||||
|
||||
### time
|
||||
use('time')
|
||||
|
||||
constants
|
||||
time.second : 1
|
||||
The number of seconds in a second.
|
||||
|
||||
time.minute : 60
|
||||
The number of seconds in a minute.
|
||||
|
||||
time.hour : 3_600
|
||||
The number of seconds in an hour of 60 minutes.
|
||||
|
||||
time.day : 86_400
|
||||
The number of seconds in a day of 24 hours.
|
||||
|
||||
time.week : 604_800
|
||||
The number of seconds in a week of 7 days.
|
||||
|
||||
time.month : 2_629_746
|
||||
The number of seconds in a Gregorian month of 30.436875 days.
|
||||
|
||||
time.year : 31_556_952
|
||||
The number of seconds in a Gregorian year of 365.2425 days
|
||||
|
||||
time has three functions: time.number(), time.record(), time.text().
|
||||
|
||||
time.number() returns the time now as a number of seconds since the epoch.
|
||||
time.record() returns the time now as a record, with year, month, day, hour, minute, second, and nanosecond.
|
||||
time.text(format) returns the time now as a text, using the format string. The standard format string is "yyyy-MM-dd HH:mm:ss.SSS".
|
||||
|
||||
Pass a time into a function to convert it.
|
||||
|
||||
It's easy to say "tomorrow": time.number() + time.day = this time tomorrow. As a string: time.text(time.number() + time.day).
|
||||
|
||||
time.number(text, format, zone)
|
||||
time.number(record)
|
||||
returns the time as a number of seconds since the epoch.
|
||||
|
||||
time.text(number, format, zone)
|
||||
time.text(record, format, zone)
|
||||
returns the time as a text, using the format string. The standard format string is "yyyy-MM-dd HH:mm:ss.SSS".
|
||||
|
||||
time.record(number)
|
||||
time.record(text, format, zone)
|
||||
|
||||
### math
|
||||
There are three math variants: use('math/radians'), use('math/degrees'), use('math/cycles'). Each has the same functions, but with different representation of angles.
|
||||
|
||||
arc_cosine(number)
|
||||
arc_sine(number)
|
||||
arc_tangent(number, denominator)
|
||||
Compute the arc tangent of a If the optional denominator is supplied, then the value is obtained from number / denominator, using the signs of both to determine the quadrant. The denominator is allowed to be 0. The result is the inverse tangent, in radians, between neg(π) and π.
|
||||
|
||||
e(power = 1)
|
||||
ln(number)
|
||||
Natural log
|
||||
|
||||
log(number)
|
||||
Base 10 log
|
||||
|
||||
log2(number)
|
||||
Base 2 log
|
||||
|
||||
power(first, second)
|
||||
root(radicand, number)
|
||||
sine(number)
|
||||
sqrt(number)
|
||||
tangent(number)
|
||||
|
||||
### blob
|
||||
use('blob')
|
||||
|
||||
A blob is a binary large object, a linear container of bits. Blobs can be used to encode data, messages, sounds, images, public keys, network addresses, and encrypted payloads.
|
||||
|
||||
A blob can be in one of two states, either antestone or stone. In the mutable antestone state, the write functions may be used to append bits to the blob. In the immutable stone state, bits can be harvested from the blob. Bits can be written to blobs as fixed size bit fields, that is a sequence of bits with a specified length, or as a kim.
|
||||
|
||||
blob.make()
|
||||
Make a new empty blob.
|
||||
|
||||
blob.make(capacity)
|
||||
Make a new empty blob with an initial capacity in bits. When turned to stone, the excess bits are discarded. If the initial capacity is too small, the write functions will extended it. A good initial guess can improve performance.
|
||||
|
||||
blob.make(length, logical)
|
||||
Make a new blob containing all zeros (false) or all ones (true).
|
||||
|
||||
blob.make(length, random)
|
||||
Make a new blob of a given length whose content is random. The random input is a random generator function that returns fit numbers, like random.random_fit().
|
||||
|
||||
blob.make(blob, from, to)
|
||||
Make a copy of all or part of a blob. The default of from is 0. The default of to is the length(blob).
|
||||
|
||||
blob.write_bit(blob, logical)
|
||||
Append a bit to the end of the blob. The logical value can be true, false, 1, or 0. Any other value will throw.
|
||||
|
||||
blob.write_blob(blob, second_blob)
|
||||
Append second_blob to the end of blob.
|
||||
|
||||
blob.write_dec64(blob, number)
|
||||
Append a 64 bit DEC64 encoded number to a stone blob.
|
||||
|
||||
blob.write_fit(blob, fit, length)
|
||||
Append a bit field to the blob. If the fit requires more bits than allowed by length, it throws.
|
||||
|
||||
blob.write_kim(blob, fit)
|
||||
Append a fit number or a single character as a kim value.
|
||||
|
||||
blob.write_pad(blob, block_size)
|
||||
Append a 1 bit to the blob followed by enough 0 bits to round up the blob's length to a multiple of the block_size.
|
||||
|
||||
blob.write_text(blob, text)
|
||||
Append a text. This will be encoded as a kim encoded length followed by a sequence of kim encoded UTF-32 characters.
|
||||
|
||||
blob.read_blob(blob, from, to)
|
||||
Make a copy of all or part of a blob. The default of from is 0. The default of to is the length(blob).
|
||||
|
||||
blob.read_dec64(blob, from)
|
||||
Retrieve a 64 bit DEC64 encoded number from a stone blob.
|
||||
|
||||
blob.read_fit(blob, from, length)
|
||||
Retrieve a fit number from a bit field from a stone blob.
|
||||
|
||||
blob.read_kim(blob, from)
|
||||
Retrieve a kim encoded fit number from a stone blob.
|
||||
|
||||
blob.read_logical(blob, from)
|
||||
Retrieve a bit from the blob. If blob is not a stone blob, or if from is out of range, it returns null.
|
||||
|
||||
blob.read_text(blob, from)
|
||||
Retrieve a kim encoded text from a stone blob.
|
||||
|
||||
blob.pad?(blob, from, block_size)
|
||||
Return true if the stone blob's length is a multiple of the block_size (in bits), and if the difference between length and from is less than or equal to the block_size, and if the bit at from is 1, and that any remaining bits are 0. See write_pad.
|
||||
|
||||
### json
|
||||
use('json')
|
||||
|
||||
json.encode(value, space, replacer, whitelist)
|
||||
json.decode(text, reviver)
|
||||
|
||||
### random
|
||||
random.random()
|
||||
The random function returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5.
|
||||
|
||||
random.random_fit()
|
||||
The random_fit function returns an integer in the range -36028797018963968 thru 36028797018963967 that contains 56 random bits. See fit.
|
||||
|
||||
random.random_whole(number)
|
||||
The random_whole function returns a whole number that is greater than or equal to zero and less than the number, which must be less than 36028797018963968.
|
||||
|
||||
## Actors
|
||||
A program has may have access to functions which are not standard which have been bestowed on it. These begin with a '$'.
|
||||
|
||||
$send(actor, message, callback)
|
||||
$clock(callback)
|
||||
$delay(callback, seconds)
|
||||
$time_limit(requestor, seconds)
|
||||
$contact(callback, record)
|
||||
$couple(actor)
|
||||
$portal(callback, port)
|
||||
$receiver(callback)
|
||||
$start(callback, program)
|
||||
$stop(actor)
|
||||
$unneeded(function, seconds)
|
||||
|
||||
$me
|
||||
The actor object of the running program.
|
||||
|
||||
207
docs/trampoline.md
Normal file
207
docs/trampoline.md
Normal file
@@ -0,0 +1,207 @@
|
||||
Yep — here’s the concrete picture, with the “no-Proxy” trampoline approach, and what it can/can’t do.
|
||||
|
||||
## A concrete hot-reload example (with trampolines)
|
||||
|
||||
### `sprite.cell` v1
|
||||
|
||||
```js
|
||||
// sprite.cell
|
||||
var X = key('x')
|
||||
var Y = key('y')
|
||||
|
||||
def proto = {
|
||||
move: function(dx, dy) {
|
||||
this[X] += dx
|
||||
this[Y] += dy
|
||||
}
|
||||
}
|
||||
|
||||
var make = function(x, y) {
|
||||
var s = meme(proto)
|
||||
s[X] = x
|
||||
s[Y] = y
|
||||
return s
|
||||
}
|
||||
|
||||
return {
|
||||
proto: proto,
|
||||
make: make
|
||||
}
|
||||
```
|
||||
|
||||
### What the runtime stores on first load
|
||||
|
||||
Internally (not visible to Cell), you keep a per-module record:
|
||||
|
||||
```js
|
||||
module = {
|
||||
scope: { X, Y, proto, make }, // bindings created by var/def in module
|
||||
export_current: { proto, make }, // what the module returned
|
||||
export_handle: null // stable thing returned by use() in hot mode
|
||||
}
|
||||
```
|
||||
|
||||
Now, **in hot-reload mode**, `use('sprite')` returns `export_handle` instead of `export_current`. Since you don’t have Proxy/getters, the handle can only be “dynamic” for **functions** (because functions are called). So you generate trampolines for exported functions:
|
||||
|
||||
```js
|
||||
// export_handle is a plain object
|
||||
export_handle = stone({
|
||||
// stable reference (proto is identity-critical and will be patched in place)
|
||||
proto: module.scope.proto,
|
||||
|
||||
// trampoline (always calls the latest implementation)
|
||||
make: function(...args) {
|
||||
return module.scope.make.apply(null, args)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
Note what this buys you:
|
||||
|
||||
* Anyone who cached `var sprite = use('sprite')` keeps the same `sprite` object forever.
|
||||
* Calling `sprite.make(...)` always goes through the trampoline and hits the *current* `module.scope.make`.
|
||||
|
||||
### Reload to `sprite.cell` v2
|
||||
|
||||
Say v2 changes `move` and `make`:
|
||||
|
||||
```js
|
||||
def proto = {
|
||||
move: function(dx, dy) {
|
||||
// new behavior
|
||||
this[X] = this[X] + dx * 2
|
||||
this[Y] = this[Y] + dy * 2
|
||||
}
|
||||
}
|
||||
|
||||
var make = function(x, y) { ... } // changed too
|
||||
return { proto, make }
|
||||
```
|
||||
|
||||
Runtime reload sequence (safe point: between actor turns):
|
||||
|
||||
1. Evaluate the new module to produce `new_scope` and `new_export`.
|
||||
2. Reconcile into the old module record:
|
||||
|
||||
* **`var` bindings:** rebind
|
||||
|
||||
* `old.scope.make = new.scope.make`
|
||||
* (and any other `var`s)
|
||||
|
||||
* **`def` bindings:** keep the binding identity, but if it’s an object you want hot-updatable, **patch in place**
|
||||
|
||||
* `old.scope.proto.move = new.scope.proto.move`
|
||||
* (and other fields on proto)
|
||||
|
||||
Now the magic happens:
|
||||
|
||||
* Existing instances `s` have prototype `old.scope.proto` (stable identity).
|
||||
* You patched `old.scope.proto.move` to point at the new function.
|
||||
* So `s.move(...)` immediately uses the new behavior.
|
||||
* And `sprite.make(...)` goes through the trampoline to `old.scope.make`, which you rebound to the new `make`.
|
||||
|
||||
That’s “real” hot reload without Proxy.
|
||||
|
||||
---
|
||||
|
||||
## “Module exports just a function” — yes, and it’s actually the easiest
|
||||
|
||||
If a module returns a function:
|
||||
|
||||
```js
|
||||
// returns a function directly
|
||||
return function(x) { ... }
|
||||
```
|
||||
|
||||
Hot-reload mode can return a **trampoline function**:
|
||||
|
||||
```js
|
||||
handle = stone(function(...args) {
|
||||
return module.scope.export_function.apply(this, args)
|
||||
})
|
||||
```
|
||||
|
||||
On reload, you rebind `module.scope.export_function` to the new function, and all cached references keep working.
|
||||
|
||||
---
|
||||
|
||||
## “Module exports just a string” — possible, but not hot-swappable by reference (without changing semantics)
|
||||
|
||||
If the export is a primitive (text/number/logical/null), there’s no call boundary to hang a trampoline on. If you do:
|
||||
|
||||
```js
|
||||
return "hello"
|
||||
```
|
||||
|
||||
Then anyone who did:
|
||||
|
||||
```js
|
||||
def msg = use('msg') // msg is a text value
|
||||
```
|
||||
|
||||
…is holding the text itself. You can’t “update” that value in place without either:
|
||||
|
||||
### Option 1: Accept the limitation (recommended)
|
||||
|
||||
* Hot reload still reloads the module.
|
||||
* But **previously returned primitive exports don’t change**; callers must call `use()` again to see the new value.
|
||||
|
||||
This keeps your semantics clean.
|
||||
|
||||
### Option 2: Dev-mode wrapping (changes semantics)
|
||||
|
||||
In hot-reload mode only, return a box/thunk instead:
|
||||
|
||||
* box: `{ get: function(){...} }`
|
||||
* thunk: `function(){ return current_text }`
|
||||
|
||||
But then code that expects a text breaks unless it’s written to handle the box/thunk. Usually not worth it unless you explicitly want “dev mode has different types”.
|
||||
|
||||
**Best convention:** if you want a reloadable “string export”, export a function:
|
||||
|
||||
```js
|
||||
var value = "hello"
|
||||
return { get: function() { return value } }
|
||||
```
|
||||
|
||||
Now `get()` is trampoline-able.
|
||||
|
||||
---
|
||||
|
||||
## About `var` vs `def` on reload
|
||||
|
||||
You’re very close, just phrase it precisely:
|
||||
|
||||
* **`var`**: binding is hot-rebindable
|
||||
On reload, `old.scope[name] = new.scope[name]`.
|
||||
|
||||
* **`def`**: binding identity is stable (const binding)
|
||||
On reload, you do **not** rebind the slot.
|
||||
|
||||
But: for `def` that points to **mutable objects that must preserve identity** (like prototypes), you *can still patch the object’s fields in place*:
|
||||
|
||||
* binding stays the same object
|
||||
* the object’s contents update
|
||||
|
||||
That’s not “setting new defs to old defs”; it’s “keeping old defs, optionally copying new content into them”.
|
||||
|
||||
If you want to avoid surprises, make one explicit rule:
|
||||
|
||||
* “def objects may be patched in place during hot reload; def primitives are never replaced.”
|
||||
|
||||
---
|
||||
|
||||
## One important consequence of “no Proxy / no getters”
|
||||
|
||||
Your trampoline trick only guarantees hot-reload for:
|
||||
|
||||
* exported **functions** (via trampolines)
|
||||
* exported **objects whose identity never changes** (like `proto`), because the handle can point at the stable old object
|
||||
|
||||
It **does not** guarantee hot-reload for exported scalars that you expect to change (because the handle can’t dynamically compute a property value).
|
||||
|
||||
That’s fine! It just becomes a convention: “export state through functions, export identity anchors as objects.”
|
||||
|
||||
---
|
||||
|
||||
If you keep those rules crisp in the doc, your hot reload story becomes genuinely robust *and* lightweight: most work is “rebind vars” + “patch proto tables” + “trampoline exported functions.” The rest is just conventions that make distributed actor code sane.
|
||||
@@ -21,7 +21,7 @@ function get_progress() {
|
||||
if (state.total_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.round((state.downloaded_bytes / state.total_bytes) * 100);
|
||||
return number.round((state.downloaded_bytes / state.total_bytes) * 100);
|
||||
}
|
||||
|
||||
// Helper to format status response
|
||||
@@ -43,7 +43,7 @@ function get_status() {
|
||||
downloaded_bytes: state.downloaded_bytes,
|
||||
total_bytes: state.total_bytes,
|
||||
elapsed_seconds: elapsed,
|
||||
bytes_per_second: Math.round(bytes_per_sec)
|
||||
bytes_per_second: number.round(bytes_per_sec)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
107
help/jit.md
Normal file
107
help/jit.md
Normal file
@@ -0,0 +1,107 @@
|
||||
Yep — what you’re describing is *exactly* how fast JS engines make “normal-looking arrays” fast: **an array carries an internal “elements kind”**, e.g. “all int32”, “all doubles”, “boxed values”, and it *transitions* to a more general representation the moment you store something that doesn’t fit. V8 literally documents this “elements kinds” + transitions model (packed smi → packed double → packed elements, plus “holey” variants). ([V8][1])
|
||||
|
||||
### 1) “Numbers-only until polluted” arrays: do it — but keep it brutally simple
|
||||
|
||||
For CellScript, you can get most of the win with just **three** internal kinds:
|
||||
|
||||
* **PACKED_I32** (or “fit int” if you want)
|
||||
* **PACKED_F64**
|
||||
* **PACKED_VALUE** (boxed JSValue)
|
||||
|
||||
Rules:
|
||||
|
||||
* On write, if kind is I32 and value is i32 → store unboxed.
|
||||
* If value is non-i32 number → upgrade whole backing store to F64.
|
||||
* If value is non-number → upgrade to VALUE.
|
||||
* Never downgrade (keep it one-way like V8 does). ([V8][1])
|
||||
|
||||
Extra credit that matters more than it sounds: **forbid sparse arrays** (or define them away). If an out-of-range write extends the array, *fill with null* so the storage remains dense. That keeps iteration tight and avoids “holey” variants (which are a real perf cliff in engines). ([V8][1])
|
||||
Your standard library already nudges toward dense arrays (constructors like `array(n)` fill with null).
|
||||
|
||||
### 2) Your “fast property op assuming data properties only” is the biggest bang
|
||||
|
||||
Since you’ve banned Proxy and all descriptor/accessor machinery , you can add VM ops that assume the world is sane:
|
||||
|
||||
* `GET_PROP_PLAIN`
|
||||
* `SET_PROP_PLAIN`
|
||||
|
||||
Then slap an **inline cache** (IC) on them: cache `(shape_id, slot_offset)` for a given property name/key. On hit → direct load/store by offset. On miss → slow path resolves, updates cache.
|
||||
|
||||
This is not hypothetical: QuickJS forks have pursued this; QuickJS-NG had discussion of polymorphic inline caches (PolyIC) with reported big wins in some forks. ([GitHub][2])
|
||||
|
||||
Even if you keep “objects are fully dynamic”, ICs still work great because most call sites are monomorphic in practice.
|
||||
|
||||
### 3) “What else should I remove to make JITing easier + faster?”
|
||||
|
||||
The best deletions are the ones that eliminate **invalidation** (stuff that forces “anything could change”):
|
||||
|
||||
1. **Prototype mutation** (you already forbid it; `meme` creates, “prototypes cannot be changed”).
|
||||
2. **Accessors / defineProperty / descriptors** (you already forbid it).
|
||||
3. **Proxy / Reflect** (already gone).
|
||||
4. **Property enumeration order guarantees** — and you already *don’t* guarantee key order for `array(object)`.
|
||||
That’s secretly huge: you can store properties in whatever layout is fastest (hash + compact slots) without “insertion order” bookkeeping.
|
||||
5. **Sparse arrays / hole semantics** (if you delete this, your array JIT story becomes *way* easier).
|
||||
|
||||
Stuff that’s *less* important than people think:
|
||||
|
||||
* Keeping `delete` as a keyword is fine *if* you implement it in a JIT-friendly way (next point).
|
||||
|
||||
### 4) You can keep `delete` without wrecking shapes: make it a “logical delete”
|
||||
|
||||
If you want “`obj[k] = null` deletes it”, you can implement deletion as:
|
||||
|
||||
* keep the slot/offset **stable**
|
||||
* store **null** and mark the property as “absent for enumeration / membership”
|
||||
|
||||
So the shape doesn’t thrash and cached offsets stay valid. `delete obj[k]` becomes the same thing.
|
||||
|
||||
That’s the trick: you keep the *semantics* of deletion, but avoid the worst-case performance behavior (shape churn) that makes JITs sad.
|
||||
|
||||
### 5) What assumptions do “meme + immutable prototypes” unlock?
|
||||
|
||||
Two big ones:
|
||||
|
||||
* **Prototype chain links never change**, so once you’ve specialized a load, you don’t need “prototype changed” invalidation machinery.
|
||||
* If your prototypes are usually **stone** (module exports from `use()` are stone) , then prototype *contents* don’t change either. That means caching “property X lives on prototype P at offset Y” is stable forever.
|
||||
|
||||
In a JIT or even in an interpreter with ICs, you can:
|
||||
|
||||
* guard receiver shape once per loop (or hoist it)
|
||||
* do direct loads either from receiver or a known prototype object
|
||||
|
||||
### 6) What do `stone` and `def` buy you, concretely?
|
||||
|
||||
**stone(value)** is a promise: “no more mutations, deep.”
|
||||
That unlocks:
|
||||
|
||||
* hoisting shape checks out of loops (because the receiver won’t change shape mid-loop)
|
||||
* for stone arrays: no push/pop → stable length + stable element kind
|
||||
* for stone objects: stable slot layout; you can treat them like read-only structs *when the key is known*
|
||||
|
||||
But: **stone doesn’t magically remove the need to identify which layout you’re looking at.** If the receiver is not a compile-time constant, you still need *some* guard (shape id or pointer class id). The win is you can often make that guard **once**, then blast through the loop.
|
||||
|
||||
**def** is about *bindings*, not object mutability:
|
||||
|
||||
* a `def` global / module binding can be constant-folded and inlined
|
||||
* a `def` that holds a `key()` capability makes `obj[that_key]` an excellent JIT target: the key identity is constant, so the lookup can be cached very aggressively.
|
||||
|
||||
### 7) LuaJIT comparison: what it’s doing, and where you could beat it
|
||||
|
||||
LuaJIT is fast largely because it’s a **tracing JIT**: it records a hot path, emits IR, and inserts **guards** that bail out if assumptions break. ([GitHub][3])
|
||||
Tables also have a split **array part + hash part** representation, which is why “array-ish” use is fast. ([Percona Community][4])
|
||||
|
||||
Could CellScript beat LuaJIT? Not as an interpreter. But with:
|
||||
|
||||
* unboxed dense arrays (like above),
|
||||
* plain-data-property ICs,
|
||||
* immutable prototypes,
|
||||
* plus either a trace JIT or a simple baseline JIT…
|
||||
|
||||
…you can absolutely be in “LuaJIT-ish” territory for the patterns you care about (actors + data + tight array loops). The big JS engines are still monsters in general-purpose optimization, but your *constraints* are real leverage if you cash them in at the VM/JIT level.
|
||||
|
||||
If you implement only two performance features this year: **(1) dense unboxed arrays with one-way kind transitions, and (2) inline-cached GET/SET for plain properties.** Everything else is garnish.
|
||||
|
||||
[1]: https://v8.dev/blog/elements-kinds?utm_source=chatgpt.com "Elements kinds in V8"
|
||||
[2]: https://github.com/quickjs-ng/quickjs/issues/116?utm_source=chatgpt.com "Optimization: Add support for Poly IC · Issue #116 · quickjs- ..."
|
||||
[3]: https://github.com/tarantool/tarantool/wiki/LuaJIT-SSA-IR?utm_source=chatgpt.com "LuaJIT SSA IR"
|
||||
[4]: https://percona.community/blog/2020/04/29/the-anatomy-of-luajit-tables-and-whats-special-about-them/?utm_source=chatgpt.com "The Anatomy of LuaJIT Tables and What's Special About Them"
|
||||
42
help/reload.md
Normal file
42
help/reload.md
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
## Hot Reloading
|
||||
Hot reloading is of primary concern here. Cell does its best to hot reload modules when possible.
|
||||
|
||||
The key function can be used to create a stable key for a module, which can be used to hot reload modules and aid in data migration.
|
||||
|
||||
During hot reload ...
|
||||
|
||||
All var declarations are rebound, so old var -> new value
|
||||
Def declarations are not rebound, but properties of objects may be adjusted.
|
||||
Returned objects and functions are wrapped in a trampoline and adjusted. The original export shape is stable (same set of keys); you will get a warning if the shape changes, and you must reload the program to get the correct new shape.
|
||||
|
||||
Hot-reload binding rules
|
||||
var bindings are rebound on hot reload
|
||||
New evaluation replaces the old value.
|
||||
Use var for:
|
||||
tunables / config
|
||||
behavior references
|
||||
caches you’re happy to discard
|
||||
anything you expect to change when code changes
|
||||
def bindings are never rebound
|
||||
The binding identity persists across reloads.
|
||||
Use def for:
|
||||
long-lived state
|
||||
prototypes / identity anchors
|
||||
capability keys
|
||||
registries you want to keep alive
|
||||
def objects may have their methods patched
|
||||
|
||||
Hot reload is best-effort. You get the full benefit when modules export a stable API object and keep long-lived identity in def (protos/state), while keeping tunables in var and reading them at use-time; caching primitives or function references opts out of live updates.
|
||||
|
||||
A module is “hot-reload friendly” if its export is a function or an object (not a primitive).
|
||||
On reload, the runtime:
|
||||
Re-evaluates the module.
|
||||
Rebinds all vars: old var binding becomes the new value from the new evaluation.
|
||||
pasted
|
||||
Keeps all def bindings (identity does not change), but:
|
||||
patches function-valued fields on def objects in place to match new code (and any other fields you explicitly define as reloadable).
|
||||
pasted
|
||||
Patches the module export handle (runtime-owned, stone-to-userland) so existing importers keep the same identity, but calls/fields can reflect new code.
|
||||
pasted
|
||||
Checks export shape: if the export is an object, its set of text keys must match the previous version; otherwise warn and require full program reload.
|
||||
298
internal/array.cm
Normal file
298
internal/array.cm
Normal file
@@ -0,0 +1,298 @@
|
||||
/* array.cm - array creation and manipulation utilities */
|
||||
|
||||
var _isArray = Array.isArray
|
||||
var _slice = Array.prototype.slice
|
||||
var _push = Array.prototype.push
|
||||
var _sort = Array.prototype.sort
|
||||
var _keys = Object.keys
|
||||
|
||||
function array(arg, arg2, arg3, arg4) {
|
||||
// array(number) - create array of size with nulls
|
||||
// array(number, initial_value) - create array with initial values
|
||||
if (typeof arg == 'number') {
|
||||
if (arg < 0) return null
|
||||
var len = number.floor(arg)
|
||||
var result = []
|
||||
|
||||
if (arg2 == null) {
|
||||
for (var i = 0; i < len; i++) result[i] = null
|
||||
} else if (typeof arg2 == 'function') {
|
||||
var arity = arg2.length
|
||||
for (var i = 0; i < len; i++) {
|
||||
result[i] = arity >= 1 ? arg2(i) : arg2()
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < len; i++) result[i] = arg2
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// array(array) - copy
|
||||
// array(array, function, reverse, exit) - map
|
||||
// array(array, another_array) - concat
|
||||
// array(array, from, to) - slice
|
||||
if (_isArray(arg)) {
|
||||
if (arg2 == null) {
|
||||
// Copy
|
||||
return _slice.call(arg)
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'function') {
|
||||
// Map
|
||||
var fn = arg2
|
||||
var reverse = arg3 == true
|
||||
var exit = arg4
|
||||
var result = []
|
||||
|
||||
if (reverse) {
|
||||
for (var i = arg.length - 1; i >= 0; i--) {
|
||||
var val = fn(arg[i], i)
|
||||
if (exit != null && val == exit) break
|
||||
result[i] = val
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var val = fn(arg[i], i)
|
||||
if (exit != null && val == exit) break
|
||||
_push.call(result, val)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (_isArray(arg2)) {
|
||||
// Concat
|
||||
var result = _slice.call(arg)
|
||||
for (var i = 0; i < arg2.length; i++) {
|
||||
_push.call(result, arg2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'number') {
|
||||
// Slice
|
||||
var from = arg2
|
||||
var to = arg3
|
||||
var len = arg.length
|
||||
|
||||
if (from < 0) from += len
|
||||
if (to == null) to = len
|
||||
if (to < 0) to += len
|
||||
|
||||
if (from < 0 || from > to || to > len) return null
|
||||
|
||||
return _slice.call(arg, from, to)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// array(object) - keys
|
||||
if (typeof arg == 'object' && arg != null && !_isArray(arg)) {
|
||||
return _keys(arg)
|
||||
}
|
||||
|
||||
// array(text) - split into grapheme clusters
|
||||
// array(text, separator) - split by separator
|
||||
// array(text, length) - dice into chunks
|
||||
if (typeof arg == 'string') {
|
||||
if (arg2 == null) {
|
||||
// Split into grapheme clusters (simplified: split into characters)
|
||||
var result = []
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
_push.call(result, arg[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'string') {
|
||||
// Split by separator
|
||||
return arg.split(arg2)
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'number') {
|
||||
// Dice into chunks
|
||||
var len = number.floor(arg2)
|
||||
if (len <= 0) return null
|
||||
var result = []
|
||||
for (var i = 0; i < arg.length; i += len) {
|
||||
_push.call(result, arg.substring(i, i + len))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.reduce = function(arr, fn, initial, reverse) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
var len = arr.length
|
||||
|
||||
if (initial == null) {
|
||||
if (len == 0) return null
|
||||
if (len == 1) return arr[0]
|
||||
|
||||
if (reverse == true) {
|
||||
var acc = arr[len - 1]
|
||||
for (var i = len - 2; i >= 0; i--) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
} else {
|
||||
var acc = arr[0]
|
||||
for (var i = 1; i < len; i++) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
}
|
||||
} else {
|
||||
if (len == 0) return initial
|
||||
|
||||
if (reverse == true) {
|
||||
var acc = initial
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
} else {
|
||||
var acc = initial
|
||||
for (var i = 0; i < len; i++) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array.for = function(arr, fn, reverse, exit) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
if (reverse == true) {
|
||||
for (var i = arr.length - 1; i >= 0; i--) {
|
||||
var result = fn(arr[i], i)
|
||||
if (exit != null && result == exit) return exit
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var result = fn(arr[i], i)
|
||||
if (exit != null && result == exit) return exit
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.find = function(arr, fn, reverse, from) {
|
||||
if (!_isArray(arr)) return null
|
||||
|
||||
var len = arr.length
|
||||
|
||||
if (typeof fn != 'function') {
|
||||
// Compare exactly
|
||||
var target = fn
|
||||
if (reverse == true) {
|
||||
var start = from != null ? from : len - 1
|
||||
for (var i = start; i >= 0; i--) {
|
||||
if (arr[i] == target) return i
|
||||
}
|
||||
} else {
|
||||
var start = from != null ? from : 0
|
||||
for (var i = start; i < len; i++) {
|
||||
if (arr[i] == target) return i
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (reverse == true) {
|
||||
var start = from != null ? from : len - 1
|
||||
for (var i = start; i >= 0; i--) {
|
||||
if (fn(arr[i], i) == true) return i
|
||||
}
|
||||
} else {
|
||||
var start = from != null ? from : 0
|
||||
for (var i = start; i < len; i++) {
|
||||
if (fn(arr[i], i) == true) return i
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.filter = function(arr, fn) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
var result = []
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var val = fn(arr[i], i)
|
||||
if (val == true) {
|
||||
_push.call(result, arr[i])
|
||||
} else if (val != false) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
array.sort = function(arr, select) {
|
||||
if (!_isArray(arr)) return null
|
||||
|
||||
var result = _slice.call(arr)
|
||||
var keys = []
|
||||
|
||||
// Extract keys
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var key
|
||||
if (select == null) {
|
||||
key = result[i]
|
||||
} else if (typeof select == 'string' || typeof select == 'number') {
|
||||
key = result[i][select]
|
||||
} else if (_isArray(select)) {
|
||||
key = select[i]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeof key != 'number' && typeof key != 'string') return null
|
||||
keys[i] = key
|
||||
}
|
||||
|
||||
// Check all keys are same type
|
||||
if (keys.length > 0) {
|
||||
var keyType = typeof keys[0]
|
||||
for (var i = 1; i < keys.length; i++) {
|
||||
if (typeof keys[i] != keyType) return null
|
||||
}
|
||||
}
|
||||
|
||||
// Create index array and sort
|
||||
var indices = []
|
||||
for (var i = 0; i < result.length; i++) indices[i] = i
|
||||
|
||||
// Stable sort using indices
|
||||
_sort.call(indices, function(a, b) {
|
||||
if (keys[a] < keys[b]) return -1
|
||||
if (keys[a] > keys[b]) return 1
|
||||
return a - b // stable
|
||||
})
|
||||
|
||||
var sorted = []
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
sorted[i] = result[indices[i]]
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
return array
|
||||
@@ -32,11 +32,13 @@ globalThis.meme = function(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
globalThis.clone = function(obj) {
|
||||
return {
|
||||
__proto__: obj.__proto__,
|
||||
...obj
|
||||
}
|
||||
globalThis.logical = function(val1)
|
||||
{
|
||||
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
|
||||
return false;
|
||||
if (val1 == 1 || val1 == true || val1 == "true")
|
||||
return true;
|
||||
return null;
|
||||
}
|
||||
|
||||
var utf8 = use_embed('utf8')
|
||||
@@ -59,6 +61,8 @@ if (!fd.is_dir(core_path)) {
|
||||
var use_cache = {}
|
||||
use_cache['core/os'] = os
|
||||
|
||||
var _Symbol = Symbol
|
||||
|
||||
// Load a core module from the file system
|
||||
function use_core(path) {
|
||||
var cache_key = 'core/' + path
|
||||
@@ -92,11 +96,22 @@ var blob_stonep = blob.prototype.stonep;
|
||||
delete blob.prototype.stone;
|
||||
delete blob.prototype.stonep;
|
||||
|
||||
// Capture Object and Array methods before they're deleted
|
||||
var _Object = Object
|
||||
var _ObjectKeys = Object.keys
|
||||
var _ObjectFreeze = Object.freeze
|
||||
var _ObjectIsFrozen = Object.isFrozen
|
||||
var _ObjectDefineProperty = Object.defineProperty
|
||||
var _ObjectGetPrototypeOf = Object.getPrototypeOf
|
||||
var _ObjectCreate = meme
|
||||
var _ArrayIsArray = Array.isArray
|
||||
|
||||
|
||||
function deepFreeze(object) {
|
||||
if (object instanceof blob)
|
||||
blob_stone.call(object);
|
||||
|
||||
var propNames = Object.keys(object);
|
||||
var propNames = _ObjectKeys(object);
|
||||
|
||||
for (var name of propNames) {
|
||||
var value = object[name];
|
||||
@@ -105,7 +120,7 @@ function deepFreeze(object) {
|
||||
deepFreeze(value);
|
||||
}
|
||||
|
||||
return Object.freeze(object);
|
||||
return _ObjectFreeze(object);
|
||||
}
|
||||
|
||||
globalThis.stone = deepFreeze
|
||||
@@ -114,18 +129,171 @@ stone.p = function(object)
|
||||
if (object instanceof blob)
|
||||
return blob_stonep.call(object)
|
||||
|
||||
return Object.isFrozen(object)
|
||||
return _ObjectIsFrozen(object)
|
||||
}
|
||||
|
||||
var actor_mod = use('actor')
|
||||
var wota = use('wota')
|
||||
var nota = use('nota')
|
||||
|
||||
// Load internal modules for global functions
|
||||
globalThis.text = use('internal/text')
|
||||
globalThis.number = use('internal/number')
|
||||
globalThis.array = use('internal/array')
|
||||
globalThis.object = use('internal/object')
|
||||
globalThis.fn = use('internal/fn')
|
||||
|
||||
// Global utility functions (use already-captured references)
|
||||
var _isArray = _ArrayIsArray
|
||||
var _keys = _ObjectKeys
|
||||
var _getPrototypeOf = _ObjectGetPrototypeOf
|
||||
var _create = _ObjectCreate
|
||||
|
||||
globalThis.length = function(value) {
|
||||
if (value == null) return null
|
||||
|
||||
// For functions, return arity
|
||||
if (typeof value == 'function') return value.length
|
||||
|
||||
// For strings, return codepoint count
|
||||
if (typeof value == 'string') return value.length
|
||||
|
||||
// For arrays, return element count
|
||||
if (_isArray(value)) return value.length
|
||||
|
||||
// For blobs, return bit count
|
||||
if (value instanceof blob && typeof value.length == 'number') return value.length
|
||||
|
||||
// For records with length field
|
||||
if (typeof value == 'object' && value != null) {
|
||||
if ('length' in value) {
|
||||
var len = value.length
|
||||
if (typeof len == 'function') return len.call(value)
|
||||
if (typeof len == 'number') return len
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
globalThis.reverse = function(value) {
|
||||
if (_isArray(value)) {
|
||||
var result = []
|
||||
for (var i = value.length - 1; i >= 0; i--) {
|
||||
result.push(value[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// For blobs, would need blob module support
|
||||
if (isa(value, blob)) {
|
||||
// Simplified: return null for now, would need proper blob reversal
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
var keyCounter = 0
|
||||
globalThis.key = function() {
|
||||
return _Symbol('key_' + (keyCounter++))
|
||||
}
|
||||
|
||||
globalThis.isa = function(value, master) {
|
||||
if (master == null) return false
|
||||
|
||||
// isa(value, array) - check if object has all keys
|
||||
if (_isArray(master)) {
|
||||
if (typeof value != 'object' || value == null) return false
|
||||
for (var i = 0; i < master.length; i++) {
|
||||
if (!(master[i] in value)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isa(value, function) - check if function.prototype is in chain
|
||||
if (typeof master == 'function') {
|
||||
// Special type checks
|
||||
if (master == stone) return _ObjectIsFrozen(value) || typeof value != 'object'
|
||||
if (master == number) return typeof value == 'number'
|
||||
if (master == text) return typeof value == 'string'
|
||||
if (master == logical) return typeof value == 'boolean'
|
||||
if (master == array) return _isArray(value)
|
||||
if (master == object) return typeof value == 'object' && value != null && !_isArray(value)
|
||||
if (master == fn) return typeof value == 'function'
|
||||
|
||||
// Check prototype chain
|
||||
if (master.prototype) {
|
||||
var proto = _getPrototypeOf(value)
|
||||
while (proto != null) {
|
||||
if (proto == master.prototype) return true
|
||||
proto = _getPrototypeOf(proto)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isa(object, master_object) - check prototype chain
|
||||
if (typeof master == 'object') {
|
||||
var proto = _getPrototypeOf(value)
|
||||
while (proto != null) {
|
||||
if (proto == master) return true
|
||||
proto = _getPrototypeOf(proto)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
globalThis.proto = function(obj) {
|
||||
if (!isa(obj, object)) return null
|
||||
var p = _getPrototypeOf(obj)
|
||||
if (p == _Object.prototype) return null
|
||||
return p
|
||||
}
|
||||
|
||||
globalThis.splat = function(obj) {
|
||||
if (typeof obj != 'object' || obj == null) return null
|
||||
|
||||
var result = {}
|
||||
var current = obj
|
||||
|
||||
// Walk prototype chain and collect text keys
|
||||
while (current != null) {
|
||||
var keys = _keys(current)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i]
|
||||
if (!(k in result)) {
|
||||
var val = current[k]
|
||||
// Only include serializable types
|
||||
if (typeof val == 'object' || typeof val == 'number' ||
|
||||
typeof val == 'string' || typeof val == 'boolean') {
|
||||
result[k] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
current = _getPrototypeOf(current)
|
||||
}
|
||||
|
||||
// Call to_data if present
|
||||
if (typeof obj.to_data == 'function') {
|
||||
var extra = obj.to_data(result)
|
||||
if (typeof extra == 'object' && extra != null) {
|
||||
var extraKeys = _keys(extra)
|
||||
for (var i = 0; i < extraKeys.length; i++) {
|
||||
result[extraKeys[i]] = extra[extraKeys[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
var ENETSERVICE = 0.1
|
||||
var REPLYTIMEOUT = 60 // seconds before replies are ignored
|
||||
|
||||
globalThis.pi = 3.1415926535897932
|
||||
globalThis.pi = 3.14159265358979323846264338327950288419716939937510
|
||||
|
||||
function caller_data(depth = 0)
|
||||
{
|
||||
@@ -275,12 +443,10 @@ function guid(bits = 256)
|
||||
return text(guid,'h')
|
||||
}
|
||||
|
||||
var HEADER = Symbol()
|
||||
var HEADER = key()
|
||||
|
||||
// returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5.
|
||||
$_.random = function() {
|
||||
// os.random() returns a 53-bit integer (uint64 >> 11)
|
||||
// Divide by 2^53 to get a value in [0, 1)
|
||||
return (os.random() / 9007199254740992)
|
||||
}
|
||||
|
||||
@@ -382,13 +548,13 @@ function handle_host(e) {
|
||||
data.replycc[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
function populate_actor_addresses(obj) {
|
||||
if (typeof obj != 'object' || obj == null) return
|
||||
if (!isa(obj, object)) return
|
||||
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
||||
obj[ACTORDATA].address = e.peer.address
|
||||
obj[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
for (var key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (object.has(obj, key)) {
|
||||
populate_actor_addresses(obj[key])
|
||||
}
|
||||
}
|
||||
@@ -695,10 +861,10 @@ function handle_message(msg) {
|
||||
switch (msg.type) {
|
||||
case "user":
|
||||
var letter = msg.data // what the sender really sent
|
||||
Object.defineProperty(letter, HEADER, {
|
||||
_ObjectDefineProperty(letter, HEADER, {
|
||||
value: msg, enumerable: false
|
||||
})
|
||||
Object.defineProperty(letter, ACTORDATA, { // this is so is_actor == true
|
||||
_ObjectDefineProperty(letter, ACTORDATA, { // this is so is_actor == true
|
||||
value: { reply: msg.reply }, enumerable: false
|
||||
})
|
||||
|
||||
@@ -747,6 +913,73 @@ if (!locator) {
|
||||
if (!locator)
|
||||
throw new Error(`Main program ${cell.args.program} could not be found`)
|
||||
|
||||
// Hide JavaScript built-ins - make them inaccessible
|
||||
// Store references we need internally before deleting
|
||||
var _Object = Object
|
||||
var _Array = Array
|
||||
var _String = String
|
||||
var _Number = Number
|
||||
var _Boolean = Boolean
|
||||
var _Math = Math
|
||||
var _Function = Function
|
||||
|
||||
var _Error = Error
|
||||
var _JSON = JSON
|
||||
|
||||
// juicing these before Math is gone
|
||||
|
||||
use('math/radians')
|
||||
use('math/cycles')
|
||||
use('math/degrees')
|
||||
|
||||
// Delete from globalThis
|
||||
delete globalThis.Object
|
||||
delete globalThis.Math
|
||||
delete globalThis.Number
|
||||
delete globalThis.String
|
||||
delete globalThis.Array
|
||||
delete globalThis.Boolean
|
||||
delete globalThis.Date
|
||||
delete globalThis.Function
|
||||
delete globalThis.Reflect
|
||||
delete globalThis.Proxy
|
||||
delete globalThis.WeakMap
|
||||
delete globalThis.WeakSet
|
||||
delete globalThis.WeakRef
|
||||
delete globalThis.BigInt
|
||||
delete globalThis.Symbol
|
||||
delete globalThis.Map
|
||||
delete globalThis.Set
|
||||
delete globalThis.Promise
|
||||
delete globalThis.ArrayBuffer
|
||||
delete globalThis.DataView
|
||||
delete globalThis.Int8Array
|
||||
delete globalThis.Uint8Array
|
||||
delete globalThis.Uint8ClampedArray
|
||||
delete globalThis.Int16Array
|
||||
delete globalThis.Uint16Array
|
||||
delete globalThis.Int32Array
|
||||
delete globalThis.Uint32Array
|
||||
delete globalThis.Float32Array
|
||||
delete globalThis.Float64Array
|
||||
delete globalThis.BigInt64Array
|
||||
delete globalThis.BigUint64Array
|
||||
delete globalThis.eval
|
||||
delete globalThis.parseInt
|
||||
delete globalThis.parseFloat
|
||||
delete globalThis.isNaN
|
||||
delete globalThis.isFinite
|
||||
delete globalThis.decodeURI
|
||||
delete globalThis.decodeURIComponent
|
||||
delete globalThis.encodeURI
|
||||
delete globalThis.encodeURIComponent
|
||||
delete globalThis.escape
|
||||
delete globalThis.unescape
|
||||
delete globalThis.Intl
|
||||
delete globalThis.RegExp
|
||||
|
||||
// TODO: delete globalThis
|
||||
|
||||
$_.clock(_ => {
|
||||
var val = locator.symbol.call(null, $_, cell.args.arg);
|
||||
|
||||
|
||||
22
internal/fn.cm
Normal file
22
internal/fn.cm
Normal file
@@ -0,0 +1,22 @@
|
||||
/* fn.cm - function utilities */
|
||||
|
||||
var _apply = Function.prototype.apply
|
||||
var _isArray = Array.isArray
|
||||
|
||||
var fn = {}
|
||||
|
||||
fn.apply = function(func, args) {
|
||||
if (typeof func != 'function') return func
|
||||
|
||||
if (!_isArray(args)) {
|
||||
args = [args]
|
||||
}
|
||||
|
||||
if (args.length > func.length) {
|
||||
throw new Error("fn.apply: too many arguments")
|
||||
}
|
||||
|
||||
return _apply.call(func, null, args)
|
||||
}
|
||||
|
||||
return fn
|
||||
172
internal/number.cm
Normal file
172
internal/number.cm
Normal file
@@ -0,0 +1,172 @@
|
||||
/* number.cm - number conversion and math utilities */
|
||||
|
||||
var math = use('math/radians')
|
||||
|
||||
var _floor = Math.floor
|
||||
var _ceil = Math.ceil
|
||||
var _round = Math.round
|
||||
var _abs = Math.abs
|
||||
var _trunc = Math.trunc
|
||||
var _min = Math.min
|
||||
var _max = Math.max
|
||||
var _pow = math.power
|
||||
var _parseInt = parseInt
|
||||
var _parseFloat = parseFloat
|
||||
|
||||
function number(val, format) {
|
||||
if (val == true) return 1
|
||||
if (val == false) return 0
|
||||
|
||||
if (typeof val == 'number') return val
|
||||
|
||||
if (typeof val == 'string') {
|
||||
if (typeof format == 'number') {
|
||||
// radix conversion
|
||||
if (format < 2 || format > 36) return null
|
||||
var result = _parseInt(val, format)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof format == 'string') {
|
||||
return parse_formatted(val, format)
|
||||
}
|
||||
|
||||
// default: parse as decimal
|
||||
var result = _parseFloat(val)
|
||||
if (!isa(result, number)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function parse_formatted(str, format) {
|
||||
if (!format || format == "" || format == "n") {
|
||||
var result = _parseFloat(str)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "u": // underbar separator
|
||||
str = str.split('_').join('')
|
||||
break
|
||||
case "d": // comma separator
|
||||
str = str.split(',').join('')
|
||||
break
|
||||
case "s": // space separator
|
||||
str = str.split(' ').join('')
|
||||
break
|
||||
case "v": // European style: period separator, comma decimal
|
||||
str = str.split('.').join('')
|
||||
str = str.replace(',', '.')
|
||||
break
|
||||
case "l": // locale - treat like 'd' for now
|
||||
str = str.split(',').join('')
|
||||
break
|
||||
case "i": // integer with underbar
|
||||
str = str.split('_').join('')
|
||||
break
|
||||
case "b": // binary
|
||||
return _parseInt(str, 2)
|
||||
case "o": // octal
|
||||
return _parseInt(str, 8)
|
||||
case "h": // hex
|
||||
return _parseInt(str, 16)
|
||||
case "t": // base32
|
||||
return _parseInt(str, 32)
|
||||
case "j": // JavaScript style prefix
|
||||
if (str.startsWith('0x') || str.startsWith('0X'))
|
||||
return _parseInt(str.slice(2), 16)
|
||||
if (str.startsWith('0o') || str.startsWith('0O'))
|
||||
return _parseInt(str.slice(2), 8)
|
||||
if (str.startsWith('0b') || str.startsWith('0B'))
|
||||
return _parseInt(str.slice(2), 2)
|
||||
return _parseFloat(str)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
var result = _parseFloat(str)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
number.whole = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return _trunc(n)
|
||||
}
|
||||
|
||||
number.fraction = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return n - _trunc(n)
|
||||
}
|
||||
|
||||
number.floor = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _floor(n)
|
||||
var mult = _pow(10, place)
|
||||
return _floor(n * mult) / mult
|
||||
}
|
||||
|
||||
number.ceiling = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _ceil(n)
|
||||
var mult = _pow(10, place)
|
||||
return _ceil(n * mult) / mult
|
||||
}
|
||||
|
||||
number.abs = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return _abs(n)
|
||||
}
|
||||
|
||||
number.round = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _round(n)
|
||||
var mult = _pow(10, place)
|
||||
return _round(n * mult) / mult
|
||||
}
|
||||
|
||||
number.sign = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
if (n < 0) return -1
|
||||
if (n > 0) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
number.trunc = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _trunc(n)
|
||||
var mult = _pow(10, place)
|
||||
return _trunc(n * mult) / mult
|
||||
}
|
||||
|
||||
number.min = function(...vals) {
|
||||
if (vals.length == 0) return null
|
||||
var result = vals[0]
|
||||
for (var i = 1; i < vals.length; i++) {
|
||||
if (typeof vals[i] != 'number') return null
|
||||
if (vals[i] < result) result = vals[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
number.max = function(...vals) {
|
||||
if (vals.length == 0) return null
|
||||
var result = vals[0]
|
||||
for (var i = 1; i < vals.length; i++) {
|
||||
if (typeof vals[i] != 'number') return null
|
||||
if (vals[i] > result) result = vals[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
number.remainder = function(dividend, divisor) {
|
||||
if (typeof dividend != 'number' || typeof divisor != 'number') return null
|
||||
if (divisor == 0) return null
|
||||
return dividend - (_trunc(dividend / divisor) * divisor)
|
||||
}
|
||||
|
||||
return number
|
||||
89
internal/object.cm
Normal file
89
internal/object.cm
Normal file
@@ -0,0 +1,89 @@
|
||||
/* object.cm - object creation and manipulation utilities */
|
||||
|
||||
var _keys = array
|
||||
var _create = meme
|
||||
var _getPrototypeOf = Object.getPrototypeOf
|
||||
var _assign = Object.assign
|
||||
var _isArray = function(val) { return isa(val, array) }
|
||||
var _values = Object.values
|
||||
|
||||
function object(arg, arg2) {
|
||||
// object(object) - shallow mutable copy
|
||||
if (isa(arg, object) && arg2 == null) {
|
||||
var result = {}
|
||||
var keys = _keys(arg)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg[keys[i]]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(object, another_object) - combine
|
||||
if (isa(arg, object) && isa(arg2, object)) {
|
||||
var result = {}
|
||||
var keys = _keys(arg)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg[keys[i]]
|
||||
}
|
||||
keys = _keys(arg2)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg2[keys[i]]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(object, array_of_keys) - select
|
||||
if (isa(arg, object) && _isArray(arg2)) {
|
||||
var result = {}
|
||||
for (var i = 0; i < arg2.length; i++) {
|
||||
var key = arg2[i]
|
||||
if (typeof key == 'string' && key in arg) {
|
||||
result[key] = arg[key]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(array_of_keys) - set with true values
|
||||
if (_isArray(arg) && arg2 == null) {
|
||||
var result = {}
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(array_of_keys, value) - value set
|
||||
// object(array_of_keys, function) - functional value set
|
||||
if (_isArray(arg) && arg2 != null) {
|
||||
var result = {}
|
||||
if (typeof arg2 == 'function') {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = arg2(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = arg2
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
object.values = function(obj)
|
||||
{
|
||||
return _values(obj)
|
||||
}
|
||||
|
||||
return object
|
||||
217
internal/text.cm
217
internal/text.cm
@@ -3,6 +3,19 @@
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
|
||||
var _toLowerCase = String.prototype.toLowerCase
|
||||
var _toUpperCase = String.prototype.toUpperCase
|
||||
var _trim = String.prototype.trim
|
||||
var _indexOf = String.prototype.indexOf
|
||||
var _lastIndexOf = String.prototype.lastIndexOf
|
||||
var _replace = String.prototype.replace
|
||||
var _normalize = String.prototype.normalize
|
||||
var _substring = String.prototype.substring
|
||||
var _charCodeAt = String.prototype.charCodeAt
|
||||
var _codePointAt = String.prototype.codePointAt
|
||||
|
||||
var _String = String
|
||||
|
||||
var that = this
|
||||
|
||||
// Convert number to string with given radix
|
||||
@@ -11,15 +24,15 @@ function to_radix(num, radix) {
|
||||
|
||||
var digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
var result = "";
|
||||
var n = Math.trunc(num);
|
||||
var n = number.whole(num);
|
||||
var negative = n < 0;
|
||||
n = Math.abs(n);
|
||||
n = number.abs(n);
|
||||
|
||||
if (n == 0) return "0";
|
||||
|
||||
while (n > 0) {
|
||||
result = digits[n % radix] + result;
|
||||
n = Math.floor(n / radix);
|
||||
n = number.floor(n / radix);
|
||||
}
|
||||
|
||||
return negative ? "-" + result : result;
|
||||
@@ -142,14 +155,14 @@ function text(...arguments) {
|
||||
}
|
||||
|
||||
// Handle array conversion
|
||||
if (Array.isArray(arg)) {
|
||||
if (isa(arg, array)) {
|
||||
var separator = arguments[1] || "";
|
||||
|
||||
// Check if all items are valid codepoints
|
||||
var all_codepoints = true;
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var item = arg[i];
|
||||
if (!(typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == Math.floor(item))) {
|
||||
if (!(typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == number.floor(item))) {
|
||||
all_codepoints = false;
|
||||
break;
|
||||
}
|
||||
@@ -165,7 +178,7 @@ function text(...arguments) {
|
||||
if (i > 0) result += separator;
|
||||
|
||||
var item = arg[i];
|
||||
if (typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == Math.floor(item)) {
|
||||
if (typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == number.floor(item)) {
|
||||
// Single codepoint - use utf8 module
|
||||
result += utf8.from_codepoints([item]);
|
||||
} else {
|
||||
@@ -191,7 +204,7 @@ function text(...arguments) {
|
||||
}
|
||||
|
||||
// Default conversion
|
||||
return String(arg);
|
||||
return _String(arg);
|
||||
}
|
||||
|
||||
// Handle text operations
|
||||
@@ -234,7 +247,7 @@ function format_number(num, format) {
|
||||
|
||||
// Parse separation digit
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
separation = parseInt(format[i]);
|
||||
separation = number(format[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
@@ -248,10 +261,10 @@ function format_number(num, format) {
|
||||
|
||||
// Parse places digits
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = parseInt(format[i]);
|
||||
places = number(format[i]);
|
||||
i++;
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = places * 10 + parseInt(format[i]);
|
||||
places = places * 10 + number(format[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
@@ -321,7 +334,7 @@ function format_number(num, format) {
|
||||
// Scientific notation
|
||||
var str = places > 0 ? num.toExponential(places) : num.toExponential();
|
||||
return str;
|
||||
} else if (style == 'n' && (Math.abs(num) >= 1e21 || (Math.abs(num) < 1e-6 && num != 0))) {
|
||||
} else if (style == 'n' && (number.abs(num) >= 1e21 || (number.abs(num) < 1e-6 && num != 0))) {
|
||||
// Use scientific notation for extreme values
|
||||
return num.toExponential();
|
||||
} else {
|
||||
@@ -387,7 +400,7 @@ function format_number(num, format) {
|
||||
if (places == 0) places = default_places;
|
||||
|
||||
// Convert to integer
|
||||
var n = Math.trunc(num);
|
||||
var n = number.whole(num);
|
||||
var str = to_radix(n, radix).toUpperCase();
|
||||
|
||||
// Pad with zeros if needed
|
||||
@@ -409,4 +422,182 @@ function format_number(num, format) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return text;
|
||||
/* -------- text sub-functions --------------------------------------- */
|
||||
|
||||
text.lower = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _toLowerCase.call(str)
|
||||
}
|
||||
|
||||
text.upper = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _toUpperCase.call(str)
|
||||
}
|
||||
|
||||
text.trim = function(str, reject) {
|
||||
if (typeof str != 'string') return null
|
||||
if (reject == null) return _trim.call(str)
|
||||
|
||||
// Custom trim with reject characters
|
||||
var start = 0
|
||||
var end = str.length
|
||||
|
||||
while (start < end && reject.indexOf(str[start]) >= 0) start++
|
||||
while (end > start && reject.indexOf(str[end - 1]) >= 0) end--
|
||||
|
||||
return _substring.call(str, start, end)
|
||||
}
|
||||
|
||||
text.normalize = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _normalize.call(str, 'NFC')
|
||||
}
|
||||
|
||||
text.codepoint = function(str) {
|
||||
if (typeof str != 'string' || str.length == 0) return null
|
||||
return _codePointAt.call(str, 0)
|
||||
}
|
||||
|
||||
text.search = function(str, target, from) {
|
||||
if (typeof str != 'string') return null
|
||||
if (typeof target != 'string') return null
|
||||
|
||||
if (from == null) from = 0
|
||||
if (from < 0) from += str.length
|
||||
if (from < 0) from = 0
|
||||
|
||||
var result = _indexOf.call(str, target, from)
|
||||
if (result == -1) return null
|
||||
return result
|
||||
}
|
||||
|
||||
text.replace = function(str, target, replacement, limit) {
|
||||
if (typeof str != 'string') return null
|
||||
if (typeof target != 'string') return null
|
||||
|
||||
if (limit == null) {
|
||||
// Replace all
|
||||
var result = str
|
||||
var pos = 0
|
||||
while (true) {
|
||||
var idx = _indexOf.call(result, target, pos)
|
||||
if (idx == -1) break
|
||||
|
||||
var rep = replacement
|
||||
if (typeof replacement == 'function') {
|
||||
rep = replacement(target, idx)
|
||||
if (rep == null) {
|
||||
pos = idx + target.length
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = _substring.call(result, 0, idx) + rep + _substring.call(result, idx + target.length)
|
||||
pos = idx + rep.length
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Replace with limit
|
||||
var result = str
|
||||
var pos = 0
|
||||
var count = 0
|
||||
|
||||
while (count < limit) {
|
||||
var idx = _indexOf.call(result, target, pos)
|
||||
if (idx == -1) break
|
||||
|
||||
var rep = replacement
|
||||
if (typeof replacement == 'function') {
|
||||
rep = replacement(target, idx)
|
||||
if (rep == null) {
|
||||
pos = idx + target.length
|
||||
count++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = _substring.call(result, 0, idx) + rep + _substring.call(result, idx + target.length)
|
||||
pos = idx + rep.length
|
||||
count++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
text.format = function(str, collection, transformer) {
|
||||
if (typeof str != 'string') return null
|
||||
|
||||
var result = ""
|
||||
var i = 0
|
||||
|
||||
while (i < str.length) {
|
||||
if (str[i] == '{') {
|
||||
var end = _indexOf.call(str, '}', i)
|
||||
if (end == -1) {
|
||||
result += str[i]
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
var middle = _substring.call(str, i + 1, end)
|
||||
var colonIdx = _indexOf.call(middle, ':')
|
||||
var key = colonIdx >= 0 ? _substring.call(middle, 0, colonIdx) : middle
|
||||
var formatSpec = colonIdx >= 0 ? _substring.call(middle, colonIdx + 1) : ""
|
||||
|
||||
var value = null
|
||||
if (isa(collection, array)) {
|
||||
var idx = number(key)
|
||||
if (!isNaN(idx) && idx >= 0 && idx < collection.length) {
|
||||
value = collection[idx]
|
||||
}
|
||||
} else if (isa(collection, object)) {
|
||||
value = collection[key]
|
||||
}
|
||||
|
||||
var substitution = null
|
||||
|
||||
if (transformer != null) {
|
||||
if (typeof transformer == 'function') {
|
||||
substitution = transformer(value, formatSpec)
|
||||
} else if (typeof transformer == 'object') {
|
||||
var fn = transformer[formatSpec]
|
||||
if (typeof fn == 'function') {
|
||||
substitution = fn(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (substitution == null && typeof value == 'number' && formatSpec) {
|
||||
// Try number formatting
|
||||
substitution = String(value) // simplified
|
||||
}
|
||||
|
||||
if (substitution == null && value != null) {
|
||||
substitution = String(value)
|
||||
}
|
||||
|
||||
if (substitution != null) {
|
||||
result += substitution
|
||||
} else {
|
||||
result += _substring.call(str, i, end + 1)
|
||||
}
|
||||
|
||||
i = end + 1
|
||||
} else {
|
||||
result += str[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
text.extract = function(str, pattern, from, to) {
|
||||
// Simplified pattern matching - returns null for now
|
||||
// Full implementation would require regex or custom pattern language
|
||||
if (typeof str != 'string') return null
|
||||
return null
|
||||
}
|
||||
|
||||
return text
|
||||
18
math/cycles.cm
Normal file
18
math/cycles.cm
Normal file
@@ -0,0 +1,18 @@
|
||||
var cycles = {}
|
||||
var Math_obj = Math
|
||||
|
||||
cycles.arc_cosine = function(x) { return Math_obj.acos(x) / (2 * pi) }
|
||||
cycles.arc_sine = function(x) { return Math_obj.asin(x) / (2 * pi) }
|
||||
cycles.arc_tangent = function(x) { return Math_obj.atan(x) / (2 * pi) }
|
||||
cycles.cosine = function(x) { return Math_obj.cos(x * 2 * pi) }
|
||||
cycles.e = Math_obj.E
|
||||
cycles.ln = function(x) { return Math_obj.log(x) }
|
||||
cycles.log = function(x) { return Math_obj.log10(x) }
|
||||
cycles.log2 = function(x) { return Math_obj.log2(x) }
|
||||
cycles.power = function(x, y) { return Math_obj.pow(x, y) }
|
||||
cycles.root = function(x, y) { return Math_obj.pow(x, 1 / y) }
|
||||
cycles.sine = function(x) { return Math_obj.sin(x * 2 * pi) }
|
||||
cycles.sqrt = function(x) { return Math_obj.sqrt(x) }
|
||||
cycles.tangent = function(x) { return Math_obj.tan(x * 2 * pi) }
|
||||
|
||||
return cycles
|
||||
21
math/degrees.cm
Normal file
21
math/degrees.cm
Normal file
@@ -0,0 +1,21 @@
|
||||
var degrees = {}
|
||||
var Math_obj = Math
|
||||
|
||||
var deg2rad = pi / 180
|
||||
var rad2deg = 180 / pi
|
||||
|
||||
return {
|
||||
arc_cosine: function(x) { return Math_obj.acos(x) * rad2deg },
|
||||
arc_sine: function(x) { return Math_obj.asin(x) * rad2deg },
|
||||
arc_tangent: function(x) { return Math_obj.atan(x) * rad2deg },
|
||||
cosine: function(x) { return Math_obj.cos(x * deg2rad) },
|
||||
e: Math_obj.E,
|
||||
ln: function(x) { return Math_obj.log(x) },
|
||||
log: function(x) { return Math_obj.log10(x) },
|
||||
log2: function(x) { return Math_obj.log2(x) },
|
||||
power: function(x, y) { return Math_obj.pow(x, y) },
|
||||
root: function(x, y) { return Math_obj.pow(x, 1/y) },
|
||||
sine: function(x) { return Math_obj.sin(x * deg2rad) },
|
||||
sqrt: function(x) { return Math_obj.sqrt(x) },
|
||||
tangent: function(x) { return Math_obj.tan(x * deg2rad) }
|
||||
}
|
||||
17
math/radians.cm
Normal file
17
math/radians.cm
Normal file
@@ -0,0 +1,17 @@
|
||||
var Math = globalThis.Math
|
||||
|
||||
return {
|
||||
arc_cosine: Math.acos,
|
||||
arc_sine: Math.asin,
|
||||
arc_tangent: Math.atan,
|
||||
cosine: Math.cos,
|
||||
e: Math.E,
|
||||
ln: Math.log,
|
||||
log: Math.log10,
|
||||
log2: Math.log2,
|
||||
power: Math.pow,
|
||||
root: function(x, n) { return Math.pow(x, 1/n) },
|
||||
sine: Math.sin,
|
||||
sqrt: Math.sqrt,
|
||||
tangent: Math.tan
|
||||
}
|
||||
@@ -70,7 +70,7 @@ scripts = [
|
||||
'net/enet.c',
|
||||
'wildstar.c',
|
||||
'archive/miniz.c',
|
||||
'internal/json.c'
|
||||
'internal/json.c',
|
||||
]
|
||||
|
||||
foreach file: scripts
|
||||
@@ -78,7 +78,7 @@ foreach file: scripts
|
||||
endforeach
|
||||
|
||||
srceng = 'source'
|
||||
includes = [srceng, 'internal', 'debug', 'net', 'archive']
|
||||
includes = [srceng, 'internal', 'debug', 'net', 'archive', 'math']
|
||||
|
||||
foreach file : src
|
||||
full_path = join_paths(srceng, file)
|
||||
|
||||
@@ -142,7 +142,7 @@ package.gather_dependencies = function(name)
|
||||
}
|
||||
|
||||
gather_recursive(name)
|
||||
return Object.keys(all_deps)
|
||||
return array(all_deps)
|
||||
}
|
||||
|
||||
package.list_files = function(pkg) {
|
||||
@@ -229,7 +229,7 @@ package.get_flags = function(name, flag_type, target) {
|
||||
// Handles patterns like fd.c vs fd_playdate.c
|
||||
package.get_c_files = function(name, target, exclude_main) {
|
||||
var toolchains = use('toolchains')
|
||||
var known_targets = Object.keys(toolchains)
|
||||
var known_targets = array(toolchains)
|
||||
var files = package.list_files(name)
|
||||
|
||||
// Group files by their base name (without target suffix)
|
||||
|
||||
14
parseq.cm
14
parseq.cm
@@ -29,7 +29,7 @@ function is_requestor (fn) {
|
||||
}
|
||||
|
||||
function check_requestors (list, factory) {
|
||||
if (!Array.isArray(list) || list.some(r => !is_requestor(r)))
|
||||
if (!isa(list, array) || list.some(r => !is_requestor(r)))
|
||||
throw make_reason(factory, 'Bad requestor list.', list)
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ function run (factory, requestors, initial, action, time_limit, throttle = 0) {
|
||||
if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
|
||||
}
|
||||
|
||||
def concurrent = throttle ? Math.min(throttle, requestors.length) : requestors.length
|
||||
def concurrent = throttle ? number.min(throttle, requestors.length) : requestors.length
|
||||
for (let i = 0; i < concurrent; i++) start_requestor(initial)
|
||||
|
||||
return cancel
|
||||
@@ -95,9 +95,9 @@ function run (factory, requestors, initial, action, time_limit, throttle = 0) {
|
||||
// ———————————————————————————————————————— factories
|
||||
|
||||
function _normalize (collection, factory) {
|
||||
if (Array.isArray(collection)) return { names: null, list: collection }
|
||||
if (isa(collection)) return { names: null, list: collection }
|
||||
if (collection && typeof collection == 'object') {
|
||||
def names = Object.keys(collection)
|
||||
def names = array(collection)
|
||||
def list = names.map(k => collection[k]).filter(is_requestor)
|
||||
return { names, list }
|
||||
}
|
||||
@@ -106,7 +106,7 @@ function _normalize (collection, factory) {
|
||||
|
||||
function _denormalize (names, list) {
|
||||
if (!names) return list
|
||||
def obj = Object.create(null)
|
||||
def obj = meme(null)
|
||||
names.forEach((k, i) => { obj[k] = list[i] })
|
||||
return obj
|
||||
}
|
||||
@@ -162,7 +162,7 @@ function par_any (collection, time_limit, throttle) {
|
||||
|
||||
function race (list, time_limit, throttle) {
|
||||
def factory = throttle == 1 ? 'fallback' : 'race'
|
||||
if (!Array.isArray(list) || list.length == 0)
|
||||
if (!isa(list, array) || list.length == 0)
|
||||
throw make_reason(factory, 'No requestors.')
|
||||
check_requestors(list, factory)
|
||||
|
||||
@@ -191,7 +191,7 @@ function fallback (list, time_limit) {
|
||||
|
||||
function sequence (list, time_limit) {
|
||||
def factory = 'sequence'
|
||||
if (!Array.isArray(list)) throw make_reason(factory, 'Not an array.', list)
|
||||
if (!isa(list, array)) throw make_reason(factory, 'Not an array.', list)
|
||||
check_requestors(list, factory)
|
||||
if (list.length == 0) return (cb, v) => cb(v)
|
||||
|
||||
|
||||
20
pronto.cm
20
pronto.cm
@@ -17,7 +17,7 @@ function is_requestor(fn) {
|
||||
}
|
||||
|
||||
function check_requestors(list, factory) {
|
||||
if (!Array.isArray(list) || list.some(r => !is_requestor(r)))
|
||||
if (!isa(list, array) || list.some(r => !is_requestor(r)))
|
||||
throw make_reason(factory, 'Bad requestor array.', list)
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ function check_callback(cb, factory) {
|
||||
// Tries each requestor in order until one succeeds.
|
||||
function fallback(requestor_array) {
|
||||
def factory = 'fallback'
|
||||
if (!Array.isArray(requestor_array) || requestor_array.length == 0)
|
||||
if (!isa(requestor_array, array) || requestor_array.length == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
@@ -82,7 +82,7 @@ function fallback(requestor_array) {
|
||||
// Runs requestors in parallel, collecting all results.
|
||||
function parallel(requestor_array, throttle, need) {
|
||||
def factory = 'parallel'
|
||||
if (!Array.isArray(requestor_array))
|
||||
if (!isa(requestor_array, array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
@@ -156,7 +156,7 @@ function parallel(requestor_array, throttle, need) {
|
||||
}
|
||||
}
|
||||
|
||||
def concurrent = throttle ? Math.min(throttle, length) : length
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
@@ -167,7 +167,7 @@ function parallel(requestor_array, throttle, need) {
|
||||
// Runs requestors in parallel, returns first success(es).
|
||||
function race(requestor_array, throttle, need) {
|
||||
def factory = 'race'
|
||||
if (!Array.isArray(requestor_array) || requestor_array.length == 0)
|
||||
if (!isa(requestor_array, array) || requestor_array.length == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
@@ -241,7 +241,7 @@ function race(requestor_array, throttle, need) {
|
||||
}
|
||||
}
|
||||
|
||||
def concurrent = throttle ? Math.min(throttle, length) : length
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
@@ -252,7 +252,7 @@ function race(requestor_array, throttle, need) {
|
||||
// Runs requestors one at a time, passing result to next.
|
||||
function sequence(requestor_array) {
|
||||
def factory = 'sequence'
|
||||
if (!Array.isArray(requestor_array))
|
||||
if (!isa(requestor_array, array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
@@ -392,10 +392,10 @@ function objectify(factory_fn) {
|
||||
throw make_reason(factory, 'Not a factory.', factory_fn)
|
||||
|
||||
return function objectified_factory(object_of_requestors, ...rest) {
|
||||
if (typeof object_of_requestors != 'object' || Array.isArray(object_of_requestors))
|
||||
if (!isa(object_of_requestors, object))
|
||||
throw make_reason(factory, 'Expected an object.', object_of_requestors)
|
||||
|
||||
def keys = Object.keys(object_of_requestors)
|
||||
def keys = array(object_of_requestors)
|
||||
def requestor_array = keys.map(k => object_of_requestors[k])
|
||||
|
||||
def inner_requestor = factory_fn(requestor_array, ...rest)
|
||||
@@ -404,7 +404,7 @@ function objectify(factory_fn) {
|
||||
return inner_requestor(function(results, reason) {
|
||||
if (results == null) {
|
||||
callback(null, reason)
|
||||
} else if (Array.isArray(results)) {
|
||||
} else if (isa(results, array)) {
|
||||
def obj = {}
|
||||
keys.forEach((k, i) => { obj[k] = results[i] })
|
||||
callback(obj, reason)
|
||||
|
||||
25
random.cm
Normal file
25
random.cm
Normal file
@@ -0,0 +1,25 @@
|
||||
var rnd = {}
|
||||
|
||||
var os = use('os')
|
||||
|
||||
rnd.random = function()
|
||||
{
|
||||
return (os.random() / 9007199254740992)
|
||||
}
|
||||
|
||||
rnd.random_fit = function()
|
||||
{
|
||||
return os.random();
|
||||
}
|
||||
|
||||
rnd.random_whole = function(num)
|
||||
{
|
||||
return number.floor(rnd.random() * num)
|
||||
}
|
||||
|
||||
rnd.random_range = function(min,max)
|
||||
{
|
||||
return rnd.random() * (max - min) + min;
|
||||
}
|
||||
|
||||
return rnd
|
||||
6
shop.cm
6
shop.cm
@@ -315,7 +315,7 @@ Shop.extract_commit_hash = function(pkg, response) {
|
||||
var data = json.decode(response)
|
||||
|
||||
if (info == 'gitea') {
|
||||
if (Array.isArray(data))
|
||||
if (isa(data, array))
|
||||
data = data[0]
|
||||
return data.commit && data.commit.id
|
||||
}
|
||||
@@ -561,7 +561,7 @@ function resolve_c_symbol(path, package_context)
|
||||
function resolve_module_info(path, package_context) {
|
||||
var c_resolve = resolve_c_symbol(path, package_context) || {scope:999}
|
||||
var mod_resolve = resolve_locator(path + '.cm', package_context) || {scope:999}
|
||||
var min_scope = Math.min(c_resolve.scope, mod_resolve.scope)
|
||||
var min_scope = number.min(c_resolve.scope, mod_resolve.scope)
|
||||
|
||||
if (min_scope == 999)
|
||||
return null
|
||||
@@ -994,7 +994,7 @@ Shop.build_package_scripts = function(package)
|
||||
Shop.list_packages = function()
|
||||
{
|
||||
var lock = Shop.load_lock()
|
||||
return Object.keys(lock)
|
||||
return array(lock)
|
||||
}
|
||||
|
||||
// Get the lib directory for dynamic libraries
|
||||
|
||||
@@ -113,6 +113,7 @@ void actor_disrupt(cell_rt *crt)
|
||||
}
|
||||
|
||||
JSValue js_os_use(JSContext *js);
|
||||
JSValue js_math_use(JSContext *js);
|
||||
|
||||
void script_startup(cell_rt *prt)
|
||||
{
|
||||
@@ -148,7 +149,6 @@ void script_startup(cell_rt *prt)
|
||||
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";
|
||||
|
||||
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0);
|
||||
|
||||
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
|
||||
|
||||
crt->actor_sym = JS_ValueToAtom(js, actorsym);
|
||||
@@ -285,9 +285,29 @@ int uncaught_exception(JSContext *js, JSValue v)
|
||||
}
|
||||
|
||||
JSValue exp = JS_GetException(js);
|
||||
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
|
||||
JS_FreeValue(js,ret);
|
||||
|
||||
if (JS_IsNull(rt->on_exception)) {
|
||||
const char *str = JS_ToCString(js, exp);
|
||||
if (str) {
|
||||
printf("Uncaught exception: %s\n", str);
|
||||
JS_FreeCString(js, str);
|
||||
}
|
||||
|
||||
JSValue stack = JS_GetPropertyStr(js, exp, "stack");
|
||||
if (!JS_IsNull(stack)) {
|
||||
const char *stack_str = JS_ToCString(js, stack);
|
||||
if (stack_str) {
|
||||
printf("Stack trace:\n%s\n", stack_str);
|
||||
JS_FreeCString(js, stack_str);
|
||||
}
|
||||
}
|
||||
JS_FreeValue(js, stack);
|
||||
} else {
|
||||
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
|
||||
JS_FreeValue(js, ret);
|
||||
}
|
||||
|
||||
JS_FreeValue(js, exp);
|
||||
JS_FreeValue(js,v);
|
||||
JS_FreeValue(js, v);
|
||||
return 0;
|
||||
}
|
||||
10
test.ce
10
test.ce
@@ -215,7 +215,7 @@ function run_tests(pkg) {
|
||||
file_result.failed++
|
||||
}
|
||||
var end_time = time.number()
|
||||
test_entry.duration_ns = Math.round((end_time - start_time) * 1000000000)
|
||||
test_entry.duration_ns = number.round((end_time - start_time) * 1000000000)
|
||||
|
||||
file_result.tests.push(test_entry)
|
||||
pkg_result.total++
|
||||
@@ -290,13 +290,13 @@ function handle_actor_message(msg) {
|
||||
pending_actor_tests.splice(found_idx, 1)
|
||||
|
||||
var end_time = time.number()
|
||||
var duration_ns = Math.round((end_time - base_entry.start_time) * 1000000000)
|
||||
var duration_ns = number.round((end_time - base_entry.start_time) * 1000000000)
|
||||
|
||||
// Normalize to an array of result objects
|
||||
var results = []
|
||||
if (Array.isArray(msg)) {
|
||||
if (isa(msg, array)) {
|
||||
results = msg
|
||||
} else if (msg && Array.isArray(msg.results)) {
|
||||
} else if (msg && isa(msg.results, array)) {
|
||||
results = msg.results
|
||||
} else {
|
||||
results = [msg]
|
||||
@@ -447,7 +447,7 @@ if (all_actor_tests.length == 0) {
|
||||
|
||||
// Generate Reports function
|
||||
function generate_reports(totals) {
|
||||
var timestamp = Math.floor(time.number()).toString()
|
||||
var timestamp = number.floor(time.number()).toString()
|
||||
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
|
||||
ensure_dir(report_dir)
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ function deepCompare(expected, actual, path) {
|
||||
if (isNaN(expected) && isNaN(actual))
|
||||
return { passed: true, messages: [] };
|
||||
|
||||
var diff = Math.abs(expected - actual);
|
||||
var diff = number.abs(expected - actual);
|
||||
if (diff <= EPSILON)
|
||||
return { passed: true, messages: [] };
|
||||
|
||||
@@ -48,7 +48,7 @@ function deepCompare(expected, actual, path) {
|
||||
return { passed: true, messages: [] }
|
||||
}
|
||||
|
||||
if (Array.isArray(expected) && Array.isArray(actual)) {
|
||||
if (isa(expected, array) && isa(actual, array)) {
|
||||
if (expected.length != actual.length)
|
||||
return {
|
||||
passed: false,
|
||||
@@ -64,10 +64,9 @@ function deepCompare(expected, actual, path) {
|
||||
return { passed: messages.length == 0, messages: messages };
|
||||
}
|
||||
|
||||
if (typeof expected == 'object' && expected != null &&
|
||||
typeof actual == 'object' && actual != null) {
|
||||
var expKeys = Object.keys(expected).sort();
|
||||
var actKeys = Object.keys(actual).sort();
|
||||
if (isa(expected, object) && isa(actual, object)) {
|
||||
var expKeys = array(expected).sort();
|
||||
var actKeys = array(actual).sort();
|
||||
if (JSON.stringify(expKeys) != JSON.stringify(actKeys))
|
||||
return {
|
||||
passed: false,
|
||||
|
||||
@@ -6,8 +6,8 @@ function deep_equal(a, b) {
|
||||
if (typeof a != typeof b) return false
|
||||
|
||||
if (typeof a == 'object') {
|
||||
var keys_a = Object.keys(a)
|
||||
var keys_b = Object.keys(b)
|
||||
var keys_a = array(a)
|
||||
var keys_b = array(b)
|
||||
if (keys_a.length != keys_b.length) return false
|
||||
|
||||
for (var i = 0; i < keys_a.length; i++) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
var wota = use('wota')
|
||||
var os = use('os')
|
||||
var blob = use('blob')
|
||||
var math = use('math/radians')
|
||||
|
||||
var EPSILON = 1e-12
|
||||
|
||||
@@ -13,7 +14,7 @@ function deep_compare(expected, actual, path) {
|
||||
|
||||
if (typeof expected == 'number' && typeof actual == 'number') {
|
||||
if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }
|
||||
var diff = Math.abs(expected - actual)
|
||||
var diff = number.abs(expected - actual)
|
||||
if (diff <= EPSILON) return { passed: true, messages: [] }
|
||||
return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] }
|
||||
}
|
||||
@@ -29,7 +30,7 @@ function deep_compare(expected, actual, path) {
|
||||
return { passed: true, messages: [] }
|
||||
}
|
||||
|
||||
if (Array.isArray(expected) && Array.isArray(actual)) {
|
||||
if (isa(expected, array) && isa(actual, array)) {
|
||||
if (expected.length != actual.length)
|
||||
return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
|
||||
var msgs = []
|
||||
@@ -42,9 +43,9 @@ function deep_compare(expected, actual, path) {
|
||||
return { passed: msgs.length == 0, messages: msgs }
|
||||
}
|
||||
|
||||
if (typeof expected == 'object' && expected && typeof actual == 'object' && actual) {
|
||||
var expKeys = Object.keys(expected).sort()
|
||||
var actKeys = Object.keys(actual).sort()
|
||||
if (isa(expected, object) && isa(actual, object)) {
|
||||
var expKeys = array(expected).sort()
|
||||
var actKeys = array(actual).sort()
|
||||
if (JSON.stringify(expKeys) != JSON.stringify(actKeys))
|
||||
return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] }
|
||||
var msgs = []
|
||||
@@ -71,8 +72,8 @@ var testCases = [
|
||||
{ name: 'neg7', input: -7 },
|
||||
{ name: '1023', input: 1023 },
|
||||
{ name: 'neg1023', input: -1023 },
|
||||
{ name: 'large_pos', input: Math.pow(2, 55) - 1 },
|
||||
{ name: 'large_neg', input: -Math.pow(2, 55) },
|
||||
{ name: 'large_pos', input: math.power(2, 55) - 1 },
|
||||
{ name: 'large_neg', input: -math.power(2, 55) },
|
||||
|
||||
{ name: 'null', input: null },
|
||||
{ name: 'false', input: false },
|
||||
@@ -83,7 +84,7 @@ var testCases = [
|
||||
{ name: 'float_euler', input: -0.5772156649 },
|
||||
{ name: 'float_precision', input: -1.00000000000001 },
|
||||
{ name: 'float_large_neg', input: -10000000000000 },
|
||||
{ name: 'float_pow2_55', input: Math.pow(2, 55) },
|
||||
{ name: 'float_pow2_55', input: math.power(2, 55) },
|
||||
|
||||
{ name: 'empty_string', input: '' },
|
||||
{ name: 'string_cat', input: 'cat' },
|
||||
|
||||
12
time.cm
12
time.cm
@@ -47,9 +47,9 @@ time.isleap = function(y) { return time.yearsize(y) == 366; };
|
||||
/* timecode utility */
|
||||
time.timecode = function(t, fps = 24)
|
||||
{
|
||||
var s = Math.trunc(t);
|
||||
var s = number.whole(t);
|
||||
var frac = t - s;
|
||||
return `${s}:${Math.trunc(frac * fps)}`;
|
||||
return `${s}:${number.whole(frac * fps)}`;
|
||||
};
|
||||
|
||||
/* per-month day counts (non-leap) */
|
||||
@@ -77,13 +77,13 @@ function record(num = now(),
|
||||
|
||||
/* split into day + seconds-of-day */
|
||||
var hms = num % time.day;
|
||||
var day = Math.floor(num / time.day);
|
||||
var day = number.floor(num / time.day);
|
||||
if (hms < 0) { hms += time.day; day--; }
|
||||
|
||||
rec.second = hms % time.minute;
|
||||
var tmp = Math.floor(hms / time.minute);
|
||||
var tmp = number.floor(hms / time.minute);
|
||||
rec.minute = tmp % time.minute;
|
||||
rec.hour = Math.floor(tmp / time.minute);
|
||||
rec.hour = number.floor(tmp / time.minute);
|
||||
rec.weekday = (day + 4_503_599_627_370_496 + 2) % 7; /* 2 → 1970-01-01 was Thursday */
|
||||
|
||||
/* year & day-of-year */
|
||||
@@ -172,7 +172,7 @@ function text(num = now(),
|
||||
/* BCE/CE */
|
||||
var year = rec.year > 0 ? rec.year : rec.year - 1;
|
||||
if (fmt.includes("c")) {
|
||||
if (year < 0) { year = Math.abs(year); fmt = fmt.replaceAll("c", "BC"); }
|
||||
if (year < 0) { year = number.abs(year); fmt = fmt.replaceAll("c", "BC"); }
|
||||
else fmt = fmt.replaceAll("c", "AD");
|
||||
}
|
||||
|
||||
|
||||
16
toml.cm
16
toml.cm
@@ -51,7 +51,7 @@ function parse_toml(text) {
|
||||
} else if (value == 'true' || value == 'false') {
|
||||
// Boolean
|
||||
current_section[key] = value == 'true'
|
||||
} else if (!isNaN(Number(value))) {
|
||||
} else if (isa(value, number)) {
|
||||
// Number
|
||||
current_section[key] = Number(value)
|
||||
} else {
|
||||
@@ -147,7 +147,7 @@ function encode_toml(obj) {
|
||||
return value ? 'true' : 'false'
|
||||
} else if (typeof value == 'number') {
|
||||
return String(value)
|
||||
} else if (Array.isArray(value)) {
|
||||
} else if (isa(value, array)) {
|
||||
var items = []
|
||||
for (var i = 0; i < value.length; i++) {
|
||||
items.push(encode_value(value[i]))
|
||||
@@ -165,24 +165,24 @@ function encode_toml(obj) {
|
||||
}
|
||||
|
||||
// First pass: encode top-level simple values
|
||||
var keys = Object.keys(obj)
|
||||
var keys = array(obj)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
if (value == null || typeof value != 'object' || Array.isArray(value)) {
|
||||
if (!isa(value, object)) {
|
||||
result.push(quote_key(key) + ' = ' + encode_value(value))
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: encode nested objects
|
||||
function encode_section(obj, path) {
|
||||
var keys = Object.keys(obj)
|
||||
var keys = array(obj)
|
||||
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i]
|
||||
var value = obj[key]
|
||||
|
||||
if (value != null && typeof value == 'object' && !Array.isArray(value)) {
|
||||
if (isa(value, object)) {
|
||||
// Nested object - create section
|
||||
// We MUST quote the key segment if it has dots, otherwise it becomes a nested table path
|
||||
var quoted = quote_key(key)
|
||||
@@ -191,11 +191,11 @@ function encode_toml(obj) {
|
||||
result.push('[' + section_path + ']')
|
||||
|
||||
// First encode direct properties of this section
|
||||
var section_keys = Object.keys(value)
|
||||
var section_keys = array(value)
|
||||
for (var j = 0; j < section_keys.length; j++) {
|
||||
var sk = section_keys[j]
|
||||
var sv = value[sk]
|
||||
if (sv == null || typeof sv != 'object' || Array.isArray(sv)) {
|
||||
if (!isa(sv, object)) {
|
||||
result.push(quote_key(sk) + ' = ' + encode_value(sv))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user