Last active
May 7, 2024 18:23
-
-
Save tangert/cd4ce84e0e7a4d240694d0e0536db27d to your computer and use it in GitHub Desktop.
Transpile p5.js global mode to instance mode
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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", | |
], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default { | |
functions: ["preload", "setup", "draw", "remove"], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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