Skip to content

Commit 942c4b6

Browse files
authored
Improve debugging of Mint values. (#704)
1 parent 64e1590 commit 942c4b6

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+706
-315
lines changed

.tool-versions

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
crystal 1.14.0
2-
mint 0.19.0
2+
mint 0.20.0
33
nodejs 20.10.0
44
yarn 1.22.19

Makefile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,15 @@ development-release:
4747
crystal build src/mint.cr -o mint-dev --static --no-debug --release
4848
mv ./mint-dev ~/.bin/
4949

50-
src/assets/runtime.js: $(shell find runtime/src -type f)
50+
src/assets/runtime.js: \
51+
$(shell find runtime/src -type f) \
52+
runtime/index.js
5153
cd runtime && make index
5254

53-
src/assets/runtime_test.js: $(shell find runtime/src -type f)
55+
src/assets/runtime_test.js: \
56+
$(shell find runtime/src -type f) \
57+
runtime/index_testing.js \
58+
runtime/index.js
5459
cd runtime && make index_testing
5560

5661
# This builds the binary and depends on files in some directories.

core/source/Debug.mint

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
/* This module provides functions for debugging purposes. */
22
module Debug {
3+
/*
4+
Returns a nicely formatted version of the value. Values of Mint types
5+
preserve their original name.
6+
7+
Debug.inspect("Hello World!") -> "Hello World!"
8+
Debug.inspect(Maybe.Nothing) -> Maybe.Nothing
9+
Debug.inspect({ name: "Joe", age: 37 }) -> User { name: "Joe", age: 37 }
10+
*/
11+
fun inspect (value : a) : String {
12+
`#{%inspect%}(#{value})`
13+
}
14+
315
/*
416
Logs an arbitrary value to the windows console.
517

runtime/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@ export * from "./src/program";
1414
export * from "./src/portals";
1515
export * from "./src/variant";
1616
export * from "./src/styles";
17+
export * from "./src/debug";

runtime/src/debug.js

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import indentString from "indent-string";
2+
import { isVnode } from './equality';
3+
import { Variant } from './variant';
4+
import { Name } from './symbols';
5+
6+
const render = (items, prefix, suffix, fn) => {
7+
items =
8+
items.map(fn);
9+
10+
const newLines =
11+
items.size > 3 || items.filter((item) => item.indexOf("\n") > 0).length;
12+
13+
const joined =
14+
items.join(newLines ? ",\n" : ", ");
15+
16+
if (newLines) {
17+
return `${prefix.trim()}\n${indentString(joined, 2)}\n${suffix.trim()}`;
18+
} else {
19+
return `${prefix}${joined}${suffix}`;
20+
}
21+
}
22+
23+
const toString = (object) => {
24+
if (object.type === "null") {
25+
return "null";
26+
} else if (object.type === "undefined") {
27+
return "undefined";
28+
} else if (object.type === "string") {
29+
return `"${object.value}"`;
30+
} else if (object.type === "number") {
31+
return `${object.value}`;
32+
} else if (object.type === "boolean") {
33+
return `${object.value}`;
34+
} else if (object.type === "element") {
35+
return `<${object.value.toLowerCase()}>`
36+
} else if (object.type === "variant") {
37+
if (object.items) {
38+
return render(object.items, `${object.value}(`, `)`, toString);
39+
} else {
40+
return object.value;
41+
}
42+
} else if (object.type === "array") {
43+
return render(object.items, `[`, `]`, toString);
44+
} else if (object.type === "object") {
45+
return render(object.items, `{ `, ` }`, toString);
46+
} else if (object.type === "record") {
47+
return render(object.items, `${object.value} { `, ` }`, toString);
48+
} else if (object.type === "unknown") {
49+
return `{ ${object.value} }`;
50+
} else if (object.type === "vnode") {
51+
return `VNode`;
52+
} else if (object.key) {
53+
return `${object.key}: ${toString(object.value)}`;
54+
} else if (object.value) {
55+
return toString(object.value);
56+
}
57+
}
58+
59+
const objectify = (value) => {
60+
if (value === null) {
61+
return { type: "null" };
62+
} else if (value === undefined) {
63+
return { type: "undefined" };
64+
} else if (typeof value === "string") {
65+
return { type: "string", value: value };
66+
} else if (typeof value === "number") {
67+
return { type: "number", value: value.toString() };
68+
} else if (typeof value === "boolean") {
69+
return { type: "boolean", value: value.toString() };
70+
} else if (value instanceof HTMLElement) {
71+
return { type: "element", value: value.tagName };
72+
} else if (value instanceof Variant) {
73+
const items = [];
74+
75+
if (value.record) {
76+
for (const key in value) {
77+
if (key === "length" || key === "record" || key.startsWith("_")) {
78+
continue;
79+
};
80+
81+
items.push({
82+
value: objectify(value[key]),
83+
key: key
84+
});
85+
}
86+
} else {
87+
for (let i = 0; i < value.length; i++) {
88+
items.push({
89+
value: objectify(value[`_${i}`])
90+
});
91+
};
92+
}
93+
94+
if (items.length) {
95+
return { type: "variant", value: value[Name], items: items };
96+
} else {
97+
return { type: "variant", value: value[Name] };
98+
}
99+
} else if (Array.isArray(value)) {
100+
return {
101+
items: value.map((item) => ({ value: objectify(item) })),
102+
type: "array"
103+
};
104+
} else if (isVnode(value)) {
105+
return { type: "vnode" }
106+
} else if (typeof value == "object") {
107+
const items = [];
108+
109+
for (const key in value) {
110+
items.push({
111+
value: objectify(value[key]),
112+
key: key
113+
});
114+
};
115+
116+
if (Name in value) {
117+
return { type: "record", value: value[Name], items: items };
118+
} else {
119+
return { type: "object", items: items };
120+
}
121+
} else {
122+
return { type: "unknown", value: value.toString() };
123+
}
124+
}
125+
126+
export const inspect = (value) => {
127+
return toString(objectify(value))
128+
}

runtime/src/decoders.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import indentString from "indent-string";
2+
import { Name } from "./symbols";
23

34
// Formats the given value as JSON with extra indentation.
45
const format = (value) => {
@@ -317,8 +318,8 @@ export const decodeMap = (decoder, ok, err) => (input) => {
317318
};
318319

319320
// Decodes a record, using the mappings.
320-
export const decoder = (mappings, ok, err) => (input) => {
321-
const object = {};
321+
export const decoder = (name, mappings, ok, err) => (input) => {
322+
const object = {[Name]: name};
322323

323324
for (let key in mappings) {
324325
let decoder = mappings[key];

runtime/src/equality.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// This file contains code to have value equality instead of reference equality.
22
// We use a `Symbol` to have a custom equality functions and then use these
33
// functions when comparing two values.
4-
export const Equals = Symbol("Equals");
4+
import { Equals } from './symbols';
55

66
/* v8 ignore next 3 */
77
if (typeof Node === "undefined") {
@@ -123,7 +123,7 @@ Map.prototype[Equals] = function (other) {
123123
};
124124

125125
// If the object has a specific set of keys it's a Preact virtual DOM node.
126-
const isVnode = (object) =>
126+
export const isVnode = (object) =>
127127
object !== undefined &&
128128
object !== null &&
129129
typeof object == "object" &&

runtime/src/symbols.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const Equals = Symbol("Equals");
2+
export const Name = Symbol('Name');

runtime/src/utilities.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { useEffect, useRef, useMemo } from "preact/hooks";
33
import { signal } from "@preact/signals";
44

55
import { compare } from "./equality";
6+
import { Name } from "./symbols";
67

78
// This finds the first element matching the key in a map ([[key, value]]).
89
export const mapAccess = (map, key, just, nothing) => {
@@ -87,6 +88,10 @@ export const access = (field) => (value) => value[field];
8788
// Identity function, used in encoders.
8889
export const identity = (a) => a;
8990

91+
// Creates an instrumented object so we know which record it belongs to.
92+
export const record = (name) => (value) => ({ [Name]: name, ...value})
93+
94+
// A component to lazy load another component.
9095
export class lazyComponent extends Component {
9196
async componentDidMount() {
9297
let x = await this.props.x();
@@ -102,8 +107,10 @@ export class lazyComponent extends Component {
102107
}
103108
}
104109

110+
// A higher order function to lazy load a module.
105111
export const lazy = (path) => async () => load(path)
106112

113+
// Loads load a module.
107114
export const load = async (path) => {
108115
const x = await import(path)
109116
return x.default

runtime/src/variant.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import { Equals, compareObjects, compare } from "./equality";
1+
import { compareObjects, compare } from "./equality";
2+
import { Equals, Name } from "./symbols";
23

34
// The base class for variants.
4-
class Variant {
5+
export class Variant {
56
[Equals](other) {
67
if (!(other instanceof this.constructor)) {
78
return false;
@@ -27,10 +28,11 @@ class Variant {
2728

2829
// Creates an type variant class, this is needed so we can do proper
2930
// comparisons and pattern matching / destructuring.
30-
export const variant = (input) => {
31+
export const variant = (input, name) => {
3132
return class extends Variant {
3233
constructor(...args) {
3334
super();
35+
this[Name] = name
3436
if (Array.isArray(input)) {
3537
this.length = input.length;
3638
this.record = true;

0 commit comments

Comments
 (0)