mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 21:14:28 -08:00
WIP
This commit is contained in:
@@ -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
5
src/effects.js
vendored
@@ -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
|
||||
||
|
||||
|
||||
18
src/eval.js
18
src/eval.js
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
40
src/patch_promise.js
Normal 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
59
src/unwrap_promises.js
Normal 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)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user