From c006aae380d2d0c16f19f8a84bd72909aa1de449 Mon Sep 17 00:00:00 2001 From: Dmitry Vasilev Date: Mon, 31 Jul 2023 23:17:48 +0300 Subject: [PATCH] refactor value explorer --- src/editor/calltree.js | 2 +- src/editor/editor.js | 3 +- src/editor/io_trace.js | 2 +- src/editor/logs.js | 2 +- src/editor/value_explorer.js | 200 +---------------------------------- src/value_explorer_utils.js | 199 ++++++++++++++++++++++++++++++++++ 6 files changed, 205 insertions(+), 203 deletions(-) create mode 100644 src/value_explorer_utils.js diff --git a/src/editor/calltree.js b/src/editor/calltree.js index 0e0505d..9224250 100644 --- a/src/editor/calltree.js +++ b/src/editor/calltree.js @@ -1,6 +1,6 @@ import {exec} from '../index.js' import {el, stringify, fn_link, scrollIntoViewIfNeeded} from './domutils.js' -import {stringify_for_header} from './value_explorer.js' +import {stringify_for_header} from '../value_explorer_utils.js' import {find_node} from '../ast_utils.js' import {is_expandable, root_calltree_node, get_deferred_calls, has_error} from '../calltree.js' diff --git a/src/editor/editor.js b/src/editor/editor.js index a2a8bd1..78bd403 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -1,5 +1,6 @@ import {exec, get_state} from '../index.js' -import {ValueExplorer, stringify_for_header} from './value_explorer.js' +import {ValueExplorer} from './value_explorer.js' +import {stringify_for_header} from '../value_explorer_utils.js' import {el, stringify, fn_link} from './domutils.js' /* diff --git a/src/editor/io_trace.js b/src/editor/io_trace.js index 01b97ea..67391fb 100644 --- a/src/editor/io_trace.js +++ b/src/editor/io_trace.js @@ -1,4 +1,4 @@ -import {header, stringify_for_header} from './value_explorer.js' +import {header, stringify_for_header} from '../value_explorer_utils.js' import {el} from './domutils.js' import {has_error} from '../calltree.js' diff --git a/src/editor/logs.js b/src/editor/logs.js index bc773ba..924c485 100644 --- a/src/editor/logs.js +++ b/src/editor/logs.js @@ -1,6 +1,6 @@ import {el, scrollIntoViewIfNeeded} from './domutils.js' import {exec} from '../index.js' -import {header} from './value_explorer.js' +import {header} from '../value_explorer_utils.js' export class Logs { constructor(ui, el) { diff --git a/src/editor/value_explorer.js b/src/editor/value_explorer.js index ef64304..6699e68 100644 --- a/src/editor/value_explorer.js +++ b/src/editor/value_explorer.js @@ -6,207 +6,9 @@ import {el, stringify, scrollIntoViewIfNeeded} from './domutils.js' import {with_code_execution} from '../index.js' +import {header, is_expandable} from '../value_explorer_utils.js' -// We test both for Object and globalThis.app_window.Object because objects may -// come both from app_window and current window (where they are created in -// metacircular interpreter -const has_custom_toString = object => - typeof(object.toString) == 'function' - && object.toString != globalThis.app_window.Object.prototype.toString - && object.toString != Object.prototype.toString - -const isError = object => - object instanceof Error - || - object instanceof globalThis.app_window.Error - -const isPromise = object => - object instanceof globalThis.app_window.Promise - -// Override behaviour for Date, becase Date has toJSON defined -const isDate = object => - object instanceof globalThis.app_window.Date - || - object instanceof globalThis.app_window.Date.__original - -const toJSON_safe = object => { - try { - return with_code_execution(() => { - return object.toJSON() - }) - } catch(e) { - return object - } -} - -const displayed_entries = object => { - if(object == null || typeof(object) != 'object') { - return [] - } else if((object[Symbol.toStringTag]) == 'Module') { - return Object.entries(object) - } else if(isPromise(object)) { - return displayed_entries( - object.status.ok ? object.status.value : object.status.error - ) - } else if(Array.isArray(object)) { - return object.map((v, i) => [i, v]) - } else if(typeof(object.toJSON) == 'function') { - const result = toJSON_safe(object) - if(result == object) { - // avoid infinite recursion when toJSON returns itself - return Object.entries(object) - } else { - return displayed_entries(result) - } - } else { - return Object.entries(object) - } -} - -const is_expandable = v => - isPromise(v) - ? ( - v.status != null - && is_expandable(v.status.ok ? v.status.value : v.status.error) - ) - : ( - typeof(v) == 'object' - && v != null - && displayed_entries(v).length != 0 - ) - - -const stringify_for_header_object = v => { - if(displayed_entries(v).length == 0) { - return '{}' - } else { - return '{…}' - } -} - -export const stringify_for_header = (v, no_toJSON = false) => { - const type = typeof(v) - - if(v === null) { - return 'null' - } else if(v === undefined) { - return 'undefined' - } else if(type == 'function') { - // TODO clickable link, 'fn', cursive - return 'fn ' + v.name - } else if(type == 'string') { - return JSON.stringify(v) - } else if(type == 'object') { - if((v[Symbol.toStringTag]) == 'Module') { - // protect against lodash module contains toJSON function - return stringify_for_header_object(v) - } else if (isPromise(v)) { - if(v.status == null) { - return `Promise` - } else { - if(v.status.ok) { - return `Promise` - } else { - return `Promise` - } - } - } else if (isDate(v)) { - return v.toString() - } else if(isError(v)) { - return v.toString() - } else if(Array.isArray(v)) { - if(v.length == 0) { - return '[]' - } else { - return '[…]' - } - } else if(typeof(v.toJSON) == 'function' && !no_toJSON) { - const json = toJSON_safe(v) - if(json == v) { - // prevent infinite recursion - return stringify_for_header(json, true) - } else { - return stringify_for_header(json) - } - } else if(has_custom_toString(v)) { - return v.toString() - } else { - return stringify_for_header_object(v) - } - } else { - return v.toString() - } -} - -const header_object = object => { - const prefix = - (object.constructor?.name == null || object.constructor?.name == 'Object') - ? '' - : object.constructor.name + ' ' - const inner = displayed_entries(object) - .map(([k,v]) => { - const value = stringify_for_header(v) - return `${k}: ${value}` - }) - .join(', ') - return `${prefix} {${inner}}` -} - -export const header = (object, no_toJSON = false) => { - const type = typeof(object) - - if(object === null) { - return 'null' - } else if(object === undefined) { - return 'undefined' - } else if(type == 'function') { - // TODO clickable link, 'fn', cursive - return 'fn ' + object.name - } else if(type == 'string') { - return JSON.stringify(object) - } else if(type == 'object') { - if((object[Symbol.toStringTag]) == 'Module') { - // protect against lodash module contains toJSON function - return header_object(object) - } else if(isPromise(object)) { - if(object.status == null) { - return `Promise` - } else { - if(object.status.ok) { - return `Promise` - } else { - return `Promise` - } - } - } else if(isDate(object)) { - return object.toString() - } else if(isError(object)) { - return object.toString() - } else if(Array.isArray(object)) { - return '[' - + object - .map(stringify_for_header) - .join(', ') - + ']' - } else if(typeof(object.toJSON) == 'function' && !no_toJSON) { - const json = toJSON_safe(object) - if(json == object) { - // prevent infinite recursion - return header(object, true) - } else { - return header(json) - } - } else if(has_custom_toString(object)) { - return object.toString() - } else { - return header_object(object) - } - } else { - return object.toString() - } -} - const get_path = (o, path) => { if(path.length == 0) { return o diff --git a/src/value_explorer_utils.js b/src/value_explorer_utils.js new file mode 100644 index 0000000..a5085cf --- /dev/null +++ b/src/value_explorer_utils.js @@ -0,0 +1,199 @@ +// We test both for Object and globalThis.app_window.Object because objects may +// come both from app_window and current window (where they are created in +// metacircular interpreter +const has_custom_toString = object => + typeof(object.toString) == 'function' + && object.toString != globalThis.app_window.Object.prototype.toString + && object.toString != Object.prototype.toString + +const isError = object => + object instanceof Error + || + object instanceof globalThis.app_window.Error + +const isPromise = object => + object instanceof globalThis.app_window.Promise + +// Override behaviour for Date, becase Date has toJSON defined +const isDate = object => + object instanceof globalThis.app_window.Date + || + object instanceof globalThis.app_window.Date.__original + +const toJSON_safe = object => { + try { + return with_code_execution(() => { + return object.toJSON() + }) + } catch(e) { + return object + } +} + +const displayed_entries = object => { + if(object == null || typeof(object) != 'object') { + return [] + } else if((object[Symbol.toStringTag]) == 'Module') { + return Object.entries(object) + } else if(isPromise(object)) { + return displayed_entries( + object.status.ok ? object.status.value : object.status.error + ) + } else if(Array.isArray(object)) { + return object.map((v, i) => [i, v]) + } else if(typeof(object.toJSON) == 'function') { + const result = toJSON_safe(object) + if(result == object) { + // avoid infinite recursion when toJSON returns itself + return Object.entries(object) + } else { + return displayed_entries(result) + } + } else { + return Object.entries(object) + } +} + +export const is_expandable = v => + isPromise(v) + ? ( + v.status != null + && is_expandable(v.status.ok ? v.status.value : v.status.error) + ) + : ( + typeof(v) == 'object' + && v != null + && displayed_entries(v).length != 0 + ) + + +export const stringify_for_header_object = v => { + if(displayed_entries(v).length == 0) { + return '{}' + } else { + return '{…}' + } +} + +export const stringify_for_header = (v, no_toJSON = false) => { + const type = typeof(v) + + if(v === null) { + return 'null' + } else if(v === undefined) { + return 'undefined' + } else if(type == 'function') { + // TODO clickable link, 'fn', cursive + return 'fn ' + v.name + } else if(type == 'string') { + return JSON.stringify(v) + } else if(type == 'object') { + if((v[Symbol.toStringTag]) == 'Module') { + // protect against lodash module contains toJSON function + return stringify_for_header_object(v) + } else if (isPromise(v)) { + if(v.status == null) { + return `Promise` + } else { + if(v.status.ok) { + return `Promise` + } else { + return `Promise` + } + } + } else if (isDate(v)) { + return v.toString() + } else if(isError(v)) { + return v.toString() + } else if(Array.isArray(v)) { + if(v.length == 0) { + return '[]' + } else { + return '[…]' + } + } else if(typeof(v.toJSON) == 'function' && !no_toJSON) { + const json = toJSON_safe(v) + if(json == v) { + // prevent infinite recursion + return stringify_for_header(json, true) + } else { + return stringify_for_header(json) + } + } else if(has_custom_toString(v)) { + return v.toString() + } else { + return stringify_for_header_object(v) + } + } else { + return v.toString() + } +} + +const header_object = object => { + const prefix = + (object.constructor?.name == null || object.constructor?.name == 'Object') + ? '' + : object.constructor.name + ' ' + const inner = displayed_entries(object) + .map(([k,v]) => { + const value = stringify_for_header(v) + return `${k}: ${value}` + }) + .join(', ') + return `${prefix} {${inner}}` +} + +export const header = (object, no_toJSON = false) => { + const type = typeof(object) + + if(object === null) { + return 'null' + } else if(object === undefined) { + return 'undefined' + } else if(type == 'function') { + // TODO clickable link, 'fn', cursive + return 'fn ' + object.name + } else if(type == 'string') { + return JSON.stringify(object) + } else if(type == 'object') { + if((object[Symbol.toStringTag]) == 'Module') { + // protect against lodash module contains toJSON function + return header_object(object) + } else if(isPromise(object)) { + if(object.status == null) { + return `Promise` + } else { + if(object.status.ok) { + return `Promise` + } else { + return `Promise` + } + } + } else if(isDate(object)) { + return object.toString() + } else if(isError(object)) { + return object.toString() + } else if(Array.isArray(object)) { + return '[' + + object + .map(stringify_for_header) + .join(', ') + + ']' + } else if(typeof(object.toJSON) == 'function' && !no_toJSON) { + const json = toJSON_safe(object) + if(json == object) { + // prevent infinite recursion + return header(object, true) + } else { + return header(json) + } + } else if(has_custom_toString(object)) { + return object.toString() + } else { + return header_object(object) + } + } else { + return object.toString() + } +} +