diff --git a/.cell/cell.toml b/.cell/cell.toml index e948c577..f6250888 100644 --- a/.cell/cell.toml +++ b/.cell/cell.toml @@ -1,6 +1,3 @@ -sdl_video = "main" -[dependencies] -extramath = "gitea.pockle.world/john/extramath@master" [system] ar_timer = 60 actor_memory = 0 @@ -16,4 +13,18 @@ main = true [actors.prosperon] main = true [actors.accio] -main = true \ No newline at end of file +main = true + +[compilation] +CFLAGS = "-Wno-incompatible-pointer-types -Wno-missing-braces -Wno-strict-prototypes -Wno-unused-function -Wno-int-conversion" +LDFLAGS = "-lstdc++ -lm" + +[compilation.macOS] +CFLAGS = "-x objective-c" +LDFLAGS = "-framework CoreFoundation -framework CFNetwork" + +[compilation.playdate] +CFLAGS = "-DMINIZ_NO_TIME -DTARGET_EXTENSION -DTARGET_PLAYDATE -I$PLAYDATE_SDK_PATH/C_API" + +[compilation.windows] +LDFLAGS = "-lbcrypt -lwinhttp -static-libgcc -static-libstdc++" \ No newline at end of file diff --git a/meson.build b/meson.build index d6a94a49..2fa061af 100644 --- a/meson.build +++ b/meson.build @@ -8,32 +8,15 @@ libtype = get_option('default_library') link = [] src = [] -add_project_arguments('-Wno-int-conversion', language: ['c']) - -git_tag_cmd = run_command('git', 'describe', '--tags', '--abbrev=0', check: false) -cell_version = 'unknown' -if git_tag_cmd.returncode() == 0 - cell_version = git_tag_cmd.stdout().strip() -endif - -git_commit_cmd = run_command('git', 'rev-parse', '--short', 'HEAD', check: false) -cell_commit = 'unknown' -if git_commit_cmd.returncode() == 0 - cell_commit = git_commit_cmd.stdout().strip() -endif - -# Important: pass the definitions without double-escaping quotes add_project_arguments( - '-DCELL_VERSION="' + cell_version + '"', - '-DCELL_COMMIT="' + cell_commit + '"', - language : 'c' + '-Wno-incompatible-pointer-types', + '-Wno-missing-braces', + '-Wno-strict-prototypes', + '-Wno-unused-function', + '-Wno-int-conversion', + language: 'c' ) - -add_project_arguments('-Wno-incompatible-pointer-types', language: 'c') add_project_arguments('-Wno-narrowing', language: 'cpp') -add_project_arguments('-Wno-missing-braces', language:'c') -add_project_arguments('-Wno-strict-prototypes', language:'c') -add_project_arguments('-Wno-unused-function', language: 'c') deps = [] @@ -52,24 +35,6 @@ if host_machine.system() == 'playdate' add_project_arguments('-DTARGET_PLAYDATE', language: 'c') endif -cmake = import('cmake') - -cc = meson.get_compiler('c') - -if host_machine.system() == 'linux' - deps += cc.find_library('asound', required:true) - deps += [dependency('x11'), dependency('xi'), dependency('xcursor'), dependency('egl'), dependency('gl')] -endif - -if host_machine.system() == 'windows' - deps += cc.find_library('bcrypt') - deps += cc.find_library('winhttp') - link += ['-static', '-static-libgcc', '-static-libstdc++'] - add_project_link_arguments('-static-libgcc', '-static-libstdc++', language: ['c', 'cpp']) -endif - -#link += '-rdynamic' - link_args = link sources = [] src += [ # core @@ -79,18 +44,12 @@ src += [ # core 'wildmatch.c', 'qjs_actor.c', 'qjs_wota.c', - 'miniz.c' + 'miniz.c', + 'quickjs.c', + 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c' ] -if host_machine.system() == 'playdate' - src += ['scheduler_single.c'] -elif get_option('single_threaded') - src += ['scheduler_single.c'] -else - src += ['scheduler_threaded.c'] -endif - -src += ['quickjs.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c'] +src += ['scheduler.c'] scripts = [ 'nota.c', @@ -118,12 +77,6 @@ foreach file: scripts sources += files(full_path) endforeach -if host_machine.system() == 'playdate' - sources += files('source/fd_playdate.c') -else - sources += files('scripts/fd.c') -endif - srceng = 'source' includes = [srceng] @@ -201,18 +154,3 @@ cell = custom_target('cell', install_headers('source/cell.h') install_headers('source/quickjs.h') install_headers('source/wota.h') - -tests = [ - 'spawn_actor', - 'empty', - 'nota', - 'wota', - 'portalspawner', - 'overling', - 'send', - 'delay' -] - -foreach file : tests - test(file, cell, args:['tests/' + file]) -endforeach diff --git a/scripts/build.ce b/scripts/build.ce new file mode 100644 index 00000000..02531bfa --- /dev/null +++ b/scripts/build.ce @@ -0,0 +1,319 @@ +// cell build [--target ] - Build a static cell binary for a project +// Collects all C files from cell source, scripts, and project packages, +// compiles them, and links into a single static executable. + +var build = use('build') +var shop = use('shop') +var fd = use('fd') +var os = use('os') + +var targets = [ + "arm64-macos", + "x86_64-macos", + "x86_64-linux", + "arm64-linux", + "windows", + "playdate" +] + +// Parse arguments +var target = null +for (var i = 0; i < args.length; i++) { + if (args[i] == '--target' || args[i] == '-t') { + if (i + 1 < args.length) { + target = args[i + 1] + i++ + } else { + log.error("--target requires an argument") + log.console("Available targets: " + targets.join(', ')) + $_.stop() + } + } else if (args[i] == '--help' || args[i] == '-h') { + log.console("Usage: cell build [--target ]") + log.console("Build a static cell binary for the current project.") + log.console("") + log.console("Options:") + log.console(" --target, -t Cross-compile for target platform") + log.console("") + log.console("Available targets: " + targets.join(', ')) + $_.stop() + } else if (args[i] == '--list-targets') { + log.console("Available targets:") + for (var t = 0; t < targets.length; t++) { + log.console(" " + targets[t]) + } + $_.stop() + } +} + +// Resolve target +target = build.resolve_target(target) +if (target) { + log.console("Building for target: " + target) +} else { + log.console("Building for host platform") +} + +// Find cell package - it should be at ~/work/cell or a dependency +var cell_dir = null + +// First check if we're in the cell directory itself +if (fd.is_file('source/cell.c') && fd.is_file('source/quickjs.c')) { + cell_dir = '.' + log.console("Building from cell source directory") +} else { + // Check for cell as a local path dependency or linked package + var config = shop.load_config() + if (config && config.dependencies && config.dependencies.cell) { + var pkg = config.dependencies.cell + var parsed = shop.parse_package(pkg) + var pkg_dir = '.cell/modules/' + parsed.path + if (fd.is_file(pkg_dir + '/source/cell.c')) { + cell_dir = pkg_dir + log.console("Using cell from dependency: " + pkg_dir) + } + } + + // Fallback: try ~/work/cell + if (!cell_dir) { + var home_cell = os.getenv('HOME') + '/work/cell' + if (fd.is_file(home_cell + '/source/cell.c')) { + cell_dir = home_cell + log.console("Using cell from: " + cell_dir) + } + } +} + +if (!cell_dir) { + log.error("Could not find cell source. Add cell as a dependency or run from cell directory.") + $_.stop() +} + +// Collect all C files from cell +var source_files = build.list_files(cell_dir + '/source') +var script_files = build.list_files(cell_dir + '/scripts') + +// Prefix with directory +var all_files = [] +for (var i = 0; i < source_files.length; i++) { + all_files.push('source/' + source_files[i]) +} +for (var i = 0; i < script_files.length; i++) { + all_files.push('scripts/' + script_files[i]) +} + +// Select C files for target +var c_files = build.select_c_files(all_files, target) + +// Debug: show which files were selected +if (target == 'playdate') { + for (var i = 0; i < c_files.length; i++) { + if (c_files[i].indexOf('fd') >= 0 || c_files[i].indexOf('main') >= 0) { + log.console(" Selected: " + c_files[i]) + } + } +} + +log.console("Found " + c_files.length + " C files to compile") + +// Get build directory +var build_dir = build.get_build_dir(target) +build.ensure_dir(build_dir) + +// Load cell config for platform-specific flags +var cell_config = build.load_config(cell_dir) +var target_system = build.get_target_system(target) +var platform = target_system || os.platform() + +var cflags = build.get_flags(cell_config, platform, 'CFLAGS') +var ldflags = build.get_flags(cell_config, platform, 'LDFLAGS') + +// Compile options +var compile_options = { + target: target, + cflags: cflags, + includes: [cell_dir + '/source'], + defines: {} +} + +// Add target-specific defines +if (target == 'playdate') { + compile_options.defines.TARGET_PLAYDATE = true +} + +// Compile all C files +var objects = [] +for (var i = 0; i < c_files.length; i++) { + var src = cell_dir + '/' + c_files[i] + var obj = build_dir + '/' + c_files[i] + '.o' + + // Check if recompilation needed + var needs_compile = true + if (fd.is_file(obj)) { + var src_stat = fd.stat(src) + var obj_stat = fd.stat(obj) + if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) { + needs_compile = false + } + } + + if (needs_compile) { + var result = build.compile_file(src, obj, compile_options) + if (!result) { + log.error("Build failed") + $_.stop() + } + } + objects.push(obj) +} + +// Collect C files from project packages +var packages = shop.list_packages() +for (var p = 0; p < packages.length; p++) { + var pkg = packages[p] + var parsed = shop.parse_package(pkg) + var pkg_dir = '.cell/modules/' + parsed.path + + if (!fd.is_dir(pkg_dir)) continue + + var pkg_files = build.list_files(pkg_dir) + var pkg_c_files = build.select_c_files(pkg_files, target) + + var pkg_config = build.load_config(pkg_dir) + var pkg_ldflags = build.get_flags(pkg_config, platform, 'LDFLAGS') + if (pkg_ldflags) { + if (ldflags != '') ldflags += ' ' + ldflags += pkg_ldflags + } + + if (pkg_c_files.length > 0) { + log.console("Compiling " + text(pkg_c_files.length) + " C files from " + parsed.path) + + var pkg_cflags = build.get_flags(pkg_config, platform, 'CFLAGS') + + // Create symbol prefix for package + var use_prefix = 'js_' + parsed.path.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_') + '_' + + for (var f = 0; f < pkg_c_files.length; f++) { + var src = pkg_dir + '/' + pkg_c_files[f] + var obj = build_dir + '/packages/' + parsed.path + '/' + pkg_c_files[f] + '.o' + + var safe_name = pkg_c_files[f].substring(0, pkg_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_') + var use_name = use_prefix + safe_name + '_use' + + var pkg_options = { + target: target, + cflags: pkg_cflags, + includes: [cell_dir + '/source', pkg_dir], + defines: { CELL_USE_NAME: use_name }, + module_dir: pkg_dir + } + + var needs_compile = true + if (fd.is_file(obj)) { + var src_stat = fd.stat(src) + var obj_stat = fd.stat(obj) + if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) { + needs_compile = false + } + } + + if (needs_compile) { + var result = build.compile_file(src, obj, pkg_options) + if (!result) { + log.error("Build failed") + $_.stop() + } + } + objects.push(obj) + } + } +} + +// Collect C files from local project (only if not building from cell source directory) +if (cell_dir != '.') { + var local_config = shop.load_config() || {} + var local_ldflags = build.get_flags(local_config, platform, 'LDFLAGS') + if (local_ldflags) { + if (ldflags != '') ldflags += ' ' + ldflags += local_ldflags + } + + var local_files = build.list_files('.') + var local_c_files = build.select_c_files(local_files, target) + + if (local_c_files.length > 0) { + log.console("Compiling " + text(local_c_files.length) + " local C files") + + var local_cflags = build.get_flags(local_config, platform, 'CFLAGS') + + for (var f = 0; f < local_c_files.length; f++) { + var src = local_c_files[f] + var obj = build_dir + '/local/' + local_c_files[f] + '.o' + + var safe_name = local_c_files[f].substring(0, local_c_files[f].lastIndexOf('.')).replace(/\//g, '_').replace(/-/g, '_') + var use_name = 'js_local_' + safe_name + '_use' + + var local_options = { + target: target, + cflags: local_cflags, + includes: [cell_dir + '/source', '.'], + defines: { CELL_USE_NAME: use_name } + } + + var needs_compile = true + if (fd.is_file(obj)) { + var src_stat = fd.stat(src) + var obj_stat = fd.stat(obj) + if (src_stat && obj_stat && src_stat.mtime <= obj_stat.mtime) { + needs_compile = false + } + } + + if (needs_compile) { + var result = build.compile_file(src, obj, local_options) + if (!result) { + log.error("Build failed") + $_.stop() + } + } + objects.push(obj) + } + } +} + +log.console("Compiled " + text(objects.length) + " object files") + +// Link into executable +var exe_ext = build.get_exe_ext(target) +var exe_name = build_dir + '/cell' + exe_ext + +var link_options = { + target: target, + ldflags: ldflags, + libs: [] +} + +// Add platform-specific libraries +if (!target || platform == 'macOS' || platform == 'darwin') { + // macOS needs no extra libs for static build +} else if (target == 'windows') { + link_options.libs.push('ws2_32') + link_options.libs.push('winmm') +} + +var result = build.link_executable(objects, exe_name, link_options) +if (!result) { + log.error("Linking failed") + $_.stop() +} + +// TODO: Append core.qop to executable +// For now, just report success +log.console("") +log.console("Build complete: " + exe_name) +log.console("") +log.console("Note: To create a fully functional cell binary, you need to append core.qop:") +log.console(" cat core.qop >> " + exe_name) + +$_.stop() \ No newline at end of file diff --git a/scripts/build.cm b/scripts/build.cm new file mode 100644 index 00000000..5e5fad34 --- /dev/null +++ b/scripts/build.cm @@ -0,0 +1,471 @@ +// build.cm - Build utilities for compiling Cell projects and binaries +var fd = use('fd') +var toml = use('toml') +var json = use('json') +var crypto = use('crypto') +var utf8 = use('utf8') +var os_mod = use('os') + +var Build = {} + +// Embedded cross-compilation toolchain configurations +Build.toolchains = { + playdate: { + binaries: { + c: 'arm-none-eabi-gcc', + cpp: 'arm-none-eabi-g++', + ar: 'arm-none-eabi-ar', + strip: 'arm-none-eabi-strip', + objcopy: 'arm-none-eabi-objcopy', + ld: 'arm-none-eabi-gcc' + }, + host_machine: { + system: 'playdate', + cpu_family: 'arm', + cpu: 'cortex-m7', + endian: 'little' + }, + c_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-fno-exceptions'], + c_link_args: ['-mcpu=cortex-m7', '-mthumb', '-mfloat-abi=hard', '-mfpu=fpv5-sp-d16', '-nostartfiles', "-T/Users/john/Developer/PlaydateSDK/C_API/buildsupport/link_map.ld"] + }, + windows: { + binaries: { + c: 'x86_64-w64-mingw32-gcc', + cpp: 'x86_64-w64-mingw32-g++', + ar: 'x86_64-w64-mingw32-ar', + windres: 'x86_64-w64-mingw32-windres', + strip: 'x86_64-w64-mingw32-strip' + }, + host_machine: { + system: 'windows', + cpu_family: 'x86_64', + cpu: 'x86_64', + endian: 'little' + }, + c_args: [], + c_link_args: [] + }, + linux: { + binaries: { + c: 'zig cc -target x86_64-linux-musl', + cpp: 'zig c++ -target x86_64-linux-musl', + ar: 'zig ar', + strip: 'strip' + }, + host_machine: { + system: 'linux', + cpu_family: 'x86_64', + cpu: 'x86_64', + endian: 'little' + }, + c_args: [], + c_link_args: [] + } +} + +// Target aliases for convenience +Build.target_aliases = { + 'win': 'windows', + 'win64': 'windows', + 'mingw': 'windows', + 'pd': 'playdate' +} + +// Resolve target name (handle aliases) +Build.resolve_target = function(target) { + if (!target) return null + target = target.toLowerCase() + return Build.target_aliases[target] || target +} + +// Get toolchain for a target (null = host) +Build.get_toolchain = function(target) { + if (!target) return null + target = Build.resolve_target(target) + return Build.toolchains[target] || null +} + +// Get compiler command for target +Build.get_cc = function(target) { + var tc = Build.get_toolchain(target) + if (tc && tc.binaries && tc.binaries.c) return tc.binaries.c + return 'cc' +} + +// Get archiver command for target +Build.get_ar = function(target) { + var tc = Build.get_toolchain(target) + if (tc && tc.binaries && tc.binaries.ar) return tc.binaries.ar + return 'ar' +} + +// Get extra C flags for target +Build.get_target_cflags = function(target) { + var tc = Build.get_toolchain(target) + if (tc && tc.c_args) return tc.c_args.join(' ') + return '' +} + +// Get extra link flags for target +Build.get_target_ldflags = function(target) { + var tc = Build.get_toolchain(target) + if (tc && tc.c_link_args) return tc.c_link_args.join(' ') + return '' +} + +// Get target system name +Build.get_target_system = function(target) { + var tc = Build.get_toolchain(target) + if (tc && tc.host_machine && tc.host_machine.system) return tc.host_machine.system + return null +} + +// Get executable extension for target +Build.get_exe_ext = function(target) { + var sys = Build.get_target_system(target) + if (sys == 'windows') return '.exe' + return '' +} + +// Get shared library extension for target +Build.get_dylib_ext = function(target) { + var sys = Build.get_target_system(target) + if (sys == 'windows') return '.dll' + if (sys == 'darwin' || sys == 'macOS') return '.dylib' + return '.so' +} + +// Ensure directory exists +function ensure_dir(path) { + if (fd.stat(path).isDirectory) return true + var parts = path.split('/') + var current = '' + for (var i = 0; i < parts.length; i++) { + if (parts[i] == '') continue + current += parts[i] + '/' + if (!fd.stat(current).isDirectory) { + fd.mkdir(current) + } + } + return true +} + +Build.ensure_dir = ensure_dir + +// Get hash of a string +function get_hash(str) { + return text(crypto.blake2(utf8.encode(str)), 'h') +} + +Build.get_hash = get_hash + +// List all files in a directory recursively +function list_files_recursive(dir, prefix, results) { + prefix = prefix || "" + results = results || [] + + var list = fd.readdir(dir) + if (!list) return results + + for (var i = 0; i < list.length; i++) { + var item = list[i] + if (item == '.' || item == '..') continue + if (item.startsWith('.')) continue + + var full_path = dir + "/" + item + var rel_path = prefix ? prefix + "/" + item : item + + var st = fd.stat(full_path) + if (st.isDirectory) { + // Skip build directories + if (item == 'build' || item.startsWith('build_')) continue + list_files_recursive(full_path, rel_path, results) + } else { + results.push(rel_path) + } + } + return results +} + +Build.list_files = list_files_recursive + +// Select C files for a target, handling target-specific variants +// e.g., if target is 'playdate', prefer fd_playdate.c over fd.c +// Platform-specific files can be in any directory (source/ or scripts/) +Build.select_c_files = function(files, target) { + var c_files = [] + var target_suffix = target ? '_' + target : null + + // Known target suffixes for platform-specific files + var known_targets = ['playdate', 'windows', 'linux', 'macos', 'threaded', 'single'] + + // First pass: collect all files and identify platform-specific ones + // Group by generic name (ignoring directory) to find cross-directory variants + var name_groups = {} // generic_name+ext -> { generics: [], variants: { target: file } } + + for (var i = 0; i < files.length; i++) { + var file = files[i] + if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue + + var ext = file.endsWith('.cpp') ? '.cpp' : '.c' + var base = file.substring(0, file.length - ext.length) + var dir = '' + var name = base + var slash = base.lastIndexOf('/') + if (slash >= 0) { + dir = base.substring(0, slash + 1) + name = base.substring(slash + 1) + } + + // Check if this is a target-specific file + var is_target_specific = false + var target_name = null + var generic_name = name + + for (var t = 0; t < known_targets.length; t++) { + var suffix = '_' + known_targets[t] + if (name.endsWith(suffix)) { + is_target_specific = true + target_name = known_targets[t] + generic_name = name.substring(0, name.length - suffix.length) + break + } + } + + // Group key is just the generic name + extension (ignoring directory) + // This allows source/fd_playdate.c to override scripts/fd.c + var group_key = generic_name + ext + if (!name_groups[group_key]) { + name_groups[group_key] = { generics: [], variants: {} } + } + + if (is_target_specific) { + // Platform-specific file - store by target name + name_groups[group_key].variants[target_name] = file + } else { + // Generic file - could have multiple in different directories + name_groups[group_key].generics.push(file) + } + } + + // Second pass: select appropriate file from each group + for (var key in name_groups) { + var group = name_groups[key] + var selected = null + + // If we have a target, check for target-specific variant first + if (target && group.variants[target]) { + selected = group.variants[target] + } else if (group.generics.length > 0) { + // Use generic if no target-specific variant + // If multiple generics exist (shouldn't happen normally), use first one + selected = group.generics[0] + } else { + // No generic, only variants exist + // This handles cases like scheduler_threaded.c vs scheduler_playdate.c + // where there's no generic scheduler.c + if (target) { + // Only include if it's for our target + if (group.variants[target]) { + selected = group.variants[target] + } + } else { + // No target specified, prefer 'threaded' variant as default + if (group.variants['threaded']) { + selected = group.variants['threaded'] + } + } + } + + if (selected) { + c_files.push(selected) + } + } + + return c_files +} + +// Get build directory for a target +Build.get_build_dir = function(target) { + if (!target) return '.cell/build/static' + return '.cell/build/' + target +} + +// Compile a single C file +// Returns object path on success, null on failure +Build.compile_file = function(src_path, obj_path, options) { + options = options || {} + var target = options.target + var cflags = options.cflags || '' + var includes = options.includes || [] + var defines = options.defines || {} + var module_dir = options.module_dir || '.' + + var cc = Build.get_cc(target) + var target_cflags = Build.get_target_cflags(target) + + ensure_dir(obj_path.substring(0, obj_path.lastIndexOf('/'))) + + var full_cmd + + if (module_dir != '.') { + // If compiling in module dir, we need to adjust paths + + // Adjust includes - prefix with $HERE if relative + var include_str = '' + for (var i = 0; i < includes.length; i++) { + var inc = includes[i] + if (!inc.startsWith('/')) { + inc = '"$HERE/' + inc + '"' + } + include_str += ' -I' + inc + } + + var define_str = '' + for (var k in defines) { + if (defines[k] == true) { + define_str += ' -D' + k + } else { + define_str += ' -D' + k + '=' + defines[k] + } + } + + var compile_flags = ' -O3' + include_str + define_str + if (target_cflags) compile_flags += ' ' + target_cflags + if (cflags) compile_flags += ' ' + cflags + + // Adjust source path relative to module dir + var src_file = src_path + if (src_path.startsWith(module_dir + '/')) { + src_file = src_path.substring(module_dir.length + 1) + } else if (!src_path.startsWith('/')) { + src_file = '"$HERE/' + src_path + '"' + } + + // Adjust output path to be absolute/relative to HERE + var out_file = obj_path + if (!out_file.startsWith('/')) { + out_file = '"$HERE/' + out_file + '"' + } + + var cc_cmd = cc + ' -c' + compile_flags + ' ' + src_file + ' -o ' + out_file + full_cmd = 'HERE=$(pwd); cd ' + module_dir + ' && ' + cc_cmd + } else { + // Standard compilation from current dir + var include_str = '' + for (var i = 0; i < includes.length; i++) { + include_str += ' -I' + includes[i] + } + + var define_str = '' + for (var k in defines) { + if (defines[k] == true) { + define_str += ' -D' + k + } else { + define_str += ' -D' + k + '=' + defines[k] + } + } + + var base_cmd = cc + ' -c' + var compile_flags = ' -O3' + include_str + define_str + if (target_cflags) compile_flags += ' ' + target_cflags + if (cflags) compile_flags += ' ' + cflags + + full_cmd = base_cmd + compile_flags + ' ' + src_path + ' -o ' + obj_path + } + + log.console("Compiling " + src_path) + var ret = os_mod.system(full_cmd) + if (ret != 0) { + log.error("Compilation failed: " + src_path) + return null + } + + return obj_path +} + +// Link object files into a static library +Build.link_static = function(objects, output, options) { + options = options || {} + var target = options.target + + var ar = Build.get_ar(target) + + ensure_dir(output.substring(0, output.lastIndexOf('/'))) + + var objs_str = objects.join(' ') + var cmd = ar + ' rcs ' + output + ' ' + objs_str + + log.console("Creating static library " + output) + var ret = os_mod.system(cmd) + if (ret != 0) { + log.error("Archiving failed") + return null + } + + return output +} + +// Link object files into an executable +Build.link_executable = function(objects, output, options) { + options = options || {} + var target = options.target + var ldflags = options.ldflags || '' + var libs = options.libs || [] + + var cc = Build.get_cc(target) + var target_ldflags = Build.get_target_ldflags(target) + var exe_ext = Build.get_exe_ext(target) + + if (!output.endsWith(exe_ext)) { + output = output + exe_ext + } + + ensure_dir(output.substring(0, output.lastIndexOf('/'))) + + var objs_str = objects.join(' ') + var libs_str = '' + for (var i = 0; i < libs.length; i++) { + libs_str += ' -l' + libs[i] + } + + var link_flags = '' + if (target_ldflags) link_flags += ' ' + target_ldflags + if (ldflags) link_flags += ' ' + ldflags + + var cmd = cc + ' ' + objs_str + libs_str + link_flags + ' -o ' + output + + log.console("Linking " + output) + var ret = os_mod.system(cmd) + if (ret != 0) { + log.error("Linking failed") + log.error(cmd) + return null + } + + return output +} + +// Get flags from config for a platform +Build.get_flags = function(config, platform, key) { + var flags = '' + if (config.compilation && config.compilation[key]) { + flags += config.compilation[key] + } + if (config.compilation && config.compilation[platform] && config.compilation[platform][key]) { + if (flags != '') flags += ' ' + flags += config.compilation[platform][key] + } + return flags +} + +// Load config from a directory +Build.load_config = function(dir) { + var path = dir + '/.cell/cell.toml' + if (!fd.is_file(path)) return {} + var content = fd.slurp(path) + if (!content || !content.length) return {} + return toml.decode(text(content)) +} + +return Build \ No newline at end of file diff --git a/scripts/enet_playdate.c b/scripts/enet_playdate.c new file mode 100644 index 00000000..9703146c --- /dev/null +++ b/scripts/enet_playdate.c @@ -0,0 +1,9 @@ +// enet_playdate.c - ENet stub for Playdate +// ENet networking is not supported on Playdate, so this module returns an empty object. + +#include "cell.h" + +JSValue js_enet_use(JSContext *js) { + // Return empty object - ENet is not available on Playdate + return JS_NewObject(js); +} diff --git a/source/fd_playdate.c b/scripts/fd_playdate.c similarity index 91% rename from source/fd_playdate.c rename to scripts/fd_playdate.c index ae2d72f4..897923f2 100644 --- a/source/fd_playdate.c +++ b/scripts/fd_playdate.c @@ -4,66 +4,10 @@ #include #include -// --- Playdate File API Definitions --- +#include "pd_api.h" -#ifndef pdext_file_h -#define pdext_file_h - -#if TARGET_EXTENSION - -typedef void SDFile; - -typedef enum -{ - kFileRead = (1<<0), - kFileReadData = (1<<1), - kFileWrite = (1<<2), - kFileAppend = (2<<2) -} FileOptions; - -typedef struct -{ - int isdir; - unsigned int size; - int m_year; - int m_month; - int m_day; - int m_hour; - int m_minute; - int m_second; -} FileStat; - -#endif - -#if !defined(SEEK_SET) -# define SEEK_SET 0 /* Seek from beginning of file. */ -# define SEEK_CUR 1 /* Seek from current position. */ -# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ -#endif - -struct playdate_file -{ - const char* (*geterr)(void); - - int (*listfiles)(const char* path, void (*callback)(const char* path, void* userdata), void* userdata, int showhidden); - int (*stat)(const char* path, FileStat* stat); - int (*mkdir)(const char* path); - int (*unlink)(const char* name, int recursive); - int (*rename)(const char* from, const char* to); - - SDFile* (*open)(const char* name, FileOptions mode); - int (*close)(SDFile* file); - int (*read)(SDFile* file, void* buf, unsigned int len); - int (*write)(SDFile* file, const void* buf, unsigned int len); - int (*flush)(SDFile* file); - int (*tell)(SDFile* file); - int (*seek)(SDFile* file, int pos, int whence); -}; - -#endif /* pdext_file_h */ - -// Assumed global pointer to the file API -extern struct playdate_file* pd_file; +// Global Playdate API pointers - defined in main_playdate.c +extern const struct playdate_file *pd_file; // --- Helper Functions --- diff --git a/scripts/http_playdate.c b/scripts/http_playdate.c new file mode 100644 index 00000000..869858d0 --- /dev/null +++ b/scripts/http_playdate.c @@ -0,0 +1,248 @@ +// http_playdate.c - HTTP module for Playdate using Playdate Network API +// Note: Playdate HTTP does not support SSL/HTTPS + +#include "cell.h" +#include "pd_api.h" +#include +#include + +// Global Playdate API pointers - defined in main_playdate.c +extern const struct playdate_network *pd_network; +extern const struct playdate_sys *pd_sys; + +#if TARGET_EXTENSION + +// Context for async HTTP fetch +typedef struct { + JSContext *js; + HTTPConnection *conn; + uint8_t *data; + size_t data_len; + size_t data_cap; + int complete; + int success; + int status_code; +} http_fetch_ctx; + +static void http_response_callback(HTTPConnection *conn) { + http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn); + if (!ctx) return; + + ctx->status_code = pd_network->http->getResponseStatus(conn); + + // Read all available data + while (1) { + size_t avail = pd_network->http->getBytesAvailable(conn); + if (avail == 0) break; + + // Grow buffer if needed + if (ctx->data_len + avail > ctx->data_cap) { + size_t new_cap = ctx->data_cap * 2; + if (new_cap < ctx->data_len + avail) new_cap = ctx->data_len + avail + 4096; + uint8_t *new_data = realloc(ctx->data, new_cap); + if (!new_data) { + ctx->success = 0; + ctx->complete = 1; + return; + } + ctx->data = new_data; + ctx->data_cap = new_cap; + } + + int read = pd_network->http->read(conn, ctx->data + ctx->data_len, (unsigned int)avail); + if (read < 0) { + ctx->success = 0; + ctx->complete = 1; + return; + } + ctx->data_len += read; + } +} + +static void http_complete_callback(HTTPConnection *conn) { + http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn); + if (!ctx) return; + + // Read any remaining data + http_response_callback(conn); + + ctx->success = (ctx->status_code >= 200 && ctx->status_code < 400); + ctx->complete = 1; +} + +static void http_closed_callback(HTTPConnection *conn) { + http_fetch_ctx *ctx = (http_fetch_ctx *)pd_network->http->getUserdata(conn); + if (!ctx) return; + ctx->complete = 1; +} + +// Parse URL into host, port, path, and check if HTTPS +static int parse_url(const char *url, char **host, int *port, char **path, int *is_https) { + *host = NULL; + *path = NULL; + *port = 80; + *is_https = 0; + + const char *p = url; + + // Check scheme + if (strncmp(p, "https://", 8) == 0) { + *is_https = 1; + *port = 443; + p += 8; + } else if (strncmp(p, "http://", 7) == 0) { + p += 7; + } else { + return -1; // Invalid scheme + } + + // Find end of host (either :, /, or end of string) + const char *host_start = p; + const char *host_end = p; + while (*host_end && *host_end != ':' && *host_end != '/') host_end++; + + size_t host_len = host_end - host_start; + *host = malloc(host_len + 1); + if (!*host) return -1; + memcpy(*host, host_start, host_len); + (*host)[host_len] = '\0'; + + p = host_end; + + // Check for port + if (*p == ':') { + p++; + *port = atoi(p); + while (*p && *p != '/') p++; + } + + // Get path (default to "/" if none) + if (*p == '/') { + *path = strdup(p); + } else { + *path = strdup("/"); + } + + if (!*path) { + free(*host); + *host = NULL; + return -1; + } + + return 0; +} + +// Performs a blocking HTTP GET and returns a QuickJS Blob of the body +static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + if (argc < 1 || !JS_IsString(argv[0])) + return JS_ThrowTypeError(ctx, "fetch: URL string required"); + + if (!pd_network || !pd_network->http) { + return JS_ThrowInternalError(ctx, "fetch: Playdate network API not available"); + } + + const char *url = JS_ToCString(ctx, argv[0]); + if (!url) return JS_ThrowTypeError(ctx, "fetch: invalid URL"); + + char *host = NULL; + char *path = NULL; + int port = 80; + int is_https = 0; + + if (parse_url(url, &host, &port, &path, &is_https) < 0) { + JS_FreeCString(ctx, url); + return JS_ThrowTypeError(ctx, "fetch: failed to parse URL"); + } + + JS_FreeCString(ctx, url); + + // Playdate doesn't support HTTPS + if (is_https) { + free(host); + free(path); + return JS_ThrowTypeError(ctx, "fetch: HTTPS not supported on Playdate"); + } + + // Create HTTP connection + HTTPConnection *conn = pd_network->http->newConnection(host, port, 0); + free(host); + + if (!conn) { + free(path); + return JS_ThrowInternalError(ctx, "fetch: failed to create connection"); + } + + // Set up context + http_fetch_ctx fetch_ctx = {0}; + fetch_ctx.js = ctx; + fetch_ctx.conn = conn; + fetch_ctx.data = malloc(4096); + fetch_ctx.data_cap = 4096; + fetch_ctx.data_len = 0; + fetch_ctx.complete = 0; + fetch_ctx.success = 0; + + if (!fetch_ctx.data) { + pd_network->http->release(conn); + free(path); + return JS_ThrowInternalError(ctx, "fetch: malloc failed"); + } + + pd_network->http->setUserdata(conn, &fetch_ctx); + pd_network->http->setResponseCallback(conn, http_response_callback); + pd_network->http->setRequestCompleteCallback(conn, http_complete_callback); + pd_network->http->setConnectionClosedCallback(conn, http_closed_callback); + pd_network->http->setConnectTimeout(conn, 30000); // 30 second timeout + pd_network->http->setReadTimeout(conn, 30000); + + // Start the GET request + PDNetErr err = pd_network->http->get(conn, path, NULL, 0); + free(path); + + if (err != NET_OK) { + free(fetch_ctx.data); + pd_network->http->release(conn); + return JS_ThrowInternalError(ctx, "fetch: request failed with error %d", err); + } + + // Poll until complete (blocking) + // Note: This is a simple blocking implementation. In a real game, + // you'd want to use async callbacks instead. + while (!fetch_ctx.complete) { + // Small delay to avoid busy-waiting + pd_sys->delay(10); + } + + pd_network->http->close(conn); + pd_network->http->release(conn); + + if (!fetch_ctx.success) { + free(fetch_ctx.data); + return JS_ThrowTypeError(ctx, "fetch: request failed (status %d)", fetch_ctx.status_code); + } + + // Return a Blob wrapping the data + JSValue blob = js_new_blob_stoned_copy(ctx, fetch_ctx.data, fetch_ctx.data_len); + free(fetch_ctx.data); + return blob; +} + +#else +// Simulator/non-extension build - provide stub +static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) { + return JS_ThrowInternalError(ctx, "fetch: not available in simulator"); +} +#endif + +static const JSCFunctionListEntry js_http_funcs[] = { + JS_CFUNC_DEF("fetch", 2, js_fetch_playdate), +}; + +JSValue js_http_use(JSContext *js) { + JSValue obj = JS_NewObject(js); + JS_SetPropertyFunctionList(js, obj, js_http_funcs, + sizeof(js_http_funcs)/sizeof(js_http_funcs[0])); + return obj; +} diff --git a/scripts/os.c b/scripts/os.c index 8ed15301..4ee9cd8c 100644 --- a/scripts/os.c +++ b/scripts/os.c @@ -537,6 +537,17 @@ JSC_CCALL(os_random, return JS_NewFloat64(js, cell_random_fit()); ) +JSC_CCALL(os_getenv, + const char *name = JS_ToCString(js, argv[0]); + if (!name) return JS_EXCEPTION; + const char *value = getenv(name); + JS_FreeCString(js, name); + if (value) + ret = JS_NewString(js, value); + else + ret = JS_NULL; +) + static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, platform, 0), MIST_FUNC_DEF(os, arch, 0), @@ -557,6 +568,7 @@ static const JSCFunctionListEntry js_os_funcs[] = { MIST_FUNC_DEF(os, internal_exists, 1), MIST_FUNC_DEF(os, print, 1), MIST_FUNC_DEF(os, random, 0), + MIST_FUNC_DEF(os, getenv, 1), }; JSValue js_os_use(JSContext *js) { diff --git a/scripts/os_playdate.c b/scripts/os_playdate.c new file mode 100644 index 00000000..c7493c20 --- /dev/null +++ b/scripts/os_playdate.c @@ -0,0 +1,178 @@ +// os_playdate.c - OS module for Playdate +// Provides system information and utilities for Playdate platform + +#include "cell.h" +#include "pd_api.h" +#include +#include + +// Global Playdate API pointers - defined in main_playdate.c +extern const struct playdate_sys *pd_sys; + +// cell_ns - nanoseconds since some epoch (used by scheduler) +uint64_t cell_ns(void) +{ + // Playdate provides milliseconds, convert to nanoseconds + if (pd_sys) { + unsigned int ms = pd_sys->getCurrentTimeMilliseconds(); + return (uint64_t)ms * 1000000ULL; + } + return 0; +} + +// cell_sleep - sleep for specified seconds +void cell_sleep(double seconds) +{ + if (pd_sys) { + pd_sys->delay((uint32_t)(seconds * 1000)); + } +} + +JSC_CCALL(os_now, + return number2js(js, cell_ns()); +) + +JSC_CCALL(os_sleep, + double secs = js2number(js, argv[0]); + cell_sleep(secs); +) + +static JSValue js_os_totalmem(JSContext *js, JSValue self, int argc, JSValue *argv) { + // Playdate has 16MB RAM, but we don't have direct access to memory stats + return JS_NewInt64(js, 16); // 16 MB +} + +static JSValue js_os_platform(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_NewString(js, "Playdate"); +} + +static JSValue js_os_hostname(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_NewString(js, "playdate"); +} + +static JSValue js_os_arch(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_NewString(js, "arm"); +} + +static JSValue js_os_freemem(JSContext *js, JSValue self, int argc, JSValue *argv) { + // No way to get free memory on Playdate + return JS_NewInt64(js, 0); +} + +static JSValue js_os_version(JSContext *js, JSValue self, int argc, JSValue *argv) { +#if TARGET_EXTENSION + if (pd_sys) { + const struct PDInfo *info = pd_sys->getSystemInfo(); + if (info) { + char buf[32]; + snprintf(buf, sizeof(buf), "%u", info->osversion); + return JS_NewString(js, buf); + } + } +#endif + return JS_NewString(js, "unknown"); +} + +JSC_CCALL(os_mallinfo, + ret = JS_NewObject(js); +) + +static JSValue js_os_rusage(JSContext *js, JSValue self, int argc, JSValue *argv) { + // No rusage on Playdate + return JS_NewObject(js); +} + +JSC_SCALL(os_system, + // system() not available on Playdate + ret = JS_NewInt32(js, -1); +) + +JSC_CCALL(os_exit, + // Can't really exit on Playdate, but we can try + exit(0); +) + +// dylib functions - not supported on Playdate +static JSValue js_os_dylib_open(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_ThrowInternalError(js, "dylib_open: not supported on Playdate"); +} + +static JSValue js_os_dylib_symbol(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_ThrowInternalError(js, "dylib_symbol: not supported on Playdate"); +} + +static JSValue js_os_dylib_has_symbol(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_NewBool(js, 0); +} + +JSC_CCALL(os_print, + size_t len; + const char *str = JS_ToCStringLen(js, &len, argv[0]); + if (pd_sys) { + pd_sys->logToConsole("%.*s", (int)len, str); + } + JS_FreeCString(js, str); +) + +static JSValue js_os_load_internal(JSContext *js, JSValue self, int argc, JSValue *argv) { + // No dlsym on Playdate - internal modules are linked statically + return JS_NULL; +} + +static JSValue js_os_internal_exists(JSContext *js, JSValue self, int argc, JSValue *argv) { + return JS_NewBool(js, 0); +} + +// Random number generation +int randombytes(void *buf, size_t n) { + // Playdate doesn't have a crypto RNG, use a simple PRNG seeded from time + // This is NOT cryptographically secure! + uint8_t *p = (uint8_t *)buf; + static uint32_t seed = 0; + if (seed == 0 && pd_sys) { + seed = pd_sys->getCurrentTimeMilliseconds(); + } + for (size_t i = 0; i < n; i++) { + seed = seed * 1103515245 + 12345; + p[i] = (uint8_t)(seed >> 16); + } + return 0; +} + +JSC_CCALL(os_random, + return JS_NewFloat64(js, cell_random_fit()); +) + +JSC_CCALL(os_getenv, + // No environment variables on Playdate + return JS_NULL; +) + +static const JSCFunctionListEntry js_os_funcs[] = { + MIST_FUNC_DEF(os, platform, 0), + MIST_FUNC_DEF(os, arch, 0), + MIST_FUNC_DEF(os, totalmem, 0), + MIST_FUNC_DEF(os, freemem, 0), + MIST_FUNC_DEF(os, hostname, 0), + MIST_FUNC_DEF(os, version, 0), + MIST_FUNC_DEF(os, now, 0), + MIST_FUNC_DEF(os, rusage, 0), + MIST_FUNC_DEF(os, mallinfo, 0), + MIST_FUNC_DEF(os, system, 1), + MIST_FUNC_DEF(os, exit, 0), + MIST_FUNC_DEF(os, sleep, 1), + MIST_FUNC_DEF(os, dylib_open, 1), + MIST_FUNC_DEF(os, dylib_symbol, 2), + MIST_FUNC_DEF(os, dylib_has_symbol, 2), + MIST_FUNC_DEF(os, load_internal, 1), + MIST_FUNC_DEF(os, internal_exists, 1), + MIST_FUNC_DEF(os, print, 1), + MIST_FUNC_DEF(os, random, 0), + MIST_FUNC_DEF(os, getenv, 1), +}; + +JSValue js_os_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_os_funcs, countof(js_os_funcs)); + return mod; +} diff --git a/scripts/shop.cm b/scripts/shop.cm index c28f2c97..0b53bd60 100644 --- a/scripts/shop.cm +++ b/scripts/shop.cm @@ -8,6 +8,7 @@ var js = use('js') var crypto = use('crypto') var utf8 = use('utf8') var blob = use('blob') +var build_utils = use('build') var qop var core_qop @@ -203,20 +204,8 @@ Shop.get_c_symbol = function get_c_symbol(name) { return os.load_internal(symname) } -function ensure_dir(path) { - if (fd.stat(path).isDirectory) return true - - var parts = path.split('/') - var current = '' - for (var i = 0; i < parts.length; i++) { - if (parts[i] == '') continue - current += parts[i] + '/' - if (!fd.stat(current).isDirectory) { - fd.mkdir(current) - } - } - return true -} +// Use ensure_dir from build_utils +var ensure_dir = build_utils.ensure_dir Shop.load_config = function(module) { var content @@ -680,6 +669,19 @@ function resolve_c_symbol(path, package_context) local = null // handled via candidates below } + // First check for statically linked/internal symbols + var local_candidates = package_context ? symbol_candidates(local_path, local_sym_base) : [local] + for (var li = 0; li < local_candidates.length; li++) { + var lc = local_candidates[li] + if (os.internal_exists(lc)) + return { + symbol: function() { return os.load_internal(lc); }, + scope: SCOPE_LOCAL, + path: lc + }; + } + + // Then try dynamic library var build_dir = get_build_dir(package_context) var local_dl_name = build_dir + '/cellmod' + dylib_ext @@ -701,29 +703,30 @@ function resolve_c_symbol(path, package_context) } } - // Try static linking fallback - var local_candidates = package_context ? symbol_candidates(local_path, local_sym_base) : [local] - for (var li = 0; li < local_candidates.length; li++) { - var lc = local_candidates[li] - if (os.internal_exists(lc)) - return { - symbol: function() { return os.load_internal(lc); }, - scope: SCOPE_LOCAL, - path: lc - }; - } - // If 'path' has a package alias (e.g. 'prosperon/sprite'), try to resolve it var pkg_alias = get_import_package(path) if (pkg_alias) { var canon_pkg = get_normalized_package(path, package_context) if (canon_pkg) { - var build_dir = get_build_dir(canon_pkg) - var dl_path = build_dir + '/cellmod' + dylib_ext + var pkg_build_dir = get_build_dir(canon_pkg) + var dl_path = pkg_build_dir + '/cellmod' + dylib_ext var mod_name = get_import_name(path) var mod_sym = mod_name.replace(/\//g, '_').replace(/-/g, '_').replace(/\./g, '_') var sym_names = symbol_candidates(canon_pkg, mod_sym) + // First check internal/static symbols for package + for (var sii = 0; sii < sym_names.length; sii++) { + var sym_name = sym_names[sii] + if (os.internal_exists(sym_name)) + return { + symbol: function() { return os.load_internal(sym_name) }, + scope: SCOPE_PACKAGE, + package: canon_pkg, + path: sym_name + }; + } + + // Then try dynamic library for package if (fd.is_file(dl_path)) { if (!open_dls[dl_path]) open_dls[dl_path] = os.dylib_open(dl_path) if (open_dls[dl_path]) { @@ -739,19 +742,9 @@ function resolve_c_symbol(path, package_context) } } } - - for (var sii = 0; sii < sym_names.length; sii++) { - var sym_name = sym_names[sii] - if (os.internal_exists(sym_name)) - return { - symbol: function() { return os.load_internal(sym_name) }, - scope: SCOPE_PACKAGE, - package: canon_pkg, - path: sym_name - }; - } } } + var core_sym = `js_${path.replace(/\//g, '_')}_use`; if (os.internal_exists(core_sym)) return { @@ -1312,9 +1305,8 @@ Shop.build_package = function(package) var c_objects = [] - function get_hash(str) { - return text(crypto.blake2(utf8.encode(str)), 'h') - } + // Use get_hash from build_utils + var get_hash = build_utils.get_hash for (var i=0; igetSecondsSinceEpoch(&ms); + return (double)secs + ms / 1000.0; + } + return 0.0; +} + +/* ---------------------------------------------------------------- *\ + JS bindings +\* ---------------------------------------------------------------- */ + +static JSValue +js_time_now(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + return JS_NewFloat64(ctx, playdate_now()); +} + +static JSValue +js_time_computer_dst(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ + // Playdate doesn't provide DST info directly + return JS_NewBool(ctx, 0); +} + +static JSValue +js_time_computer_zone(JSContext *ctx, JSValueConst this_val, + int argc, JSValueConst *argv) +{ +#if TARGET_EXTENSION + if (pd_sys) { + int32_t offset = pd_sys->getTimezoneOffset(); + // Playdate returns offset in seconds, convert to hours + return JS_NewFloat64(ctx, (double)offset / 3600.0); + } +#endif + return JS_NewFloat64(ctx, 0.0); +} + +/* ---------------------------------------------------------------- *\ + registration +\* ---------------------------------------------------------------- */ + +static const JSCFunctionListEntry js_time_funcs[] = { + JS_CFUNC_DEF("now", 0, js_time_now), + JS_CFUNC_DEF("computer_dst", 0, js_time_computer_dst), + JS_CFUNC_DEF("computer_zone", 0, js_time_computer_zone), +}; + +JSValue +js_time_use(JSContext *ctx) +{ + JSValue obj = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, obj, + js_time_funcs, + sizeof(js_time_funcs) / + sizeof(js_time_funcs[0])); + return obj; +} diff --git a/source/cell.c b/source/cell.c index 6fa3efd0..fcff4276 100644 --- a/source/cell.c +++ b/source/cell.c @@ -201,6 +201,7 @@ void script_startup(cell_rt *prt) static void signal_handler(int sig) { const char *str = NULL; +#ifndef TARGET_PLAYDATE switch (sig) { case SIGABRT: str = "SIGABRT"; break; case SIGFPE: str = "SIGFPE"; break; @@ -209,6 +210,7 @@ static void signal_handler(int sig) case SIGSEGV: str = "SIGSEGV"; break; case SIGTERM: str = "SIGTERM"; break; } +#endif if (!str) return; exit_handler(); @@ -243,12 +245,12 @@ int cell_init(int argc, char **argv) actor_initialize(); root_cell = create_actor(startwota.data); - +#ifndef TARGET_PLAYDATE signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); signal(SIGSEGV, signal_handler); signal(SIGABRT, signal_handler); - +#endif actor_loop(); return 0; diff --git a/source/main_playdate.c b/source/main_playdate.c new file mode 100644 index 00000000..90413d1c --- /dev/null +++ b/source/main_playdate.c @@ -0,0 +1,49 @@ +// main_playdate.c - Playdate entry point for Cell runtime +// This file provides the eventHandler entry point required by Playdate SDK +// and initializes the global Playdate API pointers used by other modules. + +#include "cell.h" +#include "pd_api.h" + +// Global Playdate API pointers - used by fd_playdate.c, http_playdate.c, etc. +PlaydateAPI *pd = NULL; +const struct playdate_file *pd_file = NULL; +const struct playdate_sys *pd_sys = NULL; +const struct playdate_network *pd_network = NULL; + +// Forward declaration +extern int cell_init(int argc, char **argv); + +// Playdate update callback +static int update(void *userdata) +{ + // The Cell runtime uses its own event loop, so we just return 1 to continue + return 1; +} + +// Playdate event handler - main entry point +#ifdef _WINDLL +__declspec(dllexport) +#endif +int eventHandler(PlaydateAPI *playdate, PDSystemEvent event, uint32_t arg) +{ + (void)arg; + + if (event == kEventInit) { + // Store global API pointers + pd = playdate; + pd_file = playdate->file; + pd_sys = playdate->system; + pd_network = playdate->network; + + // Set up the update callback + pd_sys->setUpdateCallback(update, NULL); + + // Initialize Cell runtime with no arguments + // On Playdate, we'll look for main.ce in the data folder + char *argv[] = { "cell", "main.ce", NULL }; + cell_init(2, argv); + } + + return 0; +} diff --git a/source/quickjs-libc.c b/source/quickjs-libc.c deleted file mode 100644 index 4b9d0ae5..00000000 --- a/source/quickjs-libc.c +++ /dev/null @@ -1,4344 +0,0 @@ -/* - * QuickJS C library - * - * Copyright (c) 2017-2021 Fabrice Bellard - * Copyright (c) 2017-2021 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(_WIN32) -#include -#include -#include -#else -#include -#include -#include -#include - -#if defined(__FreeBSD__) -extern char **environ; -#endif - -#if defined(__APPLE__) || defined(__FreeBSD__) -typedef sig_t sighandler_t; -#endif - -#if defined(__APPLE__) -#if !defined(environ) -#include -#define environ (*_NSGetEnviron()) -#endif -#endif /* __APPLE__ */ - -#endif - -/* enable the os.Worker API. It relies on POSIX threads */ -#define USE_WORKER - -#ifdef USE_WORKER -#include -#include -#endif - -#include "cutils.h" -#include "list.h" -#include "quickjs-libc.h" - -#if !defined(PATH_MAX) -#define PATH_MAX 4096 -#endif - -/* TODO: - - add socket calls -*/ - -typedef struct { - struct list_head link; - int fd; - JSValue rw_func[2]; -} JSOSRWHandler; - -typedef struct { - struct list_head link; - int sig_num; - JSValue func; -} JSOSSignalHandler; - -typedef struct { - struct list_head link; - int timer_id; - int64_t timeout; - JSValue func; -} JSOSTimer; - -typedef struct { - struct list_head link; - uint8_t *data; - size_t data_len; - /* list of SharedArrayBuffers, necessary to free the message */ - uint8_t **sab_tab; - size_t sab_tab_len; -} JSWorkerMessage; - -typedef struct JSWaker { -#ifdef _WIN32 - HANDLE handle; -#else - int read_fd; - int write_fd; -#endif -} JSWaker; - -typedef struct { - int ref_count; -#ifdef USE_WORKER - pthread_mutex_t mutex; -#endif - struct list_head msg_queue; /* list of JSWorkerMessage.link */ - JSWaker waker; -} JSWorkerMessagePipe; - -typedef struct { - struct list_head link; - JSWorkerMessagePipe *recv_pipe; - JSValue on_message_func; -} JSWorkerMessageHandler; - -typedef struct { - struct list_head link; - JSValue promise; - JSValue reason; -} JSRejectedPromiseEntry; - -typedef struct JSThreadState { - struct list_head os_rw_handlers; /* list of JSOSRWHandler.link */ - struct list_head os_signal_handlers; /* list JSOSSignalHandler.link */ - struct list_head os_timers; /* list of JSOSTimer.link */ - struct list_head port_list; /* list of JSWorkerMessageHandler.link */ - struct list_head rejected_promise_list; /* list of JSRejectedPromiseEntry.link */ - int eval_script_recurse; /* only used in the main thread */ - int next_timer_id; /* for setTimeout() */ - /* not used in the main thread */ - JSWorkerMessagePipe *recv_pipe, *send_pipe; -} JSThreadState; - -static uint64_t os_pending_signals; -static int (*os_poll_func)(JSContext *ctx); - -static void js_std_dbuf_init(JSContext *ctx, DynBuf *s) -{ - dbuf_init2(s, JS_GetRuntime(ctx), (DynBufReallocFunc *)js_realloc_rt); -} - -static BOOL my_isdigit(int c) -{ - return (c >= '0' && c <= '9'); -} - -/* XXX: use 'o' and 'O' for object using JS_PrintValue() ? */ -static JSValue js_printf_internal(JSContext *ctx, - int argc, JSValueConst *argv, FILE *fp) -{ - char fmtbuf[32]; - uint8_t cbuf[UTF8_CHAR_LEN_MAX+1]; - JSValue res; - DynBuf dbuf; - const char *fmt_str = NULL; - const uint8_t *fmt, *fmt_end; - const uint8_t *p; - char *q; - int i, c, len, mod; - size_t fmt_len; - int32_t int32_arg; - int64_t int64_arg; - double double_arg; - const char *string_arg; - /* Use indirect call to dbuf_printf to prevent gcc warning */ - int (*dbuf_printf_fun)(DynBuf *s, const char *fmt, ...) = (void*)dbuf_printf; - - js_std_dbuf_init(ctx, &dbuf); - - if (argc > 0) { - fmt_str = JS_ToCStringLen(ctx, &fmt_len, argv[0]); - if (!fmt_str) - goto fail; - - i = 1; - fmt = (const uint8_t *)fmt_str; - fmt_end = fmt + fmt_len; - while (fmt < fmt_end) { - for (p = fmt; fmt < fmt_end && *fmt != '%'; fmt++) - continue; - dbuf_put(&dbuf, p, fmt - p); - if (fmt >= fmt_end) - break; - q = fmtbuf; - *q++ = *fmt++; /* copy '%' */ - - /* flags */ - for(;;) { - c = *fmt; - if (c == '0' || c == '#' || c == '+' || c == '-' || c == ' ' || - c == '\'') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - fmt++; - } else { - break; - } - } - /* width */ - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - if (*fmt == '.') { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - if (*fmt == '*') { - if (i >= argc) - goto missing; - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - q += snprintf(q, fmtbuf + sizeof(fmtbuf) - q, "%d", int32_arg); - fmt++; - } else { - while (my_isdigit(*fmt)) { - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = *fmt++; - } - } - } - - /* we only support the "l" modifier for 64 bit numbers */ - mod = ' '; - if (*fmt == 'l') { - mod = *fmt++; - } - - /* type */ - c = *fmt++; - if (q >= fmtbuf + sizeof(fmtbuf) - 1) - goto invalid; - *q++ = c; - *q = '\0'; - - switch (c) { - case 'c': - if (i >= argc) - goto missing; - if (JS_IsString(argv[i])) { - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - int32_arg = unicode_from_utf8((const uint8_t *)string_arg, UTF8_CHAR_LEN_MAX, &p); - JS_FreeCString(ctx, string_arg); - } else { - if (JS_ToInt32(ctx, &int32_arg, argv[i++])) - goto fail; - } - /* handle utf-8 encoding explicitly */ - if ((unsigned)int32_arg > 0x10FFFF) - int32_arg = 0xFFFD; - /* ignore conversion flags, width and precision */ - len = unicode_to_utf8(cbuf, int32_arg); - dbuf_put(&dbuf, cbuf, len); - break; - - case 'd': - case 'i': - case 'o': - case 'u': - case 'x': - case 'X': - if (i >= argc) - goto missing; - if (JS_ToInt64Ext(ctx, &int64_arg, argv[i++])) - goto fail; - if (mod == 'l') { - /* 64 bit number */ -#if defined(_WIN32) - if (q >= fmtbuf + sizeof(fmtbuf) - 3) - goto invalid; - q[2] = q[-1]; - q[-1] = 'I'; - q[0] = '6'; - q[1] = '4'; - q[3] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (int64_t)int64_arg); -#else - if (q >= fmtbuf + sizeof(fmtbuf) - 2) - goto invalid; - q[1] = q[-1]; - q[-1] = q[0] = 'l'; - q[2] = '\0'; - dbuf_printf_fun(&dbuf, fmtbuf, (long long)int64_arg); -#endif - } else { - dbuf_printf_fun(&dbuf, fmtbuf, (int)int64_arg); - } - break; - - case 's': - if (i >= argc) - goto missing; - /* XXX: handle strings containing null characters */ - string_arg = JS_ToCString(ctx, argv[i++]); - if (!string_arg) - goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, string_arg); - JS_FreeCString(ctx, string_arg); - break; - - case 'e': - case 'f': - case 'g': - case 'a': - case 'E': - case 'F': - case 'G': - case 'A': - if (i >= argc) - goto missing; - if (JS_ToFloat64(ctx, &double_arg, argv[i++])) - goto fail; - dbuf_printf_fun(&dbuf, fmtbuf, double_arg); - break; - - case '%': - dbuf_putc(&dbuf, '%'); - break; - - default: - /* XXX: should support an extension mechanism */ - invalid: - JS_ThrowTypeError(ctx, "invalid conversion specifier in format string"); - goto fail; - missing: - JS_ThrowReferenceError(ctx, "missing argument for conversion specifier"); - goto fail; - } - } - JS_FreeCString(ctx, fmt_str); - } - if (dbuf.error) { - res = JS_ThrowOutOfMemory(ctx); - } else { - if (fp) { - len = fwrite(dbuf.buf, 1, dbuf.size, fp); - res = JS_NewInt32(ctx, len); - } else { - res = JS_NewStringLen(ctx, (char *)dbuf.buf, dbuf.size); - } - } - dbuf_free(&dbuf); - return res; - -fail: - JS_FreeCString(ctx, fmt_str); - dbuf_free(&dbuf); - return JS_EXCEPTION; -} - -uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename) -{ - FILE *f; - uint8_t *buf; - size_t buf_len; - long lret; - - f = fopen(filename, "rb"); - if (!f) - return NULL; - if (fseek(f, 0, SEEK_END) < 0) - goto fail; - lret = ftell(f); - if (lret < 0) - goto fail; - /* XXX: on Linux, ftell() return LONG_MAX for directories */ - if (lret == LONG_MAX) { - errno = EISDIR; - goto fail; - } - buf_len = lret; - if (fseek(f, 0, SEEK_SET) < 0) - goto fail; - if (ctx) - buf = js_malloc(ctx, buf_len + 1); - else - buf = malloc(buf_len + 1); - if (!buf) - goto fail; - if (fread(buf, 1, buf_len, f) != buf_len) { - errno = EIO; - if (ctx) - js_free(ctx, buf); - else - free(buf); - fail: - fclose(f); - return NULL; - } - buf[buf_len] = '\0'; - fclose(f); - *pbuf_len = buf_len; - return buf; -} - -/* load and evaluate a file */ -static JSValue js_loadScript(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint8_t *buf; - const char *filename; - JSValue ret; - size_t buf_len; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load '%s'", filename); - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - ret = JS_Eval(ctx, (char *)buf, buf_len, filename, - JS_EVAL_TYPE_GLOBAL); - js_free(ctx, buf); - JS_FreeCString(ctx, filename); - return ret; -} - -/* load a file as a UTF-8 encoded string */ -static JSValue js_std_loadFile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - uint8_t *buf; - const char *filename; - JSValue ret; - size_t buf_len; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - buf = js_load_file(ctx, &buf_len, filename); - JS_FreeCString(ctx, filename); - if (!buf) - return JS_NULL; - ret = JS_NewStringLen(ctx, (char *)buf, buf_len); - js_free(ctx, buf); - return ret; -} - -typedef JSModuleDef *(JSInitModuleFunc)(JSContext *ctx, - const char *module_name); - - -#if defined(_WIN32) -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JS_ThrowReferenceError(ctx, "shared library modules are not supported yet"); - return NULL; -} -#else -static JSModuleDef *js_module_loader_so(JSContext *ctx, - const char *module_name) -{ - JSModuleDef *m; - void *hd; - JSInitModuleFunc *init; - char *filename; - - if (!strchr(module_name, '/')) { - /* must add a '/' so that the DLL is not searched in the - system library paths */ - filename = js_malloc(ctx, strlen(module_name) + 2 + 1); - if (!filename) - return NULL; - strcpy(filename, "./"); - strcpy(filename + 2, module_name); - } else { - filename = (char *)module_name; - } - - /* C module */ - hd = dlopen(filename, RTLD_NOW | RTLD_LOCAL); - if (filename != module_name) - js_free(ctx, filename); - if (!hd) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s' as shared library", - module_name); - goto fail; - } - - init = dlsym(hd, "js_init_module"); - if (!init) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': js_init_module not found", - module_name); - goto fail; - } - - m = init(ctx, module_name); - if (!m) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s': initialization error", - module_name); - fail: - if (hd) - dlclose(hd); - return NULL; - } - return m; -} -#endif /* !_WIN32 */ - -int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, - JS_BOOL use_realpath, JS_BOOL is_main) -{ - JSModuleDef *m; - char buf[PATH_MAX + 16]; - JSValue meta_obj; - JSAtom module_name_atom; - const char *module_name; - - assert(JS_VALUE_GET_TAG(func_val) == JS_TAG_MODULE); - m = JS_VALUE_GET_PTR(func_val); - - module_name_atom = JS_GetModuleName(ctx, m); - module_name = JS_AtomToCString(ctx, module_name_atom); - JS_FreeAtom(ctx, module_name_atom); - if (!module_name) - return -1; - if (!strchr(module_name, ':')) { - strcpy(buf, "file://"); -#if !defined(_WIN32) - /* realpath() cannot be used with modules compiled with qjsc - because the corresponding module source code is not - necessarily present */ - if (use_realpath) { - char *res = realpath(module_name, buf + strlen(buf)); - if (!res) { - JS_ThrowTypeError(ctx, "realpath failure"); - JS_FreeCString(ctx, module_name); - return -1; - } - } else -#endif - { - pstrcat(buf, sizeof(buf), module_name); - } - } else { - pstrcpy(buf, sizeof(buf), module_name); - } - JS_FreeCString(ctx, module_name); - - meta_obj = JS_GetImportMeta(ctx, m); - if (JS_IsException(meta_obj)) - return -1; - JS_DefinePropertyValueStr(ctx, meta_obj, "url", - JS_NewString(ctx, buf), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, meta_obj, "main", - JS_NewBool(ctx, is_main), - JS_PROP_C_W_E); - JS_FreeValue(ctx, meta_obj); - return 0; -} - -static int json_module_init(JSContext *ctx, JSModuleDef *m) -{ - JSValue val; - val = JS_GetModulePrivateValue(ctx, m); - JS_SetModuleExport(ctx, m, "default", val); - return 0; -} - -static JSModuleDef *create_json_module(JSContext *ctx, const char *module_name, JSValue val) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, json_module_init); - if (!m) { - JS_FreeValue(ctx, val); - return NULL; - } - /* only export the "default" symbol which will contain the JSON object */ - JS_AddModuleExport(ctx, m, "default"); - JS_SetModulePrivateValue(ctx, m, val); - return m; -} - -/* in order to conform with the specification, only the keys should be - tested and not the associated values. */ -int js_module_check_attributes(JSContext *ctx, void *opaque, - JSValueConst attributes) -{ - JSPropertyEnum *tab; - uint32_t i, len; - int ret; - const char *cstr; - size_t cstr_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, attributes, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK)) - return -1; - ret = 0; - for(i = 0; i < len; i++) { - cstr = JS_AtomToCStringLen(ctx, &cstr_len, tab[i].atom); - if (!cstr) { - ret = -1; - break; - } - if (!(cstr_len == 4 && !memcmp(cstr, "type", cstr_len))) { - JS_ThrowTypeError(ctx, "import attribute '%s' is not supported", cstr); - ret = -1; - } - JS_FreeCString(ctx, cstr); - if (ret) - break; - } - JS_FreePropertyEnum(ctx, tab, len); - return ret; -} - -/* return > 0 if the attributes indicate a JSON module */ -int js_module_test_json(JSContext *ctx, JSValueConst attributes) -{ - JSValue str; - const char *cstr; - size_t len; - BOOL res; - - if (JS_IsNull(attributes)) - return FALSE; - str = JS_GetPropertyStr(ctx, attributes, "type"); - if (!JS_IsString(str)) - return FALSE; - cstr = JS_ToCStringLen(ctx, &len, str); - JS_FreeValue(ctx, str); - if (!cstr) - return FALSE; - /* XXX: raise an error if unknown type ? */ - if (len == 4 && !memcmp(cstr, "json", len)) { - res = 1; - } else if (len == 5 && !memcmp(cstr, "json5", len)) { - res = 2; - } else { - res = 0; - } - JS_FreeCString(ctx, cstr); - return res; -} - -JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque, - JSValueConst attributes) -{ - JSModuleDef *m; - int res; - - if (has_suffix(module_name, ".so")) { - m = js_module_loader_so(ctx, module_name); - } else { - size_t buf_len; - uint8_t *buf; - - buf = js_load_file(ctx, &buf_len, module_name); - if (!buf) { - JS_ThrowReferenceError(ctx, "could not load module filename '%s'", - module_name); - return NULL; - } - res = js_module_test_json(ctx, attributes); - if (has_suffix(module_name, ".json") || res > 0) { - /* compile as JSON or JSON5 depending on "type" */ - JSValue val; - int flags; - if (res == 2) - flags = JS_PARSE_JSON_EXT; - else - flags = 0; - val = JS_ParseJSON2(ctx, (char *)buf, buf_len, module_name, flags); - js_free(ctx, buf); - if (JS_IsException(val)) - return NULL; - m = create_json_module(ctx, module_name, val); - if (!m) - return NULL; - } else { - JSValue func_val; - /* compile the module */ - func_val = JS_Eval(ctx, (char *)buf, buf_len, module_name, - JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY); - js_free(ctx, buf); - if (JS_IsException(func_val)) - return NULL; - /* XXX: could propagate the exception */ - js_module_set_import_meta(ctx, func_val, TRUE, FALSE); - /* the module is already referenced, so we must free it */ - m = JS_VALUE_GET_PTR(func_val); - JS_FreeValue(ctx, func_val); - } - } - return m; -} - -static JSValue js_std_exit(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int status; - if (JS_ToInt32(ctx, &status, argv[0])) - status = -1; - exit(status); - return JS_NULL; -} - -static JSValue js_std_getenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name, *str; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - str = getenv(name); - JS_FreeCString(ctx, name); - if (!str) - return JS_NULL; - else - return JS_NewString(ctx, str); -} - -#if defined(_WIN32) -static void setenv(const char *name, const char *value, int overwrite) -{ - char *str; - size_t name_len, value_len; - name_len = strlen(name); - value_len = strlen(value); - str = malloc(name_len + 1 + value_len + 1); - memcpy(str, name, name_len); - str[name_len] = '='; - memcpy(str + name_len + 1, value, value_len); - str[name_len + 1 + value_len] = '\0'; - _putenv(str); - free(str); -} - -static void unsetenv(const char *name) -{ - setenv(name, "", TRUE); -} -#endif /* _WIN32 */ - -static JSValue js_std_setenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name, *value; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - value = JS_ToCString(ctx, argv[1]); - if (!value) { - JS_FreeCString(ctx, name); - return JS_EXCEPTION; - } - setenv(name, value, TRUE); - JS_FreeCString(ctx, name); - JS_FreeCString(ctx, value); - return JS_NULL; -} - -static JSValue js_std_unsetenv(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *name; - name = JS_ToCString(ctx, argv[0]); - if (!name) - return JS_EXCEPTION; - unsetenv(name); - JS_FreeCString(ctx, name); - return JS_NULL; -} - -/* return an object containing the list of the available environment - variables. */ -static JSValue js_std_getenviron(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - char **envp; - const char *name, *p, *value; - JSValue obj; - uint32_t idx; - size_t name_len; - JSAtom atom; - int ret; - - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - envp = environ; - for(idx = 0; envp[idx] != NULL; idx++) { - name = envp[idx]; - p = strchr(name, '='); - name_len = p - name; - if (!p) - continue; - value = p + 1; - atom = JS_NewAtomLen(ctx, name, name_len); - if (atom == JS_ATOM_NULL) - goto fail; - ret = JS_DefinePropertyValue(ctx, obj, atom, JS_NewString(ctx, value), - JS_PROP_C_W_E); - JS_FreeAtom(ctx, atom); - if (ret < 0) - goto fail; - } - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_std_gc(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JS_RunGC(JS_GetRuntime(ctx)); - return JS_NULL; -} - -static int interrupt_handler(JSRuntime *rt, void *opaque) -{ - return (os_pending_signals >> SIGINT) & 1; -} - -static int get_bool_option(JSContext *ctx, BOOL *pbool, - JSValueConst obj, - const char *option) -{ - JSValue val; - val = JS_GetPropertyStr(ctx, obj, option); - if (JS_IsException(val)) - return -1; - if (!JS_IsNull(val)) { - *pbool = JS_ToBool(ctx, val); - } - JS_FreeValue(ctx, val); - return 0; -} - -static JSValue js_evalScript(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - const char *str; - size_t len; - JSValue ret; - JSValueConst options_obj; - BOOL backtrace_barrier = FALSE; - BOOL is_async = FALSE; - int flags; - - if (argc >= 2) { - options_obj = argv[1]; - if (get_bool_option(ctx, &backtrace_barrier, options_obj, - "backtrace_barrier")) - return JS_EXCEPTION; - if (get_bool_option(ctx, &is_async, options_obj, - "async")) - return JS_EXCEPTION; - } - - str = JS_ToCStringLen(ctx, &len, argv[0]); - if (!str) - return JS_EXCEPTION; - if (!ts->recv_pipe && ++ts->eval_script_recurse == 1) { - /* install the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), interrupt_handler, NULL); - } - flags = JS_EVAL_TYPE_GLOBAL; - if (backtrace_barrier) - flags |= JS_EVAL_FLAG_BACKTRACE_BARRIER; - if (is_async) - flags |= JS_EVAL_FLAG_ASYNC; - ret = JS_Eval(ctx, str, len, "", flags); - JS_FreeCString(ctx, str); - if (!ts->recv_pipe && --ts->eval_script_recurse == 0) { - /* remove the interrupt handler */ - JS_SetInterruptHandler(JS_GetRuntime(ctx), NULL, NULL); - os_pending_signals &= ~((uint64_t)1 << SIGINT); - /* convert the uncatchable "interrupted" error into a normal error - so that it can be caught by the REPL */ - if (JS_IsException(ret)) - JS_SetUncatchableException(ctx, FALSE); - } - return ret; -} - -static JSClassID js_std_file_class_id; - -typedef struct { - FILE *f; - BOOL close_in_finalizer; - BOOL is_popen; -} JSSTDFile; - -static void js_std_file_finalizer(JSRuntime *rt, JSValue val) -{ - JSSTDFile *s = JS_GetOpaque(val, js_std_file_class_id); - if (s) { - if (s->f && s->close_in_finalizer) { - if (s->is_popen) - pclose(s->f); - else - fclose(s->f); - } - js_free_rt(rt, s); - } -} - -static ssize_t js_get_errno(ssize_t ret) -{ - if (ret == -1) - ret = -errno; - return ret; -} - -static JSValue js_std_strerror(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int err; - if (JS_ToInt32(ctx, &err, argv[0])) - return JS_EXCEPTION; - return JS_NewString(ctx, strerror(err)); -} - -static JSValue js_std_parseExtJSON(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue obj; - const char *str; - size_t len; - - str = JS_ToCStringLen(ctx, &len, argv[0]); - if (!str) - return JS_EXCEPTION; - obj = JS_ParseJSON2(ctx, str, len, "", JS_PARSE_JSON_EXT); - JS_FreeCString(ctx, str); - return obj; -} - -static JSValue js_new_std_file(JSContext *ctx, FILE *f, - BOOL close_in_finalizer, - BOOL is_popen) -{ - JSSTDFile *s; - JSValue obj; - obj = JS_NewObjectClass(ctx, js_std_file_class_id); - if (JS_IsException(obj)) - return obj; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) { - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; - } - s->close_in_finalizer = close_in_finalizer; - s->is_popen = is_popen; - s->f = f; - JS_SetOpaque(obj, s); - return obj; -} - -static void js_set_error_object(JSContext *ctx, JSValue obj, int err) -{ - if (!JS_IsNull(obj)) { - JS_SetPropertyStr(ctx, obj, "errno", JS_NewInt32(ctx, err)); - } -} - -static JSValue js_std_open(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+b")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fopen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, TRUE, FALSE); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -static JSValue js_std_popen(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename, *mode = NULL; - FILE *f; - int err; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rw")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = popen(filename, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, TRUE, TRUE); - fail: - JS_FreeCString(ctx, filename); - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -static JSValue js_std_fdopen(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *mode; - FILE *f; - int fd, err; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - mode = JS_ToCString(ctx, argv[1]); - if (!mode) - goto fail; - if (mode[strspn(mode, "rwa+")] != '\0') { - JS_ThrowTypeError(ctx, "invalid file mode"); - goto fail; - } - - f = fdopen(fd, mode); - if (!f) - err = errno; - else - err = 0; - if (argc >= 3) - js_set_error_object(ctx, argv[2], err); - JS_FreeCString(ctx, mode); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, TRUE, FALSE); - fail: - JS_FreeCString(ctx, mode); - return JS_EXCEPTION; -} - -static JSValue js_std_tmpfile(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f; - f = tmpfile(); - if (argc >= 1) - js_set_error_object(ctx, argv[0], f ? 0 : errno); - if (!f) - return JS_NULL; - return js_new_std_file(ctx, f, TRUE, FALSE); -} - -static JSValue js_std_sprintf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_printf_internal(ctx, argc, argv, NULL); -} - -static JSValue js_std_printf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return js_printf_internal(ctx, argc, argv, stdout); -} - -static FILE *js_std_file_get(JSContext *ctx, JSValueConst obj) -{ - JSSTDFile *s = JS_GetOpaque2(ctx, obj, js_std_file_class_id); - if (!s) - return NULL; - if (!s->f) { - JS_ThrowTypeError(ctx, "invalid file handle"); - return NULL; - } - return s->f; -} - -static JSValue js_std_file_puts(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - FILE *f; - int i; - const char *str; - size_t len; - - if (magic == 0) { - f = stdout; - } else { - f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - } - - for(i = 0; i < argc; i++) { - str = JS_ToCStringLen(ctx, &len, argv[i]); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, f); - JS_FreeCString(ctx, str); - } - return JS_NULL; -} - -static JSValue js_std_file_close(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSSTDFile *s = JS_GetOpaque2(ctx, this_val, js_std_file_class_id); - int err; - if (!s) - return JS_EXCEPTION; - if (!s->f) - return JS_ThrowTypeError(ctx, "invalid file handle"); - if (s->is_popen) - err = js_get_errno(pclose(s->f)); - else - err = js_get_errno(fclose(s->f)); - s->f = NULL; - return JS_NewInt32(ctx, err); -} - -static JSValue js_std_file_printf(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return js_printf_internal(ctx, argc, argv, f); -} - -static void js_print_value_write(void *opaque, const char *buf, size_t len) -{ - FILE *fo = opaque; - fwrite(buf, 1, len, fo); -} - -static JSValue js_std_file_printObject(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JS_PrintValue(ctx, js_print_value_write, stdout, argv[0], NULL); - return JS_NULL; -} - -static JSValue js_std_file_flush(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - fflush(f); - return JS_NULL; -} - -static JSValue js_std_file_tell(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int is_bigint) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - if (!f) - return JS_EXCEPTION; -#if defined(__linux__) || defined(__GLIBC__) - pos = ftello(f); -#else - pos = ftell(f); -#endif - if (is_bigint) - return JS_NewBigInt64(ctx, pos); - else - return JS_NewInt64(ctx, pos); -} - -static JSValue js_std_file_seek(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int64_t pos; - int whence, ret; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt64Ext(ctx, &pos, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[1])) - return JS_EXCEPTION; -#if defined(__linux__) || defined(__GLIBC__) - ret = fseeko(f, pos, whence); -#else - ret = fseek(f, pos, whence); -#endif - if (ret < 0) - ret = -errno; - return JS_NewInt32(ctx, ret); -} - -static JSValue js_std_file_eof(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, feof(f)); -} - -static JSValue js_std_file_error(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewBool(ctx, ferror(f)); -} - -static JSValue js_std_file_clearerr(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - clearerr(f); - return JS_NULL; -} - -static JSValue js_std_file_fileno(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fileno(f)); -} - -static JSValue js_std_file_read_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - FILE *f = js_std_file_get(ctx, this_val); - uint64_t pos, len; - size_t size, ret; - uint8_t *buf; - - if (!f) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[2])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[0]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = fwrite(buf + pos, 1, len, f); - else - ret = fread(buf + pos, 1, len, f); - return JS_NewInt64(ctx, ret); -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_getline(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - - if (!f) - return JS_EXCEPTION; - - js_std_dbuf_init(ctx, &dbuf); - for(;;) { - c = fgetc(f); - if (c == EOF) { - if (dbuf.size == 0) { - /* EOF */ - dbuf_free(&dbuf); - return JS_NULL; - } else { - break; - } - } - if (c == '\n') - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_ThrowOutOfMemory(ctx); - } - } - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - return obj; -} - -/* XXX: could use less memory and go faster */ -static JSValue js_std_file_readAsString(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - DynBuf dbuf; - JSValue obj; - uint64_t max_size64; - size_t max_size; - JSValueConst max_size_val; - - if (!f) - return JS_EXCEPTION; - - if (argc >= 1) - max_size_val = argv[0]; - else - max_size_val = JS_NULL; - max_size = (size_t)-1; - if (!JS_IsNull(max_size_val)) { - if (JS_ToIndex(ctx, &max_size64, max_size_val)) - return JS_EXCEPTION; - if (max_size64 < max_size) - max_size = max_size64; - } - - js_std_dbuf_init(ctx, &dbuf); - while (max_size != 0) { - c = fgetc(f); - if (c == EOF) - break; - if (dbuf_putc(&dbuf, c)) { - dbuf_free(&dbuf); - return JS_EXCEPTION; - } - max_size--; - } - obj = JS_NewStringLen(ctx, (const char *)dbuf.buf, dbuf.size); - dbuf_free(&dbuf); - return obj; -} - -static JSValue js_std_file_getByte(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - if (!f) - return JS_EXCEPTION; - return JS_NewInt32(ctx, fgetc(f)); -} - -static JSValue js_std_file_putByte(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - FILE *f = js_std_file_get(ctx, this_val); - int c; - if (!f) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &c, argv[0])) - return JS_EXCEPTION; - c = fputc(c, f); - return JS_NewInt32(ctx, c); -} - -/* urlGet */ - -#define URL_GET_PROGRAM "curl -s -i --" -#define URL_GET_BUF_SIZE 4096 - -static int http_get_header_line(FILE *f, char *buf, size_t buf_size, - DynBuf *dbuf) -{ - int c; - char *p; - - p = buf; - for(;;) { - c = fgetc(f); - if (c < 0) - return -1; - if ((p - buf) < buf_size - 1) - *p++ = c; - if (dbuf) - dbuf_putc(dbuf, c); - if (c == '\n') - break; - } - *p = '\0'; - return 0; -} - -static int http_get_status(const char *buf) -{ - const char *p = buf; - while (*p != ' ' && *p != '\0') - p++; - if (*p != ' ') - return 0; - while (*p == ' ') - p++; - return atoi(p); -} - -static JSValue js_std_urlGet(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *url; - DynBuf cmd_buf; - DynBuf data_buf_s, *data_buf = &data_buf_s; - DynBuf header_buf_s, *header_buf = &header_buf_s; - char *buf; - size_t i, len; - int status; - JSValue response = JS_NULL, ret_obj; - JSValueConst options_obj; - FILE *f; - BOOL binary_flag, full_flag; - - url = JS_ToCString(ctx, argv[0]); - if (!url) - return JS_EXCEPTION; - - binary_flag = FALSE; - full_flag = FALSE; - - if (argc >= 2) { - options_obj = argv[1]; - - if (get_bool_option(ctx, &binary_flag, options_obj, "binary")) - goto fail_obj; - - if (get_bool_option(ctx, &full_flag, options_obj, "full")) { - fail_obj: - JS_FreeCString(ctx, url); - return JS_EXCEPTION; - } - } - - js_std_dbuf_init(ctx, &cmd_buf); - dbuf_printf(&cmd_buf, "%s '", URL_GET_PROGRAM); - for(i = 0; url[i] != '\0'; i++) { - unsigned char c = url[i]; - switch (c) { - case '\'': - /* shell single quoted string does not support \' */ - dbuf_putstr(&cmd_buf, "'\\''"); - break; - case '[': case ']': case '{': case '}': case '\\': - /* prevent interpretation by curl as range or set specification */ - dbuf_putc(&cmd_buf, '\\'); - /* FALLTHROUGH */ - default: - dbuf_putc(&cmd_buf, c); - break; - } - } - JS_FreeCString(ctx, url); - dbuf_putstr(&cmd_buf, "'"); - dbuf_putc(&cmd_buf, '\0'); - if (dbuf_error(&cmd_buf)) { - dbuf_free(&cmd_buf); - return JS_EXCEPTION; - } - // printf("%s\n", (char *)cmd_buf.buf); - f = popen((char *)cmd_buf.buf, "r"); - dbuf_free(&cmd_buf); - if (!f) { - return JS_ThrowTypeError(ctx, "could not start curl"); - } - - js_std_dbuf_init(ctx, data_buf); - js_std_dbuf_init(ctx, header_buf); - - buf = js_malloc(ctx, URL_GET_BUF_SIZE); - if (!buf) - goto fail; - - /* get the HTTP status */ - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, NULL) < 0) { - status = 0; - goto bad_header; - } - status = http_get_status(buf); - if (!full_flag && !(status >= 200 && status <= 299)) { - goto bad_header; - } - - /* wait until there is an empty line */ - for(;;) { - if (http_get_header_line(f, buf, URL_GET_BUF_SIZE, header_buf) < 0) { - bad_header: - response = JS_NULL; - goto done; - } - if (!strcmp(buf, "\r\n")) - break; - } - if (dbuf_error(header_buf)) - goto fail; - header_buf->size -= 2; /* remove the trailing CRLF */ - - /* download the data */ - for(;;) { - len = fread(buf, 1, URL_GET_BUF_SIZE, f); - if (len == 0) - break; - dbuf_put(data_buf, (uint8_t *)buf, len); - } - if (dbuf_error(data_buf)) - goto fail; - if (binary_flag) { - response = JS_NewArrayBufferCopy(ctx, - data_buf->buf, data_buf->size); - } else { - response = JS_NewStringLen(ctx, (char *)data_buf->buf, data_buf->size); - } - if (JS_IsException(response)) - goto fail; - done: - js_free(ctx, buf); - buf = NULL; - pclose(f); - f = NULL; - dbuf_free(data_buf); - data_buf = NULL; - - if (full_flag) { - ret_obj = JS_NewObject(ctx); - if (JS_IsException(ret_obj)) - goto fail; - JS_DefinePropertyValueStr(ctx, ret_obj, "response", - response, - JS_PROP_C_W_E); - if (!JS_IsNull(response)) { - JS_DefinePropertyValueStr(ctx, ret_obj, "responseHeaders", - JS_NewStringLen(ctx, (char *)header_buf->buf, - header_buf->size), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, ret_obj, "status", - JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - } - } else { - ret_obj = response; - } - dbuf_free(header_buf); - return ret_obj; - fail: - if (f) - pclose(f); - js_free(ctx, buf); - if (data_buf) - dbuf_free(data_buf); - if (header_buf) - dbuf_free(header_buf); - JS_FreeValue(ctx, response); - return JS_EXCEPTION; -} - -static JSClassDef js_std_file_class = { - "FILE", - .finalizer = js_std_file_finalizer, -}; - -static const JSCFunctionListEntry js_std_error_props[] = { - /* various errno values */ -#define DEF(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - DEF(EINVAL), - DEF(EIO), - DEF(EACCES), - DEF(EEXIST), - DEF(ENOSPC), - DEF(ENOSYS), - DEF(EBUSY), - DEF(ENOENT), - DEF(EPERM), - DEF(EPIPE), - DEF(EBADF), -#undef DEF -}; - -static const JSCFunctionListEntry js_std_funcs[] = { - JS_CFUNC_DEF("exit", 1, js_std_exit ), - JS_CFUNC_DEF("gc", 0, js_std_gc ), - JS_CFUNC_DEF("evalScript", 1, js_evalScript ), - JS_CFUNC_DEF("loadScript", 1, js_loadScript ), - JS_CFUNC_DEF("getenv", 1, js_std_getenv ), - JS_CFUNC_DEF("setenv", 1, js_std_setenv ), - JS_CFUNC_DEF("unsetenv", 1, js_std_unsetenv ), - JS_CFUNC_DEF("getenviron", 1, js_std_getenviron ), - JS_CFUNC_DEF("urlGet", 1, js_std_urlGet ), - JS_CFUNC_DEF("loadFile", 1, js_std_loadFile ), - JS_CFUNC_DEF("strerror", 1, js_std_strerror ), - JS_CFUNC_DEF("parseExtJSON", 1, js_std_parseExtJSON ), - - /* FILE I/O */ - JS_CFUNC_DEF("open", 2, js_std_open ), - JS_CFUNC_DEF("popen", 2, js_std_popen ), - JS_CFUNC_DEF("fdopen", 2, js_std_fdopen ), - JS_CFUNC_DEF("tmpfile", 0, js_std_tmpfile ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 0 ), - JS_CFUNC_DEF("printf", 1, js_std_printf ), - JS_CFUNC_DEF("sprintf", 1, js_std_sprintf ), - JS_PROP_INT32_DEF("SEEK_SET", SEEK_SET, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_CUR", SEEK_CUR, JS_PROP_CONFIGURABLE ), - JS_PROP_INT32_DEF("SEEK_END", SEEK_END, JS_PROP_CONFIGURABLE ), - JS_OBJECT_DEF("Error", js_std_error_props, countof(js_std_error_props), JS_PROP_CONFIGURABLE), - JS_CFUNC_DEF("__printObject", 1, js_std_file_printObject ), -}; - -static const JSCFunctionListEntry js_std_file_proto_funcs[] = { - JS_CFUNC_DEF("close", 0, js_std_file_close ), - JS_CFUNC_MAGIC_DEF("puts", 1, js_std_file_puts, 1 ), - JS_CFUNC_DEF("printf", 1, js_std_file_printf ), - JS_CFUNC_DEF("flush", 0, js_std_file_flush ), - JS_CFUNC_MAGIC_DEF("tell", 0, js_std_file_tell, 0 ), - JS_CFUNC_MAGIC_DEF("tello", 0, js_std_file_tell, 1 ), - JS_CFUNC_DEF("seek", 2, js_std_file_seek ), - JS_CFUNC_DEF("eof", 0, js_std_file_eof ), - JS_CFUNC_DEF("fileno", 0, js_std_file_fileno ), - JS_CFUNC_DEF("error", 0, js_std_file_error ), - JS_CFUNC_DEF("clearerr", 0, js_std_file_clearerr ), - JS_CFUNC_MAGIC_DEF("read", 3, js_std_file_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 3, js_std_file_read_write, 1 ), - JS_CFUNC_DEF("getline", 0, js_std_file_getline ), - JS_CFUNC_DEF("readAsString", 0, js_std_file_readAsString ), - JS_CFUNC_DEF("getByte", 0, js_std_file_getByte ), - JS_CFUNC_DEF("putByte", 1, js_std_file_putByte ), - /* setvbuf, ... */ -}; - -static int js_std_init(JSContext *ctx, JSModuleDef *m) -{ - JSValue proto; - - /* FILE class */ - /* the class ID is created once */ - JS_NewClassID(&js_std_file_class_id); - /* the class is created once per runtime */ - JS_NewClass(JS_GetRuntime(ctx), js_std_file_class_id, &js_std_file_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_std_file_proto_funcs, - countof(js_std_file_proto_funcs)); - JS_SetClassProto(ctx, js_std_file_class_id, proto); - - JS_SetModuleExportList(ctx, m, js_std_funcs, - countof(js_std_funcs)); - JS_SetModuleExport(ctx, m, "in", js_new_std_file(ctx, stdin, FALSE, FALSE)); - JS_SetModuleExport(ctx, m, "out", js_new_std_file(ctx, stdout, FALSE, FALSE)); - JS_SetModuleExport(ctx, m, "err", js_new_std_file(ctx, stderr, FALSE, FALSE)); - return 0; -} - -JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_std_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_std_funcs, countof(js_std_funcs)); - JS_AddModuleExport(ctx, m, "in"); - JS_AddModuleExport(ctx, m, "out"); - JS_AddModuleExport(ctx, m, "err"); - return m; -} - -/**********************************************************/ -/* 'os' object */ - -static JSValue js_os_open(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename; - int flags, mode, ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &flags, argv[1])) - goto fail; - if (argc >= 3 && !JS_IsNull(argv[2])) { - if (JS_ToInt32(ctx, &mode, argv[2])) { - fail: - JS_FreeCString(ctx, filename); - return JS_EXCEPTION; - } - } else { - mode = 0666; - } -#if defined(_WIN32) - /* force binary mode by default */ - if (!(flags & O_TEXT)) - flags |= O_BINARY; -#endif - ret = js_get_errno(open(filename, flags, mode)); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_close(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, ret; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(close(fd)); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_seek(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, whence; - int64_t pos, ret; - BOOL is_bigint; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - is_bigint = JS_IsBigInt(ctx, argv[1]); - if (JS_ToInt64Ext(ctx, &pos, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &whence, argv[2])) - return JS_EXCEPTION; - ret = lseek(fd, pos, whence); - if (ret == -1) - ret = -errno; - if (is_bigint) - return JS_NewBigInt64(ctx, ret); - else - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_read_write(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - int fd; - uint64_t pos, len; - size_t size; - ssize_t ret; - uint8_t *buf; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &pos, argv[2])) - return JS_EXCEPTION; - if (JS_ToIndex(ctx, &len, argv[3])) - return JS_EXCEPTION; - buf = JS_GetArrayBuffer(ctx, &size, argv[1]); - if (!buf) - return JS_EXCEPTION; - if (pos + len > size) - return JS_ThrowRangeError(ctx, "read/write array buffer overflow"); - if (magic) - ret = js_get_errno(write(fd, buf + pos, len)); - else - ret = js_get_errno(read(fd, buf + pos, len)); - return JS_NewInt64(ctx, ret); -} - -static JSValue js_os_isatty(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - return JS_NewBool(ctx, isatty(fd)); -} - -#if defined(_WIN32) -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - HANDLE handle; - CONSOLE_SCREEN_BUFFER_INFO info; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - - if (!GetConsoleScreenBufferInfo(handle, &info)) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, info.dwSize.X), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, info.dwSize.Y), JS_PROP_C_W_E); - return obj; -} - -/* Windows 10 built-in VT100 emulation */ -#define __ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 -#define __ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 - -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - HANDLE handle; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - handle = (HANDLE)_get_osfhandle(fd); - SetConsoleMode(handle, ENABLE_WINDOW_INPUT | __ENABLE_VIRTUAL_TERMINAL_INPUT); - _setmode(fd, _O_BINARY); - if (fd == 0) { - handle = (HANDLE)_get_osfhandle(1); /* corresponding output */ - SetConsoleMode(handle, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT | __ENABLE_VIRTUAL_TERMINAL_PROCESSING); - } - return JS_NULL; -} -#else -static JSValue js_os_ttyGetWinSize(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd; - struct winsize ws; - JSValue obj; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && - ws.ws_col >= 4 && ws.ws_row >= 4) { - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ws.ws_col), JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, ws.ws_row), JS_PROP_C_W_E); - return obj; - } else { - return JS_NULL; - } -} - -static struct termios oldtty; - -static void term_exit(void) -{ - tcsetattr(0, TCSANOW, &oldtty); -} - -/* XXX: should add a way to go back to normal mode */ -static JSValue js_os_ttySetRaw(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - struct termios tty; - int fd; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - - memset(&tty, 0, sizeof(tty)); - tcgetattr(fd, &tty); - oldtty = tty; - - tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP - |INLCR|IGNCR|ICRNL|IXON); - tty.c_oflag |= OPOST; - tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); - tty.c_cflag &= ~(CSIZE|PARENB); - tty.c_cflag |= CS8; - tty.c_cc[VMIN] = 1; - tty.c_cc[VTIME] = 0; - - tcsetattr(fd, TCSANOW, &tty); - - atexit(term_exit); - return JS_NULL; -} - -#endif /* !_WIN32 */ - -static JSValue js_os_remove(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *filename; - int ret; - - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct stat st; - if (stat(filename, &st) == 0 && S_ISDIR(st.st_mode)) { - ret = rmdir(filename); - } else { - ret = unlink(filename); - } - } -#else - ret = remove(filename); -#endif - ret = js_get_errno(ret); - JS_FreeCString(ctx, filename); - return JS_NewInt32(ctx, ret); -} - -static JSValue js_os_rename(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *oldpath, *newpath; - int ret; - - oldpath = JS_ToCString(ctx, argv[0]); - if (!oldpath) - return JS_EXCEPTION; - newpath = JS_ToCString(ctx, argv[1]); - if (!newpath) { - JS_FreeCString(ctx, oldpath); - return JS_EXCEPTION; - } - ret = js_get_errno(rename(oldpath, newpath)); - JS_FreeCString(ctx, oldpath); - JS_FreeCString(ctx, newpath); - return JS_NewInt32(ctx, ret); -} - -static BOOL is_main_thread(JSRuntime *rt) -{ - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - return !ts->recv_pipe; -} - -static JSOSRWHandler *find_rh(JSThreadState *ts, int fd) -{ - JSOSRWHandler *rh; - struct list_head *el; - - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == fd) - return rh; - } - return NULL; -} - -static void free_rw_handler(JSRuntime *rt, JSOSRWHandler *rh) -{ - int i; - list_del(&rh->link); - for(i = 0; i < 2; i++) { - JS_FreeValueRT(rt, rh->rw_func[i]); - } - js_free_rt(rt, rh); -} - -static JSValue js_os_setReadHandler(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int magic) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSOSRWHandler *rh; - int fd; - JSValueConst func; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - func = argv[1]; - if (JS_IsNull(func)) { - rh = find_rh(ts, fd); - if (rh) { - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_NULL; - if (JS_IsNull(rh->rw_func[0]) && - JS_IsNull(rh->rw_func[1])) { - /* remove the entry */ - free_rw_handler(JS_GetRuntime(ctx), rh); - } - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - rh = find_rh(ts, fd); - if (!rh) { - rh = js_mallocz(ctx, sizeof(*rh)); - if (!rh) - return JS_EXCEPTION; - rh->fd = fd; - rh->rw_func[0] = JS_NULL; - rh->rw_func[1] = JS_NULL; - list_add_tail(&rh->link, &ts->os_rw_handlers); - } - JS_FreeValue(ctx, rh->rw_func[magic]); - rh->rw_func[magic] = JS_DupValue(ctx, func); - } - return JS_NULL; -} - -static JSOSSignalHandler *find_sh(JSThreadState *ts, int sig_num) -{ - JSOSSignalHandler *sh; - struct list_head *el; - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - if (sh->sig_num == sig_num) - return sh; - } - return NULL; -} - -static void free_sh(JSRuntime *rt, JSOSSignalHandler *sh) -{ - list_del(&sh->link); - JS_FreeValueRT(rt, sh->func); - js_free_rt(rt, sh); -} - -static void os_signal_handler(int sig_num) -{ - os_pending_signals |= ((uint64_t)1 << sig_num); -} - -#if defined(_WIN32) -typedef void (*sighandler_t)(int sig_num); -#endif - -static JSValue js_os_signal(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSOSSignalHandler *sh; - uint32_t sig_num; - JSValueConst func; - sighandler_t handler; - - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "signal handler can only be set in the main thread"); - - if (JS_ToUint32(ctx, &sig_num, argv[0])) - return JS_EXCEPTION; - if (sig_num >= 64) - return JS_ThrowRangeError(ctx, "invalid signal number"); - func = argv[1]; - /* func = null: SIG_DFL, func = undefined, SIG_IGN */ - if (JS_IsNull(func) || JS_IsNull(func)) { - sh = find_sh(ts, sig_num); - if (sh) { - free_sh(JS_GetRuntime(ctx), sh); - } - if (JS_IsNull(func)) - handler = SIG_DFL; - else - handler = SIG_IGN; - signal(sig_num, handler); - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - sh = find_sh(ts, sig_num); - if (!sh) { - sh = js_mallocz(ctx, sizeof(*sh)); - if (!sh) - return JS_EXCEPTION; - sh->sig_num = sig_num; - list_add_tail(&sh->link, &ts->os_signal_handlers); - } - JS_FreeValue(ctx, sh->func); - sh->func = JS_DupValue(ctx, func); - signal(sig_num, os_signal_handler); - } - return JS_NULL; -} - -#if defined(__linux__) || defined(__APPLE__) -static int64_t get_time_ms(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000); -} - -static int64_t get_time_ns(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (uint64_t)ts.tv_sec * 1000000000 + ts.tv_nsec; -} -#else -/* more portable, but does not work if the date is updated */ -static int64_t get_time_ms(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_sec * 1000 + (tv.tv_usec / 1000); -} - -static int64_t get_time_ns(void) -{ - struct timeval tv; - gettimeofday(&tv, NULL); - return (int64_t)tv.tv_sec * 1000000000 + (tv.tv_usec * 1000); -} -#endif - -static JSValue js_os_now(JSContext *ctx, JSValue this_val, - int argc, JSValue *argv) -{ - return JS_NewFloat64(ctx, (double)get_time_ns() / 1e6); -} - -static void free_timer(JSRuntime *rt, JSOSTimer *th) -{ - list_del(&th->link); - JS_FreeValueRT(rt, th->func); - js_free_rt(rt, th); -} - -static JSValue js_os_setTimeout(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int64_t delay; - JSValueConst func; - JSOSTimer *th; - - func = argv[0]; - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (JS_ToInt64(ctx, &delay, argv[1])) - return JS_EXCEPTION; - th = js_mallocz(ctx, sizeof(*th)); - if (!th) - return JS_EXCEPTION; - th->timer_id = ts->next_timer_id; - if (ts->next_timer_id == INT32_MAX) - ts->next_timer_id = 1; - else - ts->next_timer_id++; - th->timeout = get_time_ms() + delay; - th->func = JS_DupValue(ctx, func); - list_add_tail(&th->link, &ts->os_timers); - return JS_NewInt32(ctx, th->timer_id); -} - -static JSOSTimer *find_timer_by_id(JSThreadState *ts, int timer_id) -{ - struct list_head *el; - if (timer_id <= 0) - return NULL; - list_for_each(el, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - if (th->timer_id == timer_id) - return th; - } - return NULL; -} - -static JSValue js_os_clearTimeout(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSOSTimer *th; - int timer_id; - - if (JS_ToInt32(ctx, &timer_id, argv[0])) - return JS_EXCEPTION; - th = find_timer_by_id(ts, timer_id); - if (!th) - return JS_NULL; - free_timer(rt, th); - return JS_NULL; -} - -/* return a promise */ -static JSValue js_os_sleepAsync(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int64_t delay; - JSOSTimer *th; - JSValue promise, resolving_funcs[2]; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - promise = JS_NewPromiseCapability(ctx, resolving_funcs); - if (JS_IsException(promise)) - return JS_EXCEPTION; - - th = js_mallocz(ctx, sizeof(*th)); - if (!th) { - JS_FreeValue(ctx, promise); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return JS_EXCEPTION; - } - th->timer_id = -1; - th->timeout = get_time_ms() + delay; - th->func = JS_DupValue(ctx, resolving_funcs[0]); - list_add_tail(&th->link, &ts->os_timers); - JS_FreeValue(ctx, resolving_funcs[0]); - JS_FreeValue(ctx, resolving_funcs[1]); - return promise; -} - -static void call_handler(JSContext *ctx, JSValueConst func) -{ - JSValue ret, func1; - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func1 = JS_DupValue(ctx, func); - ret = JS_Call(ctx, func1, JS_NULL, 0, NULL); - JS_FreeValue(ctx, func1); - if (JS_IsException(ret)) - js_std_dump_error(ctx); - JS_FreeValue(ctx, ret); -} - -#ifdef USE_WORKER - -#ifdef _WIN32 - -static int js_waker_init(JSWaker *w) -{ - w->handle = CreateEvent(NULL, TRUE, FALSE, NULL); - return w->handle ? 0 : -1; -} - -static void js_waker_signal(JSWaker *w) -{ - SetEvent(w->handle); -} - -static void js_waker_clear(JSWaker *w) -{ - ResetEvent(w->handle); -} - -static void js_waker_close(JSWaker *w) -{ - CloseHandle(w->handle); - w->handle = INVALID_HANDLE_VALUE; -} - -#else // !_WIN32 - -static int js_waker_init(JSWaker *w) -{ - int fds[2]; - - if (pipe(fds) < 0) - return -1; - w->read_fd = fds[0]; - w->write_fd = fds[1]; - return 0; -} - -static void js_waker_signal(JSWaker *w) -{ - int ret; - - for(;;) { - ret = write(w->write_fd, "", 1); - if (ret == 1) - break; - if (ret < 0 && (errno != EAGAIN || errno != EINTR)) - break; - } -} - -static void js_waker_clear(JSWaker *w) -{ - uint8_t buf[16]; - int ret; - - for(;;) { - ret = read(w->read_fd, buf, sizeof(buf)); - if (ret >= 0) - break; - if (errno != EAGAIN && errno != EINTR) - break; - } -} - -static void js_waker_close(JSWaker *w) -{ - close(w->read_fd); - close(w->write_fd); - w->read_fd = -1; - w->write_fd = -1; -} - -#endif // _WIN32 - -static void js_free_message(JSWorkerMessage *msg); - -/* return 1 if a message was handled, 0 if no message */ -static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) -{ - JSWorkerMessagePipe *ps = port->recv_pipe; - int ret; - struct list_head *el; - JSWorkerMessage *msg; - JSValue obj, data_obj, func, retval; - - pthread_mutex_lock(&ps->mutex); - if (!list_empty(&ps->msg_queue)) { - el = ps->msg_queue.next; - msg = list_entry(el, JSWorkerMessage, link); - - /* remove the message from the queue */ - list_del(&msg->link); - - if (list_empty(&ps->msg_queue)) - js_waker_clear(&ps->waker); - - pthread_mutex_unlock(&ps->mutex); - - data_obj = JS_ReadObject(ctx, msg->data, msg->data_len, - JS_READ_OBJ_SAB | JS_READ_OBJ_REFERENCE); - - js_free_message(msg); - - if (JS_IsException(data_obj)) - goto fail; - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) { - JS_FreeValue(ctx, data_obj); - goto fail; - } - JS_DefinePropertyValueStr(ctx, obj, "data", data_obj, JS_PROP_C_W_E); - - /* 'func' might be destroyed when calling itself (if it frees the - handler), so must take extra care */ - func = JS_DupValue(ctx, port->on_message_func); - retval = JS_Call(ctx, func, JS_NULL, 1, (JSValueConst *)&obj); - JS_FreeValue(ctx, obj); - JS_FreeValue(ctx, func); - if (JS_IsException(retval)) { - fail: - js_std_dump_error(ctx); - } else { - JS_FreeValue(ctx, retval); - } - ret = 1; - } else { - pthread_mutex_unlock(&ps->mutex); - ret = 0; - } - return ret; -} -#else -static int handle_posted_message(JSRuntime *rt, JSContext *ctx, - JSWorkerMessageHandler *port) -{ - return 0; -} -#endif /* !USE_WORKER */ - -#if defined(_WIN32) - -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int min_delay, count; - int64_t cur_time, delay; - JSOSRWHandler *rh; - struct list_head *el; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; // 64 - - /* XXX: handle signals if useful */ - - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && - list_empty(&ts->port_list)) { - return -1; /* no more events */ - } - - if (!list_empty(&ts->os_timers)) { - cur_time = get_time_ms(); - min_delay = 10000; - list_for_each(el, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - delay = th->timeout - cur_time; - if (delay <= 0) { - JSValue func; - /* the timer expired */ - func = th->func; - th->func = JS_NULL; - free_timer(rt, th); - call_handler(ctx, func); - JS_FreeValue(ctx, func); - return 0; - } else if (delay < min_delay) { - min_delay = delay; - } - } - } else { - min_delay = -1; - } - - count = 0; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { - handles[count++] = (HANDLE)_get_osfhandle(rh->fd); // stdin - if (count == (int)countof(handles)) - break; - } - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (JS_IsNull(port->on_message_func)) - continue; - handles[count++] = port->recv_pipe->waker.handle; - if (count == (int)countof(handles)) - break; - } - - if (count > 0) { - DWORD ret, timeout = INFINITE; - if (min_delay != -1) - timeout = min_delay; - ret = WaitForMultipleObjects(count, handles, FALSE, timeout); - - if (ret < count) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (rh->fd == 0 && !JS_IsNull(rh->rw_func[0])) { - call_handler(ctx, rh->rw_func[0]); - /* must stop because the list may have been modified */ - goto done; - } - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (ps->waker.handle == handles[ret]) { - if (handle_posted_message(rt, ctx, port)) - goto done; - } - } - } - } - } else { - Sleep(min_delay); - } - done: - return 0; -} - -#else - -static int js_os_poll(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - int ret, fd_max, min_delay; - int64_t cur_time, delay; - fd_set rfds, wfds; - JSOSRWHandler *rh; - struct list_head *el; - struct timeval tv, *tvp; - - /* only check signals in the main thread */ - if (!ts->recv_pipe && - unlikely(os_pending_signals != 0)) { - JSOSSignalHandler *sh; - uint64_t mask; - - list_for_each(el, &ts->os_signal_handlers) { - sh = list_entry(el, JSOSSignalHandler, link); - mask = (uint64_t)1 << sh->sig_num; - if (os_pending_signals & mask) { - os_pending_signals &= ~mask; - call_handler(ctx, sh->func); - return 0; - } - } - } - - if (list_empty(&ts->os_rw_handlers) && list_empty(&ts->os_timers) && - list_empty(&ts->port_list)) - return -1; /* no more events */ - - if (!list_empty(&ts->os_timers)) { - cur_time = get_time_ms(); - min_delay = 10000; - list_for_each(el, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - delay = th->timeout - cur_time; - if (delay <= 0) { - JSValue func; - /* the timer expired */ - func = th->func; - th->func = JS_NULL; - free_timer(rt, th); - call_handler(ctx, func); - JS_FreeValue(ctx, func); - return 0; - } else if (delay < min_delay) { - min_delay = delay; - } - } - tv.tv_sec = min_delay / 1000; - tv.tv_usec = (min_delay % 1000) * 1000; - tvp = &tv; - } else { - tvp = NULL; - } - - FD_ZERO(&rfds); - FD_ZERO(&wfds); - fd_max = -1; - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - fd_max = max_int(fd_max, rh->fd); - if (!JS_IsNull(rh->rw_func[0])) - FD_SET(rh->fd, &rfds); - if (!JS_IsNull(rh->rw_func[1])) - FD_SET(rh->fd, &wfds); - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - fd_max = max_int(fd_max, ps->waker.read_fd); - FD_SET(ps->waker.read_fd, &rfds); - } - } - - ret = select(fd_max + 1, &rfds, &wfds, NULL, tvp); - if (ret > 0) { - list_for_each(el, &ts->os_rw_handlers) { - rh = list_entry(el, JSOSRWHandler, link); - if (!JS_IsNull(rh->rw_func[0]) && - FD_ISSET(rh->fd, &rfds)) { - call_handler(ctx, rh->rw_func[0]); - /* must stop because the list may have been modified */ - goto done; - } - if (!JS_IsNull(rh->rw_func[1]) && - FD_ISSET(rh->fd, &wfds)) { - call_handler(ctx, rh->rw_func[1]); - /* must stop because the list may have been modified */ - goto done; - } - } - - list_for_each(el, &ts->port_list) { - JSWorkerMessageHandler *port = list_entry(el, JSWorkerMessageHandler, link); - if (!JS_IsNull(port->on_message_func)) { - JSWorkerMessagePipe *ps = port->recv_pipe; - if (FD_ISSET(ps->waker.read_fd, &rfds)) { - if (handle_posted_message(rt, ctx, port)) - goto done; - } - } - } - } - done: - return 0; -} -#endif /* !_WIN32 */ - -static JSValue make_obj_error(JSContext *ctx, - JSValue obj, - int err) -{ - JSValue arr; - if (JS_IsException(obj)) - return obj; - arr = JS_NewArray(ctx); - if (JS_IsException(arr)) - return JS_EXCEPTION; - JS_DefinePropertyValueUint32(ctx, arr, 0, obj, - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, arr, 1, JS_NewInt32(ctx, err), - JS_PROP_C_W_E); - return arr; -} - -static JSValue make_string_error(JSContext *ctx, - const char *buf, - int err) -{ - return make_obj_error(ctx, JS_NewString(ctx, buf), err); -} - -/* return [cwd, errorcode] */ -static JSValue js_os_getcwd(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - char buf[PATH_MAX]; - int err; - - if (!getcwd(buf, sizeof(buf))) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} - -static JSValue js_os_chdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *target; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - err = js_get_errno(chdir(target)); - JS_FreeCString(ctx, target); - return JS_NewInt32(ctx, err); -} - -static JSValue js_os_mkdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int mode, ret; - const char *path; - - if (argc >= 2) { - if (JS_ToInt32(ctx, &mode, argv[1])) - return JS_EXCEPTION; - } else { - mode = 0777; - } - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - (void)mode; - ret = js_get_errno(mkdir(path)); -#else - ret = js_get_errno(mkdir(path, mode)); -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* return [array, errorcode] */ -static JSValue js_os_readdir(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - DIR *f; - struct dirent *d; - JSValue obj; - int err; - uint32_t len; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) { - JS_FreeCString(ctx, path); - return JS_EXCEPTION; - } - f = opendir(path); - if (!f) - err = errno; - else - err = 0; - JS_FreeCString(ctx, path); - if (!f) - goto done; - len = 0; - for(;;) { - errno = 0; - d = readdir(f); - if (!d) { - err = errno; - break; - } - JS_DefinePropertyValueUint32(ctx, obj, len++, - JS_NewString(ctx, d->d_name), - JS_PROP_C_W_E); - } - closedir(f); - done: - return make_obj_error(ctx, obj, err); -} - -#if !defined(_WIN32) -static int64_t timespec_to_ms(const struct timespec *tv) -{ - return (int64_t)tv->tv_sec * 1000 + (tv->tv_nsec / 1000000); -} -#endif - -/* return [obj, errcode] */ -static JSValue js_os_stat(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv, int is_lstat) -{ - const char *path; - int err, res; - struct stat st; - JSValue obj; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - res = stat(path, &st); -#else - if (is_lstat) - res = lstat(path, &st); - else - res = stat(path, &st); -#endif - if (res < 0) - err = errno; - else - err = 0; - JS_FreeCString(ctx, path); - if (res < 0) { - obj = JS_NULL; - } else { - obj = JS_NewObject(ctx); - if (JS_IsException(obj)) - return JS_EXCEPTION; - JS_DefinePropertyValueStr(ctx, obj, "dev", - JS_NewInt64(ctx, st.st_dev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ino", - JS_NewInt64(ctx, st.st_ino), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mode", - JS_NewInt32(ctx, st.st_mode), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "nlink", - JS_NewInt64(ctx, st.st_nlink), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "uid", - JS_NewInt64(ctx, st.st_uid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "gid", - JS_NewInt64(ctx, st.st_gid), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "rdev", - JS_NewInt64(ctx, st.st_rdev), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "size", - JS_NewInt64(ctx, st.st_size), - JS_PROP_C_W_E); -#if !defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "blocks", - JS_NewInt64(ctx, st.st_blocks), - JS_PROP_C_W_E); -#endif -#if defined(_WIN32) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, (int64_t)st.st_atime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, (int64_t)st.st_mtime * 1000), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, (int64_t)st.st_ctime * 1000), - JS_PROP_C_W_E); -#elif defined(__APPLE__) - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtimespec)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctimespec)), - JS_PROP_C_W_E); -#else - JS_DefinePropertyValueStr(ctx, obj, "atime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_atim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "mtime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_mtim)), - JS_PROP_C_W_E); - JS_DefinePropertyValueStr(ctx, obj, "ctime", - JS_NewInt64(ctx, timespec_to_ms(&st.st_ctim)), - JS_PROP_C_W_E); -#endif - } - return make_obj_error(ctx, obj, err); -} - -#if !defined(_WIN32) -static void ms_to_timeval(struct timeval *tv, uint64_t v) -{ - tv->tv_sec = v / 1000; - tv->tv_usec = (v % 1000) * 1000; -} -#endif - -static JSValue js_os_utimes(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - int64_t atime, mtime; - int ret; - - if (JS_ToInt64(ctx, &atime, argv[1])) - return JS_EXCEPTION; - if (JS_ToInt64(ctx, &mtime, argv[2])) - return JS_EXCEPTION; - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; -#if defined(_WIN32) - { - struct _utimbuf times; - times.actime = atime / 1000; - times.modtime = mtime / 1000; - ret = js_get_errno(_utime(path, ×)); - } -#else - { - struct timeval times[2]; - ms_to_timeval(×[0], atime); - ms_to_timeval(×[1], mtime); - ret = js_get_errno(utimes(path, times)); - } -#endif - JS_FreeCString(ctx, path); - return JS_NewInt32(ctx, ret); -} - -/* sleep(delay_ms) */ -static JSValue js_os_sleep(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int64_t delay; - int ret; - - if (JS_ToInt64(ctx, &delay, argv[0])) - return JS_EXCEPTION; - if (delay < 0) - delay = 0; -#if defined(_WIN32) - { - if (delay > INT32_MAX) - delay = INT32_MAX; - Sleep(delay); - ret = 0; - } -#else - { - struct timespec ts; - - ts.tv_sec = delay / 1000; - ts.tv_nsec = (delay % 1000) * 1000000; - ret = js_get_errno(nanosleep(&ts, NULL)); - } -#endif - return JS_NewInt32(ctx, ret); -} - -#if defined(_WIN32) -static char *realpath(const char *path, char *buf) -{ - if (!_fullpath(buf, path, PATH_MAX)) { - errno = ENOENT; - return NULL; - } else { - return buf; - } -} -#endif - -/* return [path, errorcode] */ -static JSValue js_os_realpath(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - char buf[PATH_MAX], *res; - int err; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = realpath(path, buf); - JS_FreeCString(ctx, path); - if (!res) { - buf[0] = '\0'; - err = errno; - } else { - err = 0; - } - return make_string_error(ctx, buf, err); -} - -#if !defined(_WIN32) -static JSValue js_os_symlink(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *target, *linkpath; - int err; - - target = JS_ToCString(ctx, argv[0]); - if (!target) - return JS_EXCEPTION; - linkpath = JS_ToCString(ctx, argv[1]); - if (!linkpath) { - JS_FreeCString(ctx, target); - return JS_EXCEPTION; - } - err = js_get_errno(symlink(target, linkpath)); - JS_FreeCString(ctx, target); - JS_FreeCString(ctx, linkpath); - return JS_NewInt32(ctx, err); -} - -/* return [path, errorcode] */ -static JSValue js_os_readlink(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - const char *path; - char buf[PATH_MAX]; - int err; - ssize_t res; - - path = JS_ToCString(ctx, argv[0]); - if (!path) - return JS_EXCEPTION; - res = readlink(path, buf, sizeof(buf) - 1); - if (res < 0) { - buf[0] = '\0'; - err = errno; - } else { - buf[res] = '\0'; - err = 0; - } - JS_FreeCString(ctx, path); - return make_string_error(ctx, buf, err); -} - -static char **build_envp(JSContext *ctx, JSValueConst obj) -{ - uint32_t len, i; - JSPropertyEnum *tab; - char **envp, *pair; - const char *key, *str; - JSValue val; - size_t key_len, str_len; - - if (JS_GetOwnPropertyNames(ctx, &tab, &len, obj, - JS_GPN_STRING_MASK | JS_GPN_ENUM_ONLY) < 0) - return NULL; - envp = js_mallocz(ctx, sizeof(envp[0]) * ((size_t)len + 1)); - if (!envp) - goto fail; - for(i = 0; i < len; i++) { - val = JS_GetProperty(ctx, obj, tab[i].atom); - if (JS_IsException(val)) - goto fail; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto fail; - key = JS_AtomToCString(ctx, tab[i].atom); - if (!key) { - JS_FreeCString(ctx, str); - goto fail; - } - key_len = strlen(key); - str_len = strlen(str); - pair = js_malloc(ctx, key_len + str_len + 2); - if (!pair) { - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - goto fail; - } - memcpy(pair, key, key_len); - pair[key_len] = '='; - memcpy(pair + key_len + 1, str, str_len); - pair[key_len + 1 + str_len] = '\0'; - envp[i] = pair; - JS_FreeCString(ctx, key); - JS_FreeCString(ctx, str); - } - done: - JS_FreePropertyEnum(ctx, tab, len); - return envp; - fail: - if (envp) { - for(i = 0; i < len; i++) - js_free(ctx, envp[i]); - js_free(ctx, envp); - envp = NULL; - } - goto done; -} - -/* execvpe is not available on non GNU systems */ -static int my_execvpe(const char *filename, char **argv, char **envp) -{ - char *path, *p, *p_next, *p1; - char buf[PATH_MAX]; - size_t filename_len, path_len; - BOOL eacces_error; - - filename_len = strlen(filename); - if (filename_len == 0) { - errno = ENOENT; - return -1; - } - if (strchr(filename, '/')) - return execve(filename, argv, envp); - - path = getenv("PATH"); - if (!path) - path = (char *)"/bin:/usr/bin"; - eacces_error = FALSE; - p = path; - for(p = path; p != NULL; p = p_next) { - p1 = strchr(p, ':'); - if (!p1) { - p_next = NULL; - path_len = strlen(p); - } else { - p_next = p1 + 1; - path_len = p1 - p; - } - /* path too long */ - if ((path_len + 1 + filename_len + 1) > PATH_MAX) - continue; - memcpy(buf, p, path_len); - buf[path_len] = '/'; - memcpy(buf + path_len + 1, filename, filename_len); - buf[path_len + 1 + filename_len] = '\0'; - - execve(buf, argv, envp); - - switch(errno) { - case EACCES: - eacces_error = TRUE; - break; - case ENOENT: - case ENOTDIR: - break; - default: - return -1; - } - } - if (eacces_error) - errno = EACCES; - return -1; -} - -/* exec(args[, options]) -> exitcode */ -static JSValue js_os_exec(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValueConst options, args = argv[0]; - JSValue val, ret_val; - const char **exec_argv, *file = NULL, *str, *cwd = NULL; - char **envp = environ; - uint32_t exec_argc, i; - int ret, pid, status; - BOOL block_flag = TRUE, use_path = TRUE; - static const char *std_name[3] = { "stdin", "stdout", "stderr" }; - int std_fds[3]; - uint32_t uid = -1, gid = -1; - - val = JS_GetPropertyStr(ctx, args, "length"); - if (JS_IsException(val)) - return JS_EXCEPTION; - ret = JS_ToUint32(ctx, &exec_argc, val); - JS_FreeValue(ctx, val); - if (ret) - return JS_EXCEPTION; - /* arbitrary limit to avoid overflow */ - if (exec_argc < 1 || exec_argc > 65535) { - return JS_ThrowTypeError(ctx, "invalid number of arguments"); - } - exec_argv = js_mallocz(ctx, sizeof(exec_argv[0]) * (exec_argc + 1)); - if (!exec_argv) - return JS_EXCEPTION; - for(i = 0; i < exec_argc; i++) { - val = JS_GetPropertyUint32(ctx, args, i); - if (JS_IsException(val)) - goto exception; - str = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!str) - goto exception; - exec_argv[i] = str; - } - exec_argv[exec_argc] = NULL; - - for(i = 0; i < 3; i++) - std_fds[i] = i; - - /* get the options, if any */ - if (argc >= 2) { - options = argv[1]; - - if (get_bool_option(ctx, &block_flag, options, "block")) - goto exception; - if (get_bool_option(ctx, &use_path, options, "usePath")) - goto exception; - - val = JS_GetPropertyStr(ctx, options, "file"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - file = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!file) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "cwd"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - cwd = JS_ToCString(ctx, val); - JS_FreeValue(ctx, val); - if (!cwd) - goto exception; - } - - /* stdin/stdout/stderr handles */ - for(i = 0; i < 3; i++) { - val = JS_GetPropertyStr(ctx, options, std_name[i]); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - int fd; - ret = JS_ToInt32(ctx, &fd, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - std_fds[i] = fd; - } - } - - val = JS_GetPropertyStr(ctx, options, "env"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - envp = build_envp(ctx, val); - JS_FreeValue(ctx, val); - if (!envp) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "uid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - ret = JS_ToUint32(ctx, &uid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - - val = JS_GetPropertyStr(ctx, options, "gid"); - if (JS_IsException(val)) - goto exception; - if (!JS_IsNull(val)) { - ret = JS_ToUint32(ctx, &gid, val); - JS_FreeValue(ctx, val); - if (ret) - goto exception; - } - } - - pid = fork(); - if (pid < 0) { - JS_ThrowTypeError(ctx, "fork error"); - goto exception; - } - if (pid == 0) { - /* child */ - - /* remap the stdin/stdout/stderr handles if necessary */ - for(i = 0; i < 3; i++) { - if (std_fds[i] != i) { - if (dup2(std_fds[i], i) < 0) - _exit(127); - } - } -#if defined(HAVE_CLOSEFROM) - /* closefrom() is available on many recent unix systems: - Linux with glibc 2.34+, Solaris 9+, FreeBSD 7.3+, - NetBSD 3.0+, OpenBSD 3.5+. - Linux with the musl libc and macOS don't have it. - */ - - closefrom(3); -#else - { - /* Close the file handles manually, limit to 1024 to avoid - costly loop on linux Alpine where sysconf(_SC_OPEN_MAX) - returns a huge value 1048576. - Patch inspired by nicolas-duteil-nova. See also: - https://stackoverflow.com/questions/73229353/ - https://stackoverflow.com/questions/899038/#918469 - */ - int fd_max = min_int(sysconf(_SC_OPEN_MAX), 1024); - for(i = 3; i < fd_max; i++) - close(i); - } -#endif - if (cwd) { - if (chdir(cwd) < 0) - _exit(127); - } - if (uid != -1) { - if (setuid(uid) < 0) - _exit(127); - } - if (gid != -1) { - if (setgid(gid) < 0) - _exit(127); - } - - if (!file) - file = exec_argv[0]; - if (use_path) - ret = my_execvpe(file, (char **)exec_argv, envp); - else - ret = execve(file, (char **)exec_argv, envp); - _exit(127); - } - /* parent */ - if (block_flag) { - for(;;) { - ret = waitpid(pid, &status, 0); - if (ret == pid) { - if (WIFEXITED(status)) { - ret = WEXITSTATUS(status); - break; - } else if (WIFSIGNALED(status)) { - ret = -WTERMSIG(status); - break; - } - } - } - } else { - ret = pid; - } - ret_val = JS_NewInt32(ctx, ret); - done: - JS_FreeCString(ctx, file); - JS_FreeCString(ctx, cwd); - for(i = 0; i < exec_argc; i++) - JS_FreeCString(ctx, exec_argv[i]); - js_free(ctx, exec_argv); - if (envp != environ) { - char **p; - p = envp; - while (*p != NULL) { - js_free(ctx, *p); - p++; - } - js_free(ctx, envp); - } - return ret_val; - exception: - ret_val = JS_EXCEPTION; - goto done; -} - -/* getpid() -> pid */ -static JSValue js_os_getpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - return JS_NewInt32(ctx, getpid()); -} - -/* waitpid(pid, block) -> [pid, status] */ -static JSValue js_os_waitpid(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid, status, options, ret; - JSValue obj; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &options, argv[1])) - return JS_EXCEPTION; - - ret = waitpid(pid, &status, options); - if (ret < 0) { - ret = -errno; - status = 0; - } - - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, ret), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, status), - JS_PROP_C_W_E); - return obj; -} - -/* pipe() -> [read_fd, write_fd] or null if error */ -static JSValue js_os_pipe(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pipe_fds[2], ret; - JSValue obj; - - ret = pipe(pipe_fds); - if (ret < 0) - return JS_NULL; - obj = JS_NewArray(ctx); - if (JS_IsException(obj)) - return obj; - JS_DefinePropertyValueUint32(ctx, obj, 0, JS_NewInt32(ctx, pipe_fds[0]), - JS_PROP_C_W_E); - JS_DefinePropertyValueUint32(ctx, obj, 1, JS_NewInt32(ctx, pipe_fds[1]), - JS_PROP_C_W_E); - return obj; -} - -/* kill(pid, sig) */ -static JSValue js_os_kill(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int pid, sig, ret; - - if (JS_ToInt32(ctx, &pid, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &sig, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(kill(pid, sig)); - return JS_NewInt32(ctx, ret); -} - -/* dup(fd) */ -static JSValue js_os_dup(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - ret = js_get_errno(dup(fd)); - return JS_NewInt32(ctx, ret); -} - -/* dup2(fd) */ -static JSValue js_os_dup2(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int fd, fd2, ret; - - if (JS_ToInt32(ctx, &fd, argv[0])) - return JS_EXCEPTION; - if (JS_ToInt32(ctx, &fd2, argv[1])) - return JS_EXCEPTION; - ret = js_get_errno(dup2(fd, fd2)); - return JS_NewInt32(ctx, ret); -} - -#endif /* !_WIN32 */ - -#ifdef USE_WORKER - -/* Worker */ - -typedef struct { - JSWorkerMessagePipe *recv_pipe; - JSWorkerMessagePipe *send_pipe; - JSWorkerMessageHandler *msg_handler; -} JSWorkerData; - -typedef struct { - char *filename; /* module filename */ - char *basename; /* module base name */ - JSWorkerMessagePipe *recv_pipe, *send_pipe; - int strip_flags; -} WorkerFuncArgs; - -typedef struct { - int ref_count; - uint64_t buf[0]; -} JSSABHeader; - -static JSClassID js_worker_class_id; -static JSContext *(*js_worker_new_context_func)(JSRuntime *rt); - -static int atomic_add_int(int *ptr, int v) -{ - return atomic_fetch_add((_Atomic(uint32_t) *)ptr, v) + v; -} - -/* shared array buffer allocator */ -static void *js_sab_alloc(void *opaque, size_t size) -{ - JSSABHeader *sab; - sab = malloc(sizeof(JSSABHeader) + size); - if (!sab) - return NULL; - sab->ref_count = 1; - return sab->buf; -} - -static void js_sab_free(void *opaque, void *ptr) -{ - JSSABHeader *sab; - int ref_count; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - ref_count = atomic_add_int(&sab->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - free(sab); - } -} - -static void js_sab_dup(void *opaque, void *ptr) -{ - JSSABHeader *sab; - sab = (JSSABHeader *)((uint8_t *)ptr - sizeof(JSSABHeader)); - atomic_add_int(&sab->ref_count, 1); -} - -static JSWorkerMessagePipe *js_new_message_pipe(void) -{ - JSWorkerMessagePipe *ps; - - ps = malloc(sizeof(*ps)); - if (!ps) - return NULL; - if (js_waker_init(&ps->waker)) { - free(ps); - return NULL; - } - ps->ref_count = 1; - init_list_head(&ps->msg_queue); - pthread_mutex_init(&ps->mutex, NULL); - return ps; -} - -static JSWorkerMessagePipe *js_dup_message_pipe(JSWorkerMessagePipe *ps) -{ - atomic_add_int(&ps->ref_count, 1); - return ps; -} - -static void js_free_message(JSWorkerMessage *msg) -{ - size_t i; - /* free the SAB */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_free(NULL, msg->sab_tab[i]); - } - free(msg->sab_tab); - free(msg->data); - free(msg); -} - -static void js_free_message_pipe(JSWorkerMessagePipe *ps) -{ - struct list_head *el, *el1; - JSWorkerMessage *msg; - int ref_count; - - if (!ps) - return; - - ref_count = atomic_add_int(&ps->ref_count, -1); - assert(ref_count >= 0); - if (ref_count == 0) { - list_for_each_safe(el, el1, &ps->msg_queue) { - msg = list_entry(el, JSWorkerMessage, link); - js_free_message(msg); - } - pthread_mutex_destroy(&ps->mutex); - js_waker_close(&ps->waker); - free(ps); - } -} - -static void js_free_port(JSRuntime *rt, JSWorkerMessageHandler *port) -{ - if (port) { - js_free_message_pipe(port->recv_pipe); - JS_FreeValueRT(rt, port->on_message_func); - list_del(&port->link); - js_free_rt(rt, port); - } -} - -static void js_worker_finalizer(JSRuntime *rt, JSValue val) -{ - JSWorkerData *worker = JS_GetOpaque(val, js_worker_class_id); - if (worker) { - js_free_message_pipe(worker->recv_pipe); - js_free_message_pipe(worker->send_pipe); - js_free_port(rt, worker->msg_handler); - js_free_rt(rt, worker); - } -} - -static JSClassDef js_worker_class = { - "Worker", - .finalizer = js_worker_finalizer, -}; - -static void *worker_func(void *opaque) -{ - WorkerFuncArgs *args = opaque; - JSRuntime *rt; - JSThreadState *ts; - JSContext *ctx; - JSValue val; - - rt = JS_NewRuntime(); - if (rt == NULL) { - fprintf(stderr, "JS_NewRuntime failure"); - exit(1); - } - JS_SetStripInfo(rt, args->strip_flags); - js_std_init_handlers(rt); - - JS_SetModuleLoaderFunc2(rt, NULL, js_module_loader, js_module_check_attributes, NULL); - - /* set the pipe to communicate with the parent */ - ts = JS_GetRuntimeOpaque(rt); - ts->recv_pipe = args->recv_pipe; - ts->send_pipe = args->send_pipe; - - /* function pointer to avoid linking the whole JS_NewContext() if - not needed */ - ctx = js_worker_new_context_func(rt); - if (ctx == NULL) { - fprintf(stderr, "JS_NewContext failure"); - } - - JS_SetCanBlock(rt, TRUE); - - js_std_add_helpers(ctx, -1, NULL); - - val = JS_LoadModule(ctx, args->basename, args->filename); - free(args->filename); - free(args->basename); - free(args); - val = js_std_await(ctx, val); - if (JS_IsException(val)) - js_std_dump_error(ctx); - JS_FreeValue(ctx, val); - - js_std_loop(ctx); - - JS_FreeContext(ctx); - js_std_free_handlers(rt); - JS_FreeRuntime(rt); - return NULL; -} - -static JSValue js_worker_ctor_internal(JSContext *ctx, JSValueConst new_target, - JSWorkerMessagePipe *recv_pipe, - JSWorkerMessagePipe *send_pipe) -{ - JSValue obj = JS_NULL, proto; - JSWorkerData *s; - - /* create the object */ - if (JS_IsNull(new_target)) { - proto = JS_GetClassProto(ctx, js_worker_class_id); - } else { - proto = JS_GetPropertyStr(ctx, new_target, "prototype"); - if (JS_IsException(proto)) - goto fail; - } - obj = JS_NewObjectProtoClass(ctx, proto, js_worker_class_id); - JS_FreeValue(ctx, proto); - if (JS_IsException(obj)) - goto fail; - s = js_mallocz(ctx, sizeof(*s)); - if (!s) - goto fail; - s->recv_pipe = js_dup_message_pipe(recv_pipe); - s->send_pipe = js_dup_message_pipe(send_pipe); - - JS_SetOpaque(obj, s); - return obj; - fail: - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_ctor(JSContext *ctx, JSValueConst new_target, - int argc, JSValueConst *argv) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - WorkerFuncArgs *args = NULL; - pthread_t tid; - pthread_attr_t attr; - JSValue obj = JS_NULL; - int ret; - const char *filename = NULL, *basename; - JSAtom basename_atom; - - /* XXX: in order to avoid problems with resource liberation, we - don't support creating workers inside workers */ - if (!is_main_thread(rt)) - return JS_ThrowTypeError(ctx, "cannot create a worker inside a worker"); - - /* base name, assuming the calling function is a normal JS - function */ - basename_atom = JS_GetScriptOrModuleName(ctx, 1); - if (basename_atom == JS_ATOM_NULL) { - return JS_ThrowTypeError(ctx, "could not determine calling script or module name"); - } - basename = JS_AtomToCString(ctx, basename_atom); - JS_FreeAtom(ctx, basename_atom); - if (!basename) - goto fail; - - /* module name */ - filename = JS_ToCString(ctx, argv[0]); - if (!filename) - goto fail; - - args = malloc(sizeof(*args)); - if (!args) - goto oom_fail; - memset(args, 0, sizeof(*args)); - args->filename = strdup(filename); - args->basename = strdup(basename); - - /* ports */ - args->recv_pipe = js_new_message_pipe(); - if (!args->recv_pipe) - goto oom_fail; - args->send_pipe = js_new_message_pipe(); - if (!args->send_pipe) - goto oom_fail; - - args->strip_flags = JS_GetStripInfo(rt); - - obj = js_worker_ctor_internal(ctx, new_target, - args->send_pipe, args->recv_pipe); - if (JS_IsException(obj)) - goto fail; - - pthread_attr_init(&attr); - /* no join at the end */ - pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); - ret = pthread_create(&tid, &attr, worker_func, args); - pthread_attr_destroy(&attr); - if (ret != 0) { - JS_ThrowTypeError(ctx, "could not create worker"); - goto fail; - } - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - return obj; - oom_fail: - JS_ThrowOutOfMemory(ctx); - fail: - JS_FreeCString(ctx, basename); - JS_FreeCString(ctx, filename); - if (args) { - free(args->filename); - free(args->basename); - js_free_message_pipe(args->recv_pipe); - js_free_message_pipe(args->send_pipe); - free(args); - } - JS_FreeValue(ctx, obj); - return JS_EXCEPTION; -} - -static JSValue js_worker_postMessage(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); - JSWorkerMessagePipe *ps; - size_t data_len, sab_tab_len, i; - uint8_t *data; - JSWorkerMessage *msg; - uint8_t **sab_tab; - - if (!worker) - return JS_EXCEPTION; - - data = JS_WriteObject2(ctx, &data_len, argv[0], - JS_WRITE_OBJ_SAB | JS_WRITE_OBJ_REFERENCE, - &sab_tab, &sab_tab_len); - if (!data) - return JS_EXCEPTION; - - msg = malloc(sizeof(*msg)); - if (!msg) - goto fail; - msg->data = NULL; - msg->sab_tab = NULL; - - /* must reallocate because the allocator may be different */ - msg->data = malloc(data_len); - if (!msg->data) - goto fail; - memcpy(msg->data, data, data_len); - msg->data_len = data_len; - - if (sab_tab_len > 0) { - msg->sab_tab = malloc(sizeof(msg->sab_tab[0]) * sab_tab_len); - if (!msg->sab_tab) - goto fail; - memcpy(msg->sab_tab, sab_tab, sizeof(msg->sab_tab[0]) * sab_tab_len); - } - msg->sab_tab_len = sab_tab_len; - - js_free(ctx, data); - js_free(ctx, sab_tab); - - /* increment the SAB reference counts */ - for(i = 0; i < msg->sab_tab_len; i++) { - js_sab_dup(NULL, msg->sab_tab[i]); - } - - ps = worker->send_pipe; - pthread_mutex_lock(&ps->mutex); - /* indicate that data is present */ - if (list_empty(&ps->msg_queue)) - js_waker_signal(&ps->waker); - list_add_tail(&msg->link, &ps->msg_queue); - pthread_mutex_unlock(&ps->mutex); - return JS_NULL; - fail: - if (msg) { - free(msg->data); - free(msg->sab_tab); - free(msg); - } - js_free(ctx, data); - js_free(ctx, sab_tab); - return JS_EXCEPTION; - -} - -static JSValue js_worker_set_onmessage(JSContext *ctx, JSValueConst this_val, - JSValueConst func) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); - JSWorkerMessageHandler *port; - - if (!worker) - return JS_EXCEPTION; - - port = worker->msg_handler; - if (JS_IsNull(func)) { - if (port) { - js_free_port(rt, port); - worker->msg_handler = NULL; - } - } else { - if (!JS_IsFunction(ctx, func)) - return JS_ThrowTypeError(ctx, "not a function"); - if (!port) { - port = js_mallocz(ctx, sizeof(*port)); - if (!port) - return JS_EXCEPTION; - port->recv_pipe = js_dup_message_pipe(worker->recv_pipe); - port->on_message_func = JS_NULL; - list_add_tail(&port->link, &ts->port_list); - worker->msg_handler = port; - } - JS_FreeValue(ctx, port->on_message_func); - port->on_message_func = JS_DupValue(ctx, func); - } - return JS_NULL; -} - -static JSValue js_worker_get_onmessage(JSContext *ctx, JSValueConst this_val) -{ - JSWorkerData *worker = JS_GetOpaque2(ctx, this_val, js_worker_class_id); - JSWorkerMessageHandler *port; - if (!worker) - return JS_EXCEPTION; - port = worker->msg_handler; - if (port) { - return JS_DupValue(ctx, port->on_message_func); - } else { - return JS_NULL; - } -} - -static const JSCFunctionListEntry js_worker_proto_funcs[] = { - JS_CFUNC_DEF("postMessage", 1, js_worker_postMessage ), - JS_CGETSET_DEF("onmessage", js_worker_get_onmessage, js_worker_set_onmessage ), -}; - -#endif /* USE_WORKER */ - -void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)) -{ -#ifdef USE_WORKER - js_worker_new_context_func = func; -#endif -} - -#if defined(_WIN32) -#define OS_PLATFORM "win32" -#elif defined(__APPLE__) -#define OS_PLATFORM "darwin" -#elif defined(EMSCRIPTEN) -#define OS_PLATFORM "js" -#else -#define OS_PLATFORM "linux" -#endif - -#define OS_FLAG(x) JS_PROP_INT32_DEF(#x, x, JS_PROP_CONFIGURABLE ) - -static const JSCFunctionListEntry js_os_funcs[] = { - JS_CFUNC_DEF("open", 2, js_os_open ), - OS_FLAG(O_RDONLY), - OS_FLAG(O_WRONLY), - OS_FLAG(O_RDWR), - OS_FLAG(O_APPEND), - OS_FLAG(O_CREAT), - OS_FLAG(O_EXCL), - OS_FLAG(O_TRUNC), -#if defined(_WIN32) - OS_FLAG(O_BINARY), - OS_FLAG(O_TEXT), -#endif - JS_CFUNC_DEF("close", 1, js_os_close ), - JS_CFUNC_DEF("seek", 3, js_os_seek ), - JS_CFUNC_MAGIC_DEF("read", 4, js_os_read_write, 0 ), - JS_CFUNC_MAGIC_DEF("write", 4, js_os_read_write, 1 ), - JS_CFUNC_DEF("isatty", 1, js_os_isatty ), - JS_CFUNC_DEF("ttyGetWinSize", 1, js_os_ttyGetWinSize ), - JS_CFUNC_DEF("ttySetRaw", 1, js_os_ttySetRaw ), - JS_CFUNC_DEF("remove", 1, js_os_remove ), - JS_CFUNC_DEF("rename", 2, js_os_rename ), - JS_CFUNC_MAGIC_DEF("setReadHandler", 2, js_os_setReadHandler, 0 ), - JS_CFUNC_MAGIC_DEF("setWriteHandler", 2, js_os_setReadHandler, 1 ), - JS_CFUNC_DEF("signal", 2, js_os_signal ), - OS_FLAG(SIGINT), - OS_FLAG(SIGABRT), - OS_FLAG(SIGFPE), - OS_FLAG(SIGILL), - OS_FLAG(SIGSEGV), - OS_FLAG(SIGTERM), -#if !defined(_WIN32) - OS_FLAG(SIGQUIT), - OS_FLAG(SIGPIPE), - OS_FLAG(SIGALRM), - OS_FLAG(SIGUSR1), - OS_FLAG(SIGUSR2), - OS_FLAG(SIGCHLD), - OS_FLAG(SIGCONT), - OS_FLAG(SIGSTOP), - OS_FLAG(SIGTSTP), - OS_FLAG(SIGTTIN), - OS_FLAG(SIGTTOU), -#endif - JS_CFUNC_DEF("now", 0, js_os_now ), - JS_CFUNC_DEF("setTimeout", 2, js_os_setTimeout ), - JS_CFUNC_DEF("clearTimeout", 1, js_os_clearTimeout ), - JS_CFUNC_DEF("sleepAsync", 1, js_os_sleepAsync ), - JS_PROP_STRING_DEF("platform", OS_PLATFORM, 0 ), - JS_CFUNC_DEF("getcwd", 0, js_os_getcwd ), - JS_CFUNC_DEF("chdir", 0, js_os_chdir ), - JS_CFUNC_DEF("mkdir", 1, js_os_mkdir ), - JS_CFUNC_DEF("readdir", 1, js_os_readdir ), - /* st_mode constants */ - OS_FLAG(S_IFMT), - OS_FLAG(S_IFIFO), - OS_FLAG(S_IFCHR), - OS_FLAG(S_IFDIR), - OS_FLAG(S_IFBLK), - OS_FLAG(S_IFREG), -#if !defined(_WIN32) - OS_FLAG(S_IFSOCK), - OS_FLAG(S_IFLNK), - OS_FLAG(S_ISGID), - OS_FLAG(S_ISUID), -#endif - JS_CFUNC_MAGIC_DEF("stat", 1, js_os_stat, 0 ), - JS_CFUNC_DEF("utimes", 3, js_os_utimes ), - JS_CFUNC_DEF("sleep", 1, js_os_sleep ), - JS_CFUNC_DEF("realpath", 1, js_os_realpath ), -#if !defined(_WIN32) - JS_CFUNC_MAGIC_DEF("lstat", 1, js_os_stat, 1 ), - JS_CFUNC_DEF("symlink", 2, js_os_symlink ), - JS_CFUNC_DEF("readlink", 1, js_os_readlink ), - JS_CFUNC_DEF("exec", 1, js_os_exec ), - JS_CFUNC_DEF("getpid", 0, js_os_getpid ), - JS_CFUNC_DEF("waitpid", 2, js_os_waitpid ), - OS_FLAG(WNOHANG), - JS_CFUNC_DEF("pipe", 0, js_os_pipe ), - JS_CFUNC_DEF("kill", 2, js_os_kill ), - JS_CFUNC_DEF("dup", 1, js_os_dup ), - JS_CFUNC_DEF("dup2", 2, js_os_dup2 ), -#endif -}; - -static int js_os_init(JSContext *ctx, JSModuleDef *m) -{ - os_poll_func = js_os_poll; - -#ifdef USE_WORKER - { - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSValue proto, obj; - /* Worker class */ - JS_NewClassID(&js_worker_class_id); - JS_NewClass(JS_GetRuntime(ctx), js_worker_class_id, &js_worker_class); - proto = JS_NewObject(ctx); - JS_SetPropertyFunctionList(ctx, proto, js_worker_proto_funcs, countof(js_worker_proto_funcs)); - - obj = JS_NewCFunction2(ctx, js_worker_ctor, "Worker", 1, - JS_CFUNC_constructor, 0); - JS_SetConstructor(ctx, obj, proto); - - JS_SetClassProto(ctx, js_worker_class_id, proto); - - /* set 'Worker.parent' if necessary */ - if (ts->recv_pipe && ts->send_pipe) { - JS_DefinePropertyValueStr(ctx, obj, "parent", - js_worker_ctor_internal(ctx, JS_NULL, ts->recv_pipe, ts->send_pipe), - JS_PROP_C_W_E); - } - - JS_SetModuleExport(ctx, m, "Worker", obj); - } -#endif /* USE_WORKER */ - - return JS_SetModuleExportList(ctx, m, js_os_funcs, - countof(js_os_funcs)); -} - -JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name) -{ - JSModuleDef *m; - m = JS_NewCModule(ctx, module_name, js_os_init); - if (!m) - return NULL; - JS_AddModuleExportList(ctx, m, js_os_funcs, countof(js_os_funcs)); -#ifdef USE_WORKER - JS_AddModuleExport(ctx, m, "Worker"); -#endif - return m; -} - -/**********************************************************/ - -static JSValue js_print(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - int i; - JSValueConst v; - - for(i = 0; i < argc; i++) { - if (i != 0) - putchar(' '); - v = argv[i]; - if (JS_IsString(v)) { - const char *str; - size_t len; - str = JS_ToCStringLen(ctx, &len, v); - if (!str) - return JS_EXCEPTION; - fwrite(str, 1, len, stdout); - JS_FreeCString(ctx, str); - } else { - JS_PrintValue(ctx, js_print_value_write, stdout, v, NULL); - } - } - putchar('\n'); - return JS_NULL; -} - -static JSValue js_console_log(JSContext *ctx, JSValueConst this_val, - int argc, JSValueConst *argv) -{ - JSValue ret; - ret = js_print(ctx, this_val, argc, argv); - fflush(stdout); - return ret; -} - -void js_std_add_helpers(JSContext *ctx, int argc, char **argv) -{ - JSValue global_obj, console, args, performance; - int i; - - /* XXX: should these global definitions be enumerable? */ - global_obj = JS_GetGlobalObject(ctx); - - console = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, console, "log", - JS_NewCFunction(ctx, js_console_log, "log", 1)); - JS_SetPropertyStr(ctx, global_obj, "console", console); - - performance = JS_NewObject(ctx); - JS_SetPropertyStr(ctx, performance, "now", - JS_NewCFunction(ctx, js_os_now, "now", 0)); - JS_SetPropertyStr(ctx, global_obj, "performance", performance); - - /* same methods as the mozilla JS shell */ - if (argc >= 0) { - args = JS_NewArray(ctx); - for(i = 0; i < argc; i++) { - JS_SetPropertyUint32(ctx, args, i, JS_NewString(ctx, argv[i])); - } - JS_SetPropertyStr(ctx, global_obj, "scriptArgs", args); - } - - JS_SetPropertyStr(ctx, global_obj, "print", - JS_NewCFunction(ctx, js_print, "print", 1)); - JS_SetPropertyStr(ctx, global_obj, "__loadScript", - JS_NewCFunction(ctx, js_loadScript, "__loadScript", 1)); - - JS_FreeValue(ctx, global_obj); -} - -void js_std_init_handlers(JSRuntime *rt) -{ - JSThreadState *ts; - - ts = malloc(sizeof(*ts)); - if (!ts) { - fprintf(stderr, "Could not allocate memory for the worker"); - exit(1); - } - memset(ts, 0, sizeof(*ts)); - init_list_head(&ts->os_rw_handlers); - init_list_head(&ts->os_signal_handlers); - init_list_head(&ts->os_timers); - init_list_head(&ts->port_list); - init_list_head(&ts->rejected_promise_list); - ts->next_timer_id = 1; - - JS_SetRuntimeOpaque(rt, ts); - -#ifdef USE_WORKER - /* set the SharedArrayBuffer memory handlers */ - { - JSSharedArrayBufferFunctions sf; - memset(&sf, 0, sizeof(sf)); - sf.sab_alloc = js_sab_alloc; - sf.sab_free = js_sab_free; - sf.sab_dup = js_sab_dup; - JS_SetSharedArrayBufferFunctions(rt, &sf); - } -#endif -} - -void js_std_free_handlers(JSRuntime *rt) -{ - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - struct list_head *el, *el1; - - list_for_each_safe(el, el1, &ts->os_rw_handlers) { - JSOSRWHandler *rh = list_entry(el, JSOSRWHandler, link); - free_rw_handler(rt, rh); - } - - list_for_each_safe(el, el1, &ts->os_signal_handlers) { - JSOSSignalHandler *sh = list_entry(el, JSOSSignalHandler, link); - free_sh(rt, sh); - } - - list_for_each_safe(el, el1, &ts->os_timers) { - JSOSTimer *th = list_entry(el, JSOSTimer, link); - free_timer(rt, th); - } - - list_for_each_safe(el, el1, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - JS_FreeValueRT(rt, rp->promise); - JS_FreeValueRT(rt, rp->reason); - free(rp); - } - -#ifdef USE_WORKER - /* XXX: free port_list ? */ - js_free_message_pipe(ts->recv_pipe); - js_free_message_pipe(ts->send_pipe); -#endif - - free(ts); - JS_SetRuntimeOpaque(rt, NULL); /* fail safe */ -} - -static void js_std_dump_error1(JSContext *ctx, JSValueConst exception_val) -{ - JS_PrintValue(ctx, js_print_value_write, stderr, exception_val, NULL); - fputc('\n', stderr); -} - -void js_std_dump_error(JSContext *ctx) -{ - JSValue exception_val; - - exception_val = JS_GetException(ctx); - js_std_dump_error1(ctx, exception_val); - JS_FreeValue(ctx, exception_val); -} - -static JSRejectedPromiseEntry *find_rejected_promise(JSContext *ctx, JSThreadState *ts, - JSValueConst promise) -{ - struct list_head *el; - - list_for_each(el, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - if (JS_SameValue(ctx, rp->promise, promise)) - return rp; - } - return NULL; -} - -void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, - JSValueConst reason, - BOOL is_handled, void *opaque) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - JSRejectedPromiseEntry *rp; - - if (!is_handled) { - /* add a new entry if needed */ - rp = find_rejected_promise(ctx, ts, promise); - if (!rp) { - rp = malloc(sizeof(*rp)); - if (rp) { - rp->promise = JS_DupValue(ctx, promise); - rp->reason = JS_DupValue(ctx, reason); - list_add_tail(&rp->link, &ts->rejected_promise_list); - } - } - } else { - /* the rejection is handled, so the entry can be removed if present */ - rp = find_rejected_promise(ctx, ts, promise); - if (rp) { - JS_FreeValue(ctx, rp->promise); - JS_FreeValue(ctx, rp->reason); - list_del(&rp->link); - free(rp); - } - } -} - -/* check if there are pending promise rejections. It must be done - asynchrously in case a rejected promise is handled later. Currently - we do it once the application is about to sleep. It could be done - more often if needed. */ -static void js_std_promise_rejection_check(JSContext *ctx) -{ - JSRuntime *rt = JS_GetRuntime(ctx); - JSThreadState *ts = JS_GetRuntimeOpaque(rt); - struct list_head *el; - - if (unlikely(!list_empty(&ts->rejected_promise_list))) { - list_for_each(el, &ts->rejected_promise_list) { - JSRejectedPromiseEntry *rp = list_entry(el, JSRejectedPromiseEntry, link); - fprintf(stderr, "Possibly unhandled promise rejection: "); - js_std_dump_error1(ctx, rp->reason); - } - exit(1); - } -} - -/* main loop which calls the user JS callbacks */ -void js_std_loop(JSContext *ctx) -{ - JSContext *ctx1; - int err; - - for(;;) { - /* execute the pending jobs */ - for(;;) { - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err <= 0) { - if (err < 0) { - js_std_dump_error(ctx1); - } - break; - } - } - - js_std_promise_rejection_check(ctx); - - if (!os_poll_func || os_poll_func(ctx)) - break; - } -} - -/* Wait for a promise and execute pending jobs while waiting for - it. Return the promise result or JS_EXCEPTION in case of promise - rejection. */ -JSValue js_std_await(JSContext *ctx, JSValue obj) -{ - JSValue ret; - int state; - - for(;;) { - state = JS_PromiseState(ctx, obj); - if (state == JS_PROMISE_FULFILLED) { - ret = JS_PromiseResult(ctx, obj); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_REJECTED) { - ret = JS_Throw(ctx, JS_PromiseResult(ctx, obj)); - JS_FreeValue(ctx, obj); - break; - } else if (state == JS_PROMISE_PENDING) { - JSContext *ctx1; - int err; - err = JS_ExecutePendingJob(JS_GetRuntime(ctx), &ctx1); - if (err < 0) { - js_std_dump_error(ctx1); - } - if (err == 0) { - js_std_promise_rejection_check(ctx); - - if (os_poll_func) - os_poll_func(ctx); - } - } else { - /* not a promise */ - ret = obj; - break; - } - } - return ret; -} - -void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int load_only) -{ - JSValue obj, val; - obj = JS_ReadObject(ctx, buf, buf_len, JS_READ_OBJ_BYTECODE); - if (JS_IsException(obj)) - goto exception; - if (load_only) { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - js_module_set_import_meta(ctx, obj, FALSE, FALSE); - } - } else { - if (JS_VALUE_GET_TAG(obj) == JS_TAG_MODULE) { - if (JS_ResolveModule(ctx, obj) < 0) { - JS_FreeValue(ctx, obj); - goto exception; - } - js_module_set_import_meta(ctx, obj, FALSE, TRUE); - val = JS_EvalFunction(ctx, obj); - val = js_std_await(ctx, val); - } else { - val = JS_EvalFunction(ctx, obj); - } - if (JS_IsException(val)) { - exception: - js_std_dump_error(ctx); - exit(1); - } - JS_FreeValue(ctx, val); - } -} - -void js_std_eval_binary_json_module(JSContext *ctx, - const uint8_t *buf, size_t buf_len, - const char *module_name) -{ - JSValue obj; - JSModuleDef *m; - - obj = JS_ReadObject(ctx, buf, buf_len, 0); - if (JS_IsException(obj)) - goto exception; - m = create_json_module(ctx, module_name, obj); - if (!m) { - exception: - js_std_dump_error(ctx); - exit(1); - } -} - diff --git a/source/quickjs-libc.h b/source/quickjs-libc.h deleted file mode 100644 index 5c8301b7..00000000 --- a/source/quickjs-libc.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * QuickJS C library - * - * 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 QUICKJS_LIBC_H -#define QUICKJS_LIBC_H - -#include -#include - -#include "quickjs.h" - -#ifdef __cplusplus -extern "C" { -#endif - -JSModuleDef *js_init_module_std(JSContext *ctx, const char *module_name); -JSModuleDef *js_init_module_os(JSContext *ctx, const char *module_name); -void js_std_add_helpers(JSContext *ctx, int argc, char **argv); -void js_std_loop(JSContext *ctx); -JSValue js_std_await(JSContext *ctx, JSValue obj); -void js_std_init_handlers(JSRuntime *rt); -void js_std_free_handlers(JSRuntime *rt); -void js_std_dump_error(JSContext *ctx); -uint8_t *js_load_file(JSContext *ctx, size_t *pbuf_len, const char *filename); -int js_module_set_import_meta(JSContext *ctx, JSValueConst func_val, - JS_BOOL use_realpath, JS_BOOL is_main); -int js_module_test_json(JSContext *ctx, JSValueConst attributes); -int js_module_check_attributes(JSContext *ctx, void *opaque, JSValueConst attributes); -JSModuleDef *js_module_loader(JSContext *ctx, - const char *module_name, void *opaque, - JSValueConst attributes); -void js_std_eval_binary(JSContext *ctx, const uint8_t *buf, size_t buf_len, - int flags); -void js_std_eval_binary_json_module(JSContext *ctx, - const uint8_t *buf, size_t buf_len, - const char *module_name); -void js_std_promise_rejection_tracker(JSContext *ctx, JSValueConst promise, - JSValueConst reason, - JS_BOOL is_handled, void *opaque); -void js_std_set_worker_new_context_func(JSContext *(*func)(JSRuntime *rt)); - -#ifdef __cplusplus -} /* extern "C" { */ -#endif - -#endif /* QUICKJS_LIBC_H */ diff --git a/source/quickjs.c b/source/quickjs.c index 3801bb4b..d8222275 100644 --- a/source/quickjs.c +++ b/source/quickjs.c @@ -30,7 +30,6 @@ #include #include #include -#include #include #if defined(__APPLE__) #include @@ -66,12 +65,6 @@ #define CONFIG_PRINTF_RNDN #endif -/* define to include Atomics.* operations which depend on the OS - threads */ -#if !defined(EMSCRIPTEN) -#define CONFIG_ATOMICS -#endif - #if !defined(EMSCRIPTEN) /* enable stack limitation */ #define CONFIG_STACK_CHECK @@ -108,12 +101,6 @@ /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC -#ifdef CONFIG_ATOMICS -#include -#include -#include -#endif - enum { /* classid tag */ /* union usage | properties */ JS_CLASS_OBJECT = 1, /* must be first */ @@ -2651,25 +2638,15 @@ static inline BOOL JS_IsEmptyString(JSValueConst v) /* JSClass support */ -#ifdef CONFIG_ATOMICS -static pthread_mutex_t js_class_id_mutex = PTHREAD_MUTEX_INITIALIZER; -#endif - /* a new class ID is allocated if *pclass_id != 0 */ JSClassID JS_NewClassID(JSClassID *pclass_id) { JSClassID class_id; -#ifdef CONFIG_ATOMICS - pthread_mutex_lock(&js_class_id_mutex); -#endif class_id = *pclass_id; if (class_id == 0) { class_id = js_class_id_alloc++; *pclass_id = class_id; } -#ifdef CONFIG_ATOMICS - pthread_mutex_unlock(&js_class_id_mutex); -#endif return class_id; } diff --git a/source/scheduler_threaded.c b/source/scheduler.c similarity index 100% rename from source/scheduler_threaded.c rename to source/scheduler.c diff --git a/source/scheduler_single.c b/source/scheduler_playdate.c similarity index 98% rename from source/scheduler_single.c rename to source/scheduler_playdate.c index 0c7c90d4..264e0cb5 100644 --- a/source/scheduler_single.c +++ b/source/scheduler_playdate.c @@ -405,8 +405,8 @@ void actor_turn(cell_rt *actor) if (l.type == LETTER_BLOB) { // Create a JS blob from the C blob - size_t size = l.blob_data->length / 8; // Convert bits to bytes - JSValue arg = js_new_blob_stoned_copy(actor->context, l.blob_data->data, size); + size_t size = blob_length(l.blob_data) / 8; // Convert bits to bytes + JSValue arg = js_new_blob_stoned_copy(actor->context, (void *)blob_data(l.blob_data), size); blob_destroy(l.blob_data); result = JS_Call(actor->context, actor->message_handle, JS_NULL, 1, &arg); uncaught_exception(actor->context, result); diff --git a/source/setup_playdate.c b/source/setup_playdate.c new file mode 100644 index 00000000..3b3aeadf --- /dev/null +++ b/source/setup_playdate.c @@ -0,0 +1,30 @@ + +#include "pd_api.h" + +typedef int (PDEventHandler)(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg); + +extern PDEventHandler eventHandler; + +static void* (*pdrealloc)(void* ptr, size_t size); + +int eventHandlerShim(PlaydateAPI* playdate, PDSystemEvent event, uint32_t arg) +{ + if ( event == kEventInit ) + pdrealloc = playdate->system->realloc; + + return eventHandler(playdate, event, arg); +} + +#if TARGET_PLAYDATE + +void* _malloc_r(struct _reent* _REENT, size_t nbytes) { return pdrealloc(NULL,nbytes); } +void* _realloc_r(struct _reent* _REENT, void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); } +void _free_r(struct _reent* _REENT, void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); } + +#else + +void* malloc(size_t nbytes) { return pdrealloc(NULL,nbytes); } +void* realloc(void* ptr, size_t nbytes) { return pdrealloc(ptr,nbytes); } +void free(void* ptr ) { if ( ptr != NULL ) pdrealloc(ptr,0); } + +#endif diff --git a/source/stub_playdate.c b/source/stub_playdate.c new file mode 100644 index 00000000..21ec1c08 --- /dev/null +++ b/source/stub_playdate.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include + +#undef errno +extern int errno; + +void _exit(int status) { + while(1); +} + +int _close(int file) { + return -1; +} + +int _fstat(int file, struct stat *st) { + st->st_mode = S_IFCHR; + return 0; +} + +int _isatty(int file) { + return 1; +} + +int _lseek(int file, int ptr, int dir) { + return 0; +} + +int _open(const char *name, int flags, int mode) { + return -1; +} + +int _read(int file, char *ptr, int len) { + return 0; +} + +void *_sbrk(int incr) { + errno = ENOMEM; + return (void *)-1; +} + +int _stat(const char *file, struct stat *st) { + st->st_mode = S_IFCHR; + return 0; +} + +int _unlink(const char *name) { + errno = ENOENT; + return -1; +} + +int _write(int file, char *ptr, int len) { + return len; +} + +int _getpid(void) { + return 1; +} + +int _kill(int pid, int sig) { + errno = EINVAL; + return -1; +} + +int _gettimeofday(struct timeval *tv, void *tz) { + return 0; +} + +void _fini(void) { +}