Skip to content

Instantly share code, notes, and snippets.

@bellbind
Last active August 4, 2020 11:01
Show Gist options
  • Save bellbind/27f96a2a3f6ed71ea85db92c9f906008 to your computer and use it in GitHub Desktop.
Save bellbind/27f96a2a3f6ed71ea85db92c9f906008 to your computer and use it in GitHub Desktop.
[swift4][macos] commandline REPL for JavaScriptCore with toplevel await
#!/usr/bin/env swift
// REPL for JavaScriptCore (for arrow keys, use rlwrap command)
import JavaScriptCore
class Timeout: NSObject, JSExport {
let item: DispatchWorkItem
init(_ item: DispatchWorkItem) {self.item = item}
}
func installBuiltins(_ ctx: JSContext) {
// simple console.log()
let console = ctx.evaluateScript("console")
let log: @convention(block) (JSValue) -> () = {arg in
if arg.isUndefined {print()} else {print(arg)}
}
console?.setValue(log, forProperty: "log")
// id = setTimeout(fn, ms, v) and clearTimeout(id)
let setTimeout: @convention(block) (JSValue, JSValue, JSValue) -> JSValue = {fn, ms, v in
let n = ms.toNumber().doubleValue / 1000
let item = DispatchWorkItem() {fn.call(withArguments: [v as Any])}
DispatchQueue.global().asyncAfter(deadline: .now() + n, execute: item)
return JSValue(object: Timeout(item), in: fn.context)
}
ctx.globalObject.setValue(setTimeout, forProperty: "setTimeout")
let clearTimeout: @convention(block) (JSValue) -> () = {value in
if value.isInstance(of: Timeout.self) {
let timeout = value.toObjectOf(Timeout.self) as? Timeout
timeout?.item.cancel()
}
}
ctx.globalObject.setValue(clearTimeout, forProperty: "clearTimeout")
}
class Repl {
private let sem: DispatchSemaphore
private let ctx: JSContext
private let syntaxError: JSValue
private let notEoS: JSValue
private let promise: JSValue
init() {
self.sem = DispatchSemaphore(value: 0)
self.ctx = JSContext()
self.promise = self.ctx.evaluateScript("Promise")
self.ctx.evaluateScript("(")
self.syntaxError = self.ctx.exception.forProperty("constructor")
self.notEoS = self.ctx.exception.forProperty("message")
self.ctx.exception = nil
installBuiltins(self.ctx)
}
private func repl(_ lines: String, _ prompt: String) {
print(prompt, terminator: "")
guard let line = readLine(strippingNewline: false) else {
self.sem.signal()
return
}
let code = lines + line
if code == "\n" {return asyncRepl(lines, prompt)}
let value = self.ctx.evaluateScript(code)
if let exp = self.ctx.exception {
self.ctx.exception = nil
let se = exp.isInstance(of: self.syntaxError)
let cont = se && exp.forProperty("message").isEqual(to: self.notEoS)
if cont {
return asyncRepl(code, "+ ")
} else if se {
// try toplevel await wrapping
let asyncF = self.ctx.evaluateScript("(async () => \(code))")
if self.ctx.exception == nil {
if let promise = asyncF?.call(withArguments: []) {
if promise.isInstance(of: self.promise) {
callPromise(promise)
return asyncRepl("", "> ")
}
}
}
self.ctx.exception = nil
}
print(exp)
return asyncRepl("", "> ")
}
if let value = value {print(value)}
return asyncRepl("", "> ")
}
private func asyncRepl(_ lines: String, _ prompt: String) {
DispatchQueue.global().async {[weak self] in self?.repl(lines, prompt)}
}
private func callPromise(_ promise: JSValue) {
let out: @convention(block) (JSValue) -> () = {obj in print("[resolved] \(obj)")}
let cb1 = JSValue(object: out, in: self.ctx)
let err: @convention(block) (JSValue) -> () = {obj in print("[rejected] \(obj)")}
let cb2 = JSValue(object: err, in: self.ctx)
promise.invokeMethod("then", withArguments: [cb1 as Any, cb2 as Any])
}
func run() {
self.asyncRepl("", "> ")
self.sem.wait()
}
}
Repl().run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment