Skip to content

Commit b0ef2d8

Browse files
add visualizer + revise type signatures
1 parent 558e7ab commit b0ef2d8

File tree

1 file changed

+230
-106
lines changed

1 file changed

+230
-106
lines changed

scriptum.js

Lines changed: 230 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,9 @@ export const debugIf = p => expr => {
8989
};
9090

9191

92+
//█████ Type Signatures ███████████████████████████████████████████████████████
93+
94+
9295
// intercept type signatures
9396

9497
export const trace = x => {
@@ -97,10 +100,237 @@ export const trace = x => {
97100
};
98101

99102

103+
export const Sign = {};
104+
105+
Sign.retrieve = x => {
106+
if (typeof x === "function") {
107+
if (x[$] === "Visor") return x.toString();
108+
return x.name || "λ";
109+
}
110+
111+
else if (x === null) return "Nul";
112+
else if (x === undefined) return "Und";
113+
114+
else if (typeof x === "object") {
115+
const tag = Object.prototype.toString.call(x).slice(8, -1);
116+
117+
switch (tag) {
118+
case "Array": return `[${Sign.arr(x)}]`;
119+
case "Boolean": return "Bol{}";
120+
case "Date": return "Dat{}";
121+
case "Map": return `Map<${Sign.map(x)}>`;
122+
case "Number": return "Num{}";
123+
case "Promise": return "Pro{}";
124+
case "RegExp": return "Rex{}";
125+
case "Set": return `Set<${Sign.set(x)}>`;
126+
case "String": return "Str{}";
127+
case "Symbol": return "Sym{}";
128+
case "WeakMap": return `Wap<${Sign.map(x)}>`;
129+
case "WeakRef": return "Ref{}";
130+
case "WeakSet": return `Wet<${Sign.set(x)}>`;
131+
132+
case "Object": {
133+
if (x?.[$]) return `${x[$]}{${Sign.obj(x)}}`;
134+
135+
else {
136+
const name = x?.constructor?.name === "Object"
137+
? "" : x.constructor.name;
138+
139+
return `${name}{${Sign.obj(x)}}`;
140+
}
141+
}
142+
143+
default: throw new Err(`unknown tag "${tag}`);
144+
}
145+
}
146+
147+
else switch (Object.prototype.toString.call(x).slice(8, -1)) {
148+
// Primitives (excluding function/null/undefined handled above)
149+
case "Boolean": return "Boo";
150+
case "BigInt": return "Big";
151+
case "NaN": return "NaN";
152+
case "Number": return "Num";
153+
case "String": return "Str";
154+
case "Symbol": return "Sym";
155+
default: throw new Err(`unknown primitive tag "${tag}`);
156+
}
157+
};
158+
159+
160+
Sign.arr = xs => {
161+
const s = xs.reduce((acc, x) => {
162+
return acc.add(Sign.retrieve(x))
163+
}, new Set());
164+
165+
return Array.from(s).join(",");
166+
};
167+
168+
169+
Sign.set = s => {
170+
const s2 = Array.from(s).reduce((acc, x) => {
171+
return acc.add(Sign.retrieve(x))
172+
}, new Set());
173+
174+
return Array.from(s2).join(",");
175+
};
176+
177+
178+
Sign.map = m => {
179+
const s2 = Array.from(m).reduce((acc, pair) => {
180+
return acc.add(`${Sign.retrieve(pair[0])}:${Sign.retrieve(pair[1])}`);
181+
}, new Set());
182+
183+
return Array.from(s2).join(",");
184+
};
185+
186+
187+
Sign.obj = o => {
188+
const s2 = Object.entries(o).reduce((acc, pair) => {
189+
return acc.add(`${Sign.retrieve(pair[0])}:${Sign.retrieve(pair[1])}`);
190+
}, new Set());
191+
192+
return Array.from(s2).join(",");
193+
};
194+
195+
100196
//█████ Visualization █████████████████████████████████████████████████████████
101197

102198

199+
/* Instead of static typing, scriptum tries to visualize the nested intermediate
200+
function call tree that is build by the computation. It does so by logging all
201+
intermediate results. This profoundly assists the programmer during development.
202+
Evaluating `comp(comp) (comp) (sqr) (add) (3) (4)`, for instance, yields the
203+
following log:
204+
205+
➡️ comp(comp(f) (g) (x))
206+
✅ comp(comp(f) (g) (x)) 🠲 comp(comp(f) (g) (x)) (g) (x)
207+
➡️ comp(comp(f) (g) (x)) (comp(f) (g) (x))
208+
✅ comp(comp(f) (g) (x)) (comp(f) (g) (x)) 🠲 comp(comp(f) (g) (x)) (comp(f) (g) (x)) (x)
209+
➡️ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x))
210+
➡️ comp(sqr(x))
211+
✅ comp(sqr(x)) 🠲 comp(sqr(x)) (g) (x)
212+
➡️ comp(comp(sqr(x)) (g) (x))
213+
✅ comp(comp(sqr(x)) (g) (x)) 🠲 comp(comp(sqr(x)) (g) (x)) (g) (x)
214+
➡️ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (add(x) (y))
215+
➡️ comp(comp(sqr(x)) (g) (x)) (add(x) (y))
216+
✅ comp(comp(sqr(x)) (g) (x)) (add(x) (y)) 🠲 comp(comp(sqr(x)) (g) (x)) (add(x) (y)) (x)
217+
✅ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (add(x) (y)) 🠲 comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x))
218+
➡️ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (Num)
219+
➡️ comp(comp(sqr(x)) (g) (x)) (add(x) (y)) (Num)
220+
➡️ add(Num)
221+
✅ add(Num) 🠲 add(Num) (y)
222+
➡️ comp(sqr(x)) (add(Num) (y))
223+
✅ comp(sqr(x)) (add(Num) (y)) 🠲 comp(sqr(x)) (add(Num) (y)) (x)
224+
✅ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (Num) 🠲 comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x))
225+
➡️ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (Num)
226+
➡️ comp(comp(sqr(x)) (g) (x)) (add(x) (y)) (Num) (Num)
227+
➡️ comp(sqr(x)) (add(Num) (y)) (Num)
228+
➡️ add(Num) (Num)
229+
✅ add(Num) (Num) 🠲 Num
230+
➡️ sqr(Num)
231+
✅ sqr(Num) 🠲 Num
232+
✅ comp(sqr(x)) (add(Num) (y)) (Num) 🠲 Num
233+
✅ comp(comp(sqr(x)) (g) (x)) (add(x) (y)) (Num) (Num) 🠲 Num
234+
✅ comp(comp(f) (g) (x)) (comp(f) (g) (x)) (sqr(x)) (Num) 🠲 Num */
235+
236+
237+
export const Visor = {};
238+
239+
240+
Visor.serialize = x => {
241+
if (x?.[$] === "Visor") return x.toString();
242+
return Sign.retrieve(x);
243+
};
244+
245+
246+
Visor.visualize = o => {
247+
if (!o) return "?";
248+
249+
let s = o.baseName, xs = [...o.baseParams];
250+
251+
for (let i = 0; i < o.appliedArgs.length && i < xs.length; i++) {
252+
const argValue = o.appliedArgs[i];
253+
s += `(${Visor.serialize(argValue)})`;
254+
}
255+
256+
const ys = xs.slice(o.appliedArgs.length);
257+
for (const param of ys) s += `(${param})`;
258+
return s;
259+
};
260+
261+
262+
Visor.createWrapper = (f, o) => {
263+
const wrapper = function(...newArgs) {
264+
const xs = o.appliedArgs,
265+
ys = [...xs, ...newArgs];
266+
267+
let s = o.baseName;
268+
269+
for(let i = 0; i < o.baseParams.length; i++) {
270+
if (i < xs.length) s += `(${Visor.serialize(xs[i])})`;
271+
else break;
272+
}
273+
274+
s += newArgs.map(arg => `(${Visor.serialize(arg)})`).join("");
275+
276+
const callStr = s.replaceAll(/\)(?=\()/g, ") ");
277+
console.log(`➡️ ${callStr}`);
278+
279+
let r;
280+
let resultToLog;
281+
let finalReturnValue;
282+
283+
try {
284+
r = f.apply(this, newArgs);
285+
286+
if (typeof r === "function") {
287+
const p = {...o, appliedArgs: ys};
288+
finalReturnValue = Visor.createWrapper(r, p);
289+
resultToLog = finalReturnValue;
290+
} else {
291+
finalReturnValue = r;
292+
resultToLog = r;
293+
}
294+
295+
let resultStr = Visor.serialize(resultToLog);
296+
297+
if (callStr.replaceAll(/ /g, "") !== resultStr.replaceAll(/ /g, "")) {
298+
let s3 = `✅ ${callStr} 🠲 ${resultStr}`;
299+
console.log(s3.replaceAll(/\)(?=\()/g, ") "));
300+
}
301+
}
302+
303+
catch (e) {
304+
const s2 = s.replaceAll(/\)(?=\()/g, ") ");
305+
306+
console.error(`💥 error during: ${s2}`);
307+
console.error(` function: ${f.name || "λ"}`);
308+
console.error(` wrapper: ${Visor.visualize(o)}`);
309+
console.error(` applying ${newArgs.map(Visor.serialize).join(", ")}`)
310+
console.error(` message: ${e.message}`);
311+
if (e.stack) console.error(` Stack: ${e.stack.split('\n').slice(1).join('\n')}`);
312+
throw e;
313+
}
103314

315+
return finalReturnValue;
316+
};
317+
318+
wrapper[$] = "Visor";
319+
wrapper[$$] = "Visor";
320+
wrapper.toString = () => Visor.visualize(o);
321+
return wrapper;
322+
};
323+
324+
325+
Visor.augment = (f, name, params = []) => {
326+
const o = {
327+
baseName: name,
328+
baseParams: params,
329+
appliedArgs: [],
330+
};
331+
332+
return Visor.createWrapper(f, o);
333+
};
104334

105335

106336
/*█████████████████████████████████████████████████████████████████████████████
@@ -717,112 +947,6 @@ Stack.Base = function Base(x) {
717947
};
718948

719949

720-
/*█████████████████████████████████████████████████████████████████████████████
721-
███████████████████████████████ TYPE SIGNATURES ███████████████████████████████
722-
███████████████████████████████████████████████████████████████████████████████*/
723-
724-
725-
export const Sign = {};
726-
727-
728-
Sign.get = x => {
729-
if (x === null) return "Nul";
730-
else if (x === undefined) return "Und";
731-
732-
else if (typeof x === "object") {
733-
const tag = Object.prototype.toString.call(x).slice(8, -1);
734-
735-
switch (tag) {
736-
case "Array": return `[${Sign.arr(x)}]`;
737-
case "Boolean": return "Bol{}";
738-
case "Date": return "Dat{}";
739-
case "Map": return `Map<${Sign.map(x)}>`;
740-
case "Number": return "Num{}";
741-
case "Promise": return "Pro{}";
742-
case "RegExp": return "Rex{}";
743-
case "Set": return `Set<${Sign.set(x)}>`;
744-
case "String": return "Str{}";
745-
case "Symbol": return "Sym{}";
746-
case "WeakMap": return `Wap<${Sign.map(x)}>`;
747-
case "WeakRef": return "Ref{}";
748-
case "WeakSet": return `Wet<${Sign.set(x)}>`;
749-
750-
case "Object": {
751-
if (x?.[$]) return `${x[$]}{${Sign.obj(x)}}`;
752-
753-
else {
754-
const name = x?.constructor?.name === "Object"
755-
? "" : x.constructor.name;
756-
757-
return `${name}{${Sign.obj(x)}}`;
758-
}
759-
}
760-
761-
default: throw new Err(`unknown tag "${tag}`);
762-
}
763-
}
764-
765-
else switch (Object.prototype.toString.call(x).slice(8, -1)) {
766-
case "Boolean": return "Boo";
767-
case "BigInt": return "Big";
768-
case "Function": return "=>";
769-
case "NaN": return "NaN";
770-
case "Null": return "Nul";
771-
case "Number": return "Num";
772-
case "String": return "Str";
773-
case "Undefined": return "Und";
774-
775-
default: {
776-
777-
}
778-
}
779-
};
780-
781-
782-
Sign.arr = xs => {
783-
const s = xs.reduce((acc, x) => {
784-
return acc.add(Sign.get(x))
785-
}, new Set());
786-
787-
return Array.from(s).join(",");
788-
};
789-
790-
791-
Sign.set = s => {
792-
const s2 = Array.from(s).reduce((acc, x) => {
793-
return acc.add(Sign.get(x))
794-
}, new Set());
795-
796-
return Array.from(s2).join(",");
797-
};
798-
799-
800-
Sign.map = m => {
801-
const s2 = Array.from(m).reduce((acc, pair) => {
802-
return acc.add(`${Sign.get(pair[0])}:${Sign.get(pair[1])}`);
803-
}, new Set());
804-
805-
return Array.from(s2).join(",");
806-
};
807-
808-
809-
Sign.obj = o => {
810-
const s2 = Object.entries(o).reduce((acc, pair) => {
811-
return acc.add(`${Sign.key(pair[0])}:${Sign.get(pair[1])}`);
812-
}, new Set());
813-
814-
return Array.from(s2).join(",");
815-
};
816-
817-
818-
Sign.key = x => {
819-
if (Object.prototype.toString.call(x).slice(8, -1) === "Symbol")
820-
return "Sym";
821-
822-
else return x;
823-
};
824-
825-
826950
/*█████████████████████████████████████████████████████████████████████████████
827951
███████████████████████████████████████████████████████████████████████████████
828952
████████████████████████████████████ ARRAY ████████████████████████████████████

0 commit comments

Comments
 (0)