jscall 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +225 -29
- data/examples/pdf-js.rb +56 -0
- data/lib/jscall/browser.mjs +36 -11
- data/lib/jscall/browser.rb +29 -7
- data/lib/jscall/main.mjs +218 -62
- data/lib/jscall/synch.mjs +185 -0
- data/lib/jscall/version.rb +1 -1
- data/lib/jscall.rb +246 -75
- metadata +8 -6
data/lib/jscall/main.mjs
CHANGED
@@ -1,12 +1,18 @@
|
|
1
1
|
// Copyright (C) 2022- Shigeru Chiba. All rights reserved.
|
2
2
|
|
3
|
-
const cmd_eval = 1
|
4
|
-
const cmd_call = 2
|
5
|
-
const cmd_reply = 3
|
3
|
+
export const cmd_eval = 1
|
4
|
+
export const cmd_call = 2
|
5
|
+
export const cmd_reply = 3
|
6
|
+
export const cmd_async_call = 4
|
7
|
+
export const cmd_async_eval = 5
|
8
|
+
export const cmd_retry = 6
|
9
|
+
export const cmd_reject = 7
|
10
|
+
|
6
11
|
const param_array = 0
|
7
12
|
const param_object = 1
|
8
13
|
const param_local_object = 2
|
9
14
|
const param_error = 3
|
15
|
+
const param_hash = 4
|
10
16
|
|
11
17
|
const table_size = 100
|
12
18
|
|
@@ -64,8 +70,9 @@ const exported = new class {
|
|
64
70
|
}
|
65
71
|
}
|
66
72
|
|
67
|
-
|
73
|
+
class RemoteRef extends Function {
|
68
74
|
constructor(id) {
|
75
|
+
super()
|
69
76
|
this.id = id
|
70
77
|
}
|
71
78
|
}
|
@@ -74,12 +81,20 @@ const remoteRefHandler = new class {
|
|
74
81
|
get(obj, name) {
|
75
82
|
if (name === '__self__')
|
76
83
|
return obj
|
84
|
+
else if (name === 'then')
|
85
|
+
// to prevent the Promise from handling RemoteRefs as thenable
|
86
|
+
// e.g., `Jscall.exec("{ call: (x) => Promise.resolve(x) }").call(obj)' should return that obj itself
|
87
|
+
return undefined
|
77
88
|
else
|
78
89
|
return (...args) => {
|
79
90
|
// this returns Promise
|
80
91
|
return funcall_to_ruby(obj.id, name, args)
|
81
92
|
}
|
82
93
|
}
|
94
|
+
apply(obj, self, args) {
|
95
|
+
// this returns Promise
|
96
|
+
return funcall_to_ruby(obj.id, 'call', args)
|
97
|
+
}
|
83
98
|
}
|
84
99
|
|
85
100
|
const imported = new class {
|
@@ -94,7 +109,8 @@ const imported = new class {
|
|
94
109
|
if (obj !== null && obj !== undefined)
|
95
110
|
return obj
|
96
111
|
else {
|
97
|
-
const
|
112
|
+
const ref = new RemoteRef(index)
|
113
|
+
const rref = new Proxy(ref, remoteRefHandler)
|
98
114
|
this.objects[index] = new WeakRef(rref)
|
99
115
|
return rref
|
100
116
|
}
|
@@ -104,14 +120,15 @@ const imported = new class {
|
|
104
120
|
|
105
121
|
// collect reclaimed RemoteRef objects.
|
106
122
|
dead_references() {
|
107
|
-
|
123
|
+
// In Safari, deref() may return null
|
124
|
+
if (this.canary === null || this.canary.deref() == undefined)
|
108
125
|
this.canary = new WeakRef(new RemoteRef(-1))
|
109
126
|
else
|
110
127
|
return [] // the canary is alive, so no GC has happened yet.
|
111
128
|
|
112
129
|
const deads = []
|
113
130
|
this.objects.forEach((wref, index) => {
|
114
|
-
if (wref !== null && wref !== undefined && wref.deref()
|
131
|
+
if (wref !== null && wref !== undefined && wref.deref() == undefined) {
|
115
132
|
this.objects[index] = null
|
116
133
|
deads.push(index)
|
117
134
|
}
|
@@ -127,15 +144,21 @@ const encode_obj = obj => {
|
|
127
144
|
return null
|
128
145
|
else if (obj.constructor === Array)
|
129
146
|
return [param_array, obj.map(e => encode_obj(e))]
|
147
|
+
else if (obj instanceof Map) {
|
148
|
+
const hash = {}
|
149
|
+
for (const [key, value] of obj.entries())
|
150
|
+
hash[key] = value
|
151
|
+
return [param_hash, hash]
|
152
|
+
}
|
130
153
|
else if (obj instanceof RemoteRef)
|
131
154
|
return [param_local_object, exported.export_remoteref(obj)]
|
132
155
|
else
|
133
156
|
return [param_object, exported.export(obj)]
|
134
157
|
}
|
135
158
|
|
136
|
-
const encode_error = msg => [param_error, msg]
|
159
|
+
const encode_error = msg => [param_error, msg.toString()]
|
137
160
|
|
138
|
-
|
161
|
+
class RubyError {
|
139
162
|
constructor(msg) { this.message = msg }
|
140
163
|
get() { return 'RubyError: ' + this.message }
|
141
164
|
}
|
@@ -146,6 +169,12 @@ const decode_obj = obj => {
|
|
146
169
|
else if (obj.constructor === Array && obj.length == 2)
|
147
170
|
if (obj[0] == param_array)
|
148
171
|
return obj[1].map(e => decode_obj(e))
|
172
|
+
else if (obj[0] == param_hash) {
|
173
|
+
const hash = {}
|
174
|
+
for (const [key, value] of Object.entries(obj[1]))
|
175
|
+
hash[key] = decode_obj(value)
|
176
|
+
return hash
|
177
|
+
}
|
149
178
|
else if (obj[0] == param_object)
|
150
179
|
return imported.import(obj[1])
|
151
180
|
else if (obj[0] == param_local_object)
|
@@ -156,12 +185,20 @@ const decode_obj = obj => {
|
|
156
185
|
throw `decode_obj: unsupported value, ${obj}`
|
157
186
|
}
|
158
187
|
|
188
|
+
export const decode_obj_or_error = obj => {
|
189
|
+
const result = decode_obj(obj)
|
190
|
+
if (result instanceof RubyError)
|
191
|
+
return result.get()
|
192
|
+
else
|
193
|
+
return result
|
194
|
+
}
|
195
|
+
|
159
196
|
const js_eval = eval
|
160
197
|
|
161
|
-
const funcall_from_ruby = cmd => {
|
162
|
-
const receiver = decode_obj(cmd[
|
163
|
-
const name = cmd[
|
164
|
-
const args = cmd[
|
198
|
+
export const funcall_from_ruby = cmd => {
|
199
|
+
const receiver = decode_obj(cmd[2])
|
200
|
+
const name = cmd[3]
|
201
|
+
const args = cmd[4].map(e => decode_obj(e))
|
165
202
|
if (name.endsWith('=')) {
|
166
203
|
const name2 = name.substring(0, name.length - 1)
|
167
204
|
if (receiver === null)
|
@@ -189,34 +226,41 @@ const funcall_from_ruby = cmd => {
|
|
189
226
|
return f // obtain a propety
|
190
227
|
}
|
191
228
|
|
192
|
-
throw `unknown function/method was called
|
229
|
+
throw `unknown JS function/method was called: ${name} on <${receiver}>`
|
193
230
|
}
|
194
231
|
|
195
|
-
let stdout_puts = console.log
|
232
|
+
export let stdout_puts = console.log
|
233
|
+
let num_generated_ids = 0
|
196
234
|
|
197
|
-
const
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
else {
|
202
|
-
if (last_callback_stack_depth < callback_stack.length)
|
203
|
-
throw 'Ruby code was called without await. Call Jscall.close for recovery'
|
235
|
+
const fresh_id = () => {
|
236
|
+
num_generated_ids += 1
|
237
|
+
return num_generated_ids
|
238
|
+
}
|
204
239
|
|
240
|
+
export const reply = (message_id, value, sync_mode) => {
|
241
|
+
if (sync_mode && value instanceof Promise)
|
242
|
+
value.then(result => { reply(message_id, result, true) })
|
243
|
+
.catch(err => reply_error(message_id, err))
|
244
|
+
else {
|
205
245
|
try {
|
206
|
-
const cmd = reply_with_piggyback([cmd_reply, encode_obj(value)])
|
246
|
+
const cmd = reply_with_piggyback([cmd_reply, message_id, encode_obj(value)])
|
207
247
|
const data = JSON.stringify(cmd)
|
208
248
|
stdout_puts(data)
|
209
249
|
} catch (e) {
|
210
|
-
reply_error(e)
|
250
|
+
reply_error(message_id, e)
|
211
251
|
}
|
212
252
|
}
|
213
253
|
}
|
214
254
|
|
215
|
-
const reply_error = e => {
|
216
|
-
const cmd = reply_with_piggyback([cmd_reply, encode_error(e)])
|
255
|
+
export const reply_error = (message_id, e) => {
|
256
|
+
const cmd = reply_with_piggyback([cmd_reply, message_id, encode_error(e)])
|
217
257
|
stdout_puts(JSON.stringify(cmd))
|
218
258
|
}
|
219
259
|
|
260
|
+
const puts_retry_cmd = msg_id => {
|
261
|
+
stdout_puts(JSON.stringify([cmd_retry, msg_id, encode_obj(false)]))
|
262
|
+
}
|
263
|
+
|
220
264
|
export const scavenge_references = () => {
|
221
265
|
reply_counter = 200
|
222
266
|
imported.kill_canary()
|
@@ -230,7 +274,7 @@ const reply_with_piggyback = cmd => {
|
|
230
274
|
const dead_refs = imported.dead_references()
|
231
275
|
if (dead_refs.length > 0) {
|
232
276
|
const cmd2 = cmd.concat()
|
233
|
-
cmd2[
|
277
|
+
cmd2[5] = dead_refs
|
234
278
|
return cmd2
|
235
279
|
}
|
236
280
|
}
|
@@ -239,58 +283,159 @@ const reply_with_piggyback = cmd => {
|
|
239
283
|
}
|
240
284
|
|
241
285
|
const callback_stack = []
|
242
|
-
let last_callback_stack_depth = 0
|
243
286
|
let reply_counter = 0
|
244
287
|
|
245
288
|
export const exec = src => {
|
246
289
|
return new Promise((resolve, reject) => {
|
247
|
-
const cmd =
|
248
|
-
|
290
|
+
const cmd = make_cmd_eval(src)
|
291
|
+
const message_id = cmd[1]
|
292
|
+
callback_stack.push([message_id, resolve, reject])
|
249
293
|
stdout_puts(JSON.stringify(cmd))
|
250
294
|
})
|
251
295
|
}
|
252
296
|
|
253
|
-
const
|
297
|
+
export const make_cmd_eval = src => {
|
298
|
+
const message_id = fresh_id()
|
299
|
+
return reply_with_piggyback([cmd_eval, message_id, src])
|
300
|
+
}
|
301
|
+
|
302
|
+
let funcall_to_ruby = (receiver_id, name, args) => {
|
254
303
|
return new Promise((resolve, reject) => {
|
255
|
-
const
|
256
|
-
const
|
257
|
-
|
258
|
-
callback_stack.push([resolve, reject])
|
304
|
+
const cmd = make_cmd_call(receiver_id, name, args)
|
305
|
+
const message_id = cmd[1]
|
306
|
+
callback_stack.push([message_id, resolve, reject])
|
259
307
|
stdout_puts(JSON.stringify(cmd))
|
260
308
|
})
|
261
309
|
}
|
262
310
|
|
311
|
+
export const set_funcall_to_ruby = f => { funcall_to_ruby = f }
|
312
|
+
|
313
|
+
export const make_cmd_call = (receiver_id, name, args) => {
|
314
|
+
const message_id = fresh_id()
|
315
|
+
const receiver = [param_local_object, receiver_id]
|
316
|
+
const encoded_args = args.map(e => encode_obj(e))
|
317
|
+
return reply_with_piggyback([cmd_call, message_id, receiver, name, encoded_args])
|
318
|
+
}
|
319
|
+
|
263
320
|
const returned_from_callback = cmd => {
|
264
|
-
const
|
265
|
-
const
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
321
|
+
const message_id = cmd[1]
|
322
|
+
const result = decode_obj(cmd[2])
|
323
|
+
for (let i = callback_stack.length - 1; i >= 0; i--) {
|
324
|
+
// check the most recent element first since callbacks are
|
325
|
+
// assumed to be synchronously executed
|
326
|
+
if (callback_stack[i][0] === message_id) {
|
327
|
+
const [[_, resolve, reject]] = callback_stack.splice(i, 1)
|
328
|
+
if (result instanceof RubyError)
|
329
|
+
reject(result.get())
|
330
|
+
else
|
331
|
+
resolve(result)
|
332
|
+
}
|
333
|
+
}
|
270
334
|
}
|
271
335
|
|
272
|
-
class
|
336
|
+
export class MessageReader {
|
337
|
+
static HeaderSize = 6
|
338
|
+
|
273
339
|
constructor(stream) {
|
274
|
-
this.stream
|
275
|
-
this.
|
340
|
+
this.stream = stream
|
341
|
+
this.state = "header"
|
342
|
+
this.acc = ""
|
343
|
+
this.bodySize = 0
|
344
|
+
}
|
345
|
+
|
346
|
+
parseHeader(pos) {
|
347
|
+
// skip leading whitespace characters as a countermeasure against leftover "\n"
|
348
|
+
while (pos < this.acc.length && /\s/.test(this.acc[pos]))
|
349
|
+
pos++
|
350
|
+
|
351
|
+
if (this.acc.length >= MessageReader.HeaderSize) {
|
352
|
+
const start = pos
|
353
|
+
pos += MessageReader.HeaderSize
|
354
|
+
return [parseInt(this.acc.slice(start, pos), 16), pos]
|
355
|
+
}
|
356
|
+
else
|
357
|
+
return undefined
|
358
|
+
}
|
359
|
+
|
360
|
+
parseBody(pos) {
|
361
|
+
if (this.acc.length >= this.bodySize) {
|
362
|
+
const start = pos
|
363
|
+
pos += this.bodySize
|
364
|
+
return [this.acc.slice(start, pos), pos]
|
365
|
+
}
|
366
|
+
else
|
367
|
+
return undefined
|
368
|
+
}
|
369
|
+
|
370
|
+
consume(pos) {
|
371
|
+
if (pos > 0)
|
372
|
+
this.acc = this.acc.slice(pos)
|
276
373
|
}
|
277
374
|
|
278
375
|
async *[Symbol.asyncIterator]() {
|
279
|
-
for await (
|
376
|
+
for await (const data of this.stream) {
|
280
377
|
this.acc += data
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
378
|
+
let pos = 0
|
379
|
+
while (true) {
|
380
|
+
const result = this.iteratorBody(pos)
|
381
|
+
if (result[0] === false)
|
382
|
+
break
|
383
|
+
else if (result[0] !== true)
|
384
|
+
yield result[0] // result[0] is a string
|
385
|
+
|
386
|
+
pos = result[1]
|
387
|
+
if (this.checkEmptiness(pos))
|
388
|
+
break
|
389
|
+
}
|
390
|
+
}
|
391
|
+
this.checkEOS()
|
392
|
+
}
|
393
|
+
|
394
|
+
iteratorBody(pos) {
|
395
|
+
if (this.state === "header") {
|
396
|
+
const header = this.parseHeader(pos)
|
397
|
+
if (header === undefined) {
|
398
|
+
this.consume(pos)
|
399
|
+
return [false, pos]
|
400
|
+
} else {
|
401
|
+
this.bodySize = header[0]
|
402
|
+
pos = header[1]
|
403
|
+
this.state = "body"
|
286
404
|
}
|
287
405
|
}
|
288
|
-
if (this.
|
289
|
-
|
406
|
+
if (this.state === "body") {
|
407
|
+
const body = this.parseBody(pos)
|
408
|
+
if (body === undefined) {
|
409
|
+
this.consume(pos)
|
410
|
+
return [false, pos]
|
411
|
+
} else {
|
412
|
+
this.state = "header"
|
413
|
+
return body
|
414
|
+
}
|
415
|
+
}
|
416
|
+
return [true, pos]
|
417
|
+
}
|
418
|
+
|
419
|
+
checkEmptiness(pos) {
|
420
|
+
if (pos == this.acc.length || (pos == this.acc.length - 1
|
421
|
+
&& this.acc[this.acc.length - 1] === "\n")) {
|
422
|
+
this.acc = ""
|
423
|
+
return true
|
290
424
|
}
|
425
|
+
else
|
426
|
+
return false
|
427
|
+
}
|
428
|
+
|
429
|
+
checkEOS() {
|
430
|
+
if (this.acc.length > 0)
|
431
|
+
throw new Error("The pipe closed after receiving an incomplete message")
|
291
432
|
}
|
292
433
|
}
|
293
434
|
|
435
|
+
let make_message_reader = (stdin) => new MessageReader(stdin)
|
436
|
+
|
437
|
+
export const set_make_message_reader = (f) => { make_message_reader = f }
|
438
|
+
|
294
439
|
export const start = async (stdin, use_stdout) => {
|
295
440
|
if (use_stdout)
|
296
441
|
console.log = console.error // on node.js
|
@@ -298,30 +443,41 @@ export const start = async (stdin, use_stdout) => {
|
|
298
443
|
stdout_puts = (m) => stdin.puts(m) // on browser
|
299
444
|
|
300
445
|
stdin.setEncoding('utf8')
|
301
|
-
for await (const
|
446
|
+
for await (const json_data of make_message_reader(stdin)) {
|
447
|
+
let cmd
|
302
448
|
try {
|
303
|
-
|
449
|
+
cmd = JSON.parse(json_data)
|
304
450
|
|
305
451
|
// scavenge remote references
|
306
|
-
if (cmd.length >
|
307
|
-
cmd[
|
452
|
+
if (cmd.length > 5)
|
453
|
+
cmd[5].forEach(i => exported.remove(i))
|
308
454
|
|
309
455
|
if (cmd[0] == cmd_eval) {
|
310
|
-
const result = js_eval(cmd[
|
311
|
-
reply(result)
|
456
|
+
const result = js_eval(cmd[2])
|
457
|
+
reply(cmd[1], result, true)
|
312
458
|
}
|
313
459
|
else if (cmd[0] == cmd_call) {
|
314
460
|
const result = funcall_from_ruby(cmd)
|
315
|
-
reply(result)
|
461
|
+
reply(cmd[1], result, true)
|
316
462
|
}
|
317
463
|
else if (cmd[0] == cmd_reply)
|
318
464
|
returned_from_callback(cmd)
|
319
|
-
else
|
320
|
-
|
465
|
+
else if (cmd[0] == cmd_async_call) {
|
466
|
+
const result = funcall_from_ruby(cmd)
|
467
|
+
reply(cmd[1], result, false)
|
468
|
+
}
|
469
|
+
else if (cmd[0] == cmd_async_eval) {
|
470
|
+
const result = js_eval(cmd[2])
|
471
|
+
reply(cmd[1], result, false)
|
472
|
+
}
|
473
|
+
else if (cmd[0] == cmd_reject)
|
474
|
+
puts_retry_cmd(cmd[1])
|
475
|
+
else // cmd_retry and other unknown commands
|
476
|
+
reply_error(cmd[1], `invalid command ${cmd[0]}`)
|
321
477
|
} catch (error) {
|
322
478
|
const msg = typeof error === 'string' ? error : error.toString() +
|
323
479
|
'\n ---\n' + error.stack
|
324
|
-
reply_error(msg)
|
480
|
+
reply_error(cmd[1], msg)
|
325
481
|
}
|
326
482
|
}
|
327
483
|
}
|
@@ -0,0 +1,185 @@
|
|
1
|
+
// Copyright (C) 2022- Shigeru Chiba. All rights reserved.
|
2
|
+
// This works only with node.js on Linux
|
3
|
+
|
4
|
+
import * as main from './main.mjs'
|
5
|
+
import { readSync, openSync } from 'fs'
|
6
|
+
|
7
|
+
class SynchronousStdin {
|
8
|
+
constructor() {
|
9
|
+
this.buf_size = 4096
|
10
|
+
this.buffer = Buffer.alloc(this.buf_size);
|
11
|
+
this.stdin = openSync('/dev/stdin', 'rs')
|
12
|
+
}
|
13
|
+
|
14
|
+
*[Symbol.iterator]() {
|
15
|
+
let str
|
16
|
+
while ((str = this.readOne()) !== null)
|
17
|
+
yield str
|
18
|
+
}
|
19
|
+
|
20
|
+
readOne() {
|
21
|
+
while (true) {
|
22
|
+
try {
|
23
|
+
const nbytes = readSync(this.stdin, this.buffer, 0, this.buf_size)
|
24
|
+
if (nbytes > 0)
|
25
|
+
return this.buffer.toString('utf-8', 0, nbytes)
|
26
|
+
else
|
27
|
+
return null // maybe EOF on macOS
|
28
|
+
}
|
29
|
+
catch (e) {
|
30
|
+
if (e.code === 'EOF')
|
31
|
+
return null
|
32
|
+
else if (e.code !== 'EAGAIN')
|
33
|
+
throw e
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
class SyncMessageReader extends main.MessageReader {
|
40
|
+
constructor(stream) {
|
41
|
+
super(stream)
|
42
|
+
this.stdin = new SynchronousStdin()
|
43
|
+
this.generator = null
|
44
|
+
}
|
45
|
+
|
46
|
+
gets_function() {
|
47
|
+
const iterator = this[Symbol.iterator]()
|
48
|
+
return () => {
|
49
|
+
const v = iterator.next()
|
50
|
+
if (v.done)
|
51
|
+
return null
|
52
|
+
else
|
53
|
+
return v.value
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
async *[Symbol.asyncIterator]() {
|
58
|
+
if (this.generator !== null) {
|
59
|
+
while (true) {
|
60
|
+
const v = this.generator.next()
|
61
|
+
if (v.done)
|
62
|
+
break
|
63
|
+
else
|
64
|
+
yield v.value
|
65
|
+
}
|
66
|
+
}
|
67
|
+
for await (const data of this.stream) {
|
68
|
+
this.acc += data
|
69
|
+
this.generator = this.generatorBody()
|
70
|
+
while (true) {
|
71
|
+
const v = this.generator.next()
|
72
|
+
if (v.done)
|
73
|
+
break
|
74
|
+
else
|
75
|
+
yield v.value
|
76
|
+
}
|
77
|
+
}
|
78
|
+
this.checkEOS()
|
79
|
+
}
|
80
|
+
|
81
|
+
*[Symbol.iterator]() {
|
82
|
+
if (this.generator !== null) {
|
83
|
+
while (true) {
|
84
|
+
const v = this.generator.next()
|
85
|
+
if (v.done)
|
86
|
+
break
|
87
|
+
else
|
88
|
+
yield v.value
|
89
|
+
}
|
90
|
+
}
|
91
|
+
for (const data of this.stdin) {
|
92
|
+
this.acc += data
|
93
|
+
this.generator = this.generatorBody()
|
94
|
+
while (true) {
|
95
|
+
const v = this.generator.next()
|
96
|
+
if (v.done)
|
97
|
+
break
|
98
|
+
else
|
99
|
+
yield v.value
|
100
|
+
}
|
101
|
+
}
|
102
|
+
this.checkEOS()
|
103
|
+
}
|
104
|
+
|
105
|
+
*generatorBody() {
|
106
|
+
let pos = 0
|
107
|
+
while (true) {
|
108
|
+
const result = this.iteratorBody(pos)
|
109
|
+
if (result[0] === false)
|
110
|
+
break
|
111
|
+
else if (result[0] !== true)
|
112
|
+
yield result[0] // result[0] is a string
|
113
|
+
|
114
|
+
pos = result[1]
|
115
|
+
if (this.checkEmptiness(pos))
|
116
|
+
break
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
export const start = main.start
|
122
|
+
|
123
|
+
let stdin_gets = null
|
124
|
+
|
125
|
+
main.set_make_message_reader((stdin) => {
|
126
|
+
const reader = new SyncMessageReader(stdin)
|
127
|
+
stdin_gets = reader.gets_function()
|
128
|
+
return reader
|
129
|
+
})
|
130
|
+
|
131
|
+
const js_eval = eval
|
132
|
+
const exported = main.get_exported_imported()[0]
|
133
|
+
|
134
|
+
const event_loop = () => {
|
135
|
+
let json_data
|
136
|
+
while ((json_data = stdin_gets()) !== null) {
|
137
|
+
let cmd
|
138
|
+
try {
|
139
|
+
cmd = JSON.parse(json_data)
|
140
|
+
|
141
|
+
// scavenge remote references
|
142
|
+
if (cmd.length > 5)
|
143
|
+
cmd[5].forEach(i => exported.remove(i))
|
144
|
+
|
145
|
+
if (cmd[0] == main.cmd_eval || cmd[0] == main.cmd_async_eval) {
|
146
|
+
const result = js_eval(cmd[2])
|
147
|
+
main.reply(cmd[1], result, false)
|
148
|
+
}
|
149
|
+
else if (cmd[0] == main.cmd_call || cmd[0] == main.cmd_async_call) {
|
150
|
+
const result = main.funcall_from_ruby(cmd)
|
151
|
+
main.reply(cmd[1], result, false)
|
152
|
+
}
|
153
|
+
else if (cmd[0] == main.cmd_reply)
|
154
|
+
return main.decode_obj_or_error(cmd[2])
|
155
|
+
else { // cmd_retry, cmd_reject, and other unknown commands
|
156
|
+
console.error(`*** node.js; bad message received: ${json_data}`)
|
157
|
+
break
|
158
|
+
}
|
159
|
+
} catch (error) {
|
160
|
+
const msg = typeof error === 'string' ? error : error.toString() +
|
161
|
+
'\n ---\n' + error.stack
|
162
|
+
main.reply_error(cmd[1], msg)
|
163
|
+
}
|
164
|
+
}
|
165
|
+
return undefined
|
166
|
+
}
|
167
|
+
|
168
|
+
export const exec = src => {
|
169
|
+
const cmd = main.make_cmd_eval(src)
|
170
|
+
main.stdout_puts(JSON.stringify(cmd))
|
171
|
+
return event_loop()
|
172
|
+
}
|
173
|
+
|
174
|
+
main.set_funcall_to_ruby((receiver_id, name, args) => {
|
175
|
+
const cmd = main.make_cmd_call(receiver_id, name, args)
|
176
|
+
main.stdout_puts(JSON.stringify(cmd))
|
177
|
+
return event_loop()
|
178
|
+
})
|
179
|
+
|
180
|
+
export const scavenge_references = main.scavenge_references
|
181
|
+
|
182
|
+
// for testing and debugging
|
183
|
+
export const get_exported_imported = main.get_exported_imported
|
184
|
+
|
185
|
+
export const dyn_import = main.dyn_import
|
data/lib/jscall/version.rb
CHANGED