mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
WIP
This commit is contained in:
24
docs/examples/baconjs/index.js
Normal file
24
docs/examples/baconjs/index.js
Normal 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}));
|
||||
@@ -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)
|
||||
*/
|
||||
|
||||
12
test/test.js
12
test/test.js
@@ -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[''])
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -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
63
tt.js
Normal 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
29
x.js
Normal 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())
|
||||
Reference in New Issue
Block a user