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' 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 => const has_custom_toString = object =>
object.toString != globalThis.run_window.Object.prototype.toString object.toString != globalThis.run_window.Object.prototype.toString
&& &&
@@ -16,6 +20,11 @@ const isError = object =>
|| ||
object instanceof globalThis.run_window.Error object instanceof globalThis.run_window.Error
const isPromise = object =>
object instanceof Promise
||
object instanceof globalThis.run_window.Promise
const displayed_entries = object => { const displayed_entries = object => {
if(Array.isArray(object)) { if(Array.isArray(object)) {
return object.map((v, i) => [i, v]) return object.map((v, i) => [i, v])
@@ -39,6 +48,8 @@ export const stringify_for_header = v => {
} else if(type == 'function') { } else if(type == 'function') {
// TODO clickable link, 'fn', cursive // TODO clickable link, 'fn', cursive
return 'fn ' + v.name return 'fn ' + v.name
} else if (isPromise(v)) {
return 'Promise<>'
} else if(isError(v)) { } else if(isError(v)) {
return v.toString() return v.toString()
} else if(type == 'object') { } else if(type == 'object') {
@@ -70,7 +81,9 @@ export const header = object => {
} else if(object == null) { } else if(object == null) {
return 'null' return 'null'
} else if(typeof(object) == 'object') { } else if(typeof(object) == 'object') {
if(isError(object)) { if(isPromise(object)) {
return 'Promise<>'
} else if(isError(object)) {
return object.toString() return object.toString()
} else if(Array.isArray(object)) { } else if(Array.isArray(object)) {
return '[' return '['

5
src/effects.js vendored
View File

@@ -8,6 +8,7 @@ import {
import {current_cursor_position} from './calltree.js' import {current_cursor_position} from './calltree.js'
import {FLAGS} from './feature_flags.js' import {FLAGS} from './feature_flags.js'
import {exec, FILES_ROOT} from './index.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 // Imports in the context of `run_window`, so global variables in loaded
// modules refer to that window's context // 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) 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( if(
prev.project_dir != next.project_dir prev.project_dir != next.project_dir
|| ||
@@ -187,6 +188,8 @@ export const render_common_side_effects = (prev, next, command, ui) => {
} else { } else {
await unwrap_settled_promises(next.calltree)
if( if(
prev.calltree == null 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 args = node.function_args.children.map(do_codegen).join(',')
const call = `(${args}) => ` + ( const call = (node.is_async ? 'async ' : '') + `(${args}) => ` + (
(node.body.type == 'do') (node.body.type == 'do')
? '{' + do_codegen(node.body) + '}' ? '{' + do_codegen(node.body) + '}'
: '(' + do_codegen(node.body) + ')' : '(' + do_codegen(node.body) + ')'
@@ -384,7 +384,17 @@ export const eval_modules = (
try { try {
value = fn(...args) value = fn(...args)
ok = true 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) { } catch(_error) {
ok = false ok = false
error = _error error = _error
@@ -917,6 +927,10 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
value = typeof(expr.result.value) value = typeof(expr.result.value)
} else if(node.operator == '-') { } else if(node.operator == '-') {
value = - expr.result.value 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 { } else {
throw new Error('unknown op') throw new Error('unknown op')
} }

View File

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

View File

@@ -908,6 +908,8 @@ const object_literal =
const function_expr = const function_expr =
if_ok( if_ok(
seq([ seq([
optional(literal('async')),
either( either(
// arguments inside braces // arguments inside braces
list_destructuring(['(', ')'], 'function_args'), list_destructuring(['(', ')'], 'function_args'),
@@ -934,7 +936,7 @@ const function_expr =
]), ]),
({value, ...node}) => { ({value, ...node}) => {
const [args, _, body] = value const [is_async, args, _, body] = value
const function_args = args.type == 'identifier' const function_args = args.type == 'identifier'
? { ? {
...args, ...args,
@@ -950,6 +952,7 @@ const function_expr =
return { return {
...node, ...node,
type: 'function_expr', type: 'function_expr',
is_async: is_async != null,
body, body,
children: [function_args, body] children: [function_args, body]
} }
@@ -1029,6 +1032,7 @@ const expr =
unary('!'), unary('!'),
unary('-'), unary('-'),
unary('typeof'), unary('typeof'),
unary('await'),
binary(['**']), binary(['**']),
binary(['*','/','%']), 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 // must be discarded
assert_equal(get_deferred_calls(result), null) 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 {eval_tree, eval_frame} from '../src/eval.js'
import {COMMANDS} from '../src/cmd.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}) Object.assign(globalThis, {log: console.log})
export const parse_modules = (entry, modules) => 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())