async calls WIP

This commit is contained in:
Dmitry Vasilev
2022-10-26 13:11:51 +08:00
parent a78ba0fa78
commit c3365fe1ee
8 changed files with 112 additions and 19 deletions

View File

@@ -113,7 +113,9 @@ import BigNumber from './path/to/bignumber.mjs';
![External import](docs/images/external_import.png)
Currently every external is loaded once and cached until Leporello is restarted (TODO what happens if we load modules in iframe and then recreate iframe)
Currently every external is loaded once and cached until Leporello is restarted
(TODO what happens if we load modules in iframe and then recreate iframe)
(TODO serve modules from service worker, change host every time)
## Hotkeys
@@ -123,6 +125,10 @@ See built-in Help
Editing local files is possible via [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API). Click "Allow access to local project folder" to grant access to local directory.
## Run and debug UI code in separate window
TODO
## Run Leporello locally
To run it locally, you need to clone repo to local folder and serve it via HTTPS protocol (HTTPS is required by [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API)). See [How to use HTTPS for local development](https://web.dev/how-to-use-local-https/)

View File

@@ -0,0 +1,23 @@
/* external */
import {h, render} from 'https://unpkg.com/preact?module';
/* external */
import {Stateful} from './stateful.js'
const Counter = Stateful({
getInitialState: () => ({counter: 0}),
handlers: {
inc: ({counter}) => ({counter: counter + 1}),
dec: ({counter}) => ({counter: counter - 1}),
},
render: (props, state, handlers) =>
h('div', null,
h('span', null, state.counter),
h('button', {onClick: handlers.inc}, 'Increment'),
h('button', {onClick: handlers.dec}, 'Decrement'),
)
})
render(h(Counter), globalThis.document.body)

View File

@@ -0,0 +1,31 @@
import {Component} from 'https://unpkg.com/preact?module';
export const Stateful = ({getInitialState, handlers, render}) => {
return class extends Component {
constructor() {
super()
this.compState = getInitialState()
this.handlers = Object.fromEntries(
Object
.entries(handlers)
.map(([name, h]) =>
[name, this.makeHandler(h)]
)
)
}
makeHandler(h) {
return (...args) => {
this.compState = h(this.compState, ...args)
this.forceUpdate()
}
}
render() {
return render(this.props, this.compState, this.handlers)
}
}
}

View File

@@ -447,10 +447,15 @@ nodes that are in the second tree that are not in the first tree
*/
const merge_calltrees = (prev, next) => {
return Object.fromEntries(
Object.entries(prev).map(([module, {exports, calls}]) =>
Object.entries(prev).map(([module, {is_external, exports, calls}]) =>
[
module,
{exports, calls: merge_calltree_nodes(calls, next[module].calls)[1]}
is_external
? {is_external, exports}
: {
exports,
calls: merge_calltree_nodes(calls, next[module].calls)[1]
}
]
)
)

View File

@@ -71,6 +71,7 @@ const run_code = (s, index, dirty_files) => {
...s,
parse_result,
calltree: null,
async_calls: null,
// Shows that calltree is brand new and requires entire rerender
calltree_changed_token: {},
@@ -82,7 +83,6 @@ const run_code = (s, index, dirty_files) => {
calltree_node_is_expanded: null,
frames: null,
calltree_node_by_loc: null,
// TODO keep selection_state?
selection_state: null,
loading_external_imports_state: null,
}
@@ -734,8 +734,10 @@ const move_cursor = (s, index) => {
return do_move_cursor(state, index)
}
const on_async_call = (state, ...args) => {
console.log('on_async_call', state, args)
const on_async_call = (state, call) => {
return {...state,
async_calls: [...(state.async_calls ?? []), call]
}
}
const load_dir = (state, dir) => {

View File

@@ -391,7 +391,12 @@ export const eval_modules = (
is_toplevel_call = is_toplevel_call_copy
if(is_recording_async_calls && is_toplevel_call) {
on_async_call(children)
if(children.length != 1) {
throw new Error('illegal state')
}
const call = children[0]
children = null
on_async_call(call)
}
}
}
@@ -542,13 +547,7 @@ export const eval_modules = (
,
/* on_async_call */
calls => {
on_async_call(
calls.map(c =>
assign_code(modules, c)
)
)
},
call => on_async_call(assign_code(modules, call))
)
const calltree_actions = {

View File

@@ -10,6 +10,17 @@ const EXAMPLE = `const fib = n =>
: fib(n - 1) + fib(n - 2)
fib(6)`
// By default run code in hidden iframe, until user explicitly opens visible
// window
globalThis.run_window = (() => {
const iframe = document.createElement('iframe')
iframe.src = 'about:blank'
iframe.setAttribute('hidden', '')
document.body.appendChild(iframe)
return iframe.contentWindow
})()
export const open_run_window = () => {
if(globalThis.run_window != null) {
globalThis.run_window.close()

View File

@@ -2306,11 +2306,27 @@ const y = x()`
// Use Function constructor to exec impure code for testing
new Function('fn', 'globalThis.__run_async_call = fn')(fn)
`
const i = test_initial_state(code, {
on_async_call: (calls) => {console.log('test on async call', calls)}
})
globalThis.__run_async_call()
//delete globalThis.__run_async_call
const {get_async_call, on_async_call} = (new Function(`
let call
return {
get_async_call() {
return call
},
on_async_call(_call) {
call = _call
}
}
`))()
const i = test_initial_state(code, { on_async_call })
globalThis.__run_async_call(10)
const call = get_async_call()
assert_equal(call.fn.name, 'fn')
assert_equal(call.code.index, code.indexOf('() => {'))
assert_equal(call.args, [10])
const state = COMMANDS.on_async_call(i, call)
assert_equal(state.async_calls, [call])
}),
]