Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0bd752fc8c | ||
|
|
b64e35604a |
8
bench.ce
8
bench.ce
@@ -3,7 +3,7 @@ var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
var time = use('time')
|
||||
var json = use('json')
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
var os = use('os')
|
||||
var testlib = use('internal/testlib')
|
||||
var math = use('math/radians')
|
||||
@@ -524,7 +524,7 @@ log.console(`Benchmarks: ${total_benches} total`)
|
||||
|
||||
// Generate reports
|
||||
function generate_reports() {
|
||||
var timestamp = text(number.floor(time.number()))
|
||||
var timestamp = number.floor(time.number()).toString()
|
||||
var report_dir = shop.get_reports_dir() + '/bench_' + timestamp
|
||||
testlib.ensure_dir(report_dir)
|
||||
|
||||
@@ -578,7 +578,7 @@ Total benchmarks: ${total_benches}
|
||||
}
|
||||
|
||||
testlib.ensure_dir(report_dir)
|
||||
fd.slurpwrite(`${report_dir}/bench.txt`, stone(new blob(txt_report)))
|
||||
fd.slurpwrite(`${report_dir}/bench.txt`, utf8.encode(txt_report))
|
||||
log.console(`Report written to ${report_dir}/bench.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
@@ -595,7 +595,7 @@ Total benchmarks: ${total_benches}
|
||||
}
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, stone(new blob(json.encode(pkg_benches))))
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_benches)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -257,6 +257,5 @@ return {
|
||||
x = (x + o.x) | 0
|
||||
}
|
||||
return blackhole(sink, x)
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
7
build.cm
7
build.cm
@@ -8,7 +8,7 @@
|
||||
|
||||
var fd = use('fd')
|
||||
var crypto = use('crypto')
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
var os = use('os')
|
||||
var toolchains = use('toolchains')
|
||||
var shop = use('internal/shop')
|
||||
@@ -73,8 +73,7 @@ Build.detect_host_target = function() {
|
||||
// ============================================================================
|
||||
|
||||
function content_hash(str) {
|
||||
var bb = stone(new blob(str))
|
||||
return text(crypto.blake2(bb, 32), 'h')
|
||||
return text(crypto.blake2(utf8.encode(str)), 'h')
|
||||
}
|
||||
|
||||
function get_build_dir() {
|
||||
@@ -380,7 +379,7 @@ Build.build_all_dynamic = function(target, buildtype = 'release') {
|
||||
var lib = Build.build_dynamic('core', target, buildtype)
|
||||
results.push({ package: 'core', library: lib })
|
||||
} catch (e) {
|
||||
log.error('Failed to build core: ' + text(e))
|
||||
log.error('Failed to build core: ' + e)
|
||||
results.push({ package: 'core', error: e })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
# Managed Stack Frames Implementation Plan
|
||||
|
||||
This document outlines the requirements and invariants for implementing fully managed stack frames in QuickJS, eliminating recursion through the C stack for JS->JS calls.
|
||||
|
||||
## Overview
|
||||
|
||||
The goal is to maintain interpreter state entirely on managed stacks (value stack + frame stack) rather than relying on C stack frames. This enables:
|
||||
- **Call IC fast path**: Direct dispatch to C functions without js_call_c_function overhead
|
||||
- **Proper stack traces**: Error().stack works correctly even through optimized paths
|
||||
- **Tail call optimization**: Possible without C stack growth
|
||||
- **Debugging/profiling**: Full interpreter state always inspectable
|
||||
|
||||
## Current State
|
||||
|
||||
- Property IC: Implemented with per-function polymorphic IC (up to 4 shapes per site)
|
||||
- Call IC: Infrastructure exists but disabled (`CALL_IC_ENABLED 0`) because it bypasses stack frame setup required for Error().stack
|
||||
|
||||
## Golden Invariant
|
||||
|
||||
**At any time, the entire live interpreter state must be reconstructible from:**
|
||||
```
|
||||
(ctx->value_stack, value_top) + (ctx->frame_stack, frame_top)
|
||||
```
|
||||
|
||||
No critical state may live only in C locals.
|
||||
|
||||
## Implementation Requirements
|
||||
|
||||
### 1. Offset Semantics (use `size_t` / `uint32_t`)
|
||||
|
||||
Replace pointer-based addressing with offset-based addressing:
|
||||
|
||||
```c
|
||||
typedef struct JSStackFrame {
|
||||
uint32_t sp_offset; // Offset into ctx->value_stack
|
||||
uint32_t var_offset; // Start of local variables
|
||||
uint32_t arg_offset; // Start of arguments
|
||||
// ... continuation info below
|
||||
} JSStackFrame;
|
||||
```
|
||||
|
||||
**Rationale**: Offsets survive stack reallocation, pointers don't.
|
||||
|
||||
### 2. Consistent `sp_offset` Semantics
|
||||
|
||||
Define clearly and consistently:
|
||||
- `sp_offset` = current stack pointer offset from `ctx->value_stack`
|
||||
- On function entry: `sp_offset` points to first free slot after arguments
|
||||
- On function exit: `sp_offset` restored to caller's expected position
|
||||
|
||||
### 3. Continuation Info (Caller State Restoration)
|
||||
|
||||
Each frame must store enough to restore caller state on return:
|
||||
|
||||
```c
|
||||
typedef struct JSStackFrame {
|
||||
// ... other fields
|
||||
|
||||
// Continuation info
|
||||
const uint8_t *caller_pc; // Return address in caller's bytecode
|
||||
uint32_t caller_sp_offset; // Caller's stack pointer
|
||||
JSFunctionBytecode *caller_b; // Caller's bytecode (for IC cache)
|
||||
|
||||
// Current function info
|
||||
JSFunctionBytecode *b; // Current function's bytecode
|
||||
JSValue *var_buf; // Can be offset-based
|
||||
JSValue *arg_buf; // Can be offset-based
|
||||
JSValue this_val;
|
||||
} JSStackFrame;
|
||||
```
|
||||
|
||||
### 4. Exception Handler Stack Depth Restoration
|
||||
|
||||
Exception handlers must record the `sp_offset` at handler entry so `throw` can restore the correct stack depth:
|
||||
|
||||
```c
|
||||
typedef struct JSExceptionHandler {
|
||||
uint32_t sp_offset; // Stack depth to restore on throw
|
||||
const uint8_t *catch_pc; // Where to jump on exception
|
||||
// ...
|
||||
} JSExceptionHandler;
|
||||
```
|
||||
|
||||
On `throw`:
|
||||
1. Unwind frame stack to find appropriate handler
|
||||
2. Restore `sp_offset` to handler's recorded value
|
||||
3. Push exception value
|
||||
4. Jump to `catch_pc`
|
||||
|
||||
### 5. Aliased `argv` Handling
|
||||
|
||||
When `arguments` object exists, `argv` may be aliased. The frame must track this:
|
||||
|
||||
```c
|
||||
typedef struct JSStackFrame {
|
||||
// ...
|
||||
uint16_t flags;
|
||||
#define JS_FRAME_ALIASED_ARGV (1 << 0)
|
||||
#define JS_FRAME_STRICT (1 << 1)
|
||||
// ...
|
||||
JSObject *arguments_obj; // Non-NULL if arguments object created
|
||||
} JSStackFrame;
|
||||
```
|
||||
|
||||
When `JS_FRAME_ALIASED_ARGV` is set, writes to `arguments[i]` must update the corresponding local variable.
|
||||
|
||||
### 6. Stack Trace Accuracy (`sf->cur_pc`)
|
||||
|
||||
**Critical**: `sf->cur_pc` must be updated before any operation that could:
|
||||
- Throw an exception
|
||||
- Call into another function
|
||||
- Trigger GC
|
||||
|
||||
Currently the interpreter does:
|
||||
```c
|
||||
sf->cur_pc = pc; // Before potentially-throwing ops
|
||||
```
|
||||
|
||||
With managed frames, ensure this is consistently done or use a different mechanism (e.g., store pc in frame on every call).
|
||||
|
||||
### 7. GC Integration
|
||||
|
||||
The GC must be able to mark all live values on the managed stacks:
|
||||
|
||||
```c
|
||||
void js_gc_mark_value_stack(JSRuntime *rt) {
|
||||
for (JSContext *ctx = rt->context_list; ctx; ctx = ctx->link) {
|
||||
JSValue *p = ctx->value_stack;
|
||||
JSValue *end = ctx->value_stack + ctx->value_top;
|
||||
while (p < end) {
|
||||
JS_MarkValue(rt, *p);
|
||||
p++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void js_gc_mark_frame_stack(JSRuntime *rt) {
|
||||
for (JSContext *ctx = rt->context_list; ctx; ctx = ctx->link) {
|
||||
JSStackFrame *sf = ctx->frame_stack;
|
||||
JSStackFrame *end = ctx->frame_stack + ctx->frame_top;
|
||||
while (sf < end) {
|
||||
JS_MarkValue(rt, sf->this_val);
|
||||
// Mark any other JSValue fields in frame
|
||||
sf++;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 8. Main Interpreter Loop Changes
|
||||
|
||||
Transform from recursive to iterative:
|
||||
|
||||
```c
|
||||
// Current (recursive):
|
||||
JSValue JS_CallInternal(...) {
|
||||
// ...
|
||||
CASE(OP_call):
|
||||
// Recursive call to JS_CallInternal
|
||||
ret = JS_CallInternal(ctx, func, ...);
|
||||
// ...
|
||||
}
|
||||
|
||||
// Target (iterative):
|
||||
JSValue JS_CallInternal(...) {
|
||||
// ...
|
||||
CASE(OP_call):
|
||||
// Push new frame, update pc to callee entry
|
||||
push_frame(ctx, ...);
|
||||
pc = new_func->byte_code_buf;
|
||||
BREAK; // Continue in same loop iteration
|
||||
|
||||
CASE(OP_return):
|
||||
// Pop frame, restore caller state
|
||||
ret_val = sp[-1];
|
||||
pop_frame(ctx, &pc, &sp, &b);
|
||||
sp[0] = ret_val;
|
||||
BREAK; // Continue executing caller
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## Call IC Integration (After Managed Frames)
|
||||
|
||||
Once managed frames are complete, Call IC becomes safe:
|
||||
|
||||
```c
|
||||
CASE(OP_call_method):
|
||||
// ... resolve method ...
|
||||
|
||||
if (JS_VALUE_GET_TAG(method) == JS_TAG_OBJECT) {
|
||||
JSObject *p = JS_VALUE_GET_OBJ(method);
|
||||
|
||||
// Check Call IC
|
||||
CallICEntry *entry = call_ic_lookup(cache, pc_offset, p->shape);
|
||||
if (entry && entry->cfunc) {
|
||||
// Direct C call - safe because frame is on managed stack
|
||||
push_minimal_frame(ctx, pc, sp_offset);
|
||||
ret = entry->cfunc(ctx, this_val, argc, argv);
|
||||
pop_minimal_frame(ctx);
|
||||
// Handle return...
|
||||
}
|
||||
}
|
||||
|
||||
// Slow path: full call
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
1. **Stack trace tests**: Verify Error().stack works through all call patterns
|
||||
2. **Exception tests**: Verify throw/catch restores correct stack depth
|
||||
3. **GC stress tests**: Verify all values are properly marked during GC
|
||||
4. **Benchmark**: Compare performance before/after
|
||||
|
||||
## Migration Steps
|
||||
|
||||
1. [ ] Add offset fields to JSStackFrame alongside existing pointers
|
||||
2. [ ] Create push_frame/pop_frame helper functions
|
||||
3. [ ] Convert OP_call to use push_frame instead of recursion (JS->JS calls)
|
||||
4. [ ] Convert OP_return to use pop_frame
|
||||
5. [ ] Update exception handling to use offset-based stack restoration
|
||||
6. [ ] Update GC to walk managed stacks
|
||||
7. [ ] Remove/deprecate recursive JS_CallInternal calls for JS functions
|
||||
8. [ ] Enable Call IC for C functions
|
||||
9. [ ] Benchmark and optimize
|
||||
|
||||
## References
|
||||
|
||||
- Current IC implementation: `source/quickjs.c` lines 12567-12722 (ICCache, prop_ic_*)
|
||||
- Current stack frame: `source/quickjs.c` JSStackFrame definition
|
||||
- OP_call_method: `source/quickjs.c` lines 13654-13718
|
||||
303
internal/array.cm
Normal file
303
internal/array.cm
Normal file
@@ -0,0 +1,303 @@
|
||||
/* 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
|
||||
var _from = Array.from
|
||||
|
||||
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) {
|
||||
result.length = 100
|
||||
} 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)) {
|
||||
if (arg instanceof Set) {
|
||||
return _from(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 (arr.length == 0) 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
|
||||
@@ -7,7 +7,6 @@ var SYSYM = '__SYSTEM__'
|
||||
var hidden = _cell.hidden
|
||||
|
||||
var os = hidden.os;
|
||||
|
||||
_cell.os = null
|
||||
|
||||
var dylib_ext
|
||||
@@ -28,6 +27,18 @@ function use_embed(name) {
|
||||
return load_internal(`js_${name}_use`)
|
||||
}
|
||||
|
||||
globalThis.meme = function(obj, ...mixins) {
|
||||
var result = _ObjectCreate(obj)
|
||||
|
||||
array.for(mixins, mix => {
|
||||
if (isa(mix, object)) {
|
||||
for (var key in mix)
|
||||
result[key] = mix[key]
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
globalThis.logical = function(val1)
|
||||
{
|
||||
if (val1 == 0 || val1 == false || val1 == "false" || val1 == null)
|
||||
@@ -37,6 +48,7 @@ globalThis.logical = function(val1)
|
||||
return null;
|
||||
}
|
||||
|
||||
var utf8 = use_embed('utf8')
|
||||
var js = use_embed('js')
|
||||
var fd = use_embed('fd')
|
||||
|
||||
@@ -56,6 +68,13 @@ if (!fd.is_dir(core_path)) {
|
||||
var use_cache = {}
|
||||
use_cache['core/os'] = os
|
||||
|
||||
var _Symbol = Symbol
|
||||
|
||||
globalThis.key = function()
|
||||
{
|
||||
return _Symbol()
|
||||
}
|
||||
|
||||
// Load a core module from the file system
|
||||
function use_core(path) {
|
||||
var cache_key = 'core/' + path
|
||||
@@ -69,7 +88,7 @@ function use_core(path) {
|
||||
|
||||
if (fd.is_file(file_path)) {
|
||||
var script_blob = fd.slurp(file_path)
|
||||
var script = text(script_blob)
|
||||
var script = utf8.decode(script_blob)
|
||||
var mod = `(function setup_module(use){${script}})`
|
||||
var fn = js.eval('core:' + path, mod)
|
||||
var result = fn.call(sym, use_core);
|
||||
@@ -82,36 +101,141 @@ function use_core(path) {
|
||||
}
|
||||
|
||||
var blob = use_core('blob')
|
||||
var blob_stone = blob.prototype.stone
|
||||
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 = Object.create
|
||||
var _ArrayIsArray = Array.isArray
|
||||
|
||||
Object.prototype.toString = function()
|
||||
{
|
||||
return json.encode(this)
|
||||
}
|
||||
|
||||
function deepFreeze(object) {
|
||||
if (object instanceof blob)
|
||||
blob_stone.call(object);
|
||||
|
||||
var propNames = _ObjectKeys(object);
|
||||
|
||||
for (var name of propNames) {
|
||||
var value = object[name];
|
||||
|
||||
if ((value && typeof value == "object") || typeof value == "function")
|
||||
deepFreeze(value);
|
||||
}
|
||||
|
||||
return _ObjectFreeze(object);
|
||||
}
|
||||
|
||||
globalThis.actor = function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
globalThis.stone = deepFreeze
|
||||
stone.p = function(object)
|
||||
{
|
||||
if (object instanceof blob)
|
||||
return blob_stonep.call(object)
|
||||
|
||||
return _ObjectIsFrozen(object)
|
||||
}
|
||||
|
||||
var actor_mod = use_core('actor')
|
||||
var wota = use_core('wota')
|
||||
var nota = use_core('nota')
|
||||
|
||||
// Load internal modules for global functions
|
||||
globalThis.text = use_core('internal/text')
|
||||
globalThis.number = use_core('internal/number')
|
||||
globalThis.array = use_core('internal/array')
|
||||
globalThis.object = use_core('internal/object')
|
||||
globalThis.fn = use_core('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
|
||||
}
|
||||
|
||||
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 is_stone(value)
|
||||
if (master == number) return is_number(value)
|
||||
if (master == text) return is_text(value)
|
||||
if (master == logical) return is_logical(value)
|
||||
if (master == array) return is_array(value)
|
||||
if (master == object) return is_object(value)
|
||||
if (master == fn) return is_function(value)
|
||||
if (master == actor) return is_object(value) && value[ACTORDATA]
|
||||
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'
|
||||
if (master == actor) return isa(value, object) && value[ACTORDATA]
|
||||
|
||||
// Check prototype chain
|
||||
if (master.prototype) {
|
||||
@@ -137,22 +261,61 @@ globalThis.isa = function(value, master) {
|
||||
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
|
||||
|
||||
var nullguard = false
|
||||
globalThis.pi = 3.14159265358979323846264338327950288419716939937510
|
||||
|
||||
function caller_data(depth = 0)
|
||||
{
|
||||
var file = "nofile"
|
||||
var line = 0
|
||||
|
||||
var caller = new Error().stack.split("\n")[1+depth]
|
||||
if (!nullguard && is_null(caller)) {
|
||||
os.print(`caller_data now getting null`)
|
||||
os.print("\n")
|
||||
nullguard = true
|
||||
}
|
||||
|
||||
if (caller) {
|
||||
var md = caller.match(/\((.*)\:/)
|
||||
var m = md ? md[1] : "SCRIPT"
|
||||
@@ -323,8 +486,6 @@ _cell.config = config
|
||||
ENETSERVICE = config.net_service
|
||||
REPLYTIMEOUT = config.reply_timeout
|
||||
|
||||
|
||||
|
||||
/*
|
||||
When handling a message, the message appears like this:
|
||||
{
|
||||
@@ -355,7 +516,9 @@ function guid(bits = 256)
|
||||
return text(guid,'h')
|
||||
}
|
||||
|
||||
var HEADER = key()
|
||||
var _Symbol = Symbol
|
||||
|
||||
var HEADER = _Symbol()
|
||||
|
||||
// takes a function input value that will eventually be called with the current time in number form.
|
||||
$_.clock = function(fn) {
|
||||
@@ -673,6 +836,7 @@ function turn(msg)
|
||||
}
|
||||
|
||||
//log.console(`FIXME: need to get main from config, not just set to true`)
|
||||
//log.console(`FIXME: remove global access (ie globalThis.use)`)
|
||||
//log.console(`FIXME: add freeze/unfreeze at this level, so we can do it (but scripts cannot)`)
|
||||
actor_mod.register_actor(_cell.id, turn, true, config.ar_timer)
|
||||
|
||||
@@ -727,13 +891,12 @@ function handle_actor_disconnect(id) {
|
||||
|
||||
function handle_sysym(msg)
|
||||
{
|
||||
var from
|
||||
switch(msg.kind) {
|
||||
case 'stop':
|
||||
disrupt("got stop message")
|
||||
break
|
||||
case 'underling':
|
||||
from = msg.from
|
||||
var from = msg.from
|
||||
var greeter = greeters[from[ACTORDATA].id]
|
||||
if (greeter) greeter(msg.message)
|
||||
if (msg.message.type == 'disrupt')
|
||||
@@ -748,7 +911,7 @@ function handle_sysym(msg)
|
||||
} else throw new Error('Got a contact message, but no portal is established.')
|
||||
break
|
||||
case 'couple': // from must be notified when we die
|
||||
from = msg.from
|
||||
var from = msg.from
|
||||
underlings.add(from[ACTORDATA].id)
|
||||
log.system(`actor ${from} is coupled to me`)
|
||||
break
|
||||
@@ -812,42 +975,95 @@ if (!locator) {
|
||||
if (!locator)
|
||||
throw new Error(`Main program ${_cell.args.program} could not be found`)
|
||||
|
||||
stone(globalThis)
|
||||
// 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 rads = use_core("math/radians")
|
||||
log.console(rads)
|
||||
log.console("now, should be nofile:0")
|
||||
var _Error = Error
|
||||
var _JSON = JSON
|
||||
|
||||
// juicing these before Math is gone
|
||||
|
||||
use_core('math/radians')
|
||||
use_core('math/cycles')
|
||||
use_core('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
|
||||
|
||||
_ObjectFreeze(globalThis)
|
||||
|
||||
$_.clock(_ => {
|
||||
log.console("in clock")
|
||||
// Get capabilities for the main program
|
||||
var file_info = shop.file_info ? shop.file_info(locator.path) : null
|
||||
var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : []
|
||||
|
||||
log.console("injection")
|
||||
|
||||
|
||||
// Build values array for injection
|
||||
var vals = []
|
||||
log.console(`number to inject is ${inject.length}`)
|
||||
log.console('when the log.console statements are in the loop, with backticks, it runs but with errors on the injectables especially substring not seeming to work; without them, it totally fails')
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
var key = inject[i]
|
||||
log.console(`injecting ${i}, which is ${key}`) // when this line is present, works; when not present, does not work
|
||||
|
||||
if (key && key[0] == '$') key = key.substring(1)
|
||||
if (key == 'fd') vals.push(fd)
|
||||
else vals.push($_[key])
|
||||
log.console(`split at 1 was ${key}`)
|
||||
}
|
||||
|
||||
|
||||
// Create use function bound to the program's package
|
||||
var pkg = file_info ? file_info.package : null
|
||||
var use_fn = function(path) { return shop.use(path, pkg) }
|
||||
|
||||
|
||||
// Call with signature: setup_module(args, use, ...capabilities)
|
||||
// The script wrapper builds $_ from the injected capabilities for backward compatibility
|
||||
var val = locator.symbol.call(null, _cell.args.arg, use_fn, ...vals)
|
||||
|
||||
|
||||
if (val)
|
||||
throw new Error('Program must not return anything');
|
||||
})
|
||||
|
||||
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
|
||||
169
internal/number.cm
Normal file
169
internal/number.cm
Normal file
@@ -0,0 +1,169 @@
|
||||
/* number.cm - number conversion and math utilities */
|
||||
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.pow
|
||||
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
|
||||
93
internal/object.cm
Normal file
93
internal/object.cm
Normal file
@@ -0,0 +1,93 @@
|
||||
/* object.cm - object creation and manipulation utilities */
|
||||
|
||||
var _keys = array
|
||||
var _create = meme
|
||||
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)
|
||||
}
|
||||
|
||||
object.assign = function(obj, ...args)
|
||||
{
|
||||
return _assign(obj, ...args)
|
||||
}
|
||||
|
||||
return object
|
||||
@@ -6,6 +6,7 @@ var miniz = use('miniz')
|
||||
var time = use('time')
|
||||
var js = use('js')
|
||||
var crypto = use('crypto')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var pkg_tools = use('package')
|
||||
var os = use('os')
|
||||
@@ -62,7 +63,7 @@ var dylib_ext = '.dylib' // Default extension
|
||||
|
||||
var use_cache = os.use_cache
|
||||
var global_shop_path = os.global_shop_path
|
||||
var my$_ = os.$_
|
||||
var $_ = os.$_
|
||||
|
||||
Shop.get_package_dir = function(name) {
|
||||
return global_shop_path + '/packages/' + name
|
||||
@@ -276,7 +277,7 @@ Shop.load_lock = function() {
|
||||
// Save lock.toml configuration (to global shop)
|
||||
Shop.save_lock = function(lock) {
|
||||
var path = global_shop_path + '/lock.toml'
|
||||
fd.slurpwrite(path, stone(new blob(toml.encode(lock))));
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(lock)));
|
||||
}
|
||||
|
||||
|
||||
@@ -384,7 +385,7 @@ function inject_values(inject) {
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
var key = strip_dollar(inject[i])
|
||||
if (key == 'fd') vals.push(fd)
|
||||
else vals.push(my$_[key])
|
||||
else vals.push($_[key])
|
||||
}
|
||||
return vals
|
||||
}
|
||||
@@ -399,7 +400,23 @@ var script_form = function(path, script, pkg, inject) {
|
||||
var pkg_arg = pkg ? `'${pkg}'` : 'null'
|
||||
var params = inject_params(inject)
|
||||
|
||||
var fn = `(function setup_module(args, use${params}){ def arg = args; def PACKAGE = ${pkg_arg}; ${script}})`
|
||||
// Build $_ object from injected capabilities for backward compatibility
|
||||
var build_compat = ''
|
||||
if (inject && inject.length) {
|
||||
var compat_props = []
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
var name = inject[i]
|
||||
var key = name
|
||||
if (key && key[0] == '$') key = key.substring(1)
|
||||
compat_props.push(key + ': ' + name)
|
||||
}
|
||||
build_compat = 'var $_ = {' + compat_props.join(', ') + '};'
|
||||
}
|
||||
|
||||
// use is passed as a parameter, not on globalThis for the script
|
||||
// $fd is injected as a capability, but we still allow use('fd') for now
|
||||
// $_ is built from injected capabilities for backward compatibility
|
||||
var fn = `(function setup_module(args, use${params}){ def arg = args; def PACKAGE = ${pkg_arg}; ${build_compat} ${script}})`
|
||||
return fn
|
||||
}
|
||||
|
||||
@@ -414,7 +431,7 @@ function resolve_mod_fn(path, pkg) {
|
||||
var content = text(fd.slurp(path))
|
||||
var script = script_form(path, content, file_pkg, inject);
|
||||
|
||||
var obj = pull_from_cache(stone(new blob(script)))
|
||||
var obj = pull_from_cache(utf8.encode(script))
|
||||
if (obj) {
|
||||
var fn = js.compile_unblob(obj)
|
||||
return js.eval_compile(fn)
|
||||
@@ -426,7 +443,7 @@ function resolve_mod_fn(path, pkg) {
|
||||
|
||||
var fn = js.compile(compile_name, script)
|
||||
|
||||
put_into_cache(stone(new blob(script)), js.compile_blob(fn))
|
||||
put_into_cache(utf8.encode(script), js.compile_blob(fn))
|
||||
|
||||
return js.eval_compile(fn)
|
||||
}
|
||||
@@ -607,8 +624,7 @@ Shop.open_package_dylib = function(pkg) {
|
||||
}
|
||||
|
||||
// If no package context, only check core internal symbols
|
||||
if (!package_context || package_context == 'core') {
|
||||
path = path.replace('/', '_')
|
||||
if (!package_context) {
|
||||
var core_sym = `js_${path}_use`
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
@@ -792,7 +808,7 @@ function execute_module(info)
|
||||
used = mod_resolve.symbol.call(context, null, use_fn, ...vals)
|
||||
} else if (c_resolve.scope < 900) {
|
||||
// C only
|
||||
used = c_resolve.symbol(null, my$_)
|
||||
used = c_resolve.symbol(null, $_)
|
||||
} else {
|
||||
throw new Error(`Module ${info.path} could not be found`)
|
||||
} if (!used)
|
||||
|
||||
306
internal/text.c
Normal file
306
internal/text.c
Normal file
@@ -0,0 +1,306 @@
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
JSC_CCALL(text_blob_to_hex,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
uint8_t *bytes = (uint8_t *)blob_data;
|
||||
size_t hex_len = blob_len * 2;
|
||||
char *hex_str = malloc(hex_len + 1);
|
||||
if (!hex_str) return JS_ThrowOutOfMemory(js);
|
||||
static const char hex_digits[] = "0123456789ABCDEF";
|
||||
for (size_t i = 0; i < blob_len; ++i) {
|
||||
hex_str[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF];
|
||||
hex_str[i * 2 + 1] = hex_digits[bytes[i] & 0xF];
|
||||
}
|
||||
hex_str[hex_len] = '\0';
|
||||
JSValue val = JS_NewString(js, hex_str);
|
||||
free(hex_str);
|
||||
return val;
|
||||
)
|
||||
|
||||
JSC_CCALL(text_blob_to_base32,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
uint8_t *bytes = (uint8_t *)blob_data;
|
||||
static const char b32_digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
// Calculate exact output length needed
|
||||
size_t groups = (blob_len + 4) / 5; // Round up to next group of 5
|
||||
size_t b32_len = groups * 8;
|
||||
char *b32_str = malloc(b32_len + 1);
|
||||
if (!b32_str) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_idx = 0;
|
||||
size_t out_idx = 0;
|
||||
|
||||
while (in_idx < blob_len) {
|
||||
// Read up to 5 bytes into a 40-bit buffer
|
||||
uint64_t buf = 0;
|
||||
int bytes_to_read = (blob_len - in_idx < 5) ? (blob_len - in_idx) : 5;
|
||||
|
||||
for (int i = 0; i < bytes_to_read; ++i) {
|
||||
buf = (buf << 8) | bytes[in_idx++];
|
||||
}
|
||||
|
||||
// Pad buffer to 40 bits if we read fewer than 5 bytes
|
||||
buf <<= 8 * (5 - bytes_to_read);
|
||||
|
||||
// Extract 8 groups of 5 bits each
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
b32_str[out_idx++] = b32_digits[(buf >> (35 - i * 5)) & 0x1F];
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding if necessary
|
||||
if (blob_len % 5 != 0) {
|
||||
static const int pad_count[] = {0, 6, 4, 3, 1}; // padding for 0,1,2,3,4 bytes
|
||||
int padding = pad_count[blob_len % 5];
|
||||
for (int i = 0; i < padding; ++i) {
|
||||
b32_str[b32_len - 1 - i] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
b32_str[b32_len] = '\0';
|
||||
JSValue val = JS_NewString(js, b32_str);
|
||||
free(b32_str);
|
||||
return val;
|
||||
)
|
||||
|
||||
static int base32_char_to_val(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a';
|
||||
if (c >= '2' && c <= '7') return c - '2' + 26;
|
||||
return -1;
|
||||
}
|
||||
|
||||
JSC_CCALL(text_base32_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t str_len = strlen(str);
|
||||
if (str_len == 0) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Empty base32 string");
|
||||
}
|
||||
|
||||
// Remove padding to get effective length
|
||||
size_t effective_len = str_len;
|
||||
while (effective_len > 0 && str[effective_len - 1] == '=') {
|
||||
effective_len--;
|
||||
}
|
||||
|
||||
// Calculate output length: each group of 8 base32 chars -> 5 bytes
|
||||
size_t output_len = (effective_len * 5) / 8;
|
||||
uint8_t *output = malloc(output_len);
|
||||
if (!output) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
size_t in_idx = 0;
|
||||
size_t out_idx = 0;
|
||||
|
||||
// Process in groups of 8 characters (40 bits -> 5 bytes)
|
||||
while (in_idx < effective_len) {
|
||||
uint64_t buf = 0;
|
||||
int chars_to_read = (effective_len - in_idx < 8) ? (effective_len - in_idx) : 8;
|
||||
|
||||
// Read up to 8 base32 characters into buffer
|
||||
for (int i = 0; i < chars_to_read; ++i) {
|
||||
int val = base32_char_to_val(str[in_idx++]);
|
||||
if (val < 0) {
|
||||
free(output);
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base32 character");
|
||||
}
|
||||
buf = (buf << 5) | val;
|
||||
}
|
||||
|
||||
// Calculate how many bytes we can extract
|
||||
int bytes_to_extract = (chars_to_read * 5) / 8;
|
||||
|
||||
// Shift buffer to align the most significant bits
|
||||
buf <<= (40 - chars_to_read * 5);
|
||||
|
||||
// Extract bytes from most significant to least significant
|
||||
for (int i = 0; i < bytes_to_extract && out_idx < output_len; ++i) {
|
||||
output[out_idx++] = (buf >> (32 - i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue val = js_new_blob_stoned_copy(js, output, output_len);
|
||||
free(output);
|
||||
JS_FreeCString(js, str);
|
||||
return val;
|
||||
)
|
||||
|
||||
static int base64_char_to_val_standard(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
static int base64_char_to_val_url(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '-') return 62;
|
||||
if (c == '_') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*─── blob → Base64 (standard, with ‘+’ and ‘/’, padded) ───────────────────*/
|
||||
JSC_CCALL(text_blob_to_base64,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
const uint8_t *bytes = blob_data;
|
||||
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
size_t out_len = ((blob_len + 2) / 3) * 4;
|
||||
char *out = malloc(out_len + 1);
|
||||
if (!out) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < blob_len) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
buf = (buf << 8) | bytes[in_i++];
|
||||
}
|
||||
buf <<= 8 * (3 - to_read);
|
||||
out[out_i++] = b64[(buf >> 18) & 0x3F];
|
||||
out[out_i++] = b64[(buf >> 12) & 0x3F];
|
||||
out[out_i++] = (to_read > 1 ? b64[(buf >> 6) & 0x3F] : '=');
|
||||
out[out_i++] = (to_read > 2 ? b64[ buf & 0x3F] : '=');
|
||||
}
|
||||
out[out_len] = '\0';
|
||||
JSValue v = JS_NewString(js, out);
|
||||
free(out);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── Base64 → blob (standard, expects ‘+’ and ‘/’, pads allowed) ────────────*/
|
||||
JSC_CCALL(text_base64_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t len = strlen(str);
|
||||
// strip padding for length calculation
|
||||
size_t eff = len;
|
||||
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||
size_t out_len = (eff * 6) / 8;
|
||||
uint8_t *out = malloc(out_len);
|
||||
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < eff) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
int v = base64_char_to_val_standard(str[in_i++]);
|
||||
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base64 character"); }
|
||||
buf = (buf << 6) | v;
|
||||
}
|
||||
buf <<= 6 * (4 - to_read);
|
||||
int bytes_out = (to_read * 6) / 8;
|
||||
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||
free(out);
|
||||
JS_FreeCString(js, str);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── blob → Base64URL (no padding, ‘-’ and ‘_’) ─────────────────────────────*/
|
||||
JSC_CCALL(text_blob_to_base64url,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
const uint8_t *bytes = blob_data;
|
||||
static const char b64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789-_";
|
||||
size_t raw_len = ((blob_len + 2) / 3) * 4;
|
||||
// we’ll drop any trailing '='
|
||||
char *out = malloc(raw_len + 1);
|
||||
if (!out) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < blob_len) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
buf = (buf << 8) | bytes[in_i++];
|
||||
}
|
||||
buf <<= 8 * (3 - to_read);
|
||||
out[out_i++] = b64url[(buf >> 18) & 0x3F];
|
||||
out[out_i++] = b64url[(buf >> 12) & 0x3F];
|
||||
if (to_read > 1) out[out_i++] = b64url[(buf >> 6) & 0x3F];
|
||||
if (to_read > 2) out[out_i++] = b64url[ buf & 0x3F];
|
||||
}
|
||||
out[out_i] = '\0';
|
||||
JSValue v = JS_NewString(js, out);
|
||||
free(out);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── Base64URL → blob (accepts ‘-’ / ‘_’, no padding needed) ─────────────────*/
|
||||
JSC_CCALL(text_base64url_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t len = strlen(str);
|
||||
size_t eff = len; // no '=' in URL‐safe, but strip if present
|
||||
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||
size_t out_len = (eff * 6) / 8;
|
||||
uint8_t *out = malloc(out_len);
|
||||
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < eff) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
int v = base64_char_to_val_url(str[in_i++]);
|
||||
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base64url character"); }
|
||||
buf = (buf << 6) | v;
|
||||
}
|
||||
buf <<= 6 * (4 - to_read);
|
||||
int bytes_out = (to_read * 6) / 8;
|
||||
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||
free(out);
|
||||
JS_FreeCString(js, str);
|
||||
return v;
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_text_funcs[] = {
|
||||
MIST_FUNC_DEF(text, blob_to_hex, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base32, 1),
|
||||
MIST_FUNC_DEF(text, base32_to_blob, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base64, 1),
|
||||
MIST_FUNC_DEF(text, base64_to_blob, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base64url, 1),
|
||||
MIST_FUNC_DEF(text, base64url_to_blob, 1),
|
||||
};
|
||||
|
||||
JSValue js_internal_text_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
|
||||
return mod;
|
||||
}
|
||||
602
internal/text.cm
Normal file
602
internal/text.cm
Normal file
@@ -0,0 +1,602 @@
|
||||
/* text.cm - text conversion and formatting utilities */
|
||||
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
|
||||
function to_radix(num, radix) {
|
||||
if (radix < 2 || radix > 36) return null;
|
||||
|
||||
var digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
var result = "";
|
||||
var n = number.whole(num);
|
||||
var negative = n < 0;
|
||||
n = number.abs(n);
|
||||
|
||||
if (n == 0) return "0";
|
||||
|
||||
while (n > 0) {
|
||||
result = digits[n % radix] + result;
|
||||
n = number.floor(n / radix);
|
||||
}
|
||||
|
||||
return negative ? "-" + result : result;
|
||||
}
|
||||
|
||||
// Insert separator every n digits from right
|
||||
function add_separator(str, sep, n) {
|
||||
if (!n || n == 0) return str;
|
||||
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
var parts = str.split('.');
|
||||
var integer = parts[0];
|
||||
var decimal = parts[1] || '';
|
||||
|
||||
// Add separators to integer part
|
||||
var result = "";
|
||||
for (var i = integer.length - 1, count = 0; i >= 0; i--) {
|
||||
if (count == n && i != integer.length - 1) {
|
||||
result = sep + result;
|
||||
count = 0;
|
||||
}
|
||||
result = integer[i] + result;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (decimal) result += '.' + decimal;
|
||||
return negative ? '-' + result : result;
|
||||
}
|
||||
|
||||
// Format number with separator from left
|
||||
function add_separator_left(str, sep, n) {
|
||||
if (!n || n == 0) return str;
|
||||
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
var result = "";
|
||||
for (var i = 0, count = 0; i < str.length; i++) {
|
||||
if (count == n && i != 0) {
|
||||
result += sep;
|
||||
count = 0;
|
||||
}
|
||||
result += str[i];
|
||||
count++;
|
||||
}
|
||||
|
||||
return negative ? '-' + result : result;
|
||||
}
|
||||
|
||||
/* -------- main text function --------------------------------------- */
|
||||
|
||||
function text(...arguments) {
|
||||
var arg = arguments[0];
|
||||
|
||||
// Handle blob conversion
|
||||
if (arg instanceof blob) {
|
||||
if (!stone.p(arg))
|
||||
throw new Error("text: blob must be stone for reading");
|
||||
|
||||
var format = arguments[1];
|
||||
var bit_length = arg.length;
|
||||
var result = "";
|
||||
|
||||
if (typeof format == 'string') {
|
||||
// Extract style from format
|
||||
var style = '';
|
||||
for (var i = 0; i < format.length; i++) {
|
||||
if ((format[i] >= 'a' && format[i] <= 'z') || (format[i] >= 'A' && format[i] <= 'Z')) {
|
||||
style = format[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle blob encoding styles
|
||||
switch (style) {
|
||||
case 'h': // hexadecimal
|
||||
return that.blob_to_hex(arg);
|
||||
|
||||
case 't': // base32
|
||||
return that.blob_to_base32(arg);
|
||||
|
||||
case 'b': // binary
|
||||
for (var i = 0; i < bit_length; i++) {
|
||||
result += arg.read_logical(i) ? '1' : '0';
|
||||
}
|
||||
return result;
|
||||
|
||||
case 'o': // octal
|
||||
var bits = 0;
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < bit_length; i++) {
|
||||
var bit = arg.read_logical(i);
|
||||
value = (value << 1) | (bit ? 1 : 0);
|
||||
bits++;
|
||||
|
||||
if (bits == 3) {
|
||||
result += value.toString();
|
||||
bits = 0;
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bits
|
||||
if (bits > 0) {
|
||||
value = value << (3 - bits);
|
||||
result += value.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: interpret as UTF-8 text
|
||||
// Use the utf8 module to decode the blob
|
||||
if (arg.length == 0) return ""
|
||||
return utf8.decode(arg);
|
||||
}
|
||||
|
||||
// Handle array conversion
|
||||
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 == number.floor(item))) {
|
||||
all_codepoints = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_codepoints && separator == "") {
|
||||
// Use utf8 module to convert codepoints to string
|
||||
return utf8.from_codepoints(arg);
|
||||
} else {
|
||||
// General array to string conversion
|
||||
var result = "";
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
if (i > 0) result += separator;
|
||||
|
||||
var item = arg[i];
|
||||
if (typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == number.floor(item)) {
|
||||
// Single codepoint - use utf8 module
|
||||
result += utf8.from_codepoints([item]);
|
||||
} else {
|
||||
result += String(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle number conversion
|
||||
if (typeof arg == 'number') {
|
||||
var format = arguments[1];
|
||||
|
||||
// Simple radix conversion
|
||||
if (typeof format == 'number') {
|
||||
return to_radix(arg, format);
|
||||
}
|
||||
|
||||
// Format string conversion
|
||||
if (typeof format == 'string') {
|
||||
return format_number(arg, format);
|
||||
}
|
||||
|
||||
// Default conversion
|
||||
return _String(arg);
|
||||
}
|
||||
|
||||
// Handle text operations
|
||||
if (typeof arg == 'string') {
|
||||
if (arguments.length == 1) return arg;
|
||||
|
||||
var from = arguments[1];
|
||||
var to = arguments[2];
|
||||
|
||||
if (typeof from != 'number' || typeof to != 'number') return arg;
|
||||
|
||||
var len = arg.length;
|
||||
|
||||
// Adjust negative indices
|
||||
if (from < 0) from += len;
|
||||
if (to < 0) to += len;
|
||||
|
||||
// Default values
|
||||
if (from == null) from = 0;
|
||||
if (to == null) to = len;
|
||||
|
||||
// Validate range
|
||||
if (from < 0 || from > to || to > len) return null;
|
||||
|
||||
return arg.substring(from, to);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* -------- number formatting ---------------------------------------- */
|
||||
|
||||
function format_number(num, format) {
|
||||
// Parse format string
|
||||
var separation = 0;
|
||||
var style = '';
|
||||
var places = 0;
|
||||
|
||||
var i = 0;
|
||||
|
||||
// Parse separation digit
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
separation = number(format[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Parse style letter
|
||||
if (i < format.length) {
|
||||
style = format[i];
|
||||
i++;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse places digits
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = number(format[i]);
|
||||
i++;
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = places * 10 + number(format[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid format if there's more
|
||||
if (i < format.length) return null;
|
||||
|
||||
// Real number styles
|
||||
if (style == 'e' || style == 'n' || style == 's' ||
|
||||
style == 'u' || style == 'd' || style == 'v' || style == 'l') {
|
||||
|
||||
var decimal_point = '.';
|
||||
var separator = '';
|
||||
var default_separation = 0;
|
||||
var default_places = 0;
|
||||
|
||||
switch (style) {
|
||||
case 'e': // exponential
|
||||
decimal_point = '.';
|
||||
separator = '';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'n': // number
|
||||
decimal_point = '.';
|
||||
separator = '';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 's': // space
|
||||
decimal_point = '.';
|
||||
separator = ' ';
|
||||
default_separation = 3;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'u': // underbar
|
||||
decimal_point = '.';
|
||||
separator = '_';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'd': // decimal
|
||||
decimal_point = '.';
|
||||
separator = ',';
|
||||
default_separation = 3;
|
||||
default_places = 2;
|
||||
break;
|
||||
case 'v': // comma (European style)
|
||||
decimal_point = ',';
|
||||
separator = '.';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'l': // locale (default to 'd' style for now)
|
||||
decimal_point = '.';
|
||||
separator = ',';
|
||||
default_separation = 3;
|
||||
default_places = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (separation == 0) separation = default_separation;
|
||||
if (places == 0 && style != 'e' && style != 'n') places = default_places;
|
||||
|
||||
// Format the number
|
||||
if (style == 'e') {
|
||||
// Scientific notation
|
||||
var str = places > 0 ? num.toExponential(places) : num.toExponential();
|
||||
return str;
|
||||
} 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 {
|
||||
// Regular decimal formatting
|
||||
var str;
|
||||
if (places > 0) {
|
||||
str = num.toFixed(places);
|
||||
} else {
|
||||
str = num.toString();
|
||||
}
|
||||
|
||||
// Replace decimal point if needed
|
||||
if (decimal_point != '.') {
|
||||
str = str.replace('.', decimal_point);
|
||||
}
|
||||
|
||||
// Add separators
|
||||
if (separation > 0 && separator) {
|
||||
str = add_separator(str, separator, separation);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
// Integer styles
|
||||
if (style == 'i' || style == 'b' || style == 'o' ||
|
||||
style == 'h' || style == 't') {
|
||||
|
||||
var radix = 10;
|
||||
var default_separation = 0;
|
||||
var default_places = 1;
|
||||
|
||||
switch (style) {
|
||||
case 'i': // integer
|
||||
radix = 10;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'b': // binary
|
||||
radix = 2;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'o': // octal
|
||||
radix = 8;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'h': // hexadecimal
|
||||
radix = 16;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 't': // base32
|
||||
radix = 32;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (separation == 0) separation = default_separation;
|
||||
if (places == 0) places = default_places;
|
||||
|
||||
// Convert to integer
|
||||
var n = number.whole(num);
|
||||
var str = to_radix(n, radix).toUpperCase();
|
||||
|
||||
// Pad with zeros if needed
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
while (str.length < places) {
|
||||
str = '0' + str;
|
||||
}
|
||||
|
||||
// Add separators
|
||||
if (separation > 0) {
|
||||
str = add_separator_left(str, '_', separation);
|
||||
}
|
||||
|
||||
return negative ? '-' + str : str;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* -------- 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
|
||||
4
link.cm
4
link.cm
@@ -3,7 +3,7 @@
|
||||
|
||||
var toml = use('toml')
|
||||
var fd = use('fd')
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
var os = use('os')
|
||||
|
||||
var global_shop_path = os.global_shop_path
|
||||
@@ -81,7 +81,7 @@ Link.save = function(links) {
|
||||
link_cache = links
|
||||
var cfg = { links: links }
|
||||
var path = get_links_path()
|
||||
fd.slurpwrite(path, new blob(toml.encode(cfg)))
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
|
||||
}
|
||||
|
||||
Link.add = function(canonical, target, shop) {
|
||||
|
||||
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
|
||||
}
|
||||
@@ -38,6 +38,7 @@ endif
|
||||
link_args = link
|
||||
sources = []
|
||||
src += [ # core
|
||||
'qjs_blob.c',
|
||||
'monocypher.c',
|
||||
'cell.c',
|
||||
'wildmatch.c',
|
||||
@@ -57,6 +58,8 @@ scripts = [
|
||||
'wildstar.c',
|
||||
'fit.c',
|
||||
'crypto.c',
|
||||
'internal/text.c',
|
||||
'utf8.c',
|
||||
'internal/kim.c',
|
||||
'time.c',
|
||||
'internal/nota.c',
|
||||
@@ -67,6 +70,7 @@ scripts = [
|
||||
'net/enet.c',
|
||||
'wildstar.c',
|
||||
'archive/miniz.c',
|
||||
'internal/json.c',
|
||||
]
|
||||
|
||||
foreach file: scripts
|
||||
@@ -74,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)
|
||||
|
||||
32
pronto.cm
32
pronto.cm
@@ -33,9 +33,9 @@ function fallback(requestor_array) {
|
||||
|
||||
return function fallback_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
var index = 0
|
||||
var current_cancel = null
|
||||
var cancelled = false
|
||||
let index = 0
|
||||
let current_cancel = null
|
||||
let cancelled = false
|
||||
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
@@ -98,10 +98,10 @@ function parallel(requestor_array, throttle, need) {
|
||||
check_callback(callback, factory)
|
||||
def results = new Array(length)
|
||||
def cancel_list = new Array(length)
|
||||
var next_index = 0
|
||||
var successes = 0
|
||||
var failures = 0
|
||||
var finished = false
|
||||
let next_index = 0
|
||||
let successes = 0
|
||||
let failures = 0
|
||||
let finished = false
|
||||
|
||||
function cancel(reason) {
|
||||
if (finished) return
|
||||
@@ -154,7 +154,7 @@ function parallel(requestor_array, throttle, need) {
|
||||
}
|
||||
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (var i = 0; i < concurrent; i++) start_one()
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
}
|
||||
@@ -180,10 +180,10 @@ function race(requestor_array, throttle, need) {
|
||||
check_callback(callback, factory)
|
||||
def results = new Array(length)
|
||||
def cancel_list = new Array(length)
|
||||
var next_index = 0
|
||||
var successes = 0
|
||||
var failures = 0
|
||||
var finished = false
|
||||
let next_index = 0
|
||||
let successes = 0
|
||||
let failures = 0
|
||||
let finished = false
|
||||
|
||||
function cancel(reason) {
|
||||
if (finished) return
|
||||
@@ -239,7 +239,7 @@ function race(requestor_array, throttle, need) {
|
||||
}
|
||||
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (var i = 0; i < concurrent; i++) start_one()
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
}
|
||||
@@ -258,9 +258,9 @@ function sequence(requestor_array) {
|
||||
|
||||
return function sequence_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
var index = 0
|
||||
var current_cancel = null
|
||||
var cancelled = false
|
||||
let index = 0
|
||||
let current_cancel = null
|
||||
let cancelled = false
|
||||
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
|
||||
113
source/blob.h
113
source/blob.h
@@ -138,81 +138,72 @@ void bitcpy(unsigned char *dst, size_t dst_bit_offset,
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint16_t load16_window(const uint8_t *s, size_t i, size_t last_byte)
|
||||
// Fast bit-copy for arbitrary bit ranges (inclusive) from src → dest
|
||||
void copy_bits_fast(const void *src, void *dest,
|
||||
size_t n, /* start bit in src (inclusive) */
|
||||
size_t m, /* end bit in src (inclusive) */
|
||||
size_t x) /* start bit in dest */
|
||||
{
|
||||
uint16_t lo = s[i];
|
||||
uint16_t hi = 0;
|
||||
if (i + 1 <= last_byte) hi = (uint16_t)s[i + 1] << 8;
|
||||
return lo | hi;
|
||||
}
|
||||
|
||||
void copy_bits_fast(const void *src, void *dest, size_t n, size_t m, size_t x)
|
||||
{
|
||||
if (m < n) return;
|
||||
|
||||
const uint8_t *s = (const uint8_t *)src;
|
||||
uint8_t *d = (uint8_t *)dest;
|
||||
const uint8_t *s = (const uint8_t*)src;
|
||||
uint8_t *d = (uint8_t*)dest;
|
||||
|
||||
size_t total_bits = m - n + 1;
|
||||
size_t src_bit = n;
|
||||
size_t dst_bit = x;
|
||||
if (m < n) return;
|
||||
|
||||
size_t src_bit = n;
|
||||
size_t dst_bit = x;
|
||||
size_t src_byte = src_bit >> 3;
|
||||
size_t dst_byte = dst_bit >> 3;
|
||||
int src_off = src_bit & 7;
|
||||
int dst_off = dst_bit & 7;
|
||||
int src_off = src_bit & 7;
|
||||
int dst_off = dst_bit & 7;
|
||||
|
||||
size_t last_src_byte = m >> 3;
|
||||
|
||||
/* Fast path: whole bytes, aligned */
|
||||
if (src_off == 0 && dst_off == 0 && (total_bits & 7) == 0) {
|
||||
memcpy(d + dst_byte, s + src_byte, total_bits >> 3);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 1) Leading partial byte to align dest */
|
||||
// 1) Leading partial byte to align dest
|
||||
if (dst_off != 0) {
|
||||
size_t chunk = 8 - dst_off;
|
||||
if (chunk > total_bits) chunk = total_bits;
|
||||
|
||||
uint8_t dst_mask = (uint8_t)(((1u << chunk) - 1u) << dst_off);
|
||||
uint16_t wb = load16_window(s, src_byte, last_src_byte);
|
||||
uint8_t dst_mask = (((1u << chunk) - 1u) << dst_off);
|
||||
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
|
||||
uint8_t bits = (uint8_t)((wb >> src_off) & ((1u << chunk) - 1u));
|
||||
bits <<= dst_off;
|
||||
d[dst_byte] = (d[dst_byte] & (uint8_t)~dst_mask) | (bits & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
|
||||
|
||||
total_bits -= chunk;
|
||||
src_bit += chunk;
|
||||
dst_bit += chunk;
|
||||
src_byte = src_bit >> 3;
|
||||
dst_byte = dst_bit >> 3;
|
||||
src_off = src_bit & 7;
|
||||
dst_off = dst_bit & 7;
|
||||
src_bit += chunk;
|
||||
dst_bit += chunk;
|
||||
src_byte = src_bit >> 3;
|
||||
dst_byte = dst_bit >> 3;
|
||||
src_off = src_bit & 7;
|
||||
dst_off = dst_bit & 7; // now zero
|
||||
}
|
||||
|
||||
/* 2) Copy full bytes */
|
||||
// 2) Copy full bytes
|
||||
if (total_bits >= 8) {
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
|
||||
if (src_off == 0) {
|
||||
memcpy(d + dst_byte, s + src_byte, num_bytes);
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
memcpy(&d[dst_byte], &s[src_byte], num_bytes);
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
} else {
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
for (size_t i = 0; i < num_bytes; i++) {
|
||||
uint16_t wb = load16_window(s, src_byte + i, last_src_byte);
|
||||
uint16_t wb = (uint16_t)s[src_byte + i] |
|
||||
((uint16_t)s[src_byte + i + 1] << 8);
|
||||
d[dst_byte + i] = (uint8_t)((wb >> src_off) & 0xFFu);
|
||||
}
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
}
|
||||
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
}
|
||||
|
||||
/* 3) Trailing bits (< 8), dest is byte-aligned here */
|
||||
// 3) Trailing bits (< 8)
|
||||
if (total_bits > 0) {
|
||||
uint8_t dst_mask = (uint8_t)((1u << total_bits) - 1u);
|
||||
uint16_t wb = load16_window(s, src_byte, last_src_byte);
|
||||
uint8_t dst_mask = (1u << total_bits) - 1u;
|
||||
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
|
||||
uint8_t bits = (uint8_t)((wb >> src_off) & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & (uint8_t)~dst_mask) | (bits & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,30 +378,16 @@ int blob_write_text(blob *b, const char *text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blob_write_bytes(blob *b, const void *data, size_t len_bytes)
|
||||
{
|
||||
int blob_write_bytes(blob *b, const void *data, size_t len_bytes) {
|
||||
if (!b || b->is_stone) return -1;
|
||||
if (!len_bytes) return 0;
|
||||
|
||||
size_t bit_len = len_bytes << 3;
|
||||
size_t bit_off = b->length;
|
||||
size_t new_len = bit_off + bit_len;
|
||||
if (new_len < bit_off) return -1;
|
||||
|
||||
if (blob_ensure_capacity(b, new_len) < 0) return -1;
|
||||
|
||||
if ((bit_off & 7) == 0) {
|
||||
uint8_t *dst = (uint8_t *)b->data + (bit_off >> 3);
|
||||
memcpy(dst, data, len_bytes);
|
||||
} else {
|
||||
copy_bits_fast(data, b->data, 0, bit_len - 1, bit_off);
|
||||
}
|
||||
|
||||
b->length = new_len;
|
||||
if (len_bytes == 0) return 0;
|
||||
size_t bits = len_bytes * 8;
|
||||
if (blob_ensure_capacity(b, bits) < 0) return -1;
|
||||
copy_bits_fast(data, b->data, 0, bits - 1, b->length);
|
||||
b->length += bits;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int blob_read_bit(const blob *b, size_t pos, int *out_bit) {
|
||||
if (!b || !b->is_stone || !out_bit) return -1;
|
||||
if (pos >= b->length) return -1;
|
||||
|
||||
@@ -32,7 +32,6 @@ DEF(true, "true")
|
||||
DEF(if, "if")
|
||||
DEF(else, "else")
|
||||
DEF(return, "return")
|
||||
DEF(go, "go")
|
||||
DEF(var, "var")
|
||||
DEF(def, "def")
|
||||
DEF(this, "this")
|
||||
|
||||
@@ -210,6 +210,10 @@ DEF( mul, 1, 2, 1, none)
|
||||
DEF( div, 1, 2, 1, none)
|
||||
DEF( mod, 1, 2, 1, none)
|
||||
DEF( add, 1, 2, 1, none)
|
||||
DEF( add_int, 1, 2, 1, none)
|
||||
DEF( add_float, 1, 2, 1, none)
|
||||
DEF( add_string, 1, 2, 1, none)
|
||||
DEF( add_nullnum, 1, 2, 1, none)
|
||||
DEF( sub, 1, 2, 1, none)
|
||||
DEF( pow, 1, 2, 1, none)
|
||||
DEF( shl, 1, 2, 1, none)
|
||||
|
||||
5038
source/quickjs.c
5038
source/quickjs.c
File diff suppressed because it is too large
Load Diff
@@ -262,7 +262,8 @@ static inline JS_BOOL JS_VALUE_IS_NAN(JSValue v)
|
||||
|
||||
#define JS_VALUE_IS_BOTH_INT(v1, v2) ((JS_VALUE_GET_TAG(v1) | JS_VALUE_GET_TAG(v2)) == 0)
|
||||
#define JS_VALUE_IS_BOTH_FLOAT(v1, v2) (JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v1)) && JS_TAG_IS_FLOAT64(JS_VALUE_GET_TAG(v2)))
|
||||
|
||||
#define JS_TAG_IS_STRING(v1) (v1 == JS_TAG_STRING || v1 == JS_TAG_STRING_ROPE)
|
||||
#define JS_TAG_IS_NUMBER(v1) (JS_TAG_IS_FLOAT64(v1) || v1 == JS_TAG_INT)
|
||||
#define JS_VALUE_HAS_REF_COUNT(v) ((unsigned)JS_VALUE_GET_TAG(v) >= (unsigned)JS_TAG_FIRST)
|
||||
|
||||
/* special values */
|
||||
|
||||
14
test.ce
14
test.ce
@@ -3,9 +3,7 @@ var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
var time = use('time')
|
||||
var json = use('json')
|
||||
var blob = use('blob')
|
||||
|
||||
log.console("here")
|
||||
var utf8 = use('utf8')
|
||||
|
||||
if (!args) args = []
|
||||
|
||||
@@ -552,9 +550,8 @@ function finalize_results() {
|
||||
}
|
||||
|
||||
// If no actor tests, finalize immediately
|
||||
var totals
|
||||
if (all_actor_tests.length == 0) {
|
||||
totals = { total: 0, passed: 0, failed: 0 }
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
@@ -567,9 +564,10 @@ if (all_actor_tests.length == 0) {
|
||||
$delay(check_timeouts, 1000)
|
||||
}
|
||||
|
||||
|
||||
// Generate Reports function
|
||||
function generate_reports(totals) {
|
||||
var timestamp = text(number.floor(time.number()))
|
||||
var timestamp = number.floor(time.number()).toString()
|
||||
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
|
||||
ensure_dir(report_dir)
|
||||
|
||||
@@ -630,7 +628,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
}
|
||||
}
|
||||
ensure_dir(report_dir)
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, stone(new blob(txt_report)))
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, utf8.encode(txt_report))
|
||||
log.console(`Report written to ${report_dir}/test.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
@@ -647,7 +645,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
}
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, stone(new blob(json.encode(pkg_tests))))
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_tests)))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
123
tests/suite.cm
123
tests/suite.cm
@@ -1,7 +1,6 @@
|
||||
// Comprehensive test suite for cell runtime stability
|
||||
// Tests all core features before implementing performance optimizations
|
||||
// (bytecode passes, ICs, quickening, tail call optimization)
|
||||
//
|
||||
|
||||
return {
|
||||
// ============================================================================
|
||||
@@ -166,6 +165,7 @@ return {
|
||||
if (!caught) throw "string + null should throw"
|
||||
},
|
||||
|
||||
<<<<<<< Updated upstream
|
||||
// ============================================================================
|
||||
// COMPARISON OPERATORS
|
||||
// ============================================================================
|
||||
@@ -315,100 +315,10 @@ return {
|
||||
if (x != 10) throw "var reassignment failed"
|
||||
},
|
||||
|
||||
// ============================================================================
|
||||
// VAR BLOCK SCOPING (var now behaves like let)
|
||||
// ============================================================================
|
||||
|
||||
test_var_block_scope_basic: function() {
|
||||
var x = 1
|
||||
{
|
||||
var x = 2
|
||||
if (x != 2) throw "var should be block scoped - inner scope failed"
|
||||
}
|
||||
if (x != 1) throw "var should be block scoped - outer scope affected"
|
||||
},
|
||||
|
||||
test_var_block_scope_if: function() {
|
||||
var x = 1
|
||||
if (true) {
|
||||
var x = 2
|
||||
if (x != 2) throw "var in if block should be scoped"
|
||||
}
|
||||
if (x != 1) throw "var in if block should not affect outer scope"
|
||||
},
|
||||
|
||||
test_var_block_scope_for: function() {
|
||||
var x = 1
|
||||
for (var i = 0; i < 1; i = i + 1) {
|
||||
var x = 2
|
||||
if (x != 2) throw "var in for block should be scoped"
|
||||
}
|
||||
if (x != 1) throw "var in for block should not affect outer scope"
|
||||
},
|
||||
|
||||
test_var_for_loop_iterator_scope: function() {
|
||||
var sum = 0
|
||||
for (var i = 0; i < 3; i = i + 1) {
|
||||
sum = sum + i
|
||||
}
|
||||
if (sum != 3) throw "for loop should work with block scoped var"
|
||||
var caught = false
|
||||
try {
|
||||
var y = i
|
||||
} catch (e) {
|
||||
caught = true
|
||||
}
|
||||
if (!caught) throw "for loop iterator should not leak to outer scope"
|
||||
},
|
||||
|
||||
test_var_nested_blocks: function() {
|
||||
var x = 1
|
||||
{
|
||||
var x = 2
|
||||
{
|
||||
var x = 3
|
||||
if (x != 3) throw "var in nested block level 2 failed"
|
||||
}
|
||||
if (x != 2) throw "var in nested block level 1 failed"
|
||||
}
|
||||
if (x != 1) throw "var in nested blocks outer scope failed"
|
||||
},
|
||||
|
||||
test_var_redeclaration_different_scope: function() {
|
||||
var x = 1
|
||||
{
|
||||
var x = 2
|
||||
}
|
||||
if (x != 1) throw "var in different scope should not affect outer"
|
||||
},
|
||||
|
||||
test_var_switch_scope: function() {
|
||||
var x = 1
|
||||
switch (1) {
|
||||
case 1:
|
||||
var x = 2
|
||||
if (x != 2) throw "var in switch should be block scoped"
|
||||
break
|
||||
}
|
||||
if (x != 1) throw "var in switch should not affect outer scope"
|
||||
},
|
||||
|
||||
test_var_while_scope: function() {
|
||||
var x = 1
|
||||
var count = 0
|
||||
while (count < 1) {
|
||||
var x = 2
|
||||
if (x != 2) throw "var in while should be block scoped"
|
||||
count = count + 1
|
||||
}
|
||||
if (x != 1) throw "var in while should not affect outer scope"
|
||||
},
|
||||
|
||||
test_var_no_initialization: function() {
|
||||
{
|
||||
var x
|
||||
if (x != null) throw "uninitialized var should be null"
|
||||
}
|
||||
test_var_hoisting: function() {
|
||||
var result = x
|
||||
var x = 5
|
||||
if (result != null) throw "var hoisting should initialize to null"
|
||||
},
|
||||
|
||||
test_multiple_var_declaration: function() {
|
||||
@@ -947,6 +857,7 @@ return {
|
||||
test_typeof_object: function() {
|
||||
if (typeof {} != "object") throw "typeof object failed"
|
||||
if (typeof [] != "object") throw "typeof array failed"
|
||||
if (typeof null != "object") throw "typeof null failed"
|
||||
},
|
||||
|
||||
test_typeof_function: function() {
|
||||
@@ -986,6 +897,11 @@ return {
|
||||
if (isa(null, object)) throw "isa null not object failed"
|
||||
},
|
||||
|
||||
test_isa_fn: function() {
|
||||
if (!isa(function(){}, fn)) throw "isa function failed"
|
||||
if (isa({}, fn)) throw "isa object not function failed"
|
||||
},
|
||||
|
||||
test_isa_null: function() {
|
||||
if (isa(null, number)) throw "null not number"
|
||||
if (isa(null, text)) throw "null not text"
|
||||
@@ -1307,6 +1223,10 @@ return {
|
||||
if (!(1 == 1 || 2 == 3)) throw "equality before logical precedence failed"
|
||||
},
|
||||
|
||||
test_precedence_bitwise_comparison: function() {
|
||||
if (!(5 & 3 == 1)) throw "bitwise before comparison precedence failed"
|
||||
},
|
||||
|
||||
test_precedence_unary_multiplication: function() {
|
||||
if (-2 * 3 != -6) throw "unary before multiplication precedence failed"
|
||||
},
|
||||
@@ -1371,6 +1291,16 @@ return {
|
||||
// NULL AND UNDEFINED BEHAVIOR
|
||||
// ============================================================================
|
||||
|
||||
test_null_property_access_throws: function() {
|
||||
var caught = false
|
||||
try {
|
||||
var x = null.property
|
||||
} catch (e) {
|
||||
caught = true
|
||||
}
|
||||
if (!caught) throw "null property access should throw"
|
||||
},
|
||||
|
||||
test_undefined_variable_is_null: function() {
|
||||
var x
|
||||
if (x != null) throw "undefined variable should be null"
|
||||
@@ -1600,6 +1530,8 @@ return {
|
||||
if (!hasNumbers) throw "string match with regex failed"
|
||||
},
|
||||
|
||||
|
||||
=======
|
||||
test_string_plus_string_works: function() {
|
||||
var x = "hello" + " world"
|
||||
if (x != "hello world") throw "string + string should work"
|
||||
@@ -1610,4 +1542,5 @@ return {
|
||||
var nn = val.a
|
||||
if (nn != null) throw "val.a should return null"
|
||||
},
|
||||
>>>>>>> Stashed changes
|
||||
}
|
||||
|
||||
34
tests/utf8.cm
Normal file
34
tests/utf8.cm
Normal file
@@ -0,0 +1,34 @@
|
||||
var blob = use('blob');
|
||||
var utf8 = use('utf8');
|
||||
|
||||
return {
|
||||
test_blob_to_text: function() {
|
||||
// Test blob to text conversion
|
||||
var test_string = "Hello, 世界! 🌍";
|
||||
var encoded_blob = utf8.encode(test_string);
|
||||
var decoded_text = text(encoded_blob);
|
||||
if (test_string != decoded_text) throw "Blob to text failed"
|
||||
},
|
||||
|
||||
test_codepoints_to_text: function() {
|
||||
// Test array of codepoints conversion
|
||||
var test_string = "Hello, 世界! 🌍";
|
||||
var codepoints = [72, 101, 108, 108, 111, 44, 32, 19990, 30028, 33, 32, 127757];
|
||||
var from_codepoints = text(codepoints);
|
||||
if (from_codepoints != test_string) throw "Codepoints to text failed"
|
||||
},
|
||||
|
||||
test_array_separator: function() {
|
||||
// Test array with separator
|
||||
var words = ["Hello", "world", "from", "text"];
|
||||
var joined = text(words, " ");
|
||||
if (joined != "Hello world from text") throw "Array with separator failed"
|
||||
},
|
||||
|
||||
test_mixed_array: function() {
|
||||
// Test mixed array with codepoints
|
||||
var mixed = [72, "ello", 32, "world"];
|
||||
var mixed_result = text(mixed, "");
|
||||
if (mixed_result != "Hello world") throw "Mixed array test failed"
|
||||
},
|
||||
}
|
||||
10
time.cm
10
time.cm
@@ -178,16 +178,16 @@ function time_text(num = now(),
|
||||
|
||||
/* substitutions */
|
||||
var full_offset = zone + (dst ? 1 : 0);
|
||||
fmt = fmt.replaceAll("yyyy", text(year, "i4"))
|
||||
fmt = fmt.replaceAll("yyyy", year.toString().padStart(4, "0"));
|
||||
fmt = fmt.replaceAll("y", year);
|
||||
fmt = fmt.replaceAll("eee", rec.yday + 1);
|
||||
fmt = fmt.replaceAll("dd", text(rec.day, "i2"))
|
||||
fmt = fmt.replaceAll("dd", rec.day.toString().padStart(2, "0"));
|
||||
fmt = fmt.replaceAll("d", rec.day);
|
||||
fmt = fmt.replaceAll("hh", text(rec.hour, "i2"));
|
||||
fmt = fmt.replaceAll("hh", rec.hour.toString().padStart(2, "0"));
|
||||
fmt = fmt.replaceAll("h", rec.hour);
|
||||
fmt = fmt.replaceAll("nn", text(rec.minute, "i2"));
|
||||
fmt = fmt.replaceAll("nn", rec.minute.toString().padStart(2, "0"));
|
||||
fmt = fmt.replaceAll("n", rec.minute);
|
||||
fmt = fmt.replaceAll("ss", text(rec.second, "i2"));
|
||||
fmt = fmt.replaceAll("ss", rec.second.toFixed(2).padStart(2, "0"));
|
||||
fmt = fmt.replaceAll("s", rec.second);
|
||||
fmt = fmt.replaceAll("x", dst ? "DST" : ""); /* new */
|
||||
fmt = fmt.replaceAll("z", (full_offset >= 0 ? "+" : "") + text(full_offset));
|
||||
|
||||
210
utf8.c
Normal file
210
utf8.c
Normal file
@@ -0,0 +1,210 @@
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "kim.h"
|
||||
|
||||
// Get codepoints from a UTF-8 string
|
||||
JSC_CCALL(utf8_codepoints,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_EXCEPTION;
|
||||
|
||||
JSValue arr = JS_NewArray(js);
|
||||
int idx = 0;
|
||||
|
||||
char *ptr = (char*)str;
|
||||
while (*ptr) {
|
||||
int codepoint = decode_utf8(&ptr);
|
||||
JS_SetPropertyUint32(js, arr, idx++, JS_NewInt32(js, codepoint));
|
||||
}
|
||||
|
||||
JS_FreeCString(js, str);
|
||||
ret = arr;
|
||||
)
|
||||
|
||||
// Create UTF-8 string from codepoints
|
||||
JSC_CCALL(utf8_from_codepoints,
|
||||
int len = JS_ArrayLength(js, argv[0]);
|
||||
|
||||
// Allocate buffer (worst case: 4 bytes per codepoint + null)
|
||||
char *buffer = malloc(len * 4 + 1);
|
||||
char *ptr = buffer;
|
||||
|
||||
for (int i = 0; i < len; i++) {
|
||||
JSValue val = JS_GetPropertyUint32(js, argv[0], i);
|
||||
int codepoint;
|
||||
JS_ToInt32(js, &codepoint, val);
|
||||
JS_FreeValue(js, val);
|
||||
|
||||
encode_utf8(&ptr, codepoint);
|
||||
}
|
||||
|
||||
*ptr = '\0';
|
||||
ret = JS_NewString(js, buffer);
|
||||
free(buffer);
|
||||
)
|
||||
|
||||
// Count UTF-8 characters (runes) in a string
|
||||
JSC_SCALL(utf8_length,
|
||||
int count = utf8_count(str);
|
||||
ret = JS_NewInt32(js, count);
|
||||
)
|
||||
|
||||
// Validate UTF-8 string
|
||||
JSC_SCALL(utf8_validate,
|
||||
char *ptr = (char*)str;
|
||||
int valid = 1;
|
||||
|
||||
while (*ptr) {
|
||||
int start_pos = ptr - str;
|
||||
int codepoint = decode_utf8(&ptr);
|
||||
|
||||
// Check for invalid sequences
|
||||
if (codepoint < 0 || codepoint > 0x10FFFF ||
|
||||
(codepoint >= 0xD800 && codepoint <= 0xDFFF)) {
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check for overlong encodings
|
||||
int bytes_used = ptr - (str + start_pos);
|
||||
if ((codepoint <= 0x7F && bytes_used != 1) ||
|
||||
(codepoint <= 0x7FF && bytes_used != 2) ||
|
||||
(codepoint <= 0xFFFF && bytes_used != 3) ||
|
||||
(codepoint <= 0x10FFFF && bytes_used != 4)) {
|
||||
valid = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret = JS_NewBool(js, valid);
|
||||
)
|
||||
|
||||
// Get byte length of UTF-8 string
|
||||
JSC_SCALL(utf8_byte_length,
|
||||
ret = JS_NewInt32(js, strlen(str));
|
||||
)
|
||||
|
||||
// Encode string to UTF-8 bytes
|
||||
JSC_SCALL(utf8_encode,
|
||||
size_t len = strlen(str);
|
||||
ret = js_new_blob_stoned_copy(js, str, len);
|
||||
)
|
||||
|
||||
// Decode UTF-8 bytes to string
|
||||
JSC_CCALL(utf8_decode,
|
||||
size_t len;
|
||||
void *data = js_get_blob_data(js, &len, argv[0]);
|
||||
if (data == (void*)-1) return JS_EXCEPTION;
|
||||
if (!data || len == 0) return JS_ThrowTypeError(js, "No data present in blob");
|
||||
|
||||
// Create null-terminated string
|
||||
char *str = malloc(len + 1);
|
||||
memcpy(str, data, len);
|
||||
str[len] = '\0';
|
||||
|
||||
ret = JS_NewString(js, str);
|
||||
free(str);
|
||||
)
|
||||
|
||||
// Slice UTF-8 string by character indices (not byte indices)
|
||||
JSC_CCALL(utf8_slice,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_EXCEPTION;
|
||||
|
||||
int start = 0;
|
||||
int end = utf8_count(str);
|
||||
|
||||
if (argc > 1) JS_ToInt32(js, &start, argv[1]);
|
||||
if (argc > 2) JS_ToInt32(js, &end, argv[2]);
|
||||
|
||||
// Handle negative indices
|
||||
int total = end;
|
||||
if (start < 0) start = total + start;
|
||||
if (end < 0) end = total + end;
|
||||
|
||||
// Clamp values
|
||||
if (start < 0) start = 0;
|
||||
if (end > total) end = total;
|
||||
if (start >= end) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_NewString(js, "");
|
||||
}
|
||||
|
||||
// Find start position
|
||||
char *ptr = (char*)str;
|
||||
for (int i = 0; i < start && *ptr; i++) {
|
||||
decode_utf8(&ptr);
|
||||
}
|
||||
char *start_ptr = ptr;
|
||||
|
||||
// Find end position
|
||||
for (int i = start; i < end && *ptr; i++) {
|
||||
decode_utf8(&ptr);
|
||||
}
|
||||
|
||||
// Create substring
|
||||
size_t slice_len = ptr - start_ptr;
|
||||
char *slice = malloc(slice_len + 1);
|
||||
memcpy(slice, start_ptr, slice_len);
|
||||
slice[slice_len] = '\0';
|
||||
|
||||
ret = JS_NewString(js, slice);
|
||||
free(slice);
|
||||
JS_FreeCString(js, str);
|
||||
)
|
||||
|
||||
// Get character at index
|
||||
JSC_CCALL(utf8_char_at,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_EXCEPTION;
|
||||
|
||||
int index;
|
||||
JS_ToInt32(js, &index, argv[1]);
|
||||
|
||||
char *ptr = (char*)str;
|
||||
int count = 0;
|
||||
|
||||
// Skip to index
|
||||
while (*ptr && count < index) {
|
||||
decode_utf8(&ptr);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (!*ptr || count != index) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// Get the character
|
||||
char *char_start = ptr;
|
||||
decode_utf8(&ptr);
|
||||
|
||||
size_t char_len = ptr - char_start;
|
||||
char *result = malloc(char_len + 1);
|
||||
memcpy(result, char_start, char_len);
|
||||
result[char_len] = '\0';
|
||||
|
||||
ret = JS_NewString(js, result);
|
||||
free(result);
|
||||
JS_FreeCString(js, str);
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_utf8_funcs[] = {
|
||||
MIST_FUNC_DEF(utf8, codepoints, 1),
|
||||
MIST_FUNC_DEF(utf8, from_codepoints, 1),
|
||||
MIST_FUNC_DEF(utf8, length, 1),
|
||||
MIST_FUNC_DEF(utf8, validate, 1),
|
||||
MIST_FUNC_DEF(utf8, byte_length, 1),
|
||||
MIST_FUNC_DEF(utf8, encode, 1),
|
||||
MIST_FUNC_DEF(utf8, decode, 1),
|
||||
MIST_FUNC_DEF(utf8, slice, 3),
|
||||
MIST_FUNC_DEF(utf8, char_at, 2),
|
||||
};
|
||||
|
||||
JSValue js_utf8_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_utf8_funcs, countof(js_utf8_funcs));
|
||||
return mod;
|
||||
}
|
||||
Reference in New Issue
Block a user