Merge branch 'master' into cell_lsp

This commit is contained in:
2026-02-17 01:20:11 -06:00
11 changed files with 119 additions and 32 deletions

View File

@@ -108,7 +108,8 @@ Build.compile_file = function(pkg, file, target, buildtype) {
var core_dir = null
if (!fd.is_file(src_path)) {
print('Source file not found: ' + src_path); disrupt
print('Source file not found: ' + src_path)
return null
}
// Get flags (with sigil replacement)
@@ -177,9 +178,10 @@ Build.compile_file = function(pkg, file, target, buildtype) {
log.console('Compiling ' + file)
var ret = os.system(full_cmd)
if (ret != 0) {
print('Compilation failed: ' + file); disrupt
print('Compilation failed: ' + file)
return null
}
return obj_path
}
@@ -234,6 +236,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
var _buildtype = _opts.buildtype || 'release'
var _extra = _opts.extra_objects || []
var obj = Build.compile_file(pkg, file, _target, _buildtype)
if (!obj) return null
var tc = toolchains[_target]
var dylib_ext = tc.system == 'windows' ? '.dll' : (tc.system == 'darwin' ? '.dylib' : '.so')
@@ -299,7 +302,8 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
ret = os.system(cmd_str)
if (ret != 0) {
print('Linking failed: ' + file); disrupt
print('Linking failed: ' + file)
return null
}
}
@@ -340,7 +344,9 @@ Build.build_dynamic = function(pkg, target, buildtype) {
arrfor(c_files, function(file) {
var sym_name = shop.c_symbol_for_file(pkg, file)
var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects})
push(results, {file: file, symbol: sym_name, dylib: dylib})
if (dylib) {
push(results, {file: file, symbol: sym_name, dylib: dylib})
}
})
return results

View File

@@ -3,4 +3,4 @@ cell-steam = "/Users/johnalanbrook/work/cell-steam"
cell-sdl3 = "/Users/johnalanbrook/work/cell-sdl3"
[compilation]
[compilation.playdate]
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$LOCAL/PlaydateSDK/C_API"
CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$LOCAL/PlaydateSDK/C_API"

View File

@@ -187,9 +187,11 @@ JSC_CCALL(vector_normalize,
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JSValue result = JS_NewObject(js);
JS_FRAME(js);
JS_LOCAL(result, JS_NewObject(js));
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
ret = result;
}
)
@@ -258,6 +260,81 @@ mypackage/
The module file (`rtree.c`) includes the library header and uses `cell.h` as usual. The support files are plain C — they don't need any cell macros.
## GC Safety
ƿit uses a Cheney copying garbage collector. Any JS allocation — `JS_NewObject`, `JS_NewString`, `JS_SetPropertyStr`, etc. — can trigger GC, which **moves** heap objects to new addresses. C locals holding JSValue become stale after any allocating call.
### When you need rooting
If a function creates **one** heap object and returns it immediately, no rooting is needed:
```c
JSC_CCALL(mymod_name,
ret = JS_NewString(js, "hello");
)
```
If a function holds a heap object across further allocating calls, you must root it:
```c
JSC_CCALL(vector_normalize,
double x = js2number(js, argv[0]);
double y = js2number(js, argv[1]);
double len = sqrt(x*x + y*y);
if (len > 0) {
JS_FRAME(js);
JS_LOCAL(result, JS_NewObject(js));
// result is rooted — GC can update it through these calls:
JS_SetPropertyStr(js, result, "x", number2js(js, x/len));
JS_SetPropertyStr(js, result, "y", number2js(js, y/len));
JS_RestoreFrame(_js_ctx, _js_gc_frame, _js_local_frame);
ret = result;
}
)
```
### Macros
| Macro | Purpose |
|-------|---------|
| `JS_FRAME(js)` | Save the GC and local frames. Required before any `JS_LOCAL`. |
| `JS_LOCAL(name, init)` | Declare and root a JSValue. GC updates it through its address. |
| `JS_RETURN(val)` | Restore the frame and return a value. |
| `JS_RETURN_NULL()` | Restore the frame and return `JS_NULL`. |
| `JS_RETURN_EX()` | Restore the frame and return `JS_EXCEPTION`. |
| `JS_RestoreFrame(...)` | Manual frame restore (for `JSC_CCALL` bodies that use `ret =`). |
### Rules of thumb
1. **One allocation, immediate return** — no rooting needed.
2. **Object + property sets** — root the object with `JS_LOCAL`.
3. **Array + loop** — root the array; if loop body creates objects, root the loop variable too with a manual `JSLocalRef`.
4. **`CELL_USE_INIT` bodies** — always use `JS_FRAME` / `JS_LOCAL` / `JS_RETURN`.
### Migrating from gc_mark
The old mark-and-sweep GC had a `gc_mark` callback in `JSClassDef` for C structs that held JSValue fields. This no longer exists. The copying GC needs to know the **address** of every pointer to update it when objects move.
If your C struct holds a JSValue that must survive across GC points, root it for the duration it's alive:
```c
typedef struct {
JSValue callback;
JSLocalRef callback_lr;
} MyWidget;
// When storing:
widget->callback = value;
widget->callback_lr.ptr = &widget->callback;
JS_PushLocalRef(js, &widget->callback_lr);
// When done (before freeing the struct):
// The local ref is cleaned up when the frame is restored,
// or manage it manually.
```
In practice, most C wrappers hold only opaque C pointers (like `SDL_Window*`) and never store JSValues in the struct — these need no migration.
## Static Declarations
Keep internal functions and variables `static`:

View File

@@ -248,7 +248,7 @@ is_integer(42) // true
is_logical(true) // true
is_null(null) // true
is_number(3.14) // true
is_object({}) // true
is_object({}) // true (records only)
is_text("hello") // true
```

View File

@@ -512,12 +512,13 @@ var a = nil?(null) ? "yes" : "no" // "yes"
is_number(42) // true
is_text("hi") // true
is_logical(true) // true
is_object({}) // true
is_object({}) // true (records only)
is_array([]) // true
is_function(function(){}) // true
is_null(null) // true
is_object([]) // false (array is not object)
is_array({}) // false (object is not array)
is_object([]) // false (arrays are not records)
is_object("hello") // false (text is not a record)
is_array({}) // false (records are not arrays)
```
### Truthiness

View File

@@ -1,15 +1,11 @@
var package = {}
var fd = use('fd')
var toml = use('toml')
var json = use('json')
var runtime = use('runtime')
var link = use('link')
var global_shop_path = runtime.shop_path
// Cache for loaded configs to avoid toml re-parsing corruption
var config_cache = {}
// Convert package name to a safe directory name
// For absolute paths (local packages), replace / with _
// For remote packages, keep slashes as they use nested directories
@@ -48,10 +44,6 @@ package.load_config = function(name)
{
var config_path = get_path(name) + '/cell.toml'
// Return cached config if available
if (config_cache[config_path])
return config_cache[config_path]
if (!fd.is_file(config_path)) {
print(`${config_path} does not exist`); disrupt
}
@@ -65,10 +57,6 @@ package.load_config = function(name)
return {}
}
// Deep copy to avoid toml module's shared state bug and cache it
result = json.decode(json.encode(result))
config_cache[config_path] = result
return result
}

View File

@@ -100,7 +100,6 @@ TYPE##_free(rt,n);}\
static JSClassDef js_##TYPE##_class = {\
.class_name = #TYPE,\
.finalizer = js_##TYPE##_finalizer,\
.gc_mark = js_##TYPE##_mark,\
};\
TYPE *js2##TYPE (JSContext *js, JSValue val) { \
if (JS_GetClassID(val) != js_##TYPE##_id) return NULL; \
@@ -120,7 +119,6 @@ TYPE##_free(rt,n);}\
static JSClassDef js_##TYPE##_class = {\
.class_name = #TYPE,\
.finalizer = js_##TYPE##_finalizer,\
.gc_mark = js_##TYPE##_mark,\
};\
extern JSClassID js_##TYPE##_id;\
TYPE *js2##TYPE (JSContext *js, JSValue val) { \
@@ -231,9 +229,10 @@ JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }
#define CELL_USE_FUNCS(FUNCS) \
JSValue CELL_USE_NAME(JSContext *js) { \
JSValue mod = JS_NewObject(js); \
JS_FRAME(js); \
JS_LOCAL(mod, JS_NewObject(js)); \
JS_SetPropertyFunctionList(js, mod, FUNCS, countof(FUNCS)); \
return mod; }
JS_RETURN(mod); }
#define CELL_PROGRAM_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }

View File

@@ -10872,13 +10872,10 @@ static JSValue js_cell_is_number (JSContext *ctx, JSValue this_val, int argc, JS
return JS_NewBool (ctx, JS_IsNumber (argv[0]));
}
/* is_object(val) - true for non-array, non-null objects */
/* is_object(val) - true for records */
static JSValue js_cell_is_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1) return JS_FALSE;
JSValue val = argv[0];
if (!mist_is_gc_object (val)) return JS_FALSE;
if (JS_IsArray (val)) return JS_FALSE;
return JS_TRUE;
return JS_NewBool (ctx, mist_is_record (argv[0]));
}
/* is_stone(val) - check if value is immutable */

View File

@@ -41,6 +41,14 @@ sections:
url: "/docs/shop/"
- title: "CLI"
url: "/docs/cli/"
- title: "Tools"
pages:
- title: "Testing"
url: "/docs/testing/"
- title: "Compiler Inspection"
url: "/docs/compiler-tools/"
- title: "Semantic Index"
url: "/docs/semantic-index/"
- title: "C API"
pages:
- title: "C Modules"

View File

@@ -35,3 +35,6 @@ sections:
- title: "Wota Format"
page: "/docs/wota/"
id: "wota"
- title: "C Runtime"
page: "/docs/spec/c-runtime/"
id: "c-runtime"

View File

@@ -15,3 +15,11 @@ sections:
page: "/docs/testing/"
id: "testing"
group: "CLI"
- title: "Compiler Inspection"
page: "/docs/compiler-tools/"
id: "compiler-tools"
group: "Tools"
- title: "Semantic Index"
page: "/docs/semantic-index/"
id: "semantic-index"
group: "Tools"