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'
|
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
5
src/effects.js
vendored
@@ -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
|
||||||
||
|
||
|
||||||
|
|||||||
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 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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
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
|
// 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[''])
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
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