mirror of
https://github.com/leporello-js/leporello-js
synced 2026-01-13 13:04:30 -08:00
refactor value explorer
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import {exec} from '../index.js'
|
import {exec} from '../index.js'
|
||||||
import {el, stringify, fn_link, scrollIntoViewIfNeeded} from './domutils.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 {find_node} from '../ast_utils.js'
|
||||||
import {is_expandable, root_calltree_node, get_deferred_calls, has_error}
|
import {is_expandable, root_calltree_node, get_deferred_calls, has_error}
|
||||||
from '../calltree.js'
|
from '../calltree.js'
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import {exec, get_state} from '../index.js'
|
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'
|
import {el, stringify, fn_link} from './domutils.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 {el} from './domutils.js'
|
||||||
import {has_error} from '../calltree.js'
|
import {has_error} from '../calltree.js'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {el, scrollIntoViewIfNeeded} from './domutils.js'
|
import {el, scrollIntoViewIfNeeded} from './domutils.js'
|
||||||
import {exec} from '../index.js'
|
import {exec} from '../index.js'
|
||||||
import {header} from './value_explorer.js'
|
import {header} from '../value_explorer_utils.js'
|
||||||
|
|
||||||
export class Logs {
|
export class Logs {
|
||||||
constructor(ui, el) {
|
constructor(ui, el) {
|
||||||
|
|||||||
@@ -6,207 +6,9 @@
|
|||||||
|
|
||||||
import {el, stringify, scrollIntoViewIfNeeded} from './domutils.js'
|
import {el, stringify, scrollIntoViewIfNeeded} from './domutils.js'
|
||||||
import {with_code_execution} from '../index.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<pending>`
|
|
||||||
} else {
|
|
||||||
if(v.status.ok) {
|
|
||||||
return `Promise<fulfilled: ${stringify_for_header(v.status.value)}>`
|
|
||||||
} else {
|
|
||||||
return `Promise<rejected: ${stringify_for_header(v.status.error)}>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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<pending>`
|
|
||||||
} else {
|
|
||||||
if(object.status.ok) {
|
|
||||||
return `Promise<fulfilled: ${header(object.status.value)}>`
|
|
||||||
} else {
|
|
||||||
return `Promise<rejected: ${header(object.status.error)}>`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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) => {
|
const get_path = (o, path) => {
|
||||||
if(path.length == 0) {
|
if(path.length == 0) {
|
||||||
return o
|
return o
|
||||||
|
|||||||
199
src/value_explorer_utils.js
Normal file
199
src/value_explorer_utils.js
Normal file
@@ -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<pending>`
|
||||||
|
} else {
|
||||||
|
if(v.status.ok) {
|
||||||
|
return `Promise<fulfilled: ${stringify_for_header(v.status.value)}>`
|
||||||
|
} else {
|
||||||
|
return `Promise<rejected: ${stringify_for_header(v.status.error)}>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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<pending>`
|
||||||
|
} else {
|
||||||
|
if(object.status.ok) {
|
||||||
|
return `Promise<fulfilled: ${header(object.status.value)}>`
|
||||||
|
} else {
|
||||||
|
return `Promise<rejected: ${header(object.status.error)}>`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user