Public
Script
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { generate } from "jsr:@davidbonnet/astring";
import { assertEquals, assertThrows } from "jsr:@std/assert";
import { parse, type Program } from "npm:acorn";
/**
* Can't do content addressability without a good hash function
* String -> SHA256 hash
*/
const hash = (content: string) =>
crypto.subtle
.digest("SHA-256", new TextEncoder().encode(content))
.then((b) => new Uint8Array(b))
.then(Array.from)
.then((a) => a.map((b: any) => b.toString(16).padStart(2, "0")).join(""));
/**
* A template literal fn to go from a JS string to a JS AST
*/
const js = async (strings: TemplateStringsArray, ...values: any[]) => {
const code = String.raw({ raw: strings }, ...values);
const ast = parse(code, { ecmaVersion: 2024 });
const normalizedASTs = ast.body
.map((chunk) => structuredClone(chunk))
.map((chunk) => {
walk(chunk, normalize);
return chunk;
});
const contentMap = Object.fromEntries(
await Promise.all(
normalizedASTs.map(async (ast) => [await hash(JSON.stringify(ast)), ast]),
),
);
return {
ast,
contentMap,
};
};
/**
* Generically walks an object and passes a callback that can mutate the children of the object
*/
const walk = (
node: object,
callback: (node: Record<string, unknown>) => void,
): void => {
if (typeof node !== "object" || node === null) {
return;
}
callback(node as Record<string, unknown>);
for (const value of Object.values(node)) {
switch (true) {
case typeof value === "object" && value !== null:
walk(value, callback);
break;
case Array.isArray(value):
value.forEach((v) => walk(v, callback));
break;
default:
break;
}
}
};
/**
* Our meat and potatoes. This is the function that will gradually transform the AST into something uniform.
*/
const normalize = (node: Record<string, unknown>) => {
// Start by deleting positions. Who needs those?! (This is definitely going to bite me)
delete node.start;
delete node.end;
// I believe I can get away with deleting raw too
// This avoids any unnecessary formatting shinanigans
delete node.raw;
};
const modules = {};
const variantA = await js`console.log('hello world!')`;
const variantB = await js`console.log( "hello world!" );`;
// Just to verify things are working
assertEquals(variantA.ast, variantA.ast);
assertThrows(() => {
assertEquals(variantA.ast, variantB.ast);
});
assertEquals(variantA.contentMap, variantB.contentMap);
// Chunking
const chunk1 = await js`
// comments should be ignored
function helloWorld() {}
const goodByeCruelWorld = () => {}
`;
August 27, 2024