diff --git a/README.md b/README.md index 8317bb0b98..00bf432475 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ function draw() { - +p5.js sketch of overlapping circles forming tube-like shapes in black outlines on a white background. diff --git a/contributor_docs/ko/method.example.js b/contributor_docs/ko/method.example.js index 72c889878b..782dd40111 100644 --- a/contributor_docs/ko/method.example.js +++ b/contributor_docs/ko/method.example.js @@ -1,5 +1,5 @@ /** - * "이것은 메소드의 인라인 문서 템플릿입니다. 이 템플릿을 사용하려면 큰 따옴표 + * "이것은 메소드의 인라인 문서 템플릿입니다. 이 템플릿을 사용하려면 큰 따옴표 * 사이의 모든 텍스트를 제거하십시오. 메소드에 대한 일부 설명은 여기에 들어갑니다. * 간단한 단어로 함수가 하는 일과 그에 대한 좋은/나쁜 사용 예를 설명하십시오. * 만약 비정상적인 케이스나 경고가 있다면 여기에서 설명해 주세요." @@ -31,7 +31,7 @@ * "두 번째 예시를 명확히 설명하는 줄입니다" */ -// "메소드에 둘 이상의 특징이 있으면, 각 특징은 다음과 같은 파라미터 설명과 함께 +// "메소드에 둘 이상의 특징이 있으면, 각 특징은 다음과 같은 파라미터 설명과 함께 // 자체 블록에 문서화할 수 있습니다." /** * @method "메소드명" @@ -46,11 +46,11 @@ p5.prototype.methodName = function() { // 이 부분은 템플릿을 채운 예시입니다. /** - * background() 함수는 p5.js 캔버스의 배경 색상을 - * 설정합니다. 이 함수는 일반적으로 draw()에서 각 프레임의 시작 부분에 디스플레이 - * 윈도우를 지우는 데 사용되지만, 애니메이션의 첫 번째 프레임에 배경색을 설정하거나 + * background() 함수는 p5.js 캔버스의 배경 색상을 + * 설정합니다. 이 함수는 일반적으로 draw()에서 각 프레임의 시작 부분에 디스플레이 + * 윈도우를 지우는 데 사용되지만, 애니메이션의 첫 번째 프레임에 배경색을 설정하거나 * 배경을 한 번만 설정해야 할 경우 setup() 내에서 사용할 수 있습니다. - * + * * 배경색 기본 설정은 투명입니다. * * @method background @@ -81,7 +81,7 @@ p5.prototype.methodName = function() { /** * @method background * @param {String} 문자열 형태의 색상 설정에 사용할 수 있는 형식: - 정수, rgb(), rgba(), rgb() 비율, rgba() 비율, + 정수, rgb(), rgba(), rgb() 비율, rgba() 비율, 3자리 16진법, 6자리 16진법, * @param {Number} [a] * @chainable diff --git a/contributor_docs/project_wrapups/akshaypadte_gsoc_2020.md b/contributor_docs/project_wrapups/akshaypadte_gsoc_2020.md index 47d140c9cc..782fa9d968 100644 --- a/contributor_docs/project_wrapups/akshaypadte_gsoc_2020.md +++ b/contributor_docs/project_wrapups/akshaypadte_gsoc_2020.md @@ -19,14 +19,14 @@ I had been a p5 user for more than a year and had come to love it and rely on it #### Part 1: Addressing known problems with the FES I kicked off the summer by addressing the issue of speed and size. The FES has a component called `validateParameters()`, responsible for checking if the arguments passed by the user are correct. It does this by matching the arguments against a file auto-generated from the inline docs. Earlier, this file was imported directly into the main library for the FES to use, but it also has a lot of information that is not needed by the FES which increases size unnecessarily. Pre-processing this file to keep only what was needed helped reduce the size of the final built p5.js library by around 25%. -![](https://akshay-fes-gsoc.surge.sh/image1.png) +![Graphical representation of bundle size reduced from 4.7 to 3.6 MB](https://akshay-fes-gsoc.surge.sh/image1.png) Another issue was speed. validateParameters does a lot extra work before the actual function is executed. Sometimes, as seen in [this](https://github.com/processing/p5.js-website/tree/main/src/assets/learn/performance/code/friendly-error-system/) performance test, it would slow down a function by up to 10 times. My initial assumption to speed it up did not work so I played around in chrome dev tools to figure out what was actually happening. I learnt that most of the time was spent just trying to figure out the nearest matching overload intended by the user, and that this entire process happened over and over again if the function was called multiple times with the same arguments. I addressed this with a trie like data structure [[1]](https://github.com/processing/p5.js/blob/8226395d40a9df0113b13e42c983ae578b3856fa/src/core/error_helpers.js#L300), where each node represents an argument. Thus if a function is called again with the same sequence of arguments, we don't need to run the entirety of validateParameters. This not only improved the speed but also prevented the FES from flooding the console on repetitive calls of the same function. There was another issue which caused validateParameters to ignore the last undefined argument passed to function. This sometimes used to cause confusing and inaccurate messages. Fixing this was pretty easy and only involved one line of change. Moving on. There was an issue that if one p5 function called another p5 function, validateParameters would run both times. For example, the function saveJSON() needs to call saveStrings() to do part of its work. It forwards the arguments it receives to saveStrings(). This meant that if arguments were wrong when calling saveJSON(), we used to get two messages: one for saveJSON() and one for saveStrings(). But the user never called the latter in their code! This could lead to confusion. -![](https://akshay-fes-gsoc.surge.sh/image2.png) +![Illustration of two FES messages, only one of which should be shown.](https://akshay-fes-gsoc.surge.sh/image2.png) To fix this, one can take a look at the stack trace. We need to answer "was the most recent p5 function invoked from another p5 function?" If so, we don't need to display a message even if the arguments are wrong. I used another library [stacktrace.js](https://www.stacktracejs.com/), to help with this. Analyzing stack traces was extensively employed later-on in the project as well. We'll come back to it later. As a next step, internationationalization support was added for validateParameters messages and the language of some of the messages was simplified [[2]](https://github.com/processing/p5.js/pull/4629). There were a couple of other small problems that were also fixed in this phase. You can see them in the full list of pull requests. diff --git a/contributor_docs/project_wrapups/slominski_gsoc_2022.md b/contributor_docs/project_wrapups/slominski_gsoc_2022.md index 4d00fc32e2..195f347136 100644 --- a/contributor_docs/project_wrapups/slominski_gsoc_2022.md +++ b/contributor_docs/project_wrapups/slominski_gsoc_2022.md @@ -47,6 +47,6 @@ The tutorials can be found on the p5.js websites Learn section here (to be added And the code and commits for these contributions can be found at (to be added): -##Acknowledgements +## Acknowledgements I want to express my gratitude towards my mentor Kate Hollenbach for her guidance throughout this project, as well as towards the p5.js community for its openness and helpfulness. diff --git a/src/color/p5.Color.js b/src/color/p5.Color.js index 69dab96fb0..3093165ae6 100644 --- a/src/color/p5.Color.js +++ b/src/color/p5.Color.js @@ -37,7 +37,7 @@ const map = (n, start1, stop1, start2, stop2, clamp) => { result = Math.min(result, Math.max(start2, stop2)); } return result; -} +}; const serializationMap = {}; @@ -547,11 +547,11 @@ class Color { let coords = structuredClone(to(this._color, 'srgb').coords); coords.push(this._color.alpha); - const rangeMaxes = maxes.map((v) => { + const rangeMaxes = maxes.map(v => { if(!Array.isArray(v)){ return [0, v]; }else{ - return v + return v; } }); diff --git a/src/core/friendly_errors/sketch_verifier.js b/src/core/friendly_errors/sketch_verifier.js index b8f6e70db6..c56c84b6de 100644 --- a/src/core/friendly_errors/sketch_verifier.js +++ b/src/core/friendly_errors/sketch_verifier.js @@ -24,7 +24,7 @@ const ignoreFunction = [ 'keyPressed', 'keyReleased', 'keyTyped', - 'windowResized', + 'windowResized' // 'name', // 'parent', // 'toString', diff --git a/src/core/internationalization.js b/src/core/internationalization.js index b29ef72989..b20702e4e3 100644 --- a/src/core/internationalization.js +++ b/src/core/internationalization.js @@ -148,7 +148,7 @@ export const initialize = () => { }, backend: { fallback: 'en', - + // ensure that the FES internationalization strings are loaded // from the latest patch of the current minor version of p5.js loadPath: `https://cdn.jsdelivr.net/npm/p5@${ diff --git a/src/image/filterRenderer2D.js b/src/image/filterRenderer2D.js index cd0a97fe0a..af3edbd1cc 100644 --- a/src/image/filterRenderer2D.js +++ b/src/image/filterRenderer2D.js @@ -257,9 +257,9 @@ class FilterRenderer2D { const identityMatrix = [1, 0, 0, 0, - 0, 1, 0, 0, - 0, 0, 1, 0, - 0, 0, 0, 1]; + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1]; this._shader.setUniform('uModelViewMatrix', identityMatrix); this._shader.setUniform('uProjectionMatrix', identityMatrix); diff --git a/src/image/loading_displaying.js b/src/image/loading_displaying.js index 8487dde0a5..289f27c518 100644 --- a/src/image/loading_displaying.js +++ b/src/image/loading_displaying.js @@ -273,7 +273,7 @@ function loadingDisplaying(p5, fn){ const silent = (options && options.silent) || false; const notificationDuration = (options && options.notificationDuration) || 0; const notificationID = (options && options.notificationID) || 'progressBar'; - const resetAnimation = (options && options.reset !== undefined) ? options.reset : true; + const resetAnimation = (options && options.reset !== undefined) ? options.reset : true; // if arguments in the options object are not correct, cancel operation if (typeof delay !== 'number') { throw TypeError('Delay parameter must be a number'); @@ -328,7 +328,7 @@ function loadingDisplaying(p5, fn){ // initialize variables for the frames processing let frameIterator; let totalNumberOfFrames; - + if (resetAnimation) { frameIterator = nFramesDelay; this.frameCount = frameIterator; @@ -379,7 +379,7 @@ function loadingDisplaying(p5, fn){ // // Waiting on this empty promise means we'll continue as soon as setup // finishes without waiting for another frame. - await new Promise(requestAnimationFrame) + await new Promise(requestAnimationFrame); while (frameIterator < totalNumberOfFrames) { /* diff --git a/src/shape/curves.js b/src/shape/curves.js index b83a9dbfe0..d8c1c4a3a2 100644 --- a/src/shape/curves.js +++ b/src/shape/curves.js @@ -517,22 +517,22 @@ function curves(p5, fn){ * function setup() { * createCanvas(200, 200); * background(245); - * + * * // Ensure the curve includes both end spans p0->p1 and p2->p3 * splineProperty('ends', INCLUDE); - * + * * // Control / anchor points * const p0 = createVector(30, 160); * const p1 = createVector(60, 40); * const p2 = createVector(140, 40); * const p3 = createVector(170, 160); - * + * * // Draw the spline that passes through ALL four points (INCLUDE) * noFill(); * stroke(0); * strokeWeight(2); * spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - * + * * // Draw markers + labels * fill(255); * stroke(0); @@ -541,19 +541,19 @@ function curves(p5, fn){ * circle(p1.x, p1.y, r); * circle(p2.x, p2.y, r); * circle(p3.x, p3.y, r); - * + * * noStroke(); * fill(0); * text('p0', p0.x - 14, p0.y + 14); * text('p1', p1.x - 14, p1.y - 8); * text('p2', p2.x + 4, p2.y - 8); * text('p3', p3.x + 4, p3.y + 14); - * + * * describe('A black Catmull-Rom spline passes through p0, p1, p2, p3 with endpoints included.'); * } * * - * + * *
* * function setup() { @@ -848,45 +848,45 @@ function curves(p5, fn){ * } * *
- * + * *
* * let p0, p1, p2, p3; - * + * * function setup() { * createCanvas(200, 200); * splineProperty('ends', INCLUDE); // make endpoints part of the curve - * + * * // Four points forming a gentle arch * p0 = createVector(30, 160); * p1 = createVector(60, 50); * p2 = createVector(140, 50); * p3 = createVector(170, 160); - * + * * describe('Black spline through p0–p3. A red dot marks the location at parameter t on p1->p2 using splinePoint.'); * } - * + * * function draw() { * background(245); - * + * * // Draw the spline for context * noFill(); * stroke(0); * strokeWeight(2); * spline(p0.x, p0.y, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y); - * + * * // Map mouse X to t in [0, 1] (span p1->p2) * let t = constrain(map(mouseX, 0, width, 0, 1), 0, 1); - * + * * // Evaluate the curve point by axis (splinePoint works one axis at a time) * let x = splinePoint(p0.x, p1.x, p2.x, p3.x, t); * let y = splinePoint(p0.y, p1.y, p2.y, p3.y, t); - * + * * // Marker at the evaluated position * noStroke(); * fill('red'); * circle(x, y, 8); - * + * * // Draw control/anchor points * stroke(0); * strokeWeight(1); @@ -896,7 +896,7 @@ function curves(p5, fn){ * circle(p1.x, p1.y, r); * circle(p2.x, p2.y, r); * circle(p3.x, p3.y, r); - * + * * // Labels + UI hint * noStroke(); * fill(20); @@ -909,7 +909,7 @@ function curves(p5, fn){ * } * *
- * + * */ fn.splinePoint = function(a, b, c, d, t) { diff --git a/src/shape/custom_shapes.js b/src/shape/custom_shapes.js index f9b2da245d..076c7987cb 100644 --- a/src/shape/custom_shapes.js +++ b/src/shape/custom_shapes.js @@ -1823,7 +1823,7 @@ function customShapes(p5, fn) { * } * * - * + * * @example *
* @@ -1833,44 +1833,44 @@ function customShapes(p5, fn) { * let vertexD; * let vertexE; * let vertexF; - * + * * let markerRadius; - * + * * let vectorAB; * let vectorFE; - * + * * let endOfTangentB; * let endOfTangentE; - * + * * function setup() { * createCanvas(100, 100); - * + * * // Initialize variables * // Adjusting vertices A and F affects the slopes at B and E - * + * * vertexA = createVector(35, 85); * vertexB = createVector(25, 70); * vertexC = createVector(30, 30); * vertexD = createVector(70, 30); * vertexE = createVector(75, 70); * vertexF = createVector(65, 85); - * + * * markerRadius = 4; - * + * * vectorAB = p5.Vector.sub(vertexB, vertexA); * vectorFE = p5.Vector.sub(vertexE, vertexF); - * + * * endOfTangentB = p5.Vector.add(vertexC, vectorAB); * endOfTangentE = p5.Vector.add(vertexD, vectorFE); - * + * * splineProperty(`ends`, EXCLUDE); - * + * * // Draw figure - * + * * background(220); - * + * * noFill(); - * + * * beginShape(); * splineVertex(vertexA.x, vertexA.y); * splineVertex(vertexB.x, vertexB.y); @@ -1879,15 +1879,15 @@ function customShapes(p5, fn) { * splineVertex(vertexE.x, vertexE.y); * splineVertex(vertexF.x, vertexF.y); * endShape(); - * + * * stroke('red'); * line(vertexA.x, vertexA.y, vertexC.x, vertexC.y); * line(vertexB.x, vertexB.y, endOfTangentB.x, endOfTangentB.y); - * + * * stroke('blue'); * line(vertexD.x, vertexD.y, vertexF.x, vertexF.y); * line(vertexE.x, vertexE.y, endOfTangentE.x, endOfTangentE.y); - * + * * fill('white'); * stroke('black'); * circle(vertexA.x, vertexA.y, markerRadius); @@ -1896,7 +1896,7 @@ function customShapes(p5, fn) { * circle(vertexD.x, vertexD.y, markerRadius); * circle(vertexE.x, vertexE.y, markerRadius); * circle(vertexF.x, vertexF.y, markerRadius); - * + * * fill('black'); * noStroke(); * text('A', vertexA.x - 15, vertexA.y + 5); @@ -1905,7 +1905,7 @@ function customShapes(p5, fn) { * text('D', vertexD.x - 5, vertexD.y - 5); * text('E', vertexE.x + 5, vertexE.y + 5); * text('F', vertexF.x + 5, vertexF.y + 5); - * + * * describe('On a gray background, a black spline passes through vertices A, B, C, D, E, and F, shown as white circles. A red line segment joining vertices A and C has the same slope as the red tangent segment at B. Similarly, the blue line segment joining vertices D and F has the same slope as the blue tangent at E.'); * } * @@ -2069,7 +2069,7 @@ function customShapes(p5, fn) { * spline(25, 46, 93, 44, 93, 81, 35, 85); * ``` * - * + * * In all cases, the splines in p5.js are cardinal splines. * When tightness is 0, these splines are often known as * Catmull-Rom splines @@ -2185,9 +2185,9 @@ function customShapes(p5, fn) { * } * *
- * + * * @example - * + * *
* * let vertexA; @@ -2196,44 +2196,44 @@ function customShapes(p5, fn) { * let vertexD; * let vertexE; * let vertexF; - * + * * let markerRadius; - * + * * let vectorAB; * let vectorFE; - * + * * let endOfTangentB; * let endOfTangentE; - * + * * function setup() { * createCanvas(100, 100); - * + * * // Initialize variables * // Adjusting vertices A and F affects the slopes at B and E - * + * * vertexA = createVector(35, 85); * vertexB = createVector(25, 70); * vertexC = createVector(30, 30); * vertexD = createVector(70, 30); * vertexE = createVector(75, 70); * vertexF = createVector(65, 85); - * + * * markerRadius = 4; - * + * * vectorAB = p5.Vector.sub(vertexB, vertexA); * vectorFE = p5.Vector.sub(vertexE, vertexF); - * + * * endOfTangentB = p5.Vector.add(vertexC, vectorAB); * endOfTangentE = p5.Vector.add(vertexD, vectorFE); - * + * * splineProperty(`ends`, EXCLUDE); - * + * * // Draw figure - * + * * background(220); - * + * * noFill(); - * + * * beginShape(); * splineVertex(vertexA.x, vertexA.y); * splineVertex(vertexB.x, vertexB.y); @@ -2242,15 +2242,15 @@ function customShapes(p5, fn) { * splineVertex(vertexE.x, vertexE.y); * splineVertex(vertexF.x, vertexF.y); * endShape(); - * + * * stroke('red'); * line(vertexA.x, vertexA.y, vertexC.x, vertexC.y); * line(vertexB.x, vertexB.y, endOfTangentB.x, endOfTangentB.y); - * + * * stroke('blue'); * line(vertexD.x, vertexD.y, vertexF.x, vertexF.y); * line(vertexE.x, vertexE.y, endOfTangentE.x, endOfTangentE.y); - * + * * fill('white'); * stroke('black'); * circle(vertexA.x, vertexA.y, markerRadius); @@ -2259,7 +2259,7 @@ function customShapes(p5, fn) { * circle(vertexD.x, vertexD.y, markerRadius); * circle(vertexE.x, vertexE.y, markerRadius); * circle(vertexF.x, vertexF.y, markerRadius); - * + * * fill('black'); * noStroke(); * text('A', vertexA.x - 15, vertexA.y + 5); @@ -2268,12 +2268,12 @@ function customShapes(p5, fn) { * text('D', vertexD.x - 5, vertexD.y - 5); * text('E', vertexE.x + 5, vertexE.y + 5); * text('F', vertexF.x + 5, vertexF.y + 5); - * + * * describe('On a gray background, a black spline passes through vertices A, B, C, D, E, and F, shown as white circles. A red line segment joining vertices A and C has the same slope as the red tangent segment at B. Similarly, the blue line segment joining vertices D and F has the same slope as the blue tangent at E.'); * } * *
- * + * */ /** diff --git a/src/strands/ir_builders.js b/src/strands/ir_builders.js index 127d19e235..83df61fce5 100644 --- a/src/strands/ir_builders.js +++ b/src/strands/ir_builders.js @@ -1,7 +1,7 @@ -import * as DAG from './ir_dag' -import * as CFG from './ir_cfg' -import * as FES from './strands_FES' -import { NodeType, OpCode, BaseType, DataType, BasePriority, OpCodeToSymbol, typeEquals, } from './ir_types'; +import * as DAG from './ir_dag'; +import * as CFG from './ir_cfg'; +import * as FES from './strands_FES'; +import { NodeType, OpCode, BaseType, DataType, BasePriority, OpCodeToSymbol, typeEquals } from './ir_types'; import { createStrandsNode, StrandsNode } from './strands_node'; import { strandsBuiltinFunctions } from './strands_builtins'; @@ -9,10 +9,10 @@ import { strandsBuiltinFunctions } from './strands_builtins'; // Builders for node graphs ////////////////////////////////////////////// export function scalarLiteralNode(strandsContext, typeInfo, value) { - const { cfg, dag } = strandsContext + const { cfg, dag } = strandsContext; let { dimension, baseType } = typeInfo; if (dimension !== 1) { - FES.internalError('Created a scalar literal node with dimension > 1.') + FES.internalError('Created a scalar literal node with dimension > 1.'); } const nodeData = DAG.createNodeData({ nodeType: NodeType.LITERAL, @@ -33,7 +33,7 @@ export function variableNode(strandsContext, typeInfo, identifier) { dimension, baseType, identifier - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension }; @@ -56,7 +56,7 @@ export function unaryOpNode(strandsContext, nodeOrValue, opCode) { dependsOn, baseType: dag.baseTypes[node.id], dimension: node.dimension - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension: node.dimension }; @@ -92,9 +92,9 @@ export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode) cast.toType.dimension = leftType.dimension; } else { - FES.userError("type error", `You have tried to perform a binary operation:\n`+ + FES.userError('type error', 'You have tried to perform a binary operation:\n'+ `${leftType.baseType+leftType.dimension} ${OpCodeToSymbol[opCode]} ${rightType.baseType+rightType.dimension}\n` + - `It's only possible to operate on two nodes with the same dimension, or a scalar value and a vector.` + 'It\'s only possible to operate on two nodes with the same dimension, or a scalar value and a vector.' ); } const l = primitiveConstructorNode(strandsContext, cast.toType, leftStrandsNode); @@ -142,7 +142,7 @@ export function binaryOpNode(strandsContext, leftStrandsNode, rightArg, opCode) opCode, dependsOn: [finalLeftNodeID, finalRightNodeID], baseType: cast.toType.baseType, - dimension: cast.toType.dimension, + dimension: cast.toType.dimension }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); @@ -156,7 +156,7 @@ export function memberAccessNode(strandsContext, parentNode, componentNode, memb opCode: OpCode.Binary.MEMBER_ACCESS, dimension: memberTypeInfo.dimension, baseType: memberTypeInfo.baseType, - dependsOn: [parentNode.id, componentNode.id], + dependsOn: [parentNode.id, componentNode.id] }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); @@ -164,7 +164,7 @@ export function memberAccessNode(strandsContext, parentNode, componentNode, memb } export function structInstanceNode(strandsContext, structTypeInfo, identifier, dependsOn) { - const { cfg, dag, } = strandsContext; + const { cfg, dag } = strandsContext; if (dependsOn.length === 0) { for (const prop of structTypeInfo.properties) { const typeInfo = prop.dataType; @@ -172,7 +172,7 @@ export function structInstanceNode(strandsContext, structTypeInfo, identifier, d nodeType: NodeType.VARIABLE, baseType: typeInfo.baseType, dimension: typeInfo.dimension, - identifier: `${identifier}.${prop.name}`, + identifier: `${identifier}.${prop.name}` }); const componentID = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, componentID); @@ -186,7 +186,7 @@ export function structInstanceNode(strandsContext, structTypeInfo, identifier, d baseType: structTypeInfo.typeName, identifier, dependsOn - }) + }); const structID = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, structID); @@ -238,8 +238,8 @@ function mapPrimitiveDepsToIDs(strandsContext, typeInfo, dependsOn) { const inferredTypeInfo = { dimension, baseType, - priority: BasePriority[baseType], - } + priority: BasePriority[baseType] + }; return { originalNodeID, mappedDependencies, inferredTypeInfo }; } @@ -279,7 +279,7 @@ export function structConstructorNode(strandsContext, structTypeInfo, rawUserArg if (!(rawUserArgs.length === properties.length)) { FES.userError('type error', `You've tried to construct a ${structTypeInfo.typeName} struct with ${rawUserArgs.length} properties, but it expects ${properties.length} properties.\n` + - `The properties it expects are:\n` + + 'The properties it expects are:\n' + `${properties.map(prop => prop.name + ' ' + prop.DataType.baseType + prop.DataType.dimension)}` ); } @@ -314,23 +314,23 @@ export function functionCallNode( strandsContext, functionName, rawUserArgs, - { overloads: rawOverloads } = {}, + { overloads: rawOverloads } = {} ) { const { cfg, dag } = strandsContext; const overloads = rawOverloads || strandsBuiltinFunctions[functionName]; - const preprocessedArgs = rawUserArgs.map((rawUserArg) => mapPrimitiveDepsToIDs(strandsContext, DataType.defer, rawUserArg)); + const preprocessedArgs = rawUserArgs.map(rawUserArg => mapPrimitiveDepsToIDs(strandsContext, DataType.defer, rawUserArg)); const matchingArgsCounts = overloads.filter(overload => overload.params.length === preprocessedArgs.length); if (matchingArgsCounts.length === 0) { const argsLengthSet = new Set(); const argsLengthArr = []; - overloads.forEach((overload) => argsLengthSet.add(overload.params.length)); - argsLengthSet.forEach((len) => argsLengthArr.push(`${len}`)); + overloads.forEach(overload => argsLengthSet.add(overload.params.length)); + argsLengthSet.forEach(len => argsLengthArr.push(`${len}`)); const argsLengthStr = argsLengthArr.join(', or '); - FES.userError("parameter validation error",`Function '${functionName}' has ${overloads.length} variants which expect ${argsLengthStr} arguments, but ${preprocessedArgs.length} arguments were provided.`); + FES.userError('parameter validation error',`Function '${functionName}' has ${overloads.length} variants which expect ${argsLengthStr} arguments, but ${preprocessedArgs.length} arguments were provided.`); } - const isGeneric = (T) => T.dimension === null; + const isGeneric = T => T.dimension === null; let bestOverload = null; let bestScore = 0; let inferredReturnType = null; @@ -353,7 +353,7 @@ export function functionCallNode( if (inferredDimension !== argType.dimension && !(argType.dimension === 1 && inferredDimension >= 1) - ) { + ) { isValid = false; } dimension = inferredDimension; @@ -376,7 +376,7 @@ export function functionCallNode( if (isValid && (!bestOverload || similarity > bestScore)) { bestOverload = overload; bestScore = similarity; - inferredReturnType = {...overload.returnType }; + inferredReturnType = { ...overload.returnType }; if (isGeneric(inferredReturnType)) { inferredReturnType.dimension = inferredDimension; } @@ -411,7 +411,7 @@ export function functionCallNode( dependsOn, baseType: inferredReturnType.baseType, dimension: inferredReturnType.dimension - }) + }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); return { id, dimension: inferredReturnType.dimension }; @@ -437,7 +437,7 @@ export function swizzleNode(strandsContext, parentNode, swizzle) { dimension: swizzle.length, opCode: OpCode.Unary.SWIZZLE, dependsOn: [parentNode.id], - swizzle, + swizzle }); const id = DAG.getOrCreateNode(dag, nodeData); CFG.recordInBasicBlock(cfg, cfg.currentBlock, id); @@ -445,107 +445,107 @@ export function swizzleNode(strandsContext, parentNode, swizzle) { } export function swizzleTrap(id, dimension, strandsContext, onRebind) { - const swizzleSets = [ - ['x', 'y', 'z', 'w'], - ['r', 'g', 'b', 'a'], - ['s', 't', 'p', 'q'] - ].map(s => s.slice(0, dimension)); - const trap = { - get(target, property, receiver) { - if (property in target) { - return Reflect.get(...arguments); - } else { - for (const set of swizzleSets) { - if ([...property.toString()].every(char => set.includes(char))) { - const swizzle = [...property].map(char => { - const index = set.indexOf(char); - return swizzleSets[0][index]; - }).join(''); - const node = swizzleNode(strandsContext, target, swizzle); - return createStrandsNode(node.id, node.dimension, strandsContext); - } + const swizzleSets = [ + ['x', 'y', 'z', 'w'], + ['r', 'g', 'b', 'a'], + ['s', 't', 'p', 'q'] + ].map(s => s.slice(0, dimension)); + const trap = { + get(target, property, receiver) { + if (property in target) { + return Reflect.get(...arguments); + } else { + for (const set of swizzleSets) { + if ([...property.toString()].every(char => set.includes(char))) { + const swizzle = [...property].map(char => { + const index = set.indexOf(char); + return swizzleSets[0][index]; + }).join(''); + const node = swizzleNode(strandsContext, target, swizzle); + return createStrandsNode(node.id, node.dimension, strandsContext); } } + } }, - set(target, property, value, receiver) { - for (const swizzleSet of swizzleSets) { - const chars = [...property]; - const valid = + set(target, property, value, receiver) { + for (const swizzleSet of swizzleSets) { + const chars = [...property]; + const valid = chars.every(c => swizzleSet.includes(c)) && new Set(chars).size === chars.length && target.dimension >= chars.length; - if (!valid) continue; + if (!valid) continue; - const dim = target.dimension; + const dim = target.dimension; - // lanes are the underlying values of the target vector - // e.g. lane 0 holds the value aliased by 'x', 'r', and 's' - // the lanes array is in the 'correct' order - const lanes = new Array(dim); - for (let i = 0; i < dim; i++) { - const { id, dimension } = swizzleNode(strandsContext, target, 'xyzw'[i]); - lanes[i] = createStrandsNode(id, dimension, strandsContext); - } + // lanes are the underlying values of the target vector + // e.g. lane 0 holds the value aliased by 'x', 'r', and 's' + // the lanes array is in the 'correct' order + const lanes = new Array(dim); + for (let i = 0; i < dim; i++) { + const { id, dimension } = swizzleNode(strandsContext, target, 'xyzw'[i]); + lanes[i] = createStrandsNode(id, dimension, strandsContext); + } - // The scalars array contains the individual components of the users values. - // This may not be the most efficient way, as we swizzle each component individually, - // so that .xyz becomes .x, .y, .z - let scalars = []; - if (value instanceof StrandsNode) { - if (value.dimension === 1) { - scalars = Array(chars.length).fill(value); - } else if (value.dimension === chars.length) { - for (let k = 0; k < chars.length; k++) { - const { id, dimension } = swizzleNode(strandsContext, value, 'xyzw'[k]); - scalars.push(createStrandsNode(id, dimension, strandsContext)); + // The scalars array contains the individual components of the users values. + // This may not be the most efficient way, as we swizzle each component individually, + // so that .xyz becomes .x, .y, .z + let scalars = []; + if (value instanceof StrandsNode) { + if (value.dimension === 1) { + scalars = Array(chars.length).fill(value); + } else if (value.dimension === chars.length) { + for (let k = 0; k < chars.length; k++) { + const { id, dimension } = swizzleNode(strandsContext, value, 'xyzw'[k]); + scalars.push(createStrandsNode(id, dimension, strandsContext)); + } + } else { + FES.userError('type error', `Swizzle assignment: RHS vector does not match LHS vector (need ${chars.length}, got ${value.dimension}).`); } + } else if (Array.isArray(value)) { + const flat = value.flat(Infinity); + if (flat.length === 1) { + scalars = Array(chars.length).fill(flat[0]); + } else if (flat.length === chars.length) { + scalars = flat; + } else { + FES.userError('type error', `Swizzle assignment: RHS length ${flat.length} does not match ${chars.length}.`); + } + } else if (typeof value === 'number') { + scalars = Array(chars.length).fill(value); } else { - FES.userError('type error', `Swizzle assignment: RHS vector does not match LHS vector (need ${chars.length}, got ${value.dimension}).`); - } - } else if (Array.isArray(value)) { - const flat = value.flat(Infinity); - if (flat.length === 1) { - scalars = Array(chars.length).fill(flat[0]); - } else if (flat.length === chars.length) { - scalars = flat; - } else { - FES.userError('type error', `Swizzle assignment: RHS length ${flat.length} does not match ${chars.length}.`); + FES.userError('type error', `Unsupported RHS for swizzle assignment: ${value}`); } - } else if (typeof value === 'number') { - scalars = Array(chars.length).fill(value); - } else { - FES.userError('type error', `Unsupported RHS for swizzle assignment: ${value}`); - } - // The canonical index refers to the actual value's position in the vector lanes - // i.e. we are finding (3,2,1) from .zyx - // We set the correct value in the lanes array - for (let j = 0; j < chars.length; j++) { - const canonicalIndex = swizzleSet.indexOf(chars[j]); - lanes[canonicalIndex] = scalars[j]; - } - - const orig = DAG.getNodeDataFromID(strandsContext.dag, target.id); - const baseType = orig?.baseType ?? BaseType.FLOAT; - const { id: newID } = primitiveConstructorNode( - strandsContext, - { baseType, dimension: dim }, - lanes - ); - - target.id = newID; + // The canonical index refers to the actual value's position in the vector lanes + // i.e. we are finding (3,2,1) from .zyx + // We set the correct value in the lanes array + for (let j = 0; j < chars.length; j++) { + const canonicalIndex = swizzleSet.indexOf(chars[j]); + lanes[canonicalIndex] = scalars[j]; + } - // If we swizzle assign on a struct component i.e. - // inputs.position.rg = [1, 2] - // The onRebind callback will update the structs components so that it refers to the new values, - // and make a new ID for the struct with these new values - if (typeof onRebind === 'function') { - onRebind(newID); + const orig = DAG.getNodeDataFromID(strandsContext.dag, target.id); + const baseType = orig?.baseType ?? BaseType.FLOAT; + const { id: newID } = primitiveConstructorNode( + strandsContext, + { baseType, dimension: dim }, + lanes + ); + + target.id = newID; + + // If we swizzle assign on a struct component i.e. + // inputs.position.rg = [1, 2] + // The onRebind callback will update the structs components so that it refers to the new values, + // and make a new ID for the struct with these new values + if (typeof onRebind === 'function') { + onRebind(newID); + } + return true; } - return true; + return Reflect.set(...arguments); } - return Reflect.set(...arguments); - } }; return trap; } diff --git a/src/strands/ir_cfg.js b/src/strands/ir_cfg.js index cf25a23d53..9d50d27f34 100644 --- a/src/strands/ir_cfg.js +++ b/src/strands/ir_cfg.js @@ -1,5 +1,5 @@ -import { BlockTypeToName } from "./ir_types"; -import * as FES from './strands_FES' +import { BlockTypeToName } from './ir_types'; +import * as FES from './strands_FES'; // Todo: remove edges to simplify. Block order is always ordered already. @@ -15,7 +15,7 @@ export function createControlFlowGraph() { blockStack: [], blockOrder: [], blockConditions: {}, - currentBlock: -1, + currentBlock: -1 }; } @@ -67,8 +67,8 @@ export function getBlockDataFromID(graph, id) { blockType: graph.blockTypes[id], incomingEdges: graph.incomingEdges[id], outgoingEdges: graph.outgoingEdges[id], - blockInstructions: graph.blockInstructions[id], - } + blockInstructions: graph.blockInstructions[id] + }; } export function printBlockData(graph, id) { diff --git a/src/strands/ir_dag.js b/src/strands/ir_dag.js index 45eadb473a..5da23432a6 100644 --- a/src/strands/ir_dag.js +++ b/src/strands/ir_dag.js @@ -19,7 +19,7 @@ export function createDirectedAcyclicGraph() { dependsOn: [], usedBy: [], statementTypes: [], - swizzles: [], + swizzles: [] }; return graph; @@ -30,11 +30,11 @@ export function getOrCreateNode(graph, node) { // const existing = graph.cache.get(key); // if (existing !== undefined) { - // return existing; + // return existing; // } else { - const id = createNode(graph, node); - // graph.cache.set(key, id); - return id; + const id = createNode(graph, node); + // graph.cache.set(key, id); + return id; // } } @@ -50,7 +50,7 @@ export function createNodeData(data = {}) { swizzle: data.swizzle ?? null, dependsOn: Array.isArray(data.dependsOn) ? data.dependsOn : [], usedBy: Array.isArray(data.usedBy) ? data.usedBy : [], - phiBlocks: Array.isArray(data.phiBlocks) ? data.phiBlocks : [], + phiBlocks: Array.isArray(data.phiBlocks) ? data.phiBlocks : [] }; validateNode(node); return node; @@ -69,15 +69,15 @@ export function getNodeDataFromID(graph, id) { dimension: graph.dimensions[id], baseType: graph.baseTypes[id], statementType: graph.statementTypes[id], - swizzle: graph.swizzles[id], - } + swizzle: graph.swizzles[id] + }; } export function extractNodeTypeInfo(dag, nodeID) { return { baseType: dag.baseTypes[nodeID], dimension: dag.dimensions[nodeID], - priority: BasePriority[dag.baseTypes[nodeID]], + priority: BasePriority[dag.baseTypes[nodeID]] }; } @@ -93,7 +93,7 @@ function createNode(graph, node) { graph.dependsOn[id] = node.dependsOn.slice(); graph.usedBy[id] = node.usedBy; graph.phiBlocks[id] = node.phiBlocks.slice(); - graph.baseTypes[id] = node.baseType + graph.baseTypes[id] = node.baseType; graph.dimensions[id] = node.dimension; graph.statementTypes[id] = node.statementType; graph.swizzles[id] = node.swizzle; @@ -116,7 +116,7 @@ function validateNode(node){ const nodeType = node.nodeType; const requiredFields = NodeTypeRequiredFields[nodeType]; if (requiredFields.length === 2) { - FES.internalError(`Required fields for node type '${NodeTypeToName[nodeType]}' not defined. Please add them to the utils.js file in p5.strands!`) + FES.internalError(`Required fields for node type '${NodeTypeToName[nodeType]}' not defined. Please add them to the utils.js file in p5.strands!`); } const missingFields = []; for (const field of requiredFields) { diff --git a/src/strands/ir_types.js b/src/strands/ir_types.js index 966e9d2ec2..faff18fe78 100644 --- a/src/strands/ir_types.js +++ b/src/strands/ir_types.js @@ -9,34 +9,34 @@ export const NodeType = { STRUCT: 'struct', PHI: 'phi', STATEMENT: 'statement', - ASSIGNMENT: 'assignment', + ASSIGNMENT: 'assignment' }; export const NodeTypeToName = Object.fromEntries( Object.entries(NodeType).map(([key, val]) => [val, key]) ); export const NodeTypeRequiredFields = { - [NodeType.OPERATION]: ["opCode", "dependsOn", "dimension", "baseType"], - [NodeType.LITERAL]: ["value", "dimension", "baseType"], - [NodeType.VARIABLE]: ["identifier", "dimension", "baseType"], - [NodeType.CONSTANT]: ["value", "dimension", "baseType"], - [NodeType.STRUCT]: [""], - [NodeType.PHI]: ["dependsOn", "phiBlocks", "dimension", "baseType"], - [NodeType.STATEMENT]: ["statementType"], - [NodeType.ASSIGNMENT]: ["dependsOn"] + [NodeType.OPERATION]: ['opCode', 'dependsOn', 'dimension', 'baseType'], + [NodeType.LITERAL]: ['value', 'dimension', 'baseType'], + [NodeType.VARIABLE]: ['identifier', 'dimension', 'baseType'], + [NodeType.CONSTANT]: ['value', 'dimension', 'baseType'], + [NodeType.STRUCT]: [''], + [NodeType.PHI]: ['dependsOn', 'phiBlocks', 'dimension', 'baseType'], + [NodeType.STATEMENT]: ['statementType'], + [NodeType.ASSIGNMENT]: ['dependsOn'] }; export const StatementType = { DISCARD: 'discard', BREAK: 'break', EXPRESSION: 'expression', // Used when we want to output a single expression as a statement, e.g. a for loop condition - EMPTY: 'empty', // Used for empty statements like ; in for loops + EMPTY: 'empty' // Used for empty statements like ; in for loops }; export const BaseType = { - FLOAT: "float", - INT: "int", - BOOL: "bool", - MAT: "mat", - DEFER: "defer", - SAMPLER2D: "sampler2D", + FLOAT: 'float', + INT: 'int', + BOOL: 'bool', + MAT: 'mat', + DEFER: 'defer', + SAMPLER2D: 'sampler2D' }; export const BasePriority = { [BaseType.FLOAT]: 3, @@ -44,39 +44,39 @@ export const BasePriority = { [BaseType.BOOL]: 1, [BaseType.MAT]: 0, [BaseType.DEFER]: -1, - [BaseType.SAMPLER2D]: -10, + [BaseType.SAMPLER2D]: -10 }; export const DataType = { - float1: { fnName: "float", baseType: BaseType.FLOAT, dimension:1, priority: 3, }, - float2: { fnName: "vec2", baseType: BaseType.FLOAT, dimension:2, priority: 3, }, - float3: { fnName: "vec3", baseType: BaseType.FLOAT, dimension:3, priority: 3, }, - float4: { fnName: "vec4", baseType: BaseType.FLOAT, dimension:4, priority: 3, }, - int1: { fnName: "int", baseType: BaseType.INT, dimension:1, priority: 2, }, - int2: { fnName: "ivec2", baseType: BaseType.INT, dimension:2, priority: 2, }, - int3: { fnName: "ivec3", baseType: BaseType.INT, dimension:3, priority: 2, }, - int4: { fnName: "ivec4", baseType: BaseType.INT, dimension:4, priority: 2, }, - bool1: { fnName: "bool", baseType: BaseType.BOOL, dimension:1, priority: 1, }, - bool2: { fnName: "bvec2", baseType: BaseType.BOOL, dimension:2, priority: 1, }, - bool3: { fnName: "bvec3", baseType: BaseType.BOOL, dimension:3, priority: 1, }, - bool4: { fnName: "bvec4", baseType: BaseType.BOOL, dimension:4, priority: 1, }, - mat2: { fnName: "mat2x2", baseType: BaseType.MAT, dimension:2, priority: 0, }, - mat3: { fnName: "mat3x3", baseType: BaseType.MAT, dimension:3, priority: 0, }, - mat4: { fnName: "mat4x4", baseType: BaseType.MAT, dimension:4, priority: 0, }, + float1: { fnName: 'float', baseType: BaseType.FLOAT, dimension:1, priority: 3 }, + float2: { fnName: 'vec2', baseType: BaseType.FLOAT, dimension:2, priority: 3 }, + float3: { fnName: 'vec3', baseType: BaseType.FLOAT, dimension:3, priority: 3 }, + float4: { fnName: 'vec4', baseType: BaseType.FLOAT, dimension:4, priority: 3 }, + int1: { fnName: 'int', baseType: BaseType.INT, dimension:1, priority: 2 }, + int2: { fnName: 'ivec2', baseType: BaseType.INT, dimension:2, priority: 2 }, + int3: { fnName: 'ivec3', baseType: BaseType.INT, dimension:3, priority: 2 }, + int4: { fnName: 'ivec4', baseType: BaseType.INT, dimension:4, priority: 2 }, + bool1: { fnName: 'bool', baseType: BaseType.BOOL, dimension:1, priority: 1 }, + bool2: { fnName: 'bvec2', baseType: BaseType.BOOL, dimension:2, priority: 1 }, + bool3: { fnName: 'bvec3', baseType: BaseType.BOOL, dimension:3, priority: 1 }, + bool4: { fnName: 'bvec4', baseType: BaseType.BOOL, dimension:4, priority: 1 }, + mat2: { fnName: 'mat2x2', baseType: BaseType.MAT, dimension:2, priority: 0 }, + mat3: { fnName: 'mat3x3', baseType: BaseType.MAT, dimension:3, priority: 0 }, + mat4: { fnName: 'mat4x4', baseType: BaseType.MAT, dimension:4, priority: 0 }, defer: { fnName: null, baseType: BaseType.DEFER, dimension: null, priority: -1 }, - sampler2D: { fnName: "sampler2D", baseType: BaseType.SAMPLER2D, dimension: 1, priority: -10 }, -} + sampler2D: { fnName: 'sampler2D', baseType: BaseType.SAMPLER2D, dimension: 1, priority: -10 } +}; export const structType = function (hookType) { let T = hookType.type === undefined ? hookType : hookType.type; const structType = { name: hookType.name, properties: [], - typeName: T.typeName, + typeName: T.typeName }; // TODO: handle struct properties that are themselves structs for (const prop of T.properties) { const propType = TypeInfoFromGLSLName[prop.type.typeName]; structType.properties.push( - {name: prop.name, dataType: propType } + { name: prop.name, dataType: propType } ); } return structType; @@ -115,8 +115,8 @@ export function isNativeType(typeName) { export const GenType = { FLOAT: { baseType: BaseType.FLOAT, dimension: null, priority: 3 }, INT: { baseType: BaseType.INT, dimension: null, priority: 2 }, - BOOL: { baseType: BaseType.BOOL, dimension: null, priority: 1 }, -} + BOOL: { baseType: BaseType.BOOL, dimension: null, priority: 1 } +}; export function typeEquals(nodeA, nodeB) { return (nodeA.dimension === nodeB.dimension) && (nodeA.baseType === nodeB.baseType); } @@ -140,43 +140,43 @@ export const OpCode = { LESS_EQUAL: 10, LOGICAL_AND: 11, LOGICAL_OR: 12, - MEMBER_ACCESS: 13, + MEMBER_ACCESS: 13 }, Unary: { LOGICAL_NOT: 100, NEGATE: 101, PLUS: 102, - SWIZZLE: 103, + SWIZZLE: 103 }, Nary: { FUNCTION_CALL: 200, - CONSTRUCTOR: 201, + CONSTRUCTOR: 201 }, ControlFlow: { RETURN: 300, JUMP: 301, BRANCH_IF_FALSE: 302, DISCARD: 303, - BREAK: 304, + BREAK: 304 } }; export const OperatorTable = [ - { arity: "unary", name: "not", symbol: "!", opCode: OpCode.Unary.LOGICAL_NOT }, - { arity: "unary", name: "neg", symbol: "-", opCode: OpCode.Unary.NEGATE }, - { arity: "unary", name: "plus", symbol: "+", opCode: OpCode.Unary.PLUS }, - { arity: "binary", name: "add", symbol: "+", opCode: OpCode.Binary.ADD }, - { arity: "binary", name: "sub", symbol: "-", opCode: OpCode.Binary.SUBTRACT }, - { arity: "binary", name: "mult", symbol: "*", opCode: OpCode.Binary.MULTIPLY }, - { arity: "binary", name: "div", symbol: "/", opCode: OpCode.Binary.DIVIDE }, - { arity: "binary", name: "mod", symbol: "%", opCode: OpCode.Binary.MODULO }, - { arity: "binary", name: "equalTo", symbol: "==", opCode: OpCode.Binary.EQUAL }, - { arity: "binary", name: "notEqual", symbol: "!=", opCode: OpCode.Binary.NOT_EQUAL }, - { arity: "binary", name: "greaterThan", symbol: ">", opCode: OpCode.Binary.GREATER_THAN }, - { arity: "binary", name: "greaterEqual", symbol: ">=", opCode: OpCode.Binary.GREATER_EQUAL }, - { arity: "binary", name: "lessThan", symbol: "<", opCode: OpCode.Binary.LESS_THAN }, - { arity: "binary", name: "lessEqual", symbol: "<=", opCode: OpCode.Binary.LESS_EQUAL }, - { arity: "binary", name: "and", symbol: "&&", opCode: OpCode.Binary.LOGICAL_AND }, - { arity: "binary", name: "or", symbol: "||", opCode: OpCode.Binary.LOGICAL_OR }, + { arity: 'unary', name: 'not', symbol: '!', opCode: OpCode.Unary.LOGICAL_NOT }, + { arity: 'unary', name: 'neg', symbol: '-', opCode: OpCode.Unary.NEGATE }, + { arity: 'unary', name: 'plus', symbol: '+', opCode: OpCode.Unary.PLUS }, + { arity: 'binary', name: 'add', symbol: '+', opCode: OpCode.Binary.ADD }, + { arity: 'binary', name: 'sub', symbol: '-', opCode: OpCode.Binary.SUBTRACT }, + { arity: 'binary', name: 'mult', symbol: '*', opCode: OpCode.Binary.MULTIPLY }, + { arity: 'binary', name: 'div', symbol: '/', opCode: OpCode.Binary.DIVIDE }, + { arity: 'binary', name: 'mod', symbol: '%', opCode: OpCode.Binary.MODULO }, + { arity: 'binary', name: 'equalTo', symbol: '==', opCode: OpCode.Binary.EQUAL }, + { arity: 'binary', name: 'notEqual', symbol: '!=', opCode: OpCode.Binary.NOT_EQUAL }, + { arity: 'binary', name: 'greaterThan', symbol: '>', opCode: OpCode.Binary.GREATER_THAN }, + { arity: 'binary', name: 'greaterEqual', symbol: '>=', opCode: OpCode.Binary.GREATER_EQUAL }, + { arity: 'binary', name: 'lessThan', symbol: '<', opCode: OpCode.Binary.LESS_THAN }, + { arity: 'binary', name: 'lessEqual', symbol: '<=', opCode: OpCode.Binary.LESS_EQUAL }, + { arity: 'binary', name: 'and', symbol: '&&', opCode: OpCode.Binary.LOGICAL_AND }, + { arity: 'binary', name: 'or', symbol: '||', opCode: OpCode.Binary.LOGICAL_OR } ]; export const ConstantFolding = { [OpCode.Binary.ADD]: (a, b) => a + b, @@ -191,7 +191,7 @@ export const ConstantFolding = { [OpCode.Binary.LESS_THAN]: (a, b) => a < b, [OpCode.Binary.LESS_EQUAL]: (a, b) => a <= b, [OpCode.Binary.LOGICAL_AND]: (a, b) => a && b, - [OpCode.Binary.LOGICAL_OR]: (a, b) => a || b, + [OpCode.Binary.LOGICAL_OR]: (a, b) => a || b }; // export const SymbolToOpCode = {}; export const OpCodeToSymbol = {}; @@ -218,8 +218,8 @@ export const BlockType = { SCOPE_END: 'scope_end', FOR: 'for', MERGE: 'merge', - DEFAULT: 'default', -} + DEFAULT: 'default' +}; export const BlockTypeToName = Object.fromEntries( Object.entries(BlockType).map(([key, val]) => [val, key]) ); diff --git a/src/strands/p5.strands.js b/src/strands/p5.strands.js index 384b6068d7..af3020f896 100644 --- a/src/strands/p5.strands.js +++ b/src/strands/p5.strands.js @@ -1,15 +1,15 @@ /** -* @module 3D -* @submodule strands -* @for p5 -* @requires core -*/ + * @module 3D + * @submodule strands + * @for p5 + * @requires core + */ import { glslBackend } from './strands_glslBackend'; import { transpileStrandsToJS } from './strands_transpiler'; import { BlockType } from './ir_types'; -import { createDirectedAcyclicGraph } from './ir_dag' +import { createDirectedAcyclicGraph } from './ir_dag'; import { createControlFlowGraph, createBasicBlock, pushBlock, popBlock } from './ir_cfg'; import { generateShaderCode } from './strands_codegen'; import { initGlobalStrandsAPI, createShaderHooksFunctions } from './strands_api'; @@ -54,7 +54,7 @@ function strands(p5, fn) { const strandsContext = {}; initStrandsContext(strandsContext); - initGlobalStrandsAPI(p5, fn, strandsContext) + initGlobalStrandsAPI(p5, fn, strandsContext); ////////////////////////////////////////////// // Entry Point @@ -98,15 +98,15 @@ function strands(p5, fn) { return oldModify.call(this, hooksObject); } else { - return oldModify.call(this, shaderModifier) + return oldModify.call(this, shaderModifier); } - } + }; } export default strands; if (typeof p5 !== 'undefined') { - p5.registerAddon(strands) + p5.registerAddon(strands); } /* ------------------------------------------------------------- */ @@ -141,7 +141,7 @@ if (typeof p5 !== 'undefined') { * getWorldInputs(inputs => { * // Move the vertex up and down in a wave in world space * // In world space, moving the object (e.g., with translate()) will affect these coordinates -* // The sphere is ~50 units tall here, so 20 gives a noticeable wave + * // The sphere is ~50 units tall here, so 20 gives a noticeable wave * inputs.position.y += 20 * sin(t * 0.001 + inputs.position.x * 0.05); * return inputs; * }); diff --git a/src/strands/strands_FES.js b/src/strands/strands_FES.js index 3af0aca90b..626fcd127e 100644 --- a/src/strands/strands_FES.js +++ b/src/strands/strands_FES.js @@ -1,9 +1,9 @@ export function internalError(errorMessage) { - const prefixedMessage = `[p5.strands internal error]: ${errorMessage}` - throw new Error(prefixedMessage); + const prefixedMessage = `[p5.strands internal error]: ${errorMessage}`; + throw new Error(prefixedMessage); } export function userError(errorType, errorMessage) { - const prefixedMessage = `[p5.strands ${errorType}]: ${errorMessage}`; - throw new Error(prefixedMessage); + const prefixedMessage = `[p5.strands ${errorType}]: ${errorMessage}`; + throw new Error(prefixedMessage); } \ No newline at end of file diff --git a/src/strands/strands_api.js b/src/strands/strands_api.js index f4bfabcea2..f76d6cda98 100644 --- a/src/strands/strands_api.js +++ b/src/strands/strands_api.js @@ -1,4 +1,4 @@ -import * as build from './ir_builders' +import * as build from './ir_builders'; import { OperatorTable, BlockType, @@ -8,16 +8,16 @@ import { TypeInfoFromGLSLName, isStructType, OpCode, - StatementType, + StatementType // isNativeType -} from './ir_types' -import { strandsBuiltinFunctions } from './strands_builtins' -import { StrandsConditional } from './strands_conditionals' -import { StrandsFor } from './strands_for' -import * as CFG from './ir_cfg' -import * as FES from './strands_FES' -import { getNodeDataFromID } from './ir_dag' -import { StrandsNode, createStrandsNode } from './strands_node' +} from './ir_types'; +import { strandsBuiltinFunctions } from './strands_builtins'; +import { StrandsConditional } from './strands_conditionals'; +import { StrandsFor } from './strands_for'; +import * as CFG from './ir_cfg'; +import * as FES from './strands_FES'; +import { getNodeDataFromID } from './ir_dag'; +import { StrandsNode, createStrandsNode } from './strands_node'; import noiseGLSL from '../webgl/shaders/functions/noise3DGLSL.glsl'; ////////////////////////////////////////////// @@ -37,7 +37,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { p5[name] = function (nodeOrValue) { const { id, dimension } = build.unaryOpNode(strandsContext, nodeOrValue, opCode); return createStrandsNode(id, dimension, strandsContext); - } + }; } } ////////////////////////////////////////////// @@ -45,7 +45,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { ////////////////////////////////////////////// fn.discard = function() { build.statementNode(strandsContext, StatementType.DISCARD); - } + }; fn.break = function() { build.statementNode(strandsContext, StatementType.BREAK); }; @@ -53,12 +53,12 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { fn.instanceID = function() { const node = build.variableNode(strandsContext, { baseType: BaseType.INT, dimension: 1 }, 'gl_InstanceID'); return createStrandsNode(node.id, node.dimension, strandsContext); - } + }; // Internal methods use p5 static methods; user-facing methods use fn. // Some methods need to be used by both. p5.strandsIf = function(conditionNode, ifBody) { return new StrandsConditional(strandsContext, conditionNode, ifBody); - } + }; fn.strandsIf = p5.strandsIf; p5.strandsFor = function(initialCb, conditionCb, updateCb, bodyCb, initialVars) { return new StrandsFor(strandsContext, initialCb, conditionCb, updateCb, bodyCb, initialVars).build(); @@ -69,11 +69,11 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { return args[0]; } if (args.length > 4) { - FES.userError("type error", "It looks like you've tried to construct a p5.strands node implicitly, with more than 4 components. This is currently not supported.") + FES.userError('type error', "It looks like you've tried to construct a p5.strands node implicitly, with more than 4 components. This is currently not supported."); } const { id, dimension } = build.primitiveConstructorNode(strandsContext, { baseType: BaseType.FLOAT, dimension: null }, args.flat()); return createStrandsNode(id, dimension, strandsContext);//new StrandsNode(id, dimension, strandsContext); - } + }; ////////////////////////////////////////////// // Builtins, uniforms, variable constructors ////////////////////////////////////////////// @@ -88,7 +88,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { } else { return originalFn.apply(this, args); } - } + }; } else { fn[functionName] = function (...args) { if (strandsContext.active) { @@ -97,9 +97,9 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { } else { p5._friendlyError( `It looks like you've called ${functionName} outside of a shader's modify() function.` - ) + ); } - } + }; } } // Add GLSL noise. TODO: Replace this with a backend-agnostic implementation @@ -122,7 +122,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { const { id, dimension } = build.functionCallNode(strandsContext, 'noise', nodeArgs, { overloads: [{ params: [DataType.float3], - returnType: DataType.float1, + returnType: DataType.float1 }] }); return createStrandsNode(id, dimension, strandsContext); @@ -136,10 +136,10 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { let pascalTypeName; if (/^[ib]vec/.test(typeInfo.fnName)) { pascalTypeName = typeInfo.fnName - .slice(0, 2).toUpperCase() + .slice(0, 2).toUpperCase() + typeInfo.fnName - .slice(2) - .toLowerCase(); + .slice(2) + .toLowerCase(); } else { pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase() + typeInfo.fnName.slice(1).toLowerCase(); @@ -166,7 +166,7 @@ export function initGlobalStrandsAPI(p5, fn, strandsContext) { `It looks like you've called ${typeInfo.fnName} outside of a shader's modify() function.` ); } - } + }; } } ////////////////////////////////////////////// @@ -184,8 +184,8 @@ function createHookArguments(strandsContext, parameters){ const propertyType = structTypeInfo.properties[i]; Object.defineProperty(structNode, propertyType.name, { get() { - const propNode = getNodeDataFromID(dag, dag.dependsOn[structNode.id][i]) - const onRebind = (newFieldID) => { + const propNode = getNodeDataFromID(dag, dag.dependsOn[structNode.id][i]); + const onRebind = newFieldID => { const oldDeps = dag.dependsOn[structNode.id]; const newDeps = oldDeps.slice(); newDeps[i] = newFieldID; @@ -213,7 +213,7 @@ function createHookArguments(strandsContext, parameters){ const newStructInfo = build.structInstanceNode(strandsContext, structTypeInfo, param.name, newDependsOn); structNode.id = newStructInfo.id; } - }) + }); } args.push(structNode); } @@ -229,24 +229,24 @@ function createHookArguments(strandsContext, parameters){ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName) { if (!(returned instanceof StrandsNode)) { // try { - const result = build.primitiveConstructorNode(strandsContext, expectedType, returned); - return result.id; + const result = build.primitiveConstructorNode(strandsContext, expectedType, returned); + return result.id; // } catch (e) { - // FES.userError('type error', - // `There was a type mismatch for a value returned from ${hookName}.\n` + - // `The value in question was supposed to be:\n` + - // `${expectedType.baseType + expectedType.dimension}\n` + - // `But you returned:\n` + - // `${returned}` - // ); + // FES.userError('type error', + // `There was a type mismatch for a value returned from ${hookName}.\n` + + // `The value in question was supposed to be:\n` + + // `${expectedType.baseType + expectedType.dimension}\n` + + // `But you returned:\n` + + // `${returned}` + // ); // } } const dag = strandsContext.dag; let returnedNodeID = returned.id; const receivedType = { baseType: dag.baseTypes[returnedNodeID], - dimension: dag.dimensions[returnedNodeID], - } + dimension: dag.dimensions[returnedNodeID] + }; if (receivedType.dimension !== expectedType.dimension) { if (receivedType.dimension !== 1) { FES.userError('type error', `You have returned a vector with ${receivedType.dimension} components in ${hookName} when a ${expectedType.baseType + expectedType.dimension} was expected!`); @@ -265,8 +265,8 @@ function enforceReturnTypeMatch(strandsContext, expectedType, returned, hookName export function createShaderHooksFunctions(strandsContext, fn, shader) { const availableHooks = { ...shader.hooks.vertex, - ...shader.hooks.fragment, - } + ...shader.hooks.fragment + }; const hookTypes = Object.keys(availableHooks).map(name => shader.hookTypes(name)); const { cfg, dag } = strandsContext; for (const hookType of hookTypes) { @@ -283,7 +283,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { if (userReturned instanceof StrandsNode) { const returnedNode = getNodeDataFromID(strandsContext.dag, userReturned.id); if (returnedNode.baseType !== expectedStructType.typeName) { - FES.userError("type error", `You have returned a ${userReturned.baseType} from ${hookType.name} when a ${expectedStructType.typeName} was expected.`); + FES.userError('type error', `You have returned a ${userReturned.baseType} from ${hookType.name} when a ${expectedStructType.typeName} was expected.`); } const newDeps = returnedNode.dependsOn.slice(); for (let i = 0; i < expectedStructType.properties.length; i++) { @@ -305,7 +305,7 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { FES.userError('type error', `You've returned an incomplete struct from ${hookType.name}.\n` + `Expected: { ${expectedReturnType.properties.map(p => p.name).join(', ')} }\n` + `Received: { ${Object.keys(userReturned).join(', ')} }\n` + - `All of the properties are required!`); + 'All of the properties are required!'); } const expectedTypeInfo = expectedProp.dataType; const returnedPropID = enforceReturnTypeMatch(strandsContext, expectedTypeInfo, receivedValue, hookType.name); @@ -322,10 +322,10 @@ export function createShaderHooksFunctions(strandsContext, fn, shader) { strandsContext.hooks.push({ hookType, entryBlockID, - rootNodeID, + rootNodeID }); CFG.popBlock(cfg); - } + }; strandsContext.windowOverrides[hookType.name] = window[hookType.name]; strandsContext.fnOverrides[hookType.name] = fn[hookType.name]; window[hookType.name] = hookImplementation; diff --git a/src/strands/strands_builtins.js b/src/strands/strands_builtins.js index eccfc74170..50c3754518 100644 --- a/src/strands/strands_builtins.js +++ b/src/strands/strands_builtins.js @@ -1,116 +1,116 @@ // Need the .js extension because we also import this from a Node script. // Try to keep this file minimal because of that. -import { GenType, DataType } from "./ir_types.js" +import { GenType, DataType } from './ir_types.js'; // GLSL Built in functions // https://docs.gl/el3/abs const builtInGLSLFunctions = { //////////// Trigonometry ////////// - acos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - acosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - asin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - asinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + acos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + acosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + asin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + asinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], atan: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true } ], - atanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - cos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - cosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - degrees: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - radians: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - sin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT , isp5Function: true}], - sinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - tan: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - tanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + atanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + cos: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + cosh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + degrees: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + radians: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + sin: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT , isp5Function: true }], + sinh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + tan: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + tanh: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], ////////// Mathematics ////////// abs: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT], returnType: GenType.INT, isp5Function: true} + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT], returnType: GenType.INT, isp5Function: true } ], - ceil: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], + ceil: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], clamp: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT,DataType.float1,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.INT, GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: false}, - { params: [GenType.INT, DataType.int1, DataType.int1], returnType: GenType.INT, isp5Function: false}, + { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.FLOAT,DataType.float1,DataType.float1], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.INT, GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: false }, + { params: [GenType.INT, DataType.int1, DataType.int1], returnType: GenType.INT, isp5Function: false } ], - dFdx: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - dFdy: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - exp: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - exp2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - floor: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - fma: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - fract: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - fwidth: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - inversesqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], + dFdx: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + dFdy: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + exp: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + exp2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + floor: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + fma: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + fract: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + fwidth: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + inversesqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], // "isinf": [{}], // "isnan": [{}], - log: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - log2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + log: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + log2: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], max: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true}, - { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true}, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true }, + { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true } ], min: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true}, - { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true}, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.INT, GenType.INT], returnType: GenType.INT, isp5Function: true }, + { params: [GenType.INT, DataType.int1], returnType: GenType.INT, isp5Function: true } ], mix: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.FLOAT, GenType.FLOAT, GenType.BOOL], returnType: GenType.FLOAT, isp5Function: false}, + { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.FLOAT, GenType.FLOAT, GenType.BOOL], returnType: GenType.FLOAT, isp5Function: false } ], mod: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}, - { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true}, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }, + { params: [GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: true } ], // "modf": [{}], - pow: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - round: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - roundEven: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + pow: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + round: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + roundEven: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], sign: [ - { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [GenType.INT], returnType: GenType.INT, isp5Function: false}, + { params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }, + { params: [GenType.INT], returnType: GenType.INT, isp5Function: false } ], smoothstep: [ - { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, - { params: [ DataType.float1,DataType.float1, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}, + { params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }, + { params: [ DataType.float1,DataType.float1, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false } ], - sqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], - step: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - trunc: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], + sqrt: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], + step: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + trunc: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], ////////// Vector ////////// - cross: [{ params: [DataType.float3, DataType.float3], returnType: DataType.float3, isp5Function: true}], - distance: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true}], - dot: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true}], + cross: [{ params: [DataType.float3, DataType.float3], returnType: DataType.float3, isp5Function: true }], + distance: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true }], + dot: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType:DataType.float1, isp5Function: true }], equal: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false}, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false }, + { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false }, + { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false } ], - faceforward: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - length: [{ params: [GenType.FLOAT], returnType:DataType.float1, isp5Function: false}], - normalize: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true}], + faceforward: [{ params: [GenType.FLOAT, GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + length: [{ params: [GenType.FLOAT], returnType:DataType.float1, isp5Function: false }], + normalize: [{ params: [GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: true }], notEqual: [ - { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false}, - { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false}, + { params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.BOOL, isp5Function: false }, + { params: [GenType.INT, GenType.INT], returnType: GenType.BOOL, isp5Function: false }, + { params: [GenType.BOOL, GenType.BOOL], returnType: GenType.BOOL, isp5Function: false } ], - reflect: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false}], - refract: [{ params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false}], + reflect: [{ params: [GenType.FLOAT, GenType.FLOAT], returnType: GenType.FLOAT, isp5Function: false }], + refract: [{ params: [GenType.FLOAT, GenType.FLOAT,DataType.float1], returnType: GenType.FLOAT, isp5Function: false }], ////////// Texture sampling ////////// - texture: [{params: [DataType.sampler2D, DataType.float2], returnType: DataType.float4, isp5Function: true}], - getTexture: [{params: [DataType.sampler2D, DataType.float2], returnType: DataType.float4, isp5Function: true}] -} + texture: [{ params: [DataType.sampler2D, DataType.float2], returnType: DataType.float4, isp5Function: true }], + getTexture: [{ params: [DataType.sampler2D, DataType.float2], returnType: DataType.float4, isp5Function: true }] +}; export const strandsBuiltinFunctions = { - ...builtInGLSLFunctions, -} + ...builtInGLSLFunctions +}; diff --git a/src/strands/strands_codegen.js b/src/strands/strands_codegen.js index 84c83a77da..786239fa58 100644 --- a/src/strands/strands_codegen.js +++ b/src/strands/strands_codegen.js @@ -1,4 +1,4 @@ -import { sortCFG } from "./ir_cfg"; +import { sortCFG } from './ir_cfg'; import { structType, TypeInfoFromGLSLName } from './ir_types'; export function generateShaderCode(strandsContext) { @@ -10,10 +10,10 @@ export function generateShaderCode(strandsContext) { } = strandsContext; const hooksObj = { - uniforms: {}, + uniforms: {} }; - for (const {name, typeInfo, defaultValue} of strandsContext.uniforms) { + for (const { name, typeInfo, defaultValue } of strandsContext.uniforms) { const declaration = backend.generateUniformDeclaration(name, typeInfo); hooksObj.uniforms[declaration] = defaultValue; } @@ -27,7 +27,7 @@ export function generateShaderCode(strandsContext) { }, tempNames: {}, declarations: [], - nextTempID: 0, + nextTempID: 0 }; const blocks = sortCFG(cfg.outgoingEdges, entryBlockID); diff --git a/src/strands/strands_glslBackend.js b/src/strands/strands_glslBackend.js index 2ecda9ee7a..3575e0391b 100644 --- a/src/strands/strands_glslBackend.js +++ b/src/strands/strands_glslBackend.js @@ -1,6 +1,6 @@ -import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType } from "./ir_types"; -import { getNodeDataFromID, extractNodeTypeInfo } from "./ir_dag"; -import * as FES from './strands_FES' +import { NodeType, OpCodeToSymbol, BlockType, OpCode, NodeTypeToName, isStructType, BaseType, StatementType } from './ir_types'; +import { getNodeDataFromID, extractNodeTypeInfo } from './ir_dag'; +import * as FES from './strands_FES'; function shouldCreateTemp(dag, nodeID) { const nodeType = dag.nodeTypes[nodeID]; if (nodeType !== NodeType.OPERATION) return false; @@ -23,8 +23,8 @@ const TypeNames = { 'bool4': 'bvec4', 'mat2': 'mat2x2', 'mat3': 'mat3x3', - 'mat4': 'mat4x4', -} + 'mat4': 'mat4x4' +}; const cfgHandlers = { [BlockType.DEFAULT]: (blockID, strandsContext, generationContext) => { const { dag, cfg } = strandsContext; @@ -80,7 +80,7 @@ const cfgHandlers = { this[BlockType.DEFAULT](blockID, strandsContext, generationContext); }, [BlockType.ELSE_COND](blockID, strandsContext, generationContext) { - generationContext.write(`else`); + generationContext.write('else'); this[BlockType.DEFAULT](blockID, strandsContext, generationContext); }, [BlockType.IF_BODY](blockID, strandsContext, generationContext) { @@ -88,12 +88,12 @@ const cfgHandlers = { this.assignPhiNodeValues(blockID, strandsContext, generationContext); }, [BlockType.SCOPE_START](blockID, strandsContext, generationContext) { - generationContext.write(`{`); + generationContext.write('{'); generationContext.indent++; }, [BlockType.SCOPE_END](blockID, strandsContext, generationContext) { generationContext.indent--; - generationContext.write(`}`); + generationContext.write('}'); }, [BlockType.MERGE](blockID, strandsContext, generationContext) { this[BlockType.DEFAULT](blockID, strandsContext, generationContext); @@ -105,7 +105,7 @@ const cfgHandlers = { const { dag, cfg } = strandsContext; const instructions = cfg.blockInstructions[blockID] || []; - generationContext.write(`for (`); + generationContext.write('for ('); // Set flag to suppress semicolon on the last statement const originalSuppressSemicolon = generationContext.suppressSemicolon; @@ -133,7 +133,7 @@ const cfgHandlers = { // Restore original flag generationContext.suppressSemicolon = originalSuppressSemicolon; - generationContext.write(`)`); + generationContext.write(')'); }, assignPhiNodeValues(blockID, strandsContext, generationContext) { const { dag, cfg } = strandsContext; @@ -157,17 +157,17 @@ const cfgHandlers = { } } } - }, -} + } +}; export const glslBackend = { hookEntry(hookType) { - const firstLine = `(${hookType.parameters.flatMap((param) => { + const firstLine = `(${hookType.parameters.flatMap(param => { return `${param.qualifiers?.length ? param.qualifiers.join(' ') : ''}${param.type.typeName} ${param.name}`; }).join(', ')}) {`; return firstLine; }, getTypeName(baseType, dimension) { - const primitiveTypeName = TypeNames[baseType + dimension] + const primitiveTypeName = TypeNames[baseType + dimension]; if (!primitiveTypeName) { return baseType; } @@ -226,7 +226,7 @@ export const glslBackend = { if (prop.name !== val) { generationContext.write( `${rootNode.identifier}.${prop.name} = ${val};` - ) + ); } } } @@ -239,103 +239,103 @@ export const glslBackend = { } switch (node.nodeType) { case NodeType.LITERAL: - if (node.baseType === BaseType.FLOAT) { - return node.value.toFixed(4); - } - else { - return node.value; - } + if (node.baseType === BaseType.FLOAT) { + return node.value.toFixed(4); + } + else { + return node.value; + } case NodeType.VARIABLE: - return node.identifier; + return node.identifier; case NodeType.OPERATION: - const useParantheses = node.usedBy.length > 0; - if (node.opCode === OpCode.Nary.CONSTRUCTOR) { + const useParantheses = node.usedBy.length > 0; + if (node.opCode === OpCode.Nary.CONSTRUCTOR) { // TODO: differentiate casts and constructors for more efficient codegen. // if (node.dependsOn.length === 1 && node.dimension === 1) { // return this.generateExpression(generationContext, dag, node.dependsOn[0]); // } - if (node.baseType === BaseType.SAMPLER2D) { - return this.generateExpression(generationContext, dag, node.dependsOn[0]); + if (node.baseType === BaseType.SAMPLER2D) { + return this.generateExpression(generationContext, dag, node.dependsOn[0]); + } + const T = this.getTypeName(node.baseType, node.dimension); + const deps = node.dependsOn.map(dep => this.generateExpression(generationContext, dag, dep)); + return `${T}(${deps.join(', ')})`; } - const T = this.getTypeName(node.baseType, node.dimension); - const deps = node.dependsOn.map((dep) => this.generateExpression(generationContext, dag, dep)); - return `${T}(${deps.join(', ')})`; - } - if (node.opCode === OpCode.Nary.FUNCTION_CALL) { - const functionArgs = node.dependsOn.map(arg =>this.generateExpression(generationContext, dag, arg)); - return `${node.identifier}(${functionArgs.join(', ')})`; - } - if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { - const [lID, rID] = node.dependsOn; - const lName = this.generateExpression(generationContext, dag, lID); - const rName = this.generateExpression(generationContext, dag, rID); - return `${lName}.${rName}`; - } - if (node.opCode === OpCode.Unary.SWIZZLE) { - const parentID = node.dependsOn[0]; - const parentExpr = this.generateExpression(generationContext, dag, parentID); - return `${parentExpr}.${node.swizzle}`; - } - if (node.dependsOn.length === 2) { - const [lID, rID] = node.dependsOn; - const left = this.generateExpression(generationContext, dag, lID); - const right = this.generateExpression(generationContext, dag, rID); + if (node.opCode === OpCode.Nary.FUNCTION_CALL) { + const functionArgs = node.dependsOn.map(arg =>this.generateExpression(generationContext, dag, arg)); + return `${node.identifier}(${functionArgs.join(', ')})`; + } + if (node.opCode === OpCode.Binary.MEMBER_ACCESS) { + const [lID, rID] = node.dependsOn; + const lName = this.generateExpression(generationContext, dag, lID); + const rName = this.generateExpression(generationContext, dag, rID); + return `${lName}.${rName}`; + } + if (node.opCode === OpCode.Unary.SWIZZLE) { + const parentID = node.dependsOn[0]; + const parentExpr = this.generateExpression(generationContext, dag, parentID); + return `${parentExpr}.${node.swizzle}`; + } + if (node.dependsOn.length === 2) { + const [lID, rID] = node.dependsOn; + const left = this.generateExpression(generationContext, dag, lID); + const right = this.generateExpression(generationContext, dag, rID); - // Special case for modulo: use mod() function for floats in GLSL - if (node.opCode === OpCode.Binary.MODULO) { - const leftNode = getNodeDataFromID(dag, lID); - const rightNode = getNodeDataFromID(dag, rID); - // If either operand is float, use mod() function - if (leftNode.baseType === BaseType.FLOAT || rightNode.baseType === BaseType.FLOAT) { - return `mod(${left}, ${right})`; + // Special case for modulo: use mod() function for floats in GLSL + if (node.opCode === OpCode.Binary.MODULO) { + const leftNode = getNodeDataFromID(dag, lID); + const rightNode = getNodeDataFromID(dag, rID); + // If either operand is float, use mod() function + if (leftNode.baseType === BaseType.FLOAT || rightNode.baseType === BaseType.FLOAT) { + return `mod(${left}, ${right})`; + } + // For integers, use % operator + return `(${left} % ${right})`; } - // For integers, use % operator - return `(${left} % ${right})`; - } - const opSym = OpCodeToSymbol[node.opCode]; - if (useParantheses) { - return `(${left} ${opSym} ${right})`; - } else { - return `${left} ${opSym} ${right}`; + const opSym = OpCodeToSymbol[node.opCode]; + if (useParantheses) { + return `(${left} ${opSym} ${right})`; + } else { + return `${left} ${opSym} ${right}`; + } } - } - if (node.opCode === OpCode.Unary.LOGICAL_NOT + if (node.opCode === OpCode.Unary.LOGICAL_NOT || node.opCode === OpCode.Unary.NEGATE || node.opCode === OpCode.Unary.PLUS ) { - const [i] = node.dependsOn; - const val = this.generateExpression(generationContext, dag, i); - const sym = OpCodeToSymbol[node.opCode]; - return `${sym}${val}`; - } + const [i] = node.dependsOn; + const val = this.generateExpression(generationContext, dag, i); + const sym = OpCodeToSymbol[node.opCode]; + return `${sym}${val}`; + } case NodeType.PHI: // Phi nodes represent conditional merging of values // They should already have been declared as temporary variables // and assigned in the appropriate branches - if (generationContext.tempNames?.[nodeID]) { - return generationContext.tempNames[nodeID]; - } else { + if (generationContext.tempNames?.[nodeID]) { + return generationContext.tempNames[nodeID]; + } else { // If no temp was created, this phi node only has one input // so we can just use that directly - const validInputs = node.dependsOn.filter(id => id !== null); - if (validInputs.length > 0) { - return this.generateExpression(generationContext, dag, validInputs[0]); - } else { - throw new Error(`No valid inputs for node`) - // Fallback: create a default value - const typeName = this.getTypeName(node.baseType, node.dimension); - if (node.dimension === 1) { - return node.baseType === BaseType.FLOAT ? '0.0' : '0'; + const validInputs = node.dependsOn.filter(id => id !== null); + if (validInputs.length > 0) { + return this.generateExpression(generationContext, dag, validInputs[0]); } else { - return `${typeName}(0.0)`; + throw new Error('No valid inputs for node'); + // Fallback: create a default value + const typeName = this.getTypeName(node.baseType, node.dimension); + if (node.dimension === 1) { + return node.baseType === BaseType.FLOAT ? '0.0' : '0'; + } else { + return `${typeName}(0.0)`; + } } } - } case NodeType.ASSIGNMENT: - FES.internalError(`ASSIGNMENT nodes should not be used as expressions`) + FES.internalError('ASSIGNMENT nodes should not be used as expressions'); default: - FES.internalError(`${NodeTypeToName[node.nodeType]} code generation not implemented yet`) + FES.internalError(`${NodeTypeToName[node.nodeType]} code generation not implemented yet`); } }, generateBlock(blockID, strandsContext, generationContext) { @@ -343,4 +343,4 @@ export const glslBackend = { const handler = cfgHandlers[type] || cfgHandlers[BlockType.DEFAULT]; handler.call(cfgHandlers, blockID, strandsContext, generationContext); } -} +}; diff --git a/src/strands/strands_transpiler.js b/src/strands/strands_transpiler.js index 746a238278..bf96aba8dd 100644 --- a/src/strands/strands_transpiler.js +++ b/src/strands/strands_transpiler.js @@ -42,14 +42,14 @@ const ASTCallbacks = { UnaryExpression(node, _state, ancestors) { if (ancestors.some(nodeIsUniform)) { return; } const unaryFnName = UnarySymbolToName[node.operator]; - const standardReplacement = (node) => { - node.type = 'CallExpression' + const standardReplacement = node => { + node.type = 'CallExpression'; node.callee = { type: 'Identifier', - name: `__p5.${unaryFnName}`, - } - node.arguments = [node.argument] - } + name: `__p5.${unaryFnName}` + }; + node.arguments = [node.argument]; + }; if (node.type === 'MemberExpression') { const property = node.argument.property.name; const swizzleSets = [ @@ -68,7 +68,7 @@ const ASTCallbacks = { type: 'Identifier', name: `__p5.${unaryFnName}` }, - arguments: [node.argument.object], + arguments: [node.argument.object] }; node.property = { type: 'Identifier', @@ -98,14 +98,14 @@ const ASTCallbacks = { const uniformNameLiteral = { type: 'Literal', value: node.id.name - } + }; node.init.arguments.unshift(uniformNameLiteral); } if (node.init.callee && node.init.callee.name?.startsWith('varying')) { const varyingNameLiteral = { type: 'Literal', value: node.id.name - } + }; node.init.arguments.unshift(varyingNameLiteral); _state.varyings[node.id.name] = varyingNameLiteral; } @@ -114,800 +114,800 @@ const ASTCallbacks = { if (ancestors.some(nodeIsUniform)) { return; } if (_state.varyings[node.name] && !ancestors.some(a => a.type === 'AssignmentExpression' && a.left === node)) { - node.type = 'ExpressionStatement'; - node.expression = { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { - type: 'Identifier', - name: node.name - }, - property: { - type: 'Identifier', - name: 'getValue' - }, - }, - arguments: [], - } - } - }, - // The callbacks for AssignmentExpression and BinaryExpression handle - // operator overloading including +=, *= assignment expressions - ArrayExpression(node, _state, ancestors) { - if (ancestors.some(nodeIsUniform)) { return; } - const original = JSON.parse(JSON.stringify(node)); - node.type = 'CallExpression'; - node.callee = { - type: 'Identifier', - name: '__p5.strandsNode', - }; - node.arguments = [original]; - }, - AssignmentExpression(node, _state, ancestors) { - if (ancestors.some(nodeIsUniform)) { return; } - if (node.operator !== '=') { - const methodName = replaceBinaryOperator(node.operator.replace('=','')); - const rightReplacementNode = { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: node.left, - property: { - type: 'Identifier', - name: methodName, - }, - }, - arguments: [node.right] - } - node.operator = '='; - node.right = rightReplacementNode; - } - if (_state.varyings[node.left.name]) { - node.type = 'ExpressionStatement'; - node.expression = { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { - type: 'Identifier', - name: node.left.name - }, - property: { - type: 'Identifier', - name: 'bridge', - } - }, - arguments: [node.right], - } - } - }, - BinaryExpression(node, _state, ancestors) { - // Don't convert uniform default values to node methods, as - // they should be evaluated at runtime, not compiled. - if (ancestors.some(nodeIsUniform)) { return; } - // If the left hand side of an expression is one of these types, - // we should construct a node from it. - const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier']; - if (unsafeTypes.includes(node.left.type)) { - const leftReplacementNode = { - type: 'CallExpression', - callee: { + node.type = 'ExpressionStatement'; + node.expression = { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { type: 'Identifier', - name: '__p5.strandsNode', + name: node.name }, - arguments: [node.left] - } - node.left = leftReplacementNode; - } - // Replace the binary operator with a call expression - // in other words a call to BaseNode.mult(), .div() etc. - node.type = 'CallExpression'; - node.callee = { - type: 'MemberExpression', - object: node.left, - property: { - type: 'Identifier', - name: replaceBinaryOperator(node.operator), + property: { + type: 'Identifier', + name: 'getValue' + } }, + arguments: [] }; - node.arguments = [node.right]; - }, - IfStatement(node, _state, ancestors) { - if (ancestors.some(nodeIsUniform)) { return; } - // Transform if statement into strandsIf() call - // The condition is evaluated directly, not wrapped in a function - const condition = node.test; - // Create the then function - const thenFunction = { - type: 'ArrowFunctionExpression', - params: [], - body: node.consequent.type === 'BlockStatement' ? node.consequent : { - type: 'BlockStatement', - body: [node.consequent] - } - }; - // Start building the call chain: __p5.strandsIf(condition, then) - let callExpression = { + } + }, + // The callbacks for AssignmentExpression and BinaryExpression handle + // operator overloading including +=, *= assignment expressions + ArrayExpression(node, _state, ancestors) { + if (ancestors.some(nodeIsUniform)) { return; } + const original = JSON.parse(JSON.stringify(node)); + node.type = 'CallExpression'; + node.callee = { + type: 'Identifier', + name: '__p5.strandsNode' + }; + node.arguments = [original]; + }, + AssignmentExpression(node, _state, ancestors) { + if (ancestors.some(nodeIsUniform)) { return; } + if (node.operator !== '=') { + const methodName = replaceBinaryOperator(node.operator.replace('=','')); + const rightReplacementNode = { type: 'CallExpression', callee: { - type: 'Identifier', - name: '__p5.strandsIf' + type: 'MemberExpression', + object: node.left, + property: { + type: 'Identifier', + name: methodName + } }, - arguments: [condition, thenFunction] + arguments: [node.right] }; - // Always chain .Else() even if there's no explicit else clause - // This ensures the conditional completes and returns phi nodes - let elseFunction; - if (node.alternate) { - elseFunction = { - type: 'ArrowFunctionExpression', - params: [], - body: node.alternate.type === 'BlockStatement' ? node.alternate : { - type: 'BlockStatement', - body: [node.alternate] - } - }; - } else { - // Create an empty else function - elseFunction = { - type: 'ArrowFunctionExpression', - params: [], - body: { - type: 'BlockStatement', - body: [] - } - }; - } - callExpression = { + node.operator = '='; + node.right = rightReplacementNode; + } + if (_state.varyings[node.left.name]) { + node.type = 'ExpressionStatement'; + node.expression = { type: 'CallExpression', callee: { type: 'MemberExpression', - object: callExpression, + object: { + type: 'Identifier', + name: node.left.name + }, property: { type: 'Identifier', - name: 'Else' + name: 'bridge' } }, - arguments: [elseFunction] + arguments: [node.right] }; - // Analyze which outer scope variables are assigned in any branch - const assignedVars = new Set(); - const analyzeBlock = (body) => { - if (body.type !== 'BlockStatement') return; - // First pass: collect variable declarations within this block - const localVars = new Set(); - for (const stmt of body.body) { - if (stmt.type === 'VariableDeclaration') { - for (const decl of stmt.declarations) { - if (decl.id.type === 'Identifier') { - localVars.add(decl.id.name); - } + } + }, + BinaryExpression(node, _state, ancestors) { + // Don't convert uniform default values to node methods, as + // they should be evaluated at runtime, not compiled. + if (ancestors.some(nodeIsUniform)) { return; } + // If the left hand side of an expression is one of these types, + // we should construct a node from it. + const unsafeTypes = ['Literal', 'ArrayExpression', 'Identifier']; + if (unsafeTypes.includes(node.left.type)) { + const leftReplacementNode = { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__p5.strandsNode' + }, + arguments: [node.left] + }; + node.left = leftReplacementNode; + } + // Replace the binary operator with a call expression + // in other words a call to BaseNode.mult(), .div() etc. + node.type = 'CallExpression'; + node.callee = { + type: 'MemberExpression', + object: node.left, + property: { + type: 'Identifier', + name: replaceBinaryOperator(node.operator) + } + }; + node.arguments = [node.right]; + }, + IfStatement(node, _state, ancestors) { + if (ancestors.some(nodeIsUniform)) { return; } + // Transform if statement into strandsIf() call + // The condition is evaluated directly, not wrapped in a function + const condition = node.test; + // Create the then function + const thenFunction = { + type: 'ArrowFunctionExpression', + params: [], + body: node.consequent.type === 'BlockStatement' ? node.consequent : { + type: 'BlockStatement', + body: [node.consequent] + } + }; + // Start building the call chain: __p5.strandsIf(condition, then) + let callExpression = { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__p5.strandsIf' + }, + arguments: [condition, thenFunction] + }; + // Always chain .Else() even if there's no explicit else clause + // This ensures the conditional completes and returns phi nodes + let elseFunction; + if (node.alternate) { + elseFunction = { + type: 'ArrowFunctionExpression', + params: [], + body: node.alternate.type === 'BlockStatement' ? node.alternate : { + type: 'BlockStatement', + body: [node.alternate] + } + }; + } else { + // Create an empty else function + elseFunction = { + type: 'ArrowFunctionExpression', + params: [], + body: { + type: 'BlockStatement', + body: [] + } + }; + } + callExpression = { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: callExpression, + property: { + type: 'Identifier', + name: 'Else' + } + }, + arguments: [elseFunction] + }; + // Analyze which outer scope variables are assigned in any branch + const assignedVars = new Set(); + const analyzeBlock = body => { + if (body.type !== 'BlockStatement') return; + // First pass: collect variable declarations within this block + const localVars = new Set(); + for (const stmt of body.body) { + if (stmt.type === 'VariableDeclaration') { + for (const decl of stmt.declarations) { + if (decl.id.type === 'Identifier') { + localVars.add(decl.id.name); } } } - // Second pass: find assignments to non-local variables - for (const stmt of body.body) { - if (stmt.type === 'ExpressionStatement' && + } + // Second pass: find assignments to non-local variables + for (const stmt of body.body) { + if (stmt.type === 'ExpressionStatement' && stmt.expression.type === 'AssignmentExpression') { - const left = stmt.expression.left; - if (left.type === 'Identifier') { - // Direct variable assignment: x = value - if (!localVars.has(left.name)) { - assignedVars.add(left.name); - } - } else if (left.type === 'MemberExpression' && + const left = stmt.expression.left; + if (left.type === 'Identifier') { + // Direct variable assignment: x = value + if (!localVars.has(left.name)) { + assignedVars.add(left.name); + } + } else if (left.type === 'MemberExpression' && left.object.type === 'Identifier') { - // Property assignment: obj.prop = value - if (!localVars.has(left.object.name)) { - assignedVars.add(left.object.name); - } - } else if (stmt.type === 'BlockStatement') { - // Recursively analyze nested block statements - analyzeBlock(stmt); + // Property assignment: obj.prop = value + if (!localVars.has(left.object.name)) { + assignedVars.add(left.object.name); } + } else if (stmt.type === 'BlockStatement') { + // Recursively analyze nested block statements + analyzeBlock(stmt); } } - }; + } + }; // Analyze all branches for assignments to outer scope variables - analyzeBlock(thenFunction.body); - analyzeBlock(elseFunction.body); - if (assignedVars.size > 0) { - // Add copying, reference replacement, and return statements to branch functions - const addCopyingAndReturn = (functionBody, varsToReturn) => { - if (functionBody.type === 'BlockStatement') { - // Create temporary variables and copy statements - const tempVarMap = new Map(); // original name -> temp name - const copyStatements = []; - for (const varName of varsToReturn) { - const tempName = `__copy_${varName}_${blockVarCounter++}`; - tempVarMap.set(varName, tempName); - // let tempName = originalVar.copy() - copyStatements.push({ - type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: tempName }, - init: { - type: 'CallExpression', - callee: { - type: 'MemberExpression', - object: { type: 'Identifier', name: varName }, - property: { type: 'Identifier', name: 'copy' }, - computed: false - }, - arguments: [] - } - }], - kind: 'let' - }); - } - // Replace all references to original variables with temp variables - // and wrap literal assignments in strandsNode calls - const replaceReferences = (node) => { - if (!node || typeof node !== 'object') return; - if (node.type === 'Identifier' && tempVarMap.has(node.name)) { - node.name = tempVarMap.get(node.name); - } else if (node.type === 'MemberExpression' && - node.object.type === 'Identifier' && - tempVarMap.has(node.object.name)) { - node.object.name = tempVarMap.get(node.object.name); - } - // Handle literal assignments to temp variables - if (node.type === 'AssignmentExpression' && - node.left.type === 'Identifier' && - tempVarMap.has(node.left.name) && - (node.right.type === 'Literal' || node.right.type === 'ArrayExpression')) { - // Wrap the right hand side in a strandsNode call to make sure - // it's not just a literal and has a type - node.right = { + analyzeBlock(thenFunction.body); + analyzeBlock(elseFunction.body); + if (assignedVars.size > 0) { + // Add copying, reference replacement, and return statements to branch functions + const addCopyingAndReturn = (functionBody, varsToReturn) => { + if (functionBody.type === 'BlockStatement') { + // Create temporary variables and copy statements + const tempVarMap = new Map(); // original name -> temp name + const copyStatements = []; + for (const varName of varsToReturn) { + const tempName = `__copy_${varName}_${blockVarCounter++}`; + tempVarMap.set(varName, tempName); + // let tempName = originalVar.copy() + copyStatements.push({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { type: 'Identifier', name: tempName }, + init: { type: 'CallExpression', callee: { - type: 'Identifier', - name: '__p5.strandsNode' + type: 'MemberExpression', + object: { type: 'Identifier', name: varName }, + property: { type: 'Identifier', name: 'copy' }, + computed: false }, - arguments: [node.right] - }; - } - // Recursively process all properties - for (const key in node) { - if (node.hasOwnProperty(key) && key !== 'parent') { - if (Array.isArray(node[key])) { - node[key].forEach(replaceReferences); - } else if (typeof node[key] === 'object') { - replaceReferences(node[key]); - } + arguments: [] } - } - }; - // Apply reference replacement to all statements - functionBody.body.forEach(replaceReferences); - // Insert copy statements at the beginning - functionBody.body.unshift(...copyStatements); - // Add return statement with temp variable names - const returnObj = { - type: 'ObjectExpression', - properties: Array.from(varsToReturn).map(varName => ({ - type: 'Property', - key: { type: 'Identifier', name: varName }, - value: { type: 'Identifier', name: tempVarMap.get(varName) }, - kind: 'init', - computed: false, - shorthand: false - })) - }; - functionBody.body.push({ - type: 'ReturnStatement', - argument: returnObj + }], + kind: 'let' }); } - }; - addCopyingAndReturn(thenFunction.body, assignedVars); - addCopyingAndReturn(elseFunction.body, assignedVars); - // Create a block variable to capture the return value - const blockVar = `__block_${blockVarCounter++}`; - // Replace with a block statement - const statements = []; - // Make sure every assigned variable starts as a node - for (const varName of assignedVars) { - statements.push({ - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - operator: '=', - left: { type: 'Identifier', name: varName }, - right: { + // Replace all references to original variables with temp variables + // and wrap literal assignments in strandsNode calls + const replaceReferences = node => { + if (!node || typeof node !== 'object') return; + if (node.type === 'Identifier' && tempVarMap.has(node.name)) { + node.name = tempVarMap.get(node.name); + } else if (node.type === 'MemberExpression' && + node.object.type === 'Identifier' && + tempVarMap.has(node.object.name)) { + node.object.name = tempVarMap.get(node.object.name); + } + // Handle literal assignments to temp variables + if (node.type === 'AssignmentExpression' && + node.left.type === 'Identifier' && + tempVarMap.has(node.left.name) && + (node.right.type === 'Literal' || node.right.type === 'ArrayExpression')) { + // Wrap the right hand side in a strandsNode call to make sure + // it's not just a literal and has a type + node.right = { type: 'CallExpression', - callee: { type: 'Identifier', name: '__p5.strandsNode' }, - arguments: [{ type: 'Identifier', name: varName }], + callee: { + type: 'Identifier', + name: '__p5.strandsNode' + }, + arguments: [node.right] + }; + } + // Recursively process all properties + for (const key in node) { + if (node.hasOwnProperty(key) && key !== 'parent') { + if (Array.isArray(node[key])) { + node[key].forEach(replaceReferences); + } else if (typeof node[key] === 'object') { + replaceReferences(node[key]); + } } } + }; + // Apply reference replacement to all statements + functionBody.body.forEach(replaceReferences); + // Insert copy statements at the beginning + functionBody.body.unshift(...copyStatements); + // Add return statement with temp variable names + const returnObj = { + type: 'ObjectExpression', + properties: Array.from(varsToReturn).map(varName => ({ + type: 'Property', + key: { type: 'Identifier', name: varName }, + value: { type: 'Identifier', name: tempVarMap.get(varName) }, + kind: 'init', + computed: false, + shorthand: false + })) + }; + functionBody.body.push({ + type: 'ReturnStatement', + argument: returnObj }); } + }; + addCopyingAndReturn(thenFunction.body, assignedVars); + addCopyingAndReturn(elseFunction.body, assignedVars); + // Create a block variable to capture the return value + const blockVar = `__block_${blockVarCounter++}`; + // Replace with a block statement + const statements = []; + // Make sure every assigned variable starts as a node + for (const varName of assignedVars) { statements.push({ - type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: blockVar }, - init: callExpression - }], - kind: 'const' - }); - // 2. Assignments for each modified variable - for (const varName of assignedVars) { - statements.push({ - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - operator: '=', - left: { type: 'Identifier', name: varName }, - right: { - type: 'MemberExpression', - object: { type: 'Identifier', name: blockVar }, - property: { type: 'Identifier', name: varName }, - computed: false - } + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { type: 'Identifier', name: varName }, + right: { + type: 'CallExpression', + callee: { type: 'Identifier', name: '__p5.strandsNode' }, + arguments: [{ type: 'Identifier', name: varName }] } - }); - } - // Replace the if statement with a block statement - node.type = 'BlockStatement'; - node.body = statements; - } else { - // No assignments, just replace with the call expression - node.type = 'ExpressionStatement'; - node.expression = callExpression; + } + }); } - delete node.test; - delete node.consequent; - delete node.alternate; - }, - UpdateExpression(node, _state, ancestors) { - if (ancestors.some(nodeIsUniform)) { return; } - - // Transform ++var, var++, --var, var-- into assignment expressions - let operator; - if (node.operator === '++') { - operator = '+'; - } else if (node.operator === '--') { - operator = '-'; - } else { - return; // Unknown update operator + statements.push({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { type: 'Identifier', name: blockVar }, + init: callExpression + }], + kind: 'const' + }); + // 2. Assignments for each modified variable + for (const varName of assignedVars) { + statements.push({ + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { type: 'Identifier', name: varName }, + right: { + type: 'MemberExpression', + object: { type: 'Identifier', name: blockVar }, + property: { type: 'Identifier', name: varName }, + computed: false + } + } + }); } + // Replace the if statement with a block statement + node.type = 'BlockStatement'; + node.body = statements; + } else { + // No assignments, just replace with the call expression + node.type = 'ExpressionStatement'; + node.expression = callExpression; + } + delete node.test; + delete node.consequent; + delete node.alternate; + }, + UpdateExpression(node, _state, ancestors) { + if (ancestors.some(nodeIsUniform)) { return; } + + // Transform ++var, var++, --var, var-- into assignment expressions + let operator; + if (node.operator === '++') { + operator = '+'; + } else if (node.operator === '--') { + operator = '-'; + } else { + return; // Unknown update operator + } - // Convert to: var = var + 1 or var = var - 1 - const assignmentExpr = { - type: 'AssignmentExpression', - operator: '=', + // Convert to: var = var + 1 or var = var - 1 + const assignmentExpr = { + type: 'AssignmentExpression', + operator: '=', + left: node.argument, + right: { + type: 'BinaryExpression', + operator: operator, left: node.argument, right: { - type: 'BinaryExpression', - operator: operator, - left: node.argument, - right: { - type: 'Literal', - value: 1 - } + type: 'Literal', + value: 1 + } + } + }; + + // Replace the update expression with the assignment expression + Object.assign(node, assignmentExpr); + delete node.prefix; + this.BinaryExpression(node.right, _state, [...ancestors, node]); + this.AssignmentExpression(node, _state, ancestors); + }, + ForStatement(node, _state, ancestors) { + if (ancestors.some(nodeIsUniform)) { return; } + + // Transform for statement into strandsFor() call + // for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars) + + // Create the initial callback from the for loop's init + let initialFunction; + if (node.init && node.init.type === 'VariableDeclaration') { + // Handle: for (let i = 0; ...) + const declaration = node.init.declarations[0]; + let initValue = declaration.init; + + const initAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: initValue }] }; + initValue = initAst.body[0].expression; + + initialFunction = { + type: 'ArrowFunctionExpression', + params: [], + body: { + type: 'BlockStatement', + body: [{ + type: 'ReturnStatement', + argument: initValue + }] } }; + } else { + // Handle other cases - return a default value + initialFunction = { + type: 'ArrowFunctionExpression', + params: [], + body: { + type: 'BlockStatement', + body: [{ + type: 'ReturnStatement', + argument: { + type: 'Literal', + value: 0 + } + }] + } + }; + } - // Replace the update expression with the assignment expression - Object.assign(node, assignmentExpr); - delete node.prefix; - this.BinaryExpression(node.right, _state, [...ancestors, node]); - this.AssignmentExpression(node, _state, ancestors); - }, - ForStatement(node, _state, ancestors) { - if (ancestors.some(nodeIsUniform)) { return; } - - // Transform for statement into strandsFor() call - // for (init; test; update) body -> strandsFor(initCb, conditionCb, updateCb, bodyCb, initialVars) - - // Create the initial callback from the for loop's init - let initialFunction; - if (node.init && node.init.type === 'VariableDeclaration') { - // Handle: for (let i = 0; ...) - const declaration = node.init.declarations[0]; - let initValue = declaration.init; - - const initAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: initValue }] }; - initValue = initAst.body[0].expression; - - initialFunction = { - type: 'ArrowFunctionExpression', - params: [], - body: { - type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: initValue - }] - } - }; - } else { - // Handle other cases - return a default value - initialFunction = { - type: 'ArrowFunctionExpression', - params: [], - body: { - type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: { - type: 'Literal', - value: 0 - } - }] - } - }; - } + // Create the condition callback + let conditionBody = node.test || { type: 'Literal', value: true }; + // Replace loop variable references with the parameter + if (node.init?.type === 'VariableDeclaration') { + const loopVarName = node.init.declarations[0].id.name; + conditionBody = this.replaceIdentifierReferences(conditionBody, loopVarName, 'loopVar'); + } + const conditionAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: conditionBody }] }; + conditionBody = conditionAst.body[0].expression; - // Create the condition callback - let conditionBody = node.test || { type: 'Literal', value: true }; + const conditionFunction = { + type: 'ArrowFunctionExpression', + params: [{ type: 'Identifier', name: 'loopVar' }], + body: conditionBody + }; + + // Create the update callback + let updateFunction; + if (node.update) { + let updateExpr = node.update; // Replace loop variable references with the parameter if (node.init?.type === 'VariableDeclaration') { const loopVarName = node.init.declarations[0].id.name; - conditionBody = this.replaceIdentifierReferences(conditionBody, loopVarName, 'loopVar'); + updateExpr = this.replaceIdentifierReferences(updateExpr, loopVarName, 'loopVar'); } - const conditionAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: conditionBody }] }; - conditionBody = conditionAst.body[0].expression; - - const conditionFunction = { + const updateAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: updateExpr }] }; + // const nonControlFlowCallbacks = { ...ASTCallbacks }; + // delete nonControlFlowCallbacks.IfStatement; + // delete nonControlFlowCallbacks.ForStatement; + // ancestor(updateAst, nonControlFlowCallbacks, undefined, _state); + updateExpr = updateAst.body[0].expression; + + updateFunction = { type: 'ArrowFunctionExpression', params: [{ type: 'Identifier', name: 'loopVar' }], - body: conditionBody + body: { + type: 'BlockStatement', + body: [{ + type: 'ReturnStatement', + argument: updateExpr + }] + } }; - - // Create the update callback - let updateFunction; - if (node.update) { - let updateExpr = node.update; - // Replace loop variable references with the parameter - if (node.init?.type === 'VariableDeclaration') { - const loopVarName = node.init.declarations[0].id.name; - updateExpr = this.replaceIdentifierReferences(updateExpr, loopVarName, 'loopVar'); + } else { + updateFunction = { + type: 'ArrowFunctionExpression', + params: [{ type: 'Identifier', name: 'loopVar' }], + body: { + type: 'BlockStatement', + body: [{ + type: 'ReturnStatement', + argument: { type: 'Identifier', name: 'loopVar' } + }] } - const updateAst = { type: 'Program', body: [{ type: 'ExpressionStatement', expression: updateExpr }] }; - // const nonControlFlowCallbacks = { ...ASTCallbacks }; - // delete nonControlFlowCallbacks.IfStatement; - // delete nonControlFlowCallbacks.ForStatement; - // ancestor(updateAst, nonControlFlowCallbacks, undefined, _state); - updateExpr = updateAst.body[0].expression; - - updateFunction = { - type: 'ArrowFunctionExpression', - params: [{ type: 'Identifier', name: 'loopVar' }], - body: { - type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: updateExpr - }] - } - }; - } else { - updateFunction = { - type: 'ArrowFunctionExpression', - params: [{ type: 'Identifier', name: 'loopVar' }], - body: { - type: 'BlockStatement', - body: [{ - type: 'ReturnStatement', - argument: { type: 'Identifier', name: 'loopVar' } - }] - } - }; - } - - // Create the body callback - let bodyBlock = node.body.type === 'BlockStatement' ? node.body : { - type: 'BlockStatement', - body: [node.body] }; + } - // Replace loop variable references in the body - if (node.init?.type === 'VariableDeclaration') { - const loopVarName = node.init.declarations[0].id.name; - bodyBlock = this.replaceIdentifierReferences(bodyBlock, loopVarName, 'loopVar'); - } + // Create the body callback + let bodyBlock = node.body.type === 'BlockStatement' ? node.body : { + type: 'BlockStatement', + body: [node.body] + }; - const bodyFunction = { - type: 'ArrowFunctionExpression', - params: [ - { type: 'Identifier', name: 'loopVar' }, - { type: 'Identifier', name: 'vars' } - ], - body: bodyBlock - }; + // Replace loop variable references in the body + if (node.init?.type === 'VariableDeclaration') { + const loopVarName = node.init.declarations[0].id.name; + bodyBlock = this.replaceIdentifierReferences(bodyBlock, loopVarName, 'loopVar'); + } - // Analyze which outer scope variables are assigned in the loop body - const assignedVars = new Set(); - const analyzeBlock = (body, parentLocalVars = new Set()) => { - if (body.type !== 'BlockStatement') return; - - // First pass: collect variable declarations within this block - const localVars = new Set([...parentLocalVars]); - for (const stmt of body.body) { - if (stmt.type === 'VariableDeclaration') { - for (const decl of stmt.declarations) { - if (decl.id.type === 'Identifier') { - localVars.add(decl.id.name); - } + const bodyFunction = { + type: 'ArrowFunctionExpression', + params: [ + { type: 'Identifier', name: 'loopVar' }, + { type: 'Identifier', name: 'vars' } + ], + body: bodyBlock + }; + + // Analyze which outer scope variables are assigned in the loop body + const assignedVars = new Set(); + const analyzeBlock = (body, parentLocalVars = new Set()) => { + if (body.type !== 'BlockStatement') return; + + // First pass: collect variable declarations within this block + const localVars = new Set([...parentLocalVars]); + for (const stmt of body.body) { + if (stmt.type === 'VariableDeclaration') { + for (const decl of stmt.declarations) { + if (decl.id.type === 'Identifier') { + localVars.add(decl.id.name); } } } + } - // Second pass: find assignments to non-local variables - for (const stmt of body.body) { - if (stmt.type === 'ExpressionStatement' && + // Second pass: find assignments to non-local variables + for (const stmt of body.body) { + if (stmt.type === 'ExpressionStatement' && stmt.expression.type === 'AssignmentExpression') { - const left = stmt.expression.left; - if (left.type === 'Identifier') { - // Direct variable assignment: x = value - if (!localVars.has(left.name)) { - assignedVars.add(left.name); - } - } else if (left.type === 'MemberExpression' && + const left = stmt.expression.left; + if (left.type === 'Identifier') { + // Direct variable assignment: x = value + if (!localVars.has(left.name)) { + assignedVars.add(left.name); + } + } else if (left.type === 'MemberExpression' && left.object.type === 'Identifier') { - // Property assignment: obj.prop = value (includes swizzles) - if (!localVars.has(left.object.name)) { - assignedVars.add(left.object.name); - } + // Property assignment: obj.prop = value (includes swizzles) + if (!localVars.has(left.object.name)) { + assignedVars.add(left.object.name); } - } else if (stmt.type === 'BlockStatement') { - // Recursively analyze nested block statements, passing down local vars - analyzeBlock(stmt, localVars); } + } else if (stmt.type === 'BlockStatement') { + // Recursively analyze nested block statements, passing down local vars + analyzeBlock(stmt, localVars); } - }; + } + }; - analyzeBlock(bodyFunction.body); - - if (assignedVars.size > 0) { - // Add copying, reference replacement, and return statements similar to if statements - const addCopyingAndReturn = (functionBody, varsToReturn) => { - if (functionBody.type === 'BlockStatement') { - const tempVarMap = new Map(); - const copyStatements = []; - - for (const varName of varsToReturn) { - const tempName = `__copy_${varName}_${blockVarCounter++}`; - tempVarMap.set(varName, tempName); - - copyStatements.push({ - type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: tempName }, - init: { - type: 'CallExpression', - callee: { + analyzeBlock(bodyFunction.body); + + if (assignedVars.size > 0) { + // Add copying, reference replacement, and return statements similar to if statements + const addCopyingAndReturn = (functionBody, varsToReturn) => { + if (functionBody.type === 'BlockStatement') { + const tempVarMap = new Map(); + const copyStatements = []; + + for (const varName of varsToReturn) { + const tempName = `__copy_${varName}_${blockVarCounter++}`; + tempVarMap.set(varName, tempName); + + copyStatements.push({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { type: 'Identifier', name: tempName }, + init: { + type: 'CallExpression', + callee: { + type: 'MemberExpression', + object: { type: 'MemberExpression', - object: { - type: 'MemberExpression', - object: { type: 'Identifier', name: 'vars' }, - property: { type: 'Identifier', name: varName }, - computed: false - }, - property: { type: 'Identifier', name: 'copy' }, + object: { type: 'Identifier', name: 'vars' }, + property: { type: 'Identifier', name: varName }, computed: false }, - arguments: [] - } - }], - kind: 'let' - }); - } - - // Replace references to original variables with temp variables - const replaceReferences = (node) => { - if (!node || typeof node !== 'object') return; - if (node.type === 'Identifier' && tempVarMap.has(node.name)) { - node.name = tempVarMap.get(node.name); - } - - for (const key in node) { - if (node.hasOwnProperty(key) && key !== 'parent') { - if (Array.isArray(node[key])) { - node[key].forEach(replaceReferences); - } else if (typeof node[key] === 'object') { - replaceReferences(node[key]); - } + property: { type: 'Identifier', name: 'copy' }, + computed: false + }, + arguments: [] } - } - }; - - functionBody.body.forEach(replaceReferences); - functionBody.body.unshift(...copyStatements); - - // Add return statement - const returnObj = { - type: 'ObjectExpression', - properties: Array.from(varsToReturn).map(varName => ({ - type: 'Property', - key: { type: 'Identifier', name: varName }, - value: { type: 'Identifier', name: tempVarMap.get(varName) }, - kind: 'init', - computed: false, - shorthand: false - })) - }; - - functionBody.body.push({ - type: 'ReturnStatement', - argument: returnObj + }], + kind: 'let' }); } - }; - addCopyingAndReturn(bodyFunction.body, assignedVars); + // Replace references to original variables with temp variables + const replaceReferences = node => { + if (!node || typeof node !== 'object') return; + if (node.type === 'Identifier' && tempVarMap.has(node.name)) { + node.name = tempVarMap.get(node.name); + } + + for (const key in node) { + if (node.hasOwnProperty(key) && key !== 'parent') { + if (Array.isArray(node[key])) { + node[key].forEach(replaceReferences); + } else if (typeof node[key] === 'object') { + replaceReferences(node[key]); + } + } + } + }; - // Create block variable and assignments similar to if statements - const blockVar = `__block_${blockVarCounter++}`; - const statements = []; + functionBody.body.forEach(replaceReferences); + functionBody.body.unshift(...copyStatements); - // Create initial vars object from assigned variables - const initialVarsProperties = []; - for (const varName of assignedVars) { - initialVarsProperties.push({ - type: 'Property', - key: { type: 'Identifier', name: varName }, - value: { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: '__p5.strandsNode', - }, - arguments: [ - { type: 'Identifier', name: varName }, - ], - }, - kind: 'init', - method: false, - shorthand: false, - computed: false + // Add return statement + const returnObj = { + type: 'ObjectExpression', + properties: Array.from(varsToReturn).map(varName => ({ + type: 'Property', + key: { type: 'Identifier', name: varName }, + value: { type: 'Identifier', name: tempVarMap.get(varName) }, + kind: 'init', + computed: false, + shorthand: false + })) + }; + + functionBody.body.push({ + type: 'ReturnStatement', + argument: returnObj }); } + }; - const initialVarsObject = { - type: 'ObjectExpression', - properties: initialVarsProperties - }; - - // Create the strandsFor call - const callExpression = { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: '__p5.strandsFor' + addCopyingAndReturn(bodyFunction.body, assignedVars); + + // Create block variable and assignments similar to if statements + const blockVar = `__block_${blockVarCounter++}`; + const statements = []; + + // Create initial vars object from assigned variables + const initialVarsProperties = []; + for (const varName of assignedVars) { + initialVarsProperties.push({ + type: 'Property', + key: { type: 'Identifier', name: varName }, + value: { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__p5.strandsNode' + }, + arguments: [ + { type: 'Identifier', name: varName } + ] }, - arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, initialVarsObject] - }; - - statements.push({ - type: 'VariableDeclaration', - declarations: [{ - type: 'VariableDeclarator', - id: { type: 'Identifier', name: blockVar }, - init: callExpression - }], - kind: 'const' + kind: 'init', + method: false, + shorthand: false, + computed: false }); + } - // Add assignments back to original variables - for (const varName of assignedVars) { - statements.push({ - type: 'ExpressionStatement', - expression: { - type: 'AssignmentExpression', - operator: '=', - left: { type: 'Identifier', name: varName }, - right: { - type: 'MemberExpression', - object: { type: 'Identifier', name: blockVar }, - property: { type: 'Identifier', name: varName }, - computed: false - } - } - }); - } + const initialVarsObject = { + type: 'ObjectExpression', + properties: initialVarsProperties + }; - node.type = 'BlockStatement'; - node.body = statements; - } else { - // No assignments, just replace with call expression - node.type = 'ExpressionStatement'; - node.expression = { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: '__p5.strandsFor' - }, - arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, { - type: 'ObjectExpression', - properties: [] - }] - }; + // Create the strandsFor call + const callExpression = { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__p5.strandsFor' + }, + arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, initialVarsObject] + }; + + statements.push({ + type: 'VariableDeclaration', + declarations: [{ + type: 'VariableDeclarator', + id: { type: 'Identifier', name: blockVar }, + init: callExpression + }], + kind: 'const' + }); + + // Add assignments back to original variables + for (const varName of assignedVars) { + statements.push({ + type: 'ExpressionStatement', + expression: { + type: 'AssignmentExpression', + operator: '=', + left: { type: 'Identifier', name: varName }, + right: { + type: 'MemberExpression', + object: { type: 'Identifier', name: blockVar }, + property: { type: 'Identifier', name: varName }, + computed: false + } + } + }); } - delete node.init; - delete node.test; - delete node.update; - }, + node.type = 'BlockStatement'; + node.body = statements; + } else { + // No assignments, just replace with call expression + node.type = 'ExpressionStatement'; + node.expression = { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: '__p5.strandsFor' + }, + arguments: [initialFunction, conditionFunction, updateFunction, bodyFunction, { + type: 'ObjectExpression', + properties: [] + }] + }; + } - // Helper method to replace identifier references in AST nodes - replaceIdentifierReferences(node, oldName, newName) { - if (!node || typeof node !== 'object') return node; + delete node.init; + delete node.test; + delete node.update; + }, - const replaceInNode = (n) => { - if (!n || typeof n !== 'object') return n; + // Helper method to replace identifier references in AST nodes + replaceIdentifierReferences(node, oldName, newName) { + if (!node || typeof node !== 'object') return node; - if (n.type === 'Identifier' && n.name === oldName) { - return { ...n, name: newName }; - } + const replaceInNode = n => { + if (!n || typeof n !== 'object') return n; - // Create a copy and recursively process properties - const newNode = { ...n }; - for (const key in n) { - if (n.hasOwnProperty(key) && key !== 'parent') { - if (Array.isArray(n[key])) { - newNode[key] = n[key].map(replaceInNode); - } else if (typeof n[key] === 'object') { - newNode[key] = replaceInNode(n[key]); - } + if (n.type === 'Identifier' && n.name === oldName) { + return { ...n, name: newName }; + } + + // Create a copy and recursively process properties + const newNode = { ...n }; + for (const key in n) { + if (n.hasOwnProperty(key) && key !== 'parent') { + if (Array.isArray(n[key])) { + newNode[key] = n[key].map(replaceInNode); + } else if (typeof n[key] === 'object') { + newNode[key] = replaceInNode(n[key]); } } - return newNode; - }; + } + return newNode; + }; - return replaceInNode(node); - } + return replaceInNode(node); } - export function transpileStrandsToJS(p5, sourceString, srcLocations, scope) { - const ast = parse(sourceString, { - ecmaVersion: 2021, - locations: srcLocations - }); +}; +export function transpileStrandsToJS(p5, sourceString, srcLocations, scope) { + const ast = parse(sourceString, { + ecmaVersion: 2021, + locations: srcLocations + }); // First pass: transform everything except if/for statements using normal ancestor traversal - const nonControlFlowCallbacks = { ...ASTCallbacks }; - delete nonControlFlowCallbacks.IfStatement; - delete nonControlFlowCallbacks.ForStatement; - ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {} }); - // Second pass: transform if/for statements in post-order using recursive traversal - const postOrderControlFlowTransform = { - IfStatement(node, state, c) { - // First recursively process children - if (node.test) c(node.test, state); - if (node.consequent) c(node.consequent, state); - if (node.alternate) c(node.alternate, state); - // Then apply the transformation to this node - ASTCallbacks.IfStatement(node, state, []); - }, - ForStatement(node, state, c) { - // First recursively process children - if (node.init) c(node.init, state); - if (node.test) c(node.test, state); - if (node.update) c(node.update, state); - if (node.body) c(node.body, state); - // Then apply the transformation to this node - ASTCallbacks.ForStatement(node, state, []); - } - }; - recursive(ast, { varyings: {} }, postOrderControlFlowTransform); - const transpiledSource = escodegen.generate(ast); - const scopeKeys = Object.keys(scope); - const internalStrandsCallback = new Function( - // Create a parameter called __p5, not just p5, because users of instance mode - // may pass in a variable called p5 as a scope variable. If we rely on a variable called - // p5, then the scope variable called p5 might accidentally override internal function - // calls to p5 static methods. - '__p5', - ...scopeKeys, - transpiledSource + const nonControlFlowCallbacks = { ...ASTCallbacks }; + delete nonControlFlowCallbacks.IfStatement; + delete nonControlFlowCallbacks.ForStatement; + ancestor(ast, nonControlFlowCallbacks, undefined, { varyings: {} }); + // Second pass: transform if/for statements in post-order using recursive traversal + const postOrderControlFlowTransform = { + IfStatement(node, state, c) { + // First recursively process children + if (node.test) c(node.test, state); + if (node.consequent) c(node.consequent, state); + if (node.alternate) c(node.alternate, state); + // Then apply the transformation to this node + ASTCallbacks.IfStatement(node, state, []); + }, + ForStatement(node, state, c) { + // First recursively process children + if (node.init) c(node.init, state); + if (node.test) c(node.test, state); + if (node.update) c(node.update, state); + if (node.body) c(node.body, state); + // Then apply the transformation to this node + ASTCallbacks.ForStatement(node, state, []); + } + }; + recursive(ast, { varyings: {} }, postOrderControlFlowTransform); + const transpiledSource = escodegen.generate(ast); + const scopeKeys = Object.keys(scope); + const internalStrandsCallback = new Function( + // Create a parameter called __p5, not just p5, because users of instance mode + // may pass in a variable called p5 as a scope variable. If we rely on a variable called + // p5, then the scope variable called p5 might accidentally override internal function + // calls to p5 static methods. + '__p5', + ...scopeKeys, + transpiledSource .slice( transpiledSource.indexOf('{') + 1, transpiledSource.lastIndexOf('}') ).replaceAll(';', '') - ); - return () => internalStrandsCallback(p5, ...scopeKeys.map(key => scope[key])); - } + ); + return () => internalStrandsCallback(p5, ...scopeKeys.map(key => scope[key])); +} diff --git a/src/type/textCore.js b/src/type/textCore.js index cd55559b12..2edbd63b9b 100644 --- a/src/type/textCore.js +++ b/src/type/textCore.js @@ -1910,10 +1910,10 @@ function textCore(p5, fn) { // When width is not provided (e.g., fontBounds path), fall back to the widest line. const maxWidth = boxes.reduce((m, b) => Math.max(m, b.w || 0), 0); - boxes.forEach((bb) => { - const w = (width ?? maxWidth); - bb.x += p5.Renderer2D.prototype._xAlignOffset.call(this, textAlign, w); - }); + boxes.forEach(bb => { + const w = (width ?? maxWidth); + bb.x += p5.Renderer2D.prototype._xAlignOffset.call(this, textAlign, w); + }); } // adjust the bounding boxes based on vert. text alignment diff --git a/test/unit/assets/array.js b/test/unit/assets/array.js index 9e3ff56f24..54a39fa812 100644 --- a/test/unit/assets/array.js +++ b/test/unit/assets/array.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-undef + jsonpCallbackFunction([ { id: 0, diff --git a/test/unit/assets/object.js b/test/unit/assets/object.js index 25738a7a5c..84f31fc172 100644 --- a/test/unit/assets/object.js +++ b/test/unit/assets/object.js @@ -1,4 +1,4 @@ -// eslint-disable-next-line no-undef + jsonpCallbackFunction({ id: 0, species: 'Panthera leo', diff --git a/test/unit/core/param_errors.js b/test/unit/core/param_errors.js index e37d2c4d8e..7397f32a0a 100644 --- a/test/unit/core/param_errors.js +++ b/test/unit/core/param_errors.js @@ -29,7 +29,7 @@ suite('Validate Params', function () { return 'mock p5.Graphics'; }, _error: () => {}, - decorateHelper: () => {}, + decorateHelper: () => {} }; const mockP5Prototype = {}; diff --git a/test/unit/spec.js b/test/unit/spec.js index 7dd87068a7..fd9bbc759f 100644 --- a/test/unit/spec.js +++ b/test/unit/spec.js @@ -55,7 +55,7 @@ var spec = { // to omit some for speed if they should only be run manually. 'webgl', 'typography', - 'shape_modes', + 'shape_modes' ] }; document.write( diff --git a/test/unit/type/p5.Font.js b/test/unit/type/p5.Font.js index 395dd9dc03..6ab6992b98 100644 --- a/test/unit/type/p5.Font.js +++ b/test/unit/type/p5.Font.js @@ -1,5 +1,5 @@ import p5 from '../../../src/app.js'; -import {_sanitizeFontName} from '../../../src/type/p5.Font.js'; +import { _sanitizeFontName } from '../../../src/type/p5.Font.js'; suite('p5.Font', function () { var myp5; @@ -41,14 +41,14 @@ suite('p5.Font', function () { assert.property(bbox, 'h'); }); - test('fontBounds no NaN (multiline + CENTER)', async () => { - const pFont = await myp5.loadFont(fontFile); - myp5.textAlign(myp5.CENTER, myp5.CENTER); - const b = pFont.fontBounds('Hello,\nWorld!', 50, 50, 24); - expect(b.x).not.toBeNaN(); - expect(b.y).not.toBeNaN(); - expect(b.w).not.toBeNaN(); - expect(b.h).not.toBeNaN(); + test('fontBounds no NaN (multiline + CENTER)', async () => { + const pFont = await myp5.loadFont(fontFile); + myp5.textAlign(myp5.CENTER, myp5.CENTER); + const b = pFont.fontBounds('Hello,\nWorld!', 50, 50, 24); + expect(b.x).not.toBeNaN(); + expect(b.y).not.toBeNaN(); + expect(b.w).not.toBeNaN(); + expect(b.h).not.toBeNaN(); }); suite('textToPoints', () => { diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 11a5797906..5f704db131 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -543,7 +543,7 @@ suite('p5.Shader', function() { myp5.createCanvas(100, 50, myp5.WEBGL); const testShader = myp5.baseMaterialShader().modify(() => { myp5.getPixelInputs(inputs => { - debugger + debugger; const uv = inputs.texCoord; const condition = uv.x > 0.5; // left half false, right half true let color = myp5.float(0.0); @@ -1010,14 +1010,14 @@ suite('p5.Shader', function() { const loopResult = myp5.strandsFor( () => 0, - (loopVar) => loopVar < 4, - (loopVar) => loopVar + 1, + loopVar => loopVar < 4, + loopVar => loopVar + 1, (loopVar, vars) => { let newValue = vars.accumulator.copy(); newValue = newValue + 0.125; return { accumulator: newValue }; }, - { accumulator: accumulator.copy() }, + { accumulator: accumulator.copy() } ); accumulator = loopResult.accumulator; diff --git a/utils/convert.mjs b/utils/convert.mjs index 00987eea32..e9262080d1 100644 --- a/utils/convert.mjs +++ b/utils/convert.mjs @@ -12,10 +12,10 @@ const data = JSON.parse(fs.readFileSync(path.join(__dirname, '../docs/data.json' // Strategy for HTML documentation output (maintains exact convert.mjs behavior) const htmlStrategy = { shouldSkipEntry: () => false, // Don't skip anything (including Foundation) - - processDescription: (desc) => descriptionString(desc), - - processType: (type) => typeObject(type) + + processDescription: desc => descriptionString(desc), + + processType: type => typeObject(type) }; const processed = processData(data, htmlStrategy); diff --git a/utils/data-processor.mjs b/utils/data-processor.mjs index 99f3c44d91..a532225d88 100644 --- a/utils/data-processor.mjs +++ b/utils/data-processor.mjs @@ -8,7 +8,7 @@ import { getParams } from './shared-helpers.mjs'; export function processData(rawData, strategy) { const allData = getAllEntries(rawData); - + const processed = { modules: {}, classes: {}, @@ -21,7 +21,7 @@ export function processData(rawData, strategy) { const fileModuleInfo = {}; const modules = {}; const submodules = {}; - + for (const entry of allData) { if (entry.tags?.some(tag => tag.title === 'module')) { const module = entry.tags.find(tag => tag.title === 'module').name; @@ -115,21 +115,21 @@ export function processData(rawData, strategy) { (entry.properties && entry.properties.length > 0 && entry.properties[0].title === 'property') || entry.tags?.some(tag => tag.title === 'property')) { const { module, submodule, forEntry } = getModuleInfo(entry); - + // Apply strategy filter if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule, forEntry })) { continue; } const name = entry.name || (entry.properties || [])[0]?.name; - + // Skip duplicates based on name + class combination const key = `${name}:${forEntry || 'p5'}`; if (processedNames.has(key)) { continue; } processedNames.add(key); - + // For properties, get type from the property definition const propertyType = entry.properties?.[0]?.type || entry.type; @@ -158,7 +158,7 @@ export function processData(rawData, strategy) { for (const entry of allData) { if (entry.kind === 'class') { const { module, submodule } = getModuleInfo(entry); - + // Apply strategy filter if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule })) { continue; @@ -201,7 +201,7 @@ export function processData(rawData, strategy) { for (const entry of allData) { if (entry.kind === 'function' && entry.properties?.length === 0) { const { module, submodule, forEntry } = getModuleInfo(entry); - + // Apply strategy filter if (strategy.shouldSkipEntry && strategy.shouldSkipEntry(entry, { module, submodule, forEntry })) { continue; @@ -217,7 +217,7 @@ export function processData(rawData, strategy) { } const className = memberof || forEntry || 'p5'; - + // Skip private methods if (entry.name.startsWith('_')) continue; diff --git a/utils/patch.mjs b/utils/patch.mjs index 277d7808a8..9024e7772f 100644 --- a/utils/patch.mjs +++ b/utils/patch.mjs @@ -3,18 +3,18 @@ import fs from 'fs'; export function applyPatches() { const cache = {}; const patched = {}; - + const replace = (path, src, dest) => { if (Array.isArray(path)) { path.forEach(path => replace(path, src, dest)); return; } try { - if (!path.startsWith("types/")) - path = "types/" + path; + if (!path.startsWith('types/')) + path = 'types/' + path; const before = patched[path] ?? - (cache[path] ??= fs.readFileSync("./" + path, { encoding: 'utf-8' })); + (cache[path] ??= fs.readFileSync('./' + path, { encoding: 'utf-8' })); const after = before.replaceAll(src, dest); if (after !== before) @@ -28,8 +28,8 @@ export function applyPatches() { // TODO: Handle this better in the docs instead of patching replace( - "p5.d.ts", - "constructor(detailX?: number, detailY?: number, callback?: Function);", + 'p5.d.ts', + 'constructor(detailX?: number, detailY?: number, callback?: Function);', `constructor( detailX?: number, detailY?: number, @@ -39,15 +39,15 @@ export function applyPatches() { // https://github.com/p5-types/p5.ts/issues/31 // #todo: add readonly to appropriate array params, either here or in doc comments replace( - ["p5.d.ts", "global.d.ts"], - "random(choices: any[]): any;", - "random(choices: readonly T[]): T;" + ['p5.d.ts', 'global.d.ts'], + 'random(choices: any[]): any;', + 'random(choices: readonly T[]): T;' ); replace( 'p5.d.ts', 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): object[][];', - 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): { x: number; y: number; alpha: number }[][];', + 'textToContours(str: string, x: number, y: number, options?: { sampleFactor?: number; simplifyThreshold?: number }): { x: number; y: number; alpha: number }[][];' ); replace( @@ -71,7 +71,7 @@ export function applyPatches() { 'class __Graphics extends p5.Element {', `class __Graphics extends p5.Element { elt: HTMLCanvasElement; - `, + ` ); // Type .elt more specifically for audio and video elements @@ -80,24 +80,24 @@ export function applyPatches() { `class MediaElement extends Element { elt: HTMLAudioElement | HTMLVideoElement;`, `class MediaElement extends Element { - elt: T;`, + elt: T;` ); replace( ['p5.d.ts', 'global.d.ts'], /createAudio\(src\?: string \| string\[\], callback\?: Function\): ([pP]5)\.MediaElement;/g, - 'createAudio(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;', + 'createAudio(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;' ); replace( ['p5.d.ts', 'global.d.ts'], /createVideo\(src\?: string \| string\[\], callback\?: Function\): ([pP]5)\.MediaElement;/g, - 'createVideo(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;', + 'createVideo(src?: string | string[], callback?: (video: $1.MediaElement) => any): $1.MediaElement;' ); // More callback types replace( ['p5.d.ts', 'global.d.ts'], /createFileInput\(callback: Function, multiple\?: boolean\): ([pP]5)\.Element;/g, - 'createFileInput(callback: (input: $1.File) => any, multiple?: boolean): $1.Element;', + 'createFileInput(callback: (input: $1.File) => any, multiple?: boolean): $1.Element;' ); replace( ['p5.d.ts', 'global.d.ts'], @@ -114,12 +114,12 @@ export function applyPatches() { replace( 'p5.d.ts', 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): object;', - 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };', + 'fontBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };' ); replace( 'p5.d.ts', 'textBounds(str: string, x: number, y: number, width?: number, height?: number): object;', - 'textBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };', + 'textBounds(str: string, x: number, y: number, width?: number, height?: number): { x: number; y: number; w: number; h: number };' ); // Document Typr @@ -158,7 +158,7 @@ export function applyPatches() { for (const [path, data] of Object.entries(patched)) { try { console.log(`Patched ${path}`); - fs.writeFileSync("./" + path, data); + fs.writeFileSync('./' + path, data); } catch (err) { console.error(err); } diff --git a/utils/typescript.mjs b/utils/typescript.mjs index 9d9841100a..7cad4b1054 100644 --- a/utils/typescript.mjs +++ b/utils/typescript.mjs @@ -74,8 +74,8 @@ function processStrandsFunctions() { pascalTypeName = typeInfo.fnName .slice(0, 2).toUpperCase() + typeInfo.fnName - .slice(2) - .toLowerCase(); + .slice(2) + .toLowerCase(); } else { pascalTypeName = typeInfo.fnName.charAt(0).toUpperCase() + typeInfo.fnName.slice(1).toLowerCase(); @@ -169,7 +169,7 @@ function convertTypeToTypeScript(typeNode, options = {}) { // Handle primitive types const primitiveTypes = { 'String': 'string', - 'Number': 'number', + 'Number': 'number', 'Integer': 'number', 'Boolean': 'boolean', 'Void': 'void', @@ -209,10 +209,10 @@ function convertTypeToTypeScript(typeNode, options = {}) { if (isConstantDef) { return convertTypeToTypeScript(typedefs[typeName], options); } else { - return `typeof p5.${typeName}` + return `typeof p5.${typeName}`; } } else { - return `Symbol`; + return 'Symbol'; } } @@ -296,7 +296,7 @@ const typescriptStrategy = { return context.module === 'Foundation'; }, - processDescription: (desc) => descriptionStringForTypeScript(desc), + processDescription: desc => descriptionStringForTypeScript(desc), processType: (type, param) => { // Return an object with the original type preserved @@ -370,14 +370,14 @@ function generateObjectInterface(param, allParams, options = {}) { // First, check if the parameter has a properties array (JSDoc properties field) if (param.properties && Array.isArray(param.properties)) { - nestedParams = param.properties.filter(prop => + nestedParams = param.properties.filter(prop => prop.name && prop.name.startsWith(param.name + '.') ); } // Fallback: Look for nested parameters with dot notation in allParams if (nestedParams.length === 0) { - nestedParams = allParams.filter(p => + nestedParams = allParams.filter(p => p.name && p.name.startsWith(param.name + '.') && p.name !== param.name ); } @@ -511,7 +511,7 @@ function generateClassDeclaration(classData) { const methodNames = new Set(classMethodsList.map(method => method.name)); // Class properties - const classProperties = processed.classitems.filter(item => + const classProperties = processed.classitems.filter(item => item.class === originalClassName && item.itemtype === 'property' ); diff --git a/visual-report.js b/visual-report.js index 12e21b6261..a8593af9f3 100644 --- a/visual-report.js +++ b/visual-report.js @@ -6,13 +6,13 @@ async function generateVisualReport() { const expectedDir = path.join(process.cwd(), 'test/unit/visual/screenshots'); const actualDir = path.join(process.cwd(), 'test/unit/visual/actual-screenshots'); const outputFile = path.join(process.cwd(), 'test/unit/visual/visual-report.html'); - + // Make sure the output directory exists const outputDir = path.dirname(outputFile); if (!fs.existsSync(outputDir)) { fs.mkdirSync(outputDir, { recursive: true }); } - + // Function to read image file and convert to data URL function imageToDataURL(filePath) { try { @@ -24,7 +24,7 @@ async function generateVisualReport() { return null; } } - + // Create a lookup map for actual screenshots function createActualScreenshotMap() { const actualMap = new Map(); @@ -32,33 +32,33 @@ async function generateVisualReport() { console.warn(`Actual screenshots directory does not exist: ${actualDir}`); return actualMap; } - + const files = fs.readdirSync(actualDir); for (const file of files) { if (file.endsWith('.png') && !file.endsWith('-diff.png')) { actualMap.set(file, path.join(actualDir, file)); } } - + return actualMap; } - + const actualScreenshotMap = createActualScreenshotMap(); - + // Recursively find all test cases function findTestCases(dir, prefix = '') { const testCases = []; - + if (!fs.existsSync(path.join(dir, prefix))) { console.warn(`Directory does not exist: ${path.join(dir, prefix)}`); return testCases; } - + const entries = fs.readdirSync(path.join(dir, prefix), { withFileTypes: true }); - + for (const entry of entries) { const fullPath = path.join(prefix, entry.name); - + if (entry.isDirectory()) { // Recursively search subdirectories testCases.push(...findTestCases(dir, fullPath)); @@ -66,42 +66,42 @@ async function generateVisualReport() { // Found a test case const metadataPath = path.join(dir, fullPath); let metadata; - + try { metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8')); } catch (error) { console.error(`Failed to read metadata: ${metadataPath}`, error); continue; } - + const testDir = path.dirname(fullPath); - + const test = { name: testDir, numScreenshots: metadata.numScreenshots || 0, screenshots: [] }; - + // Create flattened name for lookup const flattenedName = testDir.replace(SLASH_REGEX, '-'); - + // Collect all screenshots for this test for (let i = 0; i < test.numScreenshots; i++) { const screenshotName = i.toString().padStart(3, '0') + '.png'; const expectedPath = path.join(dir, testDir, screenshotName); - + // Use flattened name for actual screenshots const actualScreenshotName = `${flattenedName}-${i.toString().padStart(3, '0')}.png`; const actualPath = actualScreenshotMap.get(actualScreenshotName) || null; - + // Use flattened name for diff image const diffScreenshotName = `${flattenedName}-${i.toString().padStart(3, '0')}-diff.png`; const diffPath = path.join(actualDir, diffScreenshotName); - + const hasExpected = fs.existsSync(expectedPath); const hasActual = actualPath && fs.existsSync(actualPath); const hasDiff = fs.existsSync(diffPath); - + const screenshot = { index: i, expectedImage: hasExpected ? imageToDataURL(expectedPath) : null, @@ -109,41 +109,41 @@ async function generateVisualReport() { diffImage: hasDiff ? imageToDataURL(diffPath) : null, passed: hasExpected && hasActual && !hasDiff }; - + test.screenshots.push(screenshot); } - + // Don't add tests with no screenshots if (test.screenshots.length > 0) { testCases.push(test); } } } - + return testCases; } - + // Find all test cases from the expected directory const testCases = findTestCases(expectedDir); - + if (testCases.length === 0) { console.warn('No test cases found. Check if the expected directory is correct.'); } - + // Count passed/failed tests and screenshots const totalTests = testCases.length; let passedTests = 0; let totalScreenshots = 0; let passedScreenshots = 0; - + for (const test of testCases) { const testPassed = test.screenshots.every(screenshot => screenshot.passed); if (testPassed) passedTests++; - + totalScreenshots += test.screenshots.length; passedScreenshots += test.screenshots.filter(s => s.passed).length; } - + // Generate HTML const html = ` @@ -340,15 +340,15 @@ async function generateVisualReport() {
Expected
- ${screenshot.expectedImage ? - `Expected Result` : - `
No expected image found
`} + ${screenshot.expectedImage ? + `Expected Result` : + '
No expected image found
'}
Actual
- ${screenshot.actualImage ? - `Actual Result` : - `
No actual image found
`} + ${screenshot.actualImage ? + `Actual Result` : + '
No actual image found
'}
${screenshot.diffImage ? `
@@ -401,11 +401,11 @@ async function generateVisualReport() { `; - + // Write HTML to file fs.writeFileSync(outputFile, html); console.log(`Visual test report generated: ${outputFile}`); - + return { totalTests, passedTests,