mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
not iterable error
This commit is contained in:
@@ -145,12 +145,21 @@ export const find_node = (node, pred) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO refactor, have explicit information if node is error origin, without
|
||||||
|
// guessing. See also color.js
|
||||||
|
// TODO check if return result is null and throw early
|
||||||
export const find_error_origin_node = node =>
|
export const find_error_origin_node = node =>
|
||||||
find_node(
|
find_node(
|
||||||
|
// TODO do not go inside function_expr
|
||||||
node,
|
node,
|
||||||
n => n.result != null && !n.result.ok && (
|
n => n.result != null && !n.result.ok && (
|
||||||
n.result.error != null
|
n.result.error != null
|
||||||
||
|
||
|
||||||
|
// node has no error, but its children also have no error, so this node
|
||||||
|
// is error origin
|
||||||
|
n.children.find(c => find_error_origin_node(c) != null) == null
|
||||||
|
&&
|
||||||
|
(
|
||||||
// In case if throw null or throw undefined
|
// In case if throw null or throw undefined
|
||||||
n.type == 'throw'
|
n.type == 'throw'
|
||||||
||
|
||
|
||||||
@@ -161,6 +170,7 @@ export const find_error_origin_node = node =>
|
|||||||
n.type == 'function_call'
|
n.type == 'function_call'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
|
||||||
/* Maps tree nodes, discarding mapped children, so maps only node contents, not
|
/* Maps tree nodes, discarding mapped children, so maps only node contents, not
|
||||||
* allowing to modify structure */
|
* allowing to modify structure */
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ const node_to_color = node => ({
|
|||||||
? null
|
? null
|
||||||
: node.result.ok
|
: node.result.ok
|
||||||
? {ok: true}
|
? {ok: true}
|
||||||
|
// node.result.error may be null, for example if throw null
|
||||||
|
// See find_error_origin_node
|
||||||
: node.result.error == null
|
: node.result.error == null
|
||||||
? {ok: false, error_origin: false}
|
? {ok: false, error_origin: false}
|
||||||
: {ok: false, error_origin: true}
|
: {ok: false, error_origin: true}
|
||||||
|
|||||||
27
src/eval.js
27
src/eval.js
@@ -171,7 +171,7 @@ const codegen = (node, node_cxt, parent) => {
|
|||||||
} else if(node.type == 'object_literal'){
|
} else if(node.type == 'object_literal'){
|
||||||
const elements =
|
const elements =
|
||||||
node.elements.map(el => {
|
node.elements.map(el => {
|
||||||
if(el.type == 'spread'){
|
if(el.type == 'object_spread'){
|
||||||
return do_codegen(el)
|
return do_codegen(el)
|
||||||
} else if(el.type == 'identifier') {
|
} else if(el.type == 'identifier') {
|
||||||
return el.value
|
return el.value
|
||||||
@@ -241,7 +241,7 @@ const codegen = (node, node_cxt, parent) => {
|
|||||||
+ node.operator
|
+ node.operator
|
||||||
+ ' '
|
+ ' '
|
||||||
+ do_codegen(node.args[1])
|
+ do_codegen(node.args[1])
|
||||||
} else if(node.type == 'spread'){
|
} else if(node.type == 'array_spread' || node.type == 'object_spread'){
|
||||||
return '...(' + do_codegen(node.expr) + ')'
|
return '...(' + do_codegen(node.expr) + ')'
|
||||||
} else if(node.type == 'new') {
|
} else if(node.type == 'new') {
|
||||||
const args = `[${node.args.children.map(do_codegen).join(',')}]`
|
const args = `[${node.args.children.map(do_codegen).join(',')}]`
|
||||||
@@ -510,8 +510,24 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
|
|||||||
// TODO exprs inside backtick string
|
// TODO exprs inside backtick string
|
||||||
// Pass scope for backtick string
|
// Pass scope for backtick string
|
||||||
return {...eval_codestring(node.value, scope), calls: callsleft}
|
return {...eval_codestring(node.value, scope), calls: callsleft}
|
||||||
|
} else if(node.type == 'array_spread') {
|
||||||
|
const result = eval_children(node, scope, callsleft, context)
|
||||||
|
if(!result.ok) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
const child = result.children[0]
|
||||||
|
if((typeof(child.result.value?.[Symbol.iterator])) == 'function') {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
children: result.children,
|
||||||
|
calls: result.calls,
|
||||||
|
error: new TypeError(child.string + ' is not iterable'),
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if([
|
} else if([
|
||||||
'spread',
|
'object_spread',
|
||||||
'key_value_pair',
|
'key_value_pair',
|
||||||
'computed_property'
|
'computed_property'
|
||||||
].includes(node.type)) {
|
].includes(node.type)) {
|
||||||
@@ -523,8 +539,7 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
|
|||||||
}
|
}
|
||||||
const value = children.reduce(
|
const value = children.reduce(
|
||||||
(arr, el) => {
|
(arr, el) => {
|
||||||
if(el.type == 'spread') {
|
if(el.type == 'array_spread') {
|
||||||
// TODO check if iterable and throw error
|
|
||||||
return [...arr, ...el.children[0].result.value]
|
return [...arr, ...el.children[0].result.value]
|
||||||
} else {
|
} else {
|
||||||
return [...arr, el.result.value]
|
return [...arr, el.result.value]
|
||||||
@@ -540,7 +555,7 @@ const do_eval_frame_expr = (node, scope, callsleft, context) => {
|
|||||||
}
|
}
|
||||||
const value = children.reduce(
|
const value = children.reduce(
|
||||||
(value, el) => {
|
(value, el) => {
|
||||||
if(el.type == 'spread'){
|
if(el.type == 'object_spread'){
|
||||||
return {...value, ...el.children[0].result.value}
|
return {...value, ...el.children[0].result.value}
|
||||||
} else if(el.type == 'identifier') {
|
} else if(el.type == 'identifier') {
|
||||||
// TODO check that it works
|
// TODO check that it works
|
||||||
|
|||||||
@@ -828,7 +828,12 @@ const array_element = either(
|
|||||||
literal('...'),
|
literal('...'),
|
||||||
cxt => expr(cxt),
|
cxt => expr(cxt),
|
||||||
]),
|
]),
|
||||||
({value, ...node}) => ({...node, type: 'spread', not_evaluatable: true, children: [value]})
|
({value, ...node}) => ({
|
||||||
|
...node,
|
||||||
|
type: 'array_spread',
|
||||||
|
not_evaluatable: true,
|
||||||
|
children: [value]
|
||||||
|
})
|
||||||
),
|
),
|
||||||
cxt => expr(cxt),
|
cxt => expr(cxt),
|
||||||
)
|
)
|
||||||
@@ -857,7 +862,12 @@ const object_literal =
|
|||||||
literal('...'),
|
literal('...'),
|
||||||
cxt => expr(cxt),
|
cxt => expr(cxt),
|
||||||
]),
|
]),
|
||||||
({value, ...node}) => ({...node, type: 'spread', children: [value], not_evaluatable: true})
|
({value, ...node}) => ({
|
||||||
|
...node,
|
||||||
|
type: 'object_spread',
|
||||||
|
children: [value],
|
||||||
|
not_evaluatable: true
|
||||||
|
})
|
||||||
),
|
),
|
||||||
|
|
||||||
// Or key-value pair
|
// Or key-value pair
|
||||||
@@ -1456,7 +1466,7 @@ const update_children_not_rec = (node, children = node.children) => {
|
|||||||
}
|
}
|
||||||
} else if(node.type == 'call_args') {
|
} else if(node.type == 'call_args') {
|
||||||
return node
|
return node
|
||||||
} else if(node.type == 'spread') {
|
} else if(node.type == 'array_spread' || node.type == 'object_spread') {
|
||||||
return {...node,
|
return {...node,
|
||||||
expr: children[0],
|
expr: children[0],
|
||||||
}
|
}
|
||||||
|
|||||||
31
test/test.js
31
test/test.js
@@ -837,6 +837,23 @@ export const tests = [
|
|||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
test('array spread not iterable', () => {
|
||||||
|
assert_code_error(
|
||||||
|
`[...null]`,
|
||||||
|
new Error('null is not iterable'),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
|
test('args spread not iterable', () => {
|
||||||
|
assert_code_error(
|
||||||
|
`
|
||||||
|
function x() {}
|
||||||
|
x(...null)
|
||||||
|
`,
|
||||||
|
new Error('null is not iterable'),
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
test('module not found', () => {
|
test('module not found', () => {
|
||||||
const parsed = parse_modules(
|
const parsed = parse_modules(
|
||||||
'a',
|
'a',
|
||||||
@@ -2336,6 +2353,20 @@ const y = x()`
|
|||||||
assert_equal(s2.value_explorer.result.error.message, 'boom')
|
assert_equal(s2.value_explorer.result.error.message, 'boom')
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
test('move_cursor error in fn args bug', () => {
|
||||||
|
const code = `
|
||||||
|
function x() {}
|
||||||
|
x(null.foo)
|
||||||
|
`
|
||||||
|
const i = test_initial_state(code)
|
||||||
|
|
||||||
|
const m = COMMANDS.move_cursor(i, code.indexOf('x(null'))
|
||||||
|
assert_equal(
|
||||||
|
m.value_explorer.result.error,
|
||||||
|
new Error("Cannot read properties of null (reading 'foo')")
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
|
||||||
test('frame follows cursor toplevel', () => {
|
test('frame follows cursor toplevel', () => {
|
||||||
const code = `
|
const code = `
|
||||||
const x = () => {
|
const x = () => {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import {find_error_origin_node} from '../src/ast_utils.js'
|
||||||
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_modules} from '../src/eval.js'
|
import {eval_modules} from '../src/eval.js'
|
||||||
import {active_frame, pp_calltree} from '../src/calltree.js'
|
import {active_frame, pp_calltree} from '../src/calltree.js'
|
||||||
@@ -81,9 +82,8 @@ export const assert_code_evals_to = (codestring, expected) => {
|
|||||||
export const assert_code_error = (codestring, error) => {
|
export const assert_code_error = (codestring, error) => {
|
||||||
const state = test_initial_state(codestring)
|
const state = test_initial_state(codestring)
|
||||||
const frame = active_frame(state)
|
const frame = active_frame(state)
|
||||||
const result = frame.children.at(-1).result
|
assert_equal(frame.result.ok, false)
|
||||||
assert_equal(result.ok, false)
|
assert_equal(find_error_origin_node(frame).result.error, error)
|
||||||
assert_equal(result.error, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const assert_code_evals_to_async = async (codestring, expected) => {
|
export const assert_code_evals_to_async = async (codestring, expected) => {
|
||||||
@@ -168,9 +168,10 @@ export const test_deferred_calls_state = code => {
|
|||||||
|
|
||||||
export const stringify = val =>
|
export const stringify = val =>
|
||||||
JSON.stringify(val, (key, value) => {
|
JSON.stringify(val, (key, value) => {
|
||||||
// TODO do not use instanceof because currently not implemented in parser
|
if(value instanceof Set){
|
||||||
if(value?.constructor == Set){
|
|
||||||
return [...value]
|
return [...value]
|
||||||
|
} else if(value instanceof Error) {
|
||||||
|
return {message: value.message}
|
||||||
} else {
|
} else {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user