diff --git a/src/strands/strands_transpiler.js b/src/strands/strands_transpiler.js index 617fae5909..1d9b3084f4 100644 --- a/src/strands/strands_transpiler.js +++ b/src/strands/strands_transpiler.js @@ -2013,17 +2013,19 @@ function runControlFlowPass(ast, uniformCallbackNames) { } function buildStrandsCallback(p5, ast, scope) { - const transpiledSource = escodegen.generate(ast); const scopeKeys = Object.keys(scope); - const match = - /\(?\s*(?:function)?\s*\w*\s*\(([^)]*)\)\s*(?:=>)?\s*{((?:.|\n)*)}\s*;?\s*\)?/.exec( - transpiledSource - ); - if (!match) { - console.log(transpiledSource); + // Source is wrapped in parens before parsing, so the callback is the + // program's single expression. Read params/body from that node so + // `param => { ... }` works after escodegen drops the parentheses. + const fn = ast.body.length === 1 && ast.body[0].expression; + if ( + !fn || + (fn.type !== 'FunctionExpression' && fn.type !== 'ArrowFunctionExpression') + ) { + console.log(escodegen.generate(ast)); throw new Error('Could not parse p5.strands function!'); } - const params = match[1].split(/,\s*/).filter(param => !!param.trim()); + const params = fn.params.map(param => escodegen.generate(param)); let paramVals, paramNames; if (params.length > 0) { paramNames = params; @@ -2032,7 +2034,10 @@ function buildStrandsCallback(p5, ast, scope) { paramNames = scopeKeys; paramVals = scopeKeys.map(key => scope[key]); } - const body = match[2]; + const body = + fn.body.type === 'BlockStatement' + ? fn.body.body.map(statement => escodegen.generate(statement)).join('\n') + : `return ${escodegen.generate(fn.body)};`; try { const internalStrandsCallback = new Function('__p5', ...paramNames, body); // Create a parameter called __p5, not just p5, because users of instance mode diff --git a/test/unit/webgl/p5.Shader.js b/test/unit/webgl/p5.Shader.js index 06791151af..18d1ecf32f 100644 --- a/test/unit/webgl/p5.Shader.js +++ b/test/unit/webgl/p5.Shader.js @@ -506,6 +506,26 @@ suite('p5.Shader', function () { }).not.toThrowError(); }); + test('accepts an arrow function with an unparenthesized parameter', () => { + myp5.createCanvas(5, 5, myp5.WEBGL); + expect(() => { + // Source text, so this does not depend on how the test file is transformed. + const myShader = myp5.buildMaterialShader( + `param => { + const { myp5 } = param; + myp5.getPixelInputs(inputs => { + inputs.color = [1, 0, 0, 1]; + return inputs; + }); + }`, + { myp5 } + ); + myp5.noStroke(); + myp5.shader(myShader); + myp5.plane(myp5.width, myp5.height); + }).not.toThrowError(); + }); + test('buildMaterialShader forwards scope to modify', () => { myp5.createCanvas(5, 5, myp5.WEBGL); expect(() => {