This commit is contained in:
Dmitry Vasilev
2022-12-07 05:06:15 +08:00
parent 9e9400ab24
commit 707c34bc66
8 changed files with 178 additions and 49 deletions

View File

@@ -220,15 +220,6 @@ const eval_modules_finished = (state, result, node, toplevel) => {
if(toplevel) { if(toplevel) {
if(node == state.parse_result.modules[root_calltree_module(next)]) { if(node == state.parse_result.modules[root_calltree_module(next)]) {
active_calltree_node = root_calltree_node(next) active_calltree_node = root_calltree_node(next)
return add_frame(
default_expand_path(
expand_path(
next,
active_calltree_node
)
),
active_calltree_node,
)
} else { } else {
active_calltree_node = null active_calltree_node = null
} }
@@ -244,11 +235,13 @@ const eval_modules_finished = (state, result, node, toplevel) => {
} }
} }
let result_state
if(active_calltree_node == null) { if(active_calltree_node == null) {
const {node, state: next2} = initial_calltree_node(next) const {node, state: next2} = initial_calltree_node(next)
return set_active_calltree_node(next2, null, node) result_state = set_active_calltree_node(next2, null, node)
} else { } else {
return add_frame( result_state = add_frame(
default_expand_path( default_expand_path(
expand_path( expand_path(
next, next,
@@ -258,6 +251,10 @@ const eval_modules_finished = (state, result, node, toplevel) => {
active_calltree_node, active_calltree_node,
) )
} }
return result_state.eval_modules_state == null
? result_state
: {...result_state, eval_modules_state: null}
} }
const input = (state, code, index) => { const input = (state, code, index) => {

1
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'
// TODO remove
import {unwrap_settled_promises} from './unwrap_promises.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

View File

@@ -14,6 +14,9 @@ import {
import {has_toplevel_await} from './find_definitions.js' import {has_toplevel_await} from './find_definitions.js'
// external
import {patch_promise} from './patch_promise.js'
// TODO: fix error messages. For example, "__fn is not a function" // TODO: fix error messages. For example, "__fn is not a function"
/* /*
@@ -278,6 +281,13 @@ export const eval_modules = (
// TODO bug if module imported twice, once as external and as regular // TODO bug if module imported twice, once as external and as regular
patch_promise(
globalThis.run_window
??
// Code executed in test env
globalThis
)
const is_async = has_toplevel_await(parse_result.modules) const is_async = has_toplevel_await(parse_result.modules)
const codestring = const codestring =
@@ -937,21 +947,35 @@ const do_eval_frame_expr = (node, scope, callsleft) => {
return {ok: false, children, calls} return {ok: false, children, calls}
} else { } else {
const expr = children[0] const expr = children[0]
let value let ok, value, error
if(node.operator == '!') { if(node.operator == '!') {
ok = true
value = !expr.result.value value = !expr.result.value
} else if(node.operator == 'typeof') { } else if(node.operator == 'typeof') {
ok = true
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') { } else if(node.operator == 'await') {
log('expr', expr.result.value.status) const run_window = globalThis.run_window ?? globalThis
value = expr.result.value if(expr.result.value instanceof run_window.Promise.Original) {
//throw new Error('not implemented') const status = expr.result.value.status
if(status == null) {
// Promise must be already resolved
throw new Error('illegal state')
} else {
ok = status.ok
error = status.error
value = status.value
}
} else {
ok = true
value = expr.result.value
}
} else { } else {
throw new Error('unknown op') throw new Error('unknown op')
} }
return {ok: true, children, calls, value} return {ok, children, calls, value, error}
} }
} else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){ } else if(node.type == 'binary' && !['&&', '||', '??'].includes(node.operator)){

View File

@@ -1,6 +1,6 @@
export const globals = new Set([ export const globals = new Set([
'globalThis', 'globalThis',
// TODO Promise, 'Promise',
// TODO Symbol // TODO Symbol
'URL', 'URL',
'Set', 'Set',

View File

@@ -1,8 +1,8 @@
export const patch_promise = window => { export const patch_promise = window => {
// TODO check that it is not already patched
if(window.Promise.Original != null) { if(window.Promise.Original != null) {
throw new Error('already patched') // already patched
return
} }
class PromiseWithStatus extends Promise { class PromiseWithStatus extends Promise {

View File

@@ -15,10 +15,10 @@ import {
test_only, test_only,
assert_equal, assert_equal,
stringify, stringify,
assert_code_evals_to, assert_code_evals_to, assert_code_evals_to_async,
assert_code_error, assert_code_error, assert_code_error_async,
parse_modules, parse_modules,
test_initial_state, test_initial_state, test_initial_state_async,
test_deferred_calls_state, test_deferred_calls_state,
print_debug_ct_node, print_debug_ct_node,
} from './utils.js' } from './utils.js'
@@ -2589,15 +2589,80 @@ const y = x()`
assert_equal(get_deferred_calls(result), null) assert_equal(get_deferred_calls(result), null)
}), }),
test('async/await', () => { test('async/await return from async function', async () => {
const code = ` await assert_code_evals_to_async(
const x = async () => 123 `
const y = async () => await x() const x = async () => 123
await y() const y = async () => await x()
` await y()
const s = test_initial_state(code) `,
const move = COMMANDS.move_cursor(s, code.indexOf('await x()')).state 123
log('m', root_calltree_node(move).children[0].children[0].value) )
//log(s.parse_result.modules[''])
}), }),
test('async/await await Promise', async () => {
await assert_code_evals_to_async(
`
await Promise.resolve(123)
`,
123
)
}),
test('async/await await Promise returned from async function', async () => {
await assert_code_evals_to_async(
`
const x = async () => {
return Promise.resolve(123)
}
await x()
`,
123
)
}),
test('async/await throw from async function', async () => {
await assert_code_error_async(
`
const x = async () => { throw 'boom' }
await x()
`,
'boom'
)
}),
test('async/await await rejected Promise', async () => {
await assert_code_error_async(
`
await Promise.reject('boom')
`,
'boom'
)
}),
test('async/await await rejected Promise', async () => {
await assert_code_error_async(
`
await Promise.reject('boom')
`,
'boom'
)
}),
test('async/await await rejected Promise returned from async', async () => {
await assert_code_error_async(
`
const x = async () => Promise.reject('boom')
await x()
`,
'boom'
)
}),
// TODO
//assert_equal('s', active_frame
//const result = await s.eval_modules_state
//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

@@ -1,12 +1,8 @@
import {parse, print_debug_node, load_modules} from '../src/parse_js.js' 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 {active_frame} from '../src/calltree.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) =>
@@ -32,6 +28,22 @@ export const assert_code_error = (codestring, error) => {
assert_equal(result.error, error) assert_equal(result.error, error)
} }
export const assert_code_evals_to_async = async (codestring, expected) => {
const s = await test_initial_state_async(codestring)
const frame = active_frame(s)
const result = frame.children[frame.children.length - 1].result
assert_equal(result.ok, true)
assert_equal(result.value, expected)
}
export const assert_code_error_async = async (codestring, error) => {
const s = await test_initial_state_async(codestring)
const frame = active_frame(s)
const result = frame.children[frame.children.length - 1].result
assert_equal(result.ok, false)
assert_equal(result.error, error)
}
export const test_initial_state = (code, state) => { export const test_initial_state = (code, state) => {
return COMMANDS.open_run_window( return COMMANDS.open_run_window(
COMMANDS.get_initial_state( COMMANDS.get_initial_state(
@@ -45,6 +57,18 @@ export const test_initial_state = (code, state) => {
) )
} }
export const test_initial_state_async = async code => {
const s = test_initial_state(code)
assert_equal(s.eval_modules_state != null, true)
const result = await s.eval_modules_state.promise
return COMMANDS.eval_modules_finished(
s,
result,
s.eval_modules_state.node,
s.eval_modules_state.toplevel
)
}
export const test_deferred_calls_state = code => { export const test_deferred_calls_state = code => {
const {get_deferred_call, on_deferred_call} = (new Function(` const {get_deferred_call, on_deferred_call} = (new Function(`
let call, calltree_changed_token let call, calltree_changed_token
@@ -119,10 +143,13 @@ export const test_only = (message, t) => test(message, t, true)
// Wrap to Function constructor to hide from calltree view // Wrap to Function constructor to hide from calltree view
// TODO in calltree view, hide fn which has special flag set (see // TODO in calltree view, hide fn which has special flag set (see
// filter_calltree) // filter_calltree)
export const run = Object.defineProperty(new Function('tests', `
const run_test = t => { const AsyncFunction = new Function(`return (async () => {}).constructor`)()
export const run = Object.defineProperty(new AsyncFunction('tests', `
const run_test = async t => {
try { try {
t.test() await t.test()
} catch(e) { } catch(e) {
if(globalThis.process != null) { if(globalThis.process != null) {
// In node.js runner, fail fast // In node.js runner, fail fast
@@ -142,12 +169,13 @@ export const run = Object.defineProperty(new Function('tests', `
const only = tests.find(t => t.only) const only = tests.find(t => t.only)
const tests_to_run = only == null ? tests : [only] const tests_to_run = only == null ? tests : [only]
// Exec each test. After all tests are done, we rethrow first failer if // Exec each test. After all tests are done, we rethrow first error if
// any. So we will mark root calltree node if one of tests failed // any. So we will mark root calltree node if one of tests failed
const failure = tests_to_run.reduce( const failure = await tests_to_run.reduce(
(failure, t) => { async (failureP, t) => {
const next_failure = run_test(t) const failure = await failureP
return failure ?? next_failure const next_failure = await run_test(t)
return (await failure) ?? next_failure
}, },
null null
) )
@@ -165,7 +193,7 @@ export const run = Object.defineProperty(new Function('tests', `
if(test == null) { if(test == null) {
throw new Error('test not found') throw new Error('test not found')
} else { } else {
run_test(test) await run_test(test)
if(globalThis.process != null) { if(globalThis.process != null) {
console.log('Ok') console.log('Ok')
} }

20
x.js
View File

@@ -1,17 +1,31 @@
/* const delay = () => new Promise(resolve => {
const delay = new Promise(resolve => {
setTimeout(resolve, 1000) setTimeout(resolve, 1000)
}) })
await [1,2,3,4,5].reduce(
async (acc, x) => {
console.log('wait')
await acc
await delay()
console.log('wait finish')
},
Promise.resolve(),
)
/*
await delay await delay
console.log('x') console.log('x')
export const x = 1 export const x = 1
*/ */
/*
const p = {then: y => y(3)} const p = {then: y => y(3)}
async function test() { async function test() {
return await p return await p
} }
*/
// TODO remove // TODO remove
//const x = new Promise((resolve, reject) => resolve(10)) //const x = new Promise((resolve, reject) => resolve(10))
@@ -26,4 +40,4 @@ async function test() {
//x.then(() => { //x.then(() => {
// console.log('x', x.status) // console.log('x', x.status)
//}) //})
console.log(await test()) //console.log(await test())