Skip to content

Instantly share code, notes, and snippets.

@tangert
Last active May 7, 2024 18:23
Show Gist options
  • Save tangert/cd4ce84e0e7a4d240694d0e0536db27d to your computer and use it in GitHub Desktop.
Save tangert/cd4ce84e0e7a4d240694d0e0536db27d to your computer and use it in GitHub Desktop.
Transpile p5.js global mode to instance mode
/*
Source typefile from here: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/p5/global.d.ts
Used ts-morph to parse the type file and generate a list of all function names and constants.
*/
export default {
functions: [
"describe",
"describeElement",
"textOutput",
"gridOutput",
"alpha",
"blue",
"brightness",
"color",
"green",
"hue",
"lerpColor",
"lightness",
"red",
"saturation",
"background",
"clear",
"colorMode",
"fill",
"noFill",
"noStroke",
"stroke",
"erase",
"noErase",
"arc",
"ellipse",
"circle",
"line",
"point",
"quad",
"rect",
"square",
"triangle",
"ellipseMode",
"noSmooth",
"rectMode",
"smooth",
"strokeCap",
"strokeJoin",
"strokeWeight",
"bezier",
"bezierDetail",
"bezierPoint",
"bezierTangent",
"curve",
"curveDetail",
"curveTightness",
"curvePoint",
"curveTangent",
"beginContour",
"beginShape",
"bezierVertex",
"curveVertex",
"endContour",
"endShape",
"quadraticVertex",
"vertex",
"normal",
"print",
"cursor",
"frameRate",
"getTargetFrameRate",
"noCursor",
"windowResized",
"fullscreen",
"pixelDensity",
"displayDensity",
"getURL",
"getURLPath",
"getURLParams",
"createCanvas",
"resizeCanvas",
"noCanvas",
"createGraphics",
"createFramebuffer",
"blendMode",
"noLoop",
"loop",
"isLooping",
"push",
"pop",
"redraw",
"p5",
"applyMatrix",
"resetMatrix",
"rotate",
"rotateX",
"rotateY",
"rotateZ",
"scale",
"shearX",
"shearY",
"translate",
"storeItem",
"getItem",
"clearStorage",
"removeItem",
"createStringDict",
"createNumberDict",
"select",
"selectAll",
"removeElements",
"changed",
"input",
"createDiv",
"createP",
"createSpan",
"createImg",
"createA",
"createSlider",
"createButton",
"createCheckbox",
"createSelect",
"createRadio",
"createColorPicker",
"createInput",
"createFileInput",
"createVideo",
"createAudio",
"createCapture",
"createElement",
"setMoveThreshold",
"setShakeThreshold",
"deviceMoved",
"deviceTurned",
"deviceShaken",
"keyPressed",
"keyReleased",
"keyTyped",
"keyIsDown",
"mouseMoved",
"mouseDragged",
"mousePressed",
"mouseReleased",
"mouseClicked",
"doubleClicked",
"mouseWheel",
"requestPointerLock",
"exitPointerLock",
"touchStarted",
"touchMoved",
"touchEnded",
"createImage",
"saveCanvas",
"saveFrames",
"loadImage",
"saveGif",
"image",
"tint",
"noTint",
"imageMode",
"blend",
"copy",
"filter",
"get",
"loadPixels",
"set",
"updatePixels",
"loadJSON",
"loadStrings",
"loadTable",
"loadXML",
"loadBytes",
"httpGet",
"httpPost",
"httpDo",
"createWriter",
"save",
"saveJSON",
"saveStrings",
"saveTable",
"abs",
"ceil",
"constrain",
"dist",
"exp",
"floor",
"lerp",
"log",
"mag",
"map",
"max",
"min",
"norm",
"pow",
"round",
"sq",
"sqrt",
"fract",
"createVector",
"noise",
"noiseDetail",
"noiseSeed",
"randomSeed",
"random",
"randomGaussian",
"acos",
"asin",
"atan",
"atan2",
"cos",
"sin",
"tan",
"degrees",
"radians",
"angleMode",
"textAlign",
"textLeading",
"textSize",
"textStyle",
"textWidth",
"textAscent",
"textDescent",
"textWrap",
"loadFont",
"text",
"textFont",
"append",
"arrayCopy",
"concat",
"reverse",
"shorten",
"shuffle",
"sort",
"splice",
"subset",
"float",
"int",
"str",
"boolean",
"byte",
"char",
"unchar",
"hex",
"unhex",
"join",
"match",
"matchAll",
"nf",
"nfc",
"nfp",
"nfs",
"split",
"splitTokens",
"trim",
"day",
"hour",
"minute",
"millis",
"month",
"second",
"year",
"plane",
"box",
"sphere",
"cylinder",
"cone",
"ellipsoid",
"torus",
"orbitControl",
"debugMode",
"noDebugMode",
"ambientLight",
"specularColor",
"directionalLight",
"pointLight",
"lights",
"lightFalloff",
"spotLight",
"noLights",
"loadModel",
"model",
"loadShader",
"createShader",
"shader",
"resetShader",
"texture",
"textureMode",
"textureWrap",
"normalMaterial",
"ambientMaterial",
"emissiveMaterial",
"specularMaterial",
"shininess",
"camera",
"perspective",
"ortho",
"frustum",
"createCamera",
"setCamera",
"vertexNormal",
"setAttributes",
"getAudioContext",
"userStartAudio",
"getOutputVolume",
"outputVolume",
"sampleRate",
"freqToMidi",
"midiToFreq",
"soundFormats",
"saveSound",
"loadSound",
"createConvolver",
"setBPM",
],
constants: [
"VERSION",
"P2D",
"WEBGL",
"WEBGL2",
"ARROW",
"CROSS",
"HAND",
"MOVE",
"TEXT",
"WAIT",
"HALF_PI",
"PI",
"QUARTER_PI",
"TAU",
"TWO_PI",
"DEGREES",
"RADIANS",
"CORNER",
"CORNERS",
"RADIUS",
"RIGHT",
"LEFT",
"CENTER",
"TOP",
"BOTTOM",
"BASELINE",
"POINTS",
"LINES",
"LINE_STRIP",
"LINE_LOOP",
"TRIANGLES",
"TRIANGLE_FAN",
"TRIANGLE_STRIP",
"QUADS",
"QUAD_STRIP",
"TESS",
"CLOSE",
"OPEN",
"CHORD",
"PIE",
"PROJECT",
"SQUARE",
"ROUND",
"BEVEL",
"MITER",
"RGB",
"HSB",
"HSL",
"AUTO",
"ALT",
"BACKSPACE",
"CONTROL",
"DELETE",
"DOWN_ARROW",
"ENTER",
"ESCAPE",
"LEFT_ARROW",
"OPTION",
"RETURN",
"RIGHT_ARROW",
"SHIFT",
"TAB",
"UP_ARROW",
"BLEND",
"REMOVE",
"ADD",
"DARKEST",
"LIGHTEST",
"DIFFERENCE",
"SUBTRACT",
"EXCLUSION",
"MULTIPLY",
"SCREEN",
"REPLACE",
"OVERLAY",
"HARD_LIGHT",
"SOFT_LIGHT",
"DODGE",
"BURN",
"THRESHOLD",
"GRAY",
"OPAQUE",
"INVERT",
"POSTERIZE",
"DILATE",
"ERODE",
"BLUR",
"NORMAL",
"ITALIC",
"BOLD",
"BOLDITALIC",
"CHAR",
"WORD",
"LINEAR",
"QUADRATIC",
"BEZIER",
"CURVE",
"STROKE",
"FILL",
"TEXTURE",
"IMMEDIATE",
"IMAGE",
"NEAREST",
"REPEAT",
"CLAMP",
"MIRROR",
"LANDSCAPE",
"PORTRAIT",
"GRID",
"AXES",
"LABEL",
"FALLBACK",
"CONTAIN",
"COVER",
"UNSIGNED_BYTE",
"UNSIGNED_INT",
"FLOAT",
"HALF_FLOAT",
"RGBA",
"frameCount",
"deltaTime",
"focused",
"webglVersion",
"displayWidth",
"displayHeight",
"windowWidth",
"windowHeight",
"width",
"height",
"drawingContext",
"deviceOrientation",
"accelerationX",
"accelerationY",
"accelerationZ",
"pAccelerationX",
"pAccelerationY",
"pAccelerationZ",
"rotationX",
"rotationY",
"rotationZ",
"pRotationX",
"pRotationY",
"pRotationZ",
"turnAxis",
"keyIsPressed",
"key",
"keyCode",
"movedX",
"movedY",
"mouseX",
"mouseY",
"pmouseX",
"pmouseY",
"winMouseX",
"winMouseY",
"pwinMouseX",
"pwinMouseY",
"mouseButton",
"mouseIsPressed",
"touches",
"pixels",
"soundOut",
],
};
export default {
functions: ["preload", "setup", "draw", "remove"],
};
import * as acorn from "acorn";
import * as walk from "acorn-walk";
import { generate } from "astring";
import globals from "./globals";
import main from "./main";
/*
This is for use in a project that allows users to write multiple p5.js scripts on the same webpage without needing to worry about converting it to instance mode. E.g. you can have a bunch of "regular p5 scripts" and they automatically get converted to instance mode code that can be used to run multiple scripts on the same page without any issues.
The main idea here is to do two main things:
1. prepend all variables with an underscore so that there are no naming conflicts with any native p5 methods (for example, creating a circle object that conflicts with the circle() method)
2. convert all function calls to use the p5 instance
Usage:
const transpiledInstanceCode = transpileGlobalToInstance(globalCode)
new p5((_p) => {
eval(transpiledInstanceCode);
})
-------------------------
/*
Example input:
```
let circle = {
x: 300,
y: 300,
diameter: 50,
speed: {
x: random(-2, 2),
y: random(-2, 2)
},
color: color(255, 0, 0)
};
function setup() {
createCanvas(600, 600);
}
function draw() {
background(220);
circle.x += circle.speed.x;
circle.y += circle.speed.y;
// Bounce off the walls
if (circle.x - circle.diameter / 2 < 0 || circle.x + circle.diameter / 2 > width) {
circle.speed.x *= -1;
}
if (circle.y - circle.diameter / 2 < 0 || circle.y + circle.diameter / 2 > height) {
circle.speed.y *= -1;
}
fill(circle.color);
noStroke();
circle(circle.x, circle.y, circle.diameter);
}
```
Example output:
```
let _circle = {
x: 300,
y: 300,
diameter: 50,
speed: {
x: _p.random(-2, 2),
y: _p.random(-2, 2)
},
color: _p.color(255, 0, 0)
};
_p.setup = function () {
_p.createCanvas(600, 600);
}
_p.draw = function () {
_p.background(220);
_circle.x += _circle.speed.x;
_circle.y += _circle.speed.y;
if (_circle.x - _circle.diameter / 2 < 0 || _circle.x + _circle.diameter / 2 > _p.width) {
_circle.speed.x *= -1;
}
if (_circle.y - _circle.diameter / 2 < 0 || _circle.y + _circle.diameter / 2 > _p.height) {
_circle.speed.y *= -1;
}
_p.fill(_circle.color);
_p.noStroke();
_p.circle(_circle.x, _circle.y, _circle.diameter);
}
```
*/
export const P5_NAMESPACE = "_p";
export const transpileGlobalToInstance = (
globalCode: string
): string | null => {
try {
const ast = acorn.parse(globalCode, { ecmaVersion: 2020 });
const varMap = new Map();
walk.ancestor(ast, {
VariableDeclaration(node: any) {
node.declarations.forEach((declaration: any) => {
const varName: string = declaration.id.name;
if (!varMap.has(varName)) {
varMap.set(
varName,
varName.startsWith("_") ? varName : `_${varName}`
);
}
declaration.id.name = varMap.get(varName);
});
},
AssignmentExpression(node: any) {
// Handle both left and right sides of assignments
if (node.left.type === "Identifier") {
const lhsName = node.left.name;
if (varMap.has(lhsName)) {
node.left.name = varMap.get(lhsName);
}
}
if (node.right.type === "Identifier") {
const rhsName = node.right.name;
if (varMap.has(rhsName)) {
node.right.name = varMap.get(rhsName);
}
}
},
Identifier(node: any, ancestors: any[]) {
// Determine if the identifier is part of a function call
const isFunctionCall = ancestors.some((ancestor) => {
if (
ancestor.type === "CallExpression" &&
ancestor.callee.type === "Identifier" &&
ancestor.callee.name === node.name
) {
return true;
}
return false;
});
if (
(isFunctionCall && globals.functions.includes(node.name)) ||
globals.constants.includes(node.name)
) {
node.name = `${P5_NAMESPACE}.${node.name}`;
} else if (!isFunctionCall && varMap.has(node.name)) {
node.name = varMap.get(node.name);
}
},
FunctionDeclaration(node: any) {
if (
node.id &&
(globals.functions.includes(node.id.name) ||
main.functions.includes(node.id.name))
) {
node.id.name = varMap.get(node.id.name) || node.id.name;
const assignment = {
type: "AssignmentExpression",
operator: "=",
left: {
type: "MemberExpression",
computed: false,
object: { type: "Identifier", name: P5_NAMESPACE },
property: { type: "Identifier", name: node.id.name },
},
right: {
type: "FunctionExpression",
params: node.params,
body: node.body,
async: node.async,
generator: node.generator,
expression: false,
id: null,
},
};
Object.assign(node, assignment);
}
},
});
return generate(ast);
} catch (error) {
console.error("Error parsing code:", error);
return null;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment