From 1c2b8228fe4e4a5540565d9100d4ea9ef2c3ac92 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 10 Jun 2025 04:33:15 -0500 Subject: [PATCH] add num --- Makefile | 2 +- meson.build | 13 +- prosperon/{sprite.cm => _sprite.cm} | 0 prosperon/draw2d.cm | 50 +-- prosperon/graphics.cm | 8 +- prosperon/prosperon.ce | 304 +++++++++----- scripts/engine.cm | 6 + source/cell.c | 7 +- source/jsffi.c | 6 +- source/qjs_enet.c | 19 - source/qjs_imgui.cpp | 20 - source/qjs_miniz.c | 22 +- source/qjs_nota.c | 18 - source/qjs_num.c | 607 ++++++++++++++++++++++++++++ source/qjs_qr.c | 21 - source/qjs_soloud.c | 19 - source/qjs_wota.c | 20 - tests/num_property_test.ce | 23 ++ tests/num_setter_test.ce | 32 ++ tests/num_test.ce | 54 +++ 20 files changed, 957 insertions(+), 294 deletions(-) rename prosperon/{sprite.cm => _sprite.cm} (100%) create mode 100644 source/qjs_num.c create mode 100644 tests/num_property_test.ce create mode 100644 tests/num_setter_test.ce create mode 100644 tests/num_test.ce diff --git a/Makefile b/Makefile index 610947c2..883cf6f6 100755 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ debug: FORCE - meson setup build_dbg -Dbuildtype=debug + meson setup build_dbg -Dbuildtype=debugoptimized meson install --only-changed -C build_dbg fast: FORCE diff --git a/meson.build b/meson.build index 7c7ee208..60f391db 100644 --- a/meson.build +++ b/meson.build @@ -115,11 +115,20 @@ endif if host_machine.system() == 'linux' deps += cc.find_library('asound', required:true) deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')] + deps += cc.find_library('blas', required:true) + deps += cc.find_library('lapack', required:true) endif if host_machine.system() == 'windows' deps += cc.find_library('d3d11') deps += cc.find_library('ws2_32', required:true) + # For Windows, you may need to install OpenBLAS or Intel MKL + # and adjust these library names accordingly + deps += cc.find_library('openblas', required:false) + if not cc.find_library('openblas', required:false).found() + deps += cc.find_library('blas', required:false) + deps += cc.find_library('lapack', required:false) + endif deps += cc.find_library('dbghelp') deps += cc.find_library('winmm') deps += cc.find_library('setupapi') @@ -225,7 +234,7 @@ if host_machine.system() != 'emscripten' endif src += 'qjs_enet.c' - tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true'] + tracy_opts = ['fibers=true', 'no_exit=true', 'libunwind_backtrace=true', 'on_demand=true'] add_project_arguments('-DTRACY_ENABLE', language:['c','cpp']) # Try to find system-installed tracy first @@ -295,7 +304,7 @@ src += [ 'anim.c', 'config.c', 'datastream.c','font.c','HandmadeMath.c','jsffi.c','model.c', 'render.c','simplex.c','spline.c', 'transform.c','cell.c', 'wildmatch.c', 'sprite.c', 'rtree.c', 'qjs_nota.c', 'qjs_soloud.c', 'qjs_sdl.c', 'qjs_sdl_input.c', 'qjs_sdl_video.c', 'qjs_sdl_surface.c', 'qjs_math.c', 'qjs_geometry.c', 'qjs_transform.c', 'qjs_sprite.c', 'qjs_io.c', 'qjs_fd.c', 'qjs_os.c', 'qjs_actor.c', - 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c' + 'qjs_qr.c', 'qjs_wota.c', 'monocypher.c', 'qjs_blob.c', 'qjs_crypto.c', 'qjs_time.c', 'qjs_http.c', 'qjs_rtree.c', 'qjs_spline.c', 'qjs_js.c', 'qjs_debug.c', 'picohttpparser.c', 'qjs_miniz.c', 'qjs_num.c', 'timer.c', 'qjs_socket.c', 'qjs_kim.c', 'qjs_utf8.c', 'qjs_fit.c', 'qjs_text.c' ] # quirc src src += [ diff --git a/prosperon/sprite.cm b/prosperon/_sprite.cm similarity index 100% rename from prosperon/sprite.cm rename to prosperon/_sprite.cm diff --git a/prosperon/draw2d.cm b/prosperon/draw2d.cm index a1b8fec5..7f8d40b7 100644 --- a/prosperon/draw2d.cm +++ b/prosperon/draw2d.cm @@ -8,33 +8,6 @@ These are pure functions that return plain JavaScript objects representing drawing operations. No rendering or actor communication happens here. ` -// Create a new command list -draw.list = function() { - var commands = [] - - return { - // Add a command to this list - push: function(cmd) { - commands.push(cmd) - }, - - // Get all commands - get: function() { - return commands - }, - - // Clear all commands - clear: function() { - commands = [] - }, - - // Get command count - length: function() { - return commands.length - } - } -} - // Default command list for convenience var current_list = draw.list() @@ -50,18 +23,17 @@ draw.get_list = function() { // Clear current list draw.clear = function() { - current_list.clear() + current_list.length = 0 } // Get commands from current list draw.get_commands = function() { - return current_list.get() + return current_list } // Helper to add a command function add_command(type, data) { - var cmd = {cmd: type} - Object.assign(cmd, data) + data.cmd = type current_list.push(cmd) } @@ -190,16 +162,14 @@ draw.image = function image(image, rect, rotation = 0, anchor = [0,0], shear = [ if (!('x' in rect && 'y' in rect)) throw Error('Must provide X and Y for image.') - info = Object.assign({}, image_info, info); - add_command("draw_image", { - image: image, - rect: rect, - rotation: rotation, - anchor: anchor, - shear: shear, - info: info, - material: material + image, + rect, + rotation, + anchor, + shear, + info, + material, }) } diff --git a/prosperon/graphics.cm b/prosperon/graphics.cm index a49ed2e7..e2aa1db8 100644 --- a/prosperon/graphics.cm +++ b/prosperon/graphics.cm @@ -27,14 +27,13 @@ graphics.Image = function(surfaceData) { this[GPU] = undefined; this[LOADING] = false; this[LASTUSE] = time.number(); - this.rect = {x:0, y:0, width:1, height:1}; + this.rect = {x:0, y:0, width:surfaceData.width, height:surfaceData.height}; } // Define getters and methods on the prototype Object.defineProperties(graphics.Image.prototype, { gpu: { get: function() { - this[LASTUSE] = time.number(); if (!this[GPU] && !this[LOADING]) { this[LOADING] = true; var self = this; @@ -67,9 +66,6 @@ Object.defineProperties(graphics.Image.prototype, { cpu: { get: function() { - this[LASTUSE] = time.number(); - // Note: Reading texture back from GPU requires async operation - // For now, return the CPU data if available return this[CPU] } }, @@ -253,7 +249,7 @@ graphics.texture = function texture(path) { if (typeof path !== 'string') throw new Error('need a string for graphics.texture') - var id = path.split(':')[0] + var id = path //.split(':')[0] if (cache.has(id)) return cache.get(id) var ipath = res.find_image(id) diff --git a/prosperon/prosperon.ce b/prosperon/prosperon.ce index f0dfa57f..babd813c 100644 --- a/prosperon/prosperon.ce +++ b/prosperon/prosperon.ce @@ -3,11 +3,21 @@ var io = use('io'); var transform = use('transform'); var rasterize = use('rasterize'); var time = use('time') +var num = use('num'); + +// Frame timing variables +var frame_times = [] +var frame_time_index = 0 +var max_frame_samples = 60 +var frame_start_time = 0 +var average_frame_time = 0 var game = args[0] var video +var cnf = use('accio/config') + $_.start(e => { if (e.type !== 'greet') return video = e.actor @@ -17,73 +27,83 @@ $_.start(e => { if (gameactor) return gameactor = e.actor $_.couple(gameactor) - loop() - }, args[0]) + start_pipeline() + }, args[0], $_) }) -}, 'prosperon/sdl_video', { - title: "Prosperon", - width:500, - height:500 -}) +}, 'prosperon/sdl_video', cnf) -log.console('starting...') var input = use('input') var geometry = use('geometry') -function worldToScreenRect({x,y,width,height}, camera, winW, winH) { - var bl = worldToScreenPoint([x,y], camera, winW, winH) - var tr = worldToScreenPoint([x+width, y+height], camera, winW, winH) - +function updateCameraMatrix(camera, winW, winH) { + // world→NDC + const sx = 1 / camera.size[0]; + const sy = 1 / camera.size[1]; + const ox = camera.pos[0] - camera.size[0] * camera.anchor[0]; + const oy = camera.pos[1] - camera.size[1] * camera.anchor[1]; + + // NDC→pixels + const vx = camera.viewport.x * winW; + const vy = camera.viewport.y * winH; + const vw = camera.viewport.width * winW; + const vh = camera.viewport.height * winH; + + // final “mat” coefficients + // [ a 0 c ] + // [ 0 e f ] + // [ 0 0 1 ] + camera.a = sx * vw; + camera.c = vx - camera.a * ox; + camera.e = -sy * vh; + camera.f = vy + vh + sy * vh * oy; + + // and store the inverses so we can go back cheaply + camera.ia = 1 / camera.a; + camera.ic = -camera.c * camera.ia; + camera.ie = 1 / camera.e; + camera.if = -camera.f * camera.ie; +} + +//---- forward transform ---- +function worldToScreenPoint(pos, camera) { return { - x: Math.min(bl.x, tr.x), - y: Math.min(bl.y, tr.y), - width: Math.abs(tr.x - bl.x), - height: Math.abs(tr.y - bl.y) - } + x: camera.a * pos[0] + camera.c, + y: camera.e * pos[1] + camera.f + }; } -function worldToScreenPoint([wx, wy], camera, winW, winH) { - // 1) world‐window origin (bottom‐left) - const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0]; - const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1]; - - // 2) normalized device coords [0..1] - const ndcX = (wx - worldX0) / camera.size[0]; - const ndcY = (wy - worldY0) / camera.size[1]; - - // 3) map into pixel‐space via the fractional viewport - const px = camera.viewport.x * winW - + ndcX * (camera.viewport.width * winW); - const py = camera.viewport.y * winH - + (1 - ndcY) * (camera.viewport.height * winH); - - return [ px, py ]; +//---- inverse transform ---- +function screenToWorldPoint(pos, camera) { + return { + x: camera.ia * pos[0] + camera.ic, + y: camera.ie * pos[1] + camera.if + }; } -function screenToWorldPoint([px, py], camera, winW, winH) { - // 1) undo pixel→NDC within the camera’s viewport - const ndcX = (px - camera.viewport.x * winW) - / (camera.viewport.width * winW) - const ndcY = 1 - (py - camera.viewport.y * winH) - / (camera.viewport.height * winH) +//---- rectangle (two corner) ---- +function worldToScreenRect(rect, camera) { + // map bottom-left and top-right + const x1 = camera.a * rect.x + camera.c; + const y1 = camera.e * rect.y + camera.f; + const x2 = camera.a * (rect.x + rect.width) + camera.c; + const y2 = camera.e * (rect.y + rect.height) + camera.f; - // 2) compute the world‐window origin (bottom‐left) - const worldX0 = camera.pos[0] - - camera.size[0] * camera.anchor[0] - const worldY0 = camera.pos[1] - - camera.size[1] * camera.anchor[1] - - // 3) map NDC back to world coords - return [ - ndcX * camera.size[0] + worldX0, - ndcY * camera.size[1] + worldY0 - ] + // pick mins and abs deltas + const x0 = x1 < x2 ? x1 : x2; + const y0 = y1 < y2 ? y1 : y2; + return { + x: x0, + y: y0, + width: x2 > x1 ? x2 - x1 : x1 - x2, + height: y2 > y1 ? y2 - y1 : y1 - y2 + }; } + var camera = { - size: [500,500],//{width:500,height:500}, // pixel size the camera "sees", like its resolution + size: [640,320],//{width:500,height:500}, // pixel size the camera "sees", like its resolution pos: [250,250],//{x:0,y:0}, // where it is fov:50, near_z:0, @@ -104,10 +124,15 @@ var gameactor var images = {} +var renderer_commands = [] + // Convert high-level draw commands to low-level renderer commands function translate_draw_commands(commands) { if (!graphics) return - var renderer_commands = [] + + updateCameraMatrix(camera,500,500) + + renderer_commands.length = 0 commands.forEach(function(cmd) { if (cmd.material && cmd.material.color) { @@ -201,7 +226,8 @@ function translate_draw_commands(commands) { case "draw_image": var img = graphics.texture(cmd.image) - if (!img.gpu) break + var gpu = img.gpu + if (!gpu) break cmd.rect.width ??= img.width cmd.rect.height ??= img.height @@ -210,9 +236,9 @@ function translate_draw_commands(commands) { renderer_commands.push({ op: "texture", data: { - texture_id: img.gpu.id, + texture_id: gpu.id, dst: cmd.rect, - src: {x:0,y:0,width:img.width,height:img.height}, + src: img.rect } }) break @@ -236,60 +262,128 @@ function translate_draw_commands(commands) { return renderer_commands } -function loop(time) -{ - send(video, {kind:'input', op:'get'}, e => { - for (var event of e) { - if (event.type === 'quit') - $_.stop() - } - }) - - send(gameactor, {kind:'update', dt:1/60}, e => { - send(gameactor, {kind:'draw'}, draw_commands => { - var batch_commands = [] - - batch_commands.push({ - op: "set", - prop: "drawColor", - value: [0.1,0.1,0.15,1] - }) - - // Clear the screen - batch_commands.push({ - op: "clear" - }) - - if (draw_commands && draw_commands.length > 0) { - var renderer_commands = translate_draw_commands(draw_commands) - batch_commands = batch_commands.concat(renderer_commands) - } - - batch_commands.push({ - op: "present" - }) - - send(video, { - kind: "renderer", - op: "batch", - data: batch_commands - }, _ => { - }) +var parseq = use('parseq', $_.delay) + +// Wrap `send(actor,msg,cb)` into a parseq “requestor” +// • on success: cb(data) → value=data, reason=undefined +// • on failure: cb(undefined,err) +function rpc_req(actor, msg) { + return (cb, _) => { + send(actor, msg, data => { + if (data.error) + cb(undefined, data) + else + cb(data) }) - }) - - $_.delay(loop, 1/30) + } } +var game_rec = parseq.sequence([ + rpc_req(gameactor, {kind:'update', dt:1/60}), + rpc_req(gameactor, {kind:'draw'}) +]) + +var pending_draw = null +var pending_next = null +var last_time = time.number() +var frames = [] +var frame_avg = 0 + +// 1) input runs completely independently +function poll_input() { + send(video, {kind:'input', op:'get'}, evs => { + for (let ev of evs) if (ev.type === 'quit') $_.stop() + }) + $_.delay(poll_input, 1/60) +} + +// 2) helper to build & send a batch, then call done() +function create_batch(draw_cmds, done) { + const batch = [ + {op:'set', prop:'drawColor', value:[0.1,0.1,0.15,1]}, + {op:'clear'} + ] + if (draw_cmds && draw_cmds.length) + batch.push(...translate_draw_commands(draw_cmds)) + + batch.push( + {op:'debugText', data:{pos:{x:10,y:10}, text:`Frame: ${frame_avg.toFixed(2)}ms`}}, + {op:'present'} + ) + + send(video, {kind:'renderer', op:'batch', data:batch}, () => { + // update FPS + const now = time.number() + const dt = now - last_time + last_time = now + + frames.push(dt) + if (frames.length > 60) frames.shift() + let sum = 0 + for (let f of frames) sum += f + frame_avg = sum / frames.length * 1000 + + done(dt) + }) +} + +// 3) kick off the very first update→draw +function start_pipeline() { + poll_input() + send(gameactor, {kind:'update', dt:1/60}, () => { + send(gameactor, {kind:'draw'}, cmds => { + pending_draw = cmds + render_step() + }) + }) +} + +function render_step() { + // a) fire off the next update→draw immediately + const dt = time.number() - last_time + send(gameactor, {kind:'update', dt}, () => + send(gameactor, {kind:'draw'}, cmds => pending_next = cmds) + ) + + // c) render the current frame + create_batch(pending_draw, ttr => { // time to render + // only swap in when there's a new set of commands + if (pending_next) { + pending_draw = pending_next + pending_next = null + } + + // d) schedule the next render step + const render_dur = time.number() - last_time + const wait = Math.max(0, 1/60 - ttr) + $_.delay(render_step, 0) + }) +} + + $_.receiver(e => { - if (e.type === 'quit') - $_.stop() - - if (e.type.includes('mouse')) { - if (e.pos) - e.pos = screenToWorldPoint(e.pos, camera, 500, 500) + if (e.type) { + if (e.type === 'quit') + $_.stop() - if (e.d_pos) - e.d_pos.y *= -1 + if (e.type.includes('mouse')) { + if (e.pos) + e.pos = screenToWorldPoint(e.pos, camera, 500, 500) + + if (e.d_pos) + e.d_pos.y *= -1 + } + } + + switch(e.op) { + case 'resolution': + log.console(json.encode(e)) + send(video, { + kind:'renderer', + op:'set', + prop:'logicalPresentation', + value: {...e} + }) + break } }) diff --git a/scripts/engine.cm b/scripts/engine.cm index ac33aff1..23e61b54 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -82,6 +82,10 @@ var use_dyn = hidden.use_dyn var enet = hidden.enet var nota = hidden.nota +// Wota decode timing tracking +var wota_decode_times = [] +var last_wota_flush = 0 + // Strip hidden from cell so nothing else can access it delete cell.hidden @@ -831,4 +835,6 @@ $_.clock(_ => { throw new Error('Program must not return anything'); }) +log.console(`startup took ${time.number()-st_now}`) + })() \ No newline at end of file diff --git a/source/cell.c b/source/cell.c index adaf5bf4..1f6e670c 100644 --- a/source/cell.c +++ b/source/cell.c @@ -515,8 +515,11 @@ void actor_turn(cell_rt *actor) SDL_LockMutex(actor->mutex); #ifdef TRACY_ENABLE - if (tracy_profiling_enabled) + int entered = 0; + if (tracy_profiling_enabled && TracyCIsConnected) { TracyCFiberEnter(actor->name); + entered = 1; + } #endif actor->state = ACTOR_RUNNING; @@ -560,7 +563,7 @@ void actor_turn(cell_rt *actor) actor->state = ACTOR_IDLE; #ifdef TRACY_ENABLE - if (tracy_profiling_enabled) + if (tracy_profiling_enabled && entered) TracyCFiberLeave(actor->name); #endif diff --git a/source/jsffi.c b/source/jsffi.c index b48ec4c1..034ab555 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -1532,6 +1532,7 @@ JS_SetPrototype(js, js_##NAME, PARENT); \ JSValue js_layout_use(JSContext *js); JSValue js_miniz_use(JSContext *js); +JSValue js_num_use(JSContext *js); JSValue js_graphics_use(JSContext *js) { JSValue mod = JS_NewObject(js); @@ -1578,7 +1579,9 @@ void ffi_load(JSContext *js) JS_FreeValue(js, js_blob_use(js)); // juice blob - m_seedRand(&rt->mrand, time(NULL)); + uint64_t rr; + randombytes(&rr,4); + m_seedRand(&rt->mrand, rr); // cell modules arrput(rt->module_registry, MISTLINE(time)); @@ -1594,6 +1597,7 @@ void ffi_load(JSContext *js) arrput(rt->module_registry, MISTLINE(http)); arrput(rt->module_registry, MISTLINE(crypto)); arrput(rt->module_registry, MISTLINE(miniz)); + arrput(rt->module_registry, MISTLINE(num)); arrput(rt->module_registry, MISTLINE(kim)); arrput(rt->module_registry, MISTLINE(utf8)); arrput(rt->module_registry, MISTLINE(fit)); diff --git a/source/qjs_enet.c b/source/qjs_enet.c index af4c5ebf..640b6abf 100644 --- a/source/qjs_enet.c +++ b/source/qjs_enet.c @@ -546,22 +546,3 @@ JSValue js_enet_use(JSContext *ctx) JS_SetPropertyFunctionList(ctx, export_obj, js_enet_funcs, countof(js_enet_funcs)); return export_obj; } - -static int js_enet_init(JSContext *ctx, JSModuleDef *m) -{ - return JS_SetModuleExport(ctx, m, "default", js_enet_use(ctx)); -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_enet -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_enet_init); - if (!m) return NULL; - JS_AddModuleExport(ctx, m, "default"); - return m; -} diff --git a/source/qjs_imgui.cpp b/source/qjs_imgui.cpp index abab5dc8..93059ff9 100644 --- a/source/qjs_imgui.cpp +++ b/source/qjs_imgui.cpp @@ -882,23 +882,3 @@ JSValue js_imgui_use(JSContext *js) return imgui; } } - -static int js_init_imgui(JSContext *js, JSModuleDef *m) { - JS_SetModuleExportList(js, m, js_imgui_funcs, sizeof(js_imgui_funcs)/sizeof(JSCFunctionListEntry)); - JS_SetModuleExport(js, m, "default", js_imgui_use(js)); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_imgui -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *name) { - JSModuleDef *m; - m = JS_NewCModule(js, name, js_init_imgui); - if (!m) return NULL; - JS_AddModuleExport(js, m, "default"); - return m; -} diff --git a/source/qjs_miniz.c b/source/qjs_miniz.c index 2cc73c85..83452169 100644 --- a/source/qjs_miniz.c +++ b/source/qjs_miniz.c @@ -30,12 +30,12 @@ static JSClassDef js_writer_class = { static mz_zip_archive *js2reader(JSContext *js, JSValue v) { - return JS_GetOpaque2(js, v, js_reader_class_id); + return JS_GetOpaque(v, js_reader_class_id); } static mz_zip_archive *js2writer(JSContext *js, JSValue v) { - return JS_GetOpaque2(js, v, js_writer_class_id); + return JS_GetOpaque(v, js_writer_class_id); } static JSValue js_miniz_read(JSContext *js, JSValue self, int argc, JSValue *argv) @@ -397,21 +397,3 @@ JSValue js_miniz_use(JSContext *js) JS_SetPropertyFunctionList(js, export, js_miniz_funcs, sizeof(js_miniz_funcs)/sizeof(JSCFunctionListEntry)); return export; } - -static int js_miniz_init(JSContext *ctx, JSModuleDef *m) { - JS_SetModuleExport(ctx, m, "default",js_miniz_use(ctx)); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_miniz -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) { - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_miniz_init); - if (!m) return NULL; - JS_AddModuleExport(ctx, m, "default"); - return m; -} diff --git a/source/qjs_nota.c b/source/qjs_nota.c index 898e7220..c518f422 100755 --- a/source/qjs_nota.c +++ b/source/qjs_nota.c @@ -349,26 +349,8 @@ static const JSCFunctionListEntry js_nota_funcs[] = { JS_CFUNC_DEF("decode", 1, js_nota_decode), }; -static int js_nota_init(JSContext *ctx, JSModuleDef *m) { - JS_SetModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry)); - return 0; -} - JSValue js_nota_use(JSContext *js) { JSValue export = JS_NewObject(js); JS_SetPropertyFunctionList(js, export, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry)); return export; } - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_nota -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) { - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_nota_init); - if (!m) return NULL; - JS_AddModuleExportList(ctx, m, js_nota_funcs, sizeof(js_nota_funcs)/sizeof(JSCFunctionListEntry)); - return m; -} diff --git a/source/qjs_num.c b/source/qjs_num.c new file mode 100644 index 00000000..3efb869c --- /dev/null +++ b/source/qjs_num.c @@ -0,0 +1,607 @@ +#include "quickjs.h" +#include +#include + +#ifdef __APPLE__ +#include +#else +#include +#include +#endif + +static JSClassID js_matrix_class_id; +static JSClassID js_array_class_id; + +typedef struct { + double *data; + int rows; + int cols; +} matrix_t; + +typedef struct { + double *data; + int size; +} array_t; + +static void js_matrix_finalizer(JSRuntime *rt, JSValue val) { + matrix_t *mat = JS_GetOpaque(val, js_matrix_class_id); + if (mat) { + js_free_rt(rt, mat->data); + js_free_rt(rt, mat); + } +} + +static void js_array_finalizer(JSRuntime *rt, JSValue val) { + array_t *arr = JS_GetOpaque(val, js_array_class_id); + if (arr) { + js_free_rt(rt, arr->data); + js_free_rt(rt, arr); + } +} + +static JSClassDef js_matrix_class = { + "Matrix", + .finalizer = js_matrix_finalizer, +}; + +// Forward declaration for exotic methods +static const JSClassExoticMethods js_array_exotic; + +static JSClassDef js_array_class = { + "Array", + .finalizer = js_array_finalizer, + .exotic = &js_array_exotic, +}; + +static matrix_t *js2matrix(JSContext *ctx, JSValue v) { + return JS_GetOpaque(v, js_matrix_class_id); +} + +static array_t *js2array(JSContext *ctx, JSValue v) { + return JS_GetOpaque(v, js_array_class_id); +} + +static JSValue js_matrix_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) + return JS_ThrowTypeError(ctx, "Matrix constructor requires an array argument"); + + if (!JS_IsArray(ctx, argv[0])) + return JS_ThrowTypeError(ctx, "Matrix constructor requires an array argument"); + + JSValue length_val = JS_GetPropertyStr(ctx, argv[0], "length"); + int32_t rows; + JS_ToInt32(ctx, &rows, length_val); + JS_FreeValue(ctx, length_val); + + if (rows == 0) + return JS_ThrowRangeError(ctx, "Matrix cannot have 0 rows"); + + // Get first row to determine columns + JSValue first_row = JS_GetPropertyUint32(ctx, argv[0], 0); + if (!JS_IsArray(ctx, first_row)) { + JS_FreeValue(ctx, first_row); + return JS_ThrowTypeError(ctx, "Matrix rows must be arrays"); + } + + JSValue col_length_val = JS_GetPropertyStr(ctx, first_row, "length"); + int32_t cols; + JS_ToInt32(ctx, &cols, col_length_val); + JS_FreeValue(ctx, col_length_val); + JS_FreeValue(ctx, first_row); + + if (cols == 0) + return JS_ThrowRangeError(ctx, "Matrix cannot have 0 columns"); + + matrix_t *mat = js_malloc(ctx, sizeof(matrix_t)); + if (!mat) + return JS_EXCEPTION; + + mat->rows = rows; + mat->cols = cols; + mat->data = js_malloc(ctx, sizeof(double) * rows * cols); + if (!mat->data) { + js_free(ctx, mat); + return JS_EXCEPTION; + } + + // Fill matrix data + for (int i = 0; i < rows; i++) { + JSValue row = JS_GetPropertyUint32(ctx, argv[0], i); + if (!JS_IsArray(ctx, row)) { + js_free(ctx, mat->data); + js_free(ctx, mat); + JS_FreeValue(ctx, row); + return JS_ThrowTypeError(ctx, "All matrix rows must be arrays"); + } + + JSValue row_length_val = JS_GetPropertyStr(ctx, row, "length"); + int32_t row_cols; + JS_ToInt32(ctx, &row_cols, row_length_val); + JS_FreeValue(ctx, row_length_val); + + if (row_cols != cols) { + js_free(ctx, mat->data); + js_free(ctx, mat); + JS_FreeValue(ctx, row); + return JS_ThrowTypeError(ctx, "All matrix rows must have the same length"); + } + + for (int j = 0; j < cols; j++) { + JSValue elem = JS_GetPropertyUint32(ctx, row, j); + double val; + if (JS_ToFloat64(ctx, &val, elem)) { + js_free(ctx, mat->data); + js_free(ctx, mat); + JS_FreeValue(ctx, elem); + JS_FreeValue(ctx, row); + return JS_EXCEPTION; + } + mat->data[i * cols + j] = val; + JS_FreeValue(ctx, elem); + } + JS_FreeValue(ctx, row); + } + + JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id); + JS_SetOpaque(obj, mat); + return obj; +} + +static JSValue js_array_constructor(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) + return JS_ThrowTypeError(ctx, "Array constructor requires an array argument"); + + if (!JS_IsArray(ctx, argv[0])) + return JS_ThrowTypeError(ctx, "Array constructor requires an array argument"); + + JSValue length_val = JS_GetPropertyStr(ctx, argv[0], "length"); + int32_t size; + JS_ToInt32(ctx, &size, length_val); + JS_FreeValue(ctx, length_val); + + if (size == 0) + return JS_ThrowRangeError(ctx, "Array cannot have 0 elements"); + + array_t *arr = js_malloc(ctx, sizeof(array_t)); + if (!arr) + return JS_EXCEPTION; + + arr->size = size; + arr->data = js_malloc(ctx, sizeof(double) * size); + if (!arr->data) { + js_free(ctx, arr); + return JS_EXCEPTION; + } + + // Fill array data + for (int i = 0; i < size; i++) { + JSValue elem = JS_GetPropertyUint32(ctx, argv[0], i); + double val; + if (JS_ToFloat64(ctx, &val, elem)) { + js_free(ctx, arr->data); + js_free(ctx, arr); + JS_FreeValue(ctx, elem); + return JS_EXCEPTION; + } + arr->data[i] = val; + JS_FreeValue(ctx, elem); + } + + JSValue obj = JS_NewObjectClass(ctx, js_array_class_id); + JS_SetOpaque(obj, arr); + return obj; +} + +static JSValue js_matrix_inverse(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + matrix_t *mat = js2matrix(ctx, this_val); + if (!mat) + return JS_EXCEPTION; + + if (mat->rows != mat->cols) + return JS_ThrowTypeError(ctx, "Matrix must be square for inversion"); + + int n = mat->rows; + + // Create a copy of the matrix + matrix_t *result = js_malloc(ctx, sizeof(matrix_t)); + if (!result) + return JS_EXCEPTION; + + result->rows = n; + result->cols = n; + result->data = js_malloc(ctx, sizeof(double) * n * n); + if (!result->data) { + js_free(ctx, result); + return JS_EXCEPTION; + } + + memcpy(result->data, mat->data, sizeof(double) * n * n); + + // Allocate pivot array + int *ipiv = js_malloc(ctx, sizeof(int) * n); + if (!ipiv) { + js_free(ctx, result->data); + js_free(ctx, result); + return JS_EXCEPTION; + } + + // LU decomposition +#ifdef __APPLE__ + // For macOS with ILP64, use 64-bit integers + __LAPACK_int nn = n; + __LAPACK_int info = 0; + __LAPACK_int *ipiv_lapack = js_malloc(ctx, sizeof(__LAPACK_int) * n); + if (!ipiv_lapack) { + js_free(ctx, ipiv); + js_free(ctx, result->data); + js_free(ctx, result); + return JS_EXCEPTION; + } + + // LAPACK uses column-major, but we'll transpose the result + __LAPACK_int lda = n; + dgetrf_(&nn, &nn, result->data, &lda, ipiv_lapack, &info); + + if (info != 0) { + js_free(ctx, ipiv_lapack); + js_free(ctx, ipiv); + js_free(ctx, result->data); + js_free(ctx, result); + return JS_ThrowInternalError(ctx, "Matrix LU decomposition failed"); + } + + // Compute inverse + __LAPACK_int lwork = n * n; + double *work = js_malloc(ctx, sizeof(double) * lwork); + if (!work) { + js_free(ctx, ipiv_lapack); + js_free(ctx, ipiv); + js_free(ctx, result->data); + js_free(ctx, result); + return JS_EXCEPTION; + } + + dgetri_(&nn, result->data, &lda, ipiv_lapack, work, &lwork, &info); + js_free(ctx, work); + js_free(ctx, ipiv_lapack); +#else + int info = LAPACKE_dgetrf(LAPACK_ROW_MAJOR, n, n, result->data, n, ipiv); + if (info != 0) { + js_free(ctx, ipiv); + js_free(ctx, result->data); + js_free(ctx, result); + return JS_ThrowInternalError(ctx, "Matrix LU decomposition failed"); + } + + // Compute inverse + info = LAPACKE_dgetri(LAPACK_ROW_MAJOR, n, result->data, n, ipiv); +#endif + js_free(ctx, ipiv); + + if (info != 0) { + js_free(ctx, result->data); + js_free(ctx, result); + return JS_ThrowInternalError(ctx, "Matrix inversion failed"); + } + + JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id); + JS_SetOpaque(obj, result); + return obj; +} + +static JSValue js_matrix_multiply(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) + return JS_ThrowTypeError(ctx, "multiply requires an argument"); + + matrix_t *A = js2matrix(ctx, this_val); + if (!A) + return JS_EXCEPTION; + + // Check if multiplying by another matrix + matrix_t *B = js2matrix(ctx, argv[0]); + if (B) { + if (A->cols != B->rows) + return JS_ThrowTypeError(ctx, "Matrix dimensions incompatible for multiplication"); + + matrix_t *C = js_malloc(ctx, sizeof(matrix_t)); + if (!C) + return JS_EXCEPTION; + + C->rows = A->rows; + C->cols = B->cols; + C->data = js_malloc(ctx, sizeof(double) * C->rows * C->cols); + if (!C->data) { + js_free(ctx, C); + return JS_EXCEPTION; + } + + // C = A * B + cblas_dgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, + A->rows, B->cols, A->cols, + 1.0, A->data, A->cols, + B->data, B->cols, + 0.0, C->data, C->cols); + + JSValue obj = JS_NewObjectClass(ctx, js_matrix_class_id); + JS_SetOpaque(obj, C); + return obj; + } + + // Check if multiplying by an array (matrix-vector multiplication) + array_t *x = js2array(ctx, argv[0]); + if (x) { + if (A->cols != x->size) + return JS_ThrowTypeError(ctx, "Matrix columns must match array size"); + + array_t *y = js_malloc(ctx, sizeof(array_t)); + if (!y) + return JS_EXCEPTION; + + y->size = A->rows; + y->data = js_malloc(ctx, sizeof(double) * y->size); + if (!y->data) { + js_free(ctx, y); + return JS_EXCEPTION; + } + + // y = A * x + cblas_dgemv(CblasRowMajor, CblasNoTrans, + A->rows, A->cols, + 1.0, A->data, A->cols, + x->data, 1, + 0.0, y->data, 1); + + JSValue obj = JS_NewObjectClass(ctx, js_array_class_id); + JS_SetOpaque(obj, y); + return obj; + } + + return JS_ThrowTypeError(ctx, "multiply requires a Matrix or Array argument"); +} + +static JSValue js_matrix_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + matrix_t *mat = js2matrix(ctx, this_val); + if (!mat) + return JS_EXCEPTION; + + JSValue arr = JS_NewArray(ctx); + if (JS_IsException(arr)) + return arr; + + for (int i = 0; i < mat->rows; i++) { + JSValue row = JS_NewArray(ctx); + if (JS_IsException(row)) { + JS_FreeValue(ctx, arr); + return row; + } + for (int j = 0; j < mat->cols; j++) { + JS_SetPropertyUint32(ctx, row, j, JS_NewFloat64(ctx, mat->data[i * mat->cols + j])); + } + JS_SetPropertyUint32(ctx, arr, i, row); + } + return arr; +} + +static JSValue js_array_to_array(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + array_t *arr = js2array(ctx, this_val); + if (!arr) + return JS_EXCEPTION; + + JSValue jsarr = JS_NewArray(ctx); + for (int i = 0; i < arr->size; i++) { + JS_SetPropertyUint32(ctx, jsarr, i, JS_NewFloat64(ctx, arr->data[i])); + } + return jsarr; +} + +static JSValue js_array_dot(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) + return JS_ThrowTypeError(ctx, "dot requires an argument"); + + array_t *a = js2array(ctx, this_val); + if (!a) + return JS_EXCEPTION; + + array_t *b = js2array(ctx, argv[0]); + if (!b) + return JS_ThrowTypeError(ctx, "dot requires an Array argument"); + + if (a->size != b->size) + return JS_ThrowTypeError(ctx, "Arrays must have the same size for dot product"); + + double result = cblas_ddot(a->size, a->data, 1, b->data, 1); + return JS_NewFloat64(ctx, result); +} + +static JSValue js_array_norm(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + array_t *arr = js2array(ctx, this_val); + if (!arr) + return JS_EXCEPTION; + + double norm = cblas_dnrm2(arr->size, arr->data, 1); + return JS_NewFloat64(ctx, norm); +} + +static JSValue js_array_slice(JSContext *js, JSValueConst self, int argc, JSValueConst *argv) +{ + array_t *arr = js2array(js, self); + if (!arr) + return JS_EXCEPTION; + + int32_t start = 0; + int32_t end = arr->size; + + // Parse start argument + if (argc >= 1 && !JS_IsUndefined(argv[0])) { + if (JS_ToInt32(js, &start, argv[0])) + return JS_EXCEPTION; + + // Handle negative indices + if (start < 0) { + start = arr->size + start; + if (start < 0) start = 0; + } else if (start > arr->size) { + start = arr->size; + } + } + + // Parse end argument + if (argc >= 2 && !JS_IsUndefined(argv[1])) { + if (JS_ToInt32(js, &end, argv[1])) + return JS_EXCEPTION; + + // Handle negative indices + if (end < 0) { + end = arr->size + end; + if (end < 0) end = 0; + } else if (end > arr->size) { + end = arr->size; + } + } + + // Ensure start <= end + if (start > end) { + end = start; // Return empty array + } + + // Create JavaScript array + JSValue jsarr = JS_NewArray(js); + if (JS_IsException(jsarr)) + return jsarr; + + // Fill with sliced values + for (int32_t i = start; i < end; i++) { + JS_SetPropertyUint32(js, jsarr, i - start, JS_NewFloat64(js, arr->data[i])); + } + + return jsarr; +} + +static int js_array_get_own_property(JSContext *ctx, JSPropertyDescriptor *desc, + JSValueConst obj, JSAtom prop) +{ + array_t *arr = JS_GetOpaque(obj, js_array_class_id); + if(!arr) return FALSE; + + if(JS_AtomIsNumericIndex(ctx, prop) > 0) { + JSValue key = JS_AtomToValue(ctx, prop); + uint32_t idx; + JS_ToUint32(ctx, &idx, key); + JS_FreeValue(ctx, key); + if(idx < arr->size) { + if(desc) { + desc->flags = JS_PROP_C_W_E; /* configurable, writable, enumerable */ + desc->value = JS_NewFloat64(ctx, arr->data[idx]); + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + } + return TRUE; + } + return FALSE; + } + + int ret; + + const char *name = JS_AtomToCString(ctx, prop); + if (name && strcmp(name, "length") == 0) { + if(desc) { + /* length is read-only, non-enumerable */ + desc->flags = JS_PROP_CONFIGURABLE; + desc->value = JS_NewInt32(ctx, arr->size); + desc->getter = JS_UNDEFINED; + desc->setter = JS_UNDEFINED; + } + ret = TRUE; + } + + ret = FALSE; + + JS_FreeCString(ctx, name); + + return ret; +} + +#include "quickjs-atom.h" + +static int js_array_define_own_property(JSContext *ctx, JSValueConst this_obj, + JSAtom prop, JSValueConst val, + JSValueConst getter, JSValueConst setter, + int flags) +{ + array_t *arr = JS_GetOpaque(this_obj, js_array_class_id); + if(!arr) return FALSE; + + if(JS_AtomIsNumericIndex(ctx, prop) > 0) { + JSValue key = JS_AtomToValue(ctx, prop); + uint32_t idx; + JS_ToUint32(ctx, &idx, key); + JS_FreeValue(ctx, key); + if(idx < arr->size) { + double d; + if(JS_ToFloat64(ctx, &d, val) == 0) { + arr->data[idx] = d; + return TRUE; + } + return FALSE; + } + return FALSE; + } + + /* reject everything else (including “length”) */ + return FALSE; +} + + +// Set up the exotic methods structure +static const JSClassExoticMethods js_array_exotic = { + .get_own_property = js_array_get_own_property, + .define_own_property = js_array_define_own_property, + // Other methods can be NULL for now +}; + +static const JSCFunctionListEntry js_matrix_proto_funcs[] = { + JS_CFUNC_DEF("inverse", 0, js_matrix_inverse), + JS_CFUNC_DEF("multiply", 1, js_matrix_multiply), + JS_CFUNC_DEF("toArray", 0, js_matrix_to_array), +}; + +static const JSCFunctionListEntry js_array_proto_funcs[] = { + JS_CFUNC_DEF("dot", 1, js_array_dot), + JS_CFUNC_DEF("norm", 0, js_array_norm), + JS_CFUNC_DEF("toArray", 0, js_array_to_array), + JS_CFUNC_DEF("toJSON", 0, js_array_to_array), /* ← for JSON.stringify */ + JS_CFUNC_DEF("slice", 2, js_array_slice), +}; + + +JSValue js_num_use(JSContext *ctx) { + // Register Matrix class + JS_NewClassID(&js_matrix_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_matrix_class_id, &js_matrix_class); + JSValue matrix_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, matrix_proto, js_matrix_proto_funcs, + sizeof(js_matrix_proto_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(ctx, js_matrix_class_id, matrix_proto); + + // Create Matrix constructor + JSValue matrix_ctor = JS_NewCFunction2(ctx, js_matrix_constructor, "Matrix", 1, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, matrix_ctor, matrix_proto); + + // Register Array class + JS_NewClassID(&js_array_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_array_class_id, &js_array_class); + JSValue array_proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, array_proto, js_array_proto_funcs, + sizeof(js_array_proto_funcs) / sizeof(JSCFunctionListEntry)); + JS_SetClassProto(ctx, js_array_class_id, array_proto); + + // Create Array constructor + JSValue array_ctor = JS_NewCFunction2(ctx, js_array_constructor, "Array", 1, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, array_ctor, array_proto); + + JSValue export = JS_NewObject(ctx); + JS_SetPropertyStr(ctx, export, "Matrix", matrix_ctor); + JS_SetPropertyStr(ctx, export, "Array", array_ctor); + return export; +} + diff --git a/source/qjs_qr.c b/source/qjs_qr.c index b63149cb..c489dfa5 100644 --- a/source/qjs_qr.c +++ b/source/qjs_qr.c @@ -300,24 +300,3 @@ JSValue js_qr_use(JSContext *js) { JS_SetPropertyFunctionList(js, exports, js_qr_funcs, sizeof(js_qr_funcs) / sizeof(JSCFunctionListEntry)); return exports; } - -// Module init -static int js_qr_init(JSContext *js, JSModuleDef *m) { - JS_SetModuleExport(js, m, "default", js_qr_use(js)); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_qr -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *js, const char *module_name) { - JSModuleDef *m = JS_NewCModule(js, module_name, js_qr_init); - if (!m) - return NULL; - - JS_AddModuleExport(js, m, "default"); - return m; -} diff --git a/source/qjs_soloud.c b/source/qjs_soloud.c index 5ec0e00f..90c51f55 100644 --- a/source/qjs_soloud.c +++ b/source/qjs_soloud.c @@ -217,22 +217,3 @@ JSValue js_soloud_use(JSContext *js) JS_SetPropertyFunctionList(js, export, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry)); return export; } - -static int js_soloud_init(JSContext *js, JSModuleDef *m) { - js_soloud_use(js); - JS_SetModuleExportList(js, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry)); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_soloud -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) { - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_soloud_init); - if (!m) return NULL; - JS_AddModuleExportList(ctx, m, js_soloud_funcs, sizeof(js_soloud_funcs)/sizeof(JSCFunctionListEntry)); - return m; -} diff --git a/source/qjs_wota.c b/source/qjs_wota.c index 33d439f2..5d9ef4db 100644 --- a/source/qjs_wota.c +++ b/source/qjs_wota.c @@ -381,26 +381,6 @@ static const JSCFunctionListEntry js_wota_funcs[] = { JS_CFUNC_DEF("decode", 2, js_wota_decode), }; -static int js_wota_init(JSContext *ctx, JSModuleDef *m) -{ - JS_SetModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0])); - return 0; -} - -#ifdef JS_SHARED_LIBRARY -#define JS_INIT_MODULE js_init_module -#else -#define JS_INIT_MODULE js_init_module_wota -#endif - -JSModuleDef *JS_INIT_MODULE(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m = JS_NewCModule(ctx, module_name, js_wota_init); - if (!m) return NULL; - JS_AddModuleExportList(ctx, m, js_wota_funcs, sizeof(js_wota_funcs)/sizeof(js_wota_funcs[0])); - return m; -} - JSValue js_wota_use(JSContext *ctx) { JSValue exports = JS_NewObject(ctx); diff --git a/tests/num_property_test.ce b/tests/num_property_test.ce new file mode 100644 index 00000000..d94879c2 --- /dev/null +++ b/tests/num_property_test.ce @@ -0,0 +1,23 @@ +// Test property access for num.Array +var num = use('num'); + +// Create an array +var arr = new num.Array([10, 20, 30, 40, 50]); + +log.console("Testing property access:"); +log.console("arr[0] =", arr[0]); +log.console("arr[1] =", arr[1]); +log.console("arr[2] =", arr[2]); +log.console("arr[4] =", arr[4]); + +arr[0] = 15 +log.console(arr[0]) +log.console("arr[10] =", arr[10]); // Should be undefined + +log.console("arr.length =", arr.length); + +log.console(arr instanceof num.Array) +log.console(json.encode(arr)) + + +$_.stop(); \ No newline at end of file diff --git a/tests/num_setter_test.ce b/tests/num_setter_test.ce new file mode 100644 index 00000000..ccd867d1 --- /dev/null +++ b/tests/num_setter_test.ce @@ -0,0 +1,32 @@ +// Test setter functionality for num.Array +var num = use('num'); + +// Create an array +var arr = new num.Array([1, 2, 3, 4, 5]); + +log.console("Original values:"); +log.console("arr[0] =", arr[0], "arr[1] =", arr[1], "arr[2] =", arr[2]); + +// Test setting values +arr[0] = 100; +arr[1] = 200; +arr[2] = 300.5; + +log.console("After setting:"); +log.console("arr[0] =", arr[0], "arr[1] =", arr[1], "arr[2] =", arr[2]); + +// Test setting with different types +arr[3] = "123.7"; // Should convert string to number +arr[4] = true; // Should convert boolean to number + +log.console("After type conversion:"); +log.console("arr[3] =", arr[3], "(from string)"); +log.console("arr[4] =", arr[4], "(from boolean)"); + +// Test bounds checking - this should fail silently +arr[10] = 999; +log.console("arr[10] after out-of-bounds set =", arr[10]); + +log.console("Final array:", arr.toArray()); + +$_.stop(); \ No newline at end of file diff --git a/tests/num_test.ce b/tests/num_test.ce new file mode 100644 index 00000000..b8d4e214 --- /dev/null +++ b/tests/num_test.ce @@ -0,0 +1,54 @@ +// Test the num module +var num = use('num'); + +// Test matrix creation and operations +var A = new num.Matrix([ + [1, 2, 3], + [4, 5, 6], + [7, 8, 10] +]); + +log.console("Matrix A:"); +log.console(A.toArray()); + +// Test matrix inversion +var A_inv = A.inverse(); +log.console("\nMatrix A inverse:"); +log.console(A_inv.toArray()); + +// Verify A * A_inv = I (approximately) +var I = A.multiply(A_inv); +log.console("\nA * A_inv (should be identity):"); +log.console(I.toArray()); + +// Test array creation +var v = new num.Array([1, 2, 3]); +log.console("\nVector v:"); +log.console(v.toArray()); + +// Test matrix-vector multiplication +var result = A.multiply(v); +log.console("\nA * v:"); +log.console(result.toArray()); + +// Test dot product +var u = new num.Array([4, 5, 6]); +var dot_product = v.dot(u); +log.console("\nv · u =", dot_product); + +// Test norm +var v_norm = v.norm(); +log.console("||v|| =", v_norm); + +// Test matrix-matrix multiplication +var B = new num.Matrix([ + [1, 0, 0], + [0, 2, 0], + [0, 0, 3] +]); + +var C = A.multiply(B); +log.console("\nA * B:"); +log.console(C.toArray()); + +$_.stop()