38 Commits
steam ... qjs

Author SHA1 Message Date
John Alanbrook
fc58cc5a12 initial fork 2025-06-09 12:37:13 -05:00
John Alanbrook
a274fb174f faster text conversion to hex; guid generation now dealt with with -u.random_fit and blob formation; mersenne twister used for -u.random functions 2025-06-08 13:42:20 -05:00
John Alanbrook
3622a5ec58 actor messages now delivered as blobs 2025-06-08 11:06:59 -05:00
John Alanbrook
8a5f8a4d74 even faster wota encoding and decoding 2025-06-08 10:34:12 -05:00
John Alanbrook
c1d341eecd faster wota encoding 2025-06-08 08:35:12 -05:00
John Alanbrook
3176e6775d attempt for jswota in js 2025-06-08 00:49:19 -05:00
John Alanbrook
34dcd0a235 fix actors not running correctly 2025-06-07 23:35:47 -05:00
John Alanbrook
cbda7dfbc9 add utf8 and kim text encoder/decoders 2025-06-07 23:35:19 -05:00
John Alanbrook
d039e2cfe6 use spinlocks and other fixes 2025-06-07 17:09:03 -05:00
John Alanbrook
c02bd06ec0 signal kill works 2025-06-07 13:46:31 -05:00
John Alanbrook
efa63771e6 fix sockets 2025-06-07 12:24:34 -05:00
John Alanbrook
9f6d27fb3c fix multiple main thread actors not working 2025-06-06 21:26:29 -05:00
John Alanbrook
1a61ae6f77 actors keep running if they have messages, until no threads are available 2025-06-06 18:22:12 -05:00
John Alanbrook
83c816fd0e fix actors not freeing correctly if error in startup script 2025-06-06 17:42:28 -05:00
John Alanbrook
adbaa92dd5 fd now uses numbers to fix the inability to exit 2025-06-06 17:29:06 -05:00
John Alanbrook
580df9f233 add fstat; tests/cat as an example 2025-06-06 16:49:19 -05:00
John Alanbrook
d5d17560f9 add chunked reading; example with cat.ce 2025-06-06 16:15:39 -05:00
John Alanbrook
cd05ab97b5 Update agents directive 2025-06-06 14:59:30 -05:00
John Alanbrook
4eecbd692b fix actor not dying if killed while not in a turn 2025-06-06 13:59:14 -05:00
John Alanbrook
72beed7177 fix string leak with blob write_text 2025-06-06 13:58:46 -05:00
John Alanbrook
e0595de71a closes #19: kill underlings with system level interrupts 2025-06-06 12:24:32 -05:00
John Alanbrook
6687008d1a closes #14 2025-06-06 10:46:16 -05:00
John Alanbrook
5b9f1b8f51 closes #12 2025-06-06 10:31:41 -05:00
John Alanbrook
c570de7f41 closes #18: actor data now behind private symbol 2025-06-06 10:18:45 -05:00
John Alanbrook
d0138a6c23 ammend tests 2025-06-06 09:22:56 -05:00
John Alanbrook
29aa25e866 fix bug when calling unneeded inside of unneeded callback 2025-06-06 09:05:35 -05:00
John Alanbrook
ef28be93db fix js leak
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-06-06 08:42:16 -05:00
John Alanbrook
0d7be6a94e attempt fix 2025-06-05 16:19:06 -05:00
John Alanbrook
4fe78c4a63 move timer to its own file 2025-06-05 13:34:50 -05:00
John Alanbrook
b52edb2746 fix updated render loop 2025-06-05 12:04:45 -05:00
John Alanbrook
79d5412fe6 massively simplify loop logic 2025-06-05 01:08:27 -05:00
John Alanbrook
fcec2cd1dc sockets and fd 2025-06-04 22:28:06 -05:00
John Alanbrook
2038ce15a7 factor out sdl input
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-06-04 16:25:06 -05:00
John Alanbrook
08557011cb single threads; custom timer; letters
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-06-04 14:39:58 -05:00
John Alanbrook
3e87bfd6cc add to look for same folder name as well as main
Some checks failed
Build and Deploy / build-macos (push) Failing after 2s
Build and Deploy / build-windows (CLANG64) (push) Has been cancelled
Build and Deploy / package-dist (push) Has been cancelled
Build and Deploy / deploy-itch (push) Has been cancelled
Build and Deploy / deploy-gitea (push) Has been cancelled
Build and Deploy / build-linux (push) Has been cancelled
2025-06-04 13:22:58 -05:00
John Alanbrook
ef86dd3ecf remove some docstrings to save per actor memory 2025-06-03 23:58:44 -05:00
John Alanbrook
c887bcf7b9 no longer need to send ids to window renderer; per actor config 2025-06-03 16:13:54 -05:00
John Alanbrook
709f2459e4 add config options for ar timers 2025-06-03 14:33:51 -05:00
96 changed files with 73377 additions and 4455 deletions

View File

@@ -1,8 +1,17 @@
sdl_video = "main"
[dependencies]
extramath = "https://gitea.pockle.world/john/extramath@master"
[system]
ar_timer = 60 # seconds before idle actor reclamation
actor_memory = 0 # MB of memory an actor can use; 0 for unbounded
net_service = 0.1 # seconds per net service pull
reply_timeout = 60 # seconds to hold callback for reply messages; 0 for unbounded
ar_timer = 60
actor_memory = 0
net_service = 0.1
reply_timeout = 60
actor_max = "10_000"
stack_max = 0
[actors]
[actors.prosperon/sdl_video]
main = true
[actors.prosperon/prosperon]
main = true
[actors.prosperon]
main = true

View File

@@ -13,7 +13,6 @@ This is a game engine developed using a QuickJS fork as its scripting language.
## Coding Practices
- Use K&R style C
- Use as little whitespace as possible
- Javascript style prefers objects and prototypical inheritence over ES6 classes, liberal use of closures, and var everywhere
## Instructions

View File

@@ -8,6 +8,7 @@
var wota = use('wota');
var nota = use('nota');
var json = use('json');
var jswota = use('jswota')
var os = use('os');
//
@@ -22,7 +23,7 @@ const libraries = [
decode: wota.decode,
// Wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
getSize(encoded) {
return encoded.byteLength;
return encoded.length;
}
},
{
@@ -31,7 +32,7 @@ const libraries = [
decode: nota.decode,
// Nota also produces an ArrayBuffer:
getSize(encoded) {
return encoded.byteLength;
return encoded.length;
}
},
{
@@ -91,11 +92,6 @@ const benchmarks = [
data: [ Array.from({length:1000}, (_, i) => i) ],
iterations: 1000
},
{
name: "Large Binary Blob (256KB)",
data: [ new Uint8Array(256 * 1024).buffer ],
iterations: 200
}
];
////////////////////////////////////////////////////////////////////////////////

View File

@@ -152,23 +152,6 @@ else
endif
endif
quickjs_opts = []
quickjs_opts += 'default_library=static'
# Enable leak detection for non-release builds
if get_option('buildtype') != 'release'
quickjs_opts += 'leaks=true'
endif
# Try to find system-installed quickjs first
quickjs_dep = dependency('quickjs', static: true, required: false)
if not quickjs_dep.found()
message('⚙ System quickjs not found, building subproject...')
deps += dependency('quickjs', static:true, default_options:quickjs_opts)
else
deps += quickjs_dep
endif
# Try to find system-installed qjs-layout first
qjs_layout_dep = dependency('qjs-layout', static: true, required: false)
if not qjs_layout_dep.found()
@@ -186,6 +169,14 @@ else
deps += miniz_dep
endif
libuv_dep = dependency('libuv', static: true, required: false)
if not libuv_dep.found()
message('⚙ System libuv not found, building subproject...')
deps += dependency('libuv', static:true, fallback: ['libuv', 'libuv_dep'])
else
deps += libuv_dep
endif
# Try to find system-installed physfs first
physfs_dep = dependency('physfs', static: true, required: false)
if not physfs_dep.found()
@@ -286,9 +277,13 @@ sources = []
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_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'
'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'
]
# js src
src += [ 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c', 'quickjs.c' ]
# quirc src
src += [
'thirdparty/quirc/quirc.c', 'thirdparty/quirc/decode.c',

View File

@@ -1,5 +1,4 @@
var input = use('input')
var util = use('util')
var downkeys = {};

View File

@@ -277,11 +277,9 @@ function draw()
draw2d.clear()
drawBoard()
drawPieces()
draw2d.text("HELL", {x: 100, y: 100}, 'fonts/c64.ttf', 16, [1,1,1,1])
return draw2d.get_commands()
}
function startServer() {
gameState = 'server_waiting';
isServer = true;

View File

@@ -7,7 +7,6 @@ rectangle packing, etc.
`
var renderer_actor = arg[0]
var renderer_id = arg[1]
var io = use('io')
var time = use('time')
@@ -43,7 +42,6 @@ Object.defineProperties(graphics.Image.prototype, {
// Send message to load texture
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: this[CPU]
}, function(response) {
@@ -349,7 +347,6 @@ graphics.get_font = function get_font(path, size) {
// Load font texture via renderer actor (async)
send(renderer_actor, {
kind: "renderer",
id: renderer_id,
op: "loadTexture",
data: font.surface
}, function(response) {

View File

@@ -1,354 +0,0 @@
/**
* Moth Game Framework
* Higher-level game development framework built on top of Prosperon
*/
var os = use('os');
var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var video_actor = use('sdl_video')
var input = use('input')
input.watch($_)
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)
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)
}
}
function worldToScreenPoint([wx, wy], camera, winW, winH) {
// 1) worldwindow origin (bottomleft)
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 pixelspace 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 ];
}
function screenToWorldPoint([px, py], camera, winW, winH) {
// 1) undo pixel→NDC within the cameras viewport
const ndcX = (px - camera.viewport.x * winW)
/ (camera.viewport.width * winW)
const ndcY = 1 - (py - camera.viewport.y * winH)
/ (camera.viewport.height * winH)
// 2) compute the worldwindow origin (bottomleft)
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
]
}
var camera = {
size: [500,500],//{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,
far_z:1000,
viewport: {x:0,y:0,width:1,height:1}, // viewport it appears on screen
ortho:true,
anchor:[0.5,0.5],//{x:0.5,y:0.5},
surface: undefined
}
var util = use('util')
var cammy = util.camera_globals(camera)
var graphics
var window
var render
var gameactor
var dir = args[0]
if (!io.exists(args[0] + '/main.js'))
throw Error(`No main.js found in ${args[0]}`)
log.spam('Starting game in ' + dir)
io.mount(dir)
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, args[0] + "/main.js")
send(video_actor, {
kind: "window",
op:"create",
data: {
title: "Moth Test",
width: 500,
height: 500
}
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
window = e.id
send(video_actor,{
kind:"window",
op:"makeRenderer",
id:window
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
render = e.id
graphics = use('graphics', video_actor, e.id)
})
})
var last = os.now()
// FPS tracking
var fps_samples = []
var fps_sample_count = 60
var fps_sum = 0
var images = {}
// Convert high-level draw commands to low-level renderer commands
function translate_draw_commands(commands) {
if (!graphics) return
var renderer_commands = []
commands.forEach(function(cmd) {
if (cmd.material && cmd.material.color) {
renderer_commands.push({
op: "set",
prop: "drawColor",
value: cmd.material.color
})
}
switch(cmd.cmd) {
case "draw_rect":
cmd.rect = worldToScreenRect(cmd.rect, camera,500, 500)
// Handle rectangles with optional rounding and thickness
if (cmd.opt && cmd.opt.radius && cmd.opt.radius > 0) {
// Rounded rectangle
var thickness = (cmd.opt.thickness === 0) ? 0 : (cmd.opt.thickness || 1)
var raster_result = rasterize.round_rect(cmd.rect, cmd.opt.radius, thickness)
if (raster_result.type === 'rect') {
renderer_commands.push({
op: "fillRect",
data: {rect: raster_result.data}
})
} else if (raster_result.type === 'rects') {
raster_result.data.forEach(function(rect) {
renderer_commands.push({
op: "fillRect",
data: {rect: rect}
})
})
}
} else if (cmd.opt && cmd.opt.thickness && cmd.opt.thickness > 0) {
// Outlined rectangle
var raster_result = rasterize.outline_rect(cmd.rect, cmd.opt.thickness)
if (raster_result.type === 'rect') {
renderer_commands.push({
op: "fillRect",
data: {rect: raster_result.data}
})
} else if (raster_result.type === 'rects') {
renderer_commands.push({
op: "rects",
data: {rects: raster_result.data}
})
}
} else {
renderer_commands.push({
op: "fillRect",
data: {rect: cmd.rect}
})
}
break
case "draw_circle":
case "draw_ellipse":
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
// Rasterize ellipse to points or rects
var radii = cmd.radii || [cmd.radius, cmd.radius]
var raster_result = rasterize.ellipse(cmd.pos, radii, cmd.opt || {})
if (raster_result.type === 'points') {
renderer_commands.push({
op: "point",
data: {points: raster_result.data}
})
} else if (raster_result.type === 'rects') {
// Use 'rects' operation for multiple rectangles
renderer_commands.push({
op: "rects",
data: {rects: raster_result.data}
})
}
break
case "draw_line":
renderer_commands.push({
op: "line",
data: {points: cmd.points.map(p => worldToScreenPoint(p, camera, 500, 500))}
})
break
case "draw_point":
cmd.pos = worldToScreenPoint(cmd.pos, camera, 500, 500)
renderer_commands.push({
op: "point",
data: {points: [cmd.pos]}
})
break
case "draw_image":
var img = graphics.texture(cmd.image)
if (!img.gpu) break
cmd.rect.width ??= img.width
cmd.rect.height ??= img.height
cmd.rect = worldToScreenRect(cmd.rect, camera, 500, 500)
renderer_commands.push({
op: "texture",
data: {
texture_id: img.gpu.id,
dst: cmd.rect,
src: {x:0,y:0,width:img.width,height:img.height},
}
})
break
case "draw_text":
if (!cmd.text) break
if (!cmd.pos) break
var rect = worldToScreenRect({x:cmd.pos.x, y:cmd.pos.y, width:8, height:8}, camera, 500,500)
var pos = {x: rect.x, y: rect.y}
renderer_commands.push({
op: "debugText",
data: {
pos,
text: cmd.text
}
})
break
}
})
return renderer_commands
}
function loop()
{
os.frame()
var now = os.now()
var dt = now - last
last = now
// Update the game
send(gameactor, {kind:'update', dt:dt}, e => {
// Get draw commands from game
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_actor, {
kind: "renderer",
id: render,
op: "batch",
data: batch_commands
}, _ => {
var diff = os.now() - now
// Calculate and track FPS
var frame_time = os.now() - last
if (frame_time > 0) {
var current_fps = 1 / frame_time
// Add to samples
fps_samples.push(current_fps)
fps_sum += current_fps
// Keep only the last N samples
if (fps_samples.length > fps_sample_count) {
fps_sum -= fps_samples.shift()
}
// Calculate average FPS
var avg_fps = fps_sum / fps_samples.length
}
loop()
})
})
})
}
$_.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.d_pos)
e.d_pos.y *= -1
}
send(gameactor, e)
})

View File

@@ -2,10 +2,33 @@ var os = use('os');
var io = use('io');
var transform = use('transform');
var rasterize = use('rasterize');
var video_actor = use('sdl_video')
var input = use('input')
var time = use('time')
input.watch($_)
var game = args[0]
var video
$_.start(e => {
if (e.type !== 'greet') return
video = e.actor
graphics = use('graphics', video)
send(video, {kind:"window", op:"makeRenderer"}, e => {
$_.start(e => {
if (gameactor) return
gameactor = e.actor
$_.couple(gameactor)
loop()
}, args[0])
})
}, 'prosperon/sdl_video', {
title: "Prosperon",
width:500,
height:500
})
log.console('starting...')
var input = use('input')
var geometry = use('geometry')
@@ -73,63 +96,12 @@ var camera = {
}
var util = use('util')
log.console(util)
log.console(camera)
var cammy = util.camera_globals(camera)
var graphics
var window
var render
var gameactor
var game = args[0]
$_.start(e => {
if (gameactor) return
gameactor = e.actor
loop()
}, args[0])
send(video_actor, {
kind: "window",
op:"create",
data: {
title: "Moth Test",
width: 500,
height: 500
}
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
window = e.id
send(video_actor,{
kind:"window",
op:"makeRenderer",
id:window
}, e => {
if (e.error) {
log.error(e.error)
os.exit(1)
}
render = e.id
graphics = use('graphics', video_actor, e.id)
})
})
var last = os.now()
// FPS tracking
var fps_samples = []
var fps_sample_count = 60
var fps_sum = 0
var images = {}
// Convert high-level draw commands to low-level renderer commands
@@ -264,16 +236,16 @@ function translate_draw_commands(commands) {
return renderer_commands
}
function loop()
function loop(time)
{
os.frame()
var now = os.now()
var dt = now - last
last = now
send(video, {kind:'input', op:'get'}, e => {
for (var event of e) {
if (event.type === 'quit')
$_.stop()
}
})
// Update the game
send(gameactor, {kind:'update', dt:dt}, e => {
// Get draw commands from game
send(gameactor, {kind:'update', dt:1/60}, e => {
send(gameactor, {kind:'draw'}, draw_commands => {
var batch_commands = []
@@ -297,36 +269,16 @@ function loop()
op: "present"
})
send(video_actor, {
send(video, {
kind: "renderer",
id: render,
op: "batch",
data: batch_commands
}, _ => {
var diff = os.now() - now
// Calculate and track FPS
var frame_time = os.now() - last
if (frame_time > 0) {
var current_fps = 1 / frame_time
// Add to samples
fps_samples.push(current_fps)
fps_sum += current_fps
// Keep only the last N samples
if (fps_samples.length > fps_sample_count) {
fps_sum -= fps_samples.shift()
}
// Calculate average FPS
var avg_fps = fps_sum / fps_samples.length
}
loop()
})
})
})
$_.delay(loop, 1/30)
}
$_.receiver(e => {
@@ -340,6 +292,4 @@ $_.receiver(e => {
if (e.d_pos)
e.d_pos.y *= -1
}
send(gameactor, e)
})

View File

@@ -1,3 +1,5 @@
var io = use('io')
Object.defineProperty(Function.prototype, "hashify", {
value: function () {
var hash = new Map()

View File

@@ -1,9 +1,15 @@
var video = use('sdl_video');
log.console("STATED VIDE")
// SDL Video Actor
// This actor runs on the main thread and handles all SDL video operations
log.console("TO HERE")
var surface = use('surface')
var surface = use('surface');
var input = use('input')
var ren
var win
// Default window configuration - documents all available window options
var default_window = {
// Basic properties
title: "Prosperon Window",
@@ -54,10 +60,11 @@ var default_window = {
textInput: true, // Enable text input on creation
};
var config = Object.assign({}, default_window, arg[0] || {});
win = new video.window(config);
// Resource tracking
var resources = {
window: {},
renderer: {},
texture: {},
surface: {},
cursor: {}
@@ -103,6 +110,9 @@ $_.receiver(function(msg) {
case 'keyboard':
response = handle_keyboard(msg);
break;
case 'input':
response = input.get_events();
break;
default:
response = {error: "Unknown kind: " + msg.kind};
}
@@ -116,26 +126,10 @@ $_.receiver(function(msg) {
// Window operations
function handle_window(msg) {
// Special case: create doesn't need an existing window
if (msg.op === 'create') {
var config = Object.assign({}, default_window, msg.data || {});
var id = allocate_id();
var window = new prosperon.endowments.window(config);
resources.window[id] = window;
return {id: id, data: {size: window.size}};
}
// All other operations require a valid window ID
if (!msg.id || !resources.window[msg.id]) {
return {error: "Invalid window id: " + msg.id};
}
var win = resources.window[msg.id];
switch (msg.op) {
case 'destroy':
win.destroy();
delete resources.window[msg.id];
win = undefined
return {success: true};
case 'show':
@@ -212,10 +206,11 @@ function handle_window(msg) {
return {success: true};
case 'makeRenderer':
var renderer = win.make_renderer();
var renderer_id = allocate_id();
resources.renderer[renderer_id] = renderer;
return {id: renderer_id};
log.console("MAKE RENDERER")
if (ren)
return {reason: "Already made a renderer"}
ren = win.make_renderer()
return {success:true};
default:
return {error: "Unknown window operation: " + msg.op};
@@ -224,33 +219,11 @@ function handle_window(msg) {
// Renderer operations
function handle_renderer(msg) {
// Special case: createWindowAndRenderer creates both
if (msg.op === 'createWindowAndRenderer') {
var data = msg.data || {};
var result = prosperon.endowments.createWindowAndRenderer(
data.title || "Prosperon Window",
data.width || 640,
data.height || 480,
data.flags || 0
);
var win_id = allocate_id();
var ren_id = allocate_id();
resources.window[win_id] = result.window;
resources.renderer[ren_id] = result.renderer;
return {window_id: win_id, renderer_id: ren_id};
}
// All other operations require a valid renderer ID
if (!msg.id || !resources.renderer[msg.id]) {
return {error: "Invalid renderer id: " + msg.id};
}
var ren = resources.renderer[msg.id];
if (!ren) return{reason:'no renderer!'}
switch (msg.op) {
case 'destroy':
delete resources.renderer[msg.id];
// Renderer is automatically destroyed when all references are gone
ren = undefined
return {success: true};
case 'clear':
@@ -269,22 +242,6 @@ function handle_renderer(msg) {
var prop = msg.data ? msg.data.property : null;
if (!prop) return {error: "Missing property name"};
// Handle special cases
if (prop === 'window') {
var win = ren.window;
if (!win) return {data: null};
// Find window ID
for (var id in resources.window) {
if (resources.window[id] === win) {
return {data: id};
}
}
// Window not tracked, add it
var win_id = allocate_id();
resources.window[win_id] = win;
return {data: win_id};
}
// Handle special getters that might return objects
if (prop === 'drawColor') {
var color = ren[prop];
@@ -301,6 +258,8 @@ function handle_renderer(msg) {
var value = msg.value
if (!prop) return {error: "Missing property name"};
if (!value) return {error: "No value to set"}
// Validate property is settable
var readonly = ['window', 'name', 'outputSize', 'currentOutputSize', 'logicalPresentationRect', 'safeArea'];
if (readonly.indexOf(prop) !== -1) {
@@ -463,33 +422,15 @@ function handle_renderer(msg) {
return {data: ren.coordsToWindow(msg.data.pos)};
case 'batch':
// Execute a batch of operations
if (!msg.data || !Array.isArray(msg.data)) return {error: "Missing or invalid data array"};
var results = [];
for (var i = 0; i < msg.data.length; i++) {
var cmd = msg.data[i];
if (!cmd.op) {
results.push({error: "Command at index " + i + " missing op"});
continue;
}
// Create a temporary message object for the command
var temp_msg = {
kind: 'renderer',
id: msg.id,
op: cmd.op,
prop: cmd.prop,
value: cmd.value,
data: cmd.data
};
// Recursively call handle_renderer for each command
var result = handle_renderer(temp_msg);
var result = handle_renderer(msg.data[i]);
results.push(result);
}
return {results: results};
return {success:true};
default:
return {error: "Unknown renderer operation: " + msg.op};
@@ -511,11 +452,11 @@ function handle_texture(msg) {
if (msg.data.surface_id) {
var surf = resources.surface[msg.data.surface_id];
if (!surf) return {error: "Invalid surface id"};
tex = new prosperon.endowments.texture(renderer, surf);
tex = new video.texture(renderer, surf);
}
// Create from properties
else if (msg.data.width && msg.data.height) {
tex = new prosperon.endowments.texture(renderer, {
tex = new video.texture(renderer, {
width: msg.data.width,
height: msg.data.height,
format: msg.data.format || 'rgba8888',
@@ -612,7 +553,7 @@ function handle_cursor(msg) {
var surf = new surface(msg.data)
var hotspot = msg.data.hotspot || [0, 0];
var cursor = prosperon.endowments.createCursor(surf, hotspot);
var cursor = video.createCursor(surf, hotspot);
var cursor_id = allocate_id();
resources.cursor[cursor_id] = cursor;
@@ -623,7 +564,7 @@ function handle_cursor(msg) {
if (msg.id && resources.cursor[msg.id]) {
cursor = resources.cursor[msg.id];
}
prosperon.endowments.setCursor(cursor);
video.setCursor(cursor);
return {success: true};
case 'destroy':
@@ -643,7 +584,7 @@ prosperon.endowments = prosperon.endowments || {};
// Mouse operations
function handle_mouse(msg) {
var mouse = prosperon.endowments.mouse;
var mouse = video.mouse;
switch (msg.op) {
case 'show':
@@ -737,7 +678,7 @@ function handle_mouse(msg) {
// Keyboard operations
function handle_keyboard(msg) {
var keyboard = prosperon.endowments.keyboard;
var keyboard = video.keyboard;
switch (msg.op) {
case 'get_state':

View File

@@ -38,11 +38,10 @@ $_.delay($_.stop, 3)
var os = use('os')
var actor = use('actor')
var ioguy = {
__ACTORDATA__: {
var ioguy = {}
ioguy[cell.actor_sym] = {
id: actor.ioactor()
}
}
send(ioguy, {
type: "subscribe",

File diff suppressed because it is too large Load Diff

View File

@@ -15,7 +15,6 @@ log.console("Cleaning build artifacts...")
// Remove the build directory
try {
io.rmdir('.cell/build')
remove_dir('.cell/build')
log.console("Build directory removed")
} catch (e) {
log.error("Failed during cleanup: " + e)

249
scripts/config.ce Normal file
View File

@@ -0,0 +1,249 @@
// cell config - Manage system and actor configurations
var io = use('io')
var toml = use('toml')
var shop = use('shop')
var text = use('text')
function print_help() {
log.console("Usage: cell config <command> [options]")
log.console("")
log.console("Commands:")
log.console(" get <key> Get a configuration value")
log.console(" set <key> <value> Set a configuration value")
log.console(" list List all configurations")
log.console(" actor <name> get <key> Get actor-specific config")
log.console(" actor <name> set <key> <val> Set actor-specific config")
log.console(" actor <name> list List actor configurations")
log.console("")
log.console("Examples:")
log.console(" cell config get system.ar_timer")
log.console(" cell config set system.net_service 0.2")
log.console(" cell config actor prosperon/_sdl_video set resolution 1920x1080")
log.console(" cell config actor extramath/spline set precision high")
log.console("")
log.console("System keys:")
log.console(" system.ar_timer - Seconds before idle actor reclamation")
log.console(" system.actor_memory - MB of memory an actor can use (0=unbounded)")
log.console(" system.net_service - Seconds per network service pull")
log.console(" system.reply_timeout - Seconds to hold callback for replies (0=unbounded)")
log.console(" system.actor_max - Max number of simultaneous actors")
log.console(" system.stack_max - MB of memory each actor's stack can grow to")
}
// Parse a dot-notation key into path segments
function parse_key(key) {
return key.split('.')
}
// Get a value from nested object using path
function get_nested(obj, path) {
var current = obj
for (var segment of path) {
if (!current || typeof current !== 'object') return undefined
current = current[segment]
}
return current
}
// Set a value in nested object using path
function set_nested(obj, path, value) {
var current = obj
for (var i = 0; i < path.length - 1; i++) {
var segment = path[i]
if (!current[segment] || typeof current[segment] !== 'object') {
current[segment] = {}
}
current = current[segment]
}
current[path[path.length - 1]] = value
}
// Parse value string into appropriate type
function parse_value(str) {
// Boolean
if (str === 'true') return true
if (str === 'false') return false
// Number (including underscores)
var num_str = str.replace(/_/g, '')
if (/^-?\d+$/.test(num_str)) return parseInt(num_str)
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str)
// String
return str
}
// Format value for display
function format_value(val) {
if (typeof val === 'string') return '"' + val + '"'
if (typeof val === 'number' && val >= 1000) {
// Add underscores to large numbers
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '_')
}
return String(val)
}
// Print configuration tree recursively
function print_config(obj, prefix = '') {
for (var key in obj) {
var val = obj[key]
var full_key = prefix ? prefix + '.' + key : key
if (val && typeof val === 'object' && !Array.isArray(val)) {
print_config(val, full_key)
} else {
log.console(full_key + ' = ' + format_value(val))
}
}
}
// Main command handling
if (args.length === 0) {
print_help()
$_.stop()
return
}
var config = shop.load_config()
if (!config) {
log.error("Failed to load .cell/cell.toml")
$_.stop()
return
}
var command = args[0]
switch (command) {
case 'help':
case '-h':
case '--help':
print_help()
break
case 'list':
log.console("# Cell Configuration")
log.console("")
print_config(config)
break
case 'get':
if (args.length < 2) {
log.error("Usage: cell config get <key>")
$_.stop()
return
}
var key = args[1]
var path = parse_key(key)
var value = get_nested(config, path)
if (value === undefined) {
log.error("Key not found: " + key)
} else if (value && typeof value === 'object' && !Array.isArray(value)) {
// Print all nested values
print_config(value, key)
} else {
log.console(key + ' = ' + format_value(value))
}
break
case 'set':
if (args.length < 3) {
log.error("Usage: cell config set <key> <value>")
$_.stop()
return
}
var key = args[1]
var value_str = args[2]
var path = parse_key(key)
var value = parse_value(value_str)
// Validate system keys
if (path[0] === 'system') {
var valid_system_keys = [
'ar_timer', 'actor_memory', 'net_service',
'reply_timeout', 'actor_max', 'stack_max'
]
if (!valid_system_keys.includes(path[1])) {
log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', '))
$_.stop()
return
}
}
set_nested(config, path, value)
shop.save_config(config)
log.console("Set " + key + " = " + format_value(value))
break
case 'actor':
// Handle actor-specific configuration
if (args.length < 3) {
log.error("Usage: cell config actor <name> <command> [options]")
$_.stop()
return
}
var actor_name = args[1]
var actor_cmd = args[2]
// Initialize actors section if needed
config.actors = config.actors || {}
config.actors[actor_name] = config.actors[actor_name] || {}
switch (actor_cmd) {
case 'list':
if (Object.keys(config.actors[actor_name]).length === 0) {
log.console("No configuration for actor: " + actor_name)
} else {
log.console("# Configuration for actor: " + actor_name)
log.console("")
print_config(config.actors[actor_name], 'actors.' + actor_name)
}
break
case 'get':
if (args.length < 4) {
log.error("Usage: cell config actor <name> get <key>")
$_.stop()
return
}
var key = args[3]
var path = parse_key(key)
var value = get_nested(config.actors[actor_name], path)
if (value === undefined) {
log.error("Key not found for actor " + actor_name + ": " + key)
} else {
log.console('actors.' + actor_name + '.' + key + ' = ' + format_value(value))
}
break
case 'set':
if (args.length < 5) {
log.error("Usage: cell config actor <name> set <key> <value>")
$_.stop()
return
}
var key = args[3]
var value_str = args[4]
var path = parse_key(key)
var value = parse_value(value_str)
set_nested(config.actors[actor_name], path, value)
shop.save_config(config)
log.console("Set actors." + actor_name + "." + key + " = " + format_value(value))
break
default:
log.error("Unknown actor command: " + actor_cmd)
log.console("Valid commands: list, get, set")
}
break
default:
log.error("Unknown command: " + command)
print_help()
}
$_.stop()

View File

@@ -1,8 +1,7 @@
(function engine() {
globalThis.cell = prosperon
cell.DOC = cell.hidden.DOCSYM
var ACTORDATA = cell.hidden.ACTORSYM
ACTORDATA = '__ACTORDATA__' // TODO: implement the actual actorsym
cell.DOC = Symbol()
var ACTORDATA = cell.hidden.actorsym
var SYSYM = '__SYSTEM__'
var ENETSERVICE = 0.1
@@ -67,7 +66,7 @@ function noop() {}
globalThis.log = new Proxy(logs, {
get(target,prop,receiver) {
if (target[prop])
return (...args) => args.forEach(arg => target[prop](arg))
return (...args) => target[prop](args.join(' '))
return noop
}
@@ -86,21 +85,30 @@ var nota = hidden.nota
// Strip hidden from cell so nothing else can access it
delete cell.hidden
var os = use_embed('os')
function disrupt(err)
{
if (overling) {
if (err) {
// with an err, this is a forceful disrupt
var reason = (err instanceof Error) ? err.stack : err
report_to_overling({type:'disrupt', reason})
} else
report_to_overling({type:'stop'})
}
log.error(err)
for (var id of underlings)
$_.stop(create_actor({id}))
if (err) {
log.console(err);
if (err.stack)
log.console(err.stack)
}
actor_mod.disrupt()
}
os.on(disrupt)
actor_mod.on_exception(disrupt)
var js = use_embed('js')
var io = use_embed('io')
@@ -236,6 +244,9 @@ globalThis.use = function use(file, ...args) {
return ret
}
globalThis.json = use('json')
var time = use('time')
var st_now = time.number()
var shop = use('shop')
var config = shop.load_config()
@@ -243,7 +254,8 @@ var default_config = {
ar_timer: 60,
actor_memory:0,
net_service:0.1,
reply_timeout:60
reply_timeout:60,
main: false,
}
config.system ??= {}
@@ -252,15 +264,33 @@ config.system.__proto__ = default_config
ENETSERVICE = config.system.net_service
REPLYTIMEOUT = config.system.reply_timeout
globalThis.json = use('json')
globalThis.text = use('text')
var time = use('time')
// Load actor-specific configuration
function load_actor_config(program) {
// Extract actor name from program path
// e.g., "prosperon/_sdl_video" or "extramath/spline"
var actor_name = program
if (program.includes('/')) {
actor_name = program
}
if (config.actors && config.actors[actor_name]) {
for (var key in config.actors[actor_name])
cell.args[key] = config.actors[actor_name][key]
}
}
var blob = use('blob')
var blob_stone = blob.prototype.stone
var blob_stonep = blob.prototype.stonep;
delete blob.prototype.stone;
delete blob.prototype.stonep;
function deepFreeze(object) {
if (object instanceof blob)
object.stone()
blob_stone.call(object);
// Retrieve the property names defined on object
var propNames = Object.keys(object);
@@ -280,14 +310,9 @@ function deepFreeze(object) {
globalThis.stone = deepFreeze
stone.p = function(object)
{
if (object instanceof blob) {
try {
object.read_logical(0)
return true
} catch(e) {
return false
}
}
if (object instanceof blob)
return blob_stonep.call(object)
return Object.isFrozen(object)
}
@@ -314,13 +339,16 @@ stone.p = function(object)
}
*/
var util = use('util')
var math = use('math')
var crypto = use('crypto')
function guid(bits = 256)
{
var guid = new blob(bits, hidden.randi)
stone(guid)
return text(guid,'h')
}
var HEADER = Symbol()
function create_actor(desc = {id:util.guid()}) {
function create_actor(desc = {id:guid()}) {
var actor = {}
actor[ACTORDATA] = desc
return actor
@@ -328,20 +356,23 @@ function create_actor(desc = {id:util.guid()}) {
var $_ = create_actor()
$_.random = crypto.random
$_.random = hidden.rand
$_.random[cell.DOC] = "returns a number between 0 and 1. There is a 50% chance that the result is less than 0.5."
$_.random_fit = crypto.random_fit
$_.random_fit = hidden.randi
$_.clock = function(fn) { return os.now() }
$_.clock = function(fn) {
actor_mod.clock(_ => {
fn(time.number())
send_messages()
})
}
$_.clock[cell.DOC] = "takes a function input value that will eventually be called with the current time in number form."
var underlings = new Set() // this is more like "all actors that are notified when we die"
var overling = undefined
var root = undefined
// Don't make $_ global - it should only be available to actor scripts
var receive_fn = undefined
var greeters = {} // Router functions for when messages are received for a specific actor
@@ -457,7 +488,7 @@ $_.receiver[cell.DOC] = "registers a function that will receive all messages..."
$_.start = function start(cb, program, ...args) {
if (!program) return
var id = util.guid()
var id = guid()
if (args.length === 1 && Array.isArray(args[0]))
args = args[0]
@@ -475,7 +506,7 @@ $_.start = function start(cb, program, ...args) {
$_.stop = function stop(actor) {
if (!actor) {
destroyself()
need_stop = true
return
}
if (!is_actor(actor))
@@ -483,7 +514,7 @@ $_.stop = function stop(actor) {
if (!underlings.has(actor[ACTORDATA].id))
throw new Error('Can only call stop on an underling or self.')
sys_msg(actor, {kind:"stop"})
sys_msg(actor, "stop")
}
$_.stop[cell.DOC] = "The stop function stops an underling."
@@ -491,7 +522,12 @@ $_.unneeded = function unneeded(fn, seconds) {
actor_mod.unneeded(fn, seconds)
}
$_.delay = function delay(fn, seconds) {
$_.delay = function delay(fn, seconds = 0) {
if (seconds <= 0) {
$_.clock(fn)
return
}
function delay_turn() {
fn()
send_messages()
@@ -505,7 +541,7 @@ var couplings = new Set()
$_.couple = function couple(actor) {
if (actor === $_) return // can't couple to self
couplings.add(actor[ACTORDATA].id)
sys_msg(actor, {kind:'couple'})
sys_msg(actor, {kind:'couple', from: $_})
log.system(`coupled to ${actor}`)
}
$_.couple[cell.DOC] = "causes this actor to stop when another actor stops."
@@ -539,7 +575,9 @@ function actor_send(actor, message) {
// message to actor in same flock
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
actor_mod.mailbox_push(actor[ACTORDATA].id, message)
var wota_blob = wota.encode(message)
// log.console(`sending wota blob of ${wota_blob.length/8} bytes`)
actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob)
return
}
@@ -564,17 +602,22 @@ function actor_send(actor, message) {
}
return
}
log.system(`Unable to send message to actor ${json.encode(actor)}`)
log.system(`Unable to send message to actor ${json.encode(actor[ACTORDATA])}`)
}
// Holds all messages queued during the current turn.
var message_queue = []
var need_stop = false
function send_messages() {
for (var msg of message_queue)
actor_send(msg.actor,msg.send)
message_queue.length = 0
if (need_stop)
disrupt()
}
var replies = {}
@@ -597,7 +640,7 @@ globalThis.send = function send(actor, message, reply) {
}
if (reply) {
var id = util.guid()
var id = guid()
replies[id] = reply
$_.delay(_ => {
if (replies[id]) {
@@ -615,7 +658,7 @@ globalThis.send = function send(actor, message, reply) {
stone(send)
if (!cell.args.id) cell.id = util.guid()
if (!cell.args.id) cell.id = guid()
else cell.id = cell.args.id
$_[ACTORDATA].id = cell.id
@@ -623,11 +666,20 @@ $_[ACTORDATA].id = cell.id
// Actor's timeslice for processing a single message
function turn(msg)
{
handle_message(msg)
var mes = wota.decode(msg)
handle_message(mes)
send_messages()
}
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system)
load_actor_config(cell.args.program)
actor_mod.register_actor(cell.id, turn, cell.args.main, config.system.ar_timer)
if (config.system.actor_memory)
js.mem_limit(config.system.actor_memory)
if (config.system.stack_max)
js.max_stacksize(config.system.stack_max);
overling = cell.args.overling
root = cell.args.root
@@ -642,28 +694,19 @@ if (overling) {
// sys messages are always dispatched immediately
function sys_msg(actor, msg)
{
actor_send(actor, {[SYSYM]:msg, from:$_})
actor_send(actor, {[SYSYM]:msg})
}
// messages sent to here get put into the cb provided to start
function report_to_overling(msg)
{
if (!overling) return
sys_msg(overling, {kind:'underling', message:msg})
sys_msg(overling, {kind:'underling', message:msg, from: $_})
}
if (!cell.args.program)
os.exit(1)
function destroyself() {
for (var id of underlings)
$_.stop(create_actor({id}))
if (overling) report_to_overling({type:'stop'})
actor_mod.destroy()
}
function handle_actor_disconnect(id) {
var greeter = greeters[id]
if (greeter) {
@@ -674,18 +717,19 @@ function handle_actor_disconnect(id) {
if (couplings.has(id)) disrupt("coupled actor died") // couplings now disrupts instead of stop
}
function handle_sysym(msg, from)
function handle_sysym(msg)
{
switch(msg.kind) {
case 'stop':
if (from[ACTORDATA].id !== overling[ACTORDATA].id)
log.error(`Got a message from a random actor ${msg.id} to stop`)
else
disrupt("got stop message")
break
case 'underling':
var from = msg.from
var greeter = greeters[from[ACTORDATA].id]
if (greeter) greeter(msg.message)
if (msg.message.type == 'disrupt')
underlings.delete(from[ACTORDATA].id)
break
case 'contact':
if (portal_fn) {
@@ -696,6 +740,7 @@ function handle_sysym(msg, from)
} else throw new Error('Got a contact message, but no portal is established.')
break
case 'couple': // from must be notified when we die
var from = msg.from
underlings.add(from[ACTORDATA].id)
log.system(`actor ${from} is coupled to me`)
break
@@ -751,16 +796,23 @@ var progPath = cell.args.program
if (io.exists(progPath + ACTOR_EXT) && !io.is_directory(progPath + ACTOR_EXT)) {
prog = progPath + ACTOR_EXT
} else if (io.exists(progPath) && io.is_directory(progPath)) {
// First check for folder's name as a file
var folderName = progPath.split('/').pop()
var folderNamePath = progPath + '/' + folderName + ACTOR_EXT
if (io.exists(folderNamePath) && !io.is_directory(folderNamePath)) {
prog = folderNamePath
} else {
// Fall back to main.ce
var mainPath = progPath + '/main' + ACTOR_EXT
if (io.exists(mainPath) && !io.is_directory(mainPath)) {
prog = mainPath
}
}
}
if (!prog)
throw new Error(cell.args.program + " not found.");
var progDir = io.realdir(prog) + "/" + prog.substring(0, prog.lastIndexOf('/'))
io.mount(progDir.replace(/\/+$/, ''))
@@ -769,12 +821,14 @@ var progContent = io.slurp(prog)
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
var val = js.eval(cell.args.program, prog_script)($_, cell.args.arg)
// queue up its first turn instead of run immediately
var startfn = js.eval(cell.args.program, prog_script);
$_.clock(_ => {
var val = startfn($_, cell.args.arg);
if (val)
throw new Error('Program must not return anything');
log.console("WAYDOWN")
send_messages()
})
})()

View File

@@ -36,6 +36,7 @@ if (io.exists(cell_man)) {
log.console(" vendor Copy all dependencies locally")
log.console(" build Compile all modules to bytecode")
log.console(" patch Create a patch for a module")
log.console(" config Manage system and actor configurations")
log.console(" help Show this help message")
log.console("")
log.console("Run 'cell help <command>' for more information on a command.")

124
scripts/jswota.cm Normal file
View File

@@ -0,0 +1,124 @@
var blob = use('blob')
var utf8 = use('utf8')
var INT = new blob(8, false)
stone(INT)
var FP_HEADER = new blob(64)
FP_HEADER.write_fit(0,56)
FP_HEADER.write_fit(1,8)
stone(FP_HEADER)
var ARRAY = new blob(8)
ARRAY.write_fit(2,8)
stone(ARRAY)
var RECORD = new blob(8)
RECORD.write_fit(3,8)
stone(RECORD)
var BLOB = new blob(8)
BLOB.write_fit(4,8)
stone(BLOB)
var TEXT = new blob(8)
TEXT.write_fit(5,8)
stone(TEXT)
var NULL_SYMBOL = new blob(64)
NULL_SYMBOL.write_fit(0,56)
NULL_SYMBOL.write_fit(7,8)
stone(NULL_SYMBOL)
var FALSE_SYMBOL = new blob(64)
FALSE_SYMBOL.write_fit(2,56)
FALSE_SYMBOL.write_fit(7,8)
stone(FALSE_SYMBOL)
var TRUE_SYMBOL = new blob(64)
TRUE_SYMBOL.write_fit(3,56)
TRUE_SYMBOL.write_fit(7,8)
stone(TRUE_SYMBOL)
var PRIVATE_SYMBOL = new blob(64)
PRIVATE_SYMBOL.write_fit(8,56)
PRIVATE_SYMBOL.write_fit(7,8)
stone(PRIVATE_SYMBOL)
var SYSTEM_SYMBOL = new blob(64)
SYSTEM_SYMBOL.write_fit(9,56)
SYSTEM_SYMBOL.write_fit(7,8)
stone(SYSTEM_SYMBOL)
var key_cache = {}
function encode_key(key)
{
if (key_cache[key])
return key_cache[key]
var encoded_key = utf8.encode(key)
var cached_blob = new blob(64 + encoded_key.length)
cached_blob.write_fit(utf8.byte_length(key), 56)
cached_blob.write_blob(TEXT)
cached_blob.write_blob(encoded_key)
stone(cached_blob)
key_cache[key] = cached_blob
return cached_blob
}
function encode_val(b, val)
{
var type = typeof val
if (type === 'number') {
b.write_blob(FP_HEADER)
b.write_number(val)
} else if (type === 'string') {
b.write_fit(utf8.byte_length(val), 56)
b.write_blob(TEXT)
b.write_blob(utf8.encode(val))
} else if (type === 'boolean') {
if (val)
b.write_blob(TRUE_SYMBOL)
else
b.write_blob(FALSE_SYMBOL)
} else if (type === 'undefined') {
b.write_blob(NULL_SYMBOL)
} else if (type === 'object') {
if (Array.isArray(val)) {
b.write_fit(val.length, 56)
b.write_blob(ARRAY)
for (var v of val)
encode_val(b, v)
} else if (val instanceof blob) {
b.write_fit(val.length, 56)
b.write_blob(BLOB)
b.write_blob(val)
} else {
var keys = Object.keys(val)
b.write_fit(keys.length, 56)
b.write_blob(RECORD)
for (var key of keys) {
if (typeof val[key] === 'function') continue
b.write_blob(encode_key(key))
encode_val(b, val[key])
}
}
}
}
function encode(val)
{
var b = new blob(8*256)// guess a good length
encode_val(b,val)
return stone(b)
}
function decode(b)
{
return undefined
}
return { INT, FP_HEADER, ARRAY, RECORD, BLOB, TEXT, NULL_SYMBOL, FALSE_SYMBOL, TRUE_SYMBOL, PRIVATE_SYMBOL, SYSTEM_SYMBOL, encode, decode }

102
scripts/man/config.man Normal file
View File

@@ -0,0 +1,102 @@
.TH CELL-CONFIG 1 "2025" "Cell" "Cell Manual"
.SH NAME
cell config \- manage system and actor configurations
.SH SYNOPSIS
.B cell config
.I command
[options]
.SH DESCRIPTION
The
.B cell config
command manages configuration settings for the Cell system. It provides
functionality to view, set, and manage both system-wide and actor-specific
configuration values stored in
.IR .cell/cell.toml .
.SH COMMANDS
.TP
.B get \fIkey\fR
Get a configuration value. Keys use dot notation (e.g., system.ar_timer).
.TP
.B set \fIkey\fR \fIvalue\fR
Set a configuration value. Values are automatically parsed to appropriate types.
.TP
.B list
List all configuration values in a hierarchical format.
.TP
.B actor \fIname\fR get \fIkey\fR
Get an actor-specific configuration value.
.TP
.B actor \fIname\fR set \fIkey\fR \fIvalue\fR
Set an actor-specific configuration value.
.TP
.B actor \fIname\fR list
List all configuration values for a specific actor.
.SH SYSTEM KEYS
.TP
.B system.ar_timer
Seconds before idle actor reclamation (default: 60)
.TP
.B system.actor_memory
MB of memory an actor can use; 0 for unbounded (default: 0)
.TP
.B system.net_service
Seconds per network service pull (default: 0.1)
.TP
.B system.reply_timeout
Seconds to hold callback for reply messages; 0 for unbounded (default: 60)
.TP
.B system.actor_max
Maximum number of simultaneous actors (default: 10,000)
.TP
.B system.stack_max
MB of memory each actor's stack can grow to (default: 0)
.SH EXAMPLES
View a system configuration value:
.PP
.nf
cell config get system.ar_timer
.fi
.PP
Set a system configuration value:
.PP
.nf
cell config set system.net_service 0.2
.fi
.PP
Configure an actor-specific setting:
.PP
.nf
cell config actor prosperon/_sdl_video set resolution 1920x1080
cell config actor extramath/spline set precision high
.fi
.PP
List all configurations:
.PP
.nf
cell config list
.fi
.PP
List actor-specific configurations:
.PP
.nf
cell config actor prosperon/_sdl_video list
.fi
.SH FILES
.TP
.I .cell/cell.toml
The main configuration file containing all system and actor settings.
.SH NOTES
Configuration values are automatically parsed to appropriate types:
.IP \(bu 2
Boolean: true, false
.IP \(bu 2
Numbers: integers and floats (underscores allowed for readability)
.IP \(bu 2
Strings: everything else
.PP
Actor configurations are loaded automatically when an actor starts,
merging the actor-specific settings into the actor's args.
.SH SEE ALSO
.BR cell (1),
.BR cell-init (1),
.BR cell-get (1)

View File

@@ -22,10 +22,6 @@ math.sigma[cell.DOC] = "Compute standard deviation of an array of numbers."
math.median[cell.DOC] = "Compute the median of an array of numbers."
math.length[cell.DOC] = "Return the length of a vector (i.e. sqrt of sum of squares)."
math.from_to[cell.DOC] = "Return an array of points from a start to an end, spaced out by a certain distance."
math.rand[cell.DOC] = "Return a random float in [0,1)."
math.randi[cell.DOC] = "Return a random 32-bit integer."
math.srand[cell.DOC] = "Seed the random number generator with the given integer, or with current time if none."
math.TAU = Math.PI * 2;
math.deg2rad = function (deg) { return deg * 0.0174533; };

View File

@@ -7,12 +7,10 @@ os.freemem[cell.DOC] = "Return the amount of free system RAM in bytes, if known.
os.hostname[cell.DOC] = "Return the system's hostname, or an empty string if not available."
os.version[cell.DOC] = "Return the OS or kernel version string, if the platform provides it."
os.exit[cell.DOC] = "Exit the application with the specified exit code."
os.now[cell.DOC] = "Return current time (in seconds as a float) with high resolution."
os.power_state[cell.DOC] = "Return a string describing power status: 'on battery', 'charging', 'charged', etc."
os.on[cell.DOC] = "Register a global callback for certain engine-wide or system-level events."
os.rusage[cell.DOC] = "Return resource usage stats for this process, if the platform supports it."
os.mallinfo[cell.DOC] = "Return detailed memory allocation info (arena size, free blocks, etc.) on some platforms."

View File

@@ -1,114 +1,30 @@
// Test runner - runs test suites in parallel and reports results
var parseq = use("parseq");
var time = use("time");
var def = arg
// Get test names from command line arguments
var tests = arg || [];
if (arg.length === 0)
arg = [
'send',
'stop',
'blob',
'clock',
'couple',
'disrupt',
'empty', // this one should be an error
'text',
'http',
'use',
'parseq',
'kill'
]
// Track overall results
var totalPassed = 0;
var totalFailed = 0;
var totalTests = 0;
var allFailures = [];
var startTime = time.number();
for (var test of def)
$_.start(e => {
// Create a requestor for each test
function run_test_requestor(testName) {
return function (cb, val) {
// Start the test actor
$_.start(function (greet) {
log.console('senging start to ' + json.encode(greet))
// Send run_tests message
send(greet.actor, {
type: 'run_tests',
test_name: testName
}, function (result) {
// Handle test results
if (result && result.type === 'test_results') {
cb(result);
} else {
cb(null, "Test " + testName + " did not return valid results");
}
});
}, "tests/" + testName, $_);
};
}
}, 'tests/' + test, $_)
// Build array of requestors
var requestors = tests.map(function (t) {
return run_test_requestor(t);
});
$_.delay($_.stop, 1)
// Run tests in parallel
if (requestors.length === 0) {
log.error("No tests specified. Usage: cell test <test1> <test2> ...");
quit(1);
}
var concurrency = 5;
var all_tests_job = parseq.par_all(requestors, undefined, concurrency);
// Handle results
all_tests_job(function (results, reason) {
if (!results) {
log.error("\n❌ Test suite failed:", reason);
quit(1);
return;
}
// Aggregate results
log.console("\n" + "=".repeat(60));
log.console("TEST RESULTS");
log.console("=".repeat(60));
for (var i = 0; i < tests.length; i++) {
var result = results[i];
var testName = tests[i];
if (result && result.type === 'test_results') {
totalPassed += result.passed;
totalFailed += result.failed;
totalTests += result.total;
var status = result.failed === 0 ? "✅ PASSED" : "❌ FAILED";
log.console("\n" + testName + ": " + status);
log.console(" Passed: " + result.passed + "/" + result.total);
if (result.failures && result.failures.length > 0) {
log.console(" Failures:");
for (var j = 0; j < result.failures.length; j++) {
var failure = result.failures[j];
allFailures.push({test: testName, failure: failure});
log.console(" - " + failure.name);
if (failure.error) {
log.console(" " + failure.error.split("\n").join("\n "));
}
}
}
if (result.duration) {
log.console(" Duration: " + result.duration + "ms");
}
}
}
// Summary
var elapsed = time.now() - startTime;
log.console("\n" + "=".repeat(60));
log.console("SUMMARY");
log.console("=".repeat(60));
log.console("Total: " + totalPassed + "/" + totalTests + " tests passed");
log.console("Failed: " + totalFailed + " tests");
log.console("Time: " + elapsed + "ms");
log.console("=".repeat(60) + "\n");
// Exit with appropriate code
quit(totalFailed === 0 ? 0 : 1);
});
// Timeout protection
$_.delay(function() {
log.error("\n⏰ TEST TIMEOUT: Tests did not complete within 30 seconds");
quit(1);
}, 30);
$_.receiver(e => {
log.console(json.encode(e))
})

View File

@@ -3,6 +3,9 @@
/* -------- helper functions ----------------------------------------- */
var blob = use('blob')
var utf8 = use('utf8')
var that = this
// Convert number to string with given radix
function to_radix(num, radix) {
@@ -97,54 +100,10 @@ function text() {
// Handle blob encoding styles
switch (style) {
case 'h': // hexadecimal
// Read 8 bits at a time for full bytes
var hex_digits = "0123456789ABCDEF";
for (var i = 0; i < bit_length; i += 8) {
var byte_val = 0;
for (var j = 0; j < 8 && i + j < bit_length; j++) {
var bit = arg.read_logical(i + j);
if (bit) byte_val |= (1 << j);
}
result += hex_digits[(byte_val >> 4) & 0xF];
result += hex_digits[byte_val & 0xF];
}
return result;
return that.blob_to_hex(arg);
case 't': // base32
var b32_digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
var bits = 0;
var value = 0;
// Read bits from LSB to MSB within each byte
for (var byte_idx = 0; byte_idx < Math.ceil(bit_length / 8); byte_idx++) {
for (var bit_in_byte = 0; bit_in_byte < 8 && byte_idx * 8 + bit_in_byte < bit_length; bit_in_byte++) {
var bit_pos = byte_idx * 8 + bit_in_byte;
var bit = arg.read_logical(bit_pos);
// Accumulate bits from MSB to LSB for base32
value = (value << 1) | (bit ? 1 : 0);
bits++;
if (bits === 5) {
result += b32_digits[value];
bits = 0;
value = 0;
}
}
}
// Handle remaining bits
if (bits > 0) {
value = value << (5 - bits);
result += b32_digits[value];
}
// Add padding to make length multiple of 8
while (result.length % 8 !== 0) {
result += '=';
}
return result;
return that.blob_to_base32(arg);
case 'b': // binary
for (var i = 0; i < bit_length; i++) {
@@ -179,114 +138,44 @@ function text() {
}
// Default: interpret as UTF-8 text
var byte_count = Math.floor(bit_length / 8);
var bytes = [];
// Read bytes from the blob
for (var i = 0; i < byte_count; i++) {
var byte_val = 0;
for (var j = 0; j < 8; j++) {
var bit_pos = i * 8 + j;
var bit = arg.read_logical(bit_pos);
if (bit) byte_val |= (1 << j);
}
bytes.push(byte_val);
}
// Convert bytes to UTF-8 string
var result = "";
var i = 0;
while (i < bytes.length) {
var b1 = bytes[i];
var codepoint;
var nextI;
if (b1 < 0x80) {
// 1-byte ASCII
codepoint = b1;
nextI = i + 1;
} else if (b1 < 0xC0) {
// Invalid start byte, treat as replacement character
codepoint = 0xFFFD;
nextI = i + 1;
} else if (b1 < 0xE0) {
// 2-byte sequence
if (i + 1 < bytes.length && (bytes[i + 1] & 0xC0) === 0x80) {
codepoint = ((b1 & 0x1F) << 6) | (bytes[i + 1] & 0x3F);
nextI = i + 2;
} else {
codepoint = 0xFFFD;
nextI = i + 1;
}
} else if (b1 < 0xF0) {
// 3-byte sequence
if (i + 2 < bytes.length &&
(bytes[i + 1] & 0xC0) === 0x80 &&
(bytes[i + 2] & 0xC0) === 0x80) {
codepoint = ((b1 & 0x0F) << 12) |
((bytes[i + 1] & 0x3F) << 6) |
(bytes[i + 2] & 0x3F);
nextI = i + 3;
} else {
codepoint = 0xFFFD;
nextI = i + 1;
}
} else if (b1 < 0xF8) {
// 4-byte sequence
if (i + 3 < bytes.length &&
(bytes[i + 1] & 0xC0) === 0x80 &&
(bytes[i + 2] & 0xC0) === 0x80 &&
(bytes[i + 3] & 0xC0) === 0x80) {
codepoint = ((b1 & 0x07) << 18) |
((bytes[i + 1] & 0x3F) << 12) |
((bytes[i + 2] & 0x3F) << 6) |
(bytes[i + 3] & 0x3F);
nextI = i + 4;
} else {
codepoint = 0xFFFD;
nextI = i + 1;
}
} else {
// Invalid start byte
codepoint = 0xFFFD;
nextI = i + 1;
}
// Convert codepoint to string
if (codepoint <= 0xFFFF) {
result += String.fromCharCode(codepoint);
} else if (codepoint <= 0x10FFFF) {
// Convert to surrogate pair for JavaScript
codepoint -= 0x10000;
result += String.fromCharCode(0xD800 + (codepoint >> 10));
result += String.fromCharCode(0xDC00 + (codepoint & 0x3FF));
} else {
result += String.fromCharCode(0xFFFD); // Replacement character
}
i = nextI;
}
return result;
// Use the utf8 module to decode the blob
return utf8.decode(arg);
}
// Handle array conversion
if (Array.isArray(arg)) {
var separator = arguments[1] || "";
// Check if all items are valid codepoints
var all_codepoints = true;
for (var i = 0; i < arg.length; i++) {
var item = arg[i];
if (!(typeof item === 'number' && item >= 0 && item <= 0x10FFFF && item === Math.floor(item))) {
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 === Math.floor(item)) {
// Unicode codepoint
result += String.fromCharCode(item);
// Single codepoint - use utf8 module
result += utf8.from_codepoints([item]);
} else {
result += String(item);
}
}
return result;
}
}
// Handle number conversion
if (typeof arg === 'number') {
@@ -521,13 +410,4 @@ function format_number(num, format) {
return null;
}
/* -------- documentation -------------------------------------------- */
text[cell.DOC] = {
doc: "Text conversion and formatting utilities",
text: "text(value, ...) → formatted text string"
};
/* -------- exports -------------------------------------------------- */
return text;

View File

@@ -1,20 +1,6 @@
var util = this
util[cell.DOC] = `
A collection of general-purpose utility functions for object manipulation, merging,
deep copying, safe property access, etc.
`
util.deepfreeze = function (obj) {
for (var key in obj) {
if (typeof obj[key] === "object") Object.deepfreeze(obj[key])
}
Object.freeze(obj)
}
util.deepfreeze[cell.DOC] = `
:param obj: The object to recursively freeze.
:return: None
Recursively freeze an object and all of its nested objects so they cannot be modified.
`
return util
util.dainty_assign = function (target, source) {
Object.keys(source).forEach(function (k) {
@@ -25,13 +11,6 @@ util.dainty_assign = function (target, source) {
else target[k] = source[k]
})
}
util.dainty_assign[cell.DOC] = `
:param target: The target object whose keys may be updated.
:param source: The source object containing new values.
:return: None
Copy non-function properties from source into matching keys of target without overwriting
keys that don't exist in target. Arrays are deep-copied, and objects are recursively assigned.
`
util.get = function (obj, path, defValue) {
if (!path) return undefined
@@ -39,23 +18,10 @@ util.get = function (obj, path, defValue) {
var result = pathArray.reduce((prevObj, key) => prevObj && prevObj[key], obj)
return result === undefined ? defValue : result
}
util.get[cell.DOC] = `
:param obj: The object to traverse.
:param path: A string like "a.b.c" or an array of path segments.
:param defValue: The default value if the property is undefined.
:return: The nested property or defValue.
Safely retrieve a nested property from obj at path (array or dot-string).
Returns defValue if the property is undefined.
`
util.isEmpty = function(o) {
return Object.keys(o).length === 0
}
util.isEmpty[cell.DOC] = `
:param o: The object to check.
:return: Boolean indicating if the object is empty.
Return true if the object has no own properties, otherwise false.
`
util.dig = function (obj, path, def = {}) {
var pp = path.split(".")
@@ -65,14 +31,6 @@ util.dig = function (obj, path, def = {}) {
obj[pp[pp.length - 1]] = def
return def
}
util.dig[cell.DOC] = `
:param obj: The root object to modify.
:param path: A dot-string specifying nested objects to create.
:param def: The value to store in the final path component, default {}.
:return: The assigned final value.
Ensure a nested path of objects exists inside obj; create objects if missing, and set
the final path component to def.
`
util.access = function (obj, name) {
var dig = name.split(".")
@@ -82,13 +40,6 @@ util.access = function (obj, name) {
}
return obj
}
util.access[cell.DOC] = `
:param obj: The object to traverse.
:param name: A dot-string path (e.g. "foo.bar.baz").
:return: The value at that path, or undefined if missing.
Traverse obj by dot-separated path name, returning the final value or undefined
if any step is missing.
`
util.mergekey = function (o1, o2, k) {
if (!o2) return
@@ -101,38 +52,17 @@ util.mergekey = function (o1, o2, k) {
}
} else o1[k] = o2[k]
}
util.mergekey[cell.DOC] = `
:param o1: The target object.
:param o2: The source object.
:param k: The key to merge.
:return: None
Helper for merge, updating key k from o2 into o1. Arrays are deep-copied and objects are
recursively merged.
`
util.merge = function (target, ...objs) {
for (var obj of objs) for (var key of Object.keys(obj)) util.mergekey(target, obj, key)
return target
}
util.merge[cell.DOC] = `
:param target: The target object.
:param objs: One or more objects to merge into target.
:return: The updated target object.
Merge all passed objects into target, copying or merging each key as needed.
Arrays are deep-copied, objects are recursively merged, etc.
`
util.copy = function (proto, ...objs) {
var c = Object.create(proto)
for (var obj of objs) Object.mixin(c, obj)
return c
}
util.copy[cell.DOC] = `
:param proto: The prototype object for the new object.
:param objs: One or more objects whose properties will be mixed in.
:return: The newly created object.
Create a new object with proto as its prototype, then mix in additional objects properties.
`
util.obj_lerp = function(a,b,t) {
if (a.lerp) return a.lerp(b, t)
@@ -142,14 +72,6 @@ util.obj_lerp = function(a,b,t) {
})
return obj
}
util.obj_lerp[cell.DOC] = `
:param a: The start object (its properties must have .lerp()).
:param b: The end object (matching properties).
:param t: Interpolation factor (0..1).
:return: A new object with interpolated properties.
Linearly interpolate between two objects a and b by factor t, assuming each property
supports .lerp().
`
util.normalizeSpacing = function normalizeSpacing(spacing) {
if (typeof spacing === 'number') {
@@ -166,23 +88,6 @@ util.normalizeSpacing = function normalizeSpacing(spacing) {
return {l:0, r:0, t:0, b:0}
}
}
util.normalizeSpacing[cell.DOC] = `
:param spacing: A number, an array of length 2 or 4, or an object with l/r/t/b.
:return: An object {l, r, t, b}.
Normalize any spacing input into a {l, r, t, b} object.
`
util.guid[cell.DOC] = `
:return: A random 32-character string (hex).
Return a random 32-character hexadecimal UUID-like string (not guaranteed RFC4122-compliant).
`
util.insertion_sort[cell.DOC] = `
:param arr: The array to be sorted in-place.
:param cmp: Comparison function cmp(a,b)->Number.
:return: The same array, sorted in-place.
In-place insertion sort of an array using cmp(a,b)->Number for ordering.
`
function deep_copy(from) {
return json.decode(json.encode(from))

View File

@@ -46,6 +46,7 @@ int blob_write_bit(blob *b, int bit_val);
int blob_write_blob(blob *b, const blob *src);
int blob_write_dec64(blob *b, double d);
int blob_write_int64(blob *b, int64_t i);
int blob_write_fit(blob *b, int64_t value, int len);
int blob_write_pad(blob *b, int block_size);
int blob_write_text(blob *b, const char *text);
@@ -54,6 +55,7 @@ int blob_read_bit(const blob *b, size_t pos, int *out_bit);
blob *blob_read_blob(const blob *b, size_t from, size_t to);
int blob_read_dec64(const blob *b, size_t from, double *out_value);
int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value);
int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value);
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read);
int blob_pad_check(const blob *b, size_t from, int block_size);
@@ -315,6 +317,23 @@ int blob_write_int64(blob *b, int64_t i) {
return 0;
}
int blob_write_fit(blob *b, int64_t value, int len) {
if (!b || b->is_stone) return -1;
if (len < 1 || len > 64) return -1;
// Check if value fits in len bits with sign
if (len < 64) {
int64_t max = (1LL << (len - 1)) - 1;
int64_t min = -(1LL << (len - 1));
if (value < min || value > max) return -1;
}
if (blob_ensure_capacity(b, len) < 0) return -1;
copy_bits_fast(&value, b->data, 0, len - 1, (int)b->length);
b->length += len;
return 0;
}
int blob_write_pad(blob *b, int block_size) {
if (!b || b->is_stone) return -1;
if (block_size <= 0) return -1;
@@ -380,6 +399,24 @@ int blob_read_int64(const blob *b, size_t from, int length, int64_t *out_value)
return 0;
}
int blob_read_fit(const blob *b, size_t from, int len, int64_t *out_value) {
if (!b || !b->is_stone || !out_value) return -1;
if (len < 1 || len > 64) return -1;
if (from + (size_t)len > b->length) return -1;
*out_value = 0;
copy_bits_fast(b->data, out_value, (int)from, (int)(from + len - 1), 0);
// Sign extend if necessary (if the high bit is set and len < 64)
if (len < 64 && (*out_value & (1LL << (len - 1)))) {
// Set all bits above len to 1 for negative numbers
int64_t mask = ~((1LL << len) - 1);
*out_value |= mask;
}
return 0;
}
int blob_read_text(const blob *b, size_t from, char **out_text, size_t *bits_read) {
if (!b || !b->is_stone || !out_text || !bits_read) return -1;
// Need at least 64 bits for length prefix

File diff suppressed because it is too large Load Diff

View File

@@ -4,23 +4,39 @@
#include <SDL3/SDL.h>
#include "quickjs.h"
#include "qjs_macros.h"
#include "qjs_blob.h"
#include "blob.h"
#define STATE_VECTOR_LENGTH 624
#define STATE_VECTOR_M 397
/* Letter type for unified message queue */
typedef enum {
LETTER_BLOB, /* Blob message */
LETTER_CALLBACK /* JSValue callback function */
} letter_type;
#define ACTOR_IDLE 0
#define ACTOR_READY 1
#define ACTOR_RUNNING 2
#define ACTOR_EXHAUSTED 3
#define ACTOR_RECLAIMING 4
#define ACTOR_SLOW 5
typedef struct letter {
letter_type type;
union {
blob *blob_data; /* For LETTER_BLOB */
JSValue callback; /* For LETTER_CALLBACK */
};
} letter;
#define STATE_VECTOR_LENGTH 312
#define STATE_VECTOR_M 156
#define ACTOR_IDLE 0 // Actor not doing anything
#define ACTOR_READY 1 // Actor ready for a turn
#define ACTOR_RUNNING 2 // Actor taking a turn
#define ACTOR_EXHAUSTED 3 // Actor waiting for GC
#define ACTOR_RECLAIMING 4 // Actor running GC
#define ACTOR_SLOW 5 // Actor going slowly; deprioritize
typedef JSValue (*MODULEFN)(JSContext *js);
extern int tracy_profiling_enabled;
typedef struct tagMTRand {
uint32_t mt[STATE_VECTOR_LENGTH];
uint64_t mt[STATE_VECTOR_LENGTH];
int32_t index;
} MTRand;
@@ -31,11 +47,9 @@ typedef struct {
typedef struct cell_rt {
JSContext *context;
JSValue cycle_fn;
JSValue idx_buffer;
JSValue on_exception;
JSValue message_handle;
JSValue unneeded;
void *init_wota;
@@ -43,63 +57,57 @@ typedef struct cell_rt {
JSValue *js_swapchains;
/* Protects JSContext usage */
SDL_Mutex *mutex;
SDL_Mutex *turn;
SDL_Mutex *mutex; /* for everything else */
SDL_Mutex *msg_mutex; /* For message queue and timers queue */
char *id;
MTRand mrand;
double unneeded_secs;
double ar_secs;
int idx_count;
/* The “mailbox” for incoming messages + a dedicated lock for it: */
void **messages;
JSValue *events;
SDL_Mutex *msg_mutex; /* For messages queue only */
struct letter *letters;
/* CHANGED FOR EVENTS: a separate lock for the actor->events queue */
struct { Uint32 key; JSValue value; } *timers;
int state;
Uint32 ar;
int need_stop;
Uint32 ar; // timer for unneeded
double ar_secs; // time for unneeded
JSValue unneeded; // fn to call before unneeded
int disrupt;
int main_thread_only;
int affinity;
JSAtom actor_sym;
JSAtom doc_sym;
const char *name; // human friendly name
} cell_rt;
extern SDL_ThreadID main_thread;
extern SDL_TLSID prosperon_id;
extern cell_rt *root_cell; // first actor in the system
cell_rt *create_actor(void *wota, void (*hook)(JSContext*));
const char *register_actor(const char *id, cell_rt *actor, int mainthread, JSValue config);
cell_rt *create_actor(void *wota);
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
void actor_disrupt(cell_rt *actor);
const char *send_message(const char *id, void *msg);
Uint32 actor_timer_cb(cell_rt *actor, SDL_TimerID id, Uint32 interval);
JSValue js_actor_delay(JSContext *js, JSValue self, int argc, JSValue *argv);
JSValue js_actor_removetimer(JSContext *js, JSValue self, int argc, JSValue *argv);
void script_startup(cell_rt *rt, void (*hook)(JSContext*));
void script_evalf(JSContext *js, const char *format, ...);
JSValue script_eval(JSContext *js, const char *file, const char *script);
void actor_unneeded(cell_rt *actor, JSValue fn, double seconds);
void script_startup(cell_rt *rt);
int uncaught_exception(JSContext *js, JSValue v);
int actor_exists(const char *id);
cell_rt *get_actor(char *id);
void set_actor_state(cell_rt *actor);
void actor_clock(cell_rt *actor, JSValue fn);
int JS_ArrayLength(JSContext *js, JSValue a);
int prosperon_mount_core(void);
// Event watchers for SDL events
extern char **event_watchers;
extern SDL_Mutex *event_watchers_mutex;
#endif

633
source/cutils.c Normal file
View File

@@ -0,0 +1,633 @@
/*
* C utilities
*
* Copyright (c) 2017 Fabrice Bellard
* Copyright (c) 2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include "cutils.h"
void pstrcpy(char *buf, int buf_size, const char *str)
{
int c;
char *q = buf;
if (buf_size <= 0)
return;
for(;;) {
c = *str++;
if (c == 0 || q >= buf + buf_size - 1)
break;
*q++ = c;
}
*q = '\0';
}
/* strcat and truncate. */
char *pstrcat(char *buf, int buf_size, const char *s)
{
int len;
len = strlen(buf);
if (len < buf_size)
pstrcpy(buf + len, buf_size - len, s);
return buf;
}
int strstart(const char *str, const char *val, const char **ptr)
{
const char *p, *q;
p = str;
q = val;
while (*q != '\0') {
if (*p != *q)
return 0;
p++;
q++;
}
if (ptr)
*ptr = p;
return 1;
}
int has_suffix(const char *str, const char *suffix)
{
size_t len = strlen(str);
size_t slen = strlen(suffix);
return (len >= slen && !memcmp(str + len - slen, suffix, slen));
}
/* Dynamic buffer package */
static void *dbuf_default_realloc(void *opaque, void *ptr, size_t size)
{
return realloc(ptr, size);
}
void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func)
{
memset(s, 0, sizeof(*s));
if (!realloc_func)
realloc_func = dbuf_default_realloc;
s->opaque = opaque;
s->realloc_func = realloc_func;
}
void dbuf_init(DynBuf *s)
{
dbuf_init2(s, NULL, NULL);
}
/* return < 0 if error */
int dbuf_realloc(DynBuf *s, size_t new_size)
{
size_t size;
uint8_t *new_buf;
if (new_size > s->allocated_size) {
if (s->error)
return -1;
size = s->allocated_size * 3 / 2;
if (size > new_size)
new_size = size;
new_buf = s->realloc_func(s->opaque, s->buf, new_size);
if (!new_buf) {
s->error = TRUE;
return -1;
}
s->buf = new_buf;
s->allocated_size = new_size;
}
return 0;
}
int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len)
{
size_t end;
end = offset + len;
if (dbuf_realloc(s, end))
return -1;
memcpy(s->buf + offset, data, len);
if (end > s->size)
s->size = end;
return 0;
}
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len)
{
if (unlikely((s->size + len) > s->allocated_size)) {
if (dbuf_realloc(s, s->size + len))
return -1;
}
memcpy_no_ub(s->buf + s->size, data, len);
s->size += len;
return 0;
}
int dbuf_put_self(DynBuf *s, size_t offset, size_t len)
{
if (unlikely((s->size + len) > s->allocated_size)) {
if (dbuf_realloc(s, s->size + len))
return -1;
}
memcpy(s->buf + s->size, s->buf + offset, len);
s->size += len;
return 0;
}
int dbuf_putc(DynBuf *s, uint8_t c)
{
return dbuf_put(s, &c, 1);
}
int dbuf_putstr(DynBuf *s, const char *str)
{
return dbuf_put(s, (const uint8_t *)str, strlen(str));
}
int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
const char *fmt, ...)
{
va_list ap;
char buf[128];
int len;
va_start(ap, fmt);
len = vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
if (len < 0)
return -1;
if (len < sizeof(buf)) {
/* fast case */
return dbuf_put(s, (uint8_t *)buf, len);
} else {
if (dbuf_realloc(s, s->size + len + 1))
return -1;
va_start(ap, fmt);
vsnprintf((char *)(s->buf + s->size), s->allocated_size - s->size,
fmt, ap);
va_end(ap);
s->size += len;
}
return 0;
}
void dbuf_free(DynBuf *s)
{
/* we test s->buf as a fail safe to avoid crashing if dbuf_free()
is called twice */
if (s->buf) {
s->realloc_func(s->opaque, s->buf, 0);
}
memset(s, 0, sizeof(*s));
}
/* Note: at most 31 bits are encoded. At most UTF8_CHAR_LEN_MAX bytes
are output. */
int unicode_to_utf8(uint8_t *buf, unsigned int c)
{
uint8_t *q = buf;
if (c < 0x80) {
*q++ = c;
} else {
if (c < 0x800) {
*q++ = (c >> 6) | 0xc0;
} else {
if (c < 0x10000) {
*q++ = (c >> 12) | 0xe0;
} else {
if (c < 0x00200000) {
*q++ = (c >> 18) | 0xf0;
} else {
if (c < 0x04000000) {
*q++ = (c >> 24) | 0xf8;
} else if (c < 0x80000000) {
*q++ = (c >> 30) | 0xfc;
*q++ = ((c >> 24) & 0x3f) | 0x80;
} else {
return 0;
}
*q++ = ((c >> 18) & 0x3f) | 0x80;
}
*q++ = ((c >> 12) & 0x3f) | 0x80;
}
*q++ = ((c >> 6) & 0x3f) | 0x80;
}
*q++ = (c & 0x3f) | 0x80;
}
return q - buf;
}
static const unsigned int utf8_min_code[5] = {
0x80, 0x800, 0x10000, 0x00200000, 0x04000000,
};
static const unsigned char utf8_first_code_mask[5] = {
0x1f, 0xf, 0x7, 0x3, 0x1,
};
/* return -1 if error. *pp is not updated in this case. max_len must
be >= 1. The maximum length for a UTF8 byte sequence is 6 bytes. */
int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp)
{
int l, c, b, i;
c = *p++;
if (c < 0x80) {
*pp = p;
return c;
}
switch(c) {
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
case 0xc4: case 0xc5: case 0xc6: case 0xc7:
case 0xc8: case 0xc9: case 0xca: case 0xcb:
case 0xcc: case 0xcd: case 0xce: case 0xcf:
case 0xd0: case 0xd1: case 0xd2: case 0xd3:
case 0xd4: case 0xd5: case 0xd6: case 0xd7:
case 0xd8: case 0xd9: case 0xda: case 0xdb:
case 0xdc: case 0xdd: case 0xde: case 0xdf:
l = 1;
break;
case 0xe0: case 0xe1: case 0xe2: case 0xe3:
case 0xe4: case 0xe5: case 0xe6: case 0xe7:
case 0xe8: case 0xe9: case 0xea: case 0xeb:
case 0xec: case 0xed: case 0xee: case 0xef:
l = 2;
break;
case 0xf0: case 0xf1: case 0xf2: case 0xf3:
case 0xf4: case 0xf5: case 0xf6: case 0xf7:
l = 3;
break;
case 0xf8: case 0xf9: case 0xfa: case 0xfb:
l = 4;
break;
case 0xfc: case 0xfd:
l = 5;
break;
default:
return -1;
}
/* check that we have enough characters */
if (l > (max_len - 1))
return -1;
c &= utf8_first_code_mask[l - 1];
for(i = 0; i < l; i++) {
b = *p++;
if (b < 0x80 || b >= 0xc0)
return -1;
c = (c << 6) | (b & 0x3f);
}
if (c < utf8_min_code[l - 1])
return -1;
*pp = p;
return c;
}
#if 0
#if defined(EMSCRIPTEN) || defined(__ANDROID__)
static void *rqsort_arg;
static int (*rqsort_cmp)(const void *, const void *, void *);
static int rqsort_cmp2(const void *p1, const void *p2)
{
return rqsort_cmp(p1, p2, rqsort_arg);
}
/* not reentrant, but not needed with emscripten */
void rqsort(void *base, size_t nmemb, size_t size,
int (*cmp)(const void *, const void *, void *),
void *arg)
{
rqsort_arg = arg;
rqsort_cmp = cmp;
qsort(base, nmemb, size, rqsort_cmp2);
}
#endif
#else
typedef void (*exchange_f)(void *a, void *b, size_t size);
typedef int (*cmp_f)(const void *, const void *, void *opaque);
static void exchange_bytes(void *a, void *b, size_t size) {
uint8_t *ap = (uint8_t *)a;
uint8_t *bp = (uint8_t *)b;
while (size-- != 0) {
uint8_t t = *ap;
*ap++ = *bp;
*bp++ = t;
}
}
static void exchange_one_byte(void *a, void *b, size_t size) {
uint8_t *ap = (uint8_t *)a;
uint8_t *bp = (uint8_t *)b;
uint8_t t = *ap;
*ap = *bp;
*bp = t;
}
static void exchange_int16s(void *a, void *b, size_t size) {
uint16_t *ap = (uint16_t *)a;
uint16_t *bp = (uint16_t *)b;
for (size /= sizeof(uint16_t); size-- != 0;) {
uint16_t t = *ap;
*ap++ = *bp;
*bp++ = t;
}
}
static void exchange_one_int16(void *a, void *b, size_t size) {
uint16_t *ap = (uint16_t *)a;
uint16_t *bp = (uint16_t *)b;
uint16_t t = *ap;
*ap = *bp;
*bp = t;
}
static void exchange_int32s(void *a, void *b, size_t size) {
uint32_t *ap = (uint32_t *)a;
uint32_t *bp = (uint32_t *)b;
for (size /= sizeof(uint32_t); size-- != 0;) {
uint32_t t = *ap;
*ap++ = *bp;
*bp++ = t;
}
}
static void exchange_one_int32(void *a, void *b, size_t size) {
uint32_t *ap = (uint32_t *)a;
uint32_t *bp = (uint32_t *)b;
uint32_t t = *ap;
*ap = *bp;
*bp = t;
}
static void exchange_int64s(void *a, void *b, size_t size) {
uint64_t *ap = (uint64_t *)a;
uint64_t *bp = (uint64_t *)b;
for (size /= sizeof(uint64_t); size-- != 0;) {
uint64_t t = *ap;
*ap++ = *bp;
*bp++ = t;
}
}
static void exchange_one_int64(void *a, void *b, size_t size) {
uint64_t *ap = (uint64_t *)a;
uint64_t *bp = (uint64_t *)b;
uint64_t t = *ap;
*ap = *bp;
*bp = t;
}
static void exchange_int128s(void *a, void *b, size_t size) {
uint64_t *ap = (uint64_t *)a;
uint64_t *bp = (uint64_t *)b;
for (size /= sizeof(uint64_t) * 2; size-- != 0; ap += 2, bp += 2) {
uint64_t t = ap[0];
uint64_t u = ap[1];
ap[0] = bp[0];
ap[1] = bp[1];
bp[0] = t;
bp[1] = u;
}
}
static void exchange_one_int128(void *a, void *b, size_t size) {
uint64_t *ap = (uint64_t *)a;
uint64_t *bp = (uint64_t *)b;
uint64_t t = ap[0];
uint64_t u = ap[1];
ap[0] = bp[0];
ap[1] = bp[1];
bp[0] = t;
bp[1] = u;
}
static inline exchange_f exchange_func(const void *base, size_t size) {
switch (((uintptr_t)base | (uintptr_t)size) & 15) {
case 0:
if (size == sizeof(uint64_t) * 2)
return exchange_one_int128;
else
return exchange_int128s;
case 8:
if (size == sizeof(uint64_t))
return exchange_one_int64;
else
return exchange_int64s;
case 4:
case 12:
if (size == sizeof(uint32_t))
return exchange_one_int32;
else
return exchange_int32s;
case 2:
case 6:
case 10:
case 14:
if (size == sizeof(uint16_t))
return exchange_one_int16;
else
return exchange_int16s;
default:
if (size == 1)
return exchange_one_byte;
else
return exchange_bytes;
}
}
static void heapsortx(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
{
uint8_t *basep = (uint8_t *)base;
size_t i, n, c, r;
exchange_f swap = exchange_func(base, size);
if (nmemb > 1) {
i = (nmemb / 2) * size;
n = nmemb * size;
while (i > 0) {
i -= size;
for (r = i; (c = r * 2 + size) < n; r = c) {
if (c < n - size && cmp(basep + c, basep + c + size, opaque) <= 0)
c += size;
if (cmp(basep + r, basep + c, opaque) > 0)
break;
swap(basep + r, basep + c, size);
}
}
for (i = n - size; i > 0; i -= size) {
swap(basep, basep + i, size);
for (r = 0; (c = r * 2 + size) < i; r = c) {
if (c < i - size && cmp(basep + c, basep + c + size, opaque) <= 0)
c += size;
if (cmp(basep + r, basep + c, opaque) > 0)
break;
swap(basep + r, basep + c, size);
}
}
}
}
static inline void *med3(void *a, void *b, void *c, cmp_f cmp, void *opaque)
{
return cmp(a, b, opaque) < 0 ?
(cmp(b, c, opaque) < 0 ? b : (cmp(a, c, opaque) < 0 ? c : a )) :
(cmp(b, c, opaque) > 0 ? b : (cmp(a, c, opaque) < 0 ? a : c ));
}
/* pointer based version with local stack and insertion sort threshhold */
void rqsort(void *base, size_t nmemb, size_t size, cmp_f cmp, void *opaque)
{
struct { uint8_t *base; size_t count; int depth; } stack[50], *sp = stack;
uint8_t *ptr, *pi, *pj, *plt, *pgt, *top, *m;
size_t m4, i, lt, gt, span, span2;
int c, depth;
exchange_f swap = exchange_func(base, size);
exchange_f swap_block = exchange_func(base, size | 128);
if (nmemb < 2 || size <= 0)
return;
sp->base = (uint8_t *)base;
sp->count = nmemb;
sp->depth = 0;
sp++;
while (sp > stack) {
sp--;
ptr = sp->base;
nmemb = sp->count;
depth = sp->depth;
while (nmemb > 6) {
if (++depth > 50) {
/* depth check to ensure worst case logarithmic time */
heapsortx(ptr, nmemb, size, cmp, opaque);
nmemb = 0;
break;
}
/* select median of 3 from 1/4, 1/2, 3/4 positions */
/* should use median of 5 or 9? */
m4 = (nmemb >> 2) * size;
m = med3(ptr + m4, ptr + 2 * m4, ptr + 3 * m4, cmp, opaque);
swap(ptr, m, size); /* move the pivot to the start or the array */
i = lt = 1;
pi = plt = ptr + size;
gt = nmemb;
pj = pgt = top = ptr + nmemb * size;
for (;;) {
while (pi < pj && (c = cmp(ptr, pi, opaque)) >= 0) {
if (c == 0) {
swap(plt, pi, size);
lt++;
plt += size;
}
i++;
pi += size;
}
while (pi < (pj -= size) && (c = cmp(ptr, pj, opaque)) <= 0) {
if (c == 0) {
gt--;
pgt -= size;
swap(pgt, pj, size);
}
}
if (pi >= pj)
break;
swap(pi, pj, size);
i++;
pi += size;
}
/* array has 4 parts:
* from 0 to lt excluded: elements identical to pivot
* from lt to pi excluded: elements smaller than pivot
* from pi to gt excluded: elements greater than pivot
* from gt to n excluded: elements identical to pivot
*/
/* move elements identical to pivot in the middle of the array: */
/* swap values in ranges [0..lt[ and [i-lt..i[
swapping the smallest span between lt and i-lt is sufficient
*/
span = plt - ptr;
span2 = pi - plt;
lt = i - lt;
if (span > span2)
span = span2;
swap_block(ptr, pi - span, span);
/* swap values in ranges [gt..top[ and [i..top-(top-gt)[
swapping the smallest span between top-gt and gt-i is sufficient
*/
span = top - pgt;
span2 = pgt - pi;
pgt = top - span2;
gt = nmemb - (gt - i);
if (span > span2)
span = span2;
swap_block(pi, top - span, span);
/* now array has 3 parts:
* from 0 to lt excluded: elements smaller than pivot
* from lt to gt excluded: elements identical to pivot
* from gt to n excluded: elements greater than pivot
*/
/* stack the larger segment and keep processing the smaller one
to minimize stack use for pathological distributions */
if (lt > nmemb - gt) {
sp->base = ptr;
sp->count = lt;
sp->depth = depth;
sp++;
ptr = pgt;
nmemb -= gt;
} else {
sp->base = pgt;
sp->count = nmemb - gt;
sp->depth = depth;
sp++;
nmemb = lt;
}
}
/* Use insertion sort for small fragments */
for (pi = ptr + size, top = ptr + nmemb * size; pi < top; pi += size) {
for (pj = pi; pj > ptr && cmp(pj - size, pj, opaque) > 0; pj -= size)
swap(pj, pj - size, size);
}
}
}
#endif

367
source/cutils.h Normal file
View File

@@ -0,0 +1,367 @@
/*
* C utilities
*
* Copyright (c) 2017 Fabrice Bellard
* Copyright (c) 2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef CUTILS_H
#define CUTILS_H
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#define force_inline inline __attribute__((always_inline))
#define no_inline __attribute__((noinline))
#define __maybe_unused __attribute__((unused))
#define xglue(x, y) x ## y
#define glue(x, y) xglue(x, y)
#define stringify(s) tostring(s)
#define tostring(s) #s
#ifndef offsetof
#define offsetof(type, field) ((size_t) &((type *)0)->field)
#endif
#ifndef countof
#define countof(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef container_of
/* return the pointer of type 'type *' containing 'ptr' as field 'member' */
#define container_of(ptr, type, member) ((type *)((uint8_t *)(ptr) - offsetof(type, member)))
#endif
#if !defined(_MSC_VER) && defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#define minimum_length(n) static n
#else
#define minimum_length(n) n
#endif
typedef int BOOL;
#ifndef FALSE
enum {
FALSE = 0,
TRUE = 1,
};
#endif
void pstrcpy(char *buf, int buf_size, const char *str);
char *pstrcat(char *buf, int buf_size, const char *s);
int strstart(const char *str, const char *val, const char **ptr);
int has_suffix(const char *str, const char *suffix);
/* Prevent UB when n == 0 and (src == NULL or dest == NULL) */
static inline void memcpy_no_ub(void *dest, const void *src, size_t n) {
if (n)
memcpy(dest, src, n);
}
static inline int max_int(int a, int b)
{
if (a > b)
return a;
else
return b;
}
static inline int min_int(int a, int b)
{
if (a < b)
return a;
else
return b;
}
static inline uint32_t max_uint32(uint32_t a, uint32_t b)
{
if (a > b)
return a;
else
return b;
}
static inline uint32_t min_uint32(uint32_t a, uint32_t b)
{
if (a < b)
return a;
else
return b;
}
static inline int64_t max_int64(int64_t a, int64_t b)
{
if (a > b)
return a;
else
return b;
}
static inline int64_t min_int64(int64_t a, int64_t b)
{
if (a < b)
return a;
else
return b;
}
/* WARNING: undefined if a = 0 */
static inline int clz32(unsigned int a)
{
return __builtin_clz(a);
}
/* WARNING: undefined if a = 0 */
static inline int clz64(uint64_t a)
{
return __builtin_clzll(a);
}
/* WARNING: undefined if a = 0 */
static inline int ctz32(unsigned int a)
{
return __builtin_ctz(a);
}
/* WARNING: undefined if a = 0 */
static inline int ctz64(uint64_t a)
{
return __builtin_ctzll(a);
}
struct __attribute__((packed)) packed_u64 {
uint64_t v;
};
struct __attribute__((packed)) packed_u32 {
uint32_t v;
};
struct __attribute__((packed)) packed_u16 {
uint16_t v;
};
static inline uint64_t get_u64(const uint8_t *tab)
{
return ((const struct packed_u64 *)tab)->v;
}
static inline int64_t get_i64(const uint8_t *tab)
{
return (int64_t)((const struct packed_u64 *)tab)->v;
}
static inline void put_u64(uint8_t *tab, uint64_t val)
{
((struct packed_u64 *)tab)->v = val;
}
static inline uint32_t get_u32(const uint8_t *tab)
{
return ((const struct packed_u32 *)tab)->v;
}
static inline int32_t get_i32(const uint8_t *tab)
{
return (int32_t)((const struct packed_u32 *)tab)->v;
}
static inline void put_u32(uint8_t *tab, uint32_t val)
{
((struct packed_u32 *)tab)->v = val;
}
static inline uint32_t get_u16(const uint8_t *tab)
{
return ((const struct packed_u16 *)tab)->v;
}
static inline int32_t get_i16(const uint8_t *tab)
{
return (int16_t)((const struct packed_u16 *)tab)->v;
}
static inline void put_u16(uint8_t *tab, uint16_t val)
{
((struct packed_u16 *)tab)->v = val;
}
static inline uint32_t get_u8(const uint8_t *tab)
{
return *tab;
}
static inline int32_t get_i8(const uint8_t *tab)
{
return (int8_t)*tab;
}
static inline void put_u8(uint8_t *tab, uint8_t val)
{
*tab = val;
}
#ifndef bswap16
static inline uint16_t bswap16(uint16_t x)
{
return (x >> 8) | (x << 8);
}
#endif
#ifndef bswap32
static inline uint32_t bswap32(uint32_t v)
{
return ((v & 0xff000000) >> 24) | ((v & 0x00ff0000) >> 8) |
((v & 0x0000ff00) << 8) | ((v & 0x000000ff) << 24);
}
#endif
#ifndef bswap64
static inline uint64_t bswap64(uint64_t v)
{
return ((v & ((uint64_t)0xff << (7 * 8))) >> (7 * 8)) |
((v & ((uint64_t)0xff << (6 * 8))) >> (5 * 8)) |
((v & ((uint64_t)0xff << (5 * 8))) >> (3 * 8)) |
((v & ((uint64_t)0xff << (4 * 8))) >> (1 * 8)) |
((v & ((uint64_t)0xff << (3 * 8))) << (1 * 8)) |
((v & ((uint64_t)0xff << (2 * 8))) << (3 * 8)) |
((v & ((uint64_t)0xff << (1 * 8))) << (5 * 8)) |
((v & ((uint64_t)0xff << (0 * 8))) << (7 * 8));
}
#endif
/* XXX: should take an extra argument to pass slack information to the caller */
typedef void *DynBufReallocFunc(void *opaque, void *ptr, size_t size);
typedef struct DynBuf {
uint8_t *buf;
size_t size;
size_t allocated_size;
BOOL error; /* true if a memory allocation error occurred */
DynBufReallocFunc *realloc_func;
void *opaque; /* for realloc_func */
} DynBuf;
void dbuf_init(DynBuf *s);
void dbuf_init2(DynBuf *s, void *opaque, DynBufReallocFunc *realloc_func);
int dbuf_realloc(DynBuf *s, size_t new_size);
int dbuf_write(DynBuf *s, size_t offset, const uint8_t *data, size_t len);
int dbuf_put(DynBuf *s, const uint8_t *data, size_t len);
int dbuf_put_self(DynBuf *s, size_t offset, size_t len);
int dbuf_putc(DynBuf *s, uint8_t c);
int dbuf_putstr(DynBuf *s, const char *str);
static inline int dbuf_put_u16(DynBuf *s, uint16_t val)
{
return dbuf_put(s, (uint8_t *)&val, 2);
}
static inline int dbuf_put_u32(DynBuf *s, uint32_t val)
{
return dbuf_put(s, (uint8_t *)&val, 4);
}
static inline int dbuf_put_u64(DynBuf *s, uint64_t val)
{
return dbuf_put(s, (uint8_t *)&val, 8);
}
int __attribute__((format(printf, 2, 3))) dbuf_printf(DynBuf *s,
const char *fmt, ...);
void dbuf_free(DynBuf *s);
static inline BOOL dbuf_error(DynBuf *s) {
return s->error;
}
static inline void dbuf_set_error(DynBuf *s)
{
s->error = TRUE;
}
#define UTF8_CHAR_LEN_MAX 6
int unicode_to_utf8(uint8_t *buf, unsigned int c);
int unicode_from_utf8(const uint8_t *p, int max_len, const uint8_t **pp);
static inline BOOL is_surrogate(uint32_t c)
{
return (c >> 11) == (0xD800 >> 11); // 0xD800-0xDFFF
}
static inline BOOL is_hi_surrogate(uint32_t c)
{
return (c >> 10) == (0xD800 >> 10); // 0xD800-0xDBFF
}
static inline BOOL is_lo_surrogate(uint32_t c)
{
return (c >> 10) == (0xDC00 >> 10); // 0xDC00-0xDFFF
}
static inline uint32_t get_hi_surrogate(uint32_t c)
{
return (c >> 10) - (0x10000 >> 10) + 0xD800;
}
static inline uint32_t get_lo_surrogate(uint32_t c)
{
return (c & 0x3FF) | 0xDC00;
}
static inline uint32_t from_surrogate(uint32_t hi, uint32_t lo)
{
return 0x10000 + 0x400 * (hi - 0xD800) + (lo - 0xDC00);
}
static inline int from_hex(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
else if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
else if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
else
return -1;
}
void rqsort(void *base, size_t nmemb, size_t size,
int (*cmp)(const void *, const void *, void *),
void *arg);
static inline uint64_t float64_as_uint64(double d)
{
union {
double d;
uint64_t u64;
} u;
u.d = d;
return u.u64;
}
static inline double uint64_as_float64(uint64_t u64)
{
union {
double d;
uint64_t u64;
} u;
u.u64 = u64;
return u.d;
}
#endif /* CUTILS_H */

1620
source/dtoa.c Normal file

File diff suppressed because it is too large Load Diff

83
source/dtoa.h Normal file
View File

@@ -0,0 +1,83 @@
/*
* Tiny float64 printing and parsing library
*
* Copyright (c) 2024 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
//#define JS_DTOA_DUMP_STATS
/* maximum number of digits for fixed and frac formats */
#define JS_DTOA_MAX_DIGITS 101
/* radix != 10 is only supported with flags = JS_DTOA_FORMAT_FREE */
/* use as many digits as necessary */
#define JS_DTOA_FORMAT_FREE (0 << 0)
/* use n_digits significant digits (1 <= n_digits <= JS_DTOA_MAX_DIGITS) */
#define JS_DTOA_FORMAT_FIXED (1 << 0)
/* force fractional format: [-]dd.dd with n_digits fractional digits.
0 <= n_digits <= JS_DTOA_MAX_DIGITS */
#define JS_DTOA_FORMAT_FRAC (2 << 0)
#define JS_DTOA_FORMAT_MASK (3 << 0)
/* select exponential notation either in fixed or free format */
#define JS_DTOA_EXP_AUTO (0 << 2)
#define JS_DTOA_EXP_ENABLED (1 << 2)
#define JS_DTOA_EXP_DISABLED (2 << 2)
#define JS_DTOA_EXP_MASK (3 << 2)
#define JS_DTOA_MINUS_ZERO (1 << 4) /* show the minus sign for -0 */
/* only accepts integers (no dot, no exponent) */
#define JS_ATOD_INT_ONLY (1 << 0)
/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */
#define JS_ATOD_ACCEPT_BIN_OCT (1 << 1)
/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */
#define JS_ATOD_ACCEPT_LEGACY_OCTAL (1 << 2)
/* accept _ between digits as a digit separator */
#define JS_ATOD_ACCEPT_UNDERSCORES (1 << 3)
typedef struct {
uint64_t mem[37];
} JSDTOATempMem;
typedef struct {
uint64_t mem[27];
} JSATODTempMem;
/* return a maximum bound of the string length */
int js_dtoa_max_len(double d, int radix, int n_digits, int flags);
/* return the string length */
int js_dtoa(char *buf, double d, int radix, int n_digits, int flags,
JSDTOATempMem *tmp_mem);
double js_atod(const char *str, const char **pnext, int radix, int flags,
JSATODTempMem *tmp_mem);
#ifdef JS_DTOA_DUMP_STATS
void js_dtoa_dump_stats(void);
#endif
/* additional exported functions */
size_t u32toa(char *buf, uint32_t n);
size_t i32toa(char *buf, int32_t n);
size_t u64toa(char *buf, uint64_t n);
size_t i64toa(char *buf, int64_t n);
size_t u64toa_radix(char *buf, uint64_t n, unsigned int radix);
size_t i64toa_radix(char *buf, int64_t n, unsigned int radix);

View File

@@ -2,6 +2,7 @@
#include "font.h"
#include "datastream.h"
#include "qjs_sdl.h"
#include "qjs_sdl_input.h"
#include "qjs_io.h"
#include "qjs_fd.h"
#include "transform.h"
@@ -52,6 +53,10 @@
#include "qjs_debug.h"
#include "qjs_sdl_surface.h"
#include "qjs_sdl.h"
#include "qjs_kim.h"
#include "qjs_utf8.h"
#include "qjs_fit.h"
#include "qjs_text.h"
#ifndef NSTEAM
#include "qjs_steam.h"
#endif
@@ -98,55 +103,59 @@ transform *js2transform(JSContext *js, JSValue v);
//#include <cblas.h>
#endif
// Random number generation constants
#define UPPER_MASK 0x80000000
#define LOWER_MASK 0x7fffffff
#define TEMPERING_MASK_B 0x9d2c5680
#define TEMPERING_MASK_C 0xefc60000
// Random number generation constants for MT19937-64
#define NN STATE_VECTOR_LENGTH
#define MM STATE_VECTOR_M
#define MATRIX_A 0xB5026F5AA96619E9ULL
#define UM 0xFFFFFFFF80000000ULL /* Most significant 33 bits */
#define LM 0x7FFFFFFFULL /* Least significant 31 bits */
// Random number generation functions
void m_seedRand(MTRand* rand, uint32_t seed) {
/* set initial seeds to mt[STATE_VECTOR_LENGTH] using the generator
* from Line 25 of Table 1 in: Donald Knuth, "The Art of Computer
* Programming," Vol. 2 (2nd Ed.) pp.102.
*/
rand->mt[0] = seed & 0xffffffff;
for(rand->index=1; rand->index<STATE_VECTOR_LENGTH; rand->index++) {
rand->mt[rand->index] = (6069 * rand->mt[rand->index-1]) & 0xffffffff;
void m_seedRand(MTRand* rand, uint64_t seed) {
rand->mt[0] = seed;
for(rand->index = 1; rand->index < NN; rand->index++) {
rand->mt[rand->index] = (6364136223846793005ULL * (rand->mt[rand->index-1] ^ (rand->mt[rand->index-1] >> 62)) + rand->index);
}
}
uint32_t genRandLong(MTRand* rand) {
uint32_t y;
static uint32_t mag[2] = {0x0, 0x9908b0df}; /* mag[x] = x * 0x9908b0df for x = 0,1 */
if(rand->index >= STATE_VECTOR_LENGTH || rand->index < 0) {
/* generate STATE_VECTOR_LENGTH words at a time */
int32_t kk;
if(rand->index >= STATE_VECTOR_LENGTH+1 || rand->index < 0) {
m_seedRand(rand, 4357);
int64_t genRandLong(MTRand* rand) {
int i;
uint64_t x;
static uint64_t mag01[2] = {0ULL, MATRIX_A};
if (rand->index >= NN) { /* generate NN words at one time */
/* if init_genrand64() has not been called, */
/* a default initial seed is used */
if (rand->index == NN+1)
m_seedRand(rand, 5489ULL);
for (i = 0; i < NN-MM; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+MM] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
for(kk=0; kk<STATE_VECTOR_LENGTH-STATE_VECTOR_M; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+STATE_VECTOR_M] ^ (y >> 1) ^ mag[y & 0x1];
for (; i < NN-1; i++) {
x = (rand->mt[i] & UM) | (rand->mt[i+1] & LM);
rand->mt[i] = rand->mt[i+(MM-NN)] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
}
for(; kk<STATE_VECTOR_LENGTH-1; kk++) {
y = (rand->mt[kk] & UPPER_MASK) | (rand->mt[kk+1] & LOWER_MASK);
rand->mt[kk] = rand->mt[kk+(STATE_VECTOR_M-STATE_VECTOR_LENGTH)] ^ (y >> 1) ^ mag[y & 0x1];
}
y = (rand->mt[STATE_VECTOR_LENGTH-1] & UPPER_MASK) | (rand->mt[0] & LOWER_MASK);
rand->mt[STATE_VECTOR_LENGTH-1] = rand->mt[STATE_VECTOR_M-1] ^ (y >> 1) ^ mag[y & 0x1];
x = (rand->mt[NN-1] & UM) | (rand->mt[0] & LM);
rand->mt[NN-1] = rand->mt[MM-1] ^ (x>>1) ^ mag01[(int)(x&1ULL)];
rand->index = 0;
}
y = rand->mt[rand->index++];
y ^= (y >> 11);
y ^= (y << 7) & TEMPERING_MASK_B;
y ^= (y << 15) & TEMPERING_MASK_C;
y ^= (y >> 18);
return y;
x = rand->mt[rand->index++];
x ^= (x >> 29) & 0x5555555555555555ULL;
x ^= (x << 17) & 0x71D67FFFEDA60000ULL;
x ^= (x << 37) & 0xFFF7EEE000000000ULL;
x ^= (x >> 43);
return (int64_t)(x & 0x000FFFFFFFFFFFFFULL); /* return 52-bit value safe for JS */
}
double genRand(MTRand* rand) {
return((double)genRandLong(rand) / (uint32_t)0xffffffff);
/* generates a random number on [0,1)-real-interval */
return (genRandLong(rand) >> 11) * (1.0/9007199254740992.0);
}
double rand_range(JSContext *js, double min, double max)
@@ -943,15 +952,31 @@ static const JSCFunctionListEntry js_number_funcs[] = {
PROTO_FUNC_DEF(number, lerp, 2),
};
static uint32_t rng_state = 123456789;
static uint32_t xorshift32(){
uint32_t x = rng_state;
x ^= x << 13;
x ^= x >> 17;
x ^= x << 5;
return rng_state = x;
}
JSC_CCALL(os_guid,
SDL_GUID guid;
randombytes(guid.data, 16);
uint8_t data[16];
for(int i = 0; i < 4; i++){
uint32_t v = xorshift32();
memcpy(&data[i*4], &v, 4);
}
char guid_str[33];
static const char hex[] = "0123456789abcdef";
char buf[32];
for(int i = 0; i < 16; i++){
uint8_t b = data[i];
buf[i*2 ] = hex[b >> 4];
buf[i*2 + 1] = hex[b & 0x0f];
}
SDL_GUIDToString(guid, guid_str, 33);
return JS_NewString(js,guid_str);
return JS_NewStringLen(js, buf, 32);
)
JSC_SCALL(console_print, printf("%s", str); )
@@ -1193,6 +1218,21 @@ JSC_CCALL(os_make_font,
JSC_SCALL(os_system, ret = number2js(js,system(str)); )
JSC_CCALL(os_rand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRand(mrand));
)
JSC_CCALL(os_randi,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return JS_NewInt64(js, genRandLong(mrand));
)
JSC_CCALL(os_srand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
m_seedRand(mrand, js2number(js,argv[0]));
)
JSValue make_color_buffer(JSContext *js, colorf c, int verts)
{
HMM_Vec4 *colordata = malloc(sizeof(*colordata)*verts);
@@ -1526,6 +1566,8 @@ JSC_CCALL(os_value_id,
#include "qjs_time.h"
#include "qjs_http.h"
#include "qjs_wota.h"
#include "qjs_socket.h"
#include "qjs_nota.h"
//JSValue js_imgui_use(JSContext *js);
#define MISTLINE(NAME) (ModuleEntry){#NAME, js_##NAME##_use}
@@ -1546,11 +1588,18 @@ void ffi_load(JSContext *js)
// extra
arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use}));
arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use}));
arrput(rt->module_registry, MISTLINE(socket));
arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use}));
arrput(rt->module_registry, MISTLINE(qr));
arrput(rt->module_registry, MISTLINE(http));
arrput(rt->module_registry, MISTLINE(crypto));
arrput(rt->module_registry, MISTLINE(miniz));
arrput(rt->module_registry, MISTLINE(kim));
arrput(rt->module_registry, MISTLINE(utf8));
arrput(rt->module_registry, MISTLINE(fit));
arrput(rt->module_registry, MISTLINE(text));
arrput(rt->module_registry, MISTLINE(wota));
arrput(rt->module_registry, MISTLINE(nota));
// power user
arrput(rt->module_registry, MISTLINE(js));
@@ -1585,15 +1634,6 @@ void ffi_load(JSContext *js)
JSValue prosp = JS_NewObject(js);
JS_SetPropertyStr(js,globalThis,"prosperon", prosp);
JSValue c_types = JS_NewObject(js);
JS_SetPropertyStr(js,prosp, "c_types", c_types);
QJSCLASSPREP_FUNCS(font);
QJSCLASSPREP_FUNCS(datastream);
JSValue jsobject = JS_GetPropertyStr(js,globalThis, "Object");
JS_SetPropertyStr(js, jsobject, "id", JS_NewCFunction(js, js_os_value_id, "id", 1));
JS_FreeValue(js,jsobject);
JSValue jsarray = JS_GetPropertyStr(js,globalThis, "Array");
JSValue array_proto = JS_GetPropertyStr(js,jsarray, "prototype");
@@ -1607,9 +1647,6 @@ void ffi_load(JSContext *js)
JS_FreeValue(js,jsnumber);
JS_FreeValue(js,number_proto);
//JS_SetPropertyStr(js,prosp, "version", JS_NewString(js,CELL_VERSION));
//JS_SetPropertyStr(js,prosp,"revision",JS_NewString(js,CELL_COMMIT));
JSValue hidden_fn = JS_NewObject(js);
// add engine.js-only functions to hidden_fn. It should grab them and then remove so nothing else can use them.
@@ -1623,6 +1660,17 @@ void ffi_load(JSContext *js)
// Add functions that should only be accessible to engine.js
JS_SetPropertyStr(js, hidden_fn, "use_dyn", JS_NewCFunction(js, js_os_use_dyn, "use_dyn", 1));
JS_SetPropertyStr(js, hidden_fn, "use_embed", JS_NewCFunction(js, js_os_use_embed, "use_embed", 1));
JS_SetPropertyStr(js, hidden_fn, "rand", JS_NewCFunction(js, js_os_rand, "rand", 0));
JS_SetPropertyStr(js, hidden_fn, "randi", JS_NewCFunction(js, js_os_randi, "randi", 0));
JS_SetPropertyStr(js, hidden_fn, "srand", JS_NewCFunction(js, js_os_srand, "srand", 1));
const char actorsym_script[] = "var sym = Symbol(`actordata`); sym;";
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", JS_EVAL_FLAG_STRICT);
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
rt->actor_sym = JS_ValueToAtom(js, actorsym);
if (rt->init_wota) {
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, rt->init_wota));
@@ -1631,16 +1679,6 @@ void ffi_load(JSContext *js)
rt->init_wota = NULL;
}
cell_rt *actor = JS_GetContextOpaque(js);
JSValue actorsym = js_newsymbol(js, "actor symbol", 0);
actor->actor_sym = JS_ValueToAtom(js, actorsym);
JS_SetPropertyStr(js, hidden_fn, "ACTORDATA", JS_DupValue(js,actorsym));
JS_FreeValue(js, actorsym);
JSValue docsym = js_newsymbol(js, "+documentation+", 0);
actor->doc_sym = JS_ValueToAtom(js, docsym);
JS_SetPropertyStr(js, hidden_fn, "DOCSYM", JS_DupValue(js,docsym));
JS_FreeValue(js,docsym);
JS_SetPropertyStr(js, prosp, "hidden", hidden_fn);
JS_FreeValue(js,globalThis);

View File

@@ -42,8 +42,8 @@ typedef struct tagMTRand MTRand;
// Random number generation functions
double genRand(MTRand *mrand);
uint32_t genRandLong(MTRand *mrand);
void m_seedRand(MTRand *mrand, uint32_t seed);
int64_t genRandLong(MTRand *mrand);
void m_seedRand(MTRand *mrand, uint64_t seed);
double rand_range(JSContext *js, double min, double max);
// Common data structures

View File

@@ -10,17 +10,17 @@ void kim_to_utf8(char **kim, char **utf, int runes);
// Return the number of runes in a utf8 string
int utf8_count(const char *utf8);
int decode_utf8(char **s);
void encode_utf8(char **s, int code);
void encode_kim(char **s, int code);
int decode_kim(char **s);
#ifdef KIM_IMPLEMENTATION
#define KIM_CONT 0x80
#define KIM_DATA 0x7f
#define CONTINUE(CHAR) (CHAR>>7)
int decode_utf8(char **s);
void encode_utf8(char **s, int code);
static void encode_kim(char **s, int code);
int decode_kim(char **s);
static inline int utf8_bytes(char c)
{
int bytes = __builtin_clz(~(c));
@@ -70,7 +70,7 @@ void encode_utf8(char **s, int rune) {
}
// write and advance s with rune in kim
static inline void encode_kim(char **s, int rune)
void encode_kim(char **s, int rune)
{
if (rune < KIM_CONT) {
**s = 0 | (KIM_DATA & rune);

2529
source/libregexp.c Normal file

File diff suppressed because it is too large Load Diff

60
source/libregexp.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* Regular Expression Engine
*
* Copyright (c) 2017-2018 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef LIBREGEXP_H
#define LIBREGEXP_H
#include <stddef.h>
#include <stdint.h>
#define LRE_FLAG_GLOBAL (1 << 0)
#define LRE_FLAG_IGNORECASE (1 << 1)
#define LRE_FLAG_MULTILINE (1 << 2)
#define LRE_FLAG_DOTALL (1 << 3)
#define LRE_FLAG_UNICODE (1 << 4)
#define LRE_FLAG_STICKY (1 << 5)
#define LRE_FLAG_INDICES (1 << 6) /* Unused by libregexp, just recorded. */
#define LRE_FLAG_NAMED_GROUPS (1 << 7) /* named groups are present in the regexp */
#define LRE_RET_MEMORY_ERROR (-1)
#define LRE_RET_TIMEOUT (-2)
uint8_t *lre_compile(int *plen, char *error_msg, int error_msg_size,
const char *buf, size_t buf_len, int re_flags,
void *opaque);
int lre_get_capture_count(const uint8_t *bc_buf);
int lre_get_flags(const uint8_t *bc_buf);
const char *lre_get_groupnames(const uint8_t *bc_buf);
int lre_exec(uint8_t **capture,
const uint8_t *bc_buf, const uint8_t *cbuf, int cindex, int clen,
int cbuf_type, void *opaque);
int lre_parse_escape(const uint8_t **pp, int allow_utf16);
/* must be provided by the user, return non zero if overflow */
int lre_check_stack_overflow(void *opaque, size_t alloca_size);
/* must be provided by the user, return non zero if time out */
int lre_check_timeout(void *opaque);
void *lre_realloc(void *opaque, void *ptr, size_t size);
#endif /* LIBREGEXP_H */

4730
source/libunicode-table.h Normal file

File diff suppressed because it is too large Load Diff

1910
source/libunicode.c Normal file

File diff suppressed because it is too large Load Diff

182
source/libunicode.h Normal file
View File

@@ -0,0 +1,182 @@
/*
* Unicode utilities
*
* Copyright (c) 2017-2018 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef LIBUNICODE_H
#define LIBUNICODE_H
#include <stdint.h>
/* define it to include all the unicode tables (40KB larger) */
#define CONFIG_ALL_UNICODE
#define LRE_CC_RES_LEN_MAX 3
/* char ranges */
typedef struct {
int len; /* in points, always even */
int size;
uint32_t *points; /* points sorted by increasing value */
void *mem_opaque;
void *(*realloc_func)(void *opaque, void *ptr, size_t size);
} CharRange;
typedef enum {
CR_OP_UNION,
CR_OP_INTER,
CR_OP_XOR,
} CharRangeOpEnum;
void cr_init(CharRange *cr, void *mem_opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
void cr_free(CharRange *cr);
int cr_realloc(CharRange *cr, int size);
int cr_copy(CharRange *cr, const CharRange *cr1);
static inline int cr_add_point(CharRange *cr, uint32_t v)
{
if (cr->len >= cr->size) {
if (cr_realloc(cr, cr->len + 1))
return -1;
}
cr->points[cr->len++] = v;
return 0;
}
static inline int cr_add_interval(CharRange *cr, uint32_t c1, uint32_t c2)
{
if ((cr->len + 2) > cr->size) {
if (cr_realloc(cr, cr->len + 2))
return -1;
}
cr->points[cr->len++] = c1;
cr->points[cr->len++] = c2;
return 0;
}
int cr_union1(CharRange *cr, const uint32_t *b_pt, int b_len);
static inline int cr_union_interval(CharRange *cr, uint32_t c1, uint32_t c2)
{
uint32_t b_pt[2];
b_pt[0] = c1;
b_pt[1] = c2 + 1;
return cr_union1(cr, b_pt, 2);
}
int cr_op(CharRange *cr, const uint32_t *a_pt, int a_len,
const uint32_t *b_pt, int b_len, int op);
int cr_invert(CharRange *cr);
int cr_regexp_canonicalize(CharRange *cr, int is_unicode);
typedef enum {
UNICODE_NFC,
UNICODE_NFD,
UNICODE_NFKC,
UNICODE_NFKD,
} UnicodeNormalizationEnum;
int unicode_normalize(uint32_t **pdst, const uint32_t *src, int src_len,
UnicodeNormalizationEnum n_type,
void *opaque, void *(*realloc_func)(void *opaque, void *ptr, size_t size));
/* Unicode character range functions */
int unicode_script(CharRange *cr, const char *script_name, int is_ext);
int unicode_general_category(CharRange *cr, const char *gc_name);
int unicode_prop(CharRange *cr, const char *prop_name);
int lre_case_conv(uint32_t *res, uint32_t c, int conv_type);
int lre_canonicalize(uint32_t c, int is_unicode);
/* Code point type categories */
enum {
UNICODE_C_SPACE = (1 << 0),
UNICODE_C_DIGIT = (1 << 1),
UNICODE_C_UPPER = (1 << 2),
UNICODE_C_LOWER = (1 << 3),
UNICODE_C_UNDER = (1 << 4),
UNICODE_C_DOLLAR = (1 << 5),
UNICODE_C_XDIGIT = (1 << 6),
};
extern uint8_t const lre_ctype_bits[256];
/* zero or non-zero return value */
int lre_is_cased(uint32_t c);
int lre_is_case_ignorable(uint32_t c);
int lre_is_id_start(uint32_t c);
int lre_is_id_continue(uint32_t c);
static inline int lre_is_space_byte(uint8_t c) {
return lre_ctype_bits[c] & UNICODE_C_SPACE;
}
static inline int lre_is_id_start_byte(uint8_t c) {
return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER |
UNICODE_C_UNDER | UNICODE_C_DOLLAR);
}
static inline int lre_is_id_continue_byte(uint8_t c) {
return lre_ctype_bits[c] & (UNICODE_C_UPPER | UNICODE_C_LOWER |
UNICODE_C_UNDER | UNICODE_C_DOLLAR |
UNICODE_C_DIGIT);
}
int lre_is_space_non_ascii(uint32_t c);
static inline int lre_is_space(uint32_t c) {
if (c < 256)
return lre_is_space_byte(c);
else
return lre_is_space_non_ascii(c);
}
static inline int lre_js_is_ident_first(uint32_t c) {
if (c < 128) {
return lre_is_id_start_byte(c);
} else {
#ifdef CONFIG_ALL_UNICODE
return lre_is_id_start(c);
#else
return !lre_is_space_non_ascii(c);
#endif
}
}
static inline int lre_js_is_ident_next(uint32_t c) {
if (c < 128) {
return lre_is_id_continue_byte(c);
} else {
/* ZWNJ and ZWJ are accepted in identifiers */
if (c >= 0x200C && c <= 0x200D)
return TRUE;
#ifdef CONFIG_ALL_UNICODE
return lre_is_id_continue(c);
#else
return !lre_is_space_non_ascii(c);
#endif
}
}
#endif /* LIBUNICODE_H */

99
source/list.h Normal file
View File

@@ -0,0 +1,99 @@
/*
* Linux klist like system
*
* Copyright (c) 2016-2017 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef LIST_H
#define LIST_H
#ifndef NULL
#include <stddef.h>
#endif
struct list_head {
struct list_head *prev;
struct list_head *next;
};
#define LIST_HEAD_INIT(el) { &(el), &(el) }
/* return the pointer of type 'type *' containing 'el' as field 'member' */
#define list_entry(el, type, member) container_of(el, type, member)
static inline void init_list_head(struct list_head *head)
{
head->prev = head;
head->next = head;
}
/* insert 'el' between 'prev' and 'next' */
static inline void __list_add(struct list_head *el,
struct list_head *prev, struct list_head *next)
{
prev->next = el;
el->prev = prev;
el->next = next;
next->prev = el;
}
/* add 'el' at the head of the list 'head' (= after element head) */
static inline void list_add(struct list_head *el, struct list_head *head)
{
__list_add(el, head, head->next);
}
/* add 'el' at the end of the list 'head' (= before element head) */
static inline void list_add_tail(struct list_head *el, struct list_head *head)
{
__list_add(el, head->prev, head);
}
static inline void list_del(struct list_head *el)
{
struct list_head *prev, *next;
prev = el->prev;
next = el->next;
prev->next = next;
next->prev = prev;
el->prev = NULL; /* fail safe */
el->next = NULL; /* fail safe */
}
static inline int list_empty(struct list_head *el)
{
return el->next == el;
}
#define list_for_each(el, head) \
for(el = (head)->next; el != (head); el = el->next)
#define list_for_each_safe(el, el1, head) \
for(el = (head)->next, el1 = el->next; el != (head); \
el = el1, el1 = el->next)
#define list_for_each_prev(el, head) \
for(el = (head)->prev; el != (head); el = el->prev)
#define list_for_each_prev_safe(el, el1, head) \
for(el = (head)->prev, el1 = el->prev; el != (head); \
el = el1, el1 = el->prev)
#endif /* LIST_H */

View File

@@ -3,6 +3,7 @@
#include <stddef.h>
#include <stdint.h>
#include "kim.h"
/* Nota type nibble values */
#define NOTA_BLOB 0x00

View File

@@ -18,7 +18,7 @@ cell_rt *js2actor(JSContext *js, JSValue v)
return NULL;
cell_rt *crt = JS_GetContextOpaque(js);
JSValue actor_data = JS_GetPropertyStr(js, v, "__ACTORDATA__");
JSValue actor_data = JS_GetProperty(js, v, crt->actor_sym);
if (JS_IsUndefined(actor_data)) {
JS_FreeValue(js, actor_data);
@@ -68,31 +68,66 @@ JSValue actor2js(JSContext *js, cell_rt *actor)
cell_rt *crt = JS_GetContextOpaque(js);
JS_SetPropertyStr(js, actor_obj, "__ACTORDATA__", actor_data);
JS_SetProperty(js, actor_obj, crt->actor_sym, actor_data);
return actor_obj;
}
JSC_CCALL(os_createactor,
void *startup = value2wota(js, argv[0], JS_UNDEFINED);
create_actor(startup, NULL);
cell_rt *rt = JS_GetContextOpaque(js);
if (rt->disrupt)
return JS_ThrowInternalError(js, "Can't start a new actor while disrupting.");
void *startup = value2wota(js, argv[0], JS_UNDEFINED, NULL);
create_actor(startup);
)
JSC_CCALL(os_mailbox_push,
if (argc < 2) return JS_ThrowInternalError(js, "Need an actor and a message");
if (!JS_IsObject(argv[1])) return JS_ThrowInternalError(js, "Object to push must be an object.");
if (!js_is_blob(js, argv[1])) return JS_ThrowInternalError(js, "Can only send blobs.");
const char *id = JS_ToCString(js, argv[0]);
int exist = actor_exists(id);
if (!exist) {
JS_FreeCString(js,id);
if (!exist)
return JS_ThrowInternalError(js, "No mailbox found for given ID");
}
void *data = value2wota(js, argv[1], JS_UNDEFINED);
cell_rt *target = get_actor(id);
JS_FreeCString(js,id);
const char *err = send_message(id, data);
/* JSValue sys = JS_GetPropertyStr(js, argv[1], "__SYSTEM__");
if (!JS_IsUndefined(sys)) {
const char *k = JS_ToCString(js,sys);
int stop = 0;
if (strcmp(k, "stop") == 0) {
stop = 1;
actor_disrupt(target);
}
JS_FreeValue(js,sys);
JS_FreeCString(js,k);
if (stop) return JS_UNDEFINED;
}
*/
size_t size;
void *data = js_get_blob_data(js, &size, argv[1]);
// Create a new blob and copy the data
blob *msg_blob = blob_new(size * 8); // Convert bytes to bits
if (!msg_blob) {
return JS_ThrowInternalError(js, "Could not allocate blob");
}
// Copy data to blob
memcpy(msg_blob->data, data, size);
msg_blob->length = size * 8;
blob_make_stone(msg_blob); // Make it immutable
const char *err = send_message(id, msg_blob);
if (err) {
free(data);
blob_destroy(msg_blob);
return JS_ThrowInternalError(js, "Could not send message: %s", err);
}
)
@@ -100,7 +135,9 @@ JSC_CCALL(os_mailbox_push,
JSC_CCALL(os_register_actor,
cell_rt *rt = JS_GetContextOpaque(js);
const char *id = JS_ToCString(js, argv[0]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), argv[3]);
double ar;
JS_ToFloat64(js, &ar, argv[3]);
const char *err = register_actor(id, rt, JS_ToBool(js, argv[2]), ar);
if (err) return JS_ThrowInternalError(js, "Could not register actor: %s", err);
rt->message_handle = JS_DupValue(js, argv[1]);
rt->context = js;
@@ -117,30 +154,9 @@ JSC_CCALL(os_mailbox_exist,
JSC_CCALL(os_unneeded,
cell_rt *actor = JS_GetContextOpaque(js);
SDL_LockMutex(actor->msg_mutex);
JS_FreeValue(js, actor->unneeded);
if (!JS_IsFunction(js, argv[0])) {
actor->unneeded = JS_UNDEFINED;
goto END;
}
actor->unneeded = JS_DupValue(js, argv[0]);
JS_ToFloat64(js, &actor->unneeded_secs, argv[1]);
END:
if (actor->ar) {
SDL_RemoveTimer(actor->ar);
actor->ar = 0;
}
set_actor_state(actor);
SDL_UnlockMutex(actor->msg_mutex);
)
JSC_CCALL(os_destroy,
cell_rt *rt = JS_GetContextOpaque(js);
rt->need_stop = 1;
set_actor_state(rt);
double secs;
JS_ToFloat64(js, &secs, argv[1]);
actor_unneeded(actor, argv[0], secs);
)
JSC_CCALL(actor_disrupt,
@@ -153,9 +169,18 @@ JSC_SCALL(actor_setname,
rt->name = strdup(str);
)
JSC_CCALL(actor_testfn,
cell_rt *crt = js2actor(js, argv[0]);
printf("actor? %p\n", crt);
JSC_CCALL(actor_on_exception,
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception);
rt->on_exception = JS_DupValue(js,argv[0]);
)
JSC_CCALL(actor_clock,
if (!JS_IsFunction(js, argv[0]))
return JS_ThrowReferenceError(js, "Argument must be a function.");
cell_rt *actor = JS_GetContextOpaque(js);
actor_clock(actor, argv[0]);
)
static const JSCFunctionListEntry js_actor_funcs[] = {
@@ -166,10 +191,10 @@ static const JSCFunctionListEntry js_actor_funcs[] = {
MIST_FUNC_DEF(actor, removetimer, 1),
MIST_FUNC_DEF(os, register_actor, 4),
MIST_FUNC_DEF(os, unneeded, 2),
MIST_FUNC_DEF(os, destroy, 0),
MIST_FUNC_DEF(actor, disrupt, 0),
MIST_FUNC_DEF(actor, setname, 1),
MIST_FUNC_DEF(actor, testfn, 1),
MIST_FUNC_DEF(actor, on_exception, 1),
MIST_FUNC_DEF(actor, clock, 1),
};
JSValue js_actor_use(JSContext *js) {

View File

@@ -6,5 +6,6 @@
JSValue js_actor_use(JSContext *js);
cell_rt *js2actor(JSContext *js, JSValue v);
JSValue actor2js(JSContext *js, cell_rt *actor);
JSValue js_actor_set_symbol(JSContext *js, JSValue self, int argc, JSValue *argv);
#endif

View File

@@ -50,33 +50,44 @@ static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
int is_one = JS_ToBool(ctx, argv[1]);
bd = blob_new_with_fill((size_t)length_bits, is_one);
} else if (JS_IsFunction(ctx, argv[1])) {
/* Random function provided call it for each bit */
/* Random function provided call it and use up to 56 bits at a time */
size_t bytes = (length_bits + 7) / 8;
bd = blob_new((size_t)length_bits);
if (bd) {
bd->length = length_bits;
/* Ensure the backing storage starts out zeroed */
memset(bd->data, 0, bytes);
for (size_t i = 0; i < length_bits; i++) {
size_t bits_written = 0;
while (bits_written < length_bits) {
JSValue randval = JS_Call(ctx, argv[1], JS_UNDEFINED, 0, NULL);
if (JS_IsException(randval)) {
blob_destroy(bd);
return JS_EXCEPTION;
}
int32_t fitval;
JS_ToInt32(ctx, &fitval, randval);
int64_t fitval;
JS_ToInt64(ctx, &fitval, randval);
JS_FreeValue(ctx, randval);
/* Compute which byte and which bit within that byte to set/clear */
size_t byte_idx = i / 8;
size_t bit_idx = i % 8;
/* Extract up to 56 bits from the random value */
size_t bits_to_use = length_bits - bits_written;
if (bits_to_use > 52) bits_to_use = 52;
if (fitval & 1)
/* Write bits from the random value */
for (size_t j = 0; j < bits_to_use; j++) {
size_t bit_pos = bits_written + j;
size_t byte_idx = bit_pos / 8;
size_t bit_idx = bit_pos % 8;
if (fitval & (1LL << j))
bd->data[byte_idx] |= (uint8_t)(1 << bit_idx);
else
bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx);
}
bits_written += bits_to_use;
}
}
} else {
return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function");
@@ -186,6 +197,29 @@ static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val,
return JS_UNDEFINED;
}
// blob.write_fit(value, len)
static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 2)
return JS_ThrowTypeError(ctx, "write_fit(value, len) requires 2 arguments");
blob *bd = js2blob(ctx, this_val);
if (!bd)
return JS_ThrowTypeError(ctx, "write_fit: not called on a blob");
int64_t value;
int32_t len;
if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION;
if (blob_write_fit(bd, value, len) < 0) {
return JS_ThrowTypeError(ctx, "write_fit: value doesn't fit in specified bits, stone blob, or OOM");
}
return JS_UNDEFINED;
}
// blob.write_kim(fit)
static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
@@ -199,9 +233,12 @@ static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
// Handle number or single character string
const char *str = JS_ToCString(ctx, argv[0]);
if (blob_write_text(bd, str) < 0)
if (blob_write_text(bd, str) < 0) {
JS_FreeCString(ctx,str);
return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM");
}
JS_FreeCString(ctx,str);
return JS_UNDEFINED;
}
@@ -308,6 +345,40 @@ static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val,
return JS_NewFloat64(ctx, d);
}
// blob.read_fit(from, len)
static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
if (argc < 2) {
return JS_ThrowTypeError(ctx, "read_fit(from, len) requires 2 arguments");
}
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "read_fit: not called on a blob");
}
if (!bd->is_stone) {
return JS_ThrowTypeError(ctx, "read_fit: blob must be stone");
}
int64_t from;
int32_t len;
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION;
if (from < 0) {
return JS_ThrowRangeError(ctx, "read_fit: position must be non-negative");
}
int64_t value;
if (blob_read_fit(bd, from, len, &value) < 0) {
return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length");
}
return JS_NewInt64(ctx, value);
}
// blob.read_text(from)
static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
@@ -371,6 +442,15 @@ static JSValue js_blob_stone(JSContext *ctx, JSValueConst this_val,
return JS_UNDEFINED;
}
static JSValue js_blob_stonep(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv) {
blob *bd = js2blob(ctx, this_val);
if (!bd) {
return JS_ThrowTypeError(ctx, "stone: not called on a blob");
}
return JS_NewBool(ctx, bd->is_stone);
}
// blob.length getter
// Return number of bits in the blob
static JSValue js_blob_get_length(JSContext *ctx, JSValueConst this_val, int magic) {
@@ -390,6 +470,7 @@ static const JSCFunctionListEntry js_blob_funcs[] = {
JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit),
JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob),
JS_CFUNC_DEF("write_number", 1, js_blob_write_number),
JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit),
JS_CFUNC_DEF("write_text", 1, js_blob_write_text),
JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad),
@@ -397,11 +478,13 @@ static const JSCFunctionListEntry js_blob_funcs[] = {
JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical),
JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob),
JS_CFUNC_DEF("read_number", 1, js_blob_read_number),
JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit),
JS_CFUNC_DEF("read_text", 1, js_blob_read_text),
JS_CFUNC_DEF("pad?", 2, js_blob_pad_q),
// Other methods
JS_CFUNC_DEF("stone", 0, js_blob_stone),
JS_CFUNC_DEF("stonep", 0, js_blob_stonep),
// Length property getter
JS_CGETSET_DEF("length", js_blob_get_length, NULL),

View File

@@ -7,56 +7,19 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include "wildmatch.h"
// File descriptor wrapper structure
typedef struct {
// Helper to convert JS value to file descriptor
static int js2fd(JSContext *ctx, JSValueConst val)
{
int fd;
} FDWrapper;
// Free function for file descriptor
static void FD_free(JSRuntime *rt, FDWrapper *fdw)
{
if (fdw->fd >= 0) {
close(fdw->fd);
if (JS_ToInt32(ctx, &fd, val) < 0) {
JS_ThrowTypeError(ctx, "Expected file descriptor number");
return -1;
}
js_free_rt(rt, fdw);
}
// Class definition for file descriptor
static JSClassID js_fd_class_id;
static JSClassDef js_fd_class = {
"FileDescriptor",
.finalizer = (JSClassFinalizer *)FD_free,
};
// Helper to convert JS value to FDWrapper
static FDWrapper *js2fd(JSContext *ctx, JSValueConst obj)
{
return JS_GetOpaque2(ctx, obj, js_fd_class_id);
}
// Helper to create JS FDWrapper object
static JSValue fd2js(JSContext *ctx, int fd)
{
FDWrapper *fdw = js_mallocz(ctx, sizeof(FDWrapper));
if (!fdw) return JS_EXCEPTION;
fdw->fd = fd;
JSValue obj = JS_NewObjectClass(ctx, js_fd_class_id);
if (JS_IsException(obj)) {
js_free(ctx, fdw);
return obj;
}
JS_SetOpaque(obj, fdw);
return obj;
return fd;
}
// Helper function for writing
@@ -75,262 +38,8 @@ static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val)
return wrote;
}
// Glob data structure
struct fd_globdata {
JSContext *js;
JSValue arr;
char **globs;
int idx;
int recurse;
};
// Helper for recursive directory enumeration
static void enumerate_dir(struct fd_globdata *data, const char *path);
// Callback for glob matching
static void globfs_cb(struct fd_globdata *data, const char *dir, const char *file)
{
char *path;
int needfree = 0;
if (dir[0] == 0) {
path = (char*)file;
} else {
size_t len = strlen(dir) + strlen(file) + 2;
path = malloc(len);
snprintf(path, len, "%s/%s", dir, file);
needfree = 1;
}
char **glob = data->globs;
while (*glob != NULL) {
if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH)
goto END;
glob++;
}
struct stat st;
if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
enumerate_dir(data, path);
goto END;
}
JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path));
END:
if (needfree) free(path);
}
// Helper for directory enumeration
static void enumerate_dir(struct fd_globdata *data, const char *path)
{
DIR *dir = opendir(path);
if (!dir) return;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
globfs_cb(data, path, entry->d_name);
}
closedir(dir);
}
// FD I/O FUNCTIONS
JSC_SCALL(fd_rm,
if (remove(str) != 0)
ret = JS_ThrowReferenceError(js, "could not remove %s: %s", str, strerror(errno));
)
JSC_SCALL(fd_mkdir,
if (mkdir(str, 0755) != 0)
ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno));
)
JSC_SCALL(fd_exists,
struct stat st;
ret = JS_NewBool(js, stat(str, &st) == 0);
)
JSC_SCALL(fd_stat,
struct stat st;
if (stat(str, &st) != 0)
return JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno));
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "filesize", number2js(js, st.st_size));
JS_SetPropertyStr(js, ret, "modtime", number2js(js, st.st_mtime));
JS_SetPropertyStr(js, ret, "createtime", number2js(js, st.st_ctime));
JS_SetPropertyStr(js, ret, "accesstime", number2js(js, st.st_atime));
JS_SetPropertyStr(js, ret, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, ret, "is_directory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, ret, "is_file", JS_NewBool(js, S_ISREG(st.st_mode)));
)
JSC_SCALL(fd_slurpbytes,
int fd = open(str, O_RDONLY);
if (fd < 0) {
ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno));
goto END;
}
struct stat st;
if (fstat(fd, &st) != 0) {
close(fd);
ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
goto END;
}
void *data = malloc(st.st_size);
if (!data) {
close(fd);
ret = JS_ThrowReferenceError(js, "malloc failed");
goto END;
}
ssize_t bytes_read = read(fd, data, st.st_size);
close(fd);
if (bytes_read != st.st_size) {
free(data);
ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno));
goto END;
}
ret = js_new_blob_stoned_copy(js, data, st.st_size);
free(data);
END:
)
JSC_SCALL(fd_slurp,
int fd = open(str, O_RDONLY);
if (fd < 0) {
ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno));
goto END;
}
struct stat st;
if (fstat(fd, &st) != 0) {
close(fd);
ret = JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
goto END;
}
char *data = malloc(st.st_size + 1);
if (!data) {
close(fd);
ret = JS_ThrowReferenceError(js, "malloc failed");
goto END;
}
ssize_t bytes_read = read(fd, data, st.st_size);
close(fd);
if (bytes_read != st.st_size) {
free(data);
ret = JS_ThrowReferenceError(js, "read failed: %s", strerror(errno));
goto END;
}
data[st.st_size] = '\0';
ret = JS_NewStringLen(js, data, st.st_size);
free(data);
END:
)
JSC_SCALL(fd_slurpwrite,
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd < 0) {
ret = JS_ThrowReferenceError(js, "could not open %s: %s", str, strerror(errno));
goto END;
}
ssize_t wrote = js_fd_write_helper(js, fd, argv[1]);
close(fd);
if (wrote < 0)
ret = JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
END:
)
JSC_SSCALL(fd_match,
if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH)
ret = JS_NewBool(js, 1);
else
ret = JS_NewBool(js, 0);
)
JSC_CCALL(fd_globfs,
ret = JS_NewArray(js);
struct fd_globdata data;
data.js = js;
data.arr = ret;
data.idx = 0;
data.recurse = 1;
int globs_len = JS_ArrayLength(js, argv[0]);
const char *globs[globs_len + 1];
for (int i = 0; i < globs_len; i++) {
JSValue g = JS_GetPropertyUint32(js, argv[0], i);
globs[i] = JS_ToCString(js, g);
JS_FreeValue(js, g);
}
globs[globs_len] = NULL;
data.globs = (char**)globs;
const char *path = ".";
if (!JS_IsUndefined(argv[1]))
path = JS_ToCString(js, argv[1]);
enumerate_dir(&data, path);
for (int i = 0; i < globs_len; i++)
JS_FreeCString(js, globs[i]);
if (!JS_IsUndefined(argv[1]))
JS_FreeCString(js, path);
)
JSC_SCALL(fd_enumerate,
ret = JS_NewArray(js);
DIR *dir = opendir(str);
if (!dir) {
ret = JS_ThrowReferenceError(js, "could not open directory %s: %s", str, strerror(errno));
goto END;
}
int idx = 0;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
continue;
JS_SetPropertyUint32(js, ret, idx++, JS_NewString(js, entry->d_name));
}
closedir(dir);
END:
)
JSC_CCALL(fd_getcwd,
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)) == NULL)
return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno));
return JS_NewString(js, buf);
)
JSC_SCALL(fd_chdir,
if (chdir(str) != 0)
ret = JS_ThrowReferenceError(js, "chdir failed: %s", strerror(errno));
)
// POSIX FILE DESCRIPTOR FUNCTIONS
JSC_SCALL(fd_open,
int flags = O_RDWR | O_CREAT;
@@ -356,53 +65,33 @@ JSC_SCALL(fd_open,
if (fd < 0)
ret = JS_ThrowReferenceError(js, "open failed: %s", strerror(errno));
else
ret = fd2js(js, fd);
ret = JS_NewInt32(js, fd);
)
JSC_SCALL(fd_is_directory,
struct stat st;
if (stat(str, &st) != 0)
ret = JS_NewBool(js, 0);
else
ret = JS_NewBool(js, S_ISDIR(st.st_mode));
)
JSC_CCALL(fd_write,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
// FILE DESCRIPTOR FUNCTIONS
JSC_CCALL(file_close,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
if (fdw->fd >= 0) {
close(fdw->fd);
fdw->fd = -1;
}
)
JSC_CCALL(file_write,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
ssize_t wrote = js_fd_write_helper(js, fdw->fd, argv[0]);
ssize_t wrote = js_fd_write_helper(js, fd, argv[1]);
if (wrote < 0)
return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
return JS_NewInt64(js, wrote);
)
JSC_CCALL(file_read,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
JSC_CCALL(fd_read,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
size_t size = 4096;
if (argc > 0)
size = js2number(js, argv[0]);
if (argc > 1)
size = js2number(js, argv[1]);
void *buf = malloc(size);
if (!buf)
return JS_ThrowReferenceError(js, "malloc failed");
ssize_t bytes_read = read(fdw->fd, buf, size);
ssize_t bytes_read = read(fd, buf, size);
if (bytes_read < 0) {
free(buf);
return JS_ThrowReferenceError(js, "read failed: %s", strerror(errno));
@@ -413,84 +102,114 @@ JSC_CCALL(file_read,
return ret;
)
JSC_CCALL(file_seek,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
JSC_CCALL(fd_lseek,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
off_t offset = js2number(js, argv[0]);
off_t offset = js2number(js, argv[1]);
int whence = SEEK_SET;
if (argc > 1) {
const char *whence_str = JS_ToCString(js, argv[1]);
if (argc > 2) {
const char *whence_str = JS_ToCString(js, argv[2]);
if (strcmp(whence_str, "cur") == 0) whence = SEEK_CUR;
else if (strcmp(whence_str, "end") == 0) whence = SEEK_END;
JS_FreeCString(js, whence_str);
}
off_t new_pos = lseek(fdw->fd, offset, whence);
off_t new_pos = lseek(fd, offset, whence);
if (new_pos < 0)
return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno));
return JS_NewInt64(js, new_pos);
)
JSC_CCALL(file_tell,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
off_t pos = lseek(fdw->fd, 0, SEEK_CUR);
if (pos < 0)
return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno));
return JS_NewInt64(js, pos);
JSC_CCALL(fd_getcwd,
char buf[PATH_MAX];
if (getcwd(buf, sizeof(buf)) == NULL)
return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno));
return JS_NewString(js, buf);
)
JSC_CCALL(file_eof,
FDWrapper *fdw = js2fd(js, self);
if (!fdw) return JS_EXCEPTION;
off_t cur = lseek(fdw->fd, 0, SEEK_CUR);
off_t end = lseek(fdw->fd, 0, SEEK_END);
lseek(fdw->fd, cur, SEEK_SET);
return JS_NewBool(js, cur >= end);
JSC_SCALL(fd_rmdir,
if (rmdir(str) != 0)
ret = JS_ThrowReferenceError(js, "could not remove directory %s: %s", str, strerror(errno));
)
JSC_SCALL(fd_mkdir,
if (mkdir(str, 0755) != 0)
ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno));
)
JSC_CCALL(fd_fsync,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
if (fsync(fd) != 0)
return JS_ThrowReferenceError(js, "fsync failed: %s", strerror(errno));
return JS_UNDEFINED;
)
JSC_CCALL(fd_close,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
if (close(fd) != 0)
return JS_ThrowReferenceError(js, "close failed: %s", strerror(errno));
return JS_UNDEFINED;
)
JSC_CCALL(fd_fstat,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
struct stat st;
if (fstat(fd, &st) != 0)
return JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
JSValue obj = JS_NewObject(js);
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, st.st_size));
JS_SetPropertyStr(js, obj, "mode", JS_NewInt32(js, st.st_mode));
JS_SetPropertyStr(js, obj, "uid", JS_NewInt32(js, st.st_uid));
JS_SetPropertyStr(js, obj, "gid", JS_NewInt32(js, st.st_gid));
JS_SetPropertyStr(js, obj, "atime", JS_NewInt64(js, st.st_atime));
JS_SetPropertyStr(js, obj, "mtime", JS_NewInt64(js, st.st_mtime));
JS_SetPropertyStr(js, obj, "ctime", JS_NewInt64(js, st.st_ctime));
JS_SetPropertyStr(js, obj, "nlink", JS_NewInt32(js, st.st_nlink));
JS_SetPropertyStr(js, obj, "ino", JS_NewInt64(js, st.st_ino));
JS_SetPropertyStr(js, obj, "dev", JS_NewInt32(js, st.st_dev));
JS_SetPropertyStr(js, obj, "rdev", JS_NewInt32(js, st.st_rdev));
JS_SetPropertyStr(js, obj, "blksize", JS_NewInt32(js, st.st_blksize));
JS_SetPropertyStr(js, obj, "blocks", JS_NewInt64(js, st.st_blocks));
// Add boolean properties for file type
JS_SetPropertyStr(js, obj, "isFile", JS_NewBool(js, S_ISREG(st.st_mode)));
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, S_ISDIR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSymlink", JS_NewBool(js, S_ISLNK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isFIFO", JS_NewBool(js, S_ISFIFO(st.st_mode)));
JS_SetPropertyStr(js, obj, "isSocket", JS_NewBool(js, S_ISSOCK(st.st_mode)));
JS_SetPropertyStr(js, obj, "isCharDevice", JS_NewBool(js, S_ISCHR(st.st_mode)));
JS_SetPropertyStr(js, obj, "isBlockDevice", JS_NewBool(js, S_ISBLK(st.st_mode)));
return obj;
)
static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, rm, 1),
MIST_FUNC_DEF(fd, mkdir, 1),
MIST_FUNC_DEF(fd, stat, 1),
MIST_FUNC_DEF(fd, globfs, 2),
MIST_FUNC_DEF(fd, match, 2),
MIST_FUNC_DEF(fd, exists, 1),
MIST_FUNC_DEF(fd, slurp, 1),
MIST_FUNC_DEF(fd, slurpbytes, 1),
MIST_FUNC_DEF(fd, slurpwrite, 2),
MIST_FUNC_DEF(fd, getcwd, 0),
MIST_FUNC_DEF(fd, chdir, 1),
MIST_FUNC_DEF(fd, open, 2),
MIST_FUNC_DEF(fd, enumerate, 1),
MIST_FUNC_DEF(fd, is_directory, 1),
};
static const JSCFunctionListEntry js_fd_file_funcs[] = {
MIST_FUNC_DEF(file, close, 0),
MIST_FUNC_DEF(file, write, 1),
MIST_FUNC_DEF(file, read, 1),
MIST_FUNC_DEF(file, seek, 2),
MIST_FUNC_DEF(file, tell, 0),
MIST_FUNC_DEF(file, eof, 0),
MIST_FUNC_DEF(fd, write, 2),
MIST_FUNC_DEF(fd, read, 2),
MIST_FUNC_DEF(fd, lseek, 3),
MIST_FUNC_DEF(fd, getcwd, 0),
MIST_FUNC_DEF(fd, rmdir, 1),
MIST_FUNC_DEF(fd, mkdir, 1),
MIST_FUNC_DEF(fd, fsync, 1),
MIST_FUNC_DEF(fd, close, 1),
MIST_FUNC_DEF(fd, fstat, 1),
};
JSValue js_fd_use(JSContext *js) {
// Initialize the file descriptor class
JS_NewClassID(&js_fd_class_id);
JS_NewClass(JS_GetRuntime(js), js_fd_class_id, &js_fd_class);
JSValue proto = JS_NewObject(js);
JS_SetPropertyFunctionList(js, proto, js_fd_file_funcs, countof(js_fd_file_funcs));
JS_SetClassProto(js, js_fd_class_id, proto);
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_fd_funcs, countof(js_fd_funcs));
return mod;

257
source/qjs_fit.c Normal file
View File

@@ -0,0 +1,257 @@
#include "qjs_fit.h"
#include "jsffi.h"
#include <stdint.h>
#include <limits.h>
#define FIT_BITS 56
#define FIT_MAX ((1LL << 55) - 1)
#define FIT_MIN (-(1LL << 55))
#define FIT_MASK ((1ULL << 56) - 1)
static int is_fit(int64_t n) {
return n >= FIT_MIN && n <= FIT_MAX;
}
static int64_t to_fit_int(JSContext *js, JSValue val) {
if (!JS_IsNumber(val)) return LLONG_MAX;
int64_t n;
if (JS_ToInt64(js, &n, val) < 0) return LLONG_MAX;
if (!is_fit(n)) return LLONG_MAX;
return n;
}
static JSValue fit_result(JSContext *js, int64_t result) {
if (!is_fit(result)) return JS_NULL;
return JS_NewInt64(js, result);
}
JSC_CCALL(fit_and,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
int64_t result = first & second;
return fit_result(js, result);
)
JSC_CCALL(fit_left,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
if (second < 0 || second >= FIT_BITS) return JS_NULL;
// Mask to 56 bits then sign extend
uint64_t unsigned_first = (uint64_t)first & FIT_MASK;
uint64_t result = (unsigned_first << second) & FIT_MASK;
// Sign extend from bit 55
if (result & (1ULL << 55)) {
result |= ~FIT_MASK;
}
return JS_NewInt64(js, (int64_t)result);
)
JSC_CCALL(fit_mask,
if (!JS_IsNumber(argv[0])) return JS_NULL;
int32_t n;
if (JS_ToInt32(js, &n, argv[0]) < 0) return JS_NULL;
if (n > FIT_BITS || n < -FIT_BITS) return JS_NULL;
if (n == 0) return JS_NewInt64(js, 0);
int64_t result;
if (n > 0) {
// Create n ones
if (n == FIT_BITS) {
result = -1;
} else {
result = (1LL << n) - 1;
}
} else {
// Create -n zeros (which is 56+n ones)
int ones = FIT_BITS + n;
if (ones == 0) {
result = 0;
} else if (ones == FIT_BITS) {
result = -1;
} else {
uint64_t mask = (1ULL << ones) - 1;
result = ~mask;
// Ensure result is within 56-bit range
result = (int64_t)((uint64_t)result & FIT_MASK);
// Sign extend
if (result & (1LL << 55)) {
result |= ~FIT_MASK;
}
}
}
return JS_NewInt64(js, result);
)
JSC_CCALL(fit_not,
int64_t val = to_fit_int(js, argv[0]);
if (val == LLONG_MAX) return JS_NULL;
uint64_t result = ~(uint64_t)val & FIT_MASK;
// Sign extend
if (result & (1ULL << 55)) {
result |= ~FIT_MASK;
}
return JS_NewInt64(js, (int64_t)result);
)
JSC_CCALL(fit_ones,
int64_t val = to_fit_int(js, argv[0]);
if (val == LLONG_MAX) return JS_NULL;
uint64_t uval = (uint64_t)val & FIT_MASK;
int count = 0;
for (int i = 0; i < FIT_BITS; i++) {
if (uval & (1ULL << i)) count++;
}
return JS_NewInt32(js, count);
)
JSC_CCALL(fit_or,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
int64_t result = first | second;
return fit_result(js, result);
)
JSC_CCALL(fit_reverse,
int64_t val = to_fit_int(js, argv[0]);
if (val == LLONG_MAX) return JS_NULL;
uint64_t uval = (uint64_t)val & FIT_MASK;
uint64_t result = 0;
for (int i = 0; i < FIT_BITS; i++) {
if (uval & (1ULL << i)) {
result |= (1ULL << (FIT_BITS - 1 - i));
}
}
// Sign extend
if (result & (1ULL << 55)) {
result |= ~FIT_MASK;
}
return JS_NewInt64(js, (int64_t)result);
)
JSC_CCALL(fit_right,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
if (second < 0 || second >= FIT_BITS) return JS_NULL;
// Zero fill right shift
uint64_t ufirst = (uint64_t)first & FIT_MASK;
uint64_t result = ufirst >> second;
return JS_NewInt64(js, (int64_t)result);
)
JSC_CCALL(fit_right_signed,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
if (second < 0 || second >= FIT_BITS) return JS_NULL;
// Sign extend to 64 bits for arithmetic shift
int64_t extended = first;
if (first & (1LL << 55)) {
extended |= ~FIT_MASK;
}
int64_t result = extended >> second;
// Ensure result is within fit range
return fit_result(js, result);
)
JSC_CCALL(fit_rotate,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
// Normalize rotation amount
int rotation = ((int)second % FIT_BITS + FIT_BITS) % FIT_BITS;
uint64_t ufirst = (uint64_t)first & FIT_MASK;
uint64_t result = ((ufirst << rotation) | (ufirst >> (FIT_BITS - rotation))) & FIT_MASK;
// Sign extend
if (result & (1ULL << 55)) {
result |= ~FIT_MASK;
}
return JS_NewInt64(js, (int64_t)result);
)
JSC_CCALL(fit_xor,
int64_t first = to_fit_int(js, argv[0]);
int64_t second = to_fit_int(js, argv[1]);
if (first == LLONG_MAX || second == LLONG_MAX) return JS_NULL;
int64_t result = first ^ second;
return fit_result(js, result);
)
JSC_CCALL(fit_zeros,
int64_t val = to_fit_int(js, argv[0]);
if (val == LLONG_MAX) return JS_NULL;
uint64_t uval = (uint64_t)val & FIT_MASK;
int count = 0;
for (int i = FIT_BITS - 1; i >= 0; i--) {
if (uval & (1ULL << i)) break;
count++;
}
return JS_NewInt32(js, count);
)
static const JSCFunctionListEntry js_fit_funcs[] = {
MIST_FUNC_DEF(fit, and, 2),
MIST_FUNC_DEF(fit, left, 2),
MIST_FUNC_DEF(fit, mask, 1),
MIST_FUNC_DEF(fit, not, 1),
MIST_FUNC_DEF(fit, ones, 1),
MIST_FUNC_DEF(fit, or, 2),
MIST_FUNC_DEF(fit, reverse, 1),
MIST_FUNC_DEF(fit, right, 2),
MIST_FUNC_DEF(fit, right_signed, 2),
MIST_FUNC_DEF(fit, rotate, 2),
MIST_FUNC_DEF(fit, xor, 2),
MIST_FUNC_DEF(fit, zeros, 1),
};
JSValue js_fit_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_fit_funcs, countof(js_fit_funcs));
return mod;
}

8
source/qjs_fit.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_FIT_H
#define QJS_FIT_H
#include <quickjs.h>
JSValue js_fit_use(JSContext *js);
#endif

View File

@@ -54,7 +54,7 @@ JSC_CCALL(js_eval_compile,
#include "qjs_blob.h"
JSC_CCALL(js_compile_blob,
// JSRuntime *rt = JS_GetRuntime(js);
JSRuntime *rt = JS_GetRuntime(js);
// JS_SetStripInfo(rt, JS_STRIP_SOURCE);
// JS_SetStripInfo(rt, JS_STRIP_DEBUG);
size_t size;

82
source/qjs_kim.c Normal file
View File

@@ -0,0 +1,82 @@
#include "qjs_kim.h"
#include "qjs_blob.h"
#include "jsffi.h"
#include <string.h>
#include <stdlib.h>
#define KIM_IMPLEMENTATION
#include "kim.h"
JSC_CCALL(kim_encode,
const char *utf8_str = JS_ToCString(js, argv[0]);
if (!utf8_str) return JS_EXCEPTION;
// Count runes to estimate kim buffer size
int rune_count = utf8_count(utf8_str);
// Allocate kim buffer (worst case: 5 bytes per rune)
size_t kim_size = rune_count * 5;
char *kim_buffer = malloc(kim_size);
char *kim_ptr = kim_buffer;
// Encode utf8 to kim
long long runes_encoded;
utf8_to_kim(&utf8_str, &kim_ptr, &runes_encoded);
// Calculate actual size used
size_t actual_size = kim_ptr - kim_buffer;
// Create blob with the encoded data
ret = js_new_blob_stoned_copy(js, kim_buffer, actual_size);
free(kim_buffer);
JS_FreeCString(js, utf8_str);
)
JSC_CCALL(kim_decode,
size_t kim_len;
void *kim_data = js_get_blob_data(js, &kim_len, argv[0]);
if (!kim_data) return JS_ThrowTypeError(js, "Expected blob");
// Allocate UTF-8 buffer (worst case: 4 bytes per kim byte)
size_t utf8_size = kim_len * 4;
char *utf8_buffer = malloc(utf8_size + 1); // +1 for null terminator
char *utf8_ptr = utf8_buffer;
// Copy kim data since kim_to_utf8 modifies the pointer
char *kim_copy = malloc(kim_len);
memcpy(kim_copy, kim_data, kim_len);
char *kim_ptr = kim_copy;
// Count runes in kim data
int rune_count = 0;
char *temp_ptr = kim_copy;
while (temp_ptr < kim_copy + kim_len) {
decode_kim(&temp_ptr);
rune_count++;
}
// Reset pointer and decode
kim_ptr = kim_copy;
kim_to_utf8(&kim_ptr, &utf8_ptr, rune_count);
// Null terminate
*utf8_ptr = '\0';
ret = JS_NewString(js, utf8_buffer);
free(utf8_buffer);
free(kim_copy);
)
static const JSCFunctionListEntry js_kim_funcs[] = {
MIST_FUNC_DEF(kim, encode, 1),
MIST_FUNC_DEF(kim, decode, 1),
};
JSValue js_kim_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_kim_funcs, countof(js_kim_funcs));
return mod;
}

8
source/qjs_kim.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_KIM_H
#define QJS_KIM_H
#include "cell.h"
JSValue js_kim_use(JSContext*);
#endif

View File

@@ -231,20 +231,6 @@ JSC_CCALL(math_from_to,
return jsarr;
)
JSC_CCALL(math_rand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRand(mrand));
)
JSC_CCALL(math_randi,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
return number2js(js, genRandLong(mrand));
)
JSC_CCALL(math_srand,
MTRand *mrand = &((cell_rt*)JS_GetContextOpaque(js))->mrand;
m_seedRand(mrand, js2number(js,argv[0]));
)
JSC_CCALL(math_dot,
size_t alen, blen;
@@ -497,9 +483,6 @@ static const JSCFunctionListEntry js_math_funcs[] = {
MIST_FUNC_DEF(math, median, 1),
MIST_FUNC_DEF(math, length, 1),
MIST_FUNC_DEF(math, from_to, 5),
MIST_FUNC_DEF(math, rand, 0),
MIST_FUNC_DEF(math, randi, 0),
MIST_FUNC_DEF(math, srand,0),
};
JSValue js_math_use(JSContext *js) {

View File

@@ -1,7 +1,6 @@
#include "qjs_nota.h"
#include "qjs_blob.h"
#define KIM_IMPLEMENTATION
#define NOTA_IMPLEMENTATION
#include "nota.h"
@@ -102,10 +101,20 @@ char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota, JSValue holder,
break;
case NOTA_SYM:
nota = nota_read_sym(&b, nota);
if (b == NOTA_PRIVATE) {
JSValue inner;
nota = js_do_nota_decode(js, &inner, nota, holder, JS_UNDEFINED, reviver);
JSValue obj = JS_NewObject(js);
cell_rt *crt = JS_GetContextOpaque(js);
JS_SetProperty(js, obj, crt->actor_sym, inner);
*tmp = obj;
} else {
switch(b) {
case NOTA_NULL: *tmp = JS_UNDEFINED; break;
case NOTA_FALSE: *tmp = JS_NewBool(js, 0); break;
case NOTA_TRUE: *tmp = JS_NewBool(js, 1); break;
default: *tmp = JS_UNDEFINED; break;
}
}
break;
default:
@@ -191,6 +200,15 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
break;
}
cell_rt *crt = JS_GetContextOpaque(ctx);
JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
if (!JS_IsUndefined(adata)) {
nota_write_sym(&enc->nb, NOTA_PRIVATE);
nota_encode_value(enc, adata, replaced, JS_UNDEFINED);
JS_FreeValue(ctx, adata);
break;
}
JS_FreeValue(ctx, adata);
if (nota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;

View File

@@ -28,7 +28,6 @@
#endif
#endif
JSC_CCALL(os_exit, exit(js2number(js,argv[0]));)
JSC_CCALL(os_now, return number2js(js, (double)SDL_GetTicksNS()/1000000000.0))
JSC_CCALL(os_sleep,
@@ -169,12 +168,6 @@ static JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *arg
return ret;
}
JSC_CCALL(os_on,
cell_rt *rt = JS_GetContextOpaque(js);
JS_FreeValue(js, rt->on_exception);
rt->on_exception = JS_DupValue(js,argv[0]);
)
JSC_CCALL(os_buffer2string,
if (argc < 1) {
return JS_ThrowTypeError(js, "buffer2string expects an ArrayBuffer");
@@ -289,10 +282,8 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, freemem, 0),
MIST_FUNC_DEF(os, hostname, 0),
MIST_FUNC_DEF(os, version, 0),
MIST_FUNC_DEF(os, exit, 1),
MIST_FUNC_DEF(os, now, 0),
MIST_FUNC_DEF(os, power_state, 0),
MIST_FUNC_DEF(os, on, 1),
MIST_FUNC_DEF(os, rusage, 0),
MIST_FUNC_DEF(os, mallinfo, 0),
MIST_FUNC_DEF(os, buffer2string, 1),

View File

@@ -23,137 +23,6 @@ void SDL_AudioStream_free(JSRuntime *rt, SDL_AudioStream *st) {
QJSCLASS(SDL_Camera,)
QJSCLASS(SDL_AudioStream,)
// Internal keymod function for input module
static JSValue js_keymod(JSContext *js)
{
SDL_Keymod modstate = SDL_GetModState();
JSValue ret = JS_NewObject(js);
if (SDL_KMOD_CTRL & modstate)
JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1));
if (SDL_KMOD_SHIFT & modstate)
JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1));
if (SDL_KMOD_ALT & modstate)
JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1));
if (SDL_KMOD_GUI & modstate)
JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1));
if (SDL_KMOD_NUM & modstate)
JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1));
if (SDL_KMOD_CAPS & modstate)
JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1));
if (SDL_KMOD_SCROLL & modstate)
JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1));
if (SDL_KMOD_MODE & modstate)
JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1));
return ret;
}
// INPUT FUNCTIONS
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))
JSC_CCALL(input_mouse_show,
if (JS_ToBool(js,argv[0]))
SDL_ShowCursor();
else
SDL_HideCursor();
)
JSC_CCALL(input_keyname,
return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0])));
)
JSC_CCALL(input_keymod,
return js_keymod(js);
)
JSC_CCALL(input_mousestate,
float x,y;
SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y);
JSValue m = JS_NewObject(js);
JS_SetPropertyStr(js,m,"x", number2js(js,x));
JS_SetPropertyStr(js,m,"y", number2js(js,y));
if (flags & SDL_BUTTON_LMASK)
JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_MMASK)
JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_RMASK)
JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X1MASK)
JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X2MASK)
JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1));
return m;
)
// watch events
extern char **event_watchers;
extern SDL_Mutex *event_watchers_mutex;
JSC_CCALL(input_watch,
/* Use js2actor to get the actor from the JS object */
cell_rt *actor = js2actor(js, argv[0]);
if (!actor)
return JS_ThrowTypeError(js, "First argument must be a valid actor object");
SDL_LockMutex(event_watchers_mutex);
/* Check if already watching */
int already_watching = 0;
for (int i = 0; i < arrlen(event_watchers); i++) {
if (strcmp(event_watchers[i], actor->id) == 0) {
already_watching = 1;
break;
}
}
if (!already_watching) {
arrput(event_watchers, strdup(actor->id));
}
SDL_UnlockMutex(event_watchers_mutex);
return JS_UNDEFINED;
)
JSC_CCALL(input_unwatch,
/* Use js2actor to get the actor from the JS object */
cell_rt *actor = js2actor(js, argv[0]);
if (!actor)
return JS_ThrowTypeError(js, "First argument must be a valid actor object");
SDL_LockMutex(event_watchers_mutex);
/* Find and remove from watchers */
for (int i = 0; i < arrlen(event_watchers); i++) {
if (strcmp(event_watchers[i], actor->id) == 0) {
free(event_watchers[i]);
arrdel(event_watchers, i);
break;
}
}
SDL_UnlockMutex(event_watchers_mutex);
return JS_UNDEFINED;
)
static const JSCFunctionListEntry js_input_funcs[] = {
MIST_FUNC_DEF(input, mouse_show, 1),
MIST_FUNC_DEF(input, mouse_lock, 1),
MIST_FUNC_DEF(input, keyname, 1),
MIST_FUNC_DEF(input, keymod, 0),
MIST_FUNC_DEF(input, mousestate, 0),
MIST_FUNC_DEF(input, watch, 1),
MIST_FUNC_DEF(input, unwatch, 1),
};
JSValue js_input_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_input_funcs,countof(js_input_funcs));
return mod;
}
// CAMERA FUNCTIONS

707
source/qjs_sdl_input.c Normal file
View File

@@ -0,0 +1,707 @@
#include "qjs_sdl_input.h"
#include "jsffi.h"
#include "qjs_macros.h"
#include "cell.h"
#include "qjs_blob.h"
#include "stb_ds.h"
#include "qjs_actor.h"
#include "qjs_wota.h"
#include <SDL3/SDL.h>
// Internal keymod function for input module
static JSValue js_keymod(JSContext *js)
{
SDL_Keymod modstate = SDL_GetModState();
JSValue ret = JS_NewObject(js);
if (SDL_KMOD_CTRL & modstate)
JS_SetPropertyStr(js,ret,"ctrl", JS_NewBool(js,1));
if (SDL_KMOD_SHIFT & modstate)
JS_SetPropertyStr(js,ret,"shift", JS_NewBool(js,1));
if (SDL_KMOD_ALT & modstate)
JS_SetPropertyStr(js,ret,"alt", JS_NewBool(js,1));
if (SDL_KMOD_GUI & modstate)
JS_SetPropertyStr(js,ret,"super", JS_NewBool(js,1));
if (SDL_KMOD_NUM & modstate)
JS_SetPropertyStr(js,ret,"numlock", JS_NewBool(js,1));
if (SDL_KMOD_CAPS & modstate)
JS_SetPropertyStr(js,ret,"caps", JS_NewBool(js,1));
if (SDL_KMOD_SCROLL & modstate)
JS_SetPropertyStr(js,ret,"scrolllock", JS_NewBool(js,1));
if (SDL_KMOD_MODE & modstate)
JS_SetPropertyStr(js,ret,"mode", JS_NewBool(js,1));
return ret;
}
// INPUT FUNCTIONS
JSC_CCALL(input_mouse_lock, SDL_CaptureMouse(JS_ToBool(js,argv[0])))
JSC_CCALL(input_mouse_show,
if (JS_ToBool(js,argv[0]))
SDL_ShowCursor();
else
SDL_HideCursor();
)
JSC_CCALL(input_keyname,
return JS_NewString(js, SDL_GetKeyName(js2number(js,argv[0])));
)
JSC_CCALL(input_keymod,
return js_keymod(js);
)
JSC_CCALL(input_mousestate,
float x,y;
SDL_MouseButtonFlags flags = SDL_GetMouseState(&x,&y);
JSValue m = JS_NewObject(js);
JS_SetPropertyStr(js,m,"x", number2js(js,x));
JS_SetPropertyStr(js,m,"y", number2js(js,y));
if (flags & SDL_BUTTON_LMASK)
JS_SetPropertyStr(js, m, "left", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_MMASK)
JS_SetPropertyStr(js, m, "middle", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_RMASK)
JS_SetPropertyStr(js, m, "right", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X1MASK)
JS_SetPropertyStr(js, m, "x1", JS_NewBool(js, 1));
if (flags & SDL_BUTTON_X2MASK)
JS_SetPropertyStr(js, m, "x2", JS_NewBool(js, 1));
return m;
)
// Event processing functions (moved from cell.c)
const char* event_type_to_string(Uint32 event_type) {
switch (event_type) {
// Application events
case SDL_EVENT_QUIT: return "quit";
case SDL_EVENT_TERMINATING: return "terminating";
case SDL_EVENT_LOW_MEMORY: return "low_memory";
case SDL_EVENT_WILL_ENTER_BACKGROUND: return "will_enter_background";
case SDL_EVENT_DID_ENTER_BACKGROUND: return "did_enter_background";
case SDL_EVENT_WILL_ENTER_FOREGROUND: return "will_enter_foreground";
case SDL_EVENT_DID_ENTER_FOREGROUND: return "did_enter_foreground";
case SDL_EVENT_LOCALE_CHANGED: return "locale_changed";
case SDL_EVENT_SYSTEM_THEME_CHANGED: return "system_theme_changed";
// Display events
case SDL_EVENT_DISPLAY_ORIENTATION: return "display_orientation";
case SDL_EVENT_DISPLAY_ADDED: return "display_added";
case SDL_EVENT_DISPLAY_REMOVED: return "display_removed";
case SDL_EVENT_DISPLAY_MOVED: return "display_moved";
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED: return "display_desktop_mode_changed";
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED: return "display_current_mode_changed";
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED: return "display_content_scale_changed";
// Window events
case SDL_EVENT_WINDOW_SHOWN: return "window_shown";
case SDL_EVENT_WINDOW_HIDDEN: return "window_hidden";
case SDL_EVENT_WINDOW_EXPOSED: return "window_exposed";
case SDL_EVENT_WINDOW_MOVED: return "window_moved";
case SDL_EVENT_WINDOW_RESIZED: return "window_resized";
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED: return "window_pixel_size_changed";
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED: return "window_metal_view_resized";
case SDL_EVENT_WINDOW_MINIMIZED: return "window_minimized";
case SDL_EVENT_WINDOW_MAXIMIZED: return "window_maximized";
case SDL_EVENT_WINDOW_RESTORED: return "window_restored";
case SDL_EVENT_WINDOW_MOUSE_ENTER: return "window_mouse_enter";
case SDL_EVENT_WINDOW_MOUSE_LEAVE: return "window_mouse_leave";
case SDL_EVENT_WINDOW_FOCUS_GAINED: return "window_focus_gained";
case SDL_EVENT_WINDOW_FOCUS_LOST: return "window_focus_lost";
case SDL_EVENT_WINDOW_CLOSE_REQUESTED: return "window_close_requested";
case SDL_EVENT_WINDOW_HIT_TEST: return "window_hit_test";
case SDL_EVENT_WINDOW_ICCPROF_CHANGED: return "window_iccprof_changed";
case SDL_EVENT_WINDOW_DISPLAY_CHANGED: return "window_display_changed";
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED: return "window_display_scale_changed";
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED: return "window_safe_area_changed";
case SDL_EVENT_WINDOW_OCCLUDED: return "window_occluded";
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN: return "window_enter_fullscreen";
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN: return "window_leave_fullscreen";
case SDL_EVENT_WINDOW_DESTROYED: return "window_destroyed";
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED: return "window_hdr_state_changed";
// Keyboard events
case SDL_EVENT_KEY_DOWN: return "key_down";
case SDL_EVENT_KEY_UP: return "key_up";
case SDL_EVENT_TEXT_EDITING: return "text_editing";
case SDL_EVENT_TEXT_INPUT: return "text_input";
case SDL_EVENT_KEYMAP_CHANGED: return "keymap_changed";
case SDL_EVENT_KEYBOARD_ADDED: return "keyboard_added";
case SDL_EVENT_KEYBOARD_REMOVED: return "keyboard_removed";
case SDL_EVENT_TEXT_EDITING_CANDIDATES: return "text_editing_candidates";
// Mouse events
case SDL_EVENT_MOUSE_MOTION: return "mouse_motion";
case SDL_EVENT_MOUSE_BUTTON_DOWN: return "mouse_button_down";
case SDL_EVENT_MOUSE_BUTTON_UP: return "mouse_button_up";
case SDL_EVENT_MOUSE_WHEEL: return "mouse_wheel";
case SDL_EVENT_MOUSE_ADDED: return "mouse_added";
case SDL_EVENT_MOUSE_REMOVED: return "mouse_removed";
// Joystick events
case SDL_EVENT_JOYSTICK_AXIS_MOTION: return "joystick_axis_motion";
case SDL_EVENT_JOYSTICK_BALL_MOTION: return "joystick_ball_motion";
case SDL_EVENT_JOYSTICK_HAT_MOTION: return "joystick_hat_motion";
case SDL_EVENT_JOYSTICK_BUTTON_DOWN: return "joystick_button_down";
case SDL_EVENT_JOYSTICK_BUTTON_UP: return "joystick_button_up";
case SDL_EVENT_JOYSTICK_ADDED: return "joystick_added";
case SDL_EVENT_JOYSTICK_REMOVED: return "joystick_removed";
case SDL_EVENT_JOYSTICK_BATTERY_UPDATED: return "joystick_battery_updated";
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE: return "joystick_update_complete";
// Gamepad events
case SDL_EVENT_GAMEPAD_AXIS_MOTION: return "gamepad_axis_motion";
case SDL_EVENT_GAMEPAD_BUTTON_DOWN: return "gamepad_button_down";
case SDL_EVENT_GAMEPAD_BUTTON_UP: return "gamepad_button_up";
case SDL_EVENT_GAMEPAD_ADDED: return "gamepad_added";
case SDL_EVENT_GAMEPAD_REMOVED: return "gamepad_removed";
case SDL_EVENT_GAMEPAD_REMAPPED: return "gamepad_remapped";
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: return "gamepad_touchpad_down";
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: return "gamepad_touchpad_motion";
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: return "gamepad_touchpad_up";
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE: return "gamepad_sensor_update";
case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE: return "gamepad_update_complete";
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED: return "gamepad_steam_handle_updated";
// Touch events
case SDL_EVENT_FINGER_DOWN: return "finger_down";
case SDL_EVENT_FINGER_UP: return "finger_up";
case SDL_EVENT_FINGER_MOTION: return "finger_motion";
// Clipboard events
case SDL_EVENT_CLIPBOARD_UPDATE: return "clipboard_update";
// Drag and drop events
case SDL_EVENT_DROP_FILE: return "drop_file";
case SDL_EVENT_DROP_TEXT: return "drop_text";
case SDL_EVENT_DROP_BEGIN: return "drop_begin";
case SDL_EVENT_DROP_COMPLETE: return "drop_complete";
case SDL_EVENT_DROP_POSITION: return "drop_position";
// Audio device events
case SDL_EVENT_AUDIO_DEVICE_ADDED: return "audio_device_added";
case SDL_EVENT_AUDIO_DEVICE_REMOVED: return "audio_device_removed";
case SDL_EVENT_AUDIO_DEVICE_FORMAT_CHANGED: return "audio_device_format_changed";
// Sensor events
case SDL_EVENT_SENSOR_UPDATE: return "sensor_update";
// Pen events
case SDL_EVENT_PEN_PROXIMITY_IN: return "pen_proximity_in";
case SDL_EVENT_PEN_PROXIMITY_OUT: return "pen_proximity_out";
case SDL_EVENT_PEN_DOWN: return "pen_down";
case SDL_EVENT_PEN_UP: return "pen_up";
case SDL_EVENT_PEN_BUTTON_DOWN: return "pen_button_down";
case SDL_EVENT_PEN_BUTTON_UP: return "pen_button_up";
case SDL_EVENT_PEN_MOTION: return "pen_motion";
case SDL_EVENT_PEN_AXIS: return "pen_axis";
// Camera events
case SDL_EVENT_CAMERA_DEVICE_ADDED: return "camera_device_added";
case SDL_EVENT_CAMERA_DEVICE_REMOVED: return "camera_device_removed";
case SDL_EVENT_CAMERA_DEVICE_APPROVED: return "camera_device_approved";
case SDL_EVENT_CAMERA_DEVICE_DENIED: return "camera_device_denied";
// Render events
case SDL_EVENT_RENDER_TARGETS_RESET: return "render_targets_reset";
case SDL_EVENT_RENDER_DEVICE_RESET: return "render_device_reset";
case SDL_EVENT_RENDER_DEVICE_LOST: return "render_device_lost";
// User event (assuming it should be included)
case SDL_EVENT_USER: return "user";
default: return "unknown";
}
}
const char* mouse_button_to_string(int mouse) {
switch (mouse) {
case SDL_BUTTON_LEFT: return "left";
case SDL_BUTTON_MIDDLE: return "middle";
case SDL_BUTTON_RIGHT: return "right";
case SDL_BUTTON_X1: return "x1";
case SDL_BUTTON_X2: return "x2";
default: return "left";
}
}
static void wota_write_vec2(WotaBuffer *wb, double x, double y) {
// We'll store as WOTA_ARR of length 2, then two numbers
wota_write_array(wb, 2);
wota_write_number(wb, x);
wota_write_number(wb, y);
}
static int event2wota_count_props(const SDL_Event *event)
{
// We always store at least "type" and "timestamp".
int count = 2;
switch (event->type) {
case SDL_EVENT_AUDIO_DEVICE_ADDED:
case SDL_EVENT_AUDIO_DEVICE_REMOVED:
count += 2; // which, recording
break;
case SDL_EVENT_DISPLAY_ORIENTATION:
case SDL_EVENT_DISPLAY_ADDED:
case SDL_EVENT_DISPLAY_REMOVED:
case SDL_EVENT_DISPLAY_MOVED:
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
count += 3; // which, data1, data2
break;
case SDL_EVENT_MOUSE_MOTION:
count += 5;
break;
case SDL_EVENT_MOUSE_WHEEL:
// window, which, scroll, mouse => 4 extra
count += 4;
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
// window, which, down, button, clicks, mouse => 6 extra
count += 6;
break;
case SDL_EVENT_SENSOR_UPDATE:
// which, sensor_timestamp => 2 extra
count += 2;
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
// window, which, down, repeat, key, scancode, mod => 7 extra
count += 7;
break;
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP:
// touch, finger, pos, d_pos, pressure, window => 6 extra
count += 6;
break;
case SDL_EVENT_DROP_BEGIN:
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
case SDL_EVENT_DROP_COMPLETE:
case SDL_EVENT_DROP_POSITION:
// window, pos, data, source => 4 extra
count += 4;
break;
case SDL_EVENT_TEXT_INPUT:
// window, text, mod => 3 extra
count += 3;
break;
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
case SDL_EVENT_CAMERA_DEVICE_REMOVED:
case SDL_EVENT_CAMERA_DEVICE_ADDED:
case SDL_EVENT_CAMERA_DEVICE_DENIED:
// which => 1 extra
count += 1;
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
// owner => 1 extra
count += 1;
break;
/* Window events (just group them all together) */
case SDL_EVENT_WINDOW_SHOWN:
case SDL_EVENT_WINDOW_HIDDEN:
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_MOVED:
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
case SDL_EVENT_WINDOW_MOUSE_ENTER:
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
case SDL_EVENT_WINDOW_HIT_TEST:
case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
case SDL_EVENT_WINDOW_OCCLUDED:
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
case SDL_EVENT_WINDOW_DESTROYED:
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
// which, data1, data2 => 3 extra
count += 3;
break;
case SDL_EVENT_JOYSTICK_ADDED:
case SDL_EVENT_JOYSTICK_REMOVED:
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
// which => 1 extra
count += 1;
break;
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
// which, axis, value => 3 extra
count += 3;
break;
case SDL_EVENT_JOYSTICK_BALL_MOTION:
// which, ball, rel => 3 extra
count += 3;
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_EVENT_JOYSTICK_BUTTON_UP:
// which, button, down => 3 extra
count += 3;
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_EVENT_GAMEPAD_REMAPPED:
case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
// which => 1 extra
count += 1;
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
// which, axis, value => 3 extra
count += 3;
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
// which, button, down => 3 extra
count += 3;
break;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
// which, touchpad, finger, pos, pressure => 5 extra
count += 5;
break;
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
// which, sensor, sensor_timestamp => 3 extra
count += 3;
break;
case SDL_EVENT_USER:
// cb => 1 extra
count += 1;
break;
}
return count;
}
static void event2wota_write(WotaBuffer *wb, const SDL_Event *e, int c) {
wota_write_record(wb, (unsigned long long)c);
wota_write_text(wb, "type");
wota_write_text(wb, event_type_to_string(e->type));
wota_write_text(wb, "timestamp");
wota_write_number(wb, (double)e->common.timestamp);
switch(e->type) {
case SDL_EVENT_AUDIO_DEVICE_ADDED:
case SDL_EVENT_AUDIO_DEVICE_REMOVED:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->adevice.which);
wota_write_text(wb, "recording");
wota_write_sym(wb, e->adevice.recording ? WOTA_TRUE : WOTA_FALSE);
break;
case SDL_EVENT_DISPLAY_ORIENTATION:
case SDL_EVENT_DISPLAY_ADDED:
case SDL_EVENT_DISPLAY_REMOVED:
case SDL_EVENT_DISPLAY_MOVED:
case SDL_EVENT_DISPLAY_DESKTOP_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CURRENT_MODE_CHANGED:
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->display.displayID);
wota_write_text(wb, "data1");
wota_write_number(wb, (double)e->display.data1);
wota_write_text(wb, "data2");
wota_write_number(wb, (double)e->display.data2);
break;
case SDL_EVENT_MOUSE_MOTION:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->motion.windowID);
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->motion.which);
wota_write_text(wb, "state");
wota_write_number(wb, (double)e->motion.state);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->motion.x, (double)e->motion.y);
wota_write_text(wb, "d_pos");
wota_write_vec2(wb, (double)e->motion.xrel, (double)e->motion.yrel);
break;
case SDL_EVENT_MOUSE_WHEEL:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->wheel.windowID);
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->wheel.which);
wota_write_text(wb, "scroll");
wota_write_vec2(wb, (double)e->wheel.x, (double)e->wheel.y);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->wheel.mouse_x, (double)e->wheel.mouse_y);
break;
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->button.windowID);
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->button.which);
wota_write_text(wb, "down");
wota_write_sym(wb, e->button.down ? WOTA_TRUE : WOTA_FALSE);
wota_write_text(wb, "button");
wota_write_text(wb, mouse_button_to_string(e->button.button));
wota_write_text(wb, "clicks");
wota_write_number(wb, (double)e->button.clicks);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->button.x, (double)e->button.y);
break;
case SDL_EVENT_SENSOR_UPDATE:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->sensor.which);
wota_write_text(wb, "sensor_timestamp");
wota_write_number(wb, (double)e->sensor.sensor_timestamp);
break;
case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->key.windowID);
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->key.which);
wota_write_text(wb, "down");
wota_write_sym(wb, e->key.down ? WOTA_TRUE : WOTA_FALSE);
wota_write_text(wb, "repeat");
wota_write_sym(wb, e->key.repeat ? WOTA_TRUE : WOTA_FALSE);
wota_write_text(wb, "key");
wota_write_number(wb, (double)e->key.key);
wota_write_text(wb, "scancode");
wota_write_number(wb, (double)e->key.scancode);
wota_write_text(wb, "mod");
wota_write_number(wb, 0);
break;
case SDL_EVENT_FINGER_MOTION:
case SDL_EVENT_FINGER_DOWN:
case SDL_EVENT_FINGER_UP:
wota_write_text(wb, "touch");
wota_write_number(wb, (double)e->tfinger.touchID);
wota_write_text(wb, "finger");
wota_write_number(wb, (double)e->tfinger.fingerID);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->tfinger.x, (double)e->tfinger.y);
wota_write_text(wb, "d_pos");
wota_write_vec2(wb, (double)e->tfinger.dx, (double)e->tfinger.dy);
wota_write_text(wb, "pressure");
wota_write_number(wb, (double)e->tfinger.pressure);
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->key.windowID);
break;
case SDL_EVENT_DROP_BEGIN:
case SDL_EVENT_DROP_FILE:
case SDL_EVENT_DROP_TEXT:
case SDL_EVENT_DROP_COMPLETE:
case SDL_EVENT_DROP_POSITION:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->drop.windowID);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->drop.x, (double)e->drop.y);
wota_write_text(wb, "data");
wota_write_text(wb, e->drop.data ? e->drop.data : "");
wota_write_text(wb, "source");
wota_write_text(wb, e->drop.source ? e->drop.source : "");
break;
case SDL_EVENT_TEXT_INPUT:
wota_write_text(wb, "window");
wota_write_number(wb, (double)e->text.windowID);
wota_write_text(wb, "text");
wota_write_text(wb, e->text.text);
wota_write_text(wb, "mod");
wota_write_number(wb, 0);
break;
case SDL_EVENT_CAMERA_DEVICE_APPROVED:
case SDL_EVENT_CAMERA_DEVICE_REMOVED:
case SDL_EVENT_CAMERA_DEVICE_ADDED:
case SDL_EVENT_CAMERA_DEVICE_DENIED:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->cdevice.which);
break;
case SDL_EVENT_CLIPBOARD_UPDATE:
wota_write_text(wb, "owner");
wota_write_sym(wb, e->clipboard.owner ? WOTA_TRUE : WOTA_FALSE);
break;
case SDL_EVENT_WINDOW_SHOWN:
case SDL_EVENT_WINDOW_HIDDEN:
case SDL_EVENT_WINDOW_EXPOSED:
case SDL_EVENT_WINDOW_MOVED:
case SDL_EVENT_WINDOW_RESIZED:
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
case SDL_EVENT_WINDOW_METAL_VIEW_RESIZED:
case SDL_EVENT_WINDOW_MINIMIZED:
case SDL_EVENT_WINDOW_MAXIMIZED:
case SDL_EVENT_WINDOW_RESTORED:
case SDL_EVENT_WINDOW_MOUSE_ENTER:
case SDL_EVENT_WINDOW_MOUSE_LEAVE:
case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST:
case SDL_EVENT_WINDOW_CLOSE_REQUESTED:
case SDL_EVENT_WINDOW_HIT_TEST:
case SDL_EVENT_WINDOW_ICCPROF_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_CHANGED:
case SDL_EVENT_WINDOW_DISPLAY_SCALE_CHANGED:
case SDL_EVENT_WINDOW_SAFE_AREA_CHANGED:
case SDL_EVENT_WINDOW_OCCLUDED:
case SDL_EVENT_WINDOW_ENTER_FULLSCREEN:
case SDL_EVENT_WINDOW_LEAVE_FULLSCREEN:
case SDL_EVENT_WINDOW_DESTROYED:
case SDL_EVENT_WINDOW_HDR_STATE_CHANGED:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->window.windowID);
wota_write_text(wb, "data1");
wota_write_number(wb, (double)e->window.data1);
wota_write_text(wb, "data2");
wota_write_number(wb, (double)e->window.data2);
break;
case SDL_EVENT_JOYSTICK_ADDED:
case SDL_EVENT_JOYSTICK_REMOVED:
case SDL_EVENT_JOYSTICK_UPDATE_COMPLETE:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->jdevice.which);
break;
case SDL_EVENT_JOYSTICK_AXIS_MOTION:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->jaxis.which);
wota_write_text(wb, "axis");
wota_write_number(wb, (double)e->jaxis.axis);
wota_write_text(wb, "value");
wota_write_number(wb, (double)e->jaxis.value);
break;
case SDL_EVENT_JOYSTICK_BALL_MOTION:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->jball.which);
wota_write_text(wb, "ball");
wota_write_number(wb, (double)e->jball.ball);
wota_write_text(wb, "rel");
wota_write_vec2(wb, (double)e->jball.xrel, (double)e->jball.yrel);
break;
case SDL_EVENT_JOYSTICK_BUTTON_DOWN:
case SDL_EVENT_JOYSTICK_BUTTON_UP:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->jbutton.which);
wota_write_text(wb, "button");
wota_write_number(wb, (double)e->jbutton.button);
wota_write_text(wb, "down");
wota_write_sym(wb, e->jbutton.down ? WOTA_TRUE : WOTA_FALSE);
break;
case SDL_EVENT_GAMEPAD_ADDED:
case SDL_EVENT_GAMEPAD_REMOVED:
case SDL_EVENT_GAMEPAD_REMAPPED:
case SDL_EVENT_GAMEPAD_UPDATE_COMPLETE:
case SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->gdevice.which);
break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->gaxis.which);
wota_write_text(wb, "axis");
wota_write_number(wb, (double)e->gaxis.axis);
wota_write_text(wb, "value");
wota_write_number(wb, (double)e->gaxis.value);
break;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
case SDL_EVENT_GAMEPAD_BUTTON_UP:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->gbutton.which);
wota_write_text(wb, "button");
wota_write_number(wb, (double)e->gbutton.button);
wota_write_text(wb, "down");
wota_write_sym(wb, e->gbutton.down ? WOTA_TRUE : WOTA_FALSE);
break;
case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN:
case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION:
case SDL_EVENT_GAMEPAD_TOUCHPAD_UP:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->gtouchpad.which);
wota_write_text(wb, "touchpad");
wota_write_number(wb, (double)e->gtouchpad.touchpad);
wota_write_text(wb, "finger");
wota_write_number(wb, (double)e->gtouchpad.finger);
wota_write_text(wb, "pos");
wota_write_vec2(wb, (double)e->gtouchpad.x, (double)e->gtouchpad.y);
wota_write_text(wb, "pressure");
wota_write_number(wb, (double)e->gtouchpad.pressure);
break;
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
wota_write_text(wb, "which");
wota_write_number(wb, (double)e->gsensor.which);
wota_write_text(wb, "sensor");
wota_write_number(wb, (double)e->gsensor.sensor);
wota_write_text(wb, "sensor_timestamp");
wota_write_number(wb, (double)e->gsensor.sensor_timestamp);
break;
case SDL_EVENT_USER:
wota_write_text(wb, "cb");
wota_write_number(wb, (double)(uintptr_t)e->user.data1);
break;
}
}
static WotaBuffer event2wota(const SDL_Event *event) {
WotaBuffer wb;
wota_buffer_init(&wb, 8);
int n = event2wota_count_props(event);
event2wota_write(&wb, event, n);
return wb;
}
// Get all events directly from SDL event queue
JSC_CCALL(input_get_events,
JSValue events_array = JS_NewArray(js);
SDL_Event event;
int event_count = 0;
while (SDL_PollEvent(&event)) {
WotaBuffer wb = event2wota(&event);
JSValue event_obj = wota2value(js, wb.data);
JS_SetPropertyUint32(js, events_array, event_count, event_obj);
wota_buffer_free(&wb);
event_count++;
}
return events_array;
)
static const JSCFunctionListEntry js_input_funcs[] = {
MIST_FUNC_DEF(input, mouse_show, 1),
MIST_FUNC_DEF(input, mouse_lock, 1),
MIST_FUNC_DEF(input, keyname, 1),
MIST_FUNC_DEF(input, keymod, 0),
MIST_FUNC_DEF(input, mousestate, 0),
MIST_FUNC_DEF(input, get_events, 0),
};
JSValue js_input_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js,mod,js_input_funcs,countof(js_input_funcs));
return mod;
}

12
source/qjs_sdl_input.h Normal file
View File

@@ -0,0 +1,12 @@
#ifndef QJS_SDL_INPUT_H
#define QJS_SDL_INPUT_H
#include <quickjs.h>
const char* event_type_to_string(uint32_t event_type);
const char* mouse_button_to_string(int mouse);
// JavaScript module entry point
JSValue js_input_use(JSContext *js);
#endif // QJS_SDL_INPUT_H

View File

@@ -1744,12 +1744,15 @@ JSC_CCALL(sdl_createWindowAndRenderer,
return ret;
)
// Hook function to set up endowments for the video actor
static void video_actor_hook(JSContext *js) {
// Get prosperon object
JSValue global = JS_GetGlobalObject(js);
JSValue prosperon = JS_GetPropertyStr(js, global, "prosperon");
JS_FreeValue(js,global);
#include "qjs_wota.h"
JSValue js_sdl_video_use(JSContext *js) {
printf("initing on thread %d\n", SDL_GetThreadID(NULL));
if (!SDL_Init(SDL_INIT_VIDEO))
return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
JSValue ret = JS_NewObject(js);
// Initialize classes
QJSCLASSPREP_FUNCS(SDL_Window)
@@ -1769,50 +1772,19 @@ static void video_actor_hook(JSContext *js) {
// Set prototype on constructor
JS_SetConstructor(js, texture_ctor, SDL_Texture_proto);
// Get or create endowments object
JSValue endowments = JS_GetPropertyStr(js, prosperon, "endowments");
if (JS_IsUndefined(endowments)) {
endowments = JS_NewObject(js);
JS_SetPropertyStr(js, prosperon, "endowments", JS_DupValue(js, endowments));
}
// Set constructors in endowments
JS_SetPropertyStr(js, endowments, "window", window_ctor);
JS_SetPropertyStr(js, endowments, "texture", texture_ctor);
JS_SetPropertyStr(js, ret, "window", window_ctor);
JS_SetPropertyStr(js, ret, "texture", texture_ctor);
// Add utility function
JS_SetPropertyStr(js, endowments, "createWindowAndRenderer",
JS_SetPropertyStr(js, ret, "createWindowAndRenderer",
JS_NewCFunction(js, js_sdl_createWindowAndRenderer, "createWindowAndRenderer", 4));
// Add cursor functions
JS_SetPropertyStr(js, endowments, "createCursor",
JS_SetPropertyStr(js, ret, "createCursor",
JS_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2));
JS_SetPropertyStr(js, endowments, "setCursor",
JS_SetPropertyStr(js, ret, "setCursor",
JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1));
JS_FreeValue(js, endowments);
JS_FreeValue(js, prosperon);
}
#include "qjs_wota.h"
JSValue js_sdl_video_use(JSContext *js) {
if (!SDL_Init(SDL_INIT_VIDEO))
return JS_ThrowInternalError(js, "Unable to initialize video subsystem: %s", SDL_GetError());
// Generate a unique ID for the video actor
char id[64];
snprintf(id, sizeof(id), "video_%llu", (unsigned long long)SDL_GetTicks());
JSValue startup = JS_NewObject(js);
JS_SetPropertyStr(js,startup, "id", JS_NewStringLen(js,id,64));
JS_SetPropertyStr(js,startup, "program", JS_NewString(js,"prosperon/_sdl_video"));
JS_SetPropertyStr(js,startup,"main",JS_NewBool(js,1));
void *wota = value2wota(js,startup, JS_UNDEFINED);
JS_FreeValue(js,startup);
cell_rt *actor = create_actor(wota, video_actor_hook);
return actor2js(js,actor);
return ret;
}

596
source/qjs_socket.c Normal file
View File

@@ -0,0 +1,596 @@
#include "quickjs.h"
#include "jsffi.h"
#include "qjs_blob.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
// Helper to convert JS value to file descriptor
static int js2fd(JSContext *ctx, JSValueConst val)
{
int fd;
if (JS_ToInt32(ctx, &fd, val) < 0) {
JS_ThrowTypeError(ctx, "Expected file descriptor number");
return -1;
}
return fd;
}
// SOCKET FUNCTIONS
JSC_CCALL(socket_getaddrinfo,
const char *node = NULL;
const char *service = NULL;
struct addrinfo hints, *res;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if (!JS_IsNull(argv[0]) && !JS_IsUndefined(argv[0]))
node = JS_ToCString(js, argv[0]);
if (!JS_IsNull(argv[1]) && !JS_IsUndefined(argv[1]))
service = JS_ToCString(js, argv[1]);
// Parse optional hints object
if (argc > 2 && JS_IsObject(argv[2])) {
JSValue val;
val = JS_GetPropertyStr(js, argv[2], "family");
if (!JS_IsUndefined(val)) {
const char *family = JS_ToCString(js, val);
if (strcmp(family, "AF_INET") == 0) hints.ai_family = AF_INET;
else if (strcmp(family, "AF_INET6") == 0) hints.ai_family = AF_INET6;
JS_FreeCString(js, family);
}
JS_FreeValue(js, val);
val = JS_GetPropertyStr(js, argv[2], "socktype");
if (!JS_IsUndefined(val)) {
const char *socktype = JS_ToCString(js, val);
if (strcmp(socktype, "SOCK_STREAM") == 0) hints.ai_socktype = SOCK_STREAM;
else if (strcmp(socktype, "SOCK_DGRAM") == 0) hints.ai_socktype = SOCK_DGRAM;
JS_FreeCString(js, socktype);
}
JS_FreeValue(js, val);
val = JS_GetPropertyStr(js, argv[2], "flags");
if (!JS_IsUndefined(val)) {
hints.ai_flags = js2number(js, val);
}
JS_FreeValue(js, val);
val = JS_GetPropertyStr(js, argv[2], "passive");
if (JS_ToBool(js, val)) {
hints.ai_flags |= AI_PASSIVE;
}
JS_FreeValue(js, val);
}
int status = getaddrinfo(node, service, &hints, &res);
if (node) JS_FreeCString(js, node);
if (service) JS_FreeCString(js, service);
if (status != 0) {
return JS_ThrowReferenceError(js, "getaddrinfo error: %s", gai_strerror(status));
}
// Convert linked list to JS array
ret = JS_NewArray(js);
int idx = 0;
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
JSValue info = JS_NewObject(js);
JS_SetPropertyStr(js, info, "family", JS_NewInt32(js, p->ai_family));
JS_SetPropertyStr(js, info, "socktype", JS_NewInt32(js, p->ai_socktype));
JS_SetPropertyStr(js, info, "protocol", JS_NewInt32(js, p->ai_protocol));
// Convert address to string
char ipstr[INET6_ADDRSTRLEN];
void *addr;
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
} else {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
}
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
JS_SetPropertyStr(js, info, "address", JS_NewString(js, ipstr));
// Store the addrinfo for later use
struct addrinfo *copy = malloc(sizeof(struct addrinfo));
memcpy(copy, p, sizeof(struct addrinfo));
copy->ai_addr = malloc(p->ai_addrlen);
memcpy(copy->ai_addr, p->ai_addr, p->ai_addrlen);
copy->ai_next = NULL;
if (p->ai_canonname) {
copy->ai_canonname = strdup(p->ai_canonname);
}
// Store the addrinfo pointer as an internal property
// We'll need to handle this differently since we can't wrap it
// For now, we'll skip storing the raw addrinfo
JS_SetPropertyUint32(js, ret, idx++, info);
}
freeaddrinfo(res);
)
JSC_CCALL(socket_socket,
int domain = AF_INET;
int type = SOCK_STREAM;
int protocol = 0;
// Parse domain
if (JS_IsString(argv[0])) {
const char *domain_str = JS_ToCString(js, argv[0]);
if (strcmp(domain_str, "AF_INET") == 0) domain = AF_INET;
else if (strcmp(domain_str, "AF_INET6") == 0) domain = AF_INET6;
else if (strcmp(domain_str, "AF_UNIX") == 0) domain = AF_UNIX;
JS_FreeCString(js, domain_str);
} else if (JS_IsNumber(argv[0])) {
domain = js2number(js, argv[0]);
}
// Parse type
if (argc > 1) {
if (JS_IsString(argv[1])) {
const char *type_str = JS_ToCString(js, argv[1]);
if (strcmp(type_str, "SOCK_STREAM") == 0) type = SOCK_STREAM;
else if (strcmp(type_str, "SOCK_DGRAM") == 0) type = SOCK_DGRAM;
JS_FreeCString(js, type_str);
} else if (JS_IsNumber(argv[1])) {
type = js2number(js, argv[1]);
}
}
// Parse protocol
if (argc > 2) {
protocol = js2number(js, argv[2]);
}
int sockfd = socket(domain, type, protocol);
if (sockfd < 0) {
return JS_ThrowReferenceError(js, "socket failed: %s", strerror(errno));
}
return JS_NewInt32(js, sockfd);
)
JSC_CCALL(socket_bind,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
// For now, we'll only support manual address parsing
{
// Try to parse address and port manually
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address"));
int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port"));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
JS_FreeCString(js, addr_str);
return JS_ThrowReferenceError(js, "Invalid address");
}
JS_FreeCString(js, addr_str);
if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
return JS_ThrowReferenceError(js, "bind failed: %s", strerror(errno));
}
}
return JS_UNDEFINED;
)
JSC_CCALL(socket_connect,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
// For now, we'll only support manual address parsing
{
// Try to parse address and port manually
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[1], "address"));
int port = js2number(js, JS_GetPropertyStr(js, argv[1], "port"));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
JS_FreeCString(js, addr_str);
return JS_ThrowReferenceError(js, "Invalid address");
}
JS_FreeCString(js, addr_str);
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
return JS_ThrowReferenceError(js, "connect failed: %s", strerror(errno));
}
}
return JS_UNDEFINED;
)
JSC_CCALL(socket_listen,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
int backlog = 10;
if (argc > 1) {
backlog = js2number(js, argv[1]);
}
if (listen(sockfd, backlog) < 0) {
return JS_ThrowReferenceError(js, "listen failed: %s", strerror(errno));
}
return JS_UNDEFINED;
)
JSC_CCALL(socket_accept,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
struct sockaddr_storage their_addr;
socklen_t addr_size = sizeof their_addr;
int new_sockfd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);
if (new_sockfd < 0) {
return JS_ThrowReferenceError(js, "accept failed: %s", strerror(errno));
}
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "socket", JS_NewInt32(js, new_sockfd));
// Get peer address info
char ipstr[INET6_ADDRSTRLEN];
int port;
if (their_addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&their_addr;
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&their_addr;
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin6_port);
}
JSValue addr_info = JS_NewObject(js);
JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr));
JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port));
JS_SetPropertyStr(js, ret, "address", addr_info);
)
JSC_CCALL(socket_send,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
size_t len;
ssize_t sent;
int flags = 0;
if (argc > 2) {
flags = js2number(js, argv[2]);
}
if (JS_IsString(argv[1])) {
const char *data = JS_ToCStringLen(js, &len, argv[1]);
sent = send(sockfd, data, len, flags);
JS_FreeCString(js, data);
} else {
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
sent = send(sockfd, data, len, flags);
}
if (sent < 0) {
return JS_ThrowReferenceError(js, "send failed: %s", strerror(errno));
}
return JS_NewInt64(js, sent);
)
JSC_CCALL(socket_recv,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
size_t len = 4096;
if (argc > 1) {
len = js2number(js, argv[1]);
}
int flags = 0;
if (argc > 2) {
flags = js2number(js, argv[2]);
}
void *buf = malloc(len);
if (!buf) {
return JS_ThrowReferenceError(js, "malloc failed");
}
ssize_t received = recv(sockfd, buf, len, flags);
if (received < 0) {
free(buf);
return JS_ThrowReferenceError(js, "recv failed: %s", strerror(errno));
}
ret = js_new_blob_stoned_copy(js, buf, received);
free(buf);
return ret;
)
JSC_CCALL(socket_sendto,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
size_t len;
ssize_t sent;
int flags = 0;
if (argc > 3) {
flags = js2number(js, argv[3]);
}
// Get destination address
struct sockaddr *to_addr;
socklen_t to_len;
// For now, we'll only support manual address parsing
{
// Try to parse address and port manually
const char *addr_str = JS_ToCString(js, JS_GetPropertyStr(js, argv[2], "address"));
int port = js2number(js, JS_GetPropertyStr(js, argv[2], "port"));
static struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, addr_str, &addr.sin_addr) <= 0) {
JS_FreeCString(js, addr_str);
return JS_ThrowReferenceError(js, "Invalid address");
}
JS_FreeCString(js, addr_str);
to_addr = (struct sockaddr *)&addr;
to_len = sizeof(addr);
}
if (JS_IsString(argv[1])) {
const char *data = JS_ToCStringLen(js, &len, argv[1]);
sent = sendto(sockfd, data, len, flags, to_addr, to_len);
JS_FreeCString(js, data);
} else {
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
sent = sendto(sockfd, data, len, flags, to_addr, to_len);
}
if (sent < 0) {
return JS_ThrowReferenceError(js, "sendto failed: %s", strerror(errno));
}
return JS_NewInt64(js, sent);
)
JSC_CCALL(socket_recvfrom,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
size_t len = 4096;
if (argc > 1) {
len = js2number(js, argv[1]);
}
int flags = 0;
if (argc > 2) {
flags = js2number(js, argv[2]);
}
void *buf = malloc(len);
if (!buf) {
return JS_ThrowReferenceError(js, "malloc failed");
}
struct sockaddr_storage from_addr;
socklen_t from_len = sizeof from_addr;
ssize_t received = recvfrom(sockfd, buf, len, flags,
(struct sockaddr *)&from_addr, &from_len);
if (received < 0) {
free(buf);
return JS_ThrowReferenceError(js, "recvfrom failed: %s", strerror(errno));
}
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "data", js_new_blob_stoned_copy(js, buf, received));
free(buf);
// Get source address info
char ipstr[INET6_ADDRSTRLEN];
int port;
if (from_addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&from_addr;
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&from_addr;
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin6_port);
}
JSValue addr_info = JS_NewObject(js);
JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr));
JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port));
JS_SetPropertyStr(js, ret, "address", addr_info);
)
JSC_CCALL(socket_shutdown,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
int how = SHUT_RDWR;
if (argc > 1) {
how = js2number(js, argv[1]);
}
if (shutdown(sockfd, how) < 0) {
return JS_ThrowReferenceError(js, "shutdown failed: %s", strerror(errno));
}
return JS_UNDEFINED;
)
JSC_CCALL(socket_getpeername,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
struct sockaddr_storage addr;
socklen_t len = sizeof addr;
if (getpeername(sockfd, (struct sockaddr *)&addr, &len) < 0) {
return JS_ThrowReferenceError(js, "getpeername failed: %s", strerror(errno));
}
char ipstr[INET6_ADDRSTRLEN];
int port;
if (addr.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&addr;
inet_ntop(AF_INET, &s->sin_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin6_port);
}
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "address", JS_NewString(js, ipstr));
JS_SetPropertyStr(js, ret, "port", JS_NewInt32(js, port));
)
JSC_CCALL(socket_gethostname,
char hostname[256];
if (gethostname(hostname, sizeof(hostname)) < 0) {
return JS_ThrowReferenceError(js, "gethostname failed: %s", strerror(errno));
}
return JS_NewString(js, hostname);
)
JSC_CCALL(socket_gai_strerror,
int errcode = js2number(js, argv[0]);
return JS_NewString(js, gai_strerror(errcode));
)
JSC_CCALL(socket_setsockopt,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
int level = SOL_SOCKET;
int optname = 0;
// Parse level
if (JS_IsString(argv[1])) {
const char *level_str = JS_ToCString(js, argv[1]);
if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET;
else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP;
else if (strcmp(level_str, "IPPROTO_IP") == 0) level = IPPROTO_IP;
else if (strcmp(level_str, "IPPROTO_IPV6") == 0) level = IPPROTO_IPV6;
JS_FreeCString(js, level_str);
} else {
level = js2number(js, argv[1]);
}
// Parse option name
if (JS_IsString(argv[2])) {
const char *opt_str = JS_ToCString(js, argv[2]);
if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
else if (strcmp(opt_str, "SO_BROADCAST") == 0) optname = SO_BROADCAST;
JS_FreeCString(js, opt_str);
} else {
optname = js2number(js, argv[2]);
}
// Parse option value
if (JS_IsBool(argv[3])) {
int optval = JS_ToBool(js, argv[3]);
if (setsockopt(sockfd, level, optname, &optval, sizeof(optval)) < 0) {
return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno));
}
} else if (JS_IsNumber(argv[3])) {
int optval = js2number(js, argv[3]);
if (setsockopt(sockfd, level, optname, &optval, sizeof(optval)) < 0) {
return JS_ThrowReferenceError(js, "setsockopt failed: %s", strerror(errno));
}
} else {
return JS_ThrowTypeError(js, "Invalid option value");
}
return JS_UNDEFINED;
)
JSC_CCALL(socket_close,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
if (close(sockfd) != 0)
return JS_ThrowReferenceError(js, "close failed: %s", strerror(errno));
return JS_UNDEFINED;
)
static const JSCFunctionListEntry js_socket_funcs[] = {
MIST_FUNC_DEF(socket, getaddrinfo, 3),
MIST_FUNC_DEF(socket, socket, 3),
MIST_FUNC_DEF(socket, bind, 2),
MIST_FUNC_DEF(socket, connect, 2),
MIST_FUNC_DEF(socket, listen, 2),
MIST_FUNC_DEF(socket, accept, 1),
MIST_FUNC_DEF(socket, send, 3),
MIST_FUNC_DEF(socket, recv, 3),
MIST_FUNC_DEF(socket, sendto, 4),
MIST_FUNC_DEF(socket, recvfrom, 3),
MIST_FUNC_DEF(socket, shutdown, 2),
MIST_FUNC_DEF(socket, getpeername, 1),
MIST_FUNC_DEF(socket, gethostname, 0),
MIST_FUNC_DEF(socket, gai_strerror, 1),
MIST_FUNC_DEF(socket, setsockopt, 4),
MIST_FUNC_DEF(socket, close, 1),
};
JSValue js_socket_use(JSContext *js) {
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_socket_funcs, countof(js_socket_funcs));
// Add constants
JS_SetPropertyStr(js, mod, "AF_UNSPEC", JS_NewInt32(js, AF_UNSPEC));
JS_SetPropertyStr(js, mod, "AF_INET", JS_NewInt32(js, AF_INET));
JS_SetPropertyStr(js, mod, "AF_INET6", JS_NewInt32(js, AF_INET6));
JS_SetPropertyStr(js, mod, "AF_UNIX", JS_NewInt32(js, AF_UNIX));
JS_SetPropertyStr(js, mod, "SOCK_STREAM", JS_NewInt32(js, SOCK_STREAM));
JS_SetPropertyStr(js, mod, "SOCK_DGRAM", JS_NewInt32(js, SOCK_DGRAM));
JS_SetPropertyStr(js, mod, "AI_PASSIVE", JS_NewInt32(js, AI_PASSIVE));
JS_SetPropertyStr(js, mod, "SHUT_RD", JS_NewInt32(js, SHUT_RD));
JS_SetPropertyStr(js, mod, "SHUT_WR", JS_NewInt32(js, SHUT_WR));
JS_SetPropertyStr(js, mod, "SHUT_RDWR", JS_NewInt32(js, SHUT_RDWR));
JS_SetPropertyStr(js, mod, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
JS_SetPropertyStr(js, mod, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
return mod;
}

8
source/qjs_socket.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_SOCKET_H
#define QJS_SOCKET_H
#include "quickjs.h"
JSValue js_socket_use(JSContext *js);
#endif // QJS_SOCKET_H

94
source/qjs_text.c Normal file
View File

@@ -0,0 +1,94 @@
#include "qjs_text.h"
#include "qjs_blob.h"
#include "blob.h"
#include "jsffi.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;
// Hex encoding: each byte becomes 2 hex characters
size_t hex_len = blob_len * 2;
char *hex_str = malloc(hex_len + 1);
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';
ret = JS_NewString(js, hex_str);
free(hex_str);
)
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 output length: each 5 bytes becomes 8 base32 chars
size_t groups = (blob_len + 4) / 5;
size_t b32_len = groups * 8;
char *b32_str = malloc(b32_len + 1);
size_t in_idx = 0;
size_t out_idx = 0;
while (in_idx < blob_len) {
// Process 5 bytes (40 bits) at a time to produce 8 base32 chars
uint64_t buf = 0;
int bytes_read = 0;
// Read up to 5 bytes into buffer
for (int i = 0; i < 5 && in_idx < blob_len; i++) {
buf = (buf << 8) | bytes[in_idx++];
bytes_read++;
}
// Pad with zeros if we read fewer than 5 bytes
buf = buf << (8 * (5 - bytes_read));
// Extract 8 groups of 5 bits from the 40-bit buffer
for (int i = 7; i >= 0; i--) {
b32_str[out_idx + i] = b32_digits[buf & 0x1F];
buf >>= 5;
}
out_idx += 8;
}
// Replace trailing chars with padding if needed
int padding = (5 - (blob_len % 5)) % 5;
if (padding > 0) {
static const int pad_chars[] = {0, 6, 4, 3, 1};
for (int i = 0; i < pad_chars[padding]; i++) {
b32_str[b32_len - 1 - i] = '=';
}
}
b32_str[b32_len] = '\0';
ret = JS_NewString(js, b32_str);
free(b32_str);
)
static const JSCFunctionListEntry js_text_funcs[] = {
MIST_FUNC_DEF(text, blob_to_hex, 1),
MIST_FUNC_DEF(text, blob_to_base32, 1),
};
JSValue js_text_use(JSContext *js)
{
JSValue mod = JS_NewObject(js);
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
return mod;
}

8
source/qjs_text.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_TEXT_H
#define QJS_TEXT_H
#include <quickjs.h>
JSValue js_text_use(JSContext *js);
#endif

211
source/qjs_utf8.c Normal file
View File

@@ -0,0 +1,211 @@
#include "qjs_utf8.h"
#include "qjs_blob.h"
#include "jsffi.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) return JS_ThrowTypeError(js, "Expected 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_UNDEFINED;
}
// 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;
}

8
source/qjs_utf8.h Normal file
View File

@@ -0,0 +1,8 @@
#ifndef QJS_UTF8_H
#define QJS_UTF8_H
#include "cell.h"
JSValue js_utf8_use(JSContext*);
#endif

View File

@@ -4,9 +4,14 @@
#include "wota.h"
#include <stdlib.h>
typedef struct ObjectRef {
void *ptr;
struct ObjectRef *next;
} ObjectRef;
typedef struct WotaEncodeContext {
JSContext *ctx;
JSValue visited_stack;
ObjectRef *visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
@@ -14,38 +19,51 @@ typedef struct WotaEncodeContext {
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
JS_SetPropertyInt64(ctx, enc->visited_stack, len, JS_DupValue(ctx, val));
if (!JS_IsObject(val)) return;
ObjectRef *ref = malloc(sizeof(ObjectRef));
if (!ref) return;
ref->ptr = JS_VALUE_GET_PTR(val);
ref->next = enc->visited_stack;
enc->visited_stack = ref;
}
static void wota_stack_pop(WotaEncodeContext *enc)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
JS_SetPropertyStr(ctx, enc->visited_stack, "length", JS_NewUint32(ctx, len - 1));
if (!enc->visited_stack) return;
ObjectRef *top = enc->visited_stack;
enc->visited_stack = top->next;
free(top);
}
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
{
JSContext *ctx = enc->ctx;
int len = JS_ArrayLength(ctx, enc->visited_stack);
for (int i = 0; i < len; i++) {
JSValue elem = JS_GetPropertyUint32(ctx, enc->visited_stack, i);
if (JS_IsObject(elem) && JS_IsObject(val))
if (JS_StrictEq(ctx, elem, val)) {
JS_FreeValue(ctx, elem);
return 1;
}
JS_FreeValue(ctx, elem);
if (!JS_IsObject(val)) return 0;
void *ptr = JS_VALUE_GET_PTR(val);
ObjectRef *current = enc->visited_stack;
while (current) {
if (current->ptr == ptr) return 1;
current = current->next;
}
return 0;
}
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val)
static void wota_stack_free(WotaEncodeContext *enc)
{
while (enc->visited_stack) {
wota_stack_pop(enc);
}
}
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSAtom key, JSValueConst val)
{
if (JS_IsUndefined(enc->replacer)) return JS_DupValue(enc->ctx, val);
JSValue args[2] = { JS_DupValue(enc->ctx, key), JS_DupValue(enc->ctx, val) };
JSValue key_val = (key == JS_ATOM_NULL) ? JS_UNDEFINED : JS_AtomToValue(enc->ctx, key);
JSValue args[2] = { key_val, JS_DupValue(enc->ctx, val) };
JSValue result = JS_Call(enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue(enc->ctx, args[0]);
JS_FreeValue(enc->ctx, args[1]);
@@ -53,7 +71,7 @@ static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSVal
return result;
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key);
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key);
static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder)
{
@@ -65,33 +83,39 @@ static void encode_object_properties(WotaEncodeContext *enc, JSValueConst val, J
return;
}
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
if (!JS_IsFunction(ctx, prop_val)) non_function_count++;
JS_FreeValue(ctx, prop_val);
}
wota_write_record(&enc->wb, non_function_count);
JSValue props[plen];
JSAtom atoms[plen];
for (uint32_t i = 0; i < plen; i++) {
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
if (!JS_IsFunction(ctx, prop_val)) {
const char *prop_name = JS_AtomToCString(ctx, ptab[i].atom);
JSValue prop_key = JS_AtomToValue(ctx, ptab[i].atom);
wota_write_text(&enc->wb, prop_name);
wota_encode_value(enc, prop_val, val, prop_key);
JS_FreeCString(ctx, prop_name);
JS_FreeValue(ctx, prop_key);
}
atoms[non_function_count] = ptab[i].atom;
props[non_function_count++] = prop_val;
} else
JS_FreeValue(ctx, prop_val);
JS_FreeAtom(ctx, ptab[i].atom);
}
wota_write_record(&enc->wb, non_function_count);
for (uint32_t i = 0; i < non_function_count; i++) {
size_t plen;
const char *prop_name = JS_AtomToCStringLen(ctx, &plen, atoms[i]);
JSValue prop_val = props[i];
wota_write_text_len(&enc->wb, prop_name, plen);
wota_encode_value(enc, prop_val, val, atoms[i]);
JS_FreeCString(ctx, prop_name);
JS_FreeValue(ctx, prop_val);
}
for (int i = 0; i < plen; i++)
JS_FreeAtom(ctx, ptab[i].atom);
js_free(ctx, ptab);
}
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key)
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key)
{
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsUndefined(enc->replacer) && !JS_IsUndefined(key))
if (!JS_IsUndefined(enc->replacer) && key != JS_ATOM_NULL)
replaced = apply_replacer(enc, holder, key, val);
else
replaced = JS_DupValue(enc->ctx, val);
@@ -115,8 +139,9 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString(ctx, replaced);
wota_write_text(&enc->wb, str ? str : "");
size_t plen;
const char *str = JS_ToCStringLen(ctx, &plen, replaced);
wota_write_text_len(&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString(ctx, str);
break;
}
@@ -140,18 +165,28 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
break;
}
wota_stack_push(enc, replaced);
int arr_len = JS_ArrayLength(ctx, replaced);
int64_t arr_len;
JS_GetLength(ctx, replaced, &arr_len);
wota_write_array(&enc->wb, arr_len);
for (int i = 0; i < arr_len; i++) {
for (int64_t i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
JSValue elem_key = JS_NewInt32(ctx, i);
wota_encode_value(enc, elem_val, replaced, elem_key);
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
wota_encode_value(enc, elem_val, replaced, idx_atom);
JS_FreeAtom(ctx, idx_atom);
JS_FreeValue(ctx, elem_val);
JS_FreeValue(ctx, elem_key);
}
wota_stack_pop(enc);
break;
}
cell_rt *crt = JS_GetContextOpaque(ctx);
JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
if (!JS_IsUndefined(adata)) {
wota_write_sym(&enc->wb, WOTA_PRIVATE);
wota_encode_value(enc, adata, replaced, JS_ATOM_NULL);
JS_FreeValue(ctx, adata);
break;
}
JS_FreeValue(ctx, adata);
if (wota_stack_has(enc, replaced)) {
enc->cycle = 1;
break;
@@ -181,7 +216,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
JS_FreeValue(ctx, replaced);
}
static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver)
static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSAtom key, JSValue reviver)
{
uint64_t first_word = *(uint64_t *)data_ptr;
int type = (int)(first_word & 0xffU);
@@ -201,7 +236,14 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
case WOTA_SYM: {
int scode;
data_ptr = wota_read_sym(&scode, data_ptr);
if (scode == WOTA_NULL) *out_val = JS_UNDEFINED;
if (scode == WOTA_PRIVATE) {
JSValue inner = JS_UNDEFINED;
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_ATOM_NULL, reviver);
JSValue obj = JS_NewObject(ctx);
cell_rt *crt = JS_GetContextOpaque(ctx);
JS_SetProperty(ctx, obj, crt->actor_sym, inner);
*out_val = obj;
} else if (scode == WOTA_NULL) *out_val = JS_UNDEFINED;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
else if (scode == WOTA_TRUE) *out_val = JS_NewBool(ctx, 1);
else *out_val = JS_UNDEFINED;
@@ -226,10 +268,13 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
long long c;
data_ptr = wota_read_array(&c, data_ptr);
JSValue arr = JS_NewArray(ctx);
JS_SetLength(ctx, arr, c);
for (long long i = 0; i < c; i++) {
JSValue elem_val = JS_UNDEFINED;
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, JS_NewInt32(ctx, i), reviver);
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_atom, reviver);
JS_SetPropertyUint32(ctx, arr, i, elem_val);
JS_FreeAtom(ctx, idx_atom);
}
*out_val = arr;
break;
@@ -240,14 +285,15 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
JSValue obj = JS_NewObject(ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
data_ptr = wota_read_text(&tkey, data_ptr);
JSValue prop_key = tkey ? JS_NewString(ctx, tkey) : JS_UNDEFINED;
size_t key_len;
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
if (!tkey) continue; // invalid key
JSAtom prop_key = JS_NewAtomLen(ctx, tkey, key_len);
JSValue sub_val = JS_UNDEFINED;
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
if (tkey) JS_SetPropertyStr(ctx, obj, tkey, sub_val);
else JS_FreeValue(ctx, sub_val);
JS_FreeValue(ctx, prop_key);
if (tkey) free(tkey);
JS_SetProperty(ctx, obj, prop_key, sub_val);
JS_FreeAtom(ctx, prop_key);
free(tkey);
}
*out_val = obj;
break;
@@ -258,7 +304,8 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
break;
}
if (!JS_IsUndefined(reviver)) {
JSValue args[2] = { JS_DupValue(ctx, key), JS_DupValue(ctx, *out_val) };
JSValue key_val = (key == JS_ATOM_NULL) ? JS_UNDEFINED : JS_AtomToValue(ctx, key);
JSValue args[2] = { key_val, JS_DupValue(ctx, *out_val) };
JSValue revived = JS_Call(ctx, reviver, holder, 2, args);
JS_FreeValue(ctx, args[0]);
JS_FreeValue(ctx, args[1]);
@@ -271,24 +318,25 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
return data_ptr;
}
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer)
void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes)
{
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = JS_NewArray(ctx);
enc->visited_stack = NULL;
enc->cycle = 0;
enc->replacer = replacer;
wota_buffer_init(&enc->wb, 16);
wota_encode_value(enc, v, JS_UNDEFINED, JS_UNDEFINED);
wota_encode_value(enc, v, JS_UNDEFINED, JS_ATOM_NULL);
if (enc->cycle) {
JS_FreeValue(ctx, enc->visited_stack);
wota_stack_free(enc);
wota_buffer_free(&enc->wb);
return NULL;
}
JS_FreeValue(ctx, enc->visited_stack);
wota_stack_free(enc);
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
void *wota = realloc(enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
return wota;
}
@@ -296,7 +344,7 @@ JSValue wota2value(JSContext *ctx, void *wota)
{
JSValue result = JS_UNDEFINED;
JSValue holder = JS_NewObject(ctx);
decode_wota_value(ctx, wota, &result, holder, JS_UNDEFINED, JS_UNDEFINED);
decode_wota_value(ctx, wota, &result, holder, JS_ATOM_NULL, JS_UNDEFINED);
JS_FreeValue(ctx, holder);
return result;
}
@@ -304,22 +352,10 @@ JSValue wota2value(JSContext *ctx, void *wota)
static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = JS_NewArray(ctx);
enc->cycle = 0;
enc->replacer = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_UNDEFINED;
wota_buffer_init(&enc->wb, 16);
wota_encode_value(enc, argv[0], JS_UNDEFINED, JS_UNDEFINED);
if (enc->cycle) {
JS_FreeValue(ctx, enc->visited_stack);
wota_buffer_free(&enc->wb);
return JS_ThrowReferenceError(ctx, "Cannot encode cyclic object with wota");
}
JS_FreeValue(ctx, enc->visited_stack);
size_t total_bytes = enc->wb.size * sizeof(uint64_t);
JSValue ret = js_new_blob_stoned_copy(ctx, (uint8_t *)enc->wb.data, total_bytes);
wota_buffer_free(&enc->wb);
size_t total_bytes;
void *wota = value2wota(ctx, argv[0], JS_IsFunction(ctx,argv[1]) ? argv[1] : JS_UNDEFINED, &total_bytes);
JSValue ret = js_new_blob_stoned_copy(ctx, wota, total_bytes);
free(wota);
return ret;
}
@@ -333,7 +369,9 @@ static JSValue js_wota_decode(JSContext *ctx, JSValueConst this_val, int argc, J
char *data_ptr = (char *)buf;
JSValue result = JS_UNDEFINED;
JSValue holder = JS_NewObject(ctx);
decode_wota_value(ctx, data_ptr, &result, holder, JS_NewString(ctx, ""), reviver);
JSAtom empty_atom = JS_NewAtom(ctx, "");
decode_wota_value(ctx, data_ptr, &result, holder, empty_atom, reviver);
JS_FreeAtom(ctx, empty_atom);
JS_FreeValue(ctx, holder);
return result;
}

View File

@@ -6,7 +6,7 @@
JSValue js_wota_use(JSContext*);
void *value2wota(JSContext*, JSValue, JSValue);
void *value2wota(JSContext*, JSValue val, JSValue replacer, size_t *bytes);
JSValue wota2value(JSContext*, void*);
#endif

263
source/quickjs-atom.h Normal file
View File

@@ -0,0 +1,263 @@
/*
* QuickJS atom definitions
*
* Copyright (c) 2017-2018 Fabrice Bellard
* Copyright (c) 2017-2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef DEF
/* Note: first atoms are considered as keywords in the parser */
DEF(null, "null") /* must be first */
DEF(false, "false")
DEF(true, "true")
DEF(if, "if")
DEF(else, "else")
DEF(return, "return")
DEF(var, "var")
DEF(this, "this")
DEF(delete, "delete")
DEF(void, "void")
DEF(typeof, "typeof")
DEF(new, "new")
DEF(in, "in")
DEF(instanceof, "instanceof")
DEF(do, "do")
DEF(while, "while")
DEF(for, "for")
DEF(break, "break")
DEF(continue, "continue")
DEF(switch, "switch")
DEF(case, "case")
DEF(default, "default")
DEF(throw, "throw")
DEF(try, "try")
DEF(catch, "catch")
DEF(finally, "finally")
DEF(function, "function")
DEF(debugger, "debugger")
DEF(with, "with")
/* FutureReservedWord */
DEF(class, "class")
DEF(const, "const")
DEF(enum, "enum")
DEF(export, "export")
DEF(extends, "extends")
DEF(import, "import")
DEF(super, "super")
/* FutureReservedWords when parsing strict mode code */
DEF(implements, "implements")
DEF(interface, "interface")
DEF(let, "let")
DEF(package, "package")
DEF(private, "private")
DEF(protected, "protected")
DEF(public, "public")
DEF(static, "static")
DEF(yield, "yield")
DEF(await, "await")
/* empty string */
DEF(empty_string, "")
/* identifiers */
DEF(length, "length")
DEF(fileName, "fileName")
DEF(lineNumber, "lineNumber")
DEF(columnNumber, "columnNumber")
DEF(message, "message")
DEF(cause, "cause")
DEF(errors, "errors")
DEF(stack, "stack")
DEF(name, "name")
DEF(toString, "toString")
DEF(toLocaleString, "toLocaleString")
DEF(valueOf, "valueOf")
DEF(eval, "eval")
DEF(prototype, "prototype")
DEF(constructor, "constructor")
DEF(configurable, "configurable")
DEF(writable, "writable")
DEF(enumerable, "enumerable")
DEF(value, "value")
DEF(get, "get")
DEF(set, "set")
DEF(of, "of")
DEF(__proto__, "__proto__")
DEF(undefined, "undefined")
DEF(number, "number")
DEF(boolean, "boolean")
DEF(string, "string")
DEF(object, "object")
DEF(symbol, "symbol")
DEF(integer, "integer")
DEF(unknown, "unknown")
DEF(arguments, "arguments")
DEF(callee, "callee")
DEF(caller, "caller")
DEF(_eval_, "<eval>")
DEF(_ret_, "<ret>")
DEF(_var_, "<var>")
DEF(_arg_var_, "<arg_var>")
DEF(_with_, "<with>")
DEF(lastIndex, "lastIndex")
DEF(target, "target")
DEF(index, "index")
DEF(input, "input")
DEF(defineProperties, "defineProperties")
DEF(apply, "apply")
DEF(join, "join")
DEF(concat, "concat")
DEF(split, "split")
DEF(construct, "construct")
DEF(getPrototypeOf, "getPrototypeOf")
DEF(setPrototypeOf, "setPrototypeOf")
DEF(isExtensible, "isExtensible")
DEF(preventExtensions, "preventExtensions")
DEF(has, "has")
DEF(deleteProperty, "deleteProperty")
DEF(defineProperty, "defineProperty")
DEF(getOwnPropertyDescriptor, "getOwnPropertyDescriptor")
DEF(ownKeys, "ownKeys")
DEF(add, "add")
DEF(done, "done")
DEF(next, "next")
DEF(values, "values")
DEF(source, "source")
DEF(flags, "flags")
DEF(global, "global")
DEF(unicode, "unicode")
DEF(raw, "raw")
DEF(new_target, "new.target")
DEF(this_active_func, "this.active_func")
DEF(home_object, "<home_object>")
DEF(computed_field, "<computed_field>")
DEF(static_computed_field, "<static_computed_field>") /* must come after computed_fields */
DEF(class_fields_init, "<class_fields_init>")
DEF(brand, "<brand>")
DEF(hash_constructor, "#constructor")
DEF(as, "as")
DEF(from, "from")
DEF(meta, "meta")
DEF(_default_, "*default*")
DEF(_star_, "*")
DEF(Module, "Module")
DEF(then, "then")
DEF(resolve, "resolve")
DEF(reject, "reject")
DEF(promise, "promise")
DEF(proxy, "proxy")
DEF(revoke, "revoke")
DEF(async, "async")
DEF(exec, "exec")
DEF(groups, "groups")
DEF(indices, "indices")
DEF(status, "status")
DEF(reason, "reason")
DEF(globalThis, "globalThis")
DEF(bigint, "bigint")
DEF(minus_zero, "-0")
DEF(Infinity, "Infinity")
DEF(minus_Infinity, "-Infinity")
DEF(NaN, "NaN")
/* the following 3 atoms are only used with CONFIG_ATOMICS */
DEF(not_equal, "not-equal")
DEF(timed_out, "timed-out")
DEF(ok, "ok")
/* */
DEF(toJSON, "toJSON")
/* class names */
DEF(Object, "Object")
DEF(Array, "Array")
DEF(Error, "Error")
DEF(Number, "Number")
DEF(String, "String")
DEF(Boolean, "Boolean")
DEF(Symbol, "Symbol")
DEF(Arguments, "Arguments")
DEF(Math, "Math")
DEF(JSON, "JSON")
DEF(Date, "Date")
DEF(Function, "Function")
DEF(GeneratorFunction, "GeneratorFunction")
DEF(ForInIterator, "ForInIterator")
DEF(RegExp, "RegExp")
DEF(ArrayBuffer, "ArrayBuffer")
DEF(SharedArrayBuffer, "SharedArrayBuffer")
/* must keep same order as class IDs for typed arrays */
DEF(Uint8ClampedArray, "Uint8ClampedArray")
DEF(Int8Array, "Int8Array")
DEF(Uint8Array, "Uint8Array")
DEF(Int16Array, "Int16Array")
DEF(Uint16Array, "Uint16Array")
DEF(Int32Array, "Int32Array")
DEF(Uint32Array, "Uint32Array")
DEF(BigInt64Array, "BigInt64Array")
DEF(BigUint64Array, "BigUint64Array")
DEF(Float32Array, "Float32Array")
DEF(Float64Array, "Float64Array")
DEF(DataView, "DataView")
DEF(BigInt, "BigInt")
DEF(WeakRef, "WeakRef")
DEF(FinalizationRegistry, "FinalizationRegistry")
DEF(Map, "Map")
DEF(Set, "Set") /* Map + 1 */
DEF(WeakMap, "WeakMap") /* Map + 2 */
DEF(WeakSet, "WeakSet") /* Map + 3 */
DEF(Map_Iterator, "Map Iterator")
DEF(Set_Iterator, "Set Iterator")
DEF(Array_Iterator, "Array Iterator")
DEF(String_Iterator, "String Iterator")
DEF(RegExp_String_Iterator, "RegExp String Iterator")
DEF(Generator, "Generator")
DEF(Proxy, "Proxy")
DEF(Promise, "Promise")
DEF(PromiseResolveFunction, "PromiseResolveFunction")
DEF(PromiseRejectFunction, "PromiseRejectFunction")
DEF(AsyncFunction, "AsyncFunction")
DEF(AsyncFunctionResolve, "AsyncFunctionResolve")
DEF(AsyncFunctionReject, "AsyncFunctionReject")
DEF(AsyncGeneratorFunction, "AsyncGeneratorFunction")
DEF(AsyncGenerator, "AsyncGenerator")
DEF(EvalError, "EvalError")
DEF(RangeError, "RangeError")
DEF(ReferenceError, "ReferenceError")
DEF(SyntaxError, "SyntaxError")
DEF(TypeError, "TypeError")
DEF(URIError, "URIError")
DEF(InternalError, "InternalError")
/* private symbols */
DEF(Private_brand, "<brand>")
/* symbols */
DEF(Symbol_toPrimitive, "Symbol.toPrimitive")
DEF(Symbol_iterator, "Symbol.iterator")
DEF(Symbol_match, "Symbol.match")
DEF(Symbol_matchAll, "Symbol.matchAll")
DEF(Symbol_replace, "Symbol.replace")
DEF(Symbol_search, "Symbol.search")
DEF(Symbol_split, "Symbol.split")
DEF(Symbol_toStringTag, "Symbol.toStringTag")
DEF(Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable")
DEF(Symbol_hasInstance, "Symbol.hasInstance")
DEF(Symbol_species, "Symbol.species")
DEF(Symbol_unscopables, "Symbol.unscopables")
DEF(Symbol_asyncIterator, "Symbol.asyncIterator")
#endif /* DEF */

370
source/quickjs-opcode.h Normal file
View File

@@ -0,0 +1,370 @@
/*
* QuickJS opcode definitions
*
* Copyright (c) 2017-2018 Fabrice Bellard
* Copyright (c) 2017-2018 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifdef FMT
FMT(none)
FMT(none_int)
FMT(none_loc)
FMT(none_arg)
FMT(none_var_ref)
FMT(u8)
FMT(i8)
FMT(loc8)
FMT(const8)
FMT(label8)
FMT(u16)
FMT(i16)
FMT(label16)
FMT(npop)
FMT(npopx)
FMT(npop_u16)
FMT(loc)
FMT(arg)
FMT(var_ref)
FMT(u32)
FMT(i32)
FMT(const)
FMT(label)
FMT(atom)
FMT(atom_u8)
FMT(atom_u16)
FMT(atom_label_u8)
FMT(atom_label_u16)
FMT(label_u16)
#undef FMT
#endif /* FMT */
#ifdef DEF
#ifndef def
#define def(id, size, n_pop, n_push, f) DEF(id, size, n_pop, n_push, f)
#endif
DEF(invalid, 1, 0, 0, none) /* never emitted */
/* push values */
DEF( push_i32, 5, 0, 1, i32)
DEF( push_const, 5, 0, 1, const)
DEF( fclosure, 5, 0, 1, const) /* must follow push_const */
DEF(push_atom_value, 5, 0, 1, atom)
DEF( private_symbol, 5, 0, 1, atom)
DEF( undefined, 1, 0, 1, none)
DEF( null, 1, 0, 1, none)
DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */
DEF( push_false, 1, 0, 1, none)
DEF( push_true, 1, 0, 1, none)
DEF( object, 1, 0, 1, none)
DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */
DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */
DEF( drop, 1, 1, 0, none) /* a -> */
DEF( nip, 1, 2, 1, none) /* a b -> b */
DEF( nip1, 1, 3, 2, none) /* a b c -> b c */
DEF( dup, 1, 1, 2, none) /* a -> a a */
DEF( dup1, 1, 2, 3, none) /* a b -> a a b */
DEF( dup2, 1, 2, 4, none) /* a b -> a b a b */
DEF( dup3, 1, 3, 6, none) /* a b c -> a b c a b c */
DEF( insert2, 1, 2, 3, none) /* obj a -> a obj a (dup_x1) */
DEF( insert3, 1, 3, 4, none) /* obj prop a -> a obj prop a (dup_x2) */
DEF( insert4, 1, 4, 5, none) /* this obj prop a -> a this obj prop a */
DEF( perm3, 1, 3, 3, none) /* obj a b -> a obj b */
DEF( perm4, 1, 4, 4, none) /* obj prop a b -> a obj prop b */
DEF( perm5, 1, 5, 5, none) /* this obj prop a b -> a this obj prop b */
DEF( swap, 1, 2, 2, none) /* a b -> b a */
DEF( swap2, 1, 4, 4, none) /* a b c d -> c d a b */
DEF( rot3l, 1, 3, 3, none) /* x a b -> a b x */
DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */
DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */
DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */
DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */
DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */
DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */
DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */
DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */
DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */
DEF( apply, 3, 3, 1, u16)
DEF( return, 1, 1, 0, none)
DEF( return_undef, 1, 0, 0, none)
DEF(check_ctor_return, 1, 1, 2, none)
DEF( check_ctor, 1, 0, 0, none)
DEF( init_ctor, 1, 0, 1, none)
DEF( check_brand, 1, 2, 2, none) /* this_obj func -> this_obj func */
DEF( add_brand, 1, 2, 0, none) /* this_obj home_obj -> */
DEF( return_async, 1, 1, 0, none)
DEF( throw, 1, 1, 0, none)
DEF( throw_error, 6, 0, 0, atom_u8)
DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */
DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */
DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
bytecode string */
DEF( get_super, 1, 1, 1, none)
DEF( import, 1, 1, 1, none) /* dynamic module import */
DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */
DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */
DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */
DEF( put_var, 5, 1, 0, atom) /* must come after get_var */
DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */
DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */
DEF( get_ref_value, 1, 2, 3, none)
DEF( put_ref_value, 1, 3, 0, none)
DEF( define_var, 6, 0, 0, atom_u8)
DEF(check_define_var, 6, 0, 0, atom_u8)
DEF( define_func, 6, 1, 0, atom_u8)
DEF( get_field, 5, 1, 1, atom)
DEF( get_field2, 5, 1, 2, atom)
DEF( put_field, 5, 2, 0, atom)
DEF( get_private_field, 1, 2, 1, none) /* obj prop -> value */
DEF( put_private_field, 1, 3, 0, none) /* obj value prop -> */
DEF(define_private_field, 1, 3, 1, none) /* obj prop value -> obj */
DEF( get_array_el, 1, 2, 1, none)
DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */
DEF( put_array_el, 1, 3, 0, none)
DEF(get_super_value, 1, 3, 1, none) /* this obj prop -> value */
DEF(put_super_value, 1, 4, 0, none) /* this obj prop value -> */
DEF( define_field, 5, 2, 1, atom)
DEF( set_name, 5, 1, 1, atom)
DEF(set_name_computed, 1, 2, 2, none)
DEF( set_proto, 1, 2, 1, none)
DEF(set_home_object, 1, 2, 2, none)
DEF(define_array_el, 1, 3, 2, none)
DEF( append, 1, 3, 2, none) /* append enumerated object, update length */
DEF(copy_data_properties, 2, 3, 3, u8)
DEF( define_method, 6, 2, 1, atom_u8)
DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */
DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */
DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
DEF( get_loc, 3, 0, 1, loc)
DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */
DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */
DEF( get_arg, 3, 0, 1, arg)
DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */
DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */
DEF( get_var_ref, 3, 0, 1, var_ref)
DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */
DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */
DEF(set_loc_uninitialized, 3, 0, 0, loc)
DEF( get_loc_check, 3, 0, 1, loc)
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
DEF( put_loc_check_init, 3, 1, 0, loc)
DEF(get_loc_checkthis, 3, 0, 1, loc)
DEF(get_var_ref_check, 3, 0, 1, var_ref)
DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
DEF( close_loc, 3, 0, 0, loc)
DEF( if_false, 5, 1, 0, label)
DEF( if_true, 5, 1, 0, label) /* must come after if_false */
DEF( goto, 5, 0, 0, label) /* must come after if_true */
DEF( catch, 5, 0, 1, label)
DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
DEF( to_object, 1, 1, 1, none)
//DEF( to_string, 1, 1, 1, none)
DEF( to_propkey, 1, 1, 1, none)
DEF( to_propkey2, 1, 2, 2, none)
DEF( with_get_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */
DEF( with_put_var, 10, 2, 1, atom_label_u8) /* must be in the same order as scope_xxx */
DEF(with_delete_var, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */
DEF( with_make_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */
DEF( with_get_ref, 10, 1, 0, atom_label_u8) /* must be in the same order as scope_xxx */
DEF( make_loc_ref, 7, 0, 2, atom_u16)
DEF( make_arg_ref, 7, 0, 2, atom_u16)
DEF(make_var_ref_ref, 7, 0, 2, atom_u16)
DEF( make_var_ref, 5, 0, 2, atom)
DEF( for_in_start, 1, 1, 1, none)
DEF( for_of_start, 1, 1, 3, none)
DEF(for_await_of_start, 1, 1, 3, none)
DEF( for_in_next, 1, 1, 3, none)
DEF( for_of_next, 2, 3, 5, u8)
DEF(for_await_of_next, 1, 3, 4, none) /* iter next catch_offset -> iter next catch_offset obj */
DEF(iterator_check_object, 1, 1, 1, none)
DEF(iterator_get_value_done, 1, 2, 3, none) /* catch_offset obj -> catch_offset value done */
DEF( iterator_close, 1, 3, 0, none)
DEF( iterator_next, 1, 4, 4, none)
DEF( iterator_call, 2, 4, 5, u8)
DEF( initial_yield, 1, 0, 0, none)
DEF( yield, 1, 1, 2, none)
DEF( yield_star, 1, 1, 2, none)
DEF(async_yield_star, 1, 1, 2, none)
DEF( await, 1, 1, 1, none)
/* arithmetic/logic operations */
DEF( neg, 1, 1, 1, none)
DEF( plus, 1, 1, 1, none)
DEF( dec, 1, 1, 1, none)
DEF( inc, 1, 1, 1, none)
DEF( post_dec, 1, 1, 2, none)
DEF( post_inc, 1, 1, 2, none)
DEF( dec_loc, 2, 0, 0, loc8)
DEF( inc_loc, 2, 0, 0, loc8)
DEF( add_loc, 2, 1, 0, loc8)
DEF( not, 1, 1, 1, none)
DEF( lnot, 1, 1, 1, none)
DEF( typeof, 1, 1, 1, none)
DEF( delete, 1, 2, 1, none)
DEF( delete_var, 5, 0, 1, atom)
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( sub, 1, 2, 1, none)
DEF( pow, 1, 2, 1, none)
DEF( shl, 1, 2, 1, none)
DEF( sar, 1, 2, 1, none)
DEF( shr, 1, 2, 1, none)
DEF( lt, 1, 2, 1, none)
DEF( lte, 1, 2, 1, none)
DEF( gt, 1, 2, 1, none)
DEF( gte, 1, 2, 1, none)
DEF( instanceof, 1, 2, 1, none)
DEF( in, 1, 2, 1, none)
DEF( eq, 1, 2, 1, none)
DEF( neq, 1, 2, 1, none)
DEF( strict_eq, 1, 2, 1, none)
DEF( strict_neq, 1, 2, 1, none)
DEF( and, 1, 2, 1, none)
DEF( xor, 1, 2, 1, none)
DEF( or, 1, 2, 1, none)
DEF(is_undefined_or_null, 1, 1, 1, none)
DEF( private_in, 1, 2, 1, none)
DEF(push_bigint_i32, 5, 0, 1, i32)
/* must be the last non short and non temporary opcode */
DEF( nop, 1, 0, 0, none)
/* temporary opcodes: never emitted in the final bytecode */
def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
def( leave_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 */
/* the following opcodes must be in the same order as the 'with_x' and
get_var_undef, get_var and put_var opcodes */
def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */
def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
def(scope_get_private_field, 7, 1, 1, atom_u16) /* obj -> value, emitted in phase 1, removed in phase 2 */
def(scope_get_private_field2, 7, 1, 2, atom_u16) /* obj -> obj value, emitted in phase 1, removed in phase 2 */
def(scope_put_private_field, 7, 2, 0, atom_u16) /* obj value ->, emitted in phase 1, removed in phase 2 */
def(scope_in_private_field, 7, 1, 1, atom_u16) /* obj -> res emitted in phase 1, removed in phase 2 */
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
def( line_num, 5, 0, 0, u32) /* emitted in phase 1, removed in phase 3 */
#if SHORT_OPCODES
DEF( push_minus1, 1, 0, 1, none_int)
DEF( push_0, 1, 0, 1, none_int)
DEF( push_1, 1, 0, 1, none_int)
DEF( push_2, 1, 0, 1, none_int)
DEF( push_3, 1, 0, 1, none_int)
DEF( push_4, 1, 0, 1, none_int)
DEF( push_5, 1, 0, 1, none_int)
DEF( push_6, 1, 0, 1, none_int)
DEF( push_7, 1, 0, 1, none_int)
DEF( push_i8, 2, 0, 1, i8)
DEF( push_i16, 3, 0, 1, i16)
DEF( push_const8, 2, 0, 1, const8)
DEF( fclosure8, 2, 0, 1, const8) /* must follow push_const8 */
DEF(push_empty_string, 1, 0, 1, none)
DEF( get_loc8, 2, 0, 1, loc8)
DEF( put_loc8, 2, 1, 0, loc8)
DEF( set_loc8, 2, 1, 1, loc8)
DEF( get_loc0, 1, 0, 1, none_loc)
DEF( get_loc1, 1, 0, 1, none_loc)
DEF( get_loc2, 1, 0, 1, none_loc)
DEF( get_loc3, 1, 0, 1, none_loc)
DEF( put_loc0, 1, 1, 0, none_loc)
DEF( put_loc1, 1, 1, 0, none_loc)
DEF( put_loc2, 1, 1, 0, none_loc)
DEF( put_loc3, 1, 1, 0, none_loc)
DEF( set_loc0, 1, 1, 1, none_loc)
DEF( set_loc1, 1, 1, 1, none_loc)
DEF( set_loc2, 1, 1, 1, none_loc)
DEF( set_loc3, 1, 1, 1, none_loc)
DEF( get_arg0, 1, 0, 1, none_arg)
DEF( get_arg1, 1, 0, 1, none_arg)
DEF( get_arg2, 1, 0, 1, none_arg)
DEF( get_arg3, 1, 0, 1, none_arg)
DEF( put_arg0, 1, 1, 0, none_arg)
DEF( put_arg1, 1, 1, 0, none_arg)
DEF( put_arg2, 1, 1, 0, none_arg)
DEF( put_arg3, 1, 1, 0, none_arg)
DEF( set_arg0, 1, 1, 1, none_arg)
DEF( set_arg1, 1, 1, 1, none_arg)
DEF( set_arg2, 1, 1, 1, none_arg)
DEF( set_arg3, 1, 1, 1, none_arg)
DEF( get_var_ref0, 1, 0, 1, none_var_ref)
DEF( get_var_ref1, 1, 0, 1, none_var_ref)
DEF( get_var_ref2, 1, 0, 1, none_var_ref)
DEF( get_var_ref3, 1, 0, 1, none_var_ref)
DEF( put_var_ref0, 1, 1, 0, none_var_ref)
DEF( put_var_ref1, 1, 1, 0, none_var_ref)
DEF( put_var_ref2, 1, 1, 0, none_var_ref)
DEF( put_var_ref3, 1, 1, 0, none_var_ref)
DEF( set_var_ref0, 1, 1, 1, none_var_ref)
DEF( set_var_ref1, 1, 1, 1, none_var_ref)
DEF( set_var_ref2, 1, 1, 1, none_var_ref)
DEF( set_var_ref3, 1, 1, 1, none_var_ref)
DEF( get_length, 1, 1, 1, none)
DEF( if_false8, 2, 1, 0, label8)
DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */
DEF( goto8, 2, 0, 0, label8) /* must come after if_true8 */
DEF( goto16, 3, 0, 0, label16)
DEF( call0, 1, 1, 1, npopx)
DEF( call1, 1, 1, 1, npopx)
DEF( call2, 1, 1, 1, npopx)
DEF( call3, 1, 1, 1, npopx)
DEF( is_undefined, 1, 1, 1, none)
DEF( is_null, 1, 1, 1, none)
DEF(typeof_is_undefined, 1, 1, 1, none)
DEF( typeof_is_function, 1, 1, 1, none)
#endif
#undef DEF
#undef def
#endif /* DEF */

54682
source/quickjs.c Normal file

File diff suppressed because it is too large Load Diff

1250
source/quickjs.h Normal file

File diff suppressed because it is too large Load Diff

156
source/timer.c Normal file
View File

@@ -0,0 +1,156 @@
#include "timer.h"
#include <time.h>
#include <limits.h>
#include "stb_ds.h"
/* Global timer state */
static timer_t *timers = NULL;
static Uint32 next_timer_id = 1;
static SDL_Mutex *timer_mutex = NULL;
static SDL_Condition *timer_cond = NULL;
static SDL_Thread *timer_thread = NULL;
static SDL_AtomicInt timer_running;
static int timer_loop(void *data)
{
while (SDL_GetAtomicInt(&timer_running)) {
SDL_LockMutex(timer_mutex);
uint64_t to_ns = next_timeout_ns();
if (to_ns == UINT64_MAX) {
/* No timers, wait indefinitely */
SDL_WaitCondition(timer_cond, timer_mutex);
} else if (to_ns == 0) {
/* Timer(s) already due, process immediately */
SDL_UnlockMutex(timer_mutex);
process_due_timers();
continue;
} else {
/* Wait until next timer is due or a new timer is added */
Uint32 wait_ms = (Uint32)(to_ns / 1000000);
if (wait_ms == 0) wait_ms = 1; /* Minimum 1ms wait */
SDL_WaitConditionTimeout(timer_cond, timer_mutex, wait_ms);
}
SDL_UnlockMutex(timer_mutex);
/* Process any due timers */
process_due_timers();
}
return 0;
}
void timer_init(void)
{
if (!timer_mutex) {
timer_mutex = SDL_CreateMutex();
timer_cond = SDL_CreateCondition();
SDL_SetAtomicInt(&timer_running, 1);
timer_thread = SDL_CreateThread(timer_loop, "timer thread", NULL);
}
}
uint64_t get_time_ns(void)
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + ts.tv_nsec;
}
Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param)
{
timer_t t;
SDL_LockMutex(timer_mutex);
t.id = next_timer_id++;
t.interval_ns = delay_ns;
t.due_ns = get_time_ns() + delay_ns;
t.callback = callback;
t.param = param;
arrput(timers, t);
SDL_UnlockMutex(timer_mutex);
SDL_SignalCondition(timer_cond);
return t.id;
}
void remove_timer(Uint32 id)
{
SDL_LockMutex(timer_mutex);
for (int i = 0; i < arrlen(timers); i++) {
if (timers[i].id == id) {
arrdel(timers, i);
break;
}
}
SDL_UnlockMutex(timer_mutex);
}
void process_due_timers(void)
{
uint64_t now = get_time_ns();
SDL_LockMutex(timer_mutex);
for (int i = 0; i < arrlen(timers); i++) {
if (timers[i].due_ns <= now) {
timer_t t = timers[i];
arrdel(timers, i);
SDL_UnlockMutex(timer_mutex);
/* Convert interval_ns back to milliseconds for the callback */
Uint32 next_ms = t.callback(t.id, t.interval_ns, t.param);
if (next_ms > 0) {
uint64_t next_ns = (uint64_t)next_ms * 1000000ull;
add_timer_ns(next_ns, t.callback, t.param);
}
/* restart scan because array may have shifted */
SDL_LockMutex(timer_mutex);
i = -1;
continue;
}
}
SDL_UnlockMutex(timer_mutex);
}
uint64_t next_timeout_ns(void)
{
uint64_t min_due = UINT64_MAX;
uint64_t now = get_time_ns();
SDL_LockMutex(timer_mutex);
if (timers && arrlen(timers) > 0) {
min_due = timers[0].due_ns;
for (int i = 1; i < arrlen(timers); i++) {
if (timers[i].due_ns < min_due)
min_due = timers[i].due_ns;
}
}
SDL_UnlockMutex(timer_mutex);
if (min_due == UINT64_MAX)
return UINT64_MAX;
if (min_due <= now)
return 0;
return min_due - now;
}
void timer_quit(void)
{
if (timer_thread) {
SDL_SetAtomicInt(&timer_running, 0);
SDL_SignalCondition(timer_cond);
SDL_WaitThread(timer_thread, NULL);
timer_thread = NULL;
}
if (timer_cond) {
SDL_DestroyCondition(timer_cond);
timer_cond = NULL;
}
if (timer_mutex) {
SDL_DestroyMutex(timer_mutex);
timer_mutex = NULL;
}
arrfree(timers);
timers = NULL;
}

38
source/timer.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef TIMER_H
#define TIMER_H
#include <stdint.h>
#include <SDL3/SDL.h>
typedef Uint32 (*TimerCallback)(Uint32 timer_id, Uint32 interval, void *param);
typedef struct {
Uint32 id;
uint64_t due_ns;
uint64_t interval_ns;
TimerCallback callback;
void *param;
} timer_t;
/* Initialize timer system - must be called once */
void timer_init(void);
/* Shutdown timer system - must be called before exit */
void timer_quit(void);
/* Schedule a new timer to fire after delay_ns nanoseconds */
Uint32 add_timer_ns(uint64_t delay_ns, TimerCallback callback, void *param);
/* Cancel a pending timer by its ID */
void remove_timer(Uint32 id);
/* Process due timers - call once per main loop iteration */
void process_due_timers(void);
/* Get time until next timer expires (in nanoseconds) */
uint64_t next_timeout_ns(void);
/* Get current monotonic time in nanoseconds */
uint64_t get_time_ns(void);
#endif /* TIMER_H */

View File

@@ -48,6 +48,7 @@ static inline int wota_type(const uint64_t *w) {
---------------------------------------------------------------- */
char *wota_read_blob (long long *byte_len, char **blob, char *wota);
char *wota_read_text (char **text_utf8, char *wota);
char *wota_read_text_len(size_t *byte_len, char **text_utf8, char *wota);
char *wota_read_array (long long *count, char *wota);
char *wota_read_record (long long *count, char *wota);
char *wota_read_float (double *d, char *wota);
@@ -73,6 +74,7 @@ void wota_buffer_free(WotaBuffer *wb);
/* Writing function prototypes */
void wota_write_blob (WotaBuffer *wb, unsigned long long nbits, const char *data);
void wota_write_text (WotaBuffer *wb, const char *utf8);
void wota_write_text_len(WotaBuffer *wb, const char *utf8, size_t len);
void wota_write_array (WotaBuffer *wb, unsigned long long count);
void wota_write_record (WotaBuffer *wb, unsigned long long count);
/* We'll store numbers as either 56-bit integers or raw double */
@@ -396,12 +398,11 @@ char *wota_read_blob(long long *byte_len, char **blob, char *wota)
/*
TEXT:
preamble => top 56 bits = #characters, LSB=0x05
then floor((nchars+1)/2) 64-bit words
each word has 2 UTF-32 codepoints: top 32 bits = codepoint1,
low 32 bits = codepoint2
preamble => top 56 bits = #bytes in UTF-8, LSB=0x05
then floor((nbytes + 7)/8) 64-bit words
containing the UTF-8 bytes, packed 8 bytes per word
*/
char *wota_read_text(char **text_utf8, char *wota)
char *wota_read_text_len(size_t *byte_len, char **text_utf8, char *wota)
{
if (!text_utf8) return wota_skip1(wota);
@@ -412,73 +413,40 @@ char *wota_read_text(char **text_utf8, char *wota)
return wota_skip1(wota);
}
uint64_t nchars = (first >> 8);
long long nwords = (long long)((nchars + 1ULL) >> 1);
uint64_t nbytes = (first >> 8);
long long nwords = (long long)((nbytes + 7ULL) >> 3);
if (byte_len) {
*byte_len = (size_t)nbytes;
}
uint64_t *data_words = p + 1;
/*
We'll convert them to a UTF-8 string. Each codepoint can
become up to 4 bytes. So we need up to 4*nchars + 1.
*/
size_t max_utf8 = (size_t)(4 * nchars + 1);
char *out = (char *)malloc(max_utf8);
char *out = (char *)malloc((size_t)(nbytes + 1));
if (!out) {
fprintf(stderr, "malloc failed in wota_read_text\n");
fprintf(stderr, "malloc failed in wota_read_text_len\n");
abort();
}
size_t out_len = 0;
/* Copy bytes from the packed 64-bit words */
for (long long i = 0; i < nwords; i++) {
uint64_t wval = data_words[i];
uint32_t c1 = (uint32_t)(wval >> 32);
uint32_t c2 = (uint32_t)(wval & 0xffffffffULL);
// If we haven't exceeded nchars, convert c1 -> UTF-8
if ((i * 2) + 0 < (long long)nchars) {
uint32_t c = c1;
if (c < 0x80) {
out[out_len++] = (char)c;
} else if (c < 0x800) {
out[out_len++] = (char)(0xC0 | (c >> 6));
out[out_len++] = (char)(0x80 | (c & 0x3F));
} else if (c < 0x10000) {
out[out_len++] = (char)(0xE0 | (c >> 12));
out[out_len++] = (char)(0x80 | ((c >> 6) & 0x3F));
out[out_len++] = (char)(0x80 | (c & 0x3F));
} else {
out[out_len++] = (char)(0xF0 | (c >> 18));
out[out_len++] = (char)(0x80 | ((c >> 12) & 0x3F));
out[out_len++] = (char)(0x80 | ((c >> 6) & 0x3F));
out[out_len++] = (char)(0x80 | (c & 0x3F));
}
}
// Similarly for c2:
if ((i * 2) + 1 < (long long)nchars) {
uint32_t c = c2;
if (c < 0x80) {
out[out_len++] = (char)c;
} else if (c < 0x800) {
out[out_len++] = (char)(0xC0 | (c >> 6));
out[out_len++] = (char)(0x80 | (c & 0x3F));
} else if (c < 0x10000) {
out[out_len++] = (char)(0xE0 | (c >> 12));
out[out_len++] = (char)(0x80 | ((c >> 6) & 0x3F));
out[out_len++] = (char)(0x80 | (c & 0x3F));
} else {
out[out_len++] = (char)(0xF0 | (c >> 18));
out[out_len++] = (char)(0x80 | ((c >> 12) & 0x3F));
out[out_len++] = (char)(0x80 | ((c >> 6) & 0x3F));
out[out_len++] = (char)(0x80 | (c & 0x3F));
}
for (int j = 0; j < 8 && (i * 8 + j) < (long long)nbytes; j++) {
out[i * 8 + j] = (char)((wval >> (56 - j * 8)) & 0xff);
}
}
out[out_len] = '\0';
out[nbytes] = '\0';
*text_utf8 = out;
return (char *)(data_words + nwords);
}
char *wota_read_text(char **text_utf8, char *wota)
{
return wota_read_text_len(NULL, text_utf8, wota);
}
/* ================================================================
WRITING
================================================================ */
@@ -625,70 +593,37 @@ void wota_write_blob(WotaBuffer *wb, unsigned long long nbits, const char *data)
}
}
void wota_write_text(WotaBuffer *wb, const char *utf8)
void wota_write_text_len(WotaBuffer *wb, const char *utf8, size_t nbytes)
{
if (!utf8) utf8 = "";
/* Convert the utf8 string to an array of UTF-32 codepoints. */
size_t len = strlen(utf8);
const unsigned char *uc = (const unsigned char *)utf8;
/* In worst case, every single byte might form a codepoint, so we allocate enough: */
uint32_t *codepoints = (uint32_t *)malloc(sizeof(uint32_t)*(len+1));
if (!codepoints) {
fprintf(stderr, "malloc failed in wota_write_text\n");
abort();
}
size_t ccount = 0;
while (*uc) {
uint32_t c;
if ((uc[0] & 0x80) == 0) {
c = uc[0];
uc += 1;
} else if ((uc[0] & 0xe0) == 0xc0 && (uc[1] != 0)) {
c = ((uc[0] & 0x1f) << 6) | (uc[1] & 0x3f);
uc += 2;
} else if ((uc[0] & 0xf0) == 0xe0 && (uc[1] != 0) && (uc[2] != 0)) {
c = ((uc[0] & 0x0f) << 12) | ((uc[1] & 0x3f) << 6) | (uc[2] & 0x3f);
uc += 3;
} else if ((uc[0] & 0xf8) == 0xf0 && (uc[1] != 0) && (uc[2] != 0) && (uc[3] != 0)) {
c = ((uc[0] & 0x07) << 18) | ((uc[1] & 0x3f) << 12)
| ((uc[2] & 0x3f) << 6) | (uc[3] & 0x3f);
uc += 4;
} else {
/* invalid sequence => skip 1 byte */
c = uc[0];
uc++;
}
codepoints[ccount++] = c;
}
/* preamble => top 56 bits = ccount, LSB=0x05 */
uint64_t preamble = ((uint64_t)ccount << 8) | (uint64_t)WOTA_TEXT;
/* preamble => top 56 bits = nbytes, LSB=0x05 */
uint64_t preamble = ((uint64_t)nbytes << 8) | (uint64_t)WOTA_TEXT;
uint64_t *pw = wota_buffer_alloc(wb, 1);
pw[0] = preamble;
/* store pairs of 32-bit codepoints in 64-bit words */
size_t nwords = (ccount + 1) / 2;
/* pack UTF-8 bytes into 64-bit words, 8 bytes per word */
size_t nwords = (nbytes + 7) / 8;
if (nwords == 0) {
free(codepoints);
return;
}
uint64_t *blocks = wota_buffer_alloc(wb, nwords);
size_t idx = 0;
memset(blocks, 0, nwords * sizeof(uint64_t));
for (size_t i = 0; i < nwords; i++) {
uint64_t hi = 0, lo = 0;
if (idx < ccount) {
hi = codepoints[idx++];
uint64_t wval = 0;
for (int j = 0; j < 8 && (i * 8 + j) < nbytes; j++) {
wval |= ((uint64_t)(unsigned char)utf8[i * 8 + j]) << (56 - j * 8);
}
if (idx < ccount) {
lo = codepoints[idx++];
blocks[i] = wval;
}
blocks[i] = ((hi & 0xffffffffULL) << 32) | (lo & 0xffffffffULL);
}
free(codepoints);
void wota_write_text(WotaBuffer *wb, const char *utf8)
{
if (!utf8) utf8 = "";
wota_write_text_len(wb, utf8, strlen(utf8));
}
void wota_write_array(WotaBuffer *wb, unsigned long long count)

View File

@@ -51,7 +51,7 @@ test("Write and read single bit", function() {
b.write_bit(0);
assertEqual(b.length, 4, "Should have 4 bits after writing");
b.stone(); // Make it stone to read
stone(b); // Make it stone to read
assertEqual(b.read_logical(0), true, "First bit should be true");
assertEqual(b.read_logical(1), false, "Second bit should be false");
assertEqual(b.read_logical(2), true, "Third bit should be true (1)");
@@ -62,7 +62,7 @@ test("Write and read single bit", function() {
test("Out of range read throws error", function() {
var b = new Blob();
b.write_bit(true);
b.stone();
stone(b);
var threw = false;
try {
@@ -88,7 +88,7 @@ test("Write and read numbers", function() {
b.write_number(-42);
b.write_number(0);
b.write_number(1e20);
b.stone();
stone(b);
// Read back the numbers
assertEqual(b.read_number(0), 3.14159, "First number should match");
@@ -103,7 +103,7 @@ test("Write and read text", function() {
b.write_text("Hello");
b.write_text("World");
b.write_text("🎉"); // Unicode test
b.stone();
stone(b);
assertEqual(b.read_text(0), "Hello", "First text should match");
// Note: We need to know bit positions to read subsequent strings
@@ -121,7 +121,7 @@ test("Write and read blobs", function() {
b2.write_bit(false);
assertEqual(b2.length, 4, "Combined blob should have 4 bits");
b2.stone();
stone(b2);
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(1), false);
assertEqual(b2.read_logical(2), true);
@@ -135,10 +135,10 @@ test("Blob copy constructor", function() {
b1.write_bit(false);
b1.write_bit(true);
b1.write_bit(true);
b1.stone();
stone(b1);
var b2 = new Blob(b1);
b2.stone(); // Need to stone the copy before reading
stone(b2); // Need to stone the copy before reading
assertEqual(b2.length, 4, "Copied blob should have same length");
assertEqual(b2.read_logical(0), true);
assertEqual(b2.read_logical(3), true);
@@ -150,10 +150,10 @@ test("Blob partial copy constructor", function() {
for (var i = 0; i < 10; i++) {
b1.write_bit(i % 2 === 0);
}
b1.stone();
stone(b1);
var b2 = new Blob(b1, 2, 7); // Copy bits 2-6 (5 bits)
b2.stone(); // Need to stone the copy before reading
stone(b2); // Need to stone the copy before reading
assertEqual(b2.length, 5, "Partial copy should have 5 bits");
assertEqual(b2.read_logical(0), true); // bit 2 of original
assertEqual(b2.read_logical(2), true); // bit 4 of original
@@ -164,8 +164,8 @@ test("Create blob with fill", function() {
var b1 = new Blob(8, true); // 8 bits all set to 1
var b2 = new Blob(8, false); // 8 bits all set to 0
b1.stone();
b2.stone();
stone(b1);
stone(b2);
for (var i = 0; i < 8; i++) {
assertEqual(b1.read_logical(i), true, "Bit " + i + " should be true");
@@ -182,7 +182,7 @@ test("Create blob with random function", function() {
return sequence[index++] ? 1 : 0;
});
b.stone();
stone(b);
for (var i = 0; i < 5; i++) {
assertEqual(b.read_logical(i), sequence[i], "Bit " + i + " should match sequence");
}
@@ -197,7 +197,7 @@ test("Write pad and check padding", function() {
b.write_pad(8); // Pad to 8-bit boundary
assertEqual(b.length, 8, "Should be padded to 8 bits");
b.stone();
stone(b);
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
assert(!b['pad?'](2, 8), "Should detect invalid padding at position 2");
@@ -209,10 +209,10 @@ test("Read blob from stone blob", function() {
for (var i = 0; i < 16; i++) {
b1.write_bit(i % 3 === 0);
}
b1.stone();
stone(b1);
var b2 = b1.read_blob(4, 12); // Read bits 4-11 (8 bits)
b2.stone(); // Need to stone the read blob before reading from it
stone(b2); // Need to stone the read blob before reading from it
assertEqual(b2.length, 8, "Read blob should have 8 bits");
// Check the pattern
@@ -224,7 +224,7 @@ test("Read blob from stone blob", function() {
test("Stone blob is immutable", function() {
var b = new Blob();
b.write_bit(true);
b.stone();
stone(b);
var threw = false;
try {
@@ -235,13 +235,18 @@ test("Stone blob is immutable", function() {
assert(threw, "Writing to stone blob should throw error");
});
// Test 15: Multiple stone calls
test("Multiple stone calls are safe", function() {
// Test 15: Multiple stone calls and stone.p check
test("Multiple stone calls are safe and stone.p works", function() {
var b = new Blob();
b.write_bit(true);
b.stone();
b.stone(); // Should be safe to call again
assert(!stone.p(b), "Blob should not be a stone before stone() call");
stone(b);
assert(stone.p(b), "Blob should be a stone after stone() call");
stone(b); // Should be safe to call again
assertEqual(b.read_logical(0), true, "Blob data should remain intact");
// Verify blob.stone is not available
assert(b.stone === undefined, "blob.stone should not be available as a method");
});
// Test 16: Invalid constructor arguments
@@ -282,11 +287,11 @@ test("Complex data round-trip", function() {
b.write_number(-999.999);
var originalLength = b.length;
b.stone();
stone(b);
// Verify we can create a copy
var b2 = new Blob(b);
b2.stone(); // Need to stone the copy before reading
stone(b2); // Need to stone the copy before reading
assertEqual(b2.length, originalLength, "Copy should have same length");
assertEqual(b2.read_text(0), "Test", "First text should match");
});
@@ -310,7 +315,7 @@ test("Large blob handling", function() {
}
assertEqual(b.length, testSize, "Should have " + testSize + " bits");
b.stone();
stone(b);
// Verify pattern
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
@@ -372,7 +377,7 @@ test("Non-stone blob read methods should throw", function() {
test("Empty text write and read", function() {
var b = new Blob();
b.write_text("");
b.stone();
stone(b);
assertEqual(b.read_text(0), "", "Empty string should round-trip");
});
@@ -380,7 +385,7 @@ test("Empty text write and read", function() {
test("Invalid read positions", function() {
var b = new Blob();
b.write_number(42);
b.stone();
stone(b);
var threw = false;
try {
@@ -406,4 +411,4 @@ log.console("Passed: " + passed);
log.console("Failed: " + failed);
log.console("\nOverall: " + (failed === 0 ? "PASSED" : "FAILED"));
os.exit(failed === 0 ? 0 : 1);
$_.stop()

11
tests/cat.ce Normal file
View File

@@ -0,0 +1,11 @@
var fd = use('fd')
var time = use('time')
var st = time.number()
var f = fd.open(arg[0], 'r')
var stat = fd.fstat(f)
var data = fd.read(f,stat.size);
fd.close(f)
log.console(`cat took ${time.number()-st}`)
$_.stop()

23
tests/chunkread.ce Normal file
View File

@@ -0,0 +1,23 @@
var fd = use('fd')
var time = use('time')
var blob = use('blob')
var data = new blob
var st = time.number()
var f = fd.open(arg[0], 'r')
var chunksize = 4096
function getchunk()
{
var chunk = fd.read(f,chunksize);
data.write_blob(chunk);
if (chunk.length < chunksize*8) {
// we're done
fd.close(f)
log.console(`read took ${time.number()-st}`)
$_.stop()
} else
$_.clock(getchunk)
}
getchunk()

10
tests/clock.ce Normal file
View File

@@ -0,0 +1,10 @@
var i = 0
function loop(time)
{
log.console(`loop ${i} with time ${time}`)
i++
if (i > 60) $_.stop()
$_.clock(loop)
}
$_.clock(loop)

View File

@@ -1,3 +1,4 @@
var count = 0
function loop()
{

View File

@@ -1,3 +0,0 @@
log.console(arg)
$_.stop()

122
tests/fit.ce Normal file
View File

@@ -0,0 +1,122 @@
var fit = use("fit");
var tests_run = 0;
var tests_passed = 0;
var tests_failed = 0;
function test(description, actual, expected) {
tests_run++;
if (actual === expected) {
tests_passed++;
log.console("✓", description, "=", actual);
} else {
tests_failed++;
log.console("✗", description, "expected", expected, "but got", actual);
}
}
log.console("Running fit module tests...\n");
// Test fit.and
test("fit.and(12, 10)", fit.and(12, 10), 8);
test("fit.and(16, 2)", fit.and(16, 2), 0);
test("fit.and(15, 3)", fit.and(15, 3), 3);
test("fit.and(13, 3)", fit.and(13, 3), 1);
test("fit.and('10', 3)", fit.and("10", 3), null);
// Test fit.or
test("fit.or(12, 10)", fit.or(12, 10), 14);
test("fit.or(16, 2)", fit.or(16, 2), 18);
test("fit.or(15, 3)", fit.or(15, 3), 15);
test("fit.or(13, 3)", fit.or(13, 3), 15);
// Test fit.xor
test("fit.xor(12, 10)", fit.xor(12, 10), 6);
test("fit.xor(16, 2)", fit.xor(16, 2), 18);
test("fit.xor(15, 3)", fit.xor(15, 3), 12);
test("fit.xor(13, 3)", fit.xor(13, 3), 14);
test("fit.xor(13.01, 3)", fit.xor(13.01, 3), null);
// Test fit.left
test("fit.left(12, 10)", fit.left(12, 10), 12288);
test("fit.left(16, 2)", fit.left(16, 2), 64);
test("fit.left(15, 53)", fit.left(15, 53), -9007199254740992);
// Test fit.right
test("fit.right(12, 10)", fit.right(12, 10), 0);
test("fit.right(19, 2)", fit.right(19, 2), 4);
test("fit.right(-9007199254740992, 53)", fit.right(-9007199254740992, 53), 7);
// Test fit.right_signed
test("fit.right_signed(-2, 1)", fit.right_signed(-2, 1), -1);
// Test fit.mask
test("fit.mask(0)", fit.mask(0), 0);
test("fit.mask(1)", fit.mask(1), 1);
test("fit.mask(3)", fit.mask(3), 7);
test("fit.mask(8)", fit.mask(8), 255);
test("fit.mask(16)", fit.mask(16), 65535);
test("fit.mask(32)", fit.mask(32), 4294967295);
test("fit.mask(55)", fit.mask(55), 36028797018963967);
test("fit.mask(56)", fit.mask(56), -1);
test("fit.mask(57)", fit.mask(57), null);
test("fit.mask(-1)", fit.mask(-1), -2);
test("fit.mask(-3)", fit.mask(-3), -8);
test("fit.mask(-8)", fit.mask(-8), -256);
test("fit.mask(-16)", fit.mask(-16), -65536);
test("fit.mask(-32)", fit.mask(-32), -4294967296);
test("fit.mask(-55)", fit.mask(-55), -36028797018963968);
test("fit.mask(-56)", fit.mask(-56), 0);
// Test fit.not
test("fit.not(0)", fit.not(0), -1);
test("fit.not(1)", fit.not(1), -2);
test("fit.not(-1)", fit.not(-1), 0);
// Test fit.ones
test("fit.ones(-1)", fit.ones(-1), 56);
test("fit.ones(0)", fit.ones(0), 0);
test("fit.ones(8)", fit.ones(8), 1);
test("fit.ones(18)", fit.ones(18), 2);
test("fit.ones(255)", fit.ones(255), 8);
// Test fit.zeros
test("fit.zeros(-1)", fit.zeros(-1), 0);
test("fit.zeros(0)", fit.zeros(0), 56);
test("fit.zeros(1)", fit.zeros(1), 55);
test("fit.zeros(2)", fit.zeros(2), 54);
test("fit.zeros(1024)", fit.zeros(1024), 45);
// Test fit.rotate
test("fit.rotate(1, 1)", fit.rotate(1, 1), 2);
test("fit.rotate(-2, 1)", fit.rotate(-2, 1), -3);
test("fit.rotate(1, 56)", fit.rotate(1, 56), 1); // Full rotation
test("fit.rotate(1, -1)", fit.rotate(1, -1), 1 << 55); // Rotate right by 1
// Test fit.reverse
test("fit.reverse(-36028797018963968)", fit.reverse(-36028797018963968), 1);
test("fit.reverse(3141592653589793)", fit.reverse(3141592653589793), 2334719610726733);
// Test edge cases and invalid inputs
test("fit.and with out-of-range", fit.and(1 << 56, 1), null);
test("fit.left with negative shift", fit.left(1, -1), null);
test("fit.left with large shift", fit.left(1, 100), null);
test("fit.right with negative shift", fit.right(1, -1), null);
test("fit.mask with float", fit.mask(3.5), null);
// Print test summary
log.console("\n" + "=".repeat(50));
log.console("Test Summary:");
log.console(" Total tests run:", tests_run);
log.console(" Tests passed: ", tests_passed);
log.console(" Tests failed: ", tests_failed);
log.console(" Success rate: ", Math.round((tests_passed / tests_run) * 100) + "%");
log.console("=".repeat(50));
if (tests_failed > 0) {
log.console("\nSome tests failed!");
} else {
log.console("\nAll tests passed!");
}
$_.stop()

14
tests/guid.ce Normal file
View File

@@ -0,0 +1,14 @@
var blob = use('blob')
var time = use('time')
var st = time.number()
var guid = new blob(256, $_.random_fit)
stone(guid)
var btime = time.number()-st
st = time.number()
guid = text(guid,'h')
st = time.number()-st
log.console(`took ${btime*1000000} us to make blob; took ${st*1000000} us to make it text`)
log.console(guid.toLowerCase())
log.console(guid.length)
$_.stop()

5
tests/hang.ce Normal file
View File

@@ -0,0 +1,5 @@
log.console(`Going to start hanging ...`)
while(1) {
// hang!
}

View File

@@ -1,11 +1,15 @@
var http = use('http')
var text = use('text')
var time = use('time')
try {
var b2 = http.fetch("https://gitea.pockle.world/api/v1/repos/john/prosperon/branches/master")
var st = time.number()
var b2 = http.fetch(arg[0])
log.console(`time took ${time.number()-st}`)
log.console(b2.length)
var text2 = text(b2)
log.console(text(b2))
$_.stop()
} catch (e) {
log.console("dictionary error:", e)
}

47
tests/httpget.ce Normal file
View File

@@ -0,0 +1,47 @@
var socket = use('socket')
var time = use('time')
var blob = use('blob')
var data = new blob
var start_time = time.number()
var host = arg[0]
var path = arg[1] || '/'
$_.start(e => {
send(e.actor, { op: 'get', domain: host, port: 80}, addrs => {
log.console(json.encode(addrs[0]))
})
}, 'dig')
/*
var addrs = socket.getaddrinfo(host, '80')
var addr = addrs[0]
log.console(json.encode(addrs))
var sock = socket.socket()
socket.connect(sock, addr)
var req = `GET ${path} HTTP/1.1\r\nHost: ${host}\r\nConnection: close\r\n\r\n`
socket.send(sock, req)
var chunk_size = 4096
function get_chunk()
{
var chunk = socket.recv(sock, chunk_size)
if (chunk.length > 0) {
log.console('got chunk size ' + chunk.length/8 + ' bytes')
data.write_blob(chunk)
get_chunk()
} else {
log.console(`http GET took ${time.number() - start_time}`)
log.console(`total length is ${data.length}`)
stone(data)
log.console(text(data))
log.console(`time taken: ${time.number()-start_time}`)
$_.stop()
}
}
get_chunk()
*/

20
tests/jswota.ce Normal file
View File

@@ -0,0 +1,20 @@
var text = use('text');
var jswota = use('jswota');
log.console("Testing jswota headers:");
log.console("INT header:", text(jswota.INT, 'b'));
log.console("FP_HEADER:", text(jswota.FP_HEADER, 'b'));
log.console("ARRAY header:", text(jswota.ARRAY, 'b'));
log.console("RECORD header:", text(jswota.RECORD, 'b'));
log.console("BLOB header:", text(jswota.BLOB, 'b'));
log.console("TEXT header:", text(jswota.TEXT, 'b'));
log.console("NULL_SYMBOL:", text(jswota.NULL_SYMBOL, 'b'));
log.console("FALSE_SYMBOL:", text(jswota.FALSE_SYMBOL, 'b'));
log.console("TRUE_SYMBOL:", text(jswota.TRUE_SYMBOL, 'b'));
log.console("4.25:" ,text(jswota.encode(4.25),'b'));
log.console("true:", text(jswota.encode(true),'b'))
log.console("record:", text(jswota.encode({a:5,b:7}),'b'))
$_.stop()

15
tests/kill.ce Normal file
View File

@@ -0,0 +1,15 @@
// This tests forceful killing of an underling that may even be in the middle of a turn
$_.start(e => {
log.console(`got message from hanger: ${e.type}`)
if (e.type == 'greet')
$_.delay(_ => {
log.console(`sending stop message to hanger`)
$_.stop(e.actor)
}, 1)
if (e.type == 'disrupt') {
log.console(`underling successfully killed.`)
$_.stop()
}
}, 'hang')

51
tests/kim.ce Normal file
View File

@@ -0,0 +1,51 @@
var kim = use("kim");
var blob = use('blob')
// Test basic ASCII
var test1 = "Hello, World!";
var encoded1 = kim.encode(test1);
var decoded1 = kim.decode(encoded1);
log.console("ASCII test:", test1 === decoded1 ? "PASS" : "FAIL");
if (test1 !== decoded1) {
log.console(" Expected:", test1);
log.console(" Got:", decoded1);
}
// Test Unicode characters
var test2 = "Hello, 世界! 🌍 Привет мир";
var encoded2 = kim.encode(test2);
var decoded2 = kim.decode(encoded2);
log.console("Unicode test:", test2 === decoded2 ? "PASS" : "FAIL");
if (test2 !== decoded2) {
log.console(" Expected:", test2);
log.console(" Got:", decoded2);
}
// Test empty string
var test3 = "";
var encoded3 = kim.encode(test3);
log.console(typeof encoded3)
log.console(encoded3 instanceof blob)
var decoded3 = kim.decode(encoded3);
log.console("Empty string test:", test3 === decoded3 ? "PASS" : "FAIL");
// Test various Unicode ranges
var test4 = "αβγδε АБВГД 你好 😀😎🎉 ∑∏∫";
var encoded4 = kim.encode(test4);
var decoded4 = kim.decode(encoded4);
log.console("Mixed Unicode test:", test4 === decoded4 ? "PASS" : "FAIL");
if (test4 !== decoded4) {
log.console(" Expected:", test4);
log.console(" Got:", decoded4);
}
// Test efficiency - KIM should be smaller for high codepoints
var highCodepoints = "🌍🌎🌏🗺️🧭";
var encodedHigh = kim.encode(highCodepoints);
var utf8Bytes = new Blob([highCodepoints]).size;
log.console("High codepoint efficiency:");
log.console(" UTF-8 bytes:", utf8Bytes);
log.console(" KIM bytes:", encodedHigh.byteLength);
log.console(" Savings:", utf8Bytes - encodedHigh.byteLength, "bytes");
log.console("\nAll tests completed!");

View File

@@ -2,6 +2,7 @@
var io = use('io')
var shop = use('shop')
var time = use('time')
log.console("=== Testing Module System ===")
@@ -26,7 +27,7 @@ log.console("✓ TOML parser working")
// Test 2: Shop initialization
log.console("\n2. Testing shop initialization...")
var test_dir = "module_test_" + Date.now()
var test_dir = "module_test_" + time.number()
io.mkdir(test_dir)
var old_cwd = io.basedir()

View File

@@ -1,2 +0,0 @@
this.spawn()
log.console("SPAWNED")

View File

@@ -1 +0,0 @@
log.console("im alive")

View File

@@ -1,2 +0,0 @@
for (var i = 0; i < 10; i++)
$_.start(_ => {}, "spawnee")

View File

@@ -1,2 +1,2 @@
$_.delay($_.stop, 0.1)
log.console(`About to stop.`)
$_.stop()

23
tests/text_test.ce Normal file
View File

@@ -0,0 +1,23 @@
// Test text module
var text = use('text')
var blob = use('blob')
// Create a test blob with some data
var b = new blob()
b.write_text("Hello")
b.stone()
log.console("Original blob content (as text):", text(b))
log.console("Hex encoding:", text(b, 'h'))
log.console("Base32 encoding:", text(b, 't'))
// Test with binary data
var b2 = new blob()
b2.write_fit(255, 8)
b2.write_fit(170, 8) // 10101010 in binary
b2.write_fit(15, 8) // 00001111 in binary
b2.stone()
log.console("\nBinary data tests:")
log.console("Hex encoding:", text(b2, 'h'))
log.console("Base32 encoding:", text(b2, 't'))

47
tests/text_utf8.ce Normal file
View File

@@ -0,0 +1,47 @@
var text = use('text');
var blob = use('blob');
var utf8 = use('utf8');
// Test blob to text conversion
var test_string = "Hello, 世界! 🌍";
var encoded_blob = utf8.encode(test_string);
var decoded_text = text(encoded_blob);
log.console("Blob to text test:");
log.console(" Original:", test_string);
log.console(" Decoded:", decoded_text);
log.console(" Match:", test_string === decoded_text ? "PASS" : "FAIL");
// Test array of codepoints conversion
var codepoints = [72, 101, 108, 108, 111, 44, 32, 19990, 30028, 33, 32, 127757];
var from_codepoints = text(codepoints);
log.console("\nCodepoints to text test:");
log.console(" From codepoints:", from_codepoints);
log.console(" Match:", from_codepoints === test_string ? "PASS" : "FAIL");
// Test array with separator
var words = ["Hello", "world", "from", "text"];
var joined = text(words, " ");
log.console("\nArray with separator test:");
log.console(" Joined:", joined);
log.console(" Expected: Hello world from text");
log.console(" Match:", joined === "Hello world from text" ? "PASS" : "FAIL");
// Test mixed array with codepoints
var mixed = [72, "ello", 32, "world"];
var mixed_result = text(mixed, "");
log.console("\nMixed array test:");
log.console(" Result:", mixed_result);
log.console(" Expected: Hello world");
log.console(" Match:", mixed_result === "Hello world" ? "PASS" : "FAIL");
// Test blob encoding formats still work
var test_data = utf8.encode("ABC");
log.console("\nBlob format tests:");
log.console(" Hex:", text(test_data, "h"));
log.console(" Binary:", text(test_data, "b"));
log.console(" Octal:", text(test_data, "o"));
log.console("\nAll tests completed!");
$_.stop();

View File

@@ -1,4 +1,4 @@
$_.unneeded(_ => {
log.console("Unneded function fired.");
$_.unneeded($_.stop, 1);
$_.start(undefined, "unneeded")
}, 1);

70
tests/utf8.ce Normal file
View File

@@ -0,0 +1,70 @@
var utf8 = use("utf8");
// Test character counting vs byte counting
var test1 = "Hello";
log.console("ASCII length test:");
log.console(" Characters:", utf8.length(test1));
log.console(" Bytes:", utf8.byte_length(test1));
log.console(" Match:", utf8.length(test1) === utf8.byte_length(test1) ? "PASS" : "FAIL");
var test2 = "Hello 世界";
log.console("\nMixed ASCII/Unicode length test:");
log.console(" Characters:", utf8.length(test2));
log.console(" Bytes:", utf8.byte_length(test2));
log.console(" Bytes > Characters:", utf8.byte_length(test2) > utf8.length(test2) ? "PASS" : "FAIL");
// Test codepoints
var test3 = "A😀B";
var codepoints = utf8.codepoints(test3);
log.console("\nCodepoints test:");
log.console(" String:", test3);
log.console(" Codepoints:", codepoints);
log.console(" A=65:", codepoints[0] === 65 ? "PASS" : "FAIL");
log.console(" 😀=128512:", codepoints[1] === 128512 ? "PASS" : "FAIL");
log.console(" B=66:", codepoints[2] === 66 ? "PASS" : "FAIL");
// Test from_codepoints
var reconstructed = utf8.from_codepoints(codepoints);
log.console(" Reconstructed:", reconstructed);
log.console(" Match:", test3 === reconstructed ? "PASS" : "FAIL");
// Test encode/decode
var test4 = "UTF-8 encoding: 你好世界 🌍";
var encoded = utf8.encode(test4);
var decoded = utf8.decode(encoded);
log.console("\nEncode/decode test:");
log.console(" Original:", test4);
log.console(" Decoded:", decoded);
log.console(" Match:", test4 === decoded ? "PASS" : "FAIL");
// Test validation
log.console("\nValidation tests:");
log.console(" Valid UTF-8:", utf8.validate("Hello 世界") ? "PASS" : "FAIL");
// Test slicing
var test5 = "Hello 世界!";
log.console("\nSlice tests:");
log.console(" Original:", test5);
log.console(" slice(0, 5):", utf8.slice(test5, 0, 5));
log.console(" slice(6, 8):", utf8.slice(test5, 6, 8));
log.console(" slice(-3):", utf8.slice(test5, -3));
log.console(" slice(0, -1):", utf8.slice(test5, 0, -1));
// Test char_at
log.console("\nchar_at tests:");
log.console(" char_at(0):", utf8.char_at(test5, 0));
log.console(" char_at(6):", utf8.char_at(test5, 6));
log.console(" char_at(7):", utf8.char_at(test5, 7));
log.console(" char_at(100):", utf8.char_at(test5, 100));
// Test with emoji sequences
var test6 = "👨‍👩‍👧‍👦";
log.console("\nComplex emoji test:");
log.console(" String:", test6);
log.console(" Length:", utf8.length(test6));
log.console(" Byte length:", utf8.byte_length(test6));
log.console(" Codepoints:", utf8.codepoints(test6).length);
log.console("\nAll tests completed!");
$_.stop()

View File

@@ -1,260 +1,221 @@
var wota = use('wota');
var os = use('os');
/*
* wota_test.cm  selfcontained testsuite for the Wota encode/decode module
* *** rewritten to run in an environment that ONLY supports
* Blobs; TypedArrays / ArrayBuffers / DataView are GONE. ***
*
* Exit status 0 → all tests passed, nonzero otherwise.
*/
// Helper function to convert hex string to ArrayBuffer
function hexToBuffer(hex) {
let bytes = new Uint8Array(hex.length / 2);
for (let i = 0; i < hex.length; i += 2)
bytes[i / 2] = parseInt(hex.substr(i, 2), 16);
return bytes.buffer;
'use strict'
var wota = use('wota')
var os = use('os')
var Blob = use('blob')
/*──────────────────────────────────────────────────────────────────────────*/
/* Helper utilities */
/*──────────────────────────────────────────────────────────────────────────*/
const EPSILON = 1e-12
function stone_if_needed(b) { if (!stone.p(b)) stone(b) }
/* Convert an array of octets to a stone Blob */
function bytes_to_blob(bytes) {
var b = new Blob()
for (var i = 0; i < bytes.length; i++) {
var byte = bytes[i]
for (var bit = 7; bit >= 0; bit--) b.write_bit((byte >> bit) & 1)
}
stone(b)
return b
}
// Helper function to convert ArrayBuffer to hex string
function bufferToHex(buffer) {
return Array.from(new Uint8Array(buffer))
.map(b => b.toString(16).padStart(2, '0'))
.join('')
.toLowerCase();
/* Parse hex → Blob */
function hex_to_blob(hex) {
if (hex.length % 2) hex = '0' + hex // odd nibble safety
var bytes = []
for (var i = 0; i < hex.length; i += 2)
bytes.push(parseInt(hex.substr(i, 2), 16))
return bytes_to_blob(bytes)
}
var EPSILON = 1e-12;
/* Blob → lowercase hex */
function blob_to_hex(blob) {
stone_if_needed(blob)
var bytes = []
for (var i = 0; i < blob.length; i += 8) {
var byte = 0
for (var bit = 0; bit < 8; bit++) byte = (byte << 1) | (blob.read_logical(i + bit) ? 1 : 0)
bytes.push(byte)
}
return bytes.map(b => b.toString(16).padStart(2, '0')).join('').toLowerCase()
}
// Deep comparison function for objects and arrays
function deepCompare(expected, actual, path = '') {
if (expected === actual) return { passed: true, messages: [] };
function is_blob(x) { return x && typeof x === 'object' && typeof x.length === 'number' && typeof x.read_logical === 'function' }
/* Deep comparison capable of Blobs + tolerance for floating diff */
function deep_compare(expected, actual, path = '') {
if (expected === actual) return { passed: true, messages: [] }
if (typeof expected === 'number' && typeof actual === 'number') {
if (isNaN(expected) && isNaN(actual))
return { passed: true, messages: [] };
const diff = Math.abs(expected - actual);
if (diff <= EPSILON)
return { passed: true, messages: [] };
return {
passed: false,
messages: [
`Value mismatch at ${path}: expected ${expected}, got ${actual}`,
`Difference of ${diff} is larger than tolerance ${EPSILON}`
]
};
if (isNaN(expected) && isNaN(actual)) return { passed: true, messages: [] }
var diff = Math.abs(expected - actual)
if (diff <= EPSILON) return { passed: true, messages: [] }
return { passed: false, messages: [`Value mismatch at ${path}: ${expected} vs ${actual} (diff ${diff})`] }
}
if (expected instanceof ArrayBuffer && actual instanceof ArrayBuffer) {
const expArray = Array.from(new Uint8Array(expected));
const actArray = Array.from(new Uint8Array(actual));
return deepCompare(expArray, actArray, path);
if (is_blob(expected) && is_blob(actual)) {
stone_if_needed(expected); stone_if_needed(actual)
if (expected.length !== actual.length)
return { passed: false, messages: [`Blob length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
for (var i = 0; i < expected.length; i++) {
if (expected.read_logical(i) !== actual.read_logical(i))
return { passed: false, messages: [`Blob bit mismatch at ${path}[${i}]`] }
}
return { passed: true, messages: [] }
}
if (actual instanceof ArrayBuffer)
actual = Array.from(new Uint8Array(actual));
if (Array.isArray(expected) && Array.isArray(actual)) {
if (expected.length !== actual.length)
return {
passed: false,
messages: [`Array length mismatch at ${path}: expected ${expected.length}, got ${actual.length}`]
};
let messages = [];
for (let i = 0; i < expected.length; i++) {
const result = deepCompare(expected[i], actual[i], `${path}[${i}]`);
if (!result.passed) messages.push(...result.messages);
return { passed: false, messages: [`Array length mismatch at ${path}: ${expected.length} vs ${actual.length}`] }
var msgs = []
for (var i = 0; i < expected.length; i++) {
var res = deep_compare(expected[i], actual[i], `${path}[${i}]`)
if (!res.passed) msgs.push(...res.messages)
}
return { passed: messages.length === 0, messages };
return { passed: msgs.length === 0, messages: msgs }
}
if (typeof expected === 'object' && expected !== null &&
typeof actual === 'object' && actual !== null) {
const expKeys = Object.keys(expected).sort();
const actKeys = Object.keys(actual).sort();
if (typeof expected === 'object' && expected && typeof actual === 'object' && actual) {
var expKeys = Object.keys(expected).sort()
var actKeys = Object.keys(actual).sort()
if (JSON.stringify(expKeys) !== JSON.stringify(actKeys))
return {
passed: false,
messages: [`Object keys mismatch at ${path}: expected ${expKeys}, got ${actKeys}`]
};
let messages = [];
for (let key of expKeys) {
const result = deepCompare(expected[key], actual[key], `${path}.${key}`);
if (!result.passed) messages.push(...result.messages);
return { passed: false, messages: [`Object keys mismatch at ${path}: ${expKeys} vs ${actKeys}`] }
var msgs = []
for (var k of expKeys) {
var res = deep_compare(expected[k], actual[k], `${path}.${k}`)
if (!res.passed) msgs.push(...res.messages)
}
return { passed: messages.length === 0, messages };
return { passed: msgs.length === 0, messages: msgs }
}
return {
passed: false,
messages: [`Value mismatch at ${path}: expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`]
};
return { passed: false, messages: [`Value mismatch at ${path}: ${JSON.stringify(expected)} vs ${JSON.stringify(actual)}`] }
}
// Test cases covering Wota types and replacer/reviver functionality
var testarr = [];
var hex = "a374";
for (var i = 0; i < 500; i++) {
testarr.push(1);
hex += "61";
}
/*──────────────────────────────────────────────────────────────────────────*/
/* Test matrix */
/*──────────────────────────────────────────────────────────────────────────*/
var testarr = []
var hex = 'a374'
for (var i = 0; i < 500; i++) { testarr.push(1); hex += '61' }
function bb() { return bytes_to_blob.apply(null, arguments) } // shorthand
var testCases = [
// Integer tests (WOTA_INT up to 56-bit)
{ input: 0, expectedHex: "60" },
{ input: 2023, expectedHex: "e08f67" },
{ input: -1, expectedHex: "69" },
{ input: 7, expectedHex: "67" },
{ input: -7, expectedHex: "6f" },
{ input: 1023, expectedHex: "e07f" },
{ input: -1023, expectedHex: "ef7f" },
{ input: 2**55 - 1, expectedHex: "e0ffffffffffffff" }, // Max 56-bit int
{ input: -(2**55), expectedHex: "e000000000000000" }, // Min 56-bit int
{ input: 0, expectedHex: '60' },
{ input: 2023, expectedHex: 'e08f67' },
{ input: -1, expectedHex: '69' },
{ input: 7, expectedHex: '67' },
{ input: -7, expectedHex: '6f' },
{ input: 1023, expectedHex: 'e07f' },
{ input: -1023, expectedHex: 'ef7f' },
{ input: Math.pow(2, 55) - 1, expectedHex: 'e0ffffffffffffff' },
{ input: -Math.pow(2, 55), expectedHex: 'e000000000000000' },
// Symbol tests
{ input: undefined, expectedHex: "70" },
{ input: false, expectedHex: "72" },
{ input: true, expectedHex: "73" },
{ input: undefined, expectedHex: '70' },
{ input: false, expectedHex: '72' },
{ input: true, expectedHex: '73' },
// Floating Point tests (WOTA_FLOAT)
{ input: -1.01, expectedHex: "5a65" },
{ input: 98.6, expectedHex: "51875a" },
{ input: -0.5772156649, expectedHex: "d80a95c0b0bd69" },
{ input: -1.00000000000001, expectedHex: "d80e96deb183e98001" },
{ input: -10000000000000, expectedHex: "c80d01" },
{ input: 2**55, expectedHex: "d80e01" }, // Beyond 56-bit, stored as float
{ input: -1.01, expectedHex: '5a65' },
{ input: 98.6, expectedHex: '51875a' },
{ input: -0.5772156649, expectedHex: 'd80a95c0b0bd69' },
{ input: -1.00000000000001, expectedHex: 'd80e96deb183e98001' },
{ input: -10000000000000, expectedHex: 'c80d01' },
{ input: Math.pow(2, 55), expectedHex: 'd80e01' },
// Text tests
{ input: "", expectedHex: "10" },
{ input: "cat", expectedHex: "13636174" },
{ input: "U+1F4A9 「うんち絵文字」 «💩»",
expectedHex: "9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b" },
{ input: '', expectedHex: '10' },
{ input: 'cat', expectedHex: '13636174' },
{ input: 'U+1F4A9 「うんち絵文字」 «💩»', expectedHex: '9014552b314634413920e00ce046e113e06181fa7581cb0781b657e00d20812b87e929813b' },
// Blob tests
{ input: new Uint8Array([0xFF, 0xAA]).buffer, expectedHex: "8010ffaa" },
{ input: new Uint8Array([0b11110000, 0b11100011, 0b00100000, 0b10000000]).buffer,
expectedHex: "8019f0e32080" },
{ input: bytes_to_blob([0xff, 0xaa]), expectedHex: '8010ffaa' },
{ input: bytes_to_blob([0xf0, 0xe3, 0x20, 0x80]), expectedHex: '8019f0e32080' },
// Large array test
{ input: testarr, expectedHex: hex },
// Array tests
{ input: [], expectedHex: "20" },
{ input: [1, 2, 3], expectedHex: "23616263" },
{ input: [-1, 0, 1.5], expectedHex: "2369605043" },
{ input: [], expectedHex: '20' },
{ input: [1, 2, 3], expectedHex: '23616263' },
{ input: [-1, 0, 1.5], expectedHex: '2369605043' },
// Record tests
{ input: {}, expectedHex: "30" },
{ input: { a: 1, b: 2 }, expectedHex: "32116161116262" },
{ input: {}, expectedHex: '30' },
{ input: { a: 1, b: 2 }, expectedHex: '32116161116262' },
// Complex nested structures
{ input: {
num: 42,
arr: [1, -1, 2.5],
str: "test",
obj: { x: true }
},
expectedHex: "34216e756d622a2173747214746573742161727223616965235840216f626a21117873" },
{ input: { num: 42, arr: [1, -1, 2.5], str: 'test', obj: { x: true } }, expectedHex: '34216e756d622a2173747214746573742161727223616965235840216f626a21117873' },
// Additional edge cases
{ input: new Uint8Array([]).buffer, expectedHex: "00" },
{ input: [[]], expectedHex: "2120" },
{ input: { "": "" }, expectedHex: "311010" },
{ input: 1e-10, expectedHex: "d00a01" },
{ input: new Blob(), expectedHex: '00' },
{ input: [[]], expectedHex: '2120' },
{ input: { '': '' }, expectedHex: '311010' },
{ input: 1e-10, expectedHex: 'd00a01' },
// Replacer tests
{ input: { a: 1, b: 2 },
replacer: (key, value) => typeof value === 'number' ? value * 2 : value,
expected: { a: 2, b: 4 },
testType: 'replacer' },
{ input: { a: 1, b: 2 }, replacer: (k, v) => typeof v === 'number' ? v * 2 : v, expected: { a: 2, b: 4 }, testType: 'replacer' },
{ input: { x: 'test', y: 5 }, replacer: (k, v) => k === 'x' ? v + '!' : v, expected: { x: 'test!', y: 5 }, testType: 'replacer' },
{ input: { x: "test", y: 5 },
replacer: (key, value) => key === 'x' ? value + "!" : value,
expected: { x: "test!", y: 5 },
testType: 'replacer' },
{ input: { a: 1, b: 2 }, reviver: (k, v) => typeof v === 'number' ? v * 3 : v, expected: { a: 3, b: 6 }, testType: 'reviver' },
{ input: { x: 'test', y: 10 }, reviver: (k, v) => k === 'y' ? v + 1 : v, expected: { x: 'test', y: 11 }, testType: 'reviver' }
]
// Reviver tests
{ input: { a: 1, b: 2 },
reviver: (key, value) => typeof value === 'number' ? value * 3 : value,
expected: { a: 3, b: 6 },
testType: 'reviver' },
/*──────────────────────────────────────────────────────────────────────────*/
/* Execution */
/*──────────────────────────────────────────────────────────────────────────*/
{ input: { x: "test", y: 10 },
reviver: (key, value) => key === 'y' ? value + 1 : value,
expected: { x: "test", y: 11 },
testType: 'reviver' }
];
var results = []
var testCount = 0
// Run tests and collect results
let results = [];
let testCount = 0;
for (let test of testCases) {
testCount++;
let testName = `Test ${testCount}: ${JSON.stringify(test.input)}${test.testType ? ` (${test.testType})` : ''}`;
let passed = true;
let messages = [];
for (var t of testCases) {
testCount++
var name = `Test ${testCount}: ${JSON.stringify(t.input)}${t.testType ? ' (' + t.testType + ')' : ''}`
var passed = true
var msgs = []
try {
// Test encoding
let encoded = test.replacer ? wota.encode(test.input, test.replacer) : wota.encode(test.input);
if (!(encoded instanceof ArrayBuffer)) {
passed = false;
messages.push("Encode should return ArrayBuffer");
} else {
if (test.expectedHex) {
let encodedHex = bufferToHex(encoded);
if (encodedHex !== test.expectedHex.toLowerCase()) {
messages.push(
`Hex encoding differs (informational):
Expected: ${test.expectedHex}
Got: ${encodedHex}`
);
}
var enc = t.replacer ? wota.encode(t.input, t.replacer) : wota.encode(t.input)
if (!is_blob(enc)) { passed = false; msgs.push('encode() should return a Blob') }
else {
if (t.expectedHex) {
var gotHex = blob_to_hex(enc)
if (gotHex !== t.expectedHex.toLowerCase())
msgs.push(`Hex encoding differs (info): exp ${t.expectedHex}, got ${gotHex}`)
}
// Test decoding
let decoded = test.reviver ? wota.decode(encoded, test.reviver) : wota.decode(encoded);
let expected = test.expected || test.input;
var dec = t.reviver ? wota.decode(enc, t.reviver) : wota.decode(enc)
var exp = t.expected !== undefined ? t.expected : t.input
// Normalize ArrayBuffer for comparison
if (expected instanceof ArrayBuffer)
expected = Array.from(new Uint8Array(expected));
if (decoded instanceof ArrayBuffer)
decoded = Array.from(new Uint8Array(decoded));
const compareResult = deepCompare(expected, decoded);
if (!compareResult.passed) {
passed = false;
messages.push("Decoding failed:");
messages.push(...compareResult.messages);
}
}
} catch (e) {
passed = false;
messages.push(`Exception thrown: ${e}`);
var cmp = deep_compare(exp, dec)
if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) }
}
} catch (e) { passed = false; msgs.push('Exception: ' + e) }
results.push({ testName, passed, messages });
results.push({ name, passed, msgs })
if (!passed) {
log.console(`\nDetailed Failure Report for ${testName}:`);
log.console(`Input: ${JSON.stringify(test.input)}`);
if (test.replacer) log.console(`Replacer: ${test.replacer.toString()}`);
if (test.reviver) log.console(`Reviver: ${test.reviver.toString()}`);
log.console(messages.join("\n"));
log.console("");
log.console('\nFailure detail for ' + name + '\n' + msgs.join('\n') + '\n')
}
}
// Summary
log.console("\nTest Summary:");
results.forEach(result => {
log.console(`${result.testName} - ${result.passed ? "Passed" : "Failed"}`);
if (!result.passed)
log.console(result.messages);
});
/*──────────────────────────────────────────────────────────────────────────*/
/* Summary */
/*──────────────────────────────────────────────────────────────────────────*/
let passedCount = results.filter(r => r.passed).length;
log.console(`\nResult: ${passedCount}/${testCount} tests passed`);
if (passedCount < testCount) {
log.console("Overall: FAILED");
os.exit(1);
} else {
log.console("Overall: PASSED");
os.exit(0);
log.console('\nTest Summary:')
var passCount = 0
for (var r of results) {
log.console(`${r.name} ${r.passed ? 'Passed' : 'Failed'}`)
if (r.msgs.length && r.passed) log.console(' ' + r.msgs.join('\n '))
if (r.passed) passCount++
}
log.console(`\nResult: ${passCount}/${testCount} tests passed`)
if (passCount === testCount) { log.console('Overall: PASSED'); os.exit(0) }
log.console('Overall: FAILED')
$_.stop()