Skip to content

Commit 20db22a

Browse files
committed
live code (of sorts)
1 parent 1a491d4 commit 20db22a

16 files changed

+484
-33
lines changed

.prettierignore

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
test/input
12
test/output

docs/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
In publishing and graphic design, Lorem ipsum is a placeholder text commonly used to demonstrate the visual form of a document or a typeface without relying on meaningful content. Lorem ipsum may be used as a placeholder before final copy is available. It is also used to temporarily replace text in a process called greeking, which allows designers to consider the form of a webpage or publication, without the meaning of the text influencing the design.
55

6-
```js echo
6+
```js show
77
Plot.plot({
88
y: {percent: true},
99
marks: [

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
"test:prettier": "prettier --check src test"
1818
},
1919
"dependencies": {
20+
"acorn": "^8.10.0",
21+
"acorn-walk": "^8.2.0",
2022
"highlight.js": "^11.8.0",
2123
"markdown-it": "^13.0.2",
2224
"send": "^0.18.0",

src/javascript.ts

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type {Options} from "acorn";
2+
import {Parser} from "acorn";
3+
import {findAwaits} from "./javascript/awaits.js";
4+
import {findDeclarations} from "./javascript/declarations.js";
5+
import {defaultGlobals} from "./javascript/globals.js";
6+
import {findReferences} from "./javascript/references.js";
7+
8+
export function parseJavaScript(
9+
input,
10+
{globals = defaultGlobals, ...otherOptions}: Partial<Options> & {globals?: Set<string>} = {}
11+
) {
12+
const options: Options = {...otherOptions, ecmaVersion: 13, sourceType: "module"};
13+
14+
// Parse the input as a program.
15+
let body = Parser.parse(input, options) as any;
16+
17+
// If the program consists of a single expression statement, it is promoted to
18+
// an expression. (Note this means that {foo: 1} is parsed as a statement
19+
// rather than an object literal; we could fix that by trying to parse as an
20+
// expression first, if desired.) Only program cells are allowed to declare
21+
// variables and imports.
22+
let declarations;
23+
if (body.body.length === 1 && body.body[0].type === "ExpressionStatement") {
24+
body = body.body[0].expression;
25+
} else {
26+
declarations = findDeclarations(body, globals, input);
27+
}
28+
29+
const async = findAwaits(body).length > 0;
30+
const references = findReferences(body, globals, input);
31+
32+
return {
33+
body,
34+
declarations,
35+
references,
36+
async
37+
};
38+
}

src/javascript/awaits.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import {recursive} from "acorn-walk";
2+
3+
export function findAwaits(node) {
4+
const nodes = [];
5+
6+
recursive(node, null, {
7+
Function() {}, // ignore anything inside a function
8+
AwaitExpression(node) {
9+
nodes.push(node);
10+
}
11+
});
12+
13+
return nodes;
14+
}

src/javascript/declarations.js

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import {syntaxError} from "./syntaxError.js";
2+
3+
export function findDeclarations(node, globals, input) {
4+
if (node.type !== "Program") throw new Error(`unexpected type: ${node.type}`);
5+
6+
const declarations = [];
7+
8+
function declareLocal(node) {
9+
if (globals.has(node.name) || node.name === "arguments") {
10+
throw syntaxError(`Global '${node.name}' cannot be redefined`, node, input);
11+
}
12+
declarations.push(node);
13+
}
14+
15+
function declarePattern(node) {
16+
switch (node.type) {
17+
case "Identifier":
18+
declareLocal(node);
19+
break;
20+
case "ObjectPattern":
21+
node.properties.forEach((node) => declarePattern(node));
22+
break;
23+
case "ArrayPattern":
24+
node.elements.forEach((node) => node && declarePattern(node));
25+
break;
26+
case "Property":
27+
declarePattern(node.value);
28+
break;
29+
case "RestElement":
30+
declarePattern(node.argument);
31+
break;
32+
case "AssignmentPattern":
33+
declarePattern(node.left);
34+
break;
35+
default:
36+
throw new Error("Unrecognized pattern type: " + node.type);
37+
}
38+
}
39+
40+
for (const child of node.body) {
41+
switch (child.type) {
42+
case "VariableDeclaration":
43+
child.declarations.forEach((declaration) => declarePattern(declaration.id));
44+
break;
45+
case "ClassDeclaration":
46+
case "FunctionDeclaration":
47+
declareLocal(child.id);
48+
break;
49+
case "ImportDefaultSpecifier":
50+
case "ImportSpecifier":
51+
case "ImportNamespaceSpecifier":
52+
declareLocal(child.local);
53+
break;
54+
case "Class":
55+
case "Function":
56+
throw new Error(`unexpected type: ${child.type}`);
57+
}
58+
}
59+
60+
return declarations;
61+
}

src/javascript/globals.js

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
export const defaultGlobals = new Set([
2+
"Array",
3+
"ArrayBuffer",
4+
"atob",
5+
"AudioContext",
6+
"Blob",
7+
"Boolean",
8+
"BigInt",
9+
"btoa",
10+
"clearInterval",
11+
"clearTimeout",
12+
"console",
13+
"crypto",
14+
"CustomEvent",
15+
"DataView",
16+
"Date",
17+
"decodeURI",
18+
"decodeURIComponent",
19+
"devicePixelRatio",
20+
"document",
21+
"encodeURI",
22+
"encodeURIComponent",
23+
"Error",
24+
"escape",
25+
"eval",
26+
"fetch",
27+
"File",
28+
"FileList",
29+
"FileReader",
30+
"Float32Array",
31+
"Float64Array",
32+
"Function",
33+
"Headers",
34+
"Image",
35+
"ImageData",
36+
"Infinity",
37+
"Int16Array",
38+
"Int32Array",
39+
"Int8Array",
40+
"Intl",
41+
"isFinite",
42+
"isNaN",
43+
"JSON",
44+
"Map",
45+
"Math",
46+
"NaN",
47+
"Number",
48+
"navigator",
49+
"Object",
50+
"parseFloat",
51+
"parseInt",
52+
"performance",
53+
"Path2D",
54+
"Promise",
55+
"Proxy",
56+
"RangeError",
57+
"ReferenceError",
58+
"Reflect",
59+
"RegExp",
60+
"cancelAnimationFrame",
61+
"requestAnimationFrame",
62+
"Set",
63+
"setInterval",
64+
"setTimeout",
65+
"String",
66+
"Symbol",
67+
"SyntaxError",
68+
"TextDecoder",
69+
"TextEncoder",
70+
"this",
71+
"TypeError",
72+
"Uint16Array",
73+
"Uint32Array",
74+
"Uint8Array",
75+
"Uint8ClampedArray",
76+
"undefined",
77+
"unescape",
78+
"URIError",
79+
"URL",
80+
"WeakMap",
81+
"WeakSet",
82+
"WebSocket",
83+
"Worker",
84+
"window"
85+
]);

src/javascript/imports.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {syntaxError} from "./syntaxError.js";
2+
3+
export function findImports(program, input) {
4+
const node = program.body.find(isExport);
5+
if (node) throw syntaxError("Illegal export declaration", node, input);
6+
return program.body.filter(isImport);
7+
}
8+
9+
function isImport(node) {
10+
return node.type === "ImportDeclaration";
11+
}
12+
13+
function isExport(node) {
14+
return (
15+
node.type === "ExportNamedDeclaration" ||
16+
node.type === "ExportDefaultDeclaration" ||
17+
node.type === "ExportAllDeclaration"
18+
);
19+
}

0 commit comments

Comments
 (0)