Skip to content

Commit 2b59d49

Browse files
committed
add new sketch verifier to FES; it currently can read all user-defined variables and functions
1 parent 34d41a5 commit 2b59d49

File tree

5 files changed

+195
-45
lines changed

5 files changed

+195
-45
lines changed

package-lock.json

+54-22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
"version": "1.9.4",
2424
"dependencies": {
2525
"colorjs.io": "^0.5.2",
26+
"espree": "^10.2.0",
2627
"file-saver": "^1.3.8",
2728
"gifenc": "^1.0.3",
2829
"libtess": "^1.2.2",
@@ -82,4 +83,4 @@
8283
"pre-commit": "lint-staged"
8384
}
8485
}
85-
}
86+
}

preview/index.html

+30-22
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,49 @@
11
<!DOCTYPE html>
22
<html>
3+
34
<head>
45
<title>P5 test</title>
56
<meta http-equiv="pragma" content="no-cache" />
67
<meta http-equiv="cache-control" content="no-cache" />
78
<meta charset="utf-8">
89

910
<style>
10-
body{
11-
margin:0;
12-
overflow: hidden;
13-
}
11+
body {
12+
margin: 0;
13+
overflow: hidden;
14+
}
1415
</style>
1516
</head>
17+
1618
<body>
17-
<script type="module">
18-
import p5 from '../src/app.js';
19-
// import calculation from './src/math/calculation.js';
19+
<script type="module">
20+
import p5 from '../src/app.js';
21+
// import calculation from './src/math/calculation.js';
2022

21-
// p5.registerAddon(calculation);
23+
// p5.registerAddon(calculation);
2224

23-
const sketch = function(p){
24-
p.setup = function(){
25-
p.createCanvas(200, 200);
26-
};
25+
let apple = 10;
26+
function banana() {
27+
console.log('banana');
28+
}
29+
const sketch = function (p) {
30+
p.setup = function () {
31+
p.createCanvas(200, 200);
32+
p.run();
33+
};
2734

28-
p.draw = function(){
29-
p.background(0, 50, 50);
30-
p.circle(100, 100, 50);
35+
p.draw = function () {
36+
p.background(0, 50, 50);
37+
p.circle(100, 100, 50);
3138

32-
p.fill('white');
33-
p.textSize(30);
34-
p.text('hello', 10, 30);
35-
};
36-
};
39+
p.fill('white');
40+
p.textSize(30);
41+
p.text('hello', 10, 30);
42+
};
43+
};
3744

38-
new p5(sketch);
39-
</script>
45+
new p5(sketch);
46+
</script>
4047
</body>
48+
4149
</html>

src/core/friendly_errors/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import validateParams from './param_validator.js';
2+
import sketchVerifier from './sketch_verifier.js';
23

34
export default function (p5) {
45
p5.registerAddon(validateParams);
6+
p5.registerAddon(sketchVerifier);
57
}
+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import * as espree from 'espree';
2+
3+
/**
4+
* @for p5
5+
* @requires core
6+
*/
7+
function sketchVerifier(p5, fn) {
8+
/**
9+
* Fetches the contents of a script element in the user's sketch.
10+
*
11+
* @method fetchScript
12+
* @param {HTMLScriptElement} script
13+
* @returns {Promise<string>}
14+
*/
15+
fn.fetchScript = async function (script) {
16+
if (script.src) {
17+
const contents = await fetch(script.src).then((res) => res.text());
18+
return contents;
19+
} else {
20+
return script.textContent;
21+
}
22+
}
23+
24+
/**
25+
* Extracts the user's code from the script fetched. Note that this method
26+
* assumes that the user's code is always the last script element in the
27+
* sketch.
28+
*
29+
* @method getUserCode
30+
* @returns {Promise<string>} The user's code as a string.
31+
*/
32+
fn.getUserCode = async function () {
33+
const scripts = document.querySelectorAll('script');
34+
const userCodeScript = scripts[scripts.length - 1];
35+
const userCode = await fn.fetchScript(userCodeScript);
36+
37+
return userCode;
38+
}
39+
40+
fn.extractUserDefinedVariablesAndFuncs = function (codeStr) {
41+
const userDefinitions = {
42+
variables: [],
43+
functions: []
44+
};
45+
46+
try {
47+
const ast = espree.parse(codeStr, {
48+
ecmaVersion: 2021,
49+
sourceType: 'module',
50+
ecmaFeatures: {
51+
jsx: true
52+
}
53+
});
54+
55+
function traverse(node) {
56+
switch (node.type) {
57+
case 'VariableDeclaration':
58+
node.declarations.forEach(declaration => {
59+
if (declaration.id.type === 'Identifier') {
60+
userDefinitions.variables.push(declaration.id.name);
61+
}
62+
});
63+
break;
64+
case 'FunctionDeclaration':
65+
if (node.id && node.id.type === 'Identifier') {
66+
userDefinitions.functions.push(node.id.name);
67+
}
68+
break;
69+
case 'ArrowFunctionExpression':
70+
case 'FunctionExpression':
71+
if (node.parent && node.parent.type === 'VariableDeclarator') {
72+
userDefinitions.functions.push(node.parent.id.name);
73+
}
74+
break;
75+
}
76+
77+
for (const key in node) {
78+
if (node[key] && typeof node[key] === 'object') {
79+
if (Array.isArray(node[key])) {
80+
node[key].forEach(child => traverse(child));
81+
} else {
82+
traverse(node[key]);
83+
}
84+
}
85+
}
86+
}
87+
88+
traverse(ast);
89+
} catch (error) {
90+
console.error('Error parsing code:', error);
91+
}
92+
93+
return userDefinitions;
94+
}
95+
96+
fn.run = async function () {
97+
const userCode = await fn.getUserCode();
98+
const userDefinedVariablesAndFuncs = fn.extractUserDefinedVariablesAndFuncs(userCode);
99+
console.log(userDefinedVariablesAndFuncs);
100+
}
101+
}
102+
103+
export default sketchVerifier;
104+
105+
if (typeof p5 !== 'undefined') {
106+
sketchVerifier(p5, p5.prototype);
107+
}

0 commit comments

Comments
 (0)