This commit is contained in:
Dmitry Vasilev
2022-12-02 04:13:32 +08:00
parent f062056ad1
commit 0845a87960
12 changed files with 272 additions and 5 deletions

View File

@@ -0,0 +1,24 @@
// external
import {} from 'https://unpkg.com/jquery'
// external
import {fromEvent} from 'https://unpkg.com/baconjs?module'
const upEl = globalThis.document.createElement('button')
const downEl = globalThis.document.createElement('button')
const counterEl = globalThis.document.createElement('div')
globalThis.document.body.appendChild(upEl)
globalThis.document.body.appendChild(downEl)
globalThis.document.body.appendChild(counterEl)
const up = fromEvent(upEl, 'click');
const down = fromEvent(downEl, 'click');
const counter =
// map up to 1, down to -1
up.map(1).merge(down.map(-1))
// accumulate sum
.scan(0, (x,y) => x + y);
// assign observable value to jQuery property text
counter.onValue(text => Object.assign(counterEl, {innerText: text}));

View File

@@ -6,6 +6,10 @@
import {el, stringify, scrollIntoViewIfNeeded} from './domutils.js'
// TODO only test for globalThis.<GlobalObject> because we only eval code in
// another window
const has_custom_toString = object =>
object.toString != globalThis.run_window.Object.prototype.toString
&&
@@ -16,6 +20,11 @@ const isError = object =>
||
object instanceof globalThis.run_window.Error
const isPromise = object =>
object instanceof Promise
||
object instanceof globalThis.run_window.Promise
const displayed_entries = object => {
if(Array.isArray(object)) {
return object.map((v, i) => [i, v])
@@ -39,6 +48,8 @@ export const stringify_for_header = v => {
} else if(type == 'function') {
// TODO clickable link, 'fn', cursive
return 'fn ' + v.name
} else if (isPromise(v)) {
return 'Promise<>'
} else if(isError(v)) {
return v.toString()
} else if(type == 'object') {
@@ -70,7 +81,9 @@ export const header = object => {
} else if(object == null) {
return 'null'
} else if(typeof(object) == 'object') {
if(isError(object)) {
if(isPromise(object)) {
return 'Promise<>'
} else if(isError(object)) {
return object.toString()
} else if(Array.isArray(object)) {
return '['

5
src/effects.js vendored
View File

@@ -8,6 +8,7 @@ import {
import {current_cursor_position} from './calltree.js'
import {FLAGS} from './feature_flags.js'
import {exec, FILES_ROOT} from './index.js'
import {unwrap_settled_promises} from './unwrap_promises.js'
// Imports in the context of `run_window`, so global variables in loaded
// modules refer to that window's context
@@ -128,7 +129,7 @@ export const render_initial_state = (ui, state) => {
ui.editor.switch_session(state.current_module)
}
export const render_common_side_effects = (prev, next, command, ui) => {
export const render_common_side_effects = async (prev, next, command, ui) => {
if(
prev.project_dir != next.project_dir
||
@@ -187,6 +188,8 @@ export const render_common_side_effects = (prev, next, command, ui) => {
} else {
await unwrap_settled_promises(next.calltree)
if(
prev.calltree == null
||

View File

@@ -73,7 +73,7 @@ const codegen_function_expr = (node, cxt) => {
const args = node.function_args.children.map(do_codegen).join(',')
const call = `(${args}) => ` + (
const call = (node.is_async ? 'async ' : '') + `(${args}) => ` + (
(node.body.type == 'do')
? '{' + do_codegen(node.body) + '}'
: '(' + do_codegen(node.body) + ')'
@@ -384,7 +384,17 @@ export const eval_modules = (
try {
value = fn(...args)
ok = true
return value
return value instanceof Promise.Original
? value
.then(v => {
value.status = {ok: true, value: v}
return v
})
.catch(e => {
value.status = {ok: false, error: e}
throw e
})
: value
} catch(_error) {
ok = false
error = _error
@@ -917,6 +927,10 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
value = typeof(expr.result.value)
} else if(node.operator == '-') {
value = - expr.result.value
} else if(node.operator == 'await') {
log('expr', expr.result.value.status)
value = expr.result.value
//throw new Error('not implemented')
} else {
throw new Error('unknown op')
}

View File

@@ -243,6 +243,7 @@ code analysis:
- cannot import names that are not exported from modules
- module can be imported either as external or regular
- cannot return from modules (even inside toplevel if statements)
- await only in async fns
*/
export const analyze = (node, is_toplevel = true) => {
// TODO remove

View File

@@ -908,6 +908,8 @@ const object_literal =
const function_expr =
if_ok(
seq([
optional(literal('async')),
either(
// arguments inside braces
list_destructuring(['(', ')'], 'function_args'),
@@ -934,7 +936,7 @@ const function_expr =
]),
({value, ...node}) => {
const [args, _, body] = value
const [is_async, args, _, body] = value
const function_args = args.type == 'identifier'
? {
...args,
@@ -950,6 +952,7 @@ const function_expr =
return {
...node,
type: 'function_expr',
is_async: is_async != null,
body,
children: [function_args, body]
}
@@ -1029,6 +1032,7 @@ const expr =
unary('!'),
unary('-'),
unary('typeof'),
unary('await'),
binary(['**']),
binary(['*','/','%']),
binary(['+','-']),

40
src/patch_promise.js Normal file
View File

@@ -0,0 +1,40 @@
export const patch_promise = window => {
// TODO check that it is not already patched
if(window.Promise.Original != null) {
throw new Error('already patched')
}
class PromiseWithStatus extends Promise {
constructor(fn) {
let status
let is_constructor_finished = false
super(
(resolve, reject) => {
fn(
(value) => {
status = {ok: true, value}
if(is_constructor_finished) {
this.status = status
}
resolve(value)
},
(error) => {
status = {ok: false, error}
if(is_constructor_finished) {
this.status = status
}
reject(error)
},
)
}
)
is_constructor_finished = true
this.status = status
}
}
PromiseWithStatus.Original = Promise
window.Promise = PromiseWithStatus
}

59
src/unwrap_promises.js Normal file
View File

@@ -0,0 +1,59 @@
export const unwrap_settled_promises = calltree => {
let is_finished = false
const unwrap = call => {
// TODO use run_window.Promise
if(!call.ok) {
return
}
if(call.value instanceof Promise) {
call.value
.then(value => {
if(is_finished) {
return
}
call.unwrapped_value = {ok: true, value}
})
.catch(error => {
if(is_finished) {
return
}
call.unwrapped_value = {ok: false, error}
})
}
}
const unwrap_tree = call => {
unwrap(call)
if(call.children != null) {
for(let c of call.children) {
unwrap(c)
}
}
}
unwrap_tree(calltree)
return Promise.resolve().then(() => {
is_finished = true
return calltree
})
}
/*
const delay = new Promise(resolve => setTimeout(() => resolve('123'), 1000))
const tree = {
value: Promise.resolve('resolved'),
ok: true,
children: [
{value: delay, ok: true}
]
}
await unwrap_settled_promises(tree)
console.log('tree', tree)
*/

View File

@@ -2588,4 +2588,16 @@ const y = x()`
// must be discarded
assert_equal(get_deferred_calls(result), null)
}),
test_only('async/await', () => {
const code = `
const x = async () => 123
const y = async () => await x()
y()
`
const s = test_initial_state(code)
const move = COMMANDS.move_cursor(s, code.indexOf('await x()')).state
log('m', root_calltree_node(move).children[0].children[0].value)
//log(s.parse_result.modules[''])
}),
]

View File

@@ -2,6 +2,11 @@ import {parse, print_debug_node, load_modules} from '../src/parse_js.js'
import {eval_tree, eval_frame} from '../src/eval.js'
import {COMMANDS} from '../src/cmd.js'
// external
import {patch_promise} from '../src/patch_promise.js'
patch_promise(globalThis)
Object.assign(globalThis, {log: console.log})
export const parse_modules = (entry, modules) =>

63
tt.js Normal file
View File

@@ -0,0 +1,63 @@
const x = async () => 1
const trace = fn => {
try {
const value = fn()
if(value instanceof Promise) {
return value
.then(v => {
value.status = {ok: true, value: v}
return v
})
.catch(e => {
value.status = {ok: false, error: e}
throw e
})
} else {
return value
}
} catch(e) {
} finally {
}
}
//trace(x)
//const c = () => {
// return Promise.reject(1)
//}
//
//const b = () => {
// return c()
// .then(value => {
// console.log('v', value)
// return value
// })
// .catch(e => {
// console.log('e')
// throw e
// })
//}
//
//
//try {
// console.log(b()/*.catch(x => 1)*/)
//} catch(e) {
// console.log('error', e)
//}
const throws_p = () => {throw Promise.reject('err')}
try {
await throws_p()
} catch(e) {
console.log('e', e)
try {
await e
} catch(e2) {
console.log('e2', e2)
}
}

29
x.js Normal file
View File

@@ -0,0 +1,29 @@
/*
const delay = new Promise(resolve => {
setTimeout(resolve, 1000)
})
await delay
console.log('x')
export const x = 1
*/
const p = {then: y => y(3)}
async function test() {
return await p
}
// TODO remove
//const x = new Promise((resolve, reject) => resolve(10))
//const x = Promise.reject(10)
//const x = Promise.resolve(10)
//console.log('x', x.status)
//x.catch(e => {
// console.log('x', x.status)
//})
//const x = new Promise((resolve, reject) => setTimeout(() => resolve(10), 1000))
//console.log('x', x.status)
//x.then(() => {
// console.log('x', x.status)
//})
console.log(await test())