diff --git a/index.html b/index.html index 4c7e052166..f52ac615b8 100644 --- a/index.html +++ b/index.html @@ -66,7 +66,12 @@ - + diff --git a/js/js-export/AST_example.png b/js/js-export/AST_example.png new file mode 100644 index 0000000000..801d1f4d6b Binary files /dev/null and b/js/js-export/AST_example.png differ diff --git a/js/js-export/README.md b/js/js-export/README.md index eb7ec169e5..48facb3d1b 100644 --- a/js/js-export/README.md +++ b/js/js-export/README.md @@ -16,6 +16,11 @@ The code corresponding to the JavaScript Editor widget is in * `generate.js` — contains the utilities for generating code from the block stacks. +* `ast2blocklist.js` - contains the utilities for generating blocks from the code in the editor + +* `ast2blocks.json` - contains the block configurations for code to block conversion. + For detailed information on how to add blocks for code to block support see `./conversion_config_guide.md` + * `ASTutils.js` — contains the utilities for generating the *Abstract Syntax Tree* (*AST*) in `ESTree` specification, for the corresponding code to the block stacks. Used by `JSGenerate` class in `generate.js`. diff --git a/js/js-export/__tests__/ast2blocklist.test.js b/js/js-export/__tests__/ast2blocklist.test.js index 2c2e8b7aed..5f8cd2ea55 100644 --- a/js/js-export/__tests__/ast2blocklist.test.js +++ b/js/js-export/__tests__/ast2blocklist.test.js @@ -22,8 +22,19 @@ const acorn = require("../../../lib/acorn.min"); const { AST2BlockList } = require("../ast2blocklist"); +const fs = require("fs"); +const path = require("path"); describe("AST2BlockList Class", () => { + let config; + + beforeAll(() => { + // Load the config file from parent directory + const configPath = path.join(__dirname, "..", "ast2blocks.json"); + const configContent = fs.readFileSync(configPath, "utf8"); + config = JSON.parse(configContent); + }); + beforeEach(() => { jest.clearAllMocks(); }); @@ -44,7 +55,12 @@ describe("AST2BlockList Class", () => { MusicBlocks.run();`; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - expect(() => AST2BlockList.toTrees(AST)).toThrow("Unsupported AsyncCallExpression: mouse.setMusicInstrument"); + try { + AST2BlockList.toBlockList(AST, config); + } catch (e) { + //TODO: error message should isolate to smallest scope + expect(e.prefix).toEqual("Unsupported statement: "); + } }); // Test unsupported assignment expression should throw an error. @@ -65,7 +81,7 @@ describe("AST2BlockList Class", () => { const AST = acorn.parse(code, { ecmaVersion: 2020 }); try { - AST2BlockList.toTrees(AST); + AST2BlockList.toBlockList(AST, config); } catch (e) { expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported AssignmentExpression: box1 = box2 - 1"); } @@ -87,7 +103,11 @@ describe("AST2BlockList Class", () => { MusicBlocks.run();`; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - expect(() => AST2BlockList.toTrees(AST)).toThrow("Unsupported binary operator: <<"); + try { + AST2BlockList.toBlockList(AST, config); + } catch (e) { + expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported operator <<: 1 << 2"); + } }); // Test unsupported unary operator should throw an error. @@ -106,7 +126,11 @@ describe("AST2BlockList Class", () => { MusicBlocks.run();`; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - expect(() => AST2BlockList.toTrees(AST)).toThrow("Unsupported unary operator: ~"); + try { + AST2BlockList.toBlockList(AST, config); + } catch (e) { + expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported operator ~: ~2"); + } }); // Test calling unsupported function should throw an error. @@ -125,7 +149,11 @@ describe("AST2BlockList Class", () => { MusicBlocks.run();`; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - expect(() => AST2BlockList.toTrees(AST)).toThrow("Unsupported function call: Math.unsupported"); + try { + AST2BlockList.toBlockList(AST, config); + } catch (e) { + expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported operator unsupported: Math.unsupported(1)"); + } }); // Test unsupported argument type should throw an error. @@ -145,7 +173,7 @@ describe("AST2BlockList Class", () => { const AST = acorn.parse(code, { ecmaVersion: 2020 }); try { - AST2BlockList.toTrees(AST); + AST2BlockList.toBlockList(AST, config); } catch (e) { expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported argument type ArrayExpression: [1]"); } @@ -157,7 +185,7 @@ describe("AST2BlockList Class", () => { const AST = acorn.parse(code, { ecmaVersion: 2020 }); try { - AST2BlockList.toTrees(AST); + AST2BlockList.toBlockList(AST, config); } catch (e) { expect(e.prefix + code.substring(e.start, e.end)).toEqual("Unsupported statement: console.log('test');"); } @@ -198,8 +226,7 @@ describe("AST2BlockList Class", () => { ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -244,8 +271,7 @@ describe("AST2BlockList Class", () => { ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -290,8 +316,7 @@ describe("AST2BlockList Class", () => { ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -305,6 +330,7 @@ describe("AST2BlockList Class", () => { return mouse.ENDFLOW; }); box1 = box1 - 1; + box1 = box1 + 1; if (box1 > 0) { await playSol(mouse); } @@ -333,27 +359,29 @@ describe("AST2BlockList Class", () => { [9, ["number", { "value": 2 }], 0, 0, [7]], [10, "decrementOne", 0, 0, [2, 11, 12]], [11, ["namedbox", { "value": "box1" }], 0, 0, [10]], - [12, "if", 0, 0, [10, 13, 16, null]], - [13, "greater", 0, 0, [12, 14, 15]], - [14, ["namedbox", { "value": "box1" }], 0, 0, [13]], - [15, ["number", { "value": 0 }], 0, 0, [13]], - [16, ["nameddo", { "value": "playSol" }], 0, 0, [12, null]], - [17, "start", 500, 200, [null, 18, null]], - [18, ["storein2", { "value": "box1" }], 0, 0, [17, 19, 24]], - [19, "multiply", 0, 0, [18, 20, 23]], - [20, "abs", 0, 0, [19, 21]], - [21, "neg", 0, 0, [20, 22]], - [22, ["number", { "value": 2 }], 0, 0, [21]], - [23, ["number", { "value": 3 }], 0, 0, [19]], - [24, "vspace", 0, 0, [18, 25]], - [25, "settimbre", 0, 0, [24, 26, 27, null]], - [26, ["voicename", { "value": "electronic synth" }], 0, 0, [25]], - [27, ["nameddo", { "value": "playSol" }], 0, 0, [25, null]] + [12, "increment", 0, 0, [10, 13, 14, 15]], + [13, ["namedbox", { "value": "box1" }], 0, 0, [12]], + [14, ["number", { "value": 1 }], 0, 0, [12]], + [15, "if", 0, 0, [12, 16, 19, null]], + [16, "greater", 0, 0, [15, 17, 18]], + [17, ["namedbox", { "value": "box1" }], 0, 0, [16]], + [18, ["number", { "value": 0 }], 0, 0, [16]], + [19, ["nameddo", { "value": "playSol" }], 0, 0, [15, null]], + [20, "start", 500, 200, [null, 21, null]], + [21, ["storein2", { "value": "box1" }], 0, 0, [20, 22, 27]], + [22, "multiply", 0, 0, [21, 23, 26]], + [23, "abs", 0, 0, [22, 24]], + [24, "neg", 0, 0, [23, 25]], + [25, ["number", { "value": 2 }], 0, 0, [24]], + [26, ["number", { "value": 3 }], 0, 0, [22]], + [27, "vspace", 0, 0, [21, 28]], + [28, "settimbre", 0, 0, [27, 29, 30, null]], + [29, ["voicename", { "value": "electronic synth" }], 0, 0, [28]], + [30, ["nameddo", { "value": "playSol" }], 0, 0, [28, null]] ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -607,8 +635,7 @@ describe("AST2BlockList Class", () => { ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -712,8 +739,7 @@ describe("AST2BlockList Class", () => { ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -816,7 +842,7 @@ describe("AST2BlockList Class", () => { [27, ["number", { "value": 1 }], 0, 0, [26]], [28, ["number", { "value": 4 }], 0, 0, [26]], [29, "vspace", 0, 0, [25, 30]], - [30, "hertz", 0, 0, [29, 31, null, null]], + [30, "hertz", 0, 0, [29, 31, null]], [31, ["number", { "value": 392 }], 0, 0, [30]], [32, "newnote", 0, 0, [25, 33, 36, 38]], [33, "divide", 0, 0, [32, 34, 35]], @@ -897,13 +923,12 @@ describe("AST2BlockList Class", () => { [108, ["number", { "value": 2 }], 0, 0, [106]], [109, "vspace", 0, 0, [103, 110]], [110, "vspace", 0, 0, [109, 111]], - [111, "hertz", 0, 0, [110, 112, null, null]], + [111, "hertz", 0, 0, [110, 112, null]], [112, ["number", { "value": 392 }], 0, 0, [111]] ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); @@ -1025,7 +1050,7 @@ describe("AST2BlockList Class", () => { [49, "pitch", 0, 0, [48, 50, 51, null]], [50, ["solfege", { "value": "sol" }], 0, 0, [49]], [51, ["number", { "value": 4 }], 0, 0, [49]], - [52, "break", 0, 0, [44, null, null, null]], + [52, "break", 0, 0, [44, null]], [53, "forever", 0, 0, [43, 54, 62]], [54, "newnote", 0, 0, [53, 55, 58, null]], [55, "divide", 0, 0, [54, 56, 57]], @@ -1035,46 +1060,495 @@ describe("AST2BlockList Class", () => { [59, "pitch", 0, 0, [58, 60, 61, null]], [60, ["solfege", { "value": "sol" }], 0, 0, [59]], [61, ["number", { "value": 4 }], 0, 0, [59]], - [62, "until", 0, 0, [53, 63, 64, 73]], + [62, "until", 0, 0, [53, 63, 64, 72]], [63, ["boolean", { "value": true }], 0, 0, [62]], - [64, "vspace", 0, 0, [62, 65]], - [65, "newnote", 0, 0, [64, 66, 69, null]], - [66, "divide", 0, 0, [65, 67, 68]], - [67, ["number", { "value": 1 }], 0, 0, [66]], - [68, ["number", { "value": 4 }], 0, 0, [66]], - [69, "vspace", 0, 0, [65, 70]], - [70, "pitch", 0, 0, [69, 71, 72, null]], - [71, ["solfege", { "value": "sol" }], 0, 0, [70]], - [72, ["number", { "value": 4 }], 0, 0, [70]], - [73, "switch", 0, 0, [62, 74, 75, null]], - [74, ["number", { "value": 1 }], 0, 0, [73]], - [75, "case", 0, 0, [73, 76, 77, 88]], - [76, ["number", { "value": 1 }], 0, 0, [75]], - [77, "newnote", 0, 0, [75, 78, 81, 85]], - [78, "divide", 0, 0, [77, 79, 80]], - [79, ["number", { "value": 1 }], 0, 0, [78]], - [80, ["number", { "value": 4 }], 0, 0, [78]], - [81, "vspace", 0, 0, [77, 82]], - [82, "pitch", 0, 0, [81, 83, 84, null]], - [83, ["solfege", { "value": "sol" }], 0, 0, [82]], - [84, ["number", { "value": 4 }], 0, 0, [82]], - [85, "break", 0, 0, [77, 86, null, null]], - [86, "break", 0, 0, [85, 87, null, null]], - [87, "break", 0, 0, [86, null, null, null]], - [88, "defaultcase", 0, 0, [75, 89, null, null]], - [89, "newnote", 0, 0, [88, 90, 93, null]], - [90, "divide", 0, 0, [89, 91, 92]], - [91, ["number", { "value": 1 }], 0, 0, [90]], - [92, ["number", { "value": 4 }], 0, 0, [90]], - [93, "vspace", 0, 0, [89, 94]], - [94, "pitch", 0, 0, [93, 95, 96, null]], - [95, ["solfege", { "value": "5" }], 0, 0, [94]], - [96, ["number", { "value": 4 }], 0, 0, [94]] + [64, "newnote", 0, 0, [62, 65, 68, null]], + [65, "divide", 0, 0, [64, 66, 67]], + [66, ["number", { "value": 1 }], 0, 0, [65]], + [67, ["number", { "value": 4 }], 0, 0, [65]], + [68, "vspace", 0, 0, [64, 69]], + [69, "pitch", 0, 0, [68, 70, 71, null]], + [70, ["solfege", { "value": "sol" }], 0, 0, [69]], + [71, ["number", { "value": 4 }], 0, 0, [69]], + [72, "switch", 0, 0, [62, 73, 74, null]], + [73, ["number", { "value": 1 }], 0, 0, [72]], + [74, "case", 0, 0, [72, 75, 76, 87]], + [75, ["number", { "value": 1 }], 0, 0, [74]], + [76, "newnote", 0, 0, [74, 77, 80, 84]], + [77, "divide", 0, 0, [76, 78, 79]], + [78, ["number", { "value": 1 }], 0, 0, [77]], + [79, ["number", { "value": 4 }], 0, 0, [77]], + [80, "vspace", 0, 0, [76, 81]], + [81, "pitch", 0, 0, [80, 82, 83, null]], + [82, ["solfege", { "value": "sol" }], 0, 0, [81]], + [83, ["number", { "value": 4 }], 0, 0, [81]], + [84, "break", 0, 0, [76, 85]], + [85, "break", 0, 0, [84, 86]], + [86, "break", 0, 0, [85, null]], + [87, "defaultcase", 0, 0, [74, 88, null]], + [88, "newnote", 0, 0, [87, 89, 92, null]], + [89, "divide", 0, 0, [88, 90, 91]], + [90, ["number", { "value": 1 }], 0, 0, [89]], + [91, ["number", { "value": 4 }], 0, 0, [89]], + [92, "vspace", 0, 0, [88, 93]], + [93, "pitch", 0, 0, [92, 94, 95, null]], + [94, ["solfege", { "value": "5" }], 0, 0, [93]], + [95, ["number", { "value": 4 }], 0, 0, [93]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + + // Test all Meter Blocks. + test("should generate correct blockList for all meter blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.setMeter(4, 1 / 4); + mouse.PICKUP = 0 / 4; + await mouse.setBPM(90, 1 / 4); + await mouse.setMasterBPM(90, 1 / 4); + await mouse.onEveryNoteDo("action"); + await mouse.setNoClock(async () => { + await mouse.onStrongBeatDo(mouse.BEATCOUNT, "action"); + await mouse.onStrongBeatDo(mouse.MEASURECOUNT, "action"); + await mouse.onStrongBeatDo(mouse.BPM, "action"); + await mouse.onStrongBeatDo(mouse.BEATFACTOR, "action"); + await mouse.onEveryBeatDo("action"); + await mouse.onWeakBeatDo("action"); + return mouse.ENDFLOW; + }); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "meter", 0, 0, [0, 2, 3, 6]], + [2, ["number", { "value": 4 }], 0, 0, [1]], + [3, "divide", 0, 0, [1, 4, 5]], + [4, ["number", { "value": 1 }], 0, 0, [3]], + [5, ["number", { "value": 4 }], 0, 0, [3]], + [6, "vspace", 0, 0, [1, 7]], + [7, "pickup", 0, 0, [6, 8, 11]], + [8, "divide", 0, 0, [7, 9, 10]], + [9, ["number", { "value": 0 }], 0, 0, [8]], + [10, ["number", { "value": 4 }], 0, 0, [8]], + [11, "vspace", 0, 0, [7, 12]], + [12, "setbpm3", 0, 0, [11, 13, 14, 17]], + [13, ["number", { "value": 90 }], 0, 0, [12]], + [14, "divide", 0, 0, [12, 15, 16]], + [15, ["number", { "value": 1 }], 0, 0, [14]], + [16, ["number", { "value": 4 }], 0, 0, [14]], + [17, "vspace", 0, 0, [12, 18]], + [18, "setmasterbpm2", 0, 0, [17, 19, 20, 23]], + [19, ["number", { "value": 90 }], 0, 0, [18]], + [20, "divide", 0, 0, [18, 21, 22]], + [21, ["number", { "value": 1 }], 0, 0, [20]], + [22, ["number", { "value": 4 }], 0, 0, [20]], + [23, "vspace", 0, 0, [18, 24]], + [24, "everybeatdo", 0, 0, [23, 25, 26]], + [25, ["text", { "value": "action" }], 0, 0, [24]], + [26, "drift", 0, 0, [24, 27, null]], + [27, "onbeatdo", 0, 0, [26, 28, 29, 30]], + [28, ["namedbox", { "value": "BEATCOUNT" }], 0, 0, [27]], + [29, ["text", { "value": "action" }], 0, 0, [27]], + [30, "onbeatdo", 0, 0, [27, 31, 32, 33]], + [31, ["namedbox", { "value": "MEASURECOUNT" }], 0, 0, [30]], + [32, ["text", { "value": "action" }], 0, 0, [30]], + [33, "onbeatdo", 0, 0, [30, 34, 35, 36]], + [34, ["namedbox", { "value": "BPM" }], 0, 0, [33]], + [35, ["text", { "value": "action" }], 0, 0, [33]], + [36, "onbeatdo", 0, 0, [33, 37, 38, 39]], + [37, ["namedbox", { "value": "BEATFACTOR" }], 0, 0, [36]], + [38, ["text", { "value": "action" }], 0, 0, [36]], + [39, "everybeatdonew", 0, 0, [36, 40, 41]], + [40, ["text", { "value": "action" }], 0, 0, [39]], + [41, "offbeatdo", 0, 0, [39, 42, null]], + [42, ["text", { "value": "action" }], 0, 0, [41]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + // Test all Pitch Blocks. + test("should generate correct blockList for all pitch blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.playPitch("sol", 4); + await mouse.playPitch("G", 4); + await mouse.stepPitch(1); + await mouse.playNthModalPitch(4, 4); + await mouse.playPitchNumber(7); + await mouse.setAccidental("sharp ♯", async () => { + await mouse.playNote(1 / 4, async () => { + await mouse.playPitch("sol", 4); + return mouse.ENDFLOW; + }); + return mouse.ENDFLOW; + }); + await mouse.playHertz(392); + await mouse.setScalarTranspose(0 + 0 * mouse.MODELENGTH, async () => { + return mouse.ENDFLOW; + }); + await mouse.setSemitoneTranspose(1 + 0 * 12, async () => { + return mouse.ENDFLOW; + }); + await mouse.setSemitoneTranspose(50 / 100, async () => { + await mouse.playNote(1 / 4, async () => { + await mouse.playPitch("sol", 4); + return mouse.ENDFLOW; + }); + return mouse.ENDFLOW; + }); + await mouse.setRegister(0); + await mouse.invert("sol", 4, "even", async () => { + await mouse.setPitchNumberOffset("C", 4); + await mouse.setPitchNumberOffset("C", -1); + return mouse.ENDFLOW; + }); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "pitch", 0, 0, [0, 2, 3, 4]], + [2, ["solfege", { "value": "sol" }], 0, 0, [1]], + [3, ["number", { "value": 4 }], 0, 0, [1]], + [4, "pitch", 0, 0, [1, 5, 6, 7]], + [5, ["notename", { "value": "G" }], 0, 0, [4]], + [6, ["number", { "value": 4 }], 0, 0, [4]], + [7, "steppitch", 0, 0, [4, 8, 9]], + [8, ["number", { "value": 1 }], 0, 0, [7]], + [9, "nthmodalpitch", 0, 0, [7, 10, 11, 12]], + [10, ["number", { "value": 4 }], 0, 0, [9]], + [11, ["number", { "value": 4 }], 0, 0, [9]], + [12, "pitchnumber", 0, 0, [9, 13, 14]], + [13, ["number", { "value": 7 }], 0, 0, [12]], + [14, "accidental", 0, 0, [12, 15, 16, 24]], + [15, ["text", { "value": "sharp ♯" }], 0, 0, [14]], + [16, "newnote", 0, 0, [14, 17, 20, null]], + [17, "divide", 0, 0, [16, 18, 19]], + [18, ["number", { "value": 1 }], 0, 0, [17]], + [19, ["number", { "value": 4 }], 0, 0, [17]], + [20, "vspace", 0, 0, [16, 21]], + [21, "pitch", 0, 0, [20, 22, 23, null]], + [22, ["solfege", { "value": "sol" }], 0, 0, [21]], + [23, ["number", { "value": 4 }], 0, 0, [21]], + [24, "hertz", 0, 0, [14, 25, 26]], + [25, ["number", { "value": 392 }], 0, 0, [24]], + [26, "setscalartransposition", 0, 0, [24, 27, null, 28]], + [27, ["modelength", {}], 0, 0, [26]], + [28, "settransposition", 0, 0, [26, 29, null, 34]], + [29, "plus", 0, 0, [28, 30, 31]], + [30, ["number", { "value": 1 }], 0, 0, [29]], + [31, "multiply", 0, 0, [29, 32, 33]], + [32, ["number", { "value": 0 }], 0, 0, [31]], + [33, ["number", { "value": 12 }], 0, 0, [31]], + [34, "settransposition", 0, 0, [28, 35, 38, 47]], + [35, "divide", 0, 0, [34, 36, 37]], + [36, ["number", { "value": 50 }], 0, 0, [35]], + [37, ["number", { "value": 100 }], 0, 0, [35]], + [38, "vspace", 0, 0, [34, 39]], + [39, "newnote", 0, 0, [38, 40, 43, null]], + [40, "divide", 0, 0, [39, 41, 42]], + [41, ["number", { "value": 1 }], 0, 0, [40]], + [42, ["number", { "value": 4 }], 0, 0, [40]], + [43, "vspace", 0, 0, [39, 44]], + [44, "pitch", 0, 0, [43, 45, 46, null]], + [45, ["solfege", { "value": "sol" }], 0, 0, [44]], + [46, ["number", { "value": 4 }], 0, 0, [44]], + [47, "register", 0, 0, [34, 48, 49]], + [48, ["number", { "value": 0 }], 0, 0, [47]], + [49, "invert1", 0, 0, [47, 50, 51, 52, 53, null]], + [50, ["solfege", { "value": "sol" }], 0, 0, [49]], + [51, ["number", { "value": 4 }], 0, 0, [49]], + [52, ["text", { "value": "even" }], 0, 0, [49]], + [53, "setpitchnumberoffset", 0, 0, [49, 54, 55, 56]], + [54, ["notename", { "value": "C" }], 0, 0, [53]], + [55, ["number", { "value": 4 }], 0, 0, [53]], + [56, "setpitchnumberoffset", 0, 0, [53, 57, 58, null]], + [57, ["notename", { "value": "C" }], 0, 0, [56]], + [58, "neg", 0, 0, [56, 59]], + [59, ["number", { "value": 1 }], 0, 0, [58]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + // Test all Interval Blocks. + test("should generate correct blockList for all interval blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.setKey("C", "major"); + await mouse.setScalarInterval(5, async () => { + return mouse.ENDFLOW; + }); + await mouse.setSemitoneInterval(4 + 0 * 12, async () => { + return mouse.ENDFLOW; + }); + await mouse.setSemitoneInterval("major 3" + 0 * 12, async () => { + return mouse.ENDFLOW; + }); + await mouse.setTemperament("equal", "C", 4); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "setkey2", 0, 0, [0, 2, 3, 4]], + [2, ["notename", { "value": "C" }], 0, 0, [1]], + [3, ["modename", { "value": "major" }], 0, 0, [1]], + [4, "interval", 0, 0, [1, 5, null, 6]], + [5, ["number", { "value": 5 }], 0, 0, [4]], + [6, "semitoneinterval", 0, 0, [4, 7, null, 8]], + [7, ["intervalname", {}], 0, 0, [6]], + [8, "semitoneinterval", 0, 0, [6, 9, null, 10]], + [9, ["intervalname", {}], 0, 0, [8]], + [10, "settemperament", 0, 0, [8, 11, 12, 13, null]], + [11, ["temperamentname", { "value": "equal" }], 0, 0, [10]], + [12, ["notename", { "value": "C" }], 0, 0, [10]], + [13, ["number", { "value": 4 }], 0, 0, [10]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + // Test all Tone Blocks. + test("should generate correct blockList for all tone blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.setInstrument("electronic synth", async () => { + return mouse.ENDFLOW; + }); + await mouse.doVibrato(5, 1 / 16, async () => { + return mouse.ENDFLOW; + }); + await mouse.doChorus(1.5, 3.5, 70, async () => { + return mouse.ENDFLOW; + }); + await mouse.doPhaser(0.5, 3, 392, async () => { + return mouse.ENDFLOW; + }); + await mouse.doTremolo(10, 50, async () => { + return mouse.ENDFLOW; + }); + await mouse.doDistortion(40, async () => { + return mouse.ENDFLOW; + }); + await mouse.doHarmonic(1, async () => { + return mouse.ENDFLOW; + }); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "settimbre", 0, 0, [0, 2, null, 3]], + [2, ["voicename", { "value": "electronic synth" }], 0, 0, [1]], + [3, "vibrato", 0, 0, [1, 4, 5, null, 8]], + [4, ["number", { "value": 5 }], 0, 0, [3]], + [5, "divide", 0, 0, [3, 6, 7]], + [6, ["number", { "value": 1 }], 0, 0, [5]], + [7, ["number", { "value": 16 }], 0, 0, [5]], + [8, "chorus", 0, 0, [3, 9, 10, 11, null, 12]], + [9, ["number", { "value": 1.5 }], 0, 0, [8]], + [10, ["number", { "value": 3.5 }], 0, 0, [8]], + [11, ["number", { "value": 70 }], 0, 0, [8]], + [12, "phaser", 0, 0, [8, 13, 14, 15, null, 16]], + [13, ["number", { "value": 0.5 }], 0, 0, [12]], + [14, ["number", { "value": 3 }], 0, 0, [12]], + [15, ["number", { "value": 392 }], 0, 0, [12]], + [16, "tremolo", 0, 0, [12, 17, 18, null, 19]], + [17, ["number", { "value": 10 }], 0, 0, [16]], + [18, ["number", { "value": 50 }], 0, 0, [16]], + [19, "dis", 0, 0, [16, 20, null, 21]], + [20, ["number", { "value": 40 }], 0, 0, [19]], + [21, "harmonic2", 0, 0, [19, 22, null, null]], + [22, ["number", { "value": 1 }], 0, 0, [21]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + // Test all Ornament, Volume, and Drums Blocks. + test("should generate correct blockList for all Ornament, Volume, and Drums blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.setStaccato(1 / 32, async () => { + return mouse.ENDFLOW; + }); + await mouse.setSlur(1 / 16, async () => { + return mouse.ENDFLOW; + }); + await mouse.doNeighbor(1, 1 / 16, async () => { + return mouse.ENDFLOW; + }); + await mouse.doCrescendo(5, async () => { + return mouse.ENDFLOW; + }); + await mouse.doDecrescendo(5, async () => { + return mouse.ENDFLOW; + }); + await mouse.setRelativeVolume(25, async () => { + return mouse.ENDFLOW; + }); + mouse.MASTERVOLUME = 50; + mouse.PANNING = 0; + await mouse.setSynthVolume("electronic synth", 50); + await mouse.setSynthVolume("kick drum", 50); + await mouse.playDrum("kick drum"); + await mouse.playDrum("duck"); + await mouse.mapPitchToDrum("kick drum", async () => { + await mouse.playPitch("sol", 4); + return mouse.ENDFLOW; + }); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "newstaccato", 0, 0, [0, 2, null, 5]], + [2, "divide", 0, 0, [1, 3, 4]], + [3, ["number", { "value": 1 }], 0, 0, [2]], + [4, ["number", { "value": 32 }], 0, 0, [2]], + [5, "newslur", 0, 0, [1, 6, null, 9]], + [6, "divide", 0, 0, [5, 7, 8]], + [7, ["number", { "value": 1 }], 0, 0, [6]], + [8, ["number", { "value": 16 }], 0, 0, [6]], + [9, "neighbor2", 0, 0, [5, 10, 11, null, 14]], + [10, ["number", { "value": 1 }], 0, 0, [9]], + [11, "divide", 0, 0, [9, 12, 13]], + [12, ["number", { "value": 1 }], 0, 0, [11]], + [13, ["number", { "value": 16 }], 0, 0, [11]], + [14, "crescendo", 0, 0, [9, 15, null, 16]], + [15, ["number", { "value": 5 }], 0, 0, [14]], + [16, "decrescendo", 0, 0, [14, 17, null, 18]], + [17, ["number", { "value": 5 }], 0, 0, [16]], + [18, "articulation", 0, 0, [16, 19, null, 20]], + [19, ["number", { "value": 25 }], 0, 0, [18]], + [20, "setnotevolume", 0, 0, [18, 21, 22]], + [21, ["number", { "value": 50 }], 0, 0, [20]], + [22, "setpanning", 0, 0, [20, 23, 24]], + [23, ["number", { "value": 0 }], 0, 0, [22]], + [24, "setsynthvolume", 0, 0, [22, 25, 26, 27]], + [25, ["voicename", { "value": "electronic synth" }], 0, 0, [24]], + [26, ["number", { "value": 50 }], 0, 0, [24]], + [27, "setsynthvolume", 0, 0, [24, 28, 29, 30]], + [28, ["voicename", { "value": "kick drum" }], 0, 0, [27]], + [29, ["number", { "value": 50 }], 0, 0, [27]], + [30, "playdrum", 0, 0, [27, 31, 32]], + [31, ["drumname", { "value": "kick drum" }], 0, 0, [30]], + [32, "playdrum", 0, 0, [30, 33, 34]], + [33, ["drumname", { "value": "duck" }], 0, 0, [32]], + [34, "mapdrum", 0, 0, [32, 35, 36, null]], + [35, ["drumname", { "value": "kick drum" }], 0, 0, [34]], + [36, "pitch", 0, 0, [34, 37, 38, null]], + [37, ["solfege", { "value": "sol" }], 0, 0, [36]], + [38, ["number", { "value": 4 }], 0, 0, [36]] + ]; + + const AST = acorn.parse(code, { ecmaVersion: 2020 }); + let blockList = AST2BlockList.toBlockList(AST, config); + expect(blockList).toEqual(expectedBlockList); + }); + + // Test all Graphics and Pen Blocks. + test("should generate correct blockList for all Graphics and Pen blocks", () => { + const code = ` + new Mouse(async mouse => { + await mouse.goForward(100); + await mouse.goBackward(100); + await mouse.turnLeft(90); + await mouse.turnRight(90); + await mouse.setXY(0, 0); + await mouse.setHeading(0); + await mouse.drawArc(90, 100); + await mouse.setBezierControlPoint1(100, 75); + await mouse.setBezierControlPoint2(100, 25); + await mouse.drawBezier(0, 100); + await mouse.clear(); + await mouse.scrollXY(100, 0); + await mouse.setColor(0); + await mouse.setGrey(100); + await mouse.setShade(50); + await mouse.setHue(80); + await mouse.setTranslucency(50); + await mouse.setPensize(5); + await mouse.penUp(); + await mouse.penDown(); + await mouse.fillBackground(); + await mouse.setFont("sans-serif"); + return mouse.ENDMOUSE; + }); + MusicBlocks.run();`; + + const expectedBlockList = [ + [0, "start", 200, 200, [null, 1, null]], + [1, "forward", 0, 0, [0, 2, 3]], + [2, ["number", { "value": 100 }], 0, 0, [1]], + [3, "back", 0, 0, [1, 4, 5]], + [4, ["number", { "value": 100 }], 0, 0, [3]], + [5, "left", 0, 0, [3, 6, 7]], + [6, ["number", { "value": 90 }], 0, 0, [5]], + [7, "right", 0, 0, [5, 8, 9]], + [8, ["number", { "value": 90 }], 0, 0, [7]], + [9, "setxy", 0, 0, [7, 10, 11, 12]], + [10, ["number", { "value": 0 }], 0, 0, [9]], + [11, ["number", { "value": 0 }], 0, 0, [9]], + [12, "setheading", 0, 0, [9, 13, 14]], + [13, ["number", { "value": 0 }], 0, 0, [12]], + [14, "arc", 0, 0, [12, 15, 16, 17]], + [15, ["number", { "value": 90 }], 0, 0, [14]], + [16, ["number", { "value": 100 }], 0, 0, [14]], + [17, "controlpoint1", 0, 0, [14, 18, 19, 20]], + [18, ["number", { "value": 100 }], 0, 0, [17]], + [19, ["number", { "value": 75 }], 0, 0, [17]], + [20, "controlpoint2", 0, 0, [17, 21, 22, 23]], + [21, ["number", { "value": 100 }], 0, 0, [20]], + [22, ["number", { "value": 25 }], 0, 0, [20]], + [23, "bezier", 0, 0, [20, 24, 25, 26]], + [24, ["number", { "value": 0 }], 0, 0, [23]], + [25, ["number", { "value": 100 }], 0, 0, [23]], + [26, "clear", 0, 0, [23, 27]], + [27, "scrollxy", 0, 0, [26, 28, 29, 30]], + [28, ["x", { "value": 100 }], 0, 0, [27]], + [29, ["y", { "value": 0 }], 0, 0, [27]], + [30, "setcolor", 0, 0, [27, 31, 32]], + [31, ["number", { "value": 0 }], 0, 0, [30]], + [32, "setgrey", 0, 0, [30, 33, 34]], + [33, ["grey", { "value": 100 }], 0, 0, [32]], + [34, "setshade", 0, 0, [32, 35, 36]], + [35, ["shade", { "value": 50 }], 0, 0, [34]], + [36, "sethue", 0, 0, [34, 37, 38]], + [37, ["color", { "value": 80 }], 0, 0, [36]], + [38, "settranslucency", 0, 0, [36, 39, 40]], + [39, ["number", { "value": 50 }], 0, 0, [38]], + [40, "setpensize", 0, 0, [38, 41, 42]], + [41, ["pensize", { "value": 5 }], 0, 0, [40]], + [42, "penup", 0, 0, [40, 43]], + [43, "pendown", 0, 0, [42, 44]], + [44, "background", 0, 0, [43, 45]], + [45, "setfont", 0, 0, [44, 46, null]], + [46, ["text", { "value": "sans-serif" }], 0, 0, [45]] ]; const AST = acorn.parse(code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(AST); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(AST, config); expect(blockList).toEqual(expectedBlockList); }); }); \ No newline at end of file diff --git a/js/js-export/argument_AST_example.png b/js/js-export/argument_AST_example.png new file mode 100644 index 0000000000..92342d8157 Binary files /dev/null and b/js/js-export/argument_AST_example.png differ diff --git a/js/js-export/argument_block.png b/js/js-export/argument_block.png new file mode 100644 index 0000000000..75e12bcbf0 Binary files /dev/null and b/js/js-export/argument_block.png differ diff --git a/js/js-export/argument_vspace.png b/js/js-export/argument_vspace.png new file mode 100644 index 0000000000..f78b8193b6 Binary files /dev/null and b/js/js-export/argument_vspace.png differ diff --git a/js/js-export/argument_with_argument.png b/js/js-export/argument_with_argument.png new file mode 100644 index 0000000000..7d79b7077f Binary files /dev/null and b/js/js-export/argument_with_argument.png differ diff --git a/js/js-export/ast2blocklist.js b/js/js-export/ast2blocklist.js index a9e0c3bd9e..178e037c72 100644 --- a/js/js-export/ast2blocklist.js +++ b/js/js-export/ast2blocklist.js @@ -26,1102 +26,600 @@ * which can be loaded into the musicblocks UI by calling `blocks.loadNewBlocks(blockList)`. * * Usage: - * let trees = AST2BlockList.toTrees(AST); - * let blockList = AST2BlockList.toBlockList(trees); + * let blockList = AST2BlockList.toBlockList(AST, config); */ class AST2BlockList { - /** - * Given a musicblocks AST ("type": "Program"), return an array of trees. - * Each AST contains one to multiple top level blocks, each to be converted to a tree. - * For example: - * { - * name: "start", - * children: [ { - * name: "settimbre", - * args: ["guitar"], - * children: [ - * child1, - * child2 ] - * } - * ] - * } - * Each tree node contains up to three properties: name, args, and children. Each child - * is another tree node. For example, child1 could be something like: - * { - * name: "newnote" - * args: [1] - * children: [ { - * name: "pitch" - * args: ["sol", 3] - * } ] - * } - * The entire block is to play a whole note Sol on guitar in the third octave. - * - * @param {Object} AST - AST generated from JavaScript code - * @returns {Array} trees, see example above - */ - static toTrees(AST) { - // An array of predicate and visitor pairs - use the visitor to get information from - // a body AST node if the predicate evaluates to true. Each visitor defines three getters: - // getName finds the name in the AST node of the statement and returns it - // getArguments returns an array of AST nodes that contain the arguments of the statement - // getChildren returns an array of AST nodes that contain the children of the statement - // The AST structures for different statement types are different. Therefore, a new entry - // needs to be added to the array in order to support a new statement type. - const _bodyVisitors = [ - // Action Palette, Start block - { - pred: (bodyAST) => { - return bodyAST.type == "ExpressionStatement" && - bodyAST.expression.type == "NewExpression"; - }, - visitor: { - getName: () => { return "start"; }, - getArguments: () => { return []; }, - getChildren: (bodyAST) => { - for (const arg of bodyAST.expression.arguments) { - if (arg.type == "ArrowFunctionExpression") { - return arg.body.body; - } - } - return []; - }, - } - }, + static toBlockList(AST, config) { + let trees = _astToTree(AST, config); + return _treeToBlockList(trees, config); - // Action Palette, Action block (Action Declaration) - { - pred: (bodyAST) => { - return bodyAST.type == "VariableDeclaration" && - bodyAST.declarations[0].init.type == "ArrowFunctionExpression"; - }, - visitor: { - getName: () => { return "action"; }, - getArguments: (bodyAST) => { return [bodyAST.declarations[0].id]; }, - getChildren: (bodyAST) => { return bodyAST.declarations[0].init.body.body; }, + /** + * Given a musicblocks AST ("type": "Program"), return an array of trees. + * Each AST contains one to multiple top level blocks, each to be converted to a tree. + * For example: + * { + * name: "start", + * children: [ { + * name: "settimbre", + * args: ["guitar"], + * children: [ + * child1, + * child2 ] + * } + * ] + * } + * Each tree node contains up to three properties: name, args, and children. Each child + * is another tree node. For example, child1 could be something like: + * { + * name: "newnote" + * args: [1] + * children: [ { + * name: "pitch" + * args: ["sol", 3] + * } ] + * } + * The entire block is to play a whole note Sol on guitar in the third octave. + * + * @param {Object} AST - AST generated from JavaScript code + * @param {Array} config - JSON config that maps AST to corresponding blocks + * @returns {Array} trees, see example above + */ + function _astToTree(AST, config) { + // Load argument properties configuration + const argConfigs = config.argument_blocks; + + // Implementation of toTrees(AST). + let root = {}; + for (let body of AST.body) { + _createNodeAndAddToTree(body, root); + } + return root["children"]; + + // + // Helper functions + // + + function _getPropertyValue(obj, path) { + const steps = path.split("."); + let current = obj; + for (let step of steps) { + // Regex matching to handle array case such as arguments[0] + const matchStep = step.match(/(\w+)\[(\d+)\]/); + if (matchStep) { + // Following the example above, the output of matchStep will have + // 'arguments' at index 1 and the index (0) will be at index 2 + current = current[matchStep[1]]; + current = current[matchStep[2]]; + } else { + current = current[step]; + } } - }, + return current; + } - // Action Palette, Async call: await action(...) - { - pred: (bodyAST) => { - return bodyAST.type == "ExpressionStatement" && - bodyAST.expression.type == "AwaitExpression" && - bodyAST.expression.argument.type == "CallExpression" && - bodyAST.expression.argument.callee.type == "Identifier"; - }, - visitor: { - getName: (bodyAST) => { return { nameddo: bodyAST.expression.argument.callee.name }; }, - getArguments: () => { return []; }, - getChildren: () => { return []; }, - } - }, + function _matchBody(bodyAST) { + for (const entry of config.body_blocks) { + if (!("ast" in entry)) continue; - // Async call: await mouse.playNote(...) - { - pred: (bodyAST) => { - return bodyAST.type == "ExpressionStatement" && - bodyAST.expression.type == "AwaitExpression" && - bodyAST.expression.argument.type == "CallExpression" && - bodyAST.expression.argument.callee.type == "MemberExpression"; - }, - visitor: { - getName: (bodyAST) => { - let obj = bodyAST.expression.argument.callee.object.name; - let member = bodyAST.expression.argument.callee.property.name; - let numArgs = bodyAST.expression.argument.arguments.length; - if (obj in _memberLookup && member in _memberLookup[obj]) { - let name = _memberLookup[obj][member]; - if (typeof name === "object") { - if (!(numArgs in name)) { - throw { - //TODO - }; + // Group identifiers by property path + const propertyGroups = {}; + for (const identifier of entry.ast.identifiers) { + if (!propertyGroups[identifier.property]) { + propertyGroups[identifier.property] = []; + } + propertyGroups[identifier.property].push(identifier); + } + // Check if all property groups match + let matched = true; + for (const [property, identifiers] of Object.entries(propertyGroups)) { + const value = _getPropertyValue(bodyAST, property); + let groupMatched = false; + + // Check if any identifier in this group matches + for (const identifier of identifiers) { + if ("value" in identifier) { + if (value === identifier.value) { + groupMatched = true; + break; } - name = name[numArgs]; + } else if ("has_value" in identifier && ((!identifier.has_value && value == null) || + (identifier.has_value && value != null))) { + groupMatched = true; + break; + } else if (!("has_value" in identifier) && identifier.size === value.length) { + groupMatched = true; + break; } - return name; } - throw { - message: `Unsupported AsyncCallExpression: ${obj}.${member}`, - start: bodyAST.expression.argument.callee.start, - end: bodyAST.expression.argument.callee.end - }; - }, - getArguments: (bodyAST) => { return bodyAST.expression.argument.arguments; }, - getChildren: (bodyAST) => { - for (const arg of bodyAST.expression.argument.arguments) { - if (arg.type == "ArrowFunctionExpression") { - return arg.body.body; - } + if (!groupMatched) { + matched = false; + break; } - return []; - }, - } - }, - - // Flow Palette, Repeat block - { - pred: (bodyAST) => { - return bodyAST.type == "ForStatement"; - }, - visitor: { - getName: () => { return "repeat"; }, - getArguments: (bodyAST) => { return [bodyAST.test.right]; }, - getChildren: (bodyAST) => { return bodyAST.body.body; }, + } + if (matched) { + return entry; + } } - }, + return null; + } - // Flow Palette, While and Forever block - { - pred: (bodyAST) => { - return bodyAST.type == "WhileStatement"; - }, - visitor: { - getName: (bodyAST) => { return bodyAST.test.value ? "forever" : "while"; }, - getArguments: (bodyAST) => { return bodyAST.test.value ? [] : [bodyAST.test]; }, - getChildren: (bodyAST) => { return bodyAST.body.body; }, + function _matchArgument(arg) { + for (const entry of argConfigs) { + let arg_type = entry.ast; + let matched = true; + for (const identifier of arg_type.identifiers) { + let value = _getPropertyValue(arg, identifier.property); + if ("size" in identifier) { + if (value.length !== identifier.size) { + matched = false; + break; + } + } else if (value !== identifier.value) { + matched = false; + break; + } + } + if (matched) { + return entry; + } } - }, + return null; + } - // Flow Palette, DoWhile (until) block - { - pred: (bodyAST) => { - return bodyAST.type == "DoWhileStatement"; - }, - visitor: { - getName: () => { return "until"; }, - getArguments: (bodyAST) => { return [bodyAST.test]; }, - getChildren: (bodyAST) => { return bodyAST.body.body; }, + function _createNodeAndAddToTree(bodyAST, parent) { + let pair = _matchBody(bodyAST); + if (pair === null) { + throw { + prefix: "Unsupported statement: ", + start: bodyAST.start, + end: bodyAST.end + }; } - }, - - // Flow Palette, If/Ifelse block - { - pred: (bodyAST) => { - return bodyAST.type == "IfStatement"; - }, - visitor: { - getName: (bodyAST) => { return bodyAST.alternate ? "ifthenelse" : "if"; }, - getArguments: (bodyAST) => { return [bodyAST.test]; }, - getChildren: (bodyAST) => { - if (bodyAST.alternate) { - return bodyAST.consequent.body.concat([{ "type": null, }], bodyAST.alternate.body); - } else { - return bodyAST.consequent.body; - } - }, + if (!("name" in pair)) { + return; } - }, - // Special case for else section of ifelse block - { - pred: (bodyAST) => { - return bodyAST.type == null; - }, - visitor: { - getName: () => { return "else"; }, - getArguments: () => { return []; }, - getChildren: () => { return []; }, + let node = {}; + // Set block name + if ("name_property" in pair.ast) { + node["name"] = { [pair.name]: _getPropertyValue(bodyAST, pair.ast.name_property) }; + } else { + node["name"] = pair.name; } - }, - // Flow Palette, Switch block - { - pred: (bodyAST) => { - return bodyAST.type == "SwitchStatement"; - }, - visitor: { - getName: () => { return "switch"; }, - getArguments: (bodyAST) => { return [bodyAST.discriminant]; }, - getChildren: (bodyAST) => { return bodyAST.cases; }, + // Set arguments + if (pair.arguments !== undefined) { + let argArray = []; + for (const argPath of pair.ast.argument_properties) { + argArray.push(_getPropertyValue(bodyAST, argPath)); + } + let args = _createArgNode(argArray); + if (args.length > 0) { + node["arguments"] = args; + } } - }, - - // Flow Palette, Case/Default block - { - pred: (bodyAST) => { - return bodyAST.type == "SwitchCase"; - }, - visitor: { - getName: (bodyAST) => { return bodyAST.test != null ? "case" : "defaultcase"; }, - getArguments: (bodyAST) => { return bodyAST.test != null ? [bodyAST.test] : []; }, - getChildren: (bodyAST) => { return bodyAST.consequent; }, + // Set children + if (pair.ast.children_properties !== undefined) { + for (const child of _getPropertyValue(bodyAST, pair.ast.children_properties[0])) { + if (child.type != "ReturnStatement") { + _createNodeAndAddToTree(child, node); + } + } + if (pair.ast.children_properties.length > 1) { + node["children"].push({ "name": "else" }); + for (const child of _getPropertyValue(bodyAST, pair.ast.children_properties[1])) { + if (child.type != "ReturnStatement") { + _createNodeAndAddToTree(child, node); + } + } + } } - }, - - // Flow Palette, break statement - { - pred: (bodyAST) => { - return bodyAST.type == "BreakStatement"; - }, - visitor: { - getName: (bodyAST) => { return "break"; }, - getArguments: (bodyAST) => { return []; }, - getChildren: (bodyAST) => { return []; }, + // Add the node to the children list of the parent. + if (parent["children"] === undefined) { + parent["children"] = []; } - }, + parent["children"].push(node); + } - // Boxes Palette, Store in box block (Variable Declaration) - { - pred: (bodyAST) => { - return bodyAST.type == "VariableDeclaration" && - (bodyAST.declarations[0].init.type == "Literal" || // var v = 6; - bodyAST.declarations[0].init.type == "BinaryExpression" || // var v = 2 * 3; - bodyAST.declarations[0].init.type == "CallExpression" || // var v = Math.abs(-6); - bodyAST.declarations[0].init.type == "UnaryExpression"); // var v = -6; - }, - visitor: { - getName: (bodyAST) => { return { storein2: bodyAST.declarations[0].id.name }; }, - getArguments: (bodyAST) => { return [bodyAST.declarations[0].init]; }, - getChildren: () => { return []; }, - } - }, + function _createArgNode(argASTNodes) { + let argNodes = []; + for (const arg of argASTNodes) { + let argNode = null; - // Boxes Palette, Add / Subtract block (Assignment) - { - pred: (bodyAST) => { - return bodyAST.type == "ExpressionStatement" && - bodyAST.expression.type == "AssignmentExpression"; - }, - visitor: { - getName: (bodyAST) => { - if (bodyAST.expression.right.type == "BinaryExpression" && - bodyAST.expression.left.name == bodyAST.expression.right.left.name) { - // box1 = box1 - 1; - if (bodyAST.expression.right.operator == "-" && - bodyAST.expression.right.right.value == 1) { - return "decrementOne"; - } - // box1 = box1 + 3; or - // box1 = box1 + -3; - if (bodyAST.expression.right.operator == "+") { - return "increment"; - } - } + // Find matching configuration for this argument type + let argConfig = _matchArgument(arg); + if (!argConfig) { throw { - prefix: "Unsupported AssignmentExpression: ", - start: bodyAST.expression.start, - end: bodyAST.expression.end + prefix: `Unsupported argument type ${arg.type}: `, + start: arg.start, + end: arg.end }; - }, - getArguments: (bodyAST) => { - if (bodyAST.expression.right.type == "BinaryExpression" && - bodyAST.expression.left.name == bodyAST.expression.right.left.name) { - if (bodyAST.expression.right.operator == "-" && - bodyAST.expression.right.right.value == 1) { - return [bodyAST.expression.left]; + } + + if ("value_property" in argConfig.ast) { + argNode = _getPropertyValue(arg, argConfig.ast.value_property); + } else if (argConfig.ast.identifier_property) { + argNode = { "identifier": _getPropertyValue(arg, argConfig.ast.identifier_property) }; + } else { + const name = _getPropertyValue(arg, argConfig.ast.name_property); + if (name in argConfig.name_map) { + let blockName = argConfig.name_map[name]; + let args = []; + if ("arguments_property" in argConfig.ast) { + args = _getPropertyValue(arg, argConfig.ast.arguments_property); + } else { + for (const property of argConfig.ast.argument_properties) { + args.push(_getPropertyValue(arg, property)); + } + } + if (typeof blockName === "object") { + blockName = blockName[args.length]; } - if (bodyAST.expression.right.operator == "+") { - return [bodyAST.expression.left, bodyAST.expression.right.right]; + if (blockName === undefined) { + throw { + prefix: `Unsupported argument count for ${name}: `, + start: arg.start, + end: arg.end + }; } + argNode = { + "name": blockName, + "arguments": _createArgNode(args) + }; + } else { + throw { + prefix: `Unsupported operator ${name}: `, + start: arg.start, + end: arg.end + }; } - throw { - prefix: "Unsupported AssignmentExpression: ", - start: bodyAST.expression.start, - end: bodyAST.expression.end - }; - }, - getChildren: () => { return []; }, - } - }, - - // Ignore MusicBlocks.run() - { - pred: (bodyAST) => { - return bodyAST.type == "ExpressionStatement" && - bodyAST.expression.type == "CallExpression" && - bodyAST.expression.callee.type == "MemberExpression" && - bodyAST.expression.callee.object.name == "MusicBlocks" && - bodyAST.expression.callee.property.name == "run"; - }, - visitor: null - }, - ]; + } - // A map from argument AST node types to builders that build tree nodes for - // argument AST nodes with the corresponding types. - // The AST structures for different argument types are different. Therefore, a new entry - // needs to be added to the map in order to support a new argument type. - const _argNodeBuilders = { - "Identifier": (argAST) => { - return { identifier: argAST.name }; - }, - - "Literal": (argAST) => { - return argAST.value; - }, - - "BinaryExpression": (argAST) => { - if (!(argAST.operator in _binaryOperatorLookup)) { - throw { - message: `Unsupported binary operator: ${argAST.operator}`, - start: argAST.start, - end: argAST.end - }; + if (argNode !== null) { + argNodes.push(argNode); + } } - return { - "name": _binaryOperatorLookup[argAST.operator], - "args": _createArgNode([argAST.left, argAST.right]) - }; - }, - - "CallExpression": (argAST) => { - let obj = argAST.callee.object.name; - let member = argAST.callee.property.name; - let numArgs = argAST.arguments.length; - if (!(obj in _memberLookup && member in _memberLookup[obj])) { - throw { - message: `Unsupported function call: ${obj}.${member}`, - start: argAST.callee.start, - end: argAST.callee.end - }; - } - let name = _memberLookup[obj][member]; - if (typeof name === "object") { - name = name[numArgs]; - } - let args = argAST.arguments; - if (name == "getDict") { - // For getValue(Key, Dictionary Name), - // make sure the order is [Dictionary Name, Key] - args = [argAST.arguments[1], argAST.arguments[0]]; - } - return { - "name": name, - "args": _createArgNode(args) - }; - }, - - "UnaryExpression": (argAST) => { - if (!(argAST.operator in _unaryOperatorLookup)) { - throw { - message: `Unsupported unary operator: ${argAST.operator}`, - start: argAST.start, - end: argAST.end - }; - } - return { - "name": _unaryOperatorLookup[argAST.operator], - "args": _createArgNode([argAST.argument]) - }; - }, - - "AwaitExpression": (argAST) => { - return _createArgNode([argAST.argument])[0]; - }, + return argNodes; + } + } - "ArrowFunctionExpression": () => { - // Ignore the type, it's for children - return null; - }, - }; + /** + * @param {Object} trees - trees generated from JavaScript AST by toTrees(AST) + * @param {Array} config - JSON config that maps AST to corresponding blocks + * @returns {Array} a blockList that can loaded by musicblocks by calling `blocks.loadNewBlocks(blockList)` + */ + function _treeToBlockList(trees, config) { + // [1,"settimbre",0,0,[0,2,3,null]] or + // [21,["nameddo",{"value":"action"}],421,82,[20]] + function _propertyOf(block) { + const block_name = Array.isArray(block[1]) ? block[1][0] : block[1]; + for (const entry of config.body_blocks) { + if ("name" in entry && entry.name === block_name) { + // Use default_connections if blocklist_connections is not specified + const connections = { + count: entry.blocklist_connections ? entry.blocklist_connections.length : config.default_connections.length + }; - // A map from member functions in AST to block names. - // Member functions are function names used in JavaScript, while block names are - // recognizable by musicblocks. - const _memberLookup = { - "mouse": { - "setInstrument": "settimbre", - "playNote": "newnote", - "playPitch": "pitch", - "playRest": "rest2", - "dot": "rhythmicdot2", - "tie": "tie", - "multiplyNoteValue": "multiplybeatfactor", - "swing": "newswing2", - "playNoteMillis": "osctime", - "playHertz": "hertz", - // Handle function overloading with different number of arguments - "setValue": { 3: "setDict", 2: "setDict2" }, - "getValue": { 2: "getDict", 1: "getDict2" }, - }, - "Math": { - "abs": "abs", - "floor": "int", - "pow": "power", - "sqrt": "sqrt", - }, - "MathUtility": { - "doCalculateDistance": "distance", - "doOneOf": "oneOf", - "doRandom": "random", - }, - }; + // Get the connections array to use + const connectionsArray = entry.blocklist_connections || config.default_connections; - // A map from binary operators in JavaScript to operators recognizable by musicblocks. - const _binaryOperatorLookup = { - "+": "plus", - "-": "minus", - "*": "multiply", - "/": "divide", - "%": "mod", - "==": "equal", - "!=": "not_equal_to", - "<": "less", - ">": "greater", - "<=": "less_than_or_equal_to", - ">=": "greater_than_or_equal_to", - "|": "or", - "&": "and", - "^": "xor" - }; + // Only add connection indices that exist + const prevIndex = connectionsArray.indexOf("parent_or_previous_sibling"); + if (prevIndex !== -1) connections.prev = prevIndex; - // A map from unary operators in JavaScript to operators recognizable by musicblocks. - const _unaryOperatorLookup = { - "-": "neg", - "!": "not" - }; + const childIndex = connectionsArray.indexOf("first_child"); + if (childIndex !== -1) connections.child = childIndex; - // Implementation of toTrees(AST). - let root = {}; - for (let body of AST.body) { - _createNodeAndAddToTree(body, root); - } - return root["children"]; + const nextIndex = connectionsArray.indexOf("next_sibling"); + if (nextIndex !== -1) connections.next = nextIndex; - // - // Helper functions - // + const secondChildIndex = connectionsArray.indexOf("second_child"); + if (secondChildIndex !== -1) connections.second_child = secondChildIndex; - /** - * Create a tree node starting at the give AST node and add it to parent, which is also a tree node. - * A tree node is an associated array with one to three elements, for example: - * {name: "settimbre", args: ["guitar"], children: [child1, child2]}. - * - * @param {Object} bodyAST - an element in a 'body' array in the AST - * @param {Object} parent - parent node for the new node created by this method - * @returns {Void} - */ - function _createNodeAndAddToTree(bodyAST, parent) { - let visitor = undefined; - for (const entry of _bodyVisitors) { - if (entry.pred(bodyAST)) { - visitor = entry.visitor; - break; + // Use default_vspaces if not specified in the block + const vspaces = entry.default_vspaces || config.default_vspaces || { body: 1 }; + return "body" in vspaces ? { + type: "block", + connections: connections, + vspaces: vspaces.body + } : { + type: "block", + connections: connections, + argument_v_spaces: vspaces.argument + }; + } } - } - if (visitor === undefined) { - throw { - prefix: "Unsupported statement: ", - start: bodyAST.start, - end: bodyAST.end + // doesn't match means it is a vspace block + return { + type: "block", + connections: { + count: 2, + prev: 0, + next: 1 + }, + vspaces: 1, + argument_v_spaces: 0 }; } - if (visitor === null) { - return; - } - let node = {}; - // Set block name - node["name"] = visitor.getName(bodyAST); - // Set arguments - let args = _createArgNode(visitor.getArguments(bodyAST)); - if (args.length > 0) { - node["args"] = args; + // Implementation of toBlockList(trees). + let blockList = []; + let x = 200; + for (let tree of trees) { + let blockNumber = _createBlockAndAddToList(tree, blockList)["blockNumber"]; + // Set (x, y) for the top level blocks. + blockList[blockNumber][2] = x; + blockList[blockNumber][3] = 200; + x += 300; } - // Set children - for (const child of visitor.getChildren(bodyAST)) { - if (child.type != "ReturnStatement") { - _createNodeAndAddToTree(child, node); + return blockList; + + /** + * Create a block for the tree node and add the block to the blockList. + * Each block is [block number, block descriptor, x position, y position, [connections]], where + * the actual number of connections and the meaning of each connection varies from block type to block type. + * For example, for a settimbre block, connection 0 is the parent or the previous sibling of the node, + * connection 1 is the arguments of the node, connection 2 is the first child of the node, and connection 3 + * is the next sibling of the node. + * For a pitch block, connection 0 is the parent or the previous sibling of the node, connection 1 is the + * first argument of the node - solfege, connection 2 is the second argument of the node - octave, and + * connection 3 is the next sibling of the node. + * For a number block (always as an argument), such as a divide block, connection 0 is the node that this + * divide is its argument (e.g. a newnote block), connection 1 is numerator, and connection 2 is denominator. + * + * @param {Object} node - the tree node for which a new block is to be created + * @param {Array} blockList - where the new block is going to be added to + * @returns {Number} the number (index in blockList) of the newly created block + */ + + function _createBlockAndAddToList(node, blockList) { + let block = []; + let blockNumber = blockList.length; + block.push(blockNumber); + blockList.push(block); + if ((typeof node.name) === "object") { + let blockName = Object.keys(node.name)[0]; + block.push([blockName, { "value": node.name[blockName] }]); + } else if (node.name !== "else") { + block.push(node.name); } - } - - // Add the node to the children list of the parent. - if (parent["children"] === undefined) { - parent["children"] = []; - } - parent["children"].push(node); - } - - /** - * Create and return an array for the given array of AST nodes for arguments. - * For exmple, two Literal AST nodes with value 1 and 2 will result in an array [1, 2]; - * One binary AST node "1 / 2" will result in an array [{"name": "divide", "args": [1, 2]}]. - * - * @param {Array} argASTNodes - an arry of AST nodes for arguments - * @returns {Array} an array of all the arguments, note that an argument can be another node - */ - function _createArgNode(argASTNodes) { - let argNodes = []; - for (const arg of argASTNodes) { - const argNodeBuilder = _argNodeBuilders[arg.type]; - if (argNodeBuilder === undefined) { - throw { - prefix: `Unsupported argument type ${arg.type}: `, - start: arg.start, - end: arg.end - }; - } - const argNode = argNodeBuilder(arg); - if (argNode !== null) { - argNodes.push(argNode); - } - } - return argNodes; - } - } - - /** - * @param {Object} trees - trees generated from JavaScript AST by toTrees(AST) - * @returns {Array} a blockList that can loaded by musicblocks by calling `blocks.loadNewBlocks(blockList)` - */ - static toBlockList(trees) { - // A map from block name to its argument handling function. - // Keys do not include the blocks that do not take arguments (e.g., start block or action call block) - // or can only be used as arguments - any block with a connector on its left side. - // Each value is a function to add blocks for the arguments and return the number of vertical spaces - // that the argument blocks will take. For example, an action definition with name for the action will - // take 1 vertical space for its argument. playPitch(1/4, ...) will have its argument blocks take 2 - // vertical spaces. With this information, the code can decide how many vspacers to add. - const _argHandlers = { - // Action Palette, Action block (Action Declaration) takes a string literal for action name - // Example: let chunk1 = async mouse => {...} - // args: [{"identifier": "chunk1"}] => - // [2,["text",{"value":"chunck1"}],0,0,[1]] - "action": (args, blockList, parentBlockNumber) => { - return _addNthArgToBlockList(["text", { "value": args[0].identifier }], 1, blockList, parentBlockNumber); - }, - - // Tone Palette, set instrument block takes a string literal for instrument name - // Example: mouse.setInstrument("guitar", async () => {...}); - // args: ["guitar"] => - // [2,["voicename",{"value":"guitar"}],0,0,[1]] - "settimbre": (args, blockList, parentBlockNumber) => { - return _addNthArgToBlockList(["voicename", { "value": args[0] }], 1, blockList, parentBlockNumber); - }, - - // Pitch Palette, pitch block takes a note name and a number expression for octave - // Example: mouse.playPitch("sol", 4); - // args: ["sol", 4] => - // [10,["solfege",{"value":"sol"}],0,0,[9]], - // [11,["number",{"value":4}],0,0,[9]] - // args: ["G", 4] => - // [10,["notename",{"value":"G"}],0,0,[9]], - // [11,["number",{"value":4}],0,0,[9]] - "pitch": (args, blockList, parentBlockNumber) => { - const notes = new Set(["A", "B", "C", "D", "E", "F", "G"]); - // Add the 1st argument - note name - let vspaces = _addNthArgToBlockList( - [notes.has(args[0].charAt(0)) ? "notename" : "solfege", { "value": args[0] }], - 1, blockList, parentBlockNumber); - // Add the 2nd argument - a number expression for octave - vspaces += _addNthValueArgToBlockList(args[1], 2, blockList, parentBlockNumber); - return vspaces; - }, - - // Rhythm Palette, note block takes a number expression that evaluates to 1, 1/2, 1/4, etc. - // Example: mouse.playNote(1 / 4, async () => {...}); - // args: [{"name": "divide"}, "args": [1, 4]] => - // [8, "divide", 0, 0, [7, 9, 10]], - // [9, ["number", { "value": 1 }], 0, 0, [8]], - // [10, ["number", { "value": 4 }], 0, 0, [8]], - "newnote": _addValueArgsToBlockList, - - // Dot block takes in a whole number - // Example: mouse.dot(3, async () => {...}); - // args: [3] - "rhythmicdot2": _addValueArgsToBlockList, - - // Multiply note value block takes in a number expression that evaluates to 1, 1/2, 1/4, etc. - // Example: mouse.multiplyNoteValue(1 / 4, async () => {...}); - // args: [{"name": "divide"}, "args": [1, 4]] => - // [7, "divide", 0, 0, [6, 8, 9]], - // [8, ["number", { "value": 1 }], 0, 0, [7]], - // [9, ["number", { "value": 4 }], 0, 0, [7]], - "multiplybeatfactor": _addValueArgsToBlockList, - - // Swing block takes two args, a number expression as swing value, and another expression as note value - // Example: mouse.swing(1 / 24, 1 / 8, async () => {...}); - "newswing2": _addValueArgsToBlockList, - - // Milliseconds note block takes in a number expression that evaluates to 1, 1/2, 1/4, etc. - // Example: mouse.playNoteMillis(1000 / (3 / 2), async () => {...}); - "osctime": _addValueArgsToBlockList, - - // Hertz block takes in a whole number - // Example: mouse.playHertz(392); - "hertz": _addValueArgsToBlockList, - - // Boxes Palette, subtract 1 from block takes a string identifier for variable - // Example: box1 = box1 - 1; - // args: [{"identifier": "box1"}] - // [2,["namedbox",{"value":"box1"}],0,0,[1]] - "decrementOne": (args, blockList, parentBlockNumber) => { - return _addNthArgToBlockList(["namedbox", { "value": args[0].identifier }], 1, blockList, parentBlockNumber); - }, - - // Boxes Palette, add block takes a string identifier for variable and a number expression - // Example: box1 = box1 + 2; - // args: [{"identifier": "box1"}, 2] => - // [10,["namedbox",{"value":"box1"}],0,0,[9]], - // [11,["number",{"value":2}],0,0,[9]] - "increment": (args, blockList, parentBlockNumber) => { - // Add the 1st argument - variable name - let vspaces = _addNthArgToBlockList(["namedbox", { "value": args[0].identifier }], 1, blockList, parentBlockNumber); - // Add the 2nd argument - a number expression - vspaces += _addNthValueArgToBlockList(args[1], 2, blockList, parentBlockNumber); - return vspaces; - }, - - // Boxes Palette, store in box block takes a number or boolean expression - // Example: var box1 = 2 * 5; - // args: [{"name": "multiply"}, "args": [2, 5]] => - // [8, "multiply", 0, 0, [7, 9, 10]], - // [9, ["number", { "value": 2 }], 0, 0, [8]], - // [10, ["number", { "value": 5 }], 0, 0, [8]], - "storein2": _addValueArgsToBlockList, - - // Dictionary Palette, set value block takes a string for the name of the dictionary, a string for the key, and a value - // Example: mouse.setValue("times", 3, "dict"); - // args: ["times", 3, "dict"] => - // [8,["text",{"value":"dict"}],0,0,[7]], - // [9,["text",{"value":"times"}],0,0,[7]], - // [10,["number",{"value":3}],0,0,[7]] - "setDict": (args, blockList, parentBlockNumber) => { - // Add the 1st argument - Dictionary name - let vspaces = _addNthArgToBlockList(["text", { "value": args[2] }], 1, blockList, parentBlockNumber); - // Add the 2nd argument - Key - vspaces += _addNthArgToBlockList(["text", { "value": args[0] }], 2, blockList, parentBlockNumber); - // Add the 3rd argument - Value - vspaces += _addNthValueArgToBlockList(args[1], 3, blockList, parentBlockNumber); - return vspaces; - }, - - // Dictionary Palette, set value block takes a string for the key, and a value - // Example: mouse.setValue("times", 3); - // args: ["times", 3] => - // [8,["text",{"value":"times"}],0,0,[7]], - // [9,["number",{"value":3}],0,0,[7]] - "setDict2": (args, blockList, parentBlockNumber) => { - // Add the 1st argument - Key - let vspaces = _addNthArgToBlockList(["text", { "value": args[0] }], 1, blockList, parentBlockNumber); - // Add the 2nd argument - Value - vspaces += _addNthValueArgToBlockList(args[1], 2, blockList, parentBlockNumber); - return vspaces; - }, - - // Flow Palette, repeat block takes a number expression - "repeat": _addValueArgsToBlockList, - - // Flow Palette, while block takes a condition - "while": _addValueArgsToBlockList, - - // Flow Palette, until block takes a condition - "until": _addValueArgsToBlockList, - - // Flow Palette, if block takes a boolean expression - "if": _addValueArgsToBlockList, - - // Flow Palette, if block takes a boolean expression - "ifthenelse": _addValueArgsToBlockList, + block.push(0); // x + block.push(0); // y - // Flow Palette, switch block takes a numerical expression - "switch": _addValueArgsToBlockList, + let property = _propertyOf(block); + let connections = new Array(property.connections.count).fill(null); + block.push(connections); - // Flow Palette, case block takes a numerical expression - "case": _addValueArgsToBlockList, - }; + // Process arguments + let argVSpaces = _createArgBlockAndAddToList(node, blockList, blockNumber); + let vspaces = Math.max(1, argVSpaces); // A node takes at least 1 vertical space - // A map from block name to its properties including: - // 1. An object that describes its connections such as the number of connections, - // the indices of its first child block, next sibling block, etc. - // 2. For blocks that may have children, the vertical spaces allowed for its arguments - // before extra v-spacers are needed in order to prevent its argument blocks from - // covering its child blocks. - // 3. For blocks that don't have children, the vertical spaces allowed for its arguments - // before extra v-spacers are needed in order to prevent its argument blocks from - // covering its sibling blocks. - // Keys do not include the blocks that can only be used as arguments - any block with - // a connector on its left side. - const _blockProperties = { - "start": { - // null, child, null - connections: { count: 3, "child": 1 }, - argVSpaces: 1, - }, - "action": { - // Action declaration - // null, arg (action name), child, null - connections: { count: 4, "child": 2 }, - argVSpaces: 1, - }, - "nameddo": { - // Action call - // prev, next - connections: { count: 2, "prev": 0, "next": 1 }, - vspaces: 1, - }, - "settimbre": { - // prev, arg (instrument name), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "pitch": { - // prev, arg1 (solfege), arg2 (octave), next - connections: { count: 4, "prev": 0, "next": 3 }, - vspaces: 2, - }, - "rest2": { - // prev, next - connections: { count: 2, "prev": 0, "next": 1 }, - vspaces: 1, - }, - "rhythmicdot2": { - // prev, arg (duration), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "tie": { - // prev, child, next - connections: { count: 3, "prev": 0, "child": 1, "next": 2 }, - argVSpaces: 0, - }, - "multiplybeatfactor": { - // prev, arg (duration), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "newswing2": { - // prev, arg1 (swing), arg2 (note), child, next - connections: { count: 5, "prev": 0, "child": 3, "next": 4 }, - argVSpaces: 2, - }, - "osctime": { - // prev, arg (duration), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "hertz": { - // prev, child, next - connections: { count: 4, "prev": 0, "child": 1, "next": 2 }, - argVSpaces: 1, - }, - "newnote": { - // prev, arg (note), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "decrementOne": { - // prev, arg (variable name), next - connections: { count: 3, "prev": 0, "next": 2 }, - vspaces: 1, - }, - "increment": { - // prev, arg1 (variable name), arg2 (value), next - connections: { count: 4, "prev": 0, "next": 3 }, - vspaces: 2, - }, - "storein2": { - // prev, arg (variable name), next - connections: { count: 3, "prev": 0, "next": 2 }, - vspaces: 1, - }, - "setDict": { - // prev, arg1 (dictionary name), arg2 (key), arg3 (value), next - connections: { count: 5, "prev": 0, "next": 4 }, - vspaces: 3, - }, - "setDict2": { - // prev, arg1 (key), arg2 (value), next - connections: { count: 4, "prev": 0, "next": 3 }, - vspaces: 2, - }, - "repeat": { - // prev, arg (repeat counts), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "while": { - // prev, arg (condition), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 2, - }, - "forever": { - // prev, child, next - connections: { count: 3, "prev": 0, "child": 1, "next": 2 }, - argVSpaces: 0, - }, - "until": { - // prev, arg (condition), child, next - connections: { count: 3, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 0, - }, - "if": { - // prev, arg (condition), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 2, - }, - "ifthenelse": { - // prev, arg (condition), child (if and else cases), next - connections: { count: 5, "prev": 0, "child": 2, "next": 4 }, - argVSpaces: 2, - }, - "switch": { - // prev, arg (variable), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "case": { - // prev, arg (condition), child, next - connections: { count: 4, "prev": 0, "child": 2, "next": 3 }, - argVSpaces: 1, - }, - "defaultcase": { - // prev, child, next - connections: { count: 4, "prev": 0, "child": 1, "next": 2 }, - argVSpaces: 0, - }, - "break": { - // prev, next - connections: { count: 4, "prev": 0, "next": 1 }, - vspaces: 1, - }, - "vspace": { - // prev, next - connections: { count: 2, "prev": 0, "next": 1 } - }, - }; + // Process children + if (node["children"] !== undefined && node["children"].length > 0) { + let elseIndex = node.children.findIndex(child => child.name === "else"); - // [1,"settimbre",0,0,[0,2,3,null]] or - // [21,["nameddo",{"value":"action"}],421,82,[20]] - function _propertyOf(block) { - return _blockProperties[Array.isArray(block[1]) ? block[1][0] : block[1]]; - } + // Split children into if/else groups if applies (most cases first group will contain all children) + let firstGroup = (elseIndex !== -1) ? node.children.slice(0, elseIndex) : node.children; + let secondGroup = (elseIndex !== -1) ? node.children.slice(elseIndex + 1) : []; - // Implementation of toBlockList(trees). - let blockList = []; - let x = 200; - for (let tree of trees) { - let blockNumber = _createBlockAndAddToList(tree, blockList)["blockNumber"]; - // Set (x, y) for the top level blocks. - blockList[blockNumber][2] = x; - blockList[blockNumber][3] = 200; - x += 300; - } - return blockList; + // Process first children group + let ret = _processChildren(firstGroup, argVSpaces - property.argument_v_spaces, blockList); + vspaces += ret.vspaces; - /** - * Create a block for the tree node and add the block to the blockList. - * Each block is [block number, block descriptor, x position, y position, [connections]], where - * the actual number of connections and the meaning of each connection varies from block type to block type. - * For example, for a settimbre block, connection 0 is the parent or the previous sibling of the node, - * connection 1 is the arguments of the node, connection 2 is the first child of the node, and connection 3 - * is the next sibling of the node. - * For a pitch block, connection 0 is the parent or the previous sibling of the node, connection 1 is the - * first argument of the node - solfege, connection 2 is the second argument of the node - octave, and - * connection 3 is the next sibling of the node. - * For a number block (always as an argument), such as a divide block, connection 0 is the node that this - * divide is its argument (e.g. a newnote block), connection 1 is numerator, and connection 2 is denominator. - * - * @param {Object} node - the tree node for which a new block is to be created - * @param {Array} blockList - where the new block is going to be added to - * @returns {Number} the number (index in blockList) of the newly created block - */ + // Set child-parent connection for first group + if (property.connections.child !== undefined) { + connections[property.connections.child] = ret.firstChildBlockNumber; + let childBlock = blockList[ret.firstChildBlockNumber]; + let childProperty = _propertyOf(childBlock); + if (childProperty.connections.prev !== undefined) { + childBlock[4][childProperty.connections.prev] = blockNumber; + } + } + + // Process second children group (else case in ifelse block) + if (elseIndex !== -1) { + let ret = _processChildren(secondGroup, 0, blockList); + vspaces += ret.vspaces; + // Set child-parent connection for second group + if (property.connections.second_child !== undefined) { + connections[property.connections.second_child] = ret.firstChildBlockNumber; + let childBlock = blockList[ret.firstChildBlockNumber]; + let childProperty = _propertyOf(childBlock); + if (childProperty.connections.prev !== undefined) { + childBlock[4][childProperty.connections.prev] = blockNumber; + } + } + } + } - function _createBlockAndAddToList(node, blockList) { - let block = []; - let blockNumber = blockList.length; - block.push(blockNumber); - blockList.push(block); - if ((typeof node.name) === "object") { - let blockName = Object.keys(node.name)[0]; - block.push([blockName, { "value": node.name[blockName] }]); - } else if (node.name !== "else") { - block.push(node.name); + return { "blockNumber": blockNumber, "vspaces": vspaces }; } - block.push(0); // x - block.push(0); // y - - let property = _propertyOf(block); - let connections = new Array(property.connections.count).fill(null); - block.push(connections); - - // Process arguments - let argVSpaces = _createArgBlockAndAddToList(node, blockList, blockNumber); - let vspaces = Math.max(1, argVSpaces); // A node takes at least 1 vertical space - // Process children - if (node["children"] !== undefined && node["children"].length > 0) { - let elseIndex = node.children.findIndex(child => child.name === "else"); + // Helper to process a group of children (create and establish connections between them) + function _processChildren(children, padding, blockList) { + let childBlockNumbers = []; + let vspaces = 0; - // Split children into if/else groups if applies (most cases first group will contain all children) - let firstGroup = (elseIndex !== -1) ? node.children.slice(0, elseIndex) : node.children; - let secondGroup = (elseIndex !== -1) ? node.children.slice(elseIndex + 1) : []; + // Add vertical spacers if the arguments take too much vertical spaces + for (let i = 0; i < padding; i++) { + childBlockNumbers.push(_addVSpacer(blockList)); + vspaces++; + } - // Process first children group - let ret = _processChildren(firstGroup, argVSpaces - property.argVSpaces, blockList); - vspaces += ret.vspaces; + // Add the children + for (const child of children) { + let ret = _createBlockAndAddToList(child, blockList); + childBlockNumbers.push(ret.blockNumber); + vspaces += ret.vspaces; - // Set child-parent connection for first group - connections[property.connections["child"]] = ret.firstChildBlockNumber; - let childBlock = blockList[ret.firstChildBlockNumber]; - property = _propertyOf(childBlock); - childBlock[4][property.connections["prev"]] = blockNumber; + let childProperty = _propertyOf(blockList[ret.blockNumber]); + for (let i = 0; i < ret.vspaces - childProperty.vspaces; i++) { + childBlockNumbers.push(_addVSpacer(blockList)); + } + } - // Process second children group (else case in ifelse block) - if (elseIndex !== -1) { - let ret = _processChildren(secondGroup, 0, blockList); - vspaces += ret.vspaces; - // Set child-parent connection for second group - connections[property.connections["child"] + 1] = ret.firstChildBlockNumber; - let childBlock = blockList[ret.firstChildBlockNumber]; - property = _propertyOf(childBlock); - childBlock[4][property.connections["prev"]] = blockNumber; + // Establish connections between children + // Parent of children is their previous sibling, except the first one + for (let i = 1; i < childBlockNumbers.length; i++) { + let childBlock = blockList[childBlockNumbers[i]]; + let property = _propertyOf(childBlock); + if (property.connections.prev !== undefined) { + childBlock[4][property.connections.prev] = childBlockNumbers[i - 1]; + } + } + // Set the next sibling block number for the children, except the last one + for (let i = 0; i < childBlockNumbers.length - 1; i++) { + let childBlock = blockList[childBlockNumbers[i]]; + let property = _propertyOf(childBlock); + if (property.connections.next !== undefined) { + childBlock[4][property.connections.next] = childBlockNumbers[i + 1]; + } } - // For blocks with children, add 1 to vspaces for the end of the clamp. - vspaces += 1; + return { "firstChildBlockNumber": childBlockNumbers[0], "vspaces": vspaces }; } - return { "blockNumber": blockNumber, "vspaces": vspaces }; - } - - // Helper to process a group of children (create and establish connections between them) - function _processChildren(children, padding, blockList) { - let childBlockNumbers = []; - let vspaces = 0; - - // Add vertical spacers if the arguments take too much vertical spaces - for (let i = 0; i < padding; i++) { - childBlockNumbers.push(_addVSpacer(blockList)); - vspaces++; + function _addVSpacer(blockList) { + let block = []; // A block for the vertical spacer + let blockNumber = blockList.length; + block.push(blockNumber); + blockList.push(block); + block.push("vspace"); + block.push(0); // x + block.push(0); // y + block.push([null, null]); // connections, prev and next + return blockNumber; } - // Add the children - for (const child of children) { - let ret = _createBlockAndAddToList(child, blockList); - childBlockNumbers.push(ret.blockNumber); - vspaces += ret.vspaces; - - let childProperty = _propertyOf(blockList[ret.blockNumber]); - for (let i = 0; i < ret.vspaces - childProperty.vspaces; i++) { - childBlockNumbers.push(_addVSpacer(blockList)); + function _createArgBlockAndAddToList(node, blockList, parentBlockNumber) { + if (node.arguments === undefined || node.arguments.length == 0) { + return 0; } - } - // Establish connections between children - // Parent of children is their previous sibling, except the first one - for (let i = 1; i < childBlockNumbers.length; i++) { - let childBlock = blockList[childBlockNumbers[i]]; - let property = _propertyOf(childBlock); - childBlock[4][property.connections["prev"]] = childBlockNumbers[i - 1]; - } - // Set the next sibling block number for the children, except the last one - for (let i = 0; i < childBlockNumbers.length - 1; i++) { - let childBlock = blockList[childBlockNumbers[i]]; - let property = _propertyOf(childBlock); - childBlock[4][property.connections["next"]] = childBlockNumbers[i + 1]; - } - return { "firstChildBlockNumber": childBlockNumbers[0], "vspaces": vspaces }; - } + let block = blockList[parentBlockNumber]; + let property = _propertyOf(block); + let vspaces = 0; + + // Find the block configuration + const block_name = Array.isArray(block[1]) ? block[1][0] : block[1]; + let blockConfig = null; + for (const entry of config.body_blocks) { + if (entry.name === block_name) { + blockConfig = entry; + break; + } + } - function _addVSpacer(blockList) { - let block = []; // A block for the vertical spacer - let blockNumber = blockList.length; - block.push(blockNumber); - blockList.push(block); - block.push("vspace"); - block.push(0); // x - block.push(0); // y - block.push([null, null]); // connections, prev and next - return blockNumber; - } + if (!blockConfig || !blockConfig.arguments) { + throw new Error(`Cannot find argument configuration for: ${block_name}`); + } - function _createArgBlockAndAddToList(node, blockList, parentBlockNumber) { - if (node.args === undefined || node.args.length == 0) { - return 0; - } - let argHandlerKey = (typeof node.name) === "object" ? Object.keys(node.name)[0] : node.name; - let argHandler = _argHandlers[argHandlerKey]; - if (argHandler === undefined) { - throw new Error(`Cannot find argument handler for: ${argHandlerKey}`); + // Process each argument with its corresponding configuration + for (let i = 0; i < node.arguments.length; i++) { + const arg = node.arguments[i]; + const argConfig = blockConfig.arguments[i]; + if (argConfig.type === "note_or_solfege") { + // Handle pitch notes (solfege or note names) + const notes = new Set(["A", "B", "C", "D", "E", "F", "G"]); + vspaces += _addNthArgToBlockList( + [notes.has(arg.charAt(0)) ? "notename" : "solfege", { "value": arg }], + i + 1, blockList, parentBlockNumber); + } else if (argConfig.type === "ValueExpression") { + // Handle value expressions (like storein2) + vspaces += _addNthValueArgToBlockList(arg, i + 1, blockList, parentBlockNumber); + } else if (argConfig.type === "NumberExpression" || argConfig.type === "BooleanExpression") { + // Handle number/boolean expressions + vspaces += _addNthValueArgToBlockList(arg, i + 1, blockList, parentBlockNumber); + } else { + vspaces += _addNthArgToBlockList([argConfig.type, { "value": typeof arg === "object" ? arg.identifier : arg }], i + 1, blockList, parentBlockNumber); + } + } + return vspaces; } - return argHandler(node.args, blockList, parentBlockNumber); - } - - // Add a new block to the blockList for the nth argument (1-indexed) of the parent block. - function _addNthArgToBlockList(arg, nth, blockList, parentBlockNumber) { - let block = []; // A block for the argument - let blockNumber = blockList.length; - block.push(blockNumber); - blockList.push(block); - block.push(arg); - block.push(0); // x - block.push(0); // y - block.push([parentBlockNumber]); // connections - let parentConnections = blockList[parentBlockNumber][4]; - parentConnections[nth] = blockNumber; - return 1; // vspaces - } - /** - * Examples: - * - * args: [2] => - * [5,["number",{"value":2}],0,0,[4]], - * - * args: [{"name": "divide", - * "args": [1,4]}] => - * [5,"divide",0,0,[4,6,7]], - * [6,["number",{"value":1}],0,0,[5]], - * [7,["number",{"value":4}],0,0,[5]] - * - * args: [{"name": "abs", - * "args": [{"name": "neg", - * "args": [1]}]}] => - * [5,"abs",0,0,[4,6,7]], - * [6,["neg",0,0,[5,7]], - * [7,["number",{"value":1}],0,0,[6]] - * - * @param {Object} args - the args property of a tree node - * @param {Array} blockList - the blockList to which the new argument blocks will be added - * @param {Number} parentBlockNumber - the number of the parent block of the new argument blocks - */ - function _addValueArgsToBlockList(args, blockList, parentBlockNumber) { - let vspaces = 0; - for (let i = 0; i < args.length; i++) { - vspaces += _addNthValueArgToBlockList(args[i], i + 1, blockList, parentBlockNumber); + // Add a new block to the blockList for the nth argument (1-indexed) of the parent block. + function _addNthArgToBlockList(arg, nth, blockList, parentBlockNumber) { + let block = []; // A block for the argument + let blockNumber = blockList.length; + block.push(blockNumber); + blockList.push(block); + block.push(arg); + block.push(0); // x + block.push(0); // y + block.push([parentBlockNumber]); // connections + let parentConnections = blockList[parentBlockNumber][4]; + parentConnections[nth] = blockNumber; + return 1; // vspaces } - return vspaces; - } - function _addNthValueArgToBlockList(arg, nth, blockList, parentBlockNumber) { - let vspaces = 0; - let block = []; - let blockNumber = blockList.length; - block.push(blockNumber); - blockList.push(block); - let type = typeof arg; - if (type === "string") { - type = "text"; + /** + * Examples: + * + * args: [2] => + * [5,["number",{"value":2}],0,0,[4]], + * + * args: [{"name": "divide", + * "args": [1,4]}] => + * [5,"divide",0,0,[4,6,7]], + * [6,["number",{"value":1}],0,0,[5]], + * [7,["number",{"value":4}],0,0,[5]] + * + * args: [{"name": "abs", + * "args": [{"name": "neg", + * "args": [1]}]}] => + * [5,"abs",0,0,[4,6,7]], + * [6,["neg",0,0,[5,7]], + * [7,["number",{"value":1}],0,0,[6]] + * + * @param {Object} args - the args property of a tree node + * @param {Array} blockList - the blockList to which the new argument blocks will be added + * @param {Number} parentBlockNumber - the number of the parent block of the new argument blocks + */ + function _addValueArgsToBlockList(args, blockList, parentBlockNumber) { + let vspaces = 0; + for (let i = 0; i < args.length; i++) { + vspaces += _addNthValueArgToBlockList(args[i], i + 1, blockList, parentBlockNumber); + } + return vspaces; } - if (type === "number" || type === "boolean" || type === "text" || - (type === "object" && arg.identifier !== undefined)) { - // variables can be in number or boolean expressions - block.push(type === "object" ? ["namedbox", { "value": arg.identifier }] : [type, { "value": arg }]); - block.push(0); // x - block.push(0); // y - // Initialize connections with just the parent. - block.push([parentBlockNumber]); - vspaces = 1; - } else if (type === "object") { - block.push(arg.name); - block.push(0); // x - block.push(0); // y - let connections = new Array(1 + arg.args.length).fill(null); - connections[0] = parentBlockNumber; - block.push(connections); - vspaces = _addValueArgsToBlockList(arg.args, blockList, blockNumber); - } else { - throw new Error(`Unsupported value argument: ${arg}`); + + function _addNthValueArgToBlockList(arg, nth, blockList, parentBlockNumber) { + let vspaces = 0; + let block = []; + let blockNumber = blockList.length; + block.push(blockNumber); + blockList.push(block); + let type = typeof arg; + if (type === "string") { + type = "text"; + } + if (type === "number" || type === "boolean" || type === "text" || + (type === "object" && arg.identifier !== undefined)) { + // variables can be in number or boolean expressions + block.push(type === "object" ? ["namedbox", { "value": arg.identifier }] : [type, { "value": arg }]); + block.push(0); // x + block.push(0); // y + // Initialize connections with just the parent. + block.push([parentBlockNumber]); + vspaces = 1; + } else if (type === "object") { + block.push(arg.name); + block.push(0); // x + block.push(0); // y + let connections = new Array(1 + arg.arguments.length).fill(null); + connections[0] = parentBlockNumber; + block.push(connections); + vspaces = _addValueArgsToBlockList(arg.arguments, blockList, blockNumber); + } else { + throw new Error(`Unsupported value argument: ${arg}`); + } + let parentConnections = blockList[parentBlockNumber][4]; + parentConnections[nth] = blockNumber; + return vspaces; } - let parentConnections = blockList[parentBlockNumber][4]; - parentConnections[nth] = blockNumber; - return vspaces; } } } if (typeof module !== "undefined" && module.exports) { module.exports = { AST2BlockList }; -} +} \ No newline at end of file diff --git a/js/js-export/ast2blocks.json b/js/js-export/ast2blocks.json new file mode 100644 index 0000000000..0b970cec44 --- /dev/null +++ b/js/js-export/ast2blocks.json @@ -0,0 +1,4230 @@ +{ + "default_connections": [ + "parent_or_previous_sibling", + "argument", + "first_child", + "next_sibling" + ], + "argument_blocks": [ + { + "comment": "Variable name like 'box1' in the Boxes palette or action name like 'action' in the Action palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "Identifier" + } + ], + "identifier_property": "name" + } + }, + { + "comment": "Number expression like '1 / 4' in the Number palette or boolean expressions like 'box1 >= 0' in the Boolean palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "BinaryExpression" + } + ], + "name_property": "operator", + "argument_properties": [ + "left", + "right" + ] + }, + "name_map": { + "+": "plus", + "-": "minus", + "*": "multiply", + "/": "divide", + "%": "mod", + "==": "equal", + "!=": "not_equal_to", + "<": "less", + ">": "greater", + "<=": "less_than_or_equal_to", + ">=": "greater_than_or_equal_to", + "|": "or", + "&": "and", + "^": "xor" + } + }, + { + "comment": "Unary expressions such as ! in boolean palette or - in number palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "UnaryExpression" + } + ], + "name_property": "operator", + "argument_properties": [ + "argument" + ] + }, + "name_map": { + "-": "neg", + "!": "not" + } + }, + { + "comment": "Literals such as numbers or booleans or strings", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "Literal" + } + ], + "value_property": "value" + } + }, + { + "comment": "Dictionary get function for 2 arguments", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "AwaitExpression" + }, + { + "property": "argument.type", + "value": "CallExpression" + }, + { + "property": "argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "argument.callee.property.name", + "value": "getValue" + }, + { + "property": "argument.arguments", + "size": 2 + } + ], + "name_property": "argument.callee.property.name", + "argument_properties": [ + "argument.arguments[1]", + "argument.arguments[0]" + ] + }, + "name_map": { + "getValue": "getDict" + } + }, + { + "comment": "Dictionary get function for 1 argument", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "AwaitExpression" + }, + { + "property": "argument.type", + "value": "CallExpression" + }, + { + "property": "argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "argument.callee.property.name", + "value": "getValue" + }, + { + "property": "argument.arguments", + "size": 1 + } + ], + "name_property": "argument.callee.property.name", + "argument_properties": [ + "argument.arguments[0]" + ] + }, + "name_map": { + "getValue": "getDict2" + } + }, + { + "comment": "Skip, this is for children", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ArrowFunctionExpression" + } + ] + } + }, + { + "comment": "Math operators such as absolute value or square", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "CallExpression" + }, + { + "property": "callee.type", + "value": "MemberExpression" + }, + { + "property": "callee.object.type", + "value": "Identifier" + }, + { + "property": "callee.object.name", + "value": "Math" + } + ], + "name_property": "callee.property.name", + "arguments_property": "arguments" + }, + "name_map": { + "abs": "abs", + "floor": "int", + "pow": "power", + "sqrt": "sqrt" + } + }, + { + "comment": "Math utility operators such as distance or random", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "CallExpression" + }, + { + "property": "callee.type", + "value": "MemberExpression" + }, + { + "property": "callee.object.type", + "value": "Identifier" + }, + { + "property": "callee.object.name", + "value": "MathUtility" + } + ], + "name_property": "callee.property.name", + "arguments_property": "arguments" + }, + "name_map": { + "doCalculateDistance": "distance", + "doOneOf": "oneOf", + "doRandom": "random" + } + }, + { + "comment": "Singular argument mouse blocks", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "MemberExpression" + }, + { + "property": "object.name", + "value": "mouse" + } + ], + "identifier_property": "property.name" + }, + "name_map": { + "MODELENGTH": "modelength", + "SCALARCHANGEINPITCH": "deltapitch2", + "CHANGEINPITCH": "deltapitch", + "CURRENTKEY": "currentkey", + "CURRENTMODE": "currentmode", + "X": "x", + "Y": "y", + "GREY": "grey", + "SHADE": "shade", + "PENSIZE": "pensize", + "COLOR": "color", + "BEATCOUNT": "beatcount", + "MEASURECOUNT": "measurecount", + "BPM": "bpm", + "BEATFACTOR": "beatfactor" + } + }, + { + "comment": "Number to octave conversion", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "CallExpression" + }, + { + "property": "callee.name", + "value": "numToOctave" + } + ], + "name_property": "callee.name", + "argument_properties": [ + "arguments[0]" + ] + }, + "name_map": { + "numToOctave": "number2octave" + } + }, + { + "comment": "Number to pitch conversion", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "CallExpression" + }, + { + "property": "callee.name", + "value": "numToPitch" + } + ], + "name_property": "callee.name", + "argument_properties": [ + "arguments[0]" + ] + }, + "name_map": { + "numToPitch": "number2pitch" + } + }, + { + "comment": "Get synth volume value", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "CallExpression" + }, + { + "property": "callee.name", + "value": "getSynthVolume" + } + ], + "name_property": "callee.name", + "argument_properties": [ + "arguments[0]" + ] + }, + "name_map": { + "getSynthVolume": "synthvolumefactor" + } + } + ], + "body_blocks": [ + { + "comment": "run is ignored", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "CallExpression" + }, + { + "property": "expression.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.callee.object.name", + "value": "MusicBlocks" + }, + { + "property": "expression.callee.property.name", + "value": "run" + } + ] + } + }, + { + "name": "storein2", + "comment": "Variable assignment in the Boxes palette like 'var box1 = 2 * 5;'", + "arguments": [ + { + "type": "ValueExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "VariableDeclaration" + }, + { + "property": "declarations[0].init.type", + "value": "Literal" + }, + { + "property": "declarations[0].init.type", + "value": "BinaryExpression" + }, + { + "property": "declarations[0].init.type", + "value": "UnaryExpression" + }, + { + "property": "declarations[0].init.type", + "value": "CallExpression" + } + ], + "name_property": "declarations[0].id.name", + "argument_properties": [ + "declarations[0].init" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "decrementOne", + "comment": "Decrement by one block in the Boxes palette 'box1 = box1 - 1;'", + "arguments": [ + { + "type": "namedbox" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AssignmentExpression" + }, + { + "property": "expression.right.type", + "value": "BinaryExpression" + }, + { + "property": "expression.right.operator", + "value": "-" + }, + { + "property": "expression.right.right.value", + "value": 1 + } + ], + "argument_properties": [ + "expression.left" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "increment", + "comment": "Increment block in the Boxes palette 'box1 = box1 + 3;'", + "arguments": [ + { + "type": "namedbox" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AssignmentExpression" + }, + { + "property": "expression.right.type", + "value": "BinaryExpression" + }, + { + "property": "expression.right.operator", + "value": "+" + } + ], + "argument_properties": [ + "expression.left", + "expression.right.right" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "settimbre", + "comment": "Set instrument block", + "arguments": [ + { + "type": "voicename" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setInstrument" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "newnote", + "comment": "Note block in the Rhythm palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playNote" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "rhythmicdot2", + "comment": "Dot block in the Rhythm palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "dot" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "tie", + "comment": "Tie block in the Rhythm palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "tie" + } + ], + "children_properties": [ + "expression.argument.arguments[0].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 0 + } + }, + { + "name": "multiplybeatfactor", + "comment": "Multiply note value block in the Rhythm palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "multiplyNoteValue" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "newswing2", + "comment": "Swing block in the Rhythm palette", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "swing" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ], + "children_properties": [ + "expression.argument.arguments[2].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "osctime", + "comment": "Millisecond note block in the Rhythm palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playNoteMillis" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "pitch", + "comment": "Pitch block", + "arguments": [ + { + "type": "note_or_solfege" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playPitch" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "hertz", + "comment": "Hertz block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playHertz" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "rest2", + "comment": "Rest block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playRest" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "nameddo", + "comment": "Action palette, async block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "Identifier" + } + ], + "name_property": "expression.argument.callee.name" + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "start", + "comment": "Start block in the Flow palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "NewExpression" + } + ], + "children_properties": [ + "expression.arguments[0].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "action", + "comment": "Action block in the Action palette", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "VariableDeclaration" + }, + { + "property": "declarations[0].init.type", + "value": "ArrowFunctionExpression" + } + ], + "argument_properties": [ + "declarations[0].id" + ], + "children_properties": [ + "declarations[0].init.body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "repeat", + "comment": "Repeat block in the Flow palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ForStatement" + } + ], + "argument_properties": [ + "test.right" + ], + "children_properties": [ + "body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "forever", + "comment": "Forever block in the Flow palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "WhileStatement" + }, + { + "property": "test.raw", + "value": "1000" + } + ], + "children_properties": [ + "body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "while", + "comment": "While block in the Flow palette", + "arguments": [ + { + "type": "BooleanExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "WhileStatement" + } + ], + "argument_properties": [ + "test" + ], + "children_properties": [ + "body.body" + ] + }, + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "until", + "comment": "Do While block in the Flow palette", + "arguments": [ + { + "type": "BooleanExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "DoWhileStatement" + } + ], + "argument_properties": [ + "test" + ], + "children_properties": [ + "body.body" + ] + }, + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "switch", + "comment": "Switch block in the Flow palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "SwitchStatement" + } + ], + "argument_properties": [ + "discriminant" + ], + "children_properties": [ + "cases" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "defaultcase", + "comment": "Default case block in the Flow palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "SwitchCase" + }, + { + "property": "test", + "has_value": false + } + ], + "children_properties": [ + "consequent" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "case", + "comment": "Case block in the Flow palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "SwitchCase" + }, + { + "property": "test.type", + "value": "Literal" + } + ], + "argument_properties": [ + "test" + ], + "children_properties": [ + "consequent" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "break", + "comment": "Break block in the Flow palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "BreakStatement" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "if", + "comment": "Basic if block in the Flow palette", + "arguments": [ + { + "type": "BooleanExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "IfStatement" + }, + { + "property": "alternate", + "has_value": false + } + ], + "argument_properties": [ + "test" + ], + "children_properties": [ + "consequent.body" + ] + }, + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "ifthenelse", + "comment": "If-then-else block in the Flow palette", + "arguments": [ + { + "type": "BooleanExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "IfStatement" + }, + { + "property": "alternate", + "has_value": true + } + ], + "argument_properties": [ + "test" + ], + "children_properties": [ + "consequent.body", + "alternate.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "first_child", + "second_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "crescendo", + "comment": "Crescendo block in the Volume palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doCrescendo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "setDict", + "comment": "Set value block for dictionary, with specific dictionary", + "arguments": [ + { + "type": "text" + }, + { + "type": "text" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setValue" + }, + { + "property": "expression.argument.arguments", + "size": 3 + } + ], + "argument_properties": [ + "expression.argument.arguments[2]", + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 3 + } + }, + { + "name": "setDict2", + "comment": "Dictionary blocks, the set functions", + "arguments": [ + { + "type": "text" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setValue" + }, + { + "property": "expression.argument.arguments", + "size": 2 + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "meter", + "comment": "Set meter block from the meter palette", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setMeter" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "pickup", + "comment": "PICKUP block from the meter palette", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AssignmentExpression" + }, + { + "property": "expression.operator", + "value": "=" + }, + { + "property": "expression.left.type", + "value": "MemberExpression" + }, + { + "property": "expression.left.property.name", + "value": "PICKUP" + } + ], + "argument_properties": [ + "expression.right" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setbpm3", + "comment": "Set BPM block from the meter palette", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setBPM" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "setmasterbpm2", + "comment": "Set master BPM block from the meter palette", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setMasterBPM" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "everybeatdo", + "comment": "On every note do block from the meter palette", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "onEveryNoteDo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "everybeatdonew", + "comment": "On every beat do block from the meter palette", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "onEveryBeatDo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "onbeatdo", + "comment": "On strong beat do block from the meter palette", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "onStrongBeatDo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "offbeatdo", + "comment": "On weak beat do block from the meter palette", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "onWeakBeatDo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "drift", + "comment": "No clock block from the meter palette", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setNoClock" + } + ], + "children_properties": [ + "expression.argument.arguments[0].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "steppitch", + "comment": "Step pitch block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "stepPitch" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "nthmodalpitch", + "comment": "Play nth modal pitch block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playNthModalPitch" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "pitchnumber", + "comment": "Play pitch number block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playPitchNumber" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "accidental", + "comment": "Set accidental block", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setAccidental" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "setscalartransposition", + "comment": "Set scalar transposition block", + "arguments": [ + { + "type": "modelength" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setScalarTranspose" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "settransposition", + "comment": "Set semitone transposition block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setSemitoneTranspose" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "register", + "comment": "Set register block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setRegister" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "invert1", + "comment": "Invert block", + "arguments": [ + { + "type": "note_or_solfege" + }, + { + "type": "NumberExpression" + }, + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "invert" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]", + "expression.argument.arguments[2]" + ], + "children_properties": [ + "expression.argument.arguments[3].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 3 + } + }, + { + "name": "setpitchnumberoffset", + "comment": "Set pitch number offset block", + "arguments": [ + { + "type": "note_or_solfege" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setPitchNumberOffset" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "setkey2", + "comment": "Set key block", + "arguments": [ + { + "type": "notename" + }, + { + "type": "modename" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setKey" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "interval", + "comment": "Set scalar interval block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setScalarInterval" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "semitoneinterval", + "comment": "Set semitone interval block", + "arguments": [ + { + "type": "intervalname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setSemitoneInterval" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "settemperament", + "comment": "Set temperament block", + "arguments": [ + { + "type": "temperamentname" + }, + { + "type": "notename" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setTemperament" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]", + "expression.argument.arguments[2]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 3 + } + }, + { + "name": "vibrato", + "comment": "Vibrato effect block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doVibrato" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ], + "children_properties": [ + "expression.argument.arguments[2].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "chorus", + "comment": "Chorus effect block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doChorus" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]", + "expression.argument.arguments[2]" + ], + "children_properties": [ + "expression.argument.arguments[3].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 3 + } + }, + { + "name": "phaser", + "comment": "Phaser effect block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doPhaser" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]", + "expression.argument.arguments[2]" + ], + "children_properties": [ + "expression.argument.arguments[3].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 3 + } + }, + { + "name": "tremolo", + "comment": "Tremolo effect block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doTremolo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ], + "children_properties": [ + "expression.argument.arguments[2].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "dis", + "comment": "Distortion effect block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doDistortion" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "harmonic2", + "comment": "Harmonic effect block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doHarmonic" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "newstaccato", + "comment": "Set staccato block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setStaccato" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "newslur", + "comment": "Set slur block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setSlur" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "neighbor2", + "comment": "Neighbor tone block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doNeighbor" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ], + "children_properties": [ + "expression.argument.arguments[2].body.body" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 2 + } + }, + { + "name": "decrescendo", + "comment": "Decrescendo block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "doDecrescendo" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "articulation", + "comment": "Set relative volume block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setRelativeVolume" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "setsynthvolume", + "comment": "Set synth volume block", + "arguments": [ + { + "type": "voicename" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setSynthVolume" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "setnotevolume", + "comment": "Set master volume block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AssignmentExpression" + }, + { + "property": "expression.operator", + "value": "=" + }, + { + "property": "expression.left.type", + "value": "MemberExpression" + }, + { + "property": "expression.left.property.name", + "value": "MASTERVOLUME" + } + ], + "argument_properties": [ + "expression.right" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setpanning", + "comment": "Set panning block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AssignmentExpression" + }, + { + "property": "expression.operator", + "value": "=" + }, + { + "property": "expression.left.type", + "value": "MemberExpression" + }, + { + "property": "expression.left.property.name", + "value": "PANNING" + } + ], + "argument_properties": [ + "expression.right" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "playdrum", + "comment": "Play drum block", + "arguments": [ + { + "type": "drumname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setdrum", + "comment": "Set drum block", + "arguments": [ + { + "type": "effectsname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "mapdrum", + "comment": "Map pitch to drum block", + "arguments": [ + { + "type": "drumname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "mapPitchToDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "playdrum", + "comment": "Play drum block", + "arguments": [ + { + "type": "drumname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "playDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setdrum", + "comment": "Set drum block", + "arguments": [ + { + "type": "effectsname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "mapdrum", + "comment": "Map pitch to drum block", + "arguments": [ + { + "type": "drumname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "mapPitchToDrum" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } + }, + { + "name": "forward", + "comment": "Forward block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "goForward" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "back", + "comment": "Backward block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "goBackward" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "left", + "comment": "Turn left block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "turnLeft" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "right", + "comment": "Turn right block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "turnRight" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setxy", + "comment": "Set position block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setXY" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "setheading", + "comment": "Set heading block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setHeading" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "arc", + "comment": "Draw arc block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "drawArc" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "controlpoint1", + "comment": "Set bezier control point 1 block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setBezierControlPoint1" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "controlpoint2", + "comment": "Set bezier control point 2 block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setBezierControlPoint2" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "bezier", + "comment": "Draw bezier block", + "arguments": [ + { + "type": "NumberExpression" + }, + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "drawBezier" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "clear", + "comment": "Clear block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "clear" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "scrollxy", + "comment": "Scroll XY block", + "arguments": [ + { + "type": "x" + }, + { + "type": "y" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "scrollXY" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]", + "expression.argument.arguments[1]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 2 + } + }, + { + "name": "setcolor", + "comment": "Set color block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setColor" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setgrey", + "comment": "Set grey block", + "arguments": [ + { + "type": "grey" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setGrey" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setshade", + "comment": "Set shade block", + "arguments": [ + { + "type": "shade" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setShade" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "sethue", + "comment": "Set hue block", + "arguments": [ + { + "type": "color" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setHue" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "settranslucency", + "comment": "Set translucency block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setTranslucency" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "setpensize", + "comment": "Set pen size block", + "arguments": [ + { + "type": "pensize" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setPensize" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + }, + { + "name": "penup", + "comment": "Pen up block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "penUp" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "pendown", + "comment": "Pen down block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "penDown" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "background", + "comment": "Fill background block", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "fillBackground" + } + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "next_sibling" + ] + }, + { + "name": "setfont", + "comment": "Set font block", + "arguments": [ + { + "type": "text" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setFont" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ] + } + ] +} \ No newline at end of file diff --git a/js/js-export/ast2blocks.min.json b/js/js-export/ast2blocks.min.json new file mode 100644 index 0000000000..430aa3a7e3 --- /dev/null +++ b/js/js-export/ast2blocks.min.json @@ -0,0 +1 @@ +{"default_connections":["parent_or_previous_sibling","argument","first_child","next_sibling"],"argument_blocks":[{"comment":"Variable name like 'box1' in the Boxes palette or action name like 'action' in the Action palette","ast":{"identifiers":[{"property":"type","value":"Identifier"}],"identifier_property":"name"}},{"comment":"Number expression like '1 / 4' in the Number palette or boolean expressions like 'box1 >= 0' in the Boolean palette","ast":{"identifiers":[{"property":"type","value":"BinaryExpression"}],"name_property":"operator","argument_properties":["left","right"]},"name_map":{"+":"plus","-":"minus","*":"multiply","/":"divide","%":"mod","==":"equal","!=":"not_equal_to","<":"less",">":"greater","<=":"less_than_or_equal_to",">=":"greater_than_or_equal_to","|":"or","&":"and","^":"xor"}},{"comment":"Unary expressions such as ! in boolean palette or - in number palette","ast":{"identifiers":[{"property":"type","value":"UnaryExpression"}],"name_property":"operator","argument_properties":["argument"]},"name_map":{"-":"neg","!":"not"}},{"comment":"Literals such as numbers or booleans or strings","ast":{"identifiers":[{"property":"type","value":"Literal"}],"value_property":"value"}},{"comment":"Dictionary get function for 2 arguments","ast":{"identifiers":[{"property":"type","value":"AwaitExpression"},{"property":"argument.type","value":"CallExpression"},{"property":"argument.callee.type","value":"MemberExpression"},{"property":"argument.callee.property.name","value":"getValue"},{"property":"argument.arguments","size":2}],"name_property":"argument.callee.property.name","argument_properties":["argument.arguments[1]","argument.arguments[0]"]},"name_map":{"getValue":"getDict"}},{"comment":"Dictionary get function for 1 argument","ast":{"identifiers":[{"property":"type","value":"AwaitExpression"},{"property":"argument.type","value":"CallExpression"},{"property":"argument.callee.type","value":"MemberExpression"},{"property":"argument.callee.property.name","value":"getValue"},{"property":"argument.arguments","size":1}],"name_property":"argument.callee.property.name","argument_properties":["argument.arguments[0]"]},"name_map":{"getValue":"getDict2"}},{"comment":"Skip, this is for children","ast":{"identifiers":[{"property":"type","value":"ArrowFunctionExpression"}]}},{"comment":"Math operators such as absolute value or square","ast":{"identifiers":[{"property":"type","value":"CallExpression"},{"property":"callee.type","value":"MemberExpression"},{"property":"callee.object.type","value":"Identifier"},{"property":"callee.object.name","value":"Math"}],"name_property":"callee.property.name","arguments_property":"arguments"},"name_map":{"abs":"abs","floor":"int","pow":"power","sqrt":"sqrt"}},{"comment":"Math utility operators such as distance or random","ast":{"identifiers":[{"property":"type","value":"CallExpression"},{"property":"callee.type","value":"MemberExpression"},{"property":"callee.object.type","value":"Identifier"},{"property":"callee.object.name","value":"MathUtility"}],"name_property":"callee.property.name","arguments_property":"arguments"},"name_map":{"doCalculateDistance":"distance","doOneOf":"oneOf","doRandom":"random"}},{"comment":"Singular argument mouse blocks","ast":{"identifiers":[{"property":"type","value":"MemberExpression"},{"property":"object.name","value":"mouse"}],"identifier_property":"property.name"},"name_map":{"MODELENGTH":"modelength","SCALARCHANGEINPITCH":"deltapitch2","CHANGEINPITCH":"deltapitch","CURRENTKEY":"currentkey","CURRENTMODE":"currentmode","X":"x","Y":"y","GREY":"grey","SHADE":"shade","PENSIZE":"pensize","COLOR":"color","BEATCOUNT":"beatcount","MEASURECOUNT":"measurecount","BPM":"bpm","BEATFACTOR":"beatfactor"}},{"comment":"Number to octave conversion","ast":{"identifiers":[{"property":"type","value":"CallExpression"},{"property":"callee.name","value":"numToOctave"}],"name_property":"callee.name","argument_properties":["arguments[0]"]},"name_map":{"numToOctave":"number2octave"}},{"comment":"Number to pitch conversion","ast":{"identifiers":[{"property":"type","value":"CallExpression"},{"property":"callee.name","value":"numToPitch"}],"name_property":"callee.name","argument_properties":["arguments[0]"]},"name_map":{"numToPitch":"number2pitch"}},{"comment":"Get synth volume value","ast":{"identifiers":[{"property":"type","value":"CallExpression"},{"property":"callee.name","value":"getSynthVolume"}],"name_property":"callee.name","argument_properties":["arguments[0]"]},"name_map":{"getSynthVolume":"synthvolumefactor"}}],"body_blocks":[{"comment":"run is ignored","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"CallExpression"},{"property":"expression.callee.type","value":"MemberExpression"},{"property":"expression.callee.object.name","value":"MusicBlocks"},{"property":"expression.callee.property.name","value":"run"}]}},{"name":"storein2","comment":"Variable assignment in the Boxes palette like 'var box1 = 2 * 5;'","arguments":[{"type":"ValueExpression"}],"ast":{"identifiers":[{"property":"type","value":"VariableDeclaration"},{"property":"declarations[0].init.type","value":"Literal"},{"property":"declarations[0].init.type","value":"BinaryExpression"},{"property":"declarations[0].init.type","value":"UnaryExpression"},{"property":"declarations[0].init.type","value":"CallExpression"}],"name_property":"declarations[0].id.name","argument_properties":["declarations[0].init"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"decrementOne","comment":"Decrement by one block in the Boxes palette 'box1 = box1 - 1;'","arguments":[{"type":"namedbox"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AssignmentExpression"},{"property":"expression.right.type","value":"BinaryExpression"},{"property":"expression.right.operator","value":"-"},{"property":"expression.right.right.value","value":1}],"argument_properties":["expression.left"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"increment","comment":"Increment block in the Boxes palette 'box1 = box1 + 3;'","arguments":[{"type":"namedbox"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AssignmentExpression"},{"property":"expression.right.type","value":"BinaryExpression"},{"property":"expression.right.operator","value":"+"}],"argument_properties":["expression.left","expression.right.right"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"settimbre","comment":"Set instrument block","arguments":[{"type":"voicename"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setInstrument"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"newnote","comment":"Note block in the Rhythm palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playNote"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"rhythmicdot2","comment":"Dot block in the Rhythm palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"dot"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"tie","comment":"Tie block in the Rhythm palette","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"tie"}],"children_properties":["expression.argument.arguments[0].body.body"]},"blocklist_connections":["parent_or_previous_sibling","first_child","next_sibling"],"default_vspaces":{"argument":0}},{"name":"multiplybeatfactor","comment":"Multiply note value block in the Rhythm palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"multiplyNoteValue"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"newswing2","comment":"Swing block in the Rhythm palette","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"swing"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"],"children_properties":["expression.argument.arguments[2].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":2}},{"name":"osctime","comment":"Millisecond note block in the Rhythm palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playNoteMillis"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"pitch","comment":"Pitch block","arguments":[{"type":"note_or_solfege"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playPitch"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"hertz","comment":"Hertz block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playHertz"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"rest2","comment":"Rest block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playRest"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"nameddo","comment":"Action palette, async block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"Identifier"}],"name_property":"expression.argument.callee.name"},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"start","comment":"Start block in the Flow palette","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"NewExpression"}],"children_properties":["expression.arguments[0].body.body"]},"blocklist_connections":["parent_or_previous_sibling","first_child","next_sibling"],"default_vspaces":{"argument":1}},{"name":"action","comment":"Action block in the Action palette","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"VariableDeclaration"},{"property":"declarations[0].init.type","value":"ArrowFunctionExpression"}],"argument_properties":["declarations[0].id"],"children_properties":["declarations[0].init.body.body"]},"default_vspaces":{"argument":1}},{"name":"repeat","comment":"Repeat block in the Flow palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ForStatement"}],"argument_properties":["test.right"],"children_properties":["body.body"]},"default_vspaces":{"argument":1}},{"name":"forever","comment":"Forever block in the Flow palette","ast":{"identifiers":[{"property":"type","value":"WhileStatement"},{"property":"test.raw","value":"1000"}],"children_properties":["body.body"]},"blocklist_connections":["parent_or_previous_sibling","first_child","next_sibling"],"default_vspaces":{"argument":1}},{"name":"while","comment":"While block in the Flow palette","arguments":[{"type":"BooleanExpression"}],"ast":{"identifiers":[{"property":"type","value":"WhileStatement"}],"argument_properties":["test"],"children_properties":["body.body"]},"default_vspaces":{"argument":2}},{"name":"until","comment":"Do While block in the Flow palette","arguments":[{"type":"BooleanExpression"}],"ast":{"identifiers":[{"property":"type","value":"DoWhileStatement"}],"argument_properties":["test"],"children_properties":["body.body"]},"default_vspaces":{"argument":2}},{"name":"switch","comment":"Switch block in the Flow palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"SwitchStatement"}],"argument_properties":["discriminant"],"children_properties":["cases"]},"default_vspaces":{"argument":1}},{"name":"defaultcase","comment":"Default case block in the Flow palette","ast":{"identifiers":[{"property":"type","value":"SwitchCase"},{"property":"test","has_value":false}],"children_properties":["consequent"]},"blocklist_connections":["parent_or_previous_sibling","first_child","next_sibling"],"default_vspaces":{"argument":1}},{"name":"case","comment":"Case block in the Flow palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"SwitchCase"},{"property":"test.type","value":"Literal"}],"argument_properties":["test"],"children_properties":["consequent"]},"default_vspaces":{"argument":1}},{"name":"break","comment":"Break block in the Flow palette","ast":{"identifiers":[{"property":"type","value":"BreakStatement"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"if","comment":"Basic if block in the Flow palette","arguments":[{"type":"BooleanExpression"}],"ast":{"identifiers":[{"property":"type","value":"IfStatement"},{"property":"alternate","has_value":false}],"argument_properties":["test"],"children_properties":["consequent.body"]},"default_vspaces":{"argument":2}},{"name":"ifthenelse","comment":"If-then-else block in the Flow palette","arguments":[{"type":"BooleanExpression"}],"ast":{"identifiers":[{"property":"type","value":"IfStatement"},{"property":"alternate","has_value":true}],"argument_properties":["test"],"children_properties":["consequent.body","alternate.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","first_child","second_child","next_sibling"],"default_vspaces":{"argument":2}},{"name":"crescendo","comment":"Crescendo block in the Volume palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doCrescendo"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"setDict","comment":"Set value block for dictionary, with specific dictionary","arguments":[{"type":"text"},{"type":"text"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setValue"},{"property":"expression.argument.arguments","size":3}],"argument_properties":["expression.argument.arguments[2]","expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","argument","next_sibling"],"default_vspaces":{"body":3}},{"name":"setDict2","comment":"Dictionary blocks, the set functions","arguments":[{"type":"text"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setValue"},{"property":"expression.argument.arguments","size":2}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"meter","comment":"Set meter block from the meter palette","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setMeter"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"pickup","comment":"PICKUP block from the meter palette","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AssignmentExpression"},{"property":"expression.operator","value":"="},{"property":"expression.left.type","value":"MemberExpression"},{"property":"expression.left.property.name","value":"PICKUP"}],"argument_properties":["expression.right"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setbpm3","comment":"Set BPM block from the meter palette","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setBPM"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"setmasterbpm2","comment":"Set master BPM block from the meter palette","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setMasterBPM"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"everybeatdo","comment":"On every note do block from the meter palette","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"onEveryNoteDo"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"everybeatdonew","comment":"On every beat do block from the meter palette","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"onEveryBeatDo"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"onbeatdo","comment":"On strong beat do block from the meter palette","arguments":[{"type":"NumberExpression"},{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"onStrongBeatDo"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"offbeatdo","comment":"On weak beat do block from the meter palette","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"onWeakBeatDo"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"drift","comment":"No clock block from the meter palette","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setNoClock"}],"children_properties":["expression.argument.arguments[0].body.body"]},"blocklist_connections":["parent_or_previous_sibling","first_child","next_sibling"],"default_vspaces":{"argument":1}},{"name":"steppitch","comment":"Step pitch block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"stepPitch"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"nthmodalpitch","comment":"Play nth modal pitch block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playNthModalPitch"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"pitchnumber","comment":"Play pitch number block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playPitchNumber"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"accidental","comment":"Set accidental block","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setAccidental"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"setscalartransposition","comment":"Set scalar transposition block","arguments":[{"type":"modelength"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setScalarTranspose"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"settransposition","comment":"Set semitone transposition block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setSemitoneTranspose"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"register","comment":"Set register block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setRegister"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"invert1","comment":"Invert block","arguments":[{"type":"note_or_solfege"},{"type":"NumberExpression"},{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"invert"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]","expression.argument.arguments[2]"],"children_properties":["expression.argument.arguments[3].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":3}},{"name":"setpitchnumberoffset","comment":"Set pitch number offset block","arguments":[{"type":"note_or_solfege"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setPitchNumberOffset"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"setkey2","comment":"Set key block","arguments":[{"type":"notename"},{"type":"modename"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setKey"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"interval","comment":"Set scalar interval block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setScalarInterval"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"semitoneinterval","comment":"Set semitone interval block","arguments":[{"type":"intervalname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setSemitoneInterval"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"settemperament","comment":"Set temperament block","arguments":[{"type":"temperamentname"},{"type":"notename"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setTemperament"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]","expression.argument.arguments[2]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","argument","next_sibling"],"default_vspaces":{"body":3}},{"name":"vibrato","comment":"Vibrato effect block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doVibrato"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"],"children_properties":["expression.argument.arguments[2].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":2}},{"name":"chorus","comment":"Chorus effect block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doChorus"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]","expression.argument.arguments[2]"],"children_properties":["expression.argument.arguments[3].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":3}},{"name":"phaser","comment":"Phaser effect block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doPhaser"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]","expression.argument.arguments[2]"],"children_properties":["expression.argument.arguments[3].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":3}},{"name":"tremolo","comment":"Tremolo effect block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doTremolo"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"],"children_properties":["expression.argument.arguments[2].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":2}},{"name":"dis","comment":"Distortion effect block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doDistortion"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"harmonic2","comment":"Harmonic effect block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doHarmonic"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"newstaccato","comment":"Set staccato block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setStaccato"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"newslur","comment":"Set slur block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setSlur"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"neighbor2","comment":"Neighbor tone block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doNeighbor"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"],"children_properties":["expression.argument.arguments[2].body.body"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","first_child","next_sibling"],"default_vspaces":{"argument":2}},{"name":"decrescendo","comment":"Decrescendo block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"doDecrescendo"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"articulation","comment":"Set relative volume block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setRelativeVolume"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"setsynthvolume","comment":"Set synth volume block","arguments":[{"type":"voicename"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setSynthVolume"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"setnotevolume","comment":"Set master volume block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AssignmentExpression"},{"property":"expression.operator","value":"="},{"property":"expression.left.type","value":"MemberExpression"},{"property":"expression.left.property.name","value":"MASTERVOLUME"}],"argument_properties":["expression.right"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setpanning","comment":"Set panning block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AssignmentExpression"},{"property":"expression.operator","value":"="},{"property":"expression.left.type","value":"MemberExpression"},{"property":"expression.left.property.name","value":"PANNING"}],"argument_properties":["expression.right"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"playdrum","comment":"Play drum block","arguments":[{"type":"drumname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playDrum"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setdrum","comment":"Set drum block","arguments":[{"type":"effectsname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setDrum"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"mapdrum","comment":"Map pitch to drum block","arguments":[{"type":"drumname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"mapPitchToDrum"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"playdrum","comment":"Play drum block","arguments":[{"type":"drumname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"playDrum"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setdrum","comment":"Set drum block","arguments":[{"type":"effectsname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setDrum"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"mapdrum","comment":"Map pitch to drum block","arguments":[{"type":"drumname"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"mapPitchToDrum"}],"argument_properties":["expression.argument.arguments[0]"],"children_properties":["expression.argument.arguments[1].body.body"]},"default_vspaces":{"argument":1}},{"name":"forward","comment":"Forward block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"goForward"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"back","comment":"Backward block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"goBackward"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"left","comment":"Turn left block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"turnLeft"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"right","comment":"Turn right block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"turnRight"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setxy","comment":"Set position block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setXY"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"setheading","comment":"Set heading block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setHeading"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"arc","comment":"Draw arc block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"drawArc"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"controlpoint1","comment":"Set bezier control point 1 block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setBezierControlPoint1"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"controlpoint2","comment":"Set bezier control point 2 block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setBezierControlPoint2"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"bezier","comment":"Draw bezier block","arguments":[{"type":"NumberExpression"},{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"drawBezier"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"clear","comment":"Clear block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"clear"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"scrollxy","comment":"Scroll XY block","arguments":[{"type":"x"},{"type":"y"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"scrollXY"}],"argument_properties":["expression.argument.arguments[0]","expression.argument.arguments[1]"]},"blocklist_connections":["parent_or_previous_sibling","argument","argument","next_sibling"],"default_vspaces":{"body":2}},{"name":"setcolor","comment":"Set color block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setColor"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setgrey","comment":"Set grey block","arguments":[{"type":"grey"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setGrey"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setshade","comment":"Set shade block","arguments":[{"type":"shade"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setShade"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"sethue","comment":"Set hue block","arguments":[{"type":"color"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setHue"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"settranslucency","comment":"Set translucency block","arguments":[{"type":"NumberExpression"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setTranslucency"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"setpensize","comment":"Set pen size block","arguments":[{"type":"pensize"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setPensize"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]},{"name":"penup","comment":"Pen up block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"penUp"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"pendown","comment":"Pen down block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"penDown"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"background","comment":"Fill background block","ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"fillBackground"}]},"blocklist_connections":["parent_or_previous_sibling","next_sibling"]},{"name":"setfont","comment":"Set font block","arguments":[{"type":"text"}],"ast":{"identifiers":[{"property":"type","value":"ExpressionStatement"},{"property":"expression.type","value":"AwaitExpression"},{"property":"expression.argument.type","value":"CallExpression"},{"property":"expression.argument.callee.type","value":"MemberExpression"},{"property":"expression.argument.callee.property.name","value":"setFont"}],"argument_properties":["expression.argument.arguments[0]"]},"blocklist_connections":["parent_or_previous_sibling","argument","next_sibling"]}]} \ No newline at end of file diff --git a/js/js-export/blocklist_example.png b/js/js-export/blocklist_example.png new file mode 100644 index 0000000000..9d2e3a15d4 Binary files /dev/null and b/js/js-export/blocklist_example.png differ diff --git a/js/js-export/body_vspace.png b/js/js-export/body_vspace.png new file mode 100644 index 0000000000..cdef1842b8 Binary files /dev/null and b/js/js-export/body_vspace.png differ diff --git a/js/js-export/body_with_child.png b/js/js-export/body_with_child.png new file mode 100644 index 0000000000..d70b17a5e8 Binary files /dev/null and b/js/js-export/body_with_child.png differ diff --git a/js/js-export/body_without_child.png b/js/js-export/body_without_child.png new file mode 100644 index 0000000000..7c7f0ccb6b Binary files /dev/null and b/js/js-export/body_without_child.png differ diff --git a/js/js-export/conversion_config_guide.md b/js/js-export/conversion_config_guide.md new file mode 100644 index 0000000000..7c492f4491 --- /dev/null +++ b/js/js-export/conversion_config_guide.md @@ -0,0 +1,491 @@ +# Guide to adding blocks for support in code to blocks conversion + +This guide explains how to add new blocks for code to blocks support in the [ast2blocks.json](./ast2blocks.json) configuration file. This file contains two main sections: `argument_blocks` and `body_blocks`. + +## Table of Contents +1. [Basic Structure](#basic-structure) +2. [Argument Blocks](#argument-blocks) +3. [Body Blocks](#body-blocks) +4. [How to Add a Block](#how-to-add-a-block) +5. [Common Patterns](#common-patterns) +6. [Examples](#examples) +7. [Helper Code](#helper-code) +8. [Troubleshooting](#troubleshooting) + +## Basic Structure + +The ast2blocks.json file has two main sections: +```json +{ + "argument_blocks": [...], + "body_blocks": [...] +} +``` + +- `argument_blocks`: Defines blocks that can be used as arguments in other blocks +- `body_blocks`: Defines main blocks that can contain other blocks, or are just a block of their own + +## Argument Blocks + +Argument blocks are used as arguments for other blocks. They typically represent values or expressions. + +### Basic Structure +```json +{ + "comment": "Description of the argument block", + "ast": { + "identifiers": [ + { + "property": "path_one", + "value": "the_value_at_this_path" + }, + { + "property": "path_n", + "value": "the_value_at_this_path" + }, + { + "property": "last_path", + "value": "PROPERTY_NAME" + } + ], + "name_property": "name", + "argument_properties": [ + "path_to_get_arguments" + ] + }, + "name_map": { + "PROPERTY_NAME": "block_name" + } +} +``` + +They usually look like the following, with a connector section on the left that can fit into another block: + +![argument](./argument_block.png) + +They may also look like this, an argument with space for one or more arguments themself: + +![argument](./argument_with_argument.png) + +### Key Components +- `comment`: Description of what the block does +- `ast`: Abstract Syntax Tree pattern matching + - `identifiers`: Array of conditions that must match + - `name_property`: Property to extract for block name, such as 'voicename' and 'random' in the above blocks + - `argument_properties`: Properties to extract for arguments such as 'min' and 'max' in the random block example above. The goal is to construct an array of paths to each argument, and there are two ways to do this. The first way is when you know exactly how many arguments and where they are, so you can construct an array `argument_properties`: ['path_to_arg1', 'path_to_arg_n']. The second way is seen directly below. + - `arguments_property`: This is an ALTERNATIVE TO `argument_properties`, this is used when not all blocks in name_map have the same number of arguments. Therefore you will construct your arguments array using `arguments_property`: 'path_to_all_args'. The code will recognize this pattern and construct the array for you. +- `name_map`: Maps AST identifiers to block names + +## Body Blocks + +Body blocks are the main blocks that are either their own block or can contain other blocks. There are two kinds of body blocks: + +A body block that can take in children as shown below (see the clamp like structure that can hold children): + +note + +A body block that is just a block by itself: + +note + +### Basic Structure +```json +{ + "name": "block_name", + "comments": "Description of the block", + "arguments": [ + { + "type": "argument_type" + } + ], + "ast": { + "identifiers": [ + { + "property": "path_one", + "value": "the_value_at_this_path" + }, + { + "property": "path_n", + "value": "the_value_at_this_path" + } + ], + "argument_properties": [ + "argument_path" + ], + "children_properties": [ + "children_path" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "first_child", + "next_sibling" + ], + "default_vspaces": { + "argument": 1 + } +} +``` + +### Key Components +- `name`: Unique identifier for the block as represented in the blocklist +- `comments`: Description of what the block does + - This is only for readability, write whatever you want and make it clear. +- `arguments`: Array of argument types the block accepts +- `ast`: Abstract Syntax Tree pattern matching + - `identifiers`: Array of conditions that must match + - `argument_properties`: Paths to extract arguments + - `children_properties`: Paths to extract child blocks +- `blocklist_connections`: Defines how the block connects to others + - The blocks in the blocklist is in the form of [ID, name, x, y, [blocklist_connections]] +- `default_vspaces`: Vertical spacing configuration. This property is needed purely for the addition of vertical spacing if arguments are too long and could cover up blocks beneath them. There are two types of blocks that needs this: + - Blocks with children need to know how long their arguments are compared to the initial space for arguments they have to add the delta amount of vspaces to the children as seen here: note +Therefore in this case we add 'argument' : 1, because the initial argument space is 1. + - Blocks without children need to know how long the block itself is to add space beneath it in case the argument length exceeds that, as seen here: note Therefore in this case, we add 'body' : 2, since the block is 2 vertical spaces tall without the arguments. + +## How to Add a Block +First, read the argument and body blocks section, understand their structures, and categorize the new block into either an argument block or a body block. + +Make a new .js file with just the JS code with your new unsupported block in it, go to the Helper code section, and follow the initial steps of running that script to get your AST representation of that code. + +Initially, you will get an unsupported statement error, which should be your unadded block. + +Now, download the HTML file for your block and locate the blocklist. This will look something like below, with the blocklist between the red parentheses. + +![blocklist](./blocklist_example.png) + +As you can see, blocklist representation is in the form of + +`[ID, name, x, y, [blocklist_connections]]` + +Using this structure, you can find the name of your new block in this way and add under the name property. + +Next, use the AST you previously generated and command/ctrl F to find the name of that block AS SHOWN IN YOUR CODE (note the name from the blocklist is most times different from what shows up in the code and AST). + - Work your way up the tree from the name until you hit the object the name is in right before you hit a list. This means find the largest possible object the name is in. You will either find the object is in an array called "body", as shown below + +note + + - Or the object is in an array called "arguments", as shown below + +note + + - Your identifiers path will start from the top of this object, all the way down back to the name to get your identifiers path + - For the above example, the path would be +```json +{ + "property": "type", + "value": "ExpressionStatement" +}, +{ + "property": "expression.type", + "value": "AwaitExpression" +}, +{ + "property": "expression.argument.type", + "value": "CallExpression" +}, +{ + "property": "expression.argument.callee.type", + "value": "MemberExpression" +}, +{ + "property": "expression.argument.callee.property.name", + "value": "doVibrato" +} +``` +One detail to note for the identifiers path is that during matching, we use AND logic, meaning all of these property value pairs must be satisfied. However, if there are multiple possible paths, we use the same property with different values as much as needed, and the code will use OR logic on those paths. An example is shown below: + +```json +{ + "property": "type", + "value": "VariableDeclaration" +}, +{ + "property": "declarations[0].init.type", + "value": "Literal" +}, +{ + "property": "declarations[0].init.type", + "value": "BinaryExpression" +}, +{ + "property": "declarations[0].init.type", + "value": "UnaryExpression" +}, +{ + "property": "declarations[0].init.type", + "value": "CallExpression" +} +``` +For this example, all values with property path 'declarations[0].init.type' count as a match. + - For arguments and children property paths, there is a property called "arguments" in the object. Get the full path to the arguments list, and the first n elements should be the n arguments, so list them in the argument_properties for your block. + - If there are arguments, you also have to add an "arguments" section above "ast", with the types of the arguments. Based on the block, this can be one of the following supported types: + - text (string arguments) + - BooleanExpression (expression returning boolean such as a = 1) + - NumberExpression (expression returning number such as 1 + 1) + - ValueExpression (variable assignment) + - custom type (if the argument has a custom name, just write that name) + + - The children path is most times the path to the last argument in that list, then add .body.body to the path + - See examples and generate your own AST for those blocks to follow along so the process will become clearer. + + - To get the blocklist_connections: + - Use UI to see what kind of connections a block needs. This will be in the form of "parent_or_previous_sibling" if a block can connect with a block above it, "argument" if the block has an argument slot, "first_child" or "second_child" depending on how many clamps a block has, and "next_sibling", if a block can have another block connected underneath it. + - The default blocklist_connections configuration is a simple single clamp block that takes in one argument, which means it will simply have all 4 of the above components. For example: + + ![child](./body_with_child.png) + + If the new block being added has that configuration, there's no need to add this section. + + - The default vertical spacing will be for a single body block of vspace 1. Do not add this section if the block being added fits that description. Otherwise, use "argument": n for clamp blocks with n arguments, and "body": n for non clamp blocks with n arguments. + +## Common Patterns + +### 1. Simple Method Call +```json +{ + "name": "methodname", + "comments": "Description", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "methodName" + } + ] + } +} +``` + +### 2. Method with Arguments +```json +{ + "name": "methodname", + "comments": "Description", + "arguments": [ + { + "type": "argument_type" + } + ], + "ast": { + "identifiers": [...], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + } +} +``` + +### 3. Method with Children +```json +{ + "name": "methodname", + "comments": "Description", + "ast": { + "identifiers": [...], + "children_properties": [ + "expression.argument.arguments[0].body.body" + ] + } +} +``` + +## Examples + +### Example 1: Simple Argument Block +```json +{ + "comment": "Mouse X coordinate", + "ast": { + "identifiers": [ + { + "property": "type", + "value": "MemberExpression" + }, + { + "property": "object.name", + "value": "mouse" + }, + { + "property": "property.name", + "value": "X" + } + ], + "identifier_property": "property.name" + }, + "name_map": { + "X": "x" + } +} +``` + +### Example 2: Body Block with Arguments +```json +{ + "name": "setcolor", + "comments": "Set color block", + "arguments": [ + { + "type": "NumberExpression" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setColor" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ] + }, + "blocklist_connections": [ + "parent_or_previous_sibling", + "argument", + "next_sibling" + ], + "default_vspaces": { + "body": 1 + } +} +``` + +### Example 3: Body block with arguments and children +```json +{ + "name": "semitoneinterval", + "comment": "Set semitone interval block", + "arguments": [ + { + "type": "intervalname" + } + ], + "ast": { + "identifiers": [ + { + "property": "type", + "value": "ExpressionStatement" + }, + { + "property": "expression.type", + "value": "AwaitExpression" + }, + { + "property": "expression.argument.type", + "value": "CallExpression" + }, + { + "property": "expression.argument.callee.type", + "value": "MemberExpression" + }, + { + "property": "expression.argument.callee.property.name", + "value": "setSemitoneInterval" + } + ], + "argument_properties": [ + "expression.argument.arguments[0]" + ], + "children_properties": [ + "expression.argument.arguments[1].body.body" + ] + }, + "default_vspaces": { + "argument": 1 + } +} +``` + +## Helper Code +Copy the following driver code into a file in the same folder as ast2blocklist.js, then add another file with the JS code you want to convert to blocks and run using + +```node [name of this file] [name of file you want to convert]```. + +You will get an error for unsupported blocks, and also the AST that will help you with adding the blocks. + +Once finished adding, run the same command again, and you should see the outputted blocklist form if done right. + +Finally, run ```node minify.js``` to compress the JSON as the code uses the minified file. + +``` +function main() { + const fs = require('node:fs'); + const acorn = require('../../lib/acorn.min'); + const { AST2BlockList } = require('./ast2blocklist'); + const config = require('./ast2blocks.json'); + + if (process.argv.length <= 2) { + console.error("node code2blocks.js [music_block_js_file]"); + return; + } + let jsFile = process.argv[2]; + + fs.readFile(jsFile, 'utf8', (err, data) => { + if (err) { + console.error(err); + return; + } + const AST = acorn.parse(data, { ecmaVersion: 2020 }); + console.log(JSON.stringify(AST, null, 2)); + try { + let blockList = AST2BlockList.toBlockList(AST, config); + console.log(toString(blockList)); + } catch (e) { + console.error("message" in e ? e.message : e.prefix + data.substring(e.start, e.end)); + } + }); +} + +function toString(blockList) { + if (!Array.isArray(blockList)) { + return String(blockList); // Handle non-array inputs + } + + return blockList.map(item => { + if (typeof item === 'object' && item !== null) { + return JSON.stringify(item); // Expand objects + } else { + return String(item); // Convert other items to strings + } + }).join(',\n'); +} + +if (require.main === module) { + main(); +} +``` + +## Troubleshooting + +Common issues when adding blocks: + +1. **Pattern Not Matching** + - Check all identifier conditions + - Verify property paths are correct + +2. **Arguments Not Working** + - Verify argument types are defined + - Check argument property paths + - Ensure proper connection points + +3. **Children Not Appearing** + - Verify children_properties paths + - Check blocklist_connections \ No newline at end of file diff --git a/js/js-export/minify.js b/js/js-export/minify.js new file mode 100644 index 0000000000..5a0e44ec74 --- /dev/null +++ b/js/js-export/minify.js @@ -0,0 +1,7 @@ +const fs = require("fs"); + +const jsonContent = fs.readFileSync("ast2blocks.json", "utf8"); + +const minified = JSON.stringify(JSON.parse(jsonContent)); + +fs.writeFileSync("ast2blocks.min.json", minified); diff --git a/js/widgets/jseditor.js b/js/widgets/jseditor.js index 6a2f6a4a28..b3fd713d92 100644 --- a/js/widgets/jseditor.js +++ b/js/widgets/jseditor.js @@ -453,8 +453,7 @@ class JSEditor { try { let ast = acorn.parse(this._code, { ecmaVersion: 2020 }); - let trees = AST2BlockList.toTrees(ast); - let blockList = AST2BlockList.toBlockList(trees); + let blockList = AST2BlockList.toBlockList(ast, ast2blocklist_config); const activity = this.activity; // Wait for the old blocks to be removed, then load new blocks. const __listener = (event) => { @@ -586,4 +585,4 @@ class JSEditor { if (arrowBtn) arrowBtn.innerHTML = "keyboard_arrow_down"; } } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 43ed2685fd..43c98f1aa0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "gulp-sourcemaps": "^2.6.5", "http-server": "^14.1.1", "lodash.template": "^4.5.0", + "node": "^24.2.0", "node-static": "^0.7.11", "tone": "^15.0.4" }, @@ -9329,6 +9330,28 @@ "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, + "node_modules/node": { + "version": "24.2.0", + "resolved": "https://registry.npmjs.org/node/-/node-24.2.0.tgz", + "integrity": "sha512-+LxcguHN48uLeFUdC8w9SGbJhuOklUrCgl0K3dmqhXdokzwd83HKXUbeA8JLd41G+77W9Ovq7FXGCij74Ff/tw==", + "hasInstallScript": true, + "license": "ISC", + "dependencies": { + "node-bin-setup": "^1.0.0" + }, + "bin": { + "node": "bin/node" + }, + "engines": { + "npm": ">=5.0.0" + } + }, + "node_modules/node-bin-setup": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.1.4.tgz", + "integrity": "sha512-vWNHOne0ZUavArqPP5LJta50+S8R261Fr5SvGul37HbEDcowvLjwdvd0ZeSr0r2lTSrPxl6okq9QUw8BFGiAxA==", + "license": "ISC" + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", diff --git a/package.json b/package.json index b04ae92572..528de36199 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "gulp-sourcemaps": "^2.6.5", "http-server": "^14.1.1", "lodash.template": "^4.5.0", + "node": "^24.2.0", "node-static": "^0.7.11", "tone": "^15.0.4" }