apple502j / parse-sb3-blocks

Parse sb3 blocks, and generate scratchblocks formatted code.
https://apple502j.github.io/parse-sb3-blocks/
BSD 3-Clause "New" or "Revised" License
14 stars 6 forks source link

If statements with null substack fail to parse as scratchblocks #9

Open ajskateboarder opened 7 months ago

ajskateboarder commented 7 months ago

Here's a minimal reproducable example:

import {toScratchblocks} from "./parse-sb3-blocks.mjs"

let projectJSON = {"targets":[{"isStage":true,"name":"Stage","variables":{"`jEk@4|i[#Fk?(8x)AV.-my variable":["my variable",0]},"lists":{},"broadcasts":{},"blocks":{},"comments":{},"currentCostume":0,"costumes":[{"name":"backdrop1","dataFormat":"svg","assetId":"cd21514d0531fdffb22204e0ec5ed84a","md5ext":"cd21514d0531fdffb22204e0ec5ed84a.svg","rotationCenterX":240,"rotationCenterY":180}],"sounds":[],"volume":100,"layerOrder":0,"tempo":60,"videoTransparency":50,"videoState":"on","textToSpeechLanguage":null},{"isStage":false,"name":"Sprite1","variables":{},"lists":{},"broadcasts":{},"blocks":{"a":{"opcode":"event_whenflagclicked","next":"b","parent":null,"inputs":{},"fields":{},"shadow":false,"topLevel":true,"x":34,"y":152},"b":{"opcode":"control_forever","next":null,"parent":"a","inputs":{},"fields":{},"shadow":false,"topLevel":false}},"comments":{},"currentCostume":0,"costumes":[{"name":"costume1","bitmapResolution":1,"dataFormat":"svg","assetId":"592bae6f8bb9c8d88401b54ac431f7b6","md5ext":"592bae6f8bb9c8d88401b54ac431f7b6.svg","rotationCenterX":44,"rotationCenterY":44}],"sounds":[],"volume":100,"layerOrder":1,"visible":true,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"rotationStyle":"all around"}],"monitors":[],"extensions":[],"meta":{"semver":"3.0.0","vm":"0.2.0","agent":""}};
// this also fails
// projectJSON = {"targets":[{"isStage":true,"name":"Stage","variables":{"`jEk@4|i[#Fk?(8x)AV.-my variable":["my variable",0]},"lists":{},"broadcasts":{},"blocks":{},"comments":{},"currentCostume":0,"costumes":[{"name":"backdrop1","dataFormat":"svg","assetId":"cd21514d0531fdffb22204e0ec5ed84a","md5ext":"cd21514d0531fdffb22204e0ec5ed84a.svg","rotationCenterX":240,"rotationCenterY":180}],"sounds":[],"volume":100,"layerOrder":0,"tempo":60,"videoTransparency":50,"videoState":"on","textToSpeechLanguage":null},{"isStage":false,"name":"Sprite1","variables":{},"lists":{},"broadcasts":{},"blocks":{"a":{"opcode":"event_whenflagclicked","next":"b","parent":null,"inputs":{},"fields":{},"shadow":false,"topLevel":true,"x":34,"y":152},"b":{"opcode":"control_forever","next":null,"parent":"a","inputs":{},"fields":{},"shadow":false,"topLevel":false}},"comments":{},"currentCostume":0,"costumes":[{"name":"costume1","bitmapResolution":1,"dataFormat":"svg","assetId":"592bae6f8bb9c8d88401b54ac431f7b6","md5ext":"592bae6f8bb9c8d88401b54ac431f7b6.svg","rotationCenterX":44,"rotationCenterY":44}],"sounds":[],"volume":100,"layerOrder":1,"visible":true,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"rotationStyle":"all around"}],"monitors":[],"extensions":[],"meta":{"semver":"3.0.0","vm":"0.2.0","agent":""}};
const stage = projectJSON.targets.filter(t => t.isStage)[0];
const whenGreenflag = Object.keys(stage.blocks).filter(key => stage.blocks[key].opcode === 'event_whenflagclicked')[0];
console.log(toScratchblocks(whenGreenflag, stage.blocks, "en", {tab: "  "}));

This will throw the following:

TypeError: Cannot set properties of undefined (setting 'id')
mxmou commented 5 months ago

The reason why this example code doesn't work is unrelated to empty C blocks - it's trying to find a "when green flag clicked" block in the stage but the code is in a sprite. The following works correctly:

import {toScratchblocks} from "./parse-sb3-blocks.mjs"

let projectJSON = {"targets":[{"isStage":true,"name":"Stage","variables":{"`jEk@4|i[#Fk?(8x)AV.-my variable":["my variable",0]},"lists":{},"broadcasts":{},"blocks":{},"comments":{},"currentCostume":0,"costumes":[{"name":"backdrop1","dataFormat":"svg","assetId":"cd21514d0531fdffb22204e0ec5ed84a","md5ext":"cd21514d0531fdffb22204e0ec5ed84a.svg","rotationCenterX":240,"rotationCenterY":180}],"sounds":[],"volume":100,"layerOrder":0,"tempo":60,"videoTransparency":50,"videoState":"on","textToSpeechLanguage":null},{"isStage":false,"name":"Sprite1","variables":{},"lists":{},"broadcasts":{},"blocks":{"a":{"opcode":"event_whenflagclicked","next":"b","parent":null,"inputs":{},"fields":{},"shadow":false,"topLevel":true,"x":34,"y":152},"b":{"opcode":"control_forever","next":null,"parent":"a","inputs":{},"fields":{},"shadow":false,"topLevel":false}},"comments":{},"currentCostume":0,"costumes":[{"name":"costume1","bitmapResolution":1,"dataFormat":"svg","assetId":"592bae6f8bb9c8d88401b54ac431f7b6","md5ext":"592bae6f8bb9c8d88401b54ac431f7b6.svg","rotationCenterX":44,"rotationCenterY":44}],"sounds":[],"volume":100,"layerOrder":1,"visible":true,"x":0,"y":0,"size":100,"direction":90,"draggable":false,"rotationStyle":"all around"}],"monitors":[],"extensions":[],"meta":{"semver":"3.0.0","vm":"0.2.0","agent":""}};
const sprite = projectJSON.targets.filter(t => !t.isStage)[0];
const whenGreenflag = Object.keys(sprite.blocks).filter(key => sprite.blocks[key].opcode === 'event_whenflagclicked')[0];
console.log(toScratchblocks(whenGreenflag, sprite.blocks, "en", {tab: "  "}));

The library parses an empty C block correctly in this project: https://apple502j.github.io/parse-sb3-blocks/demo?pid=1007401022&locale=en&sprite=%20Player

ajskateboarder commented 5 months ago

Whoops, I was being too general (and also less attentive) when making this issue. So far, I have only had issues trying to parse if statements that have no inner blocks. Here's what I believe is a bug from looking at issues in my use case:

import { toScratchblocks } from "./parse-sb3-blocks.module.js";

let script = {
  "(": {
    fields: {},
    inputs: {},
    next: ")",
    opcode: "event_whenflagclicked",
    parent: null,
    shadow: false,
    topLevel: true,
    x: 416,
    y: 846,
    id: "(",
  },
  ")": {
    fields: {},
    inputs: {
      SUBSTACK: [1, null],
    },
    next: null,
    opcode: "control_if",
    parent: "(",
    shadow: false,
    topLevel: false,
    id: ")",
  },
};

console.log(toScratchblocks("(", script, "en", { tab: "  " }));

Which throws:

TypeError: Cannot set properties of undefined (setting 'id')
    at se (parse-sb3-blocks.module.js:1:1616164)
    at parse-sb3-blocks.module.js:1:1614024
    at Array.forEach (<anonymous>)
    at ae (parse-sb3-blocks.module.js:1:1613930)
    at se (parse-sb3-blocks.module.js:1:1616392)
    at ne (parse-sb3-blocks.module.js:1:1616636)
    at index.js:30:13
    at ModuleJob.run (node:internal/modules/esm/module_job:193:25)
    at async Promise.all (index 0)
    at async ESMLoader.import (node:internal/modules/esm/loader:526:24)

I received this error on the latest build of parse-sb3-blocks here. The demo also seems to fail to parse it as well: https://apple502j.github.io/parse-sb3-blocks/demo?pid=1009824086&locale=en&sprite=Sprite1

mxmou commented 5 months ago

How was this project made? When saving it using Scratch, "inputs": {"SUBSTACK": [1, null]}, becomes "inputs": {}, which parses correctly.

ajskateboarder commented 5 months ago

The project I made for the demo was made using plain Scratch. The JSON I provided was through TurboWarp though

I will try replacing {"SUBSTACK": [1, null]} with {} before passing it to parseSB3Blocks

edit: This works:

import { toScratchblocks } from "./parse-sb3-blocks.module.js";

let script = {
  "(": {
    fields: {},
    inputs: {},
    next: ")",
    opcode: "event_whenflagclicked",
    parent: null,
    shadow: false,
    topLevel: true,
    x: 416,
    y: 846,
    id: "(",
  },
  ")": {
    fields: {},
    inputs: {
      SUBSTACK: [1, null],
    },
    next: null,
    opcode: "control_if",
    parent: "(",
    shadow: false,
    topLevel: false,
    id: ")",
  },
};

let script = JSON.parse(
  JSON.stringify(script)
    .replaceAll('{"SUBSTACK":[1,null]}', "{}")
    .replaceAll(',"SUBSTACK":[1,null]', "")
)
console.log(toScratchblocks("(", script, "en", { tab: "  " }));

Thanks so much for this workaround, this bug was driving me nuts. As for a proper fix, I don't think the project author would need to do much other than adding documentation somewhere on how to handle it.