Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc58cc5a12 | ||
|
|
a274fb174f | ||
|
|
3622a5ec58 | ||
|
|
8a5f8a4d74 | ||
|
|
c1d341eecd | ||
|
|
3176e6775d | ||
|
|
34dcd0a235 | ||
|
|
cbda7dfbc9 | ||
|
|
d039e2cfe6 | ||
|
|
c02bd06ec0 | ||
|
|
efa63771e6 | ||
|
|
9f6d27fb3c | ||
|
|
1a61ae6f77 | ||
|
|
83c816fd0e | ||
|
|
adbaa92dd5 | ||
|
|
580df9f233 | ||
|
|
d5d17560f9 | ||
|
|
cd05ab97b5 | ||
|
|
4eecbd692b | ||
|
|
72beed7177 | ||
|
|
e0595de71a | ||
|
|
6687008d1a | ||
|
|
5b9f1b8f51 | ||
|
|
c570de7f41 | ||
|
|
d0138a6c23 | ||
|
|
29aa25e866 | ||
|
|
ef28be93db | ||
|
|
0d7be6a94e | ||
|
|
4fe78c4a63 | ||
|
|
b52edb2746 | ||
|
|
79d5412fe6 | ||
|
|
fcec2cd1dc | ||
|
|
2038ce15a7 | ||
|
|
08557011cb | ||
|
|
3e87bfd6cc | ||
|
|
ef86dd3ecf | ||
|
|
c887bcf7b9 | ||
|
|
709f2459e4 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
];
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
33
meson.build
33
meson.build
@@ -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',
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var input = use('input')
|
||||
var util = use('util')
|
||||
|
||||
var downkeys = {};
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) world‐window origin (bottom‐left)
|
||||
const worldX0 = camera.pos[0] - camera.size[0] * camera.anchor[0];
|
||||
const worldY0 = camera.pos[1] - camera.size[1] * camera.anchor[1];
|
||||
|
||||
// 2) normalized device coords [0..1]
|
||||
const ndcX = (wx - worldX0) / camera.size[0];
|
||||
const ndcY = (wy - worldY0) / camera.size[1];
|
||||
|
||||
// 3) map into pixel‐space via the fractional viewport
|
||||
const px = camera.viewport.x * winW
|
||||
+ ndcX * (camera.viewport.width * winW);
|
||||
const py = camera.viewport.y * winH
|
||||
+ (1 - ndcY) * (camera.viewport.height * winH);
|
||||
|
||||
return [ px, py ];
|
||||
}
|
||||
|
||||
function screenToWorldPoint([px, py], camera, winW, winH) {
|
||||
// 1) undo pixel→NDC within the camera’s viewport
|
||||
const ndcX = (px - camera.viewport.x * winW)
|
||||
/ (camera.viewport.width * winW)
|
||||
const ndcY = 1 - (py - camera.viewport.y * winH)
|
||||
/ (camera.viewport.height * winH)
|
||||
|
||||
// 2) compute the world‐window origin (bottom‐left)
|
||||
const worldX0 = camera.pos[0]
|
||||
- camera.size[0] * camera.anchor[0]
|
||||
const worldY0 = camera.pos[1]
|
||||
- camera.size[1] * camera.anchor[1]
|
||||
|
||||
// 3) map NDC back to world coords
|
||||
return [
|
||||
ndcX * camera.size[0] + worldX0,
|
||||
ndcY * camera.size[1] + worldY0
|
||||
]
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,5 @@
|
||||
var io = use('io')
|
||||
|
||||
Object.defineProperty(Function.prototype, "hashify", {
|
||||
value: function () {
|
||||
var hash = new Map()
|
||||
|
||||
@@ -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':
|
||||
@@ -38,10 +38,9 @@ $_.delay($_.stop, 3)
|
||||
|
||||
var os = use('os')
|
||||
var actor = use('actor')
|
||||
var ioguy = {
|
||||
__ACTORDATA__: {
|
||||
id: actor.ioactor()
|
||||
}
|
||||
var ioguy = {}
|
||||
ioguy[cell.actor_sym] = {
|
||||
id: actor.ioactor()
|
||||
}
|
||||
|
||||
send(ioguy, {
|
||||
|
||||
1266
scripts/base.cm
1266
scripts/base.cm
File diff suppressed because it is too large
Load Diff
@@ -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
249
scripts/config.ce
Normal 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()
|
||||
@@ -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) {
|
||||
var reason = (err instanceof Error) ? err.stack : err
|
||||
report_to_overling({type:'disrupt', reason})
|
||||
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 ??= {}
|
||||
@@ -251,16 +263,34 @@ 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,15 +506,15 @@ $_.start = function start(cb, program, ...args) {
|
||||
|
||||
$_.stop = function stop(actor) {
|
||||
if (!actor) {
|
||||
destroyself()
|
||||
need_stop = true
|
||||
return
|
||||
}
|
||||
if (!is_actor(actor))
|
||||
throw new Error('Can only call stop on an 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")
|
||||
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,15 +796,22 @@ 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)) {
|
||||
var mainPath = progPath + '/main' + ACTOR_EXT
|
||||
if (io.exists(mainPath) && !io.is_directory(mainPath)) {
|
||||
prog = mainPath
|
||||
// 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('/'))
|
||||
|
||||
@@ -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)
|
||||
if (val)
|
||||
throw new Error('Program must not return anything');
|
||||
// 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);
|
||||
|
||||
log.console("WAYDOWN")
|
||||
|
||||
send_messages()
|
||||
if (val)
|
||||
throw new Error('Program must not return anything');
|
||||
})
|
||||
|
||||
})()
|
||||
@@ -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
124
scripts/jswota.cm
Normal 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
102
scripts/man/config.man
Normal 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)
|
||||
@@ -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; };
|
||||
|
||||
@@ -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."
|
||||
|
||||
|
||||
130
scripts/test.ce
130
scripts/test.ce
@@ -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();
|
||||
|
||||
// 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, $_);
|
||||
};
|
||||
}
|
||||
|
||||
// Build array of requestors
|
||||
var requestors = tests.map(function (t) {
|
||||
return run_test_requestor(t);
|
||||
});
|
||||
|
||||
// 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];
|
||||
for (var test of def)
|
||||
$_.start(e => {
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 'tests/' + test, $_)
|
||||
|
||||
// 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);
|
||||
});
|
||||
$_.delay($_.stop, 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))
|
||||
})
|
||||
186
scripts/text.cm
186
scripts/text.cm
@@ -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,113 +138,43 @@ 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] || "";
|
||||
var result = "";
|
||||
|
||||
// Check if all items are valid codepoints
|
||||
var all_codepoints = true;
|
||||
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);
|
||||
} else {
|
||||
result += String(item);
|
||||
if (!(typeof item === 'number' && item >= 0 && item <= 0x10FFFF && item === Math.floor(item))) {
|
||||
all_codepoints = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
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)) {
|
||||
// Single codepoint - use utf8 module
|
||||
result += utf8.from_codepoints([item]);
|
||||
} else {
|
||||
result += String(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle number conversion
|
||||
@@ -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;
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
1296
source/cell.c
1296
source/cell.c
File diff suppressed because it is too large
Load Diff
@@ -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,75 +47,67 @@ 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;
|
||||
|
||||
ModuleEntry *module_registry;
|
||||
JSValue *js_swapchains;
|
||||
|
||||
/* Protects JSContext usage */
|
||||
SDL_Mutex *mutex;
|
||||
SDL_Mutex *mutex; /* for everything else */
|
||||
SDL_Mutex *msg_mutex; /* For message queue and timers queue */
|
||||
|
||||
SDL_Mutex *turn;
|
||||
|
||||
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
633
source/cutils.c
Normal 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
367
source/cutils.h
Normal 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
1620
source/dtoa.c
Normal file
File diff suppressed because it is too large
Load Diff
83
source/dtoa.h
Normal file
83
source/dtoa.h
Normal 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);
|
||||
170
source/jsffi.c
170
source/jsffi.c
@@ -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);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
char guid_str[33];
|
||||
|
||||
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);
|
||||
|
||||
@@ -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
|
||||
|
||||
12
source/kim.h
12
source/kim.h
@@ -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
2529
source/libregexp.c
Normal file
File diff suppressed because it is too large
Load Diff
60
source/libregexp.h
Normal file
60
source/libregexp.h
Normal 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
4730
source/libunicode-table.h
Normal file
File diff suppressed because it is too large
Load Diff
1910
source/libunicode.c
Normal file
1910
source/libunicode.c
Normal file
File diff suppressed because it is too large
Load Diff
182
source/libunicode.h
Normal file
182
source/libunicode.h
Normal 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
99
source/list.h
Normal 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 */
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "kim.h"
|
||||
|
||||
/* Nota type nibble values */
|
||||
#define NOTA_BLOB 0x00
|
||||
|
||||
@@ -17,8 +17,8 @@ cell_rt *js2actor(JSContext *js, JSValue v)
|
||||
if (!JS_IsObject(v))
|
||||
return NULL;
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JSValue actor_data = JS_GetPropertyStr(js, v, "__ACTORDATA__");
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
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);
|
||||
JS_FreeCString(js,id);
|
||||
if (!exist)
|
||||
return JS_ThrowInternalError(js, "No mailbox found for given ID");
|
||||
|
||||
void *data = value2wota(js, argv[1], JS_UNDEFINED);
|
||||
if (!exist) {
|
||||
JS_FreeCString(js,id);
|
||||
return JS_ThrowInternalError(js, "No mailbox found for given ID");
|
||||
}
|
||||
|
||||
const char *err = send_message(id, data);
|
||||
cell_rt *target = get_actor(id);
|
||||
JS_FreeCString(js,id);
|
||||
|
||||
/* 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) {
|
||||
|
||||
@@ -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
|
||||
@@ -50,32 +50,43 @@ 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;
|
||||
|
||||
if (fitval & 1)
|
||||
bd->data[byte_idx] |= (uint8_t)(1 << bit_idx);
|
||||
else
|
||||
bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx);
|
||||
|
||||
/* 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;
|
||||
|
||||
/* 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 {
|
||||
@@ -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),
|
||||
|
||||
489
source/qjs_fd.c
489
source/qjs_fd.c
@@ -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));
|
||||
)
|
||||
|
||||
// FILE DESCRIPTOR FUNCTIONS
|
||||
|
||||
JSC_CCALL(file_close,
|
||||
FDWrapper *fdw = js2fd(js, self);
|
||||
if (!fdw) return JS_EXCEPTION;
|
||||
JSC_CCALL(fd_write,
|
||||
int fd = js2fd(js, argv[0]);
|
||||
if (fd < 0) 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
257
source/qjs_fit.c
Normal 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
8
source/qjs_fit.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_FIT_H
|
||||
#define QJS_FIT_H
|
||||
|
||||
#include <quickjs.h>
|
||||
|
||||
JSValue js_fit_use(JSContext *js);
|
||||
|
||||
#endif
|
||||
@@ -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
82
source/qjs_kim.c
Normal 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
8
source/qjs_kim.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_KIM_H
|
||||
#define QJS_KIM_H
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
JSValue js_kim_use(JSContext*);
|
||||
|
||||
#endif
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
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;
|
||||
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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
131
source/qjs_sdl.c
131
source/qjs_sdl.c
@@ -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
707
source/qjs_sdl_input.c
Normal 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
12
source/qjs_sdl_input.h
Normal 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
|
||||
@@ -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_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2));
|
||||
JS_SetPropertyStr(js, endowments, "setCursor",
|
||||
JS_NewCFunction(js, js_sdl_set_cursor, "setCursor", 1));
|
||||
JS_SetPropertyStr(js, ret, "createCursor",
|
||||
JS_NewCFunction(js, js_sdl_create_cursor, "createCursor", 2));
|
||||
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
596
source/qjs_socket.c
Normal 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
8
source/qjs_socket.h
Normal 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
94
source/qjs_text.c
Normal 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
8
source/qjs_text.h
Normal 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
211
source/qjs_utf8.c
Normal 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
8
source/qjs_utf8.h
Normal file
@@ -0,0 +1,8 @@
|
||||
#ifndef QJS_UTF8_H
|
||||
#define QJS_UTF8_H
|
||||
|
||||
#include "cell.h"
|
||||
|
||||
JSValue js_utf8_use(JSContext*);
|
||||
|
||||
#endif
|
||||
@@ -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);
|
||||
}
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeAtom(ctx, ptab[i].atom);
|
||||
atoms[non_function_count] = ptab[i].atom;
|
||||
props[non_function_count++] = prop_val;
|
||||
} else
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
263
source/quickjs-atom.h
Normal 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
370
source/quickjs-opcode.h
Normal 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
54682
source/quickjs.c
Normal file
File diff suppressed because it is too large
Load Diff
1250
source/quickjs.h
Normal file
1250
source/quickjs.h
Normal file
File diff suppressed because it is too large
Load Diff
156
source/timer.c
Normal file
156
source/timer.c
Normal 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
38
source/timer.h
Normal 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 */
|
||||
151
source/wota.h
151
source/wota.h
@@ -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;
|
||||
for (size_t i = 0; i < nwords; i++) {
|
||||
uint64_t hi = 0, lo = 0;
|
||||
if (idx < ccount) {
|
||||
hi = codepoints[idx++];
|
||||
}
|
||||
if (idx < ccount) {
|
||||
lo = codepoints[idx++];
|
||||
}
|
||||
blocks[i] = ((hi & 0xffffffffULL) << 32) | (lo & 0xffffffffULL);
|
||||
}
|
||||
memset(blocks, 0, nwords * sizeof(uint64_t));
|
||||
|
||||
free(codepoints);
|
||||
for (size_t i = 0; i < nwords; i++) {
|
||||
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);
|
||||
}
|
||||
blocks[i] = wval;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
11
tests/cat.ce
Normal 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
23
tests/chunkread.ce
Normal 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
10
tests/clock.ce
Normal 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)
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
var count = 0
|
||||
function loop()
|
||||
{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
log.console(arg)
|
||||
|
||||
$_.stop()
|
||||
122
tests/fit.ce
Normal file
122
tests/fit.ce
Normal 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
14
tests/guid.ce
Normal 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
5
tests/hang.ce
Normal file
@@ -0,0 +1,5 @@
|
||||
log.console(`Going to start hanging ...`)
|
||||
|
||||
while(1) {
|
||||
// hang!
|
||||
}
|
||||
@@ -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")
|
||||
log.console(b2.length)
|
||||
var text2 = text(b2)
|
||||
log.console(text(b2))
|
||||
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)
|
||||
log.console("dictionary error:", e)
|
||||
}
|
||||
47
tests/httpget.ce
Normal file
47
tests/httpget.ce
Normal 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
20
tests/jswota.ce
Normal 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
15
tests/kill.ce
Normal 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
51
tests/kim.ce
Normal 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!");
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
this.spawn()
|
||||
log.console("SPAWNED")
|
||||
@@ -1 +0,0 @@
|
||||
log.console("im alive")
|
||||
@@ -1,2 +0,0 @@
|
||||
for (var i = 0; i < 10; i++)
|
||||
$_.start(_ => {}, "spawnee")
|
||||
@@ -1,2 +1,2 @@
|
||||
$_.delay($_.stop, 0.1)
|
||||
log.console(`About to stop.`)
|
||||
$_.stop()
|
||||
23
tests/text_test.ce
Normal file
23
tests/text_test.ce
Normal 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
47
tests/text_utf8.ce
Normal 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();
|
||||
@@ -1,4 +1,4 @@
|
||||
$_.unneeded(_ => {
|
||||
log.console("Unneded function fired.");
|
||||
$_.unneeded($_.stop, 1);
|
||||
$_.start(undefined, "unneeded")
|
||||
}, 1);
|
||||
|
||||
70
tests/utf8.ce
Normal file
70
tests/utf8.ce
Normal 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()
|
||||
377
tests/wota.ce
377
tests/wota.ce
@@ -1,260 +1,221 @@
|
||||
var wota = use('wota');
|
||||
var os = use('os');
|
||||
/*
|
||||
* wota_test.cm – self‑contained test‑suite 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, non‑zero 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 → lower‑case 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 },
|
||||
{ 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);
|
||||
}
|
||||
var cmp = deep_compare(exp, dec)
|
||||
if (!cmp.passed) { passed = false; msgs.push(...cmp.messages) }
|
||||
}
|
||||
} catch (e) {
|
||||
passed = false;
|
||||
messages.push(`Exception thrown: ${e}`);
|
||||
}
|
||||
|
||||
results.push({ testName, passed, messages });
|
||||
} catch (e) { passed = false; msgs.push('Exception: ' + e) }
|
||||
|
||||
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()
|
||||
Reference in New Issue
Block a user