MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add readline module, allow text and json imports

+1748 -19
+253
examples/demo/readline.js
··· 1 + import * as readline from 'node:readline'; 2 + 3 + const COLORS = { 4 + reset: '\x1b[0m', 5 + bold: '\x1b[1m', 6 + dim: '\x1b[2m', 7 + italic: '\x1b[3m', 8 + underline: '\x1b[4m', 9 + 10 + black: '\x1b[30m', 11 + red: '\x1b[31m', 12 + green: '\x1b[32m', 13 + yellow: '\x1b[33m', 14 + blue: '\x1b[34m', 15 + magenta: '\x1b[35m', 16 + cyan: '\x1b[36m', 17 + white: '\x1b[37m', 18 + 19 + bgBlack: '\x1b[40m', 20 + bgRed: '\x1b[41m', 21 + bgGreen: '\x1b[42m', 22 + bgYellow: '\x1b[43m', 23 + bgBlue: '\x1b[44m', 24 + bgMagenta: '\x1b[45m', 25 + bgCyan: '\x1b[46m', 26 + bgWhite: '\x1b[47m' 27 + }; 28 + 29 + const c = (color, text) => `${COLORS[color]}${text}${COLORS.reset}`; 30 + 31 + const LOGO = ` 32 + ${c('yellow', 'โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—')} 33 + ${c('yellow', 'โ•‘')} ${c('bold', c('red', '๐Ÿœ Ant JavaScript'))} ${c('dim', '- Interactive Task Manager')} ${' '.repeat(11)} ${c('yellow', 'โ•‘')} 34 + ${c('yellow', 'โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•')} 35 + `; 36 + 37 + const HELP = ` 38 + ${c('bold', 'Commands:')} 39 + ${c('green', 'add')} ${c('dim', '<task>')} Add a new task 40 + ${c('green', 'list')} List all tasks 41 + ${c('green', 'done')} ${c('dim', '<id>')} Mark task as complete 42 + ${c('green', 'remove')} ${c('dim', '<id>')} Remove a task 43 + ${c('green', 'clear')} Clear all tasks 44 + ${c('green', 'stats')} Show statistics 45 + ${c('green', 'help')} Show this help 46 + ${c('green', 'quit')} Exit the app 47 + `; 48 + 49 + class TaskManager { 50 + constructor() { 51 + this.tasks = []; 52 + this.nextId = 1; 53 + } 54 + 55 + add(description) { 56 + const task = { 57 + id: this.nextId++, 58 + description, 59 + done: false, 60 + createdAt: new Date() 61 + }; 62 + this.tasks.push(task); 63 + return task; 64 + } 65 + 66 + list() { 67 + return this.tasks; 68 + } 69 + 70 + markDone(id) { 71 + const task = this.tasks.find(t => t.id === id); 72 + if (task) { 73 + task.done = true; 74 + task.completedAt = new Date(); 75 + return task; 76 + } 77 + return null; 78 + } 79 + 80 + remove(id) { 81 + const idx = this.tasks.findIndex(t => t.id === id); 82 + if (idx !== -1) { 83 + return this.tasks.splice(idx, 1)[0]; 84 + } 85 + return null; 86 + } 87 + 88 + clear() { 89 + const count = this.tasks.length; 90 + this.tasks = []; 91 + return count; 92 + } 93 + 94 + stats() { 95 + const total = this.tasks.length; 96 + const completed = this.tasks.filter(t => t.done).length; 97 + const pending = total - completed; 98 + return { total, completed, pending }; 99 + } 100 + } 101 + 102 + function formatTask(task) { 103 + const checkbox = task.done ? c('green', 'โœ“') : c('dim', 'โ—‹'); 104 + const id = c('dim', `#${task.id}`); 105 + const desc = task.done ? c('dim', task.description) : task.description; 106 + return ` ${checkbox} ${id} ${desc}`; 107 + } 108 + 109 + function progressBar(current, total, width = 20) { 110 + if (total === 0) return c('dim', 'โ–‘'.repeat(width)); 111 + const filled = Math.round((current / total) * width); 112 + const empty = width - filled; 113 + return c('green', 'โ–ˆ'.repeat(filled)) + c('dim', 'โ–‘'.repeat(empty)); 114 + } 115 + 116 + function main() { 117 + const rl = readline.createInterface({ 118 + input: process.stdin, 119 + output: process.stdout, 120 + prompt: `${c('cyan', 'โฏ')} `, 121 + historySize: 100, 122 + removeHistoryDuplicates: true 123 + }); 124 + 125 + const manager = new TaskManager(); 126 + 127 + console.log(LOGO); 128 + console.log(c('dim', ' Type "help" for available commands\n')); 129 + 130 + manager.add('Learn the Ant JavaScript runtime'); 131 + manager.add('Build something awesome'); 132 + manager.add('Read the documentation'); 133 + 134 + rl.prompt(); 135 + 136 + rl.on('line', input => { 137 + const line = input.trim(); 138 + const [cmd, ...args] = line.split(/\s+/); 139 + const arg = args.join(' '); 140 + 141 + console.log(); 142 + 143 + switch (cmd?.toLowerCase()) { 144 + case 'add': { 145 + if (!arg) { 146 + console.log(` ${c('red', 'โœ—')} Please provide a task description`); 147 + break; 148 + } 149 + const task = manager.add(arg); 150 + console.log(` ${c('green', 'โœ“')} Added: ${formatTask(task)}`); 151 + break; 152 + } 153 + 154 + case 'list': 155 + case 'ls': { 156 + const tasks = manager.list(); 157 + if (tasks.length === 0) { 158 + console.log(` ${c('dim', 'No tasks yet. Use "add <task>" to create one.')}`); 159 + } else { 160 + console.log(` ${c('bold', 'Tasks:')}\n`); 161 + for (const task of tasks) { 162 + console.log(formatTask(task)); 163 + } 164 + } 165 + break; 166 + } 167 + 168 + case 'done': 169 + case 'complete': { 170 + const id = parseInt(arg); 171 + if (isNaN(id)) { 172 + console.log(` ${c('red', 'โœ—')} Please provide a valid task ID`); 173 + break; 174 + } 175 + const task = manager.markDone(id); 176 + if (task) { 177 + console.log(` ${c('green', 'โœ“')} Completed: ${formatTask(task)}`); 178 + } else { 179 + console.log(` ${c('red', 'โœ—')} Task #${id} not found`); 180 + } 181 + break; 182 + } 183 + 184 + case 'remove': 185 + case 'rm': 186 + case 'delete': { 187 + const id = parseInt(arg); 188 + if (isNaN(id)) { 189 + console.log(` ${c('red', 'โœ—')} Please provide a valid task ID`); 190 + break; 191 + } 192 + const task = manager.remove(id); 193 + if (task) { 194 + console.log(` ${c('yellow', 'โœ—')} Removed: ${c('dim', task.description)}`); 195 + } else { 196 + console.log(` ${c('red', 'โœ—')} Task #${id} not found`); 197 + } 198 + break; 199 + } 200 + 201 + case 'clear': { 202 + const count = manager.clear(); 203 + console.log(` ${c('yellow', 'โœ—')} Cleared ${count} task(s)`); 204 + break; 205 + } 206 + 207 + case 'stats': { 208 + const stats = manager.stats(); 209 + const percent = stats.total > 0 ? Math.round((stats.completed / stats.total) * 100) : 0; 210 + 211 + console.log(` ${c('bold', 'Statistics:')}\n`); 212 + console.log(` Total: ${c('cyan', stats.total)}`); 213 + console.log(` Completed: ${c('green', stats.completed)}`); 214 + console.log(` Pending: ${c('yellow', stats.pending)}`); 215 + console.log(); 216 + console.log(` Progress: ${progressBar(stats.completed, stats.total)} ${percent}%`); 217 + break; 218 + } 219 + 220 + case 'help': 221 + case '?': { 222 + console.log(HELP); 223 + break; 224 + } 225 + 226 + case 'quit': 227 + case 'exit': 228 + case 'q': { 229 + console.log(` ${c('cyan', 'Goodbye!')} ๐Ÿ‘‹`); 230 + rl.close(); 231 + return; 232 + } 233 + 234 + case '': { 235 + break; 236 + } 237 + 238 + default: { 239 + console.log(` ${c('red', 'โœ—')} Unknown command: ${c('dim', cmd)}`); 240 + console.log(` ${c('dim', 'Type "help" for available commands')}`); 241 + } 242 + } 243 + 244 + console.log(); 245 + rl.prompt(); 246 + }); 247 + 248 + rl.on('close', () => { 249 + process.exit(0); 250 + }); 251 + } 252 + 253 + main();
+15
examples/spec/modules.js
··· 5 5 import * as shell from 'ant:shell'; 6 6 import * as ffi from 'ant:ffi'; 7 7 8 + import testJson from './test.json'; 9 + import { name, version, count } from './test.json'; 10 + import textContent from './test.txt'; 11 + 8 12 console.log('Module Tests\n'); 9 13 10 14 test('import.meta exists', typeof import.meta, 'object'); ··· 25 29 test('fs toStringTag', Object.prototype.toString.call(fs), '[object fs]'); 26 30 test('shell toStringTag', Object.prototype.toString.call(shell), '[object shell]'); 27 31 test('ffi toStringTag', Object.prototype.toString.call(ffi), '[object FFI]'); 32 + 33 + test('JSON default import', typeof testJson, 'object'); 34 + test('JSON default import name', testJson.name, 'test-package'); 35 + test('JSON default import version', testJson.version, '1.0.0'); 36 + test('JSON default import count', testJson.count, 42); 37 + test('JSON named import name', name, 'test-package'); 38 + test('JSON named import version', version, '1.0.0'); 39 + test('JSON named import count', count, 42); 40 + 41 + test('text default import type', typeof textContent, 'string'); 42 + test('text default import value', textContent, 'Hello from text file\n'); 28 43 29 44 summary();
+196
examples/spec/readline.js
··· 1 + import { test, summary } from './helpers.js'; 2 + import * as readline from 'node:readline'; 3 + import * as readlinePromises from 'node:readline/promises'; 4 + 5 + console.log('Readline Tests\n'); 6 + 7 + test('readline has createInterface', typeof readline.createInterface, 'function'); 8 + test('readline has clearLine', typeof readline.clearLine, 'function'); 9 + test('readline has clearScreenDown', typeof readline.clearScreenDown, 'function'); 10 + test('readline has cursorTo', typeof readline.cursorTo, 'function'); 11 + test('readline has moveCursor', typeof readline.moveCursor, 'function'); 12 + test('readline has emitKeypressEvents', typeof readline.emitKeypressEvents, 'function'); 13 + 14 + test('readline/promises has createInterface', typeof readlinePromises.createInterface, 'function'); 15 + 16 + const rl = readline.createInterface({ 17 + input: process.stdin, 18 + output: process.stdout, 19 + prompt: 'test> ', 20 + historySize: 50, 21 + removeHistoryDuplicates: true, 22 + tabSize: 4 23 + }); 24 + 25 + test('interface is object', typeof rl, 'object'); 26 + test('interface has on method', typeof rl.on, 'function'); 27 + test('interface has once method', typeof rl.once, 'function'); 28 + test('interface has off method', typeof rl.off, 'function'); 29 + test('interface has emit method', typeof rl.emit, 'function'); 30 + test('interface has close method', typeof rl.close, 'function'); 31 + test('interface has pause method', typeof rl.pause, 'function'); 32 + test('interface has resume method', typeof rl.resume, 'function'); 33 + test('interface has prompt method', typeof rl.prompt, 'function'); 34 + test('interface has setPrompt method', typeof rl.setPrompt, 'function'); 35 + test('interface has getPrompt method', typeof rl.getPrompt, 'function'); 36 + test('interface has write method', typeof rl.write, 'function'); 37 + test('interface has question method', typeof rl.question, 'function'); 38 + test('interface has getCursorPos method', typeof rl.getCursorPos, 'function'); 39 + 40 + test('line property is string', typeof rl.line, 'string'); 41 + test('line property initially empty', rl.line, ''); 42 + 43 + test('cursor property is number', typeof rl.cursor, 'number'); 44 + test('cursor property initially 0', rl.cursor, 0); 45 + 46 + test('closed property is boolean', typeof rl.closed, 'boolean'); 47 + test('closed property initially false', rl.closed, false); 48 + 49 + const cursorPos = rl.getCursorPos(); 50 + test('getCursorPos returns object', typeof cursorPos, 'object'); 51 + test('getCursorPos has rows', typeof cursorPos.rows, 'number'); 52 + test('getCursorPos has cols', typeof cursorPos.cols, 'number'); 53 + 54 + test('getPrompt returns initial prompt', rl.getPrompt(), 'test> '); 55 + 56 + rl.setPrompt('new> '); 57 + test('setPrompt updates prompt', rl.getPrompt(), 'new> '); 58 + 59 + test('terminal property exists', typeof rl.terminal, 'boolean'); 60 + test('has Symbol.asyncIterator', typeof rl[Symbol.asyncIterator], 'function'); 61 + 62 + let closeEventFired = false; 63 + rl.on('close', () => { 64 + closeEventFired = true; 65 + }); 66 + 67 + let pauseEventFired = false; 68 + rl.on('pause', () => { 69 + pauseEventFired = true; 70 + }); 71 + 72 + let resumeEventFired = false; 73 + rl.on('resume', () => { 74 + resumeEventFired = true; 75 + }); 76 + 77 + rl.pause(); 78 + test('pause event fired', pauseEventFired, true); 79 + 80 + rl.resume(); 81 + test('resume event fired', resumeEventFired, true); 82 + 83 + rl.close(); 84 + test('close event fired', closeEventFired, true); 85 + test('closed property true after close', rl.closed, true); 86 + 87 + const clearLineResult = readline.clearLine(process.stdout, 0); 88 + test('clearLine returns boolean', typeof clearLineResult, 'boolean'); 89 + 90 + const cursorToResult = readline.cursorTo(process.stdout, 0); 91 + test('cursorTo returns boolean', typeof cursorToResult, 'boolean'); 92 + 93 + const moveCursorResult = readline.moveCursor(process.stdout, 0, 0); 94 + test('moveCursor returns boolean', typeof moveCursorResult, 'boolean'); 95 + 96 + const rlPromises = readlinePromises.createInterface({ 97 + input: process.stdin, 98 + output: process.stdout 99 + }); 100 + 101 + test('promises interface is object', typeof rlPromises, 'object'); 102 + test('promises interface has question', typeof rlPromises.question, 'function'); 103 + rlPromises.close(); 104 + 105 + const rlDefault = readline.createInterface({ 106 + input: process.stdin, 107 + output: process.stdout 108 + }); 109 + test('default prompt is "> "', rlDefault.getPrompt(), '> '); 110 + rlDefault.close(); 111 + 112 + const rlHistory = readline.createInterface({ 113 + input: process.stdin, 114 + output: process.stdout, 115 + history: ['cmd1', 'cmd2', 'cmd3'] 116 + }); 117 + test('history interface created', typeof rlHistory, 'object'); 118 + rlHistory.close(); 119 + 120 + const rl2 = readline.createInterface({ 121 + input: process.stdin, 122 + output: process.stdout 123 + }); 124 + 125 + test('addListener is function', typeof rl2.addListener, 'function'); 126 + test('removeListener is function', typeof rl2.removeListener, 'function'); 127 + 128 + let testEventFired = false; 129 + const testHandler = () => { 130 + testEventFired = true; 131 + }; 132 + rl2.addListener('test', testHandler); 133 + rl2.emit('test'); 134 + test('addListener works', testEventFired, true); 135 + 136 + testEventFired = false; 137 + rl2.removeListener('test', testHandler); 138 + rl2.emit('test'); 139 + test('removeListener works', testEventFired, false); 140 + 141 + rl2.close(); 142 + 143 + const rl3 = readline.createInterface({ 144 + input: process.stdin, 145 + output: process.stdout 146 + }); 147 + 148 + let onceCount = 0; 149 + rl3.once('myevent', () => { 150 + onceCount++; 151 + }); 152 + rl3.emit('myevent'); 153 + rl3.emit('myevent'); 154 + test('once only fires once', onceCount, 1); 155 + rl3.close(); 156 + 157 + const rl4 = readline.createInterface({ 158 + input: process.stdin, 159 + output: process.stdout 160 + }); 161 + 162 + const chainResult = rl4.on('test', () => {}); 163 + test('on returns interface for chaining', chainResult, rl4); 164 + rl4.close(); 165 + 166 + const rl5 = readline.createInterface({ 167 + input: process.stdin, 168 + output: process.stdout 169 + }); 170 + 171 + rl5.write('hello'); 172 + test('write updates line', rl5.line, 'hello'); 173 + test('write updates cursor', rl5.cursor, 5); 174 + 175 + rl5.write(null, { name: 'backspace' }); 176 + test('backspace key works', rl5.line, 'hell'); 177 + test('backspace updates cursor', rl5.cursor, 4); 178 + 179 + rl5.write(null, { name: 'left' }); 180 + test('left arrow updates cursor', rl5.cursor, 3); 181 + 182 + rl5.write(null, { name: 'right' }); 183 + test('right arrow updates cursor', rl5.cursor, 4); 184 + 185 + rl5.write(null, { name: 'home' }); 186 + test('home key moves cursor to 0', rl5.cursor, 0); 187 + 188 + rl5.write(null, { name: 'end' }); 189 + test('end key moves cursor to end', rl5.cursor, 4); 190 + 191 + rl5.write(null, { name: 'u', ctrl: true }); 192 + test('ctrl+u clears line', rl5.line, ''); 193 + 194 + rl5.close(); 195 + 196 + summary();
+1
examples/spec/test.json
··· 1 + {"name": "test-package", "version": "1.0.0", "count": 42}
+1
examples/spec/test.txt
··· 1 + Hello from text file
+13
include/modules/readline.h
··· 1 + #ifndef READLINE_H 2 + #define READLINE_H 3 + 4 + #include "ant.h" 5 + #include <stdbool.h> 6 + 7 + jsval_t readline_library(struct js *js); 8 + jsval_t readline_promises_library(struct js *js); 9 + 10 + bool has_active_readline_interfaces(void); 11 + void readline_gc_update_roots(GC_FWD_ARGS); 12 + 13 + #endif
+2
include/modules/symbol.h
··· 5 5 6 6 void init_symbol_module(void); 7 7 jsval_t get_iterator_symbol(void); 8 + jsval_t get_asyncIterator_symbol(void); 8 9 9 10 const char *get_iterator_sym_key(void); 11 + const char *get_asyncIterator_sym_key(void); 10 12 const char *get_toStringTag_sym_key(void); 11 13 const char *get_symbol_description_from_key(const char *sym_key, size_t key_len); 12 14
+1 -1
meson.build
··· 162 162 timestamp = timestamp_opt 163 163 endif 164 164 165 - version_conf.set('ANT_VERSION', '0.3.4.' + timestamp + '-g' + git_hash) 165 + version_conf.set('ANT_VERSION', '0.3.5.' + timestamp + '-g' + git_hash) 166 166 version_conf.set('ANT_BUILD_TIMESTAMP', timestamp) 167 167 168 168 cmd_cc = meson.get_compiler('c')
+31 -17
src/ant.c
··· 29 29 #include <utarray.h> 30 30 #include <uthash.h> 31 31 #include <float.h> 32 + #include <uv.h> 32 33 33 34 #define MCO_USE_VMEM_ALLOCATOR 34 35 #define MCO_ZERO_MEMORY ··· 42 43 #include "modules/symbol.h" 43 44 #include "modules/ffi.h" 44 45 #include "modules/child_process.h" 46 + #include "modules/readline.h" 47 + #include "modules/json.h" 45 48 46 49 #define CORO_MALLOC(size) calloc(1, size) 47 50 #define CORO_FREE(ptr) free(ptr) ··· 973 976 WORK_FETCHES = 1 << 5, 974 977 WORK_FS_OPS = 1 << 6, 975 978 WORK_CHILD_PROCS = 1 << 7, 979 + WORK_READLINE = 1 << 8, 976 980 } work_flags_t; 977 981 978 982 static inline work_flags_t get_pending_work(void) { 979 983 work_flags_t flags = 0; 980 - if (has_pending_microtasks()) flags |= WORK_MICROTASKS; 981 - if (has_pending_timers()) flags |= WORK_TIMERS; 982 - if (has_pending_immediates()) flags |= WORK_IMMEDIATES; 983 - if (js_has_pending_coroutines()) flags |= WORK_COROUTINES; 984 - if (has_ready_coroutines()) flags |= WORK_COROUTINES_READY; 985 - if (has_pending_fetches()) flags |= WORK_FETCHES; 986 - if (has_pending_fs_ops()) flags |= WORK_FS_OPS; 987 - if (has_pending_child_processes()) flags |= WORK_CHILD_PROCS; 984 + if (has_pending_microtasks()) flags |= WORK_MICROTASKS; 985 + if (has_pending_timers()) flags |= WORK_TIMERS; 986 + if (has_pending_immediates()) flags |= WORK_IMMEDIATES; 987 + if (js_has_pending_coroutines()) flags |= WORK_COROUTINES; 988 + if (has_ready_coroutines()) flags |= WORK_COROUTINES_READY; 989 + if (has_pending_fetches()) flags |= WORK_FETCHES; 990 + if (has_pending_fs_ops()) flags |= WORK_FS_OPS; 991 + if (has_pending_child_processes()) flags |= WORK_CHILD_PROCS; 992 + if (has_active_readline_interfaces()) flags |= WORK_READLINE; 988 993 return flags; 989 994 } 990 995 991 - #define WORK_PENDING (WORK_MICROTASKS | WORK_TIMERS | WORK_IMMEDIATES | WORK_COROUTINES | WORK_FETCHES | WORK_FS_OPS | WORK_CHILD_PROCS) 996 + #define WORK_TASKS (WORK_MICROTASKS | WORK_TIMERS | WORK_IMMEDIATES | WORK_COROUTINES | WORK_FETCHES) 997 + #define WORK_PENDING (WORK_TASKS | WORK_FS_OPS | WORK_CHILD_PROCS | WORK_READLINE) 992 998 #define WORK_BLOCKING (WORK_MICROTASKS | WORK_IMMEDIATES | WORK_COROUTINES_READY) 993 999 994 1000 void js_run_event_loop(struct js *js) { ··· 998 1004 js_poll_events(js); 999 1005 work = get_pending_work(); 1000 1006 1007 + if (work & WORK_READLINE) { 1008 + uv_run(uv_default_loop(), UV_RUN_NOWAIT); 1009 + } 1010 + 1001 1011 if (!(work & WORK_BLOCKING) && (work & WORK_TIMERS)) { 1002 1012 int64_t ms = get_next_timer_timeout(); 1003 1013 if (ms > 0) usleep(ms > 1000 ? 1000000 : (useconds_t)(ms * 1000)); 1004 - } 1014 + } else if ((work & WORK_READLINE) && !(work & WORK_BLOCKING)) uv_run(uv_default_loop(), UV_RUN_ONCE); 1005 1015 } 1006 1016 1007 1017 js_poll_events(js); ··· 19046 19056 fclose(fp); 19047 19057 content[size] = '\0'; 19048 19058 19049 - jsval_t result = js_eval(js, content, size); 19059 + jsval_t json_str = js_mkstr(js, content, size); 19050 19060 free(content); 19051 19061 19052 - return result; 19062 + return js_json_parse(js, &json_str, 1); 19053 19063 } 19054 19064 19055 19065 static jsval_t esm_load_text(struct js *js, const char *path) { ··· 19518 19528 js->consumed = 1; next(js); js->consumed = 0; 19519 19529 if (is_err(ns)) return ns; 19520 19530 19521 - jsoff_t default_off = lkp(js, ns, "default", 7); 19522 - jsval_t default_val = default_off != 0 ? resolveprop(js, mkval(T_PROP, default_off)) : ns; 19531 + jsval_t default_val; 19532 + if (vtype(ns) == T_OBJ) { 19533 + jsoff_t default_off = lkp(js, ns, "default", 7); 19534 + default_val = default_off != 0 ? resolveprop(js, mkval(T_PROP, default_off)) : ns; 19535 + } else default_val = ns; 19523 19536 setprop(js, js->scope, js_mkstr(js, default_name, default_len), default_val); 19524 19537 19525 19538 for (int i = 0; i < binding_count; i++) { 19526 19539 if (bindings[i].import_name == NULL) { 19527 19540 setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), ns); 19528 - } else { 19541 + } else if (vtype(ns) == T_OBJ) { 19529 19542 jsoff_t prop_off = lkp(js, ns, bindings[i].import_name, bindings[i].import_len); 19530 19543 jsval_t imported_val = prop_off != 0 ? resolveprop(js, mkval(T_PROP, prop_off)) : js_mkundef(); 19531 19544 setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), imported_val); 19532 - } 19545 + } else setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), js_mkundef()); 19533 19546 } 19534 19547 19535 19548 return js_mkundef(); ··· 19622 19635 js->consumed = 0; 19623 19636 19624 19637 if (is_err(ns)) return ns; 19638 + if (vtype(ns) != T_OBJ) return js_mkerr(js, "Cannot use named imports from non-object module"); 19625 19639 19626 19640 for (int i = 0; i < binding_count; i++) { 19627 19641 jsoff_t prop_off = lkp(js, ns, bindings[i].import_name, bindings[i].import_len); 19628 19642 jsval_t imported_val = prop_off != 0 ? resolveprop(js, mkval(T_PROP, prop_off)) : js_mkundef(); 19629 - 19630 19643 setprop(js, js->scope, js_mkstr(js, bindings[i].local_name, bindings[i].local_len), imported_val); 19631 19644 } 19632 19645 ··· 21456 21469 fetch_gc_update_roots(fwd_val, ctx); 21457 21470 fs_gc_update_roots(fwd_val, ctx); 21458 21471 child_process_gc_update_roots(fwd_val, ctx); 21472 + readline_gc_update_roots(fwd_val, ctx); 21459 21473 21460 21474 map_registry_entry_t *map_reg, *map_reg_tmp; 21461 21475 HASH_ITER(hh, map_registry, map_reg, map_reg_tmp) {
+4 -1
src/main.c
··· 39 39 #include "modules/localstorage.h" 40 40 #include "modules/navigator.h" 41 41 #include "modules/child_process.h" 42 + #include "modules/readline.h" 42 43 43 44 int js_result = EXIT_SUCCESS; 44 45 ··· 227 228 ant_standard_library("os", os_library); 228 229 ant_standard_library("crypto", crypto_library); 229 230 ant_standard_library("events", events_library); 231 + ant_standard_library("readline", readline_library); 232 + ant_standard_library("readline/promises", readline_promises_library); 230 233 ant_standard_library("child_process", child_process_library); 231 - 234 + 232 235 jsval_t snapshot_result = ant_load_snapshot(js); 233 236 if (js_type(snapshot_result) == JS_ERR) { 234 237 fprintf(stderr, "Warning: Failed to load snapshot: %s\n", js_str(js, snapshot_result));
+1223
src/modules/readline.c
··· 1 + #include <compat.h> // IWYU pragma: keep 2 + 3 + #include <stdlib.h> 4 + #include <stdio.h> 5 + #include <string.h> 6 + #include <stdbool.h> 7 + #include <ctype.h> 8 + #include <uthash.h> 9 + #include <uv.h> 10 + 11 + #ifdef _WIN32 12 + #include <conio.h> 13 + #include <windows.h> 14 + #include <io.h> 15 + #define STDIN_FILENO 0 16 + #define STDOUT_FILENO 1 17 + #else 18 + #include <termios.h> 19 + #include <unistd.h> 20 + #include <sys/select.h> 21 + #include <sys/ioctl.h> 22 + #endif 23 + 24 + #include "ant.h" 25 + #include "runtime.h" 26 + #include "modules/readline.h" 27 + #include "modules/symbol.h" 28 + 29 + #define MAX_LINE_LENGTH 4096 30 + #define MAX_HISTORY 1000 31 + #define DEFAULT_PROMPT "> " 32 + #define DEFAULT_HISTORY_SIZE 30 33 + #define DEFAULT_TAB_SIZE 8 34 + #define MAX_INTERFACES 64 35 + 36 + typedef struct { 37 + jsval_t listener; 38 + bool once; 39 + } RLEventListener; 40 + 41 + #define MAX_LISTENERS_PER_EVENT 16 42 + 43 + typedef struct { 44 + char *event_type; 45 + RLEventListener listeners[MAX_LISTENERS_PER_EVENT]; 46 + int listener_count; 47 + UT_hash_handle hh; 48 + } RLEventType; 49 + 50 + typedef struct { 51 + char **lines; 52 + int count; 53 + int capacity; 54 + int current; 55 + } rl_history_t; 56 + 57 + typedef struct rl_interface { 58 + uint64_t id; 59 + jsval_t input_stream; 60 + jsval_t output_stream; 61 + jsval_t completer; 62 + jsval_t js_obj; 63 + char *prompt; 64 + char *line_buffer; 65 + int line_pos; 66 + int line_len; 67 + rl_history_t history; 68 + bool terminal; 69 + bool paused; 70 + bool closed; 71 + bool reading; 72 + int history_size; 73 + bool remove_history_duplicates; 74 + int crlf_delay; 75 + int escape_code_timeout; 76 + int tab_size; 77 + jsval_t pending_question_resolve; 78 + jsval_t pending_question_reject; 79 + RLEventType *events; 80 + uv_tty_t tty_in; 81 + uv_tty_t tty_out; 82 + bool tty_initialized; 83 + int escape_state; 84 + char escape_buf[16]; 85 + int escape_len; 86 + UT_hash_handle hh; 87 + #ifndef _WIN32 88 + struct termios saved_termios; 89 + bool raw_mode; 90 + #endif 91 + } rl_interface_t; 92 + 93 + static rl_interface_t *interfaces = NULL; 94 + static uint64_t next_interface_id = 1; 95 + 96 + static void rl_history_init(rl_history_t *hist, int capacity) { 97 + hist->capacity = capacity > 0 ? capacity : DEFAULT_HISTORY_SIZE; 98 + hist->lines = calloc(hist->capacity, sizeof(char*)); 99 + hist->count = 0; 100 + hist->current = -1; 101 + } 102 + 103 + static void rl_history_add(rl_history_t *hist, const char *line, bool remove_duplicates) { 104 + if (!line || strlen(line) == 0) return; 105 + 106 + if (remove_duplicates) { 107 + for (int i = 0; i < hist->count; i++) { 108 + if (strcmp(hist->lines[i], line) == 0) { 109 + free(hist->lines[i]); 110 + for (int j = i; j < hist->count - 1; j++) { 111 + hist->lines[j] = hist->lines[j + 1]; 112 + } 113 + hist->count--; break; 114 + } 115 + } 116 + } else if (hist->count > 0 && strcmp(hist->lines[hist->count - 1], line) == 0) { 117 + return; 118 + } 119 + 120 + if (hist->count >= hist->capacity) { 121 + free(hist->lines[0]); 122 + memmove(hist->lines, hist->lines + 1, sizeof(char*) * (hist->capacity - 1)); 123 + hist->count--; 124 + } 125 + 126 + hist->lines[hist->count++] = strdup(line); 127 + hist->current = hist->count; 128 + } 129 + 130 + static const char *rl_history_prev(rl_history_t *hist) { 131 + if (hist->count == 0) return NULL; 132 + if (hist->current > 0) hist->current--; 133 + return hist->lines[hist->current]; 134 + } 135 + 136 + static const char *rl_history_next(rl_history_t *hist) { 137 + if (hist->count == 0) return NULL; 138 + if (hist->current < hist->count - 1) { 139 + hist->current++; 140 + return hist->lines[hist->current]; 141 + } 142 + hist->current = hist->count; 143 + return ""; 144 + } 145 + 146 + static void rl_history_free(rl_history_t *hist) { 147 + if (hist->lines) { 148 + for (int i = 0; i < hist->count; i++) { 149 + free(hist->lines[i]); 150 + } 151 + free(hist->lines); 152 + hist->lines = NULL; 153 + } 154 + hist->count = 0; 155 + hist->current = -1; 156 + } 157 + 158 + static RLEventType *find_or_create_event_type(rl_interface_t *iface, const char *event_type) { 159 + RLEventType *evt = NULL; 160 + HASH_FIND_STR(iface->events, event_type, evt); 161 + 162 + if (evt == NULL) { 163 + evt = malloc(sizeof(RLEventType)); 164 + evt->event_type = strdup(event_type); 165 + evt->listener_count = 0; 166 + HASH_ADD_KEYPTR(hh, iface->events, evt->event_type, strlen(evt->event_type), evt); 167 + } 168 + 169 + return evt; 170 + } 171 + 172 + static void emit_event(struct js *js, rl_interface_t *iface, const char *event_type, jsval_t *args, int nargs) { 173 + RLEventType *evt = NULL; 174 + HASH_FIND_STR(iface->events, event_type, evt); 175 + 176 + if (evt == NULL || evt->listener_count == 0) return; 177 + 178 + int i = 0; 179 + while (i < evt->listener_count) { 180 + RLEventListener *listener = &evt->listeners[i]; 181 + js_call(js, listener->listener, args, nargs); 182 + 183 + if (listener->once) { 184 + for (int j = i; j < evt->listener_count - 1; j++) { 185 + evt->listeners[j] = evt->listeners[j + 1]; 186 + } 187 + evt->listener_count--; 188 + } else i++; 189 + } 190 + } 191 + 192 + static jsval_t get_history_array(struct js *js, rl_interface_t *iface) { 193 + jsval_t arr = js_mkarr(js); 194 + for (int i = 0; i < iface->history.count; i++) { 195 + js_arr_push(js, arr, js_mkstr(js, iface->history.lines[i], strlen(iface->history.lines[i]))); 196 + } 197 + return arr; 198 + } 199 + 200 + static void emit_history_event(struct js *js, rl_interface_t *iface) { 201 + jsval_t history_arr = get_history_array(js, iface); 202 + emit_event(js, iface, "history", &history_arr, 1); 203 + } 204 + 205 + #ifndef _WIN32 206 + static void enter_raw_mode(rl_interface_t *iface) { 207 + if (iface->raw_mode) return; 208 + 209 + struct termios raw; 210 + if (tcgetattr(STDIN_FILENO, &iface->saved_termios) == -1) return; 211 + 212 + raw = iface->saved_termios; 213 + raw.c_lflag &= ~(ICANON | ECHO | ISIG); 214 + raw.c_cc[VMIN] = 1; 215 + raw.c_cc[VTIME] = 0; 216 + 217 + if (tcsetattr(STDIN_FILENO, TCSANOW, &raw) == -1) return; 218 + iface->raw_mode = true; 219 + } 220 + 221 + static void exit_raw_mode(rl_interface_t *iface) { 222 + if (!iface->raw_mode) return; 223 + tcsetattr(STDIN_FILENO, TCSANOW, &iface->saved_termios); 224 + iface->raw_mode = false; 225 + } 226 + #endif 227 + 228 + static void write_output(rl_interface_t *iface, const char *str) { 229 + (void)iface; 230 + printf("%s", str); 231 + fflush(stdout); 232 + } 233 + 234 + static void clear_line_display(rl_interface_t *iface) { 235 + write_output(iface, "\r\033[K"); 236 + } 237 + 238 + static void refresh_line(rl_interface_t *iface) { 239 + char buf[MAX_LINE_LENGTH + 256]; 240 + snprintf(buf, sizeof(buf), "\r\033[K%s%s", iface->prompt, iface->line_buffer); 241 + write_output(iface, buf); 242 + 243 + int cursor_offset = iface->line_len - iface->line_pos; 244 + if (cursor_offset > 0) { 245 + char move_buf[32]; 246 + snprintf(move_buf, sizeof(move_buf), "\033[%dD", cursor_offset); 247 + write_output(iface, move_buf); 248 + } 249 + } 250 + 251 + static rl_interface_t *find_interface_by_id(uint64_t id) { 252 + rl_interface_t *iface = NULL; 253 + HASH_FIND(hh, interfaces, &id, sizeof(uint64_t), iface); 254 + return iface; 255 + } 256 + 257 + static void handle_history_up(rl_interface_t *iface) { 258 + const char *hist_line = rl_history_prev(&iface->history); 259 + if (hist_line) { 260 + strcpy(iface->line_buffer, hist_line); 261 + iface->line_len = strlen(iface->line_buffer); 262 + iface->line_pos = iface->line_len; 263 + refresh_line(iface); 264 + } 265 + } 266 + 267 + static void handle_history_down(rl_interface_t *iface) { 268 + const char *hist_line = rl_history_next(&iface->history); 269 + if (hist_line) { 270 + strcpy(iface->line_buffer, hist_line); 271 + iface->line_len = strlen(iface->line_buffer); 272 + iface->line_pos = iface->line_len; 273 + refresh_line(iface); 274 + } 275 + } 276 + 277 + static void handle_char_input(rl_interface_t *iface, char c) { 278 + if (iface->line_len < MAX_LINE_LENGTH - 1) { 279 + memmove(iface->line_buffer + iface->line_pos + 1, 280 + iface->line_buffer + iface->line_pos, 281 + iface->line_len - iface->line_pos + 1); 282 + iface->line_buffer[iface->line_pos] = c; 283 + iface->line_pos++; 284 + iface->line_len++; 285 + 286 + if (iface->line_pos == iface->line_len) { 287 + printf("%c", c); 288 + fflush(stdout); 289 + } else { 290 + refresh_line(iface); 291 + } 292 + } 293 + } 294 + 295 + static void handle_backspace(rl_interface_t *iface) { 296 + if (iface->line_pos > 0) { 297 + memmove( 298 + iface->line_buffer + iface->line_pos - 1, 299 + iface->line_buffer + iface->line_pos, 300 + iface->line_len - iface->line_pos + 1 301 + ); 302 + iface->line_pos--; 303 + iface->line_len--; 304 + refresh_line(iface); 305 + } 306 + } 307 + 308 + static void handle_delete(rl_interface_t *iface) { 309 + if (iface->line_pos < iface->line_len) { 310 + memmove( 311 + iface->line_buffer + iface->line_pos, 312 + iface->line_buffer + iface->line_pos + 1, 313 + iface->line_len - iface->line_pos 314 + ); 315 + iface->line_len--; 316 + refresh_line(iface); 317 + } 318 + } 319 + 320 + static void handle_escape_sequence(rl_interface_t *iface, const char *seq, int len) { 321 + if (len >= 2 && seq[0] == '[') { 322 + switch (seq[1]) { 323 + case 'A': handle_history_up(iface); break; 324 + case 'B': handle_history_down(iface); break; 325 + case 'C': 326 + if (iface->line_pos < iface->line_len) { 327 + iface->line_pos++; 328 + printf("\033[C"); 329 + fflush(stdout); 330 + } 331 + break; 332 + case 'D': 333 + if (iface->line_pos > 0) { 334 + iface->line_pos--; 335 + printf("\033[D"); 336 + fflush(stdout); 337 + } 338 + break; 339 + case 'H': 340 + iface->line_pos = 0; 341 + refresh_line(iface); 342 + break; 343 + case 'F': 344 + iface->line_pos = iface->line_len; 345 + refresh_line(iface); 346 + break; 347 + case '3': 348 + if (len >= 3 && seq[2] == '~') { 349 + handle_delete(iface); 350 + } 351 + break; 352 + } 353 + } 354 + } 355 + 356 + static void alloc_buffer(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf) { 357 + (void)handle; 358 + buf->base = malloc(suggested_size); 359 + buf->len = suggested_size; 360 + } 361 + 362 + static void on_stdin_read(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf) { 363 + rl_interface_t *iface = (rl_interface_t *)stream->data; 364 + struct js *js = rt->js; 365 + 366 + if (!iface || iface->closed || iface->paused) { 367 + if (buf->base) free(buf->base); 368 + return; 369 + } 370 + 371 + if (nread < 0) { 372 + if (nread == UV_EOF) { 373 + emit_event(js, iface, "close", NULL, 0); 374 + iface->closed = true; 375 + } 376 + if (buf->base) free(buf->base); 377 + return; 378 + } 379 + 380 + for (ssize_t i = 0; i < nread; i++) { 381 + char c = buf->base[i]; 382 + 383 + if (iface->escape_state > 0) { 384 + iface->escape_buf[iface->escape_len++] = c; 385 + 386 + if (iface->escape_state == 1) { 387 + if (c == '[' || c == 'O') { 388 + iface->escape_state = 2; 389 + } else { 390 + iface->escape_state = 0; 391 + iface->escape_len = 0; 392 + } 393 + } else if (iface->escape_state == 2) { 394 + if ((c >= 'A' && c <= 'Z') || c == '~') { 395 + handle_escape_sequence(iface, iface->escape_buf, iface->escape_len); 396 + iface->escape_state = 0; 397 + iface->escape_len = 0; 398 + } else if (iface->escape_len >= 15) { 399 + iface->escape_state = 0; 400 + iface->escape_len = 0; 401 + } 402 + } 403 + continue; 404 + } 405 + 406 + if (c == 27) { 407 + iface->escape_state = 1; 408 + iface->escape_len = 0; 409 + continue; 410 + } 411 + 412 + if (c == '\r' || c == '\n') { 413 + printf("\n"); 414 + fflush(stdout); 415 + 416 + char *line = strdup(iface->line_buffer); 417 + rl_history_add(&iface->history, line, iface->remove_history_duplicates); 418 + emit_history_event(js, iface); 419 + 420 + jsval_t line_val = js_mkstr(js, line, strlen(line)); 421 + emit_event(js, iface, "line", &line_val, 1); 422 + 423 + if (js_type(iface->pending_question_resolve) == JS_FUNC) { 424 + js_call(js, iface->pending_question_resolve, &line_val, 1); 425 + iface->pending_question_resolve = js_mkundef(); 426 + iface->pending_question_reject = js_mkundef(); 427 + } 428 + 429 + iface->line_buffer[0] = '\0'; 430 + iface->line_pos = 0; 431 + iface->line_len = 0; 432 + iface->history.current = iface->history.count; 433 + 434 + free(line); 435 + } else if (c == 127 || c == 8) { 436 + handle_backspace(iface); 437 + } else if (c == 3) { 438 + emit_event(js, iface, "SIGINT", NULL, 0); 439 + } else if (c == 4) { 440 + if (iface->line_len == 0) { 441 + emit_event(js, iface, "close", NULL, 0); 442 + iface->closed = true; 443 + uv_read_stop(stream); 444 + } else { 445 + handle_delete(iface); 446 + } 447 + } else if (c == 1) { 448 + iface->line_pos = 0; 449 + refresh_line(iface); 450 + } else if (c == 5) { 451 + iface->line_pos = iface->line_len; 452 + refresh_line(iface); 453 + } else if (c == 11) { 454 + iface->line_buffer[iface->line_pos] = '\0'; 455 + iface->line_len = iface->line_pos; 456 + refresh_line(iface); 457 + } else if (c == 21) { 458 + iface->line_buffer[0] = '\0'; 459 + iface->line_pos = 0; 460 + iface->line_len = 0; 461 + refresh_line(iface); 462 + } else if (c == 12) { 463 + printf("\033[2J\033[H"); 464 + refresh_line(iface); 465 + } else if (c >= 32 && c < 127) { 466 + handle_char_input(iface, c); 467 + } 468 + } 469 + 470 + if (buf->base) free(buf->base); 471 + } 472 + 473 + static void start_reading(rl_interface_t *iface) { 474 + if (iface->reading || iface->closed) return; 475 + 476 + if (!iface->tty_initialized) { 477 + uv_loop_t *loop = uv_default_loop(); 478 + 479 + int is_tty = uv_guess_handle(STDIN_FILENO) == UV_TTY; 480 + 481 + if (is_tty) { 482 + if (uv_tty_init(loop, &iface->tty_in, STDIN_FILENO, 1) != 0) { 483 + return; 484 + } 485 + uv_tty_set_mode(&iface->tty_in, UV_TTY_MODE_RAW); 486 + } else { 487 + if (uv_tty_init(loop, &iface->tty_in, STDIN_FILENO, 1) != 0) { 488 + return; 489 + } 490 + } 491 + 492 + iface->tty_in.data = iface; 493 + iface->tty_initialized = true; 494 + } 495 + 496 + iface->reading = true; 497 + uv_read_start((uv_stream_t *)&iface->tty_in, alloc_buffer, on_stdin_read); 498 + } 499 + 500 + static void stop_reading(rl_interface_t *iface) { 501 + if (!iface->reading) return; 502 + 503 + uv_read_stop((uv_stream_t *)&iface->tty_in); 504 + iface->reading = false; 505 + } 506 + 507 + static void process_line(struct js *js, rl_interface_t *iface) { 508 + char *line = strdup(iface->line_buffer); 509 + 510 + rl_history_add(&iface->history, line, iface->remove_history_duplicates); 511 + emit_history_event(js, iface); 512 + 513 + jsval_t line_val = js_mkstr(js, line, strlen(line)); 514 + emit_event(js, iface, "line", &line_val, 1); 515 + 516 + if (js_type(iface->pending_question_resolve) == JS_FUNC) { 517 + js_call(js, iface->pending_question_resolve, &line_val, 1); 518 + iface->pending_question_resolve = js_mkundef(); 519 + iface->pending_question_reject = js_mkundef(); 520 + } 521 + 522 + iface->line_buffer[0] = '\0'; 523 + iface->line_pos = 0; 524 + iface->line_len = 0; 525 + 526 + free(line); 527 + } 528 + 529 + static rl_interface_t *get_interface(struct js *js, jsval_t this_obj) { 530 + jsval_t id_val = js_get(js, this_obj, "_rl_id"); 531 + if (js_type(id_val) != JS_NUM) return NULL; 532 + 533 + uint64_t id = (uint64_t)js_getnum(id_val); 534 + rl_interface_t *iface = NULL; 535 + HASH_FIND(hh, interfaces, &id, sizeof(uint64_t), iface); 536 + return iface; 537 + } 538 + 539 + static jsval_t rl_interface_on(struct js *js, jsval_t *args, int nargs) { 540 + jsval_t this_obj = js_getthis(js); 541 + rl_interface_t *iface = get_interface(js, this_obj); 542 + 543 + if (!iface) return js_mkerr(js, "Invalid Interface"); 544 + if (nargs < 2) return js_mkerr(js, "on requires 2 arguments"); 545 + 546 + char *event = js_getstr(js, args[0], NULL); 547 + if (!event) return js_mkerr(js, "event must be a string"); 548 + if (js_type(args[1]) != JS_FUNC) return js_mkerr(js, "listener must be a function"); 549 + 550 + RLEventType *evt = find_or_create_event_type(iface, event); 551 + if (evt->listener_count >= MAX_LISTENERS_PER_EVENT) { 552 + return js_mkerr(js, "maximum listeners reached"); 553 + } 554 + 555 + evt->listeners[evt->listener_count].listener = args[1]; 556 + evt->listeners[evt->listener_count].once = false; 557 + evt->listener_count++; 558 + 559 + return this_obj; 560 + } 561 + 562 + static jsval_t rl_interface_once(struct js *js, jsval_t *args, int nargs) { 563 + jsval_t this_obj = js_getthis(js); 564 + rl_interface_t *iface = get_interface(js, this_obj); 565 + 566 + if (!iface) return js_mkerr(js, "Invalid Interface"); 567 + if (nargs < 2) return js_mkerr(js, "once requires 2 arguments"); 568 + 569 + char *event = js_getstr(js, args[0], NULL); 570 + if (!event) return js_mkerr(js, "event must be a string"); 571 + if (js_type(args[1]) != JS_FUNC) return js_mkerr(js, "listener must be a function"); 572 + 573 + RLEventType *evt = find_or_create_event_type(iface, event); 574 + if (evt->listener_count >= MAX_LISTENERS_PER_EVENT) { 575 + return js_mkerr(js, "maximum listeners reached"); 576 + } 577 + 578 + evt->listeners[evt->listener_count].listener = args[1]; 579 + evt->listeners[evt->listener_count].once = true; 580 + evt->listener_count++; 581 + 582 + return this_obj; 583 + } 584 + 585 + static jsval_t rl_interface_off(struct js *js, jsval_t *args, int nargs) { 586 + jsval_t this_obj = js_getthis(js); 587 + rl_interface_t *iface = get_interface(js, this_obj); 588 + 589 + if (!iface) return js_mkerr(js, "Invalid Interface"); 590 + if (nargs < 2) return this_obj; 591 + 592 + char *event = js_getstr(js, args[0], NULL); 593 + if (!event) return this_obj; 594 + 595 + RLEventType *evt = NULL; 596 + HASH_FIND_STR(iface->events, event, evt); 597 + if (!evt) return this_obj; 598 + 599 + for (int i = 0; i < evt->listener_count; i++) { 600 + if (evt->listeners[i].listener == args[1]) { 601 + for (int j = i; j < evt->listener_count - 1; j++) { 602 + evt->listeners[j] = evt->listeners[j + 1]; 603 + } 604 + evt->listener_count--; 605 + break; 606 + } 607 + } 608 + 609 + return this_obj; 610 + } 611 + 612 + static jsval_t rl_interface_emit(struct js *js, jsval_t *args, int nargs) { 613 + jsval_t this_obj = js_getthis(js); 614 + rl_interface_t *iface = get_interface(js, this_obj); 615 + 616 + if (!iface) return js_mkerr(js, "Invalid Interface"); 617 + if (nargs < 1) return js_mkfalse(); 618 + 619 + char *event = js_getstr(js, args[0], NULL); 620 + if (!event) return js_mkfalse(); 621 + 622 + emit_event(js, iface, event, nargs > 1 ? &args[1] : NULL, nargs - 1); 623 + return js_mktrue(); 624 + } 625 + 626 + static jsval_t rl_interface_close(struct js *js, jsval_t *args, int nargs) { 627 + (void)args; (void)nargs; 628 + jsval_t this_obj = js_getthis(js); 629 + rl_interface_t *iface = get_interface(js, this_obj); 630 + 631 + if (!iface || iface->closed) return js_mkundef(); 632 + 633 + stop_reading(iface); 634 + 635 + if (iface->tty_initialized) { 636 + uv_tty_reset_mode(); 637 + uv_close((uv_handle_t *)&iface->tty_in, NULL); 638 + iface->tty_initialized = false; 639 + } 640 + 641 + #ifndef _WIN32 642 + exit_raw_mode(iface); 643 + #endif 644 + 645 + iface->closed = true; 646 + emit_event(js, iface, "close", NULL, 0); 647 + 648 + return js_mkundef(); 649 + } 650 + 651 + static jsval_t rl_interface_pause(struct js *js, jsval_t *args, int nargs) { 652 + (void)args; (void)nargs; 653 + jsval_t this_obj = js_getthis(js); 654 + rl_interface_t *iface = get_interface(js, this_obj); 655 + 656 + if (!iface) return js_mkerr(js, "Invalid Interface"); 657 + 658 + if (!iface->paused) { 659 + iface->paused = true; 660 + stop_reading(iface); 661 + emit_event(js, iface, "pause", NULL, 0); 662 + } 663 + 664 + return this_obj; 665 + } 666 + 667 + static jsval_t rl_interface_resume(struct js *js, jsval_t *args, int nargs) { 668 + (void)args; (void)nargs; 669 + jsval_t this_obj = js_getthis(js); 670 + rl_interface_t *iface = get_interface(js, this_obj); 671 + 672 + if (!iface) return js_mkerr(js, "Invalid Interface"); 673 + 674 + if (iface->paused) { 675 + iface->paused = false; 676 + start_reading(iface); 677 + emit_event(js, iface, "resume", NULL, 0); 678 + } 679 + 680 + return this_obj; 681 + } 682 + 683 + static jsval_t rl_interface_prompt(struct js *js, jsval_t *args, int nargs) { 684 + jsval_t this_obj = js_getthis(js); 685 + rl_interface_t *iface = get_interface(js, this_obj); 686 + 687 + if (!iface || iface->closed) return js_mkundef(); 688 + 689 + if (iface->paused) { 690 + iface->paused = false; 691 + emit_event(js, iface, "resume", NULL, 0); 692 + } 693 + 694 + bool preserve_cursor = false; 695 + if (nargs > 0) preserve_cursor = js_truthy(js, args[0]); 696 + 697 + if (!preserve_cursor) { 698 + iface->line_buffer[0] = '\0'; 699 + iface->line_pos = 0; 700 + iface->line_len = 0; 701 + } 702 + 703 + write_output(iface, iface->prompt); 704 + if (iface->line_len > 0) { 705 + write_output(iface, iface->line_buffer); 706 + } 707 + 708 + start_reading(iface); 709 + 710 + return js_mkundef(); 711 + } 712 + 713 + static jsval_t rl_interface_set_prompt(struct js *js, jsval_t *args, int nargs) { 714 + jsval_t this_obj = js_getthis(js); 715 + rl_interface_t *iface = get_interface(js, this_obj); 716 + 717 + if (!iface) return js_mkerr(js, "Invalid Interface"); 718 + if (nargs < 1) return js_mkundef(); 719 + 720 + char *new_prompt = js_getstr(js, args[0], NULL); 721 + if (new_prompt) { 722 + free(iface->prompt); 723 + iface->prompt = strdup(new_prompt); 724 + } 725 + 726 + return js_mkundef(); 727 + } 728 + 729 + static jsval_t rl_interface_get_prompt(struct js *js, jsval_t *args, int nargs) { 730 + (void)args; (void)nargs; 731 + jsval_t this_obj = js_getthis(js); 732 + rl_interface_t *iface = get_interface(js, this_obj); 733 + 734 + if (!iface) return js_mkerr(js, "Invalid Interface"); 735 + 736 + return js_mkstr(js, iface->prompt, strlen(iface->prompt)); 737 + } 738 + 739 + static void process_key_sequence(rl_interface_t *iface, const char *name, bool ctrl, bool meta, bool shift) { 740 + (void)meta; (void)shift; 741 + 742 + if (!name) return; 743 + 744 + if (strcmp(name, "return") == 0 || strcmp(name, "enter") == 0) { 745 + printf("\n"); 746 + fflush(stdout); 747 + } else if (strcmp(name, "backspace") == 0) { 748 + if (iface->line_pos > 0) { 749 + memmove(iface->line_buffer + iface->line_pos - 1, 750 + iface->line_buffer + iface->line_pos, 751 + iface->line_len - iface->line_pos + 1); 752 + iface->line_pos--; 753 + iface->line_len--; 754 + } 755 + } else if (strcmp(name, "delete") == 0) { 756 + if (iface->line_pos < iface->line_len) { 757 + memmove(iface->line_buffer + iface->line_pos, 758 + iface->line_buffer + iface->line_pos + 1, 759 + iface->line_len - iface->line_pos); 760 + iface->line_len--; 761 + } 762 + } else if (strcmp(name, "left") == 0) { 763 + if (iface->line_pos > 0) iface->line_pos--; 764 + } else if (strcmp(name, "right") == 0) { 765 + if (iface->line_pos < iface->line_len) iface->line_pos++; 766 + } else if (strcmp(name, "home") == 0 || (ctrl && strcmp(name, "a") == 0)) { 767 + iface->line_pos = 0; 768 + } else if (strcmp(name, "end") == 0 || (ctrl && strcmp(name, "e") == 0)) { 769 + iface->line_pos = iface->line_len; 770 + } else if (ctrl && strcmp(name, "u") == 0) { 771 + iface->line_buffer[0] = '\0'; 772 + iface->line_pos = 0; 773 + iface->line_len = 0; 774 + } else if (ctrl && strcmp(name, "k") == 0) { 775 + iface->line_buffer[iface->line_pos] = '\0'; 776 + iface->line_len = iface->line_pos; 777 + } 778 + } 779 + 780 + static jsval_t rl_interface_write(struct js *js, jsval_t *args, int nargs) { 781 + jsval_t this_obj = js_getthis(js); 782 + rl_interface_t *iface = get_interface(js, this_obj); 783 + 784 + if (!iface || iface->closed) return js_mkundef(); 785 + 786 + if (iface->paused) { 787 + iface->paused = false; 788 + emit_event(js, iface, "resume", NULL, 0); 789 + } 790 + 791 + if (nargs >= 2 && js_type(args[1]) == JS_OBJ) { 792 + jsval_t key = args[1]; 793 + jsval_t name_val = js_get(js, key, "name"); 794 + jsval_t ctrl_val = js_get(js, key, "ctrl"); 795 + jsval_t meta_val = js_get(js, key, "meta"); 796 + jsval_t shift_val = js_get(js, key, "shift"); 797 + 798 + char *name = (js_type(name_val) == JS_STR) ? js_getstr(js, name_val, NULL) : NULL; 799 + bool ctrl = js_truthy(js, ctrl_val); 800 + bool meta = js_truthy(js, meta_val); 801 + bool shift = js_truthy(js, shift_val); 802 + 803 + if (name) { 804 + process_key_sequence(iface, name, ctrl, meta, shift); 805 + return js_mkundef(); 806 + } 807 + } 808 + 809 + if (nargs < 1 || js_type(args[0]) == JS_NULL || js_type(args[0]) == JS_UNDEF) { 810 + return js_mkundef(); 811 + } 812 + 813 + size_t len; 814 + char *data = js_getstr(js, args[0], &len); 815 + if (!data) return js_mkundef(); 816 + 817 + for (size_t i = 0; i < len && iface->line_len < MAX_LINE_LENGTH - 1; i++) { 818 + char c = data[i]; 819 + 820 + if (c == '\n' || c == '\r') { 821 + process_line(js, iface); 822 + } else { 823 + memmove(iface->line_buffer + iface->line_pos + 1, 824 + iface->line_buffer + iface->line_pos, 825 + iface->line_len - iface->line_pos + 1); 826 + iface->line_buffer[iface->line_pos] = c; 827 + iface->line_pos++; 828 + iface->line_len++; 829 + } 830 + } 831 + 832 + return js_mkundef(); 833 + } 834 + 835 + static jsval_t rl_interface_line_getter(struct js *js, jsval_t *args, int nargs) { 836 + (void)args; (void)nargs; 837 + jsval_t this_obj = js_getthis(js); 838 + rl_interface_t *iface = get_interface(js, this_obj); 839 + 840 + if (!iface) return js_mkundef(); 841 + return js_mkstr(js, iface->line_buffer, strlen(iface->line_buffer)); 842 + } 843 + 844 + static jsval_t rl_interface_cursor_getter(struct js *js, jsval_t *args, int nargs) { 845 + (void)args; (void)nargs; 846 + jsval_t this_obj = js_getthis(js); 847 + rl_interface_t *iface = get_interface(js, this_obj); 848 + 849 + if (!iface) return js_mknum(0); 850 + return js_mknum((double)iface->line_pos); 851 + } 852 + 853 + static jsval_t rl_interface_question_callback(struct js *js, jsval_t *args, int nargs) { 854 + jsval_t this_obj = js_getthis(js); 855 + rl_interface_t *iface = get_interface(js, this_obj); 856 + 857 + if (!iface || iface->closed) return js_mkundef(); 858 + if (nargs < 2) return js_mkerr(js, "question requires query and callback"); 859 + 860 + size_t query_len; 861 + char *query = js_getstr(js, args[0], &query_len); 862 + if (!query) return js_mkerr(js, "query must be a string"); 863 + 864 + if (js_type(args[1]) != JS_FUNC) { 865 + return js_mkerr(js, "callback must be a function"); 866 + } 867 + 868 + write_output(iface, query); 869 + 870 + RLEventType *evt = find_or_create_event_type(iface, "line"); 871 + if (evt->listener_count >= MAX_LISTENERS_PER_EVENT) { 872 + return js_mkerr(js, "maximum listeners reached"); 873 + } 874 + 875 + evt->listeners[evt->listener_count].listener = args[1]; 876 + evt->listeners[evt->listener_count].once = true; 877 + evt->listener_count++; 878 + 879 + return js_mkundef(); 880 + } 881 + 882 + static jsval_t rl_interface_question_promise(struct js *js, jsval_t *args, int nargs) { 883 + jsval_t this_obj = js_getthis(js); 884 + rl_interface_t *iface = get_interface(js, this_obj); 885 + 886 + if (!iface || iface->closed) return js_mkerr(js, "Interface is closed"); 887 + if (nargs < 1) return js_mkerr(js, "question requires a query string"); 888 + 889 + size_t query_len; 890 + char *query = js_getstr(js, args[0], &query_len); 891 + if (!query) return js_mkerr(js, "query must be a string"); 892 + 893 + jsval_t promise = js_mkpromise(js); 894 + 895 + write_output(iface, query); 896 + 897 + iface->pending_question_resolve = js_get(js, promise, "_resolve"); 898 + iface->pending_question_reject = js_get(js, promise, "_reject"); 899 + 900 + return promise; 901 + } 902 + 903 + static jsval_t rl_interface_get_cursor_pos(struct js *js, jsval_t *args, int nargs) { 904 + (void)args; (void)nargs; 905 + jsval_t this_obj = js_getthis(js); 906 + rl_interface_t *iface = get_interface(js, this_obj); 907 + 908 + if (!iface) { 909 + jsval_t result = js_mkobj(js); 910 + js_set(js, result, "rows", js_mknum(0)); 911 + js_set(js, result, "cols", js_mknum(0)); 912 + return result; 913 + } 914 + 915 + int prompt_len = (int)strlen(iface->prompt); 916 + int total_cols = prompt_len + iface->line_pos; 917 + 918 + int cols = 80; 919 + #ifndef _WIN32 920 + struct winsize ws; 921 + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 922 + cols = ws.ws_col; 923 + } 924 + #else 925 + CONSOLE_SCREEN_BUFFER_INFO csbi; 926 + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi)) { 927 + cols = csbi.srWindow.Right - csbi.srWindow.Left + 1; 928 + } 929 + #endif 930 + 931 + int rows = total_cols / cols; 932 + int col_pos = total_cols % cols; 933 + 934 + jsval_t result = js_mkobj(js); 935 + js_set(js, result, "rows", js_mknum((double)rows)); 936 + js_set(js, result, "cols", js_mknum((double)col_pos)); 937 + return result; 938 + } 939 + 940 + static jsval_t rl_interface_closed_getter(struct js *js, jsval_t *args, int nargs) { 941 + (void)args; (void)nargs; 942 + jsval_t this_obj = js_getthis(js); 943 + rl_interface_t *iface = get_interface(js, this_obj); 944 + 945 + if (!iface) return js_mktrue(); 946 + return iface->closed ? js_mktrue() : js_mkfalse(); 947 + } 948 + 949 + static jsval_t rl_interface_async_iterator(struct js *js, jsval_t *args, int nargs) { 950 + (void)args; (void)nargs; 951 + jsval_t this_obj = js_getthis(js); 952 + rl_interface_t *iface = get_interface(js, this_obj); 953 + 954 + if (!iface) return js_mkerr(js, "Invalid Interface"); 955 + 956 + jsval_t iterator = js_mkobj(js); 957 + js_set(js, iterator, "_rl_id", js_mknum((double)iface->id)); 958 + js_set(js, iterator, "_lines", js_mkarr(js)); 959 + js_set(js, iterator, "_done", js_mkfalse()); 960 + 961 + return iterator; 962 + } 963 + 964 + static void free_interface(rl_interface_t *iface) { 965 + if (!iface) return; 966 + 967 + HASH_DEL(interfaces, iface); 968 + 969 + free(iface->prompt); 970 + free(iface->line_buffer); 971 + rl_history_free(&iface->history); 972 + 973 + RLEventType *evt, *tmp; 974 + HASH_ITER(hh, iface->events, evt, tmp) { 975 + HASH_DEL(iface->events, evt); 976 + free(evt->event_type); 977 + free(evt); 978 + } 979 + 980 + free(iface); 981 + } 982 + 983 + static jsval_t rl_create_interface(struct js *js, jsval_t *args, int nargs) { 984 + if (nargs < 1) return js_mkerr(js, "createInterface requires options"); 985 + 986 + jsval_t options = args[0]; 987 + if (js_type(options) != JS_OBJ) return js_mkerr(js, "options must be an object"); 988 + 989 + rl_interface_t *iface = calloc(1, sizeof(rl_interface_t)); 990 + if (!iface) return js_mkerr(js, "out of memory"); 991 + 992 + iface->id = next_interface_id++; 993 + iface->prompt = strdup(DEFAULT_PROMPT); 994 + iface->line_buffer = calloc(MAX_LINE_LENGTH, 1); 995 + iface->line_pos = 0; 996 + iface->line_len = 0; 997 + iface->paused = false; 998 + iface->closed = false; 999 + iface->reading = false; 1000 + iface->pending_question_resolve = js_mkundef(); 1001 + iface->pending_question_reject = js_mkundef(); 1002 + iface->events = NULL; 1003 + iface->tty_initialized = false; 1004 + iface->escape_state = 0; 1005 + iface->escape_len = 0; 1006 + iface->js_obj = js_mkundef(); 1007 + #ifndef _WIN32 1008 + iface->raw_mode = false; 1009 + #endif 1010 + 1011 + iface->input_stream = js_get(js, options, "input"); 1012 + iface->output_stream = js_get(js, options, "output"); 1013 + 1014 + jsval_t terminal_val = js_get(js, options, "terminal"); 1015 + iface->terminal = (js_type(terminal_val) == JS_TRUE) || 1016 + (js_type(terminal_val) == JS_UNDEF); 1017 + 1018 + jsval_t history_size_val = js_get(js, options, "historySize"); 1019 + iface->history_size = (js_type(history_size_val) == JS_NUM) 1020 + ? (int)js_getnum(history_size_val) 1021 + : DEFAULT_HISTORY_SIZE; 1022 + 1023 + jsval_t remove_dup_val = js_get(js, options, "removeHistoryDuplicates"); 1024 + iface->remove_history_duplicates = js_truthy(js, remove_dup_val); 1025 + 1026 + jsval_t prompt_val = js_get(js, options, "prompt"); 1027 + if (js_type(prompt_val) == JS_STR) { 1028 + free(iface->prompt); 1029 + iface->prompt = strdup(js_getstr(js, prompt_val, NULL)); 1030 + } 1031 + 1032 + jsval_t crlf_delay_val = js_get(js, options, "crlfDelay"); 1033 + iface->crlf_delay = (js_type(crlf_delay_val) == JS_NUM) 1034 + ? (int)js_getnum(crlf_delay_val) 1035 + : 100; 1036 + if (iface->crlf_delay < 100) iface->crlf_delay = 100; 1037 + 1038 + jsval_t tab_size_val = js_get(js, options, "tabSize"); 1039 + iface->tab_size = (js_type(tab_size_val) == JS_NUM) 1040 + ? (int)js_getnum(tab_size_val) 1041 + : DEFAULT_TAB_SIZE; 1042 + if (iface->tab_size < 1) iface->tab_size = 1; 1043 + 1044 + jsval_t completer_val = js_get(js, options, "completer"); 1045 + iface->completer = (js_type(completer_val) == JS_FUNC) ? completer_val : js_mkundef(); 1046 + 1047 + jsval_t history_val = js_get(js, options, "history"); 1048 + if (js_type(history_val) == JS_OBJ) { 1049 + jsval_t len_val = js_get(js, history_val, "length"); 1050 + int len = (js_type(len_val) == JS_NUM) ? (int)js_getnum(len_val) : 0; 1051 + 1052 + rl_history_init(&iface->history, iface->history_size); 1053 + 1054 + for (int i = 0; i < len; i++) { 1055 + char key[16]; 1056 + snprintf(key, sizeof(key), "%d", i); 1057 + jsval_t item = js_get(js, history_val, key); 1058 + if (js_type(item) == JS_STR) { 1059 + char *line = js_getstr(js, item, NULL); 1060 + if (line) rl_history_add(&iface->history, line, false); 1061 + } 1062 + } 1063 + } else { 1064 + rl_history_init(&iface->history, iface->history_size); 1065 + } 1066 + 1067 + HASH_ADD(hh, interfaces, id, sizeof(uint64_t), iface); 1068 + 1069 + jsval_t obj = js_mkobj(js); 1070 + js_set(js, obj, "_rl_id", js_mknum((double)iface->id)); 1071 + 1072 + js_set(js, obj, "on", js_mkfun(rl_interface_on)); 1073 + js_set(js, obj, "once", js_mkfun(rl_interface_once)); 1074 + js_set(js, obj, "off", js_mkfun(rl_interface_off)); 1075 + js_set(js, obj, "addListener", js_mkfun(rl_interface_on)); 1076 + js_set(js, obj, "removeListener", js_mkfun(rl_interface_off)); 1077 + js_set(js, obj, "emit", js_mkfun(rl_interface_emit)); 1078 + 1079 + js_set(js, obj, "close", js_mkfun(rl_interface_close)); 1080 + js_set(js, obj, "pause", js_mkfun(rl_interface_pause)); 1081 + js_set(js, obj, "resume", js_mkfun(rl_interface_resume)); 1082 + js_set(js, obj, "prompt", js_mkfun(rl_interface_prompt)); 1083 + js_set(js, obj, "setPrompt", js_mkfun(rl_interface_set_prompt)); 1084 + js_set(js, obj, "getPrompt", js_mkfun(rl_interface_get_prompt)); 1085 + js_set(js, obj, "write", js_mkfun(rl_interface_write)); 1086 + js_set(js, obj, "question", js_mkfun(rl_interface_question_callback)); 1087 + js_set(js, obj, "getCursorPos", js_mkfun(rl_interface_get_cursor_pos)); 1088 + 1089 + js_set_getter_desc(js, obj, "line", 4, js_mkfun(rl_interface_line_getter), JS_DESC_E | JS_DESC_C); 1090 + js_set_getter_desc(js, obj, "cursor", 6, js_mkfun(rl_interface_cursor_getter), JS_DESC_E | JS_DESC_C); 1091 + js_set_getter_desc(js, obj, "closed", 6, js_mkfun(rl_interface_closed_getter), JS_DESC_E | JS_DESC_C); 1092 + 1093 + js_set(js, obj, "terminal", iface->terminal ? js_mktrue() : js_mkfalse()); 1094 + js_set(js, obj, get_asyncIterator_sym_key(), js_mkfun(rl_interface_async_iterator)); 1095 + js_set(js, obj, get_toStringTag_sym_key(), js_mkstr(js, "Interface", 9)); 1096 + 1097 + return obj; 1098 + } 1099 + 1100 + static jsval_t rl_create_interface_promises(struct js *js, jsval_t *args, int nargs) { 1101 + jsval_t iface_obj = rl_create_interface(js, args, nargs); 1102 + if (js_type(iface_obj) == JS_ERR) return iface_obj; 1103 + js_set(js, iface_obj, "question", js_mkfun(rl_interface_question_promise)); 1104 + 1105 + return iface_obj; 1106 + } 1107 + 1108 + static jsval_t rl_clear_line(struct js *js, jsval_t *args, int nargs) { 1109 + if (nargs < 2) return js_mkfalse(); 1110 + int dir = (int)js_getnum(args[1]); 1111 + 1112 + const char *seq; 1113 + switch (dir) { 1114 + case -1: seq = "\033[1K"; break; 1115 + case 1: seq = "\033[0K"; break; 1116 + case 0: 1117 + default: seq = "\033[2K\r"; break; 1118 + } 1119 + 1120 + printf("%s", seq); 1121 + fflush(stdout); 1122 + 1123 + return js_mktrue(); 1124 + } 1125 + 1126 + static jsval_t rl_clear_screen_down(struct js *js, jsval_t *args, int nargs) { 1127 + (void)args; (void)nargs; 1128 + 1129 + printf("\033[J"); 1130 + fflush(stdout); 1131 + 1132 + return js_mktrue(); 1133 + } 1134 + 1135 + static jsval_t rl_cursor_to(struct js *js, jsval_t *args, int nargs) { 1136 + if (nargs < 2) return js_mkfalse(); 1137 + int x = (int)js_getnum(args[1]); 1138 + 1139 + if (nargs >= 3 && js_type(args[2]) == JS_NUM) { 1140 + int y = (int)js_getnum(args[2]); 1141 + printf("\033[%d;%dH", y + 1, x + 1); 1142 + } else { 1143 + printf("\033[%dG", x + 1); 1144 + } 1145 + fflush(stdout); 1146 + 1147 + return js_mktrue(); 1148 + } 1149 + 1150 + static jsval_t rl_move_cursor(struct js *js, jsval_t *args, int nargs) { 1151 + if (nargs < 3) return js_mkfalse(); 1152 + 1153 + int dx = (int)js_getnum(args[1]); 1154 + int dy = (int)js_getnum(args[2]); 1155 + 1156 + if (dx > 0) printf("\033[%dC", dx); 1157 + else if (dx < 0) printf("\033[%dD", -dx); 1158 + 1159 + if (dy > 0) printf("\033[%dB", dy); 1160 + else if (dy < 0) printf("\033[%dA", -dy); 1161 + 1162 + fflush(stdout); 1163 + return js_mktrue(); 1164 + } 1165 + 1166 + static jsval_t rl_emit_keypress_events(struct js *js, jsval_t *args, int nargs) { 1167 + (void)args; (void)nargs; 1168 + return js_mkundef(); 1169 + } 1170 + 1171 + bool has_active_readline_interfaces(void) { 1172 + rl_interface_t *iface, *tmp; 1173 + HASH_ITER(hh, interfaces, iface, tmp) { 1174 + if (!iface->closed && iface->reading) return true; 1175 + } 1176 + return false; 1177 + } 1178 + 1179 + void readline_gc_update_roots(GC_FWD_ARGS) { 1180 + rl_interface_t *iface, *tmp; 1181 + HASH_ITER(hh, interfaces, iface, tmp) { 1182 + iface->input_stream = fwd_val(ctx, iface->input_stream); 1183 + iface->output_stream = fwd_val(ctx, iface->output_stream); 1184 + iface->completer = fwd_val(ctx, iface->completer); 1185 + iface->js_obj = fwd_val(ctx, iface->js_obj); 1186 + iface->pending_question_resolve = fwd_val(ctx, iface->pending_question_resolve); 1187 + iface->pending_question_reject = fwd_val(ctx, iface->pending_question_reject); 1188 + 1189 + RLEventType *evt, *evt_tmp; 1190 + HASH_ITER(hh, iface->events, evt, evt_tmp) { 1191 + for (int i = 0; i < evt->listener_count; i++) evt->listeners[i].listener = fwd_val(ctx, evt->listeners[i].listener); 1192 + } 1193 + } 1194 + } 1195 + 1196 + jsval_t readline_library(struct js *js) { 1197 + jsval_t lib = js_mkobj(js); 1198 + 1199 + js_set(js, lib, "createInterface", js_mkfun(rl_create_interface)); 1200 + js_set(js, lib, "clearLine", js_mkfun(rl_clear_line)); 1201 + js_set(js, lib, "clearScreenDown", js_mkfun(rl_clear_screen_down)); 1202 + js_set(js, lib, "cursorTo", js_mkfun(rl_cursor_to)); 1203 + js_set(js, lib, "moveCursor", js_mkfun(rl_move_cursor)); 1204 + js_set(js, lib, "emitKeypressEvents", js_mkfun(rl_emit_keypress_events)); 1205 + js_set(js, lib, get_toStringTag_sym_key(), js_mkstr(js, "readline", 8)); 1206 + 1207 + return lib; 1208 + } 1209 + 1210 + jsval_t readline_promises_library(struct js *js) { 1211 + jsval_t lib = js_mkobj(js); 1212 + 1213 + js_set(js, lib, "createInterface", js_mkfun(rl_create_interface_promises)); 1214 + js_set(js, lib, "clearLine", js_mkfun(rl_clear_line)); 1215 + js_set(js, lib, "clearScreenDown", js_mkfun(rl_clear_screen_down)); 1216 + js_set(js, lib, "cursorTo", js_mkfun(rl_cursor_to)); 1217 + js_set(js, lib, "moveCursor", js_mkfun(rl_move_cursor)); 1218 + js_set(js, lib, "emitKeypressEvents", js_mkfun(rl_emit_keypress_events)); 1219 + js_set(js, lib, get_toStringTag_sym_key(), js_mkstr(js, "readline/promises", 17)); 1220 + 1221 + return lib; 1222 + } 1223 +
+8
src/modules/symbol.c
··· 7 7 #include "modules/symbol.h" 8 8 9 9 static jsval_t g_iterator_sym = 0; 10 + static jsval_t g_asyncIterator_sym = 0; 10 11 static jsval_t g_toStringTag_sym = 0; 11 12 static jsval_t g_hasInstance_sym = 0; 12 13 13 14 static char g_iter_sym_key[32] = {0}; 15 + static char g_asyncIter_sym_key[32] = {0}; 14 16 static char g_toStringTag_sym_key[32] = {0}; 15 17 16 18 jsval_t get_iterator_symbol(void) { return g_iterator_sym; } 19 + jsval_t get_asyncIterator_symbol(void) { return g_asyncIterator_sym; } 20 + 17 21 const char *get_iterator_sym_key(void) { return g_iter_sym_key; } 22 + const char *get_asyncIterator_sym_key(void) { return g_asyncIter_sym_key; } 18 23 const char *get_toStringTag_sym_key(void) { return g_toStringTag_sym_key; } 19 24 20 25 static jsval_t builtin_Symbol(struct js *js, jsval_t *args, int nargs) { ··· 150 155 struct js *js = rt->js; 151 156 152 157 g_iterator_sym = js_mksym(js, "Symbol.iterator"); 158 + g_asyncIterator_sym = js_mksym(js, "Symbol.asyncIterator"); 153 159 g_toStringTag_sym = js_mksym(js, "Symbol.toStringTag"); 154 160 g_hasInstance_sym = js_mksym(js, "Symbol.hasInstance"); 155 161 156 162 snprintf(g_iter_sym_key, sizeof(g_iter_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_iterator_sym)); 163 + snprintf(g_asyncIter_sym_key, sizeof(g_asyncIter_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_asyncIterator_sym)); 157 164 snprintf(g_toStringTag_sym_key, sizeof(g_toStringTag_sym_key), "__sym_%llu__", (unsigned long long)js_sym_id(g_toStringTag_sym)); 158 165 159 166 jsval_t symbol_ctor = js_mkobj(js); ··· 162 169 js_set(js, symbol_ctor, "keyFor", js_mkfun(builtin_Symbol_keyFor)); 163 170 164 171 js_set(js, symbol_ctor, "iterator", g_iterator_sym); 172 + js_set(js, symbol_ctor, "asyncIterator", g_asyncIterator_sym); 165 173 js_set(js, symbol_ctor, "toStringTag", g_toStringTag_sym); 166 174 js_set(js, symbol_ctor, "hasInstance", g_hasInstance_sym); 167 175