Scratch_vlm_v1 / utils /block_relation_builder.py
WebashalarForML's picture
Upload 175 files
a522962 verified
raw
history blame
158 kB
import json
import copy
import re
from collections import defaultdict
import secrets
import string
from typing import Dict, Any, TypedDict
#################################################################################################################################################################
#--------------------------------------------------[Creating a skelton json with some initial default value inside it]-------------------------------------------
#################################################################################################################################################################
def generate_blocks_from_opcodes(opcode_counts, all_block_definitions):
"""
Generates a dictionary of Scratch-like blocks based on a list of opcodes and a reference block definition,
and groups all generated block keys by their corresponding opcode.
Returns:
tuple: (generated_blocks, opcode_to_keys)
- generated_blocks: dict of block_key -> block_data
- opcode_to_keys: dict of opcode -> list of block_keys
"""
generated_blocks = {}
opcode_counts_map = {} # For counting unique suffix per opcode
opcode_to_keys = {} # For grouping block keys by opcode
explicit_menu_links = {
"motion_goto": [("TO", "motion_goto_menu")],
"motion_glideto": [("TO", "motion_glideto_menu")],
"motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
"sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
"sensing_of": [("OBJECT", "sensing_of_object_menu")],
"sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
"control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
"sound_play": [("SOUND_MENU", "sound_sounds_menu")],
"sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
"looks_switchcostumeto": [("COSTUME", "looks_costume")],
"looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
}
for item in opcode_counts:
opcode = item.get("opcode")
count = item.get("count", 1)
if opcode == "sensing_istouching": # Handle potential old opcode name
opcode = "sensing_touchingobject"
if not opcode or opcode not in all_block_definitions:
print(f"Warning: Skipping opcode '{opcode}' (missing or not in definitions).")
continue
for _ in range(count):
# Count occurrences per opcode for unique key generation
opcode_counts_map[opcode] = opcode_counts_map.get(opcode, 0) + 1
instance_num = opcode_counts_map[opcode]
main_key = f"{opcode}_{instance_num}"
# Track the generated key
opcode_to_keys.setdefault(opcode, []).append(main_key)
main_block_data = copy.deepcopy(all_block_definitions[opcode])
main_block_data["parent"] = None
main_block_data["next"] = None
main_block_data["topLevel"] = True
main_block_data["shadow"] = False
# Ensure inputs and fields are dictionaries, even if they were None or list in definition
if "inputs" not in main_block_data or not isinstance(main_block_data["inputs"], dict):
main_block_data["inputs"] = {}
if "fields" not in main_block_data or not isinstance(main_block_data["fields"], dict):
main_block_data["fields"] = {}
generated_blocks[main_key] = main_block_data
# Handle menus
if opcode in explicit_menu_links:
for input_name, menu_opcode in explicit_menu_links[opcode]:
if menu_opcode not in all_block_definitions:
continue
opcode_counts_map[menu_opcode] = opcode_counts_map.get(menu_opcode, 0) + 1
menu_instance_num = opcode_counts_map[menu_opcode]
menu_key = f"{menu_opcode}_{menu_instance_num}"
opcode_to_keys.setdefault(menu_opcode, []).append(menu_key)
menu_block_data = copy.deepcopy(all_block_definitions[menu_opcode])
menu_block_data["shadow"] = True
menu_block_data["topLevel"] = False
menu_block_data["next"] = None
menu_block_data["parent"] = main_key
# Ensure inputs and fields are dictionaries for menu blocks too
if "inputs" not in menu_block_data or not isinstance(menu_block_data["inputs"], dict):
menu_block_data["inputs"] = {}
if "fields" not in menu_block_data or not isinstance(menu_block_data["fields"], dict):
menu_block_data["fields"] = {}
if input_name in main_block_data.get("inputs", {}) and \
isinstance(main_block_data["inputs"][input_name], list) and \
len(main_block_data["inputs"][input_name]) > 1 and \
main_block_data["inputs"][input_name][0] == 1:
main_block_data["inputs"][input_name][1] = menu_key
generated_blocks[menu_key] = menu_block_data
return generated_blocks, opcode_to_keys
#################################################################################################################################################################
#--------------------------------------------------[Block Defination which hold skelton json with default value inside it]---------------------------------------
#################################################################################################################################################################
# Consolidated block definitions from all JSON files
all_block_definitions = {
# motion_block.json
"motion_movesteps": {
"block_name": "move () steps", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_movesteps",
"functionality": "Moves the sprite forward by the specified number of steps in the direction it is currently facing. A positive value moves it forward, and a negative value moves it backward.",
"inputs": {"STEPS": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_turnright": {
"block_name": "turn right () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnright",
"functionality": "Turns the sprite clockwise by the specified number of degrees.",
"inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_turnleft": {
"block_name": "turn left () degrees", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_turnleft",
"functionality": "Turns the sprite counter-clockwise by the specified number of degrees.",
"inputs": {"DEGREES": [1, [4, "15"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_goto": {
"block_name": "go to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_goto",
"functionality": "Moves the sprite to a specified location, which can be a random position or at the mouse pointer or another to the sprite.",
"inputs": {"TO": [1, "motion_goto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_goto_menu": {
"block_name": "go to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_goto_menu",
"functionality": "Menu for go to block.",
"inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
},
"motion_gotoxy": {
"block_name": "go to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_gotoxy",
"functionality": "Moves the sprite to the specified X and Y coordinates on the stage.",
"inputs": {"X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_glideto": {
"block_name": "glide () secs to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glideto",
"functionality": "Glides the sprite smoothly to a specified location (random position, mouse pointer, or another sprite) over a given number of seconds.",
"inputs": {"SECS": [1, [4, "1"]], "TO": [1, "motion_glideto_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_glideto_menu": {
"block_name": "glide to menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_glideto_menu",
"functionality": "Menu for glide to block.",
"inputs": {}, "fields": {"TO": ["_random_", None]}, "shadow": True, "topLevel": False
},
"motion_glidesecstoxy": {
"block_name": "glide () secs to x: () y: ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_glidesecstoxy",
"functionality": "Glides the sprite smoothly to the specified X and Y coordinates over a given number of seconds.",
"inputs": {"SECS": [1, [4, "1"]], "X": [1, [4, "0"]], "Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_pointindirection": {
"block_name": "point in direction ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointindirection",
"functionality": "Sets the sprite's direction to a specified angle in degrees (0 = up, 90 = right, 180 = down, -90 = left).",
"inputs": {"DIRECTION": [1, [8, "90"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_pointtowards": {
"block_name": "point towards ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_pointtowards",
"functionality": "Points the sprite towards the mouse pointer or another specified sprite.",
"inputs": {"TOWARDS": [1, "motion_pointtowards_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_pointtowards_menu": {
"block_name": "point towards menu", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_pointtowards_menu",
"functionality": "Menu for point towards block.",
"inputs": {}, "fields": {"TOWARDS": ["_mouse_", None]}, "shadow": True, "topLevel": False
},
"motion_changexby": {
"block_name": "change x by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changexby",
"functionality": "Changes the sprite's X-coordinate by the specified amount, moving it horizontally.",
"inputs": {"DX": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_setx": {
"block_name": "set x to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setx",
"functionality": "Sets the sprite's X-coordinate to a specific value, placing it at a precise horizontal position.",
"inputs": {"X": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_changeyby": {
"block_name": "change y by ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_changeyby",
"functionality": "Changes the sprite's Y-coordinate by the specified amount, moving it vertically.",
"inputs": {"DY": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_sety": {
"block_name": "set y to ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_sety",
"functionality": "Sets the sprite's Y-coordinate to a specific value, placing it at a precise vertical position.",
"inputs": {"Y": [1, [4, "0"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_ifonedgebounce": {
"block_name": "if on edge, bounce", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_ifonedgebounce",
"functionality": "Reverses the sprite's direction if it touches the edge of the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_setrotationstyle": {
"block_name": "set rotation style ()", "block_type": "Motion", "block_shape": "Stack Block", "op_code": "motion_setrotationstyle",
"functionality": "Determines how the sprite rotates: 'left-right' (flips horizontally), 'don't rotate' (stays facing one direction), or 'all around' (rotates freely).",
"inputs": {}, "fields": {"STYLE": ["left-right", None]}, "shadow": False, "topLevel": True
},
"motion_xposition": {
"block_name": "(x position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_xposition",
"functionality": "Reports the current X-coordinate of the sprite.[NOTE: not used in stage/backdrops]",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_yposition": {
"block_name": "(y position)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_yposition",
"functionality": "Reports the current Y coordinate of the sprite on the stage.[NOTE: not used in stage/backdrops]",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"motion_direction": {
"block_name": "(direction)", "block_type": "Motion", "block_shape": "Reporter Block", "op_code": "motion_direction",
"functionality": "Reports the current direction of the sprite in degrees (0 = up, 90 = right, 180 = down, -90 = left).[NOTE: not used in stage/backdrops]",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_distanceto": { # Added sensing_distanceto
"block_name": "(distance to ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_distanceto",
"functionality": "Reports the distance from the sprite to the mouse-pointer or another specified sprite.",
"inputs": {}, "fields": {"TARGET": ["_mouse_", None]}, "shadow": False, "topLevel": True
},
# control_block.json
"control_wait": {
"block_name": "wait () seconds", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait",
"functionality": "Pauses the script for a specified duration.",
"inputs": {"DURATION": [1, [5, "1"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_repeat": {
"block_name": "repeat ()", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat",
"functionality": "Repeats the blocks inside it a specified number of times.",
"inputs": {"TIMES": [1, [6, "10"]], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_forever": {
"block_name": "forever", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_forever",
"functionality": "Continuously runs the blocks inside it.",
"inputs": {"SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_if": {
"block_name": "if <> then", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if",
"functionality": "Executes the blocks inside it only if the specified boolean condition is true. [NOTE: it takes boolean blocks as input]",
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_if_else": {
"block_name": "if <> then else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else",
"functionality": "Executes one set of blocks if the specified boolean condition is true, and a different set of blocks if the condition is false. [NOTE: it takes boolean blocks as input]",
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_wait_until": {
"block_name": "wait until <>", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_wait_until",
"functionality": "Pauses the script until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
"inputs": {"CONDITION": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_repeat_until": {
"block_name": "repeat until <>", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_repeat_until",
"functionality": "Repeats the blocks inside it until the specified boolean condition becomes true. [NOTE: it takes boolean blocks as input]",
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_stop": {
"block_name": "stop [v]", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_stop",
"functionality": "Halts all scripts, only the current script, or other scripts within the same sprite. Its shape can dynamically change based on the selected option.",
"inputs": {}, "fields": {"STOP_OPTION": ["all", None]}, "shadow": False, "topLevel": True, "mutation": {"tagName": "mutation", "children": [], "hasnext": "false"}
},
"control_start_as_clone": {
"block_name": "When I Start as a Clone", "block_type": "Control", "block_shape": "Hat Block", "op_code": "control_start_as_clone",
"functionality": "This Hat block initiates the script when a clone of the sprite is created. It defines the behavior of individual clones.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"control_create_clone_of": {
"block_name": "create clone of ()", "block_type": "Control", "block_shape": "Stack Block", "op_code": "control_create_clone_of",
"functionality": "Generates a copy, or clone, of a specified sprite (or 'myself' for the current sprite).",
"inputs": {"CLONE_OPTION": [1, "control_create_clone_of_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"control_create_clone_of_menu": {
"block_name": "create clone of menu", "block_type": "Control", "block_shape": "Reporter Block", "op_code": "control_create_clone_of_menu",
"functionality": "Menu for create clone of block.",
"inputs": {}, "fields": {"CLONE_OPTION": ["_myself_", None]}, "shadow": True, "topLevel": False
},
"control_delete_this_clone": {
"block_name": "delete this clone", "block_type": "Control", "block_shape": "Cap Block", "op_code": "control_delete_this_clone",
"functionality": "Removes the clone that is executing it from the stage.",
"inputs":None, "fields": {}, "shadow": False, "topLevel": True
},
# data_block.json
"data_setvariableto": {
"block_name": "set [my variable v] to ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_setvariableto",
"functionality": "Assigns a specific value (number, string, or boolean) to a variable.",
"inputs": {"VALUE": [1, [10, "0"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
},
"data_changevariableby": {
"block_name": "change [my variable v] by ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_changevariableby",
"functionality": "Increases or decreases a variable's numerical value by a specified amount.",
"inputs": {"VALUE": [1, [4, "1"]]}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
},
"data_showvariable": {
"block_name": "show variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showvariable",
"functionality": "Makes a variable's monitor visible on the stage.",
"inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
},
"data_hidevariable": {
"block_name": "hide variable [my variable v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidevariable",
"functionality": "Hides a variable's monitor from the stage.",
"inputs": {}, "fields": {"VARIABLE": ["my variable", "`jEk@4|i[#Fk?(8x)AV.-my variable"]}, "shadow": False, "topLevel": True
},
"data_addtolist": {
"block_name": "add () to [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_addtolist",
"functionality": "Appends an item to the end of a list.",
"inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_deleteoflist": {
"block_name": "delete () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deleteoflist",
"functionality": "Removes an item from a list by its index or by selecting 'all' items.",
"inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_deletealloflist": {
"block_name": "delete all of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_deletealloflist",
"functionality": "Removes all items from a list.",
"inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_insertatlist": {
"block_name": "insert () at () of [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_insertatlist",
"functionality": "Inserts an item at a specific position within a list.",
"inputs": {"ITEM": [1, [10, "thing"]], "INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_replaceitemoflist": {
"block_name": "replace item () of [my list v] with ()", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_replaceitemoflist",
"functionality": "Replaces an item at a specific position in a list with a new value.",
"inputs": {"INDEX": [1, [7, "1"]], "ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_itemoflist": {
"block_name": "(item (2) of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemoflist",
"functionality": "Reports the item located at a specific position in a list.",
"inputs": {"INDEX": [1, [7, "1"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_itemnumoflist": {
"block_name": "(item # of [Dog] in [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_itemnumoflist",
"functionality": "Reports the index number of the first occurrence of a specified item in a list. If the item is not found, it reports 0.",
"inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_lengthoflist": {
"block_name": "(length of [myList v])", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_lengthoflist",
"functionality": "Provides the total number of items contained in a list.",
"inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_listcontainsitem": {
"block_name": "<[my list v] contains ()?>", "block_type": "Data", "block_shape": "Boolean Block", "op_code": "data_listcontainsitem",
"functionality": "Checks if a list includes a specific item.",
"inputs": {"ITEM": [1, [10, "thing"]]}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_showlist": {
"block_name": "show list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_showlist",
"functionality": "Makes a list's monitor visible on the stage.",
"inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_hidelist": {
"block_name": "hide list [my list v]", "block_type": "Data", "block_shape": "Stack Block", "op_code": "data_hidelist",
"functionality": "Hides a list's monitor from the stage.",
"inputs": {}, "fields": {"LIST": ["MY_LIST", "o6`kIhtT{xWH+rX(5d,A"]}, "shadow": False, "topLevel": True
},
"data_variable": { # This is a reporter block for a variable's value
"block_name": "[variable v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_variable",
"functionality": "Provides the current value stored in a variable.",
"inputs": {}, "fields": {"VARIABLE": ["my variable", None]}, "shadow": True, "topLevel": False
},
"data_list": { # Added this block definition
"block_name": "[list v]", "block_type": "Data", "block_shape": "Reporter Block", "op_code": "data_list",
"functionality": "Reports the entire content of a specified list. When clicked in the editor, it displays the list as a monitor.",
"inputs": {}, "fields": {"LIST": ["my list", None]}, "shadow": True, "topLevel": False
},
# event_block.json
"event_whenflagclicked": {
"block_name": "when green flag pressed", "block_type": "Events", "op_code": "event_whenflagclicked", "block_shape": "Hat Block",
"functionality": "This Hat block initiates the script when the green flag is clicked, serving as the common starting point for most Scratch projects.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"event_whenkeypressed": {
"block_name": "when () key pressed", "block_type": "Events", "op_code": "event_whenkeypressed", "block_shape": "Hat Block",
"functionality": "This Hat block initiates the script when a specified keyboard key is pressed.",
"inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": False, "topLevel": True
},
"event_whenthisspriteclicked": {
"block_name": "when this sprite clicked", "block_type": "Events", "op_code": "event_whenthisspriteclicked", "block_shape": "Hat Block",
"functionality": "This Hat block starts the script when the sprite itself is clicked.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"event_whenbackdropswitchesto": {
"block_name": "when backdrop switches to ()", "block_type": "Events", "op_code": "event_whenbackdropswitchesto", "block_shape": "Hat Block",
"functionality": "This Hat block triggers the script when the stage backdrop changes to a specified backdrop.",
"inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": False, "topLevel": True
},
"event_whengreaterthan": {
"block_name": "when () > ()", "block_type": "Events", "op_code": "event_whengreaterthan", "block_shape": "Hat Block",
"functionality": "This Hat block starts the script when a certain value (e.g., loudness from a microphone, or the timer) exceeds a defined threshold.",
"inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"WHENGREATERTHANMENU": ["LOUDNESS", None]}, "shadow": False, "topLevel": True
},
"event_whenbroadcastreceived": {
"block_name": "when I receive ()", "block_type": "Events", "op_code": "event_whenbroadcastreceived", "block_shape": "Hat Block",
"functionality": "This Hat block initiates the script upon the reception of a specific broadcast message. This mechanism facilitates indirect communication between sprites or the stage.",
"inputs": {}, "fields": {"BROADCAST_OPTION": ["message1", "5O!nei;S$!c!=hCT}0:a"]}, "shadow": False, "topLevel": True
},
"event_broadcast": {
"block_name": "broadcast ()", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcast",
"functionality": "Sends a broadcast message throughout the Scratch program, activating any 'when I receive ()' blocks that are set to listen for that message, enabling indirect communication.",
"inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"event_broadcastandwait": {
"block_name": "broadcast () and wait", "block_type": "Events", "block_shape": "Stack Block", "op_code": "event_broadcastandwait",
"functionality": "Sends a broadcast message and pauses the current script until all other scripts activated by that broadcast have completed their execution, ensuring sequential coordination.",
"inputs": {"BROADCAST_INPUT": [1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]}, "fields": {}, "shadow": False, "topLevel": True
},
# looks_block.json
"looks_sayforsecs": {
"block_name": "say () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_sayforsecs",
"functionality": "Displays a speech bubble containing specified text for a set duration.",
"inputs": {"MESSAGE": [1, [10, "Hello!"]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_say": {
"block_name": "say ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_say",
"functionality": "Displays a speech bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
"inputs": {"MESSAGE": [1, [10, "Hello!"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_thinkforsecs": {
"block_name": "think () for () seconds", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_thinkforsecs",
"functionality": "Displays a thought bubble containing specified text for a set duration.",
"inputs": {"MESSAGE": [1, [10, "Hmm..."]], "SECS": [1, [4, "2"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_think": {
"block_name": "think ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_think",
"functionality": "Displays a thought bubble with the specified text indefinitely until another 'say' or 'think' block is activated.",
"inputs": {"MESSAGE": [1, [10, "Hmm..."]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_switchcostumeto": {
"block_name": "switch costume to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchcostumeto",
"functionality": "Alters the sprite's appearance to a designated costume.",
"inputs": {"COSTUME": [1, "looks_costume"]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_costume": {
"block_name": "costume menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costume",
"functionality": "Menu for switch costume to block.",
"inputs": {}, "fields": {"COSTUME": ["costume1", None]}, "shadow": True, "topLevel": False
},
"looks_nextcostume": {
"block_name": "next costume", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextcostume",
"functionality": "Switches the sprite's costume to the next one in its costume list. If it's the last costume, it cycles back to the first.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_switchbackdropto": {
"block_name": "switch backdrop to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdropto",
"functionality": "Changes the stage's backdrop to a specified backdrop.",
"inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_backdrops": {
"block_name": "backdrop menu", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdrops",
"functionality": "Menu for switch backdrop to block.",
"inputs": {}, "fields": {"BACKDROP": ["backdrop1", None]}, "shadow": True, "topLevel": False
},
"looks_switchbackdroptowait": {
"block_name": "switch backdrop to () and wait", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_switchbackdroptowait",
"functionality": "Changes the stage's backdrop to a specified backdrop and pauses the script until any 'When backdrop switches to' scripts for that backdrop have finished.",
"inputs": {"BACKDROP": [1, "looks_backdrops"]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_nextbackdrop": {
"block_name": "next backdrop", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_nextbackdrop",
"functionality": "Switches the stage's backdrop to the next one in its backdrop list. If it's the last backdrop, it cycles back to the first.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_changesizeby": {
"block_name": "change size by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changesizeby",
"functionality": "Changes the sprite's size by a specified percentage. Positive values make it larger, negative values make it smaller.",
"inputs": {"CHANGE": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_setsizeto": {
"block_name": "set size to () %", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_setsizeto",
"functionality": "Sets the sprite's size to a specific percentage of its original size.",
"inputs": {"SIZE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_changeeffectby": {
"block_name": "change () effect by ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_changeeffectby",
"functionality": "Changes a visual effect on the sprite by a specified amount (e.g., color, fisheye, whirl, pixelate, mosaic, brightness, ghost).",
"inputs": {"CHANGE": [1, [4, "25"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
},
"looks_seteffectto": {
"block_name": "set () effect to ()", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_seteffectto",
"functionality": "Sets a visual effect on the sprite to a specific value.",
"inputs": {"VALUE": [1, [4, "0"]]}, "fields": {"EFFECT": ["COLOR", None]}, "shadow": False, "topLevel": True
},
"looks_cleargraphiceffects": {
"block_name": "clear graphic effects", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_cleargraphiceffects",
"functionality": "Removes all visual effects applied to the sprite.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_show": {
"block_name": "show", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_show",
"functionality": "Makes the sprite visible on the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_hide": {
"block_name": "hide", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_hide",
"functionality": "Makes the sprite invisible on the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"looks_gotofrontback": {
"block_name": "go to () layer", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_gotofrontback",
"functionality": "Moves the sprite to the front-most or back-most layer of other sprites on the stage.",
"inputs": {}, "fields": {"FRONT_BACK": ["front", None]}, "shadow": False, "topLevel": True
},
"looks_goforwardbackwardlayers": {
"block_name": "go () layers", "block_type": "Looks", "block_shape": "Stack Block", "op_code": "looks_goforwardbackwardlayers",
"functionality": "Moves the sprite forward or backward a specified number of layers in relation to other sprites.",
"inputs": {"NUM": [1, [7, "1"]]}, "fields": {"FORWARD_BACKWARD": ["forward", None]}, "shadow": False, "topLevel": True
},
"looks_costumenumbername": {
"block_name": "(costume ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_costumenumbername",
"functionality": "Reports the current costume's number or name.",
"inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
},
"looks_backdropnumbername": {
"block_name": "(backdrop ())", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_backdropnumbername",
"functionality": "Reports the current backdrop's number or name.",
"inputs": {}, "fields": {"NUMBER_NAME": ["number", None]}, "shadow": False, "topLevel": True
},
"looks_size": {
"block_name": "(size)", "block_type": "Looks", "block_shape": "Reporter Block", "op_code": "looks_size",
"functionality": "Reports the current size of the sprite as a percentage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
# operator_block.json
"operator_add": {
"block_name": "(() + ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_add",
"functionality": "Adds two numerical values.",
"inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_subtract": {
"block_name": "(() - ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_subtract",
"functionality": "Subtracts the second numerical value from the first.",
"inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_multiply": {
"block_name": "(() * ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_multiply",
"functionality": "Multiplies two numerical values.",
"inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_divide": {
"block_name": "(() / ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_divide",
"functionality": "Divides the first numerical value by the second.",
"inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_random": {
"block_name": "(pick random () to ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_random",
"functionality": "Generates a random integer within a specified inclusive range.",
"inputs": {"FROM": [1, [4, "1"]], "TO": [1, [4, "10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_gt": {
"block_name": "<() > ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_gt",
"functionality": "Checks if the first value is greater than the second.",
"inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_lt": {
"block_name": "<() < ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_lt",
"functionality": "Checks if the first value is less than the second.",
"inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_equals": {
"block_name": "<() = ()>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_equals",
"functionality": "Checks if two values are equal.",
"inputs": {"OPERAND1": [1, [10, ""]], "OPERAND2": [1, [10, "50"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_and": {
"block_name": "<<> and <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_and",
"functionality": "Returns 'true' if both provided Boolean conditions are 'true'.",
"inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_or": {
"block_name": "<<> or <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_or",
"functionality": "Returns 'true' if at least one of the provided Boolean conditions is 'true'.",
"inputs": {"OPERAND1": [2, None], "OPERAND2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_not": {
"block_name": "<not <>>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_not",
"functionality": "Returns 'true' if the provided Boolean condition is 'false', and 'false' if it is 'true'.",
"inputs": {"OPERAND": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_join": {
"block_name": "(join ()())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_join",
"functionality": "Concatenates two strings or values into a single string.",
"inputs": {"STRING1": [1, [10, "apple "]], "STRING2": [1, [10, "banana"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_letterof": {
"block_name": "letter () of ()", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_letterof",
"functionality": "Reports the character at a specific numerical position within a string.",
"inputs": {"LETTER": [1, [6, "1"]], "STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_length": {
"block_name": "(length of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_length",
"functionality": "Reports the total number of characters in a given string.",
"inputs": {"STRING": [1, [10, "apple"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_contains": {
"block_name": "<() contains ()?>", "block_type": "operator", "block_shape": "Boolean Block", "op_code": "operator_contains",
"functionality": "Checks if one string contains another string.",
"inputs": {"STRING1": [1, [10, "apple"]], "STRING2": [1, [10, "a"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_mod": {
"block_name": "(() mod ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mod",
"functionality": "Reports the remainder when the first number is divided by the second.",
"inputs": {"NUM1": [1, [4, ""]], "NUM2": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_round": {
"block_name": "(round ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_round",
"functionality": "Rounds a numerical value to the nearest integer.",
"inputs": {"NUM": [1, [4, ""]]}, "fields": {}, "shadow": False, "topLevel": True
},
"operator_mathop": {
"block_name": "(() of ())", "block_type": "operator", "block_shape": "Reporter Block", "op_code": "operator_mathop",
"functionality": "Performs various mathematical functions (e.g., absolute value, square root, trigonometric functions).",
"inputs": {"NUM": [1, [4, ""]]}, "fields": {"OPERATOR": ["abs", None]}, "shadow": False, "topLevel": True
},
# sensing_block.json
"sensing_touchingobject": {
"block_name": "<touching [edge v]?>", "block_type": "Sensing", "op_code": "sensing_touchingobject", "block_shape": "Boolean Block",
"functionality": "Checks if its sprite is touching the mouse-pointer, edge, or another specified sprite.",
"inputs": {"TOUCHINGOBJECTMENU": [1, "sensing_touchingobjectmenu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_touchingobjectmenu": {
"block_name": "touching object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_touchingobjectmenu",
"functionality": "Menu for touching object block.",
"inputs": {}, "fields": {"TOUCHINGOBJECTMENU": ["_mouse_", None]}, "shadow": True, "topLevel": False
},
"sensing_touchingcolor": {
"block_name": "<touching color ()?>", "block_type": "Sensing", "op_code": "sensing_touchingcolor", "block_shape": "Boolean Block",
"functionality": "Checks whether its sprite is touching a specified color.",
"inputs": {"COLOR": [1, [9, "#55b888"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_coloristouchingcolor": {
"block_name": "<color () is touching ()?>", "block_type": "Sensing", "op_code": "sensing_coloristouchingcolor", "block_shape": "Boolean Block",
"functionality": "Checks whether a specific color on its sprite is touching another specified color on the stage or another sprite.",
"inputs": {"COLOR1": [1, [9, "#d019f2"]], "COLOR2": [1, [9, "#2b0de3"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_askandwait": {
"block_name": "Ask () and Wait", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_askandwait",
"functionality": "Displays an input box with specified text at the bottom of the screen, allowing users to input text, which is stored in the 'Answer' block.",
"inputs": {"QUESTION": [1, [10, "What's your name?"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_answer": {
"block_name": "(answer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_answer",
"functionality": "Holds the most recent text inputted using the 'Ask () and Wait' block.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_keypressed": {
"block_name": "<key () pressed?>", "block_type": "Sensing", "op_code": "sensing_keypressed", "block_shape": "Boolean Block",
"functionality": "Checks if a specified keyboard key is currently being pressed.",
"inputs": {"KEY_OPTION": [1, "sensing_keyoptions"]}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_keyoptions": {
"block_name": "key options menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_keyoptions",
"functionality": "Menu for key pressed block.",
"inputs": {}, "fields": {"KEY_OPTION": ["space", None]}, "shadow": True, "topLevel": False
},
"sensing_mousedown": {
"block_name": "<mouse down?>", "block_type": "Sensing", "op_code": "sensing_mousedown", "block_shape": "Boolean Block",
"functionality": "Checks if the computer mouse's primary button is being clicked while the cursor is over the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_mousex": {
"block_name": "(mouse x)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousex",
"functionality": "Reports the mouse-pointer’s current X position on the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_mousey": {
"block_name": "(mouse y)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_mousey",
"functionality": "Reports the mouse-pointer’s current Y position on the stage.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_setdragmode": {
"block_name": "set drag mode [draggable v]", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_setdragmode",
"functionality": "Sets whether the sprite can be dragged by the mouse on the stage.",
"inputs": {}, "fields": {"DRAG_MODE": ["draggable", None]}, "shadow": False, "topLevel": True
},
"sensing_loudness": {
"block_name": "(loudness)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_loudness",
"functionality": "Reports the loudness of noise received by a microphone on a scale of 0 to 100.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_timer": {
"block_name": "(timer)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_timer",
"functionality": "Reports the elapsed time since Scratch was launched or the timer was reset, increasing by 1 every second.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_resettimer": {
"block_name": "Reset Timer", "block_type": "Sensing", "block_shape": "Stack Block", "op_code": "sensing_resettimer",
"functionality": "Sets the timer’s value back to 0.0.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_of": {
"block_name": "(() of ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of",
"functionality": "Reports a specified value (e.g., x position, direction, costume number) of a specified sprite or the Stage to be accessed in current sprite or stage.",
"inputs": {"OBJECT": [1, "sensing_of_object_menu"]}, "fields": {"PROPERTY": ["backdrop #", None]}, "shadow": False, "topLevel": True
},
"sensing_of_object_menu": {
"block_name": "of object menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_of_object_menu",
"functionality": "Menu for of block.",
"inputs": {}, "fields": {"OBJECT": ["_stage_", None]}, "shadow": True, "topLevel": False
},
"sensing_current": {
"block_name": "(current ())", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_current",
"functionality": "Reports the current local year, month, date, day of the week, hour, minutes, or seconds.",
"inputs": {}, "fields": {"CURRENTMENU": ["YEAR", None]}, "shadow": False, "topLevel": True
},
"sensing_dayssince2000": {
"block_name": "(days since 2000)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_dayssince2000",
"functionality": "Reports the number of days (and fractions of a day) since 00:00:00 UTC on January 1, 2000.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sensing_username": {
"block_name": "(username)", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_username",
"functionality": "Reports the username of the user currently logged into Scratch. If no user is logged in, it reports nothing.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
# sound_block.json
"sound_playuntildone": {
"block_name": "play sound () until done", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_playuntildone",
"functionality": "Plays a specified sound and pauses the script's execution until the sound has completed.",
"inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_sounds_menu": {
"block_name": "sound menu", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_sounds_menu",
"functionality": "Menu for sound blocks.",
"inputs": {}, "fields": {"SOUND_MENU": ["Meow", None]}, "shadow": True, "topLevel": False
},
"sound_play": {
"block_name": "start sound ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_play",
"functionality": "Initiates playback of a specified sound without pausing the script, allowing other actions to proceed concurrently.",
"inputs": {"SOUND_MENU": [1, "sound_sounds_menu"]}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_stopallsounds": {
"block_name": "stop all sounds", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_stopallsounds",
"functionality": "Stops all currently playing sounds.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_changeeffectby": {
"block_name": "change () effect by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changeeffectby",
"functionality": "Changes the project's sound effect by a specified amount.",
"inputs": {"VALUE": [1, [4, "10"]]}, "fields": {"EFFECT": ["PITCH", None]}, "shadow": False, "topLevel": True
},
"sound_seteffectto": {
"block_name": "set () effect to ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_seteffectto",
"functionality": "Sets the sound effect to a specific value.",
"inputs": {"VALUE": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_cleareffects": {
"block_name": "clear sound effects", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_cleareffects",
"functionality": "Removes all sound effects applied to the sprite.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_changevolumeby": {
"block_name": "change volume by ()", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_changevolumeby",
"functionality": "Changes the project's sound volume by a specified amount.",
"inputs": {"VOLUME": [1, [4, "-10"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_setvolumeto": {
"block_name": "set volume to () %", "block_type": "Sound", "block_shape": "Stack Block", "op_code": "sound_setvolumeto",
"functionality": "Sets the sound volume to a specific percentage (0-100).",
"inputs": {"VOLUME": [1, [4, "100"]]}, "fields": {}, "shadow": False, "topLevel": True
},
"sound_volume": {
"block_name": "(volume)", "block_type": "Sound", "block_shape": "Reporter Block", "op_code": "sound_volume",
"functionality": "Reports the current volume level of the sprite.",
"inputs": {}, "fields": {}, "shadow": False, "topLevel": True
},
"procedures_definition": {
"block_name": "define [my custom block]",
"block_type": "My Blocks",
"op_code": "procedures_definition",
"block_shape": "Hat Block",
"functionality": "This Hat block serves as the definition header for a custom block's script.",
"inputs": {}, # Changed to empty dict
"fields": {},
"shadow": False,
"topLevel": True
},
"procedures_call": {
"block_name": "[my custom block]",
"block_type": "My Blocks",
"block_shape": "Stack Block",
"op_code": "procedures_call",
"functionality": "Executes the script defined by a corresponding 'define' Hat block.",
"inputs": {}, # Changed to empty dict
"fields": {},
"shadow": False,
"topLevel": True
}
}
#################################################################################################################################################################
#--------------------------------------------------[Helper Functions]---------------------------------------------------------------------------------------------
#################################################################################################################################################################
def unparen(s):
s = s.strip()
# keep peeling off *all* matching outer parens
while True:
m = re.fullmatch(r"\((.*)\)", s)
if not m:
break
s = m.group(1).strip()
return s
def _register_block(opcode, parent_key, is_shadow, pick_key_func, all_blocks_dict, inputs=None, fields=None):
"""
Helper to create and register a block in the all_blocks_dict.
It uses pick_key_func to get a unique ID.
"""
key = pick_key_func(opcode)
block_data = copy.deepcopy(all_block_definitions[opcode])
block_data["id"] = key
block_data["parent"] = parent_key # Set parent directly here if known
block_data["next"] = None
block_data["topLevel"] = not is_shadow # Shadow blocks are not top-level
block_data["shadow"] = is_shadow
# Ensure inputs and fields are dictionaries
if "inputs" not in block_data or not isinstance(block_data["inputs"], dict):
block_data["inputs"] = {}
if "fields" not in block_data or not isinstance(block_data["fields"], dict):
block_data["fields"] = {}
if inputs:
block_data["inputs"].update(inputs)
if fields:
block_data["fields"].update(fields)
all_blocks_dict[key] = block_data
return key
def _auto_balance(text):
# if there are more "(" than ")", append the missing ")"
diff = text.count("(") - text.count(")")
if diff > 0:
text = text + ")"*diff
# same for square brackets
diff = text.count("[") - text.count("]")
if diff > 0:
text = text + "]"*diff
return text
def strip_outer_angle_brackets(text):
"""
Strip exactly one balanced pair of outer <...> brackets, only if they wrap the whole string.
"""
text = text.strip()
if text.startswith("<") and text.endswith(">"):
depth = 0
for i, char in enumerate(text):
if char == '<':
depth += 1
elif char == '>':
depth -= 1
if depth == 0 and i == len(text) - 1:
return text[1:-1].strip()
# If we exit the loop and depth is 0, it means the outer brackets were balanced and wrapped the whole string
if depth == 0:
return text[1:-1].strip()
return text
def extract_condition_balanced(stmt):
# 1. Remove "if" and "then"
stmt = stmt.strip()
if stmt.lower().startswith("if "):
stmt = stmt[3:].strip()
if stmt.lower().startswith("repeat until"):
stmt = stmt[12:].strip()
if stmt.lower().startswith("wait until "):
stmt = stmt[11:].strip()
if stmt.lower().endswith(" then"):
stmt = stmt[:-5].strip()
# Helper to detect and strip single outer balanced angle brackets
def unwrap_balanced(s):
if s.startswith("<") and s.endswith(">"):
depth = 0
for i in range(len(s)):
if s[i] == "<":
depth += 1
elif s[i] == ">":
depth -= 1
if depth == 0 and i < len(s) - 1:
return s # Early balance → not a single outer wrapper
if depth == 0:
return s[1:-1].strip()
return s
# Recursively simplify things like <not <x>> to not <x>
def simplify(s):
s = unwrap_balanced(s)
s = s.strip()
# Match <not <...>> pattern
m = re.fullmatch(r"not\s*<(.+)>", s, re.IGNORECASE)
if m:
inner = m.group(1).strip()
inner = simplify(inner)
return f"not <{inner}>"
# Match comparison operators like <(x position) < (100)>
# This part might be redundant if the main parser handles it, but good for internal consistency
m_comp = re.fullmatch(r"<\s*\(([^<>]+?)\)\s*([<>=])\s*\(([^<>]+?)\)\s*>", stmt)
if m_comp:
return f"({m_comp.group(1).strip()}) {m_comp.group(2)} ({m_comp.group(3).strip()})"
return s
return simplify(stmt)
#################################################################################################################################################################
#--------------------------------------------------[Regular Expression which handle the reporter variable and values]--------------------------------------------
#################################################################################################################################################################
# Nested helper for parsing reporters or values
def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_blocks):
text = _auto_balance(text.strip())
text = unparen(text.strip())
# Check for numeric literal (including parenthesized numbers like "(0)" or "(10)")
m_num = re.fullmatch(r"\(?\s*(-?\d+(\.\d+)?)\s*\)?", text)
if m_num:
val_str = m_num.group(1)
return {"kind": "value", "value": float(val_str) if '.' in val_str else int(val_str)}
# Variable reporter: [score v], [health v], etc.
m_var = re.fullmatch(r"\[([^\]]+)\s*v\]", text)
if m_var:
var_name = m_var.group(1).strip()
block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks,
fields={"VARIABLE": [var_name, None]})
return {"kind": "block", "block": block_id}
# Now catch other bracketed values as literal strings
if text.startswith('[') and text.endswith(']'):
return {"kind": "value", "value": text[1:-1]}
# --- Reporter Blocks ---
# (x position), (y position), (direction), (mouse x), (mouse y), (loudness), (timer), (days since 2000), (username), (answer), (size), (volume)
simple_reporters = {
"x position": "motion_xposition",
"y position": "motion_yposition",
"direction": "motion_direction",
"mouse x": "sensing_mousex",
"mouse y": "sensing_mousey",
"loudness": "sensing_loudness",
"timer": "sensing_timer",
"days since 2000": "sensing_dayssince2000",
"username": "sensing_username",
"answer": "sensing_answer",
"size": "looks_size",
"volume": "sound_volume"
}
# Check for simple reporters, potentially with outer parentheses
m_simple_reporter = re.fullmatch(r"\((.+?)\)", text)
if m_simple_reporter:
inner_text = m_simple_reporter.group(1).strip()
if inner_text in simple_reporters:
block_id = _register_block(simple_reporters[inner_text], parent_key, False, pick_key_func, all_generated_blocks)
return {"kind": "block", "block": block_id}
# Also check for simple reporters without parentheses (e.g., if passed directly)
if text in simple_reporters:
block_id = _register_block(simple_reporters[text], parent_key, False, pick_key_func, all_generated_blocks)
return {"kind": "block", "block": block_id}
# Variable reporter: [score v] or (score) or just "score"
m_var = re.fullmatch(r"\[([^\]]+)\s*v\]", text)
if m_var:
var_name = m_var.group(1).strip()
block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [var_name, None]})
return {"kind": "block", "block": block_id}
m_paren_var = re.fullmatch(r"\(([^)]+)\)", text)
if m_paren_var:
potential_var_name = m_paren_var.group(1).strip()
# Ensure it's not a simple reporter already handled, or a number
if potential_var_name not in simple_reporters and not re.fullmatch(r"-?\d+(\.\d+)?", potential_var_name):
block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [potential_var_name, None]})
return {"kind": "block", "block": block_id}
# Handle plain variable names like "score", "number 1", "total score"
if re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_ ]*", text): # Allow spaces for "number 1" etc.
# Exclude known simple reporters that don't have 'v' or parentheses
if text not in simple_reporters:
block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [text, None]})
return {"kind": "block", "block": block_id}
# List reporter: [my list v]
m_list_reporter = re.fullmatch(r"\[([^\]]+)\s*v\]", text)
if m_list_reporter:
list_name = m_list_reporter.group(1).strip()
block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]})
return {"kind": "block", "block": block_id}
# (pick random () to ()) (operator_random)
m = re.search(r"pick random \((.+?)\) to \((.+?)\)", text)
if m:
min_val_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks) # Parent will be set later
max_val_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks) # Parent will be set later
inputs = {"FROM": min_val_obj, "TO": max_val_obj}
block_id = _register_block("operator_random", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
# Set parents for nested inputs
if min_val_obj.get("kind") == "block": all_generated_blocks[min_val_obj["block"]]["parent"] = block_id
if max_val_obj.get("kind") == "block": all_generated_blocks[max_val_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (join ()()) (operator_join) - handle both [] and () for inputs
#m = re.search(r"join\s+(\[.+?\]|\(.+?\))\s+(\[.+?\]|\(.+?\))", text) # Try (val) (val)
m = re.search(r"join\s+(\[.+?\]|\(.+?\))\s*(\[.+?\]|\(.+?\))", text)
if m:
part1_txt = m.group(1).strip()
part2_txt = m.group(2).strip()
str1_obj = parse_reporter_or_value(part1_txt, None, pick_key_func, all_generated_blocks)
str2_obj = parse_reporter_or_value(part2_txt, None, pick_key_func, all_generated_blocks)
inputs = {"STRING1": str1_obj, "STRING2": str2_obj}
block_id = _register_block("operator_join", parent_key, False,
pick_key_func, all_generated_blocks,
inputs=inputs)
# set parents if nested blocks
for obj in (str1_obj, str2_obj):
if obj.get("kind") == "block":
all_generated_blocks[obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# letter () of () (operator_letterof) - handle both [] and () for inputs
m = re.search(r"letter \((.+?)\) of \((.+?)\)", text)
if not m:
m = re.search(r"letter \((.+?)\) of \[(.+?)\]", text)
if m:
index_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
string_val_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"LETTER": index_obj, "STRING": string_val_obj}
block_id = _register_block("operator_letterof", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id
if string_val_obj.get("kind") == "block": all_generated_blocks[string_val_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (length of ()) (operator_length) - handle both [] and () for inputs
#m = re.search(r"length of \((.+?)\)", text)
m = re.search(r"length of\s*(?:\((.+?)\)|\[(.+?)\])", text)
if not m:
m = re.search(r"length of \[([^\]]+)\s*v\]", text)
if m:
arg_txt = (m.group(1) or m.group(2)).strip()
list_or_string_val_obj = parse_reporter_or_value(arg_txt, None, pick_key_func, all_generated_blocks)
#list_or_string_val_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"STRING": list_or_string_val_obj}
block_id = _register_block("operator_length", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
if list_or_string_val_obj.get("kind") == "block": all_generated_blocks[list_or_string_val_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (() mod ()) (operator_mod)
# m = re.search(r"\((.+?)\)\s*mod\s*\((.+?)\)", text)
m = re.search(r"\[([^\]]+)\s*v\]\s*mod\s*\(?\s*(.+?)\s*\)?", text)
if m:
num1_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
num2_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"NUM1": num1_obj, "NUM2": num2_obj}
block_id = _register_block("operator_mod", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
if num1_obj.get("kind") == "block": all_generated_blocks[num1_obj["block"]]["parent"] = block_id
if num2_obj.get("kind") == "block": all_generated_blocks[num2_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (round ()) (operator_round)
m = re.search(r"round \((.+?)\)", text)
if m:
num_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"NUM": num_obj}
block_id = _register_block("operator_round", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
if num_obj.get("kind") == "block": all_generated_blocks[num_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (() of ()) (operator_mathop) - handle variable for function type
m = re.search(r"\[([^\]]+)\s*v\] of \((.+?)\)", text) # e.g. [sqrt v] of ((x pos) * (x pos))
if m:
func_type = m.group(1).strip()
value_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"NUM": value_obj}
fields = {"OPERATOR": [func_type.upper(), None]}
block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# Also handle direct string for function type (e.g., "abs of (x)")
m = re.search(r"([a-zA-Z]+)\s*of\s*\((.+?)\)", text)
if m:
func_type = m.group(1).strip()
value_obj = parse_reporter_or_value(m.group(2).strip(), None, pick_key_func, all_generated_blocks)
inputs = {"NUM": value_obj}
fields = {"OPERATOR": [func_type.upper(), None]}
block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# Arithmetic operations: (() + ()), (() - ()), (() * ()), (() / ())
# This regex is designed to handle nested parentheses correctly.
# It looks for an opening parenthesis, then non-parenthesis characters or balanced parentheses,
# followed by an operator, and then the second operand.
# This is a simplified approach; a full-fledged parser would use a stack.
# arithmetic_match = re.search(r"\((.+?)\)\s*([+\-*/])\s*\((.+?)\)", text)
arithmetic_match = re.search(r"\(?\s*(.+?)\s*\)?\s*([\+\-\*/])\s*\(?\s*(.+?)\s*\)?", text)
if not arithmetic_match:
# Try to match without outer parentheses for the operands, but still with an operator
arithmetic_match = re.search(r"(.+?)\s*([+\-*/])\s*(.+)", text)
if arithmetic_match:
op1_str = arithmetic_match.group(1).strip()
operator_symbol = arithmetic_match.group(2).strip()
op2_str = arithmetic_match.group(3).strip()
op1_obj = parse_reporter_or_value(op1_str, None, pick_key_func, all_generated_blocks)
op2_obj = parse_reporter_or_value(op2_str, None, pick_key_func, all_generated_blocks)
opcode_map = {'+': 'operator_add', '-': 'operator_subtract', '*': 'operator_multiply', '/': 'operator_divide'}
inputs = {"NUM1": op1_obj, "NUM2": op2_obj}
block_id = _register_block(opcode_map[operator_symbol], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
if op1_obj.get("kind") == "block": all_generated_blocks[op1_obj["block"]]["parent"] = block_id
if op2_obj.get("kind") == "block": all_generated_blocks[op2_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (costume ()) (looks_costumenumbername) - handle with or without 'v'
m = re.search(r"costume \((.+?)\)", text)
if not m:
m = re.search(r"costume \[([^\]]+)\s*v\]", text)
if m:
option = m.group(1).strip()
fields = {"NUMBER_NAME": [option, None]}
block_id = _register_block("looks_costumenumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields)
return {"kind": "block", "block": block_id}
# (backdrop ()) (looks_backdropnumbername) - handle with or without 'v'
m = re.search(r"backdrop \((.+?)\)", text)
if not m:
m = re.search(r"backdrop \[([^\]]+)\s*v\]", text)
if m:
option = m.group(1).strip()
fields = {"NUMBER_NAME": [option, None]}
block_id = _register_block("looks_backdropnumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields)
return {"kind": "block", "block": block_id}
# (distance to ()) (sensing_distanceto) - handle with or without 'v'
m = re.search(r"distance to \((.+?)\)", text)
if not m:
m = re.search(r"distance to \[([^\]]+)\s*v\]", text)
if m:
target = m.group(1).strip()
if target == "mouse-pointer": target_val = "_mouse_"
elif target == "edge": target_val = "_edge_"
else: target_val = target
# This block has a dropdown FIELD, not an input that links to a shadow block
fields = {"TARGET": [target_val, None]}
block_id = _register_block("sensing_distanceto", parent_key, False, pick_key_func, all_generated_blocks, fields=fields)
return {"kind": "block", "block": block_id}
# (current ()) (sensing_current) - handle with or without 'v'
m = re.search(r"current \((.+?)\)", text)
if not m:
m = re.search(r"current \[([^\]]+)\s*v\]", text)
if m:
unit = m.group(1).strip()
fields = {"CURRENTMENU": [unit.upper(), None]}
block_id = _register_block("sensing_current", parent_key, False, pick_key_func, all_generated_blocks, fields=fields)
return {"kind": "block", "block": block_id}
# (() of ()) (sensing_of) - Corrected logic
#m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text)
m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text)
if m:
prop_str = m.group(1).strip()
obj = (m.group(2) or m.group(3)).strip()
prop_map = {
"x position": "x position", "y position": "y position", "direction": "direction",
"costume #": "costume number", "costume name": "costume name", "size": "size",
"volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name"
}
property_value = prop_map.get(prop_str, prop_str)
if obj.lower() == "stage": obj_val = "_stage_"
elif obj.lower() == "myself": obj_val = "_myself_"
else: obj_val = obj
# Create the sensing_of_object_menu shadow block
object_menu_id = _register_block("sensing_of_object_menu", parent_key, True, pick_key_func, all_generated_blocks, fields={"OBJECT": [obj_val, None]})
# Create the main sensing_of block
inputs = {"OBJECT": [1, object_menu_id]}
fields = {"PROPERTY": [property_value, None]} # PROPERTY is a field of the main block
block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
all_generated_blocks[object_menu_id]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (item (index) of [list v]) (data_itemoflist)
m = re.search(r"item \((.+?)\) of \((.+?)\)", text)
if not m:
m = re.search(r"item \((.+?)\) of \[([^\]]+)\s*v\]", text)
if m:
index_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
list_name = m.group(2).strip()
# Create data_list shadow block for the list name
list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]})
inputs = {"INDEX": index_obj, "LIST": [1, list_block_id]}
fields = {} # No fields in data_itemoflist itself for the list name
block_id = _register_block("data_itemoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id
all_generated_blocks[list_block_id]["parent"] = block_id
return {"kind": "block", "block": block_id}
# (item # of [item] in [list v]) (data_itemnumoflist)
m = re.search(r"item # of \((.+?)\) in \((.+?)\)", text)
if not m:
m = re.search(r"item # of \[([^\]]+)\] in \[([^\]]+)\s*v\]", text)
if m:
item_obj = parse_reporter_or_value(m.group(1).strip(), None, pick_key_func, all_generated_blocks)
list_name = m.group(2).strip()
# Create data_list shadow block for the list name
list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]})
inputs = {"ITEM": item_obj, "LIST": [1, list_block_id]}
fields = {} # No fields in data_itemnumoflist itself for the list name
block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id
all_generated_blocks[list_block_id]["parent"] = block_id
return {"kind": "block", "block": block_id}
raise ValueError(f"Can't parse reporter or value: {text}")
#################################################################################################################################################################
#--------------------------------------------------[Regular Expression which handle the conditon and other operation logics]-------------------------------------
#################################################################################################################################################################
def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks):
"""
Parse Scratch-style boolean conditions, handling comparisons (<, =, >),
boolean operators (and, or, not), and other sensing conditions.
"""
s = stmt.strip()
s = extract_condition_balanced(s)
s = s.lower()
print(f"the stmt was this {stmt} and parsed was this {s}")
# 1) Boolean NOT: `not <...>`
#m_not = re.fullmatch(r'not\s*<\s*(.+?)\s*>', s, re.IGNORECASE)
#m_not = re.fullmatch(r"\s*<\s*not\s+(.+?)\s*>\s*", s, re.IGNORECASE)
m_not = re.fullmatch(r"\s*(?:<\s*)?not\s+(.+?)(?:\s*>)?\s*",s, re.IGNORECASE)
if m_not:
inner = m_not.group(1).strip()
print(f"[2]the stmt was this {stmt} and parsed was this {s}")
inner_obj = parse_condition(inner, parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
bid = _register_block("operator_not", parent_key, False, pick_key_func, all_generated_blocks, # Pass parent_key
inputs={"OPERAND": inner_obj})
if inner_obj.get("kind") == "block":
all_generated_blocks[inner_obj["block"]]["parent"] = bid
return {"kind": "block", "block": bid}
# 2) Boolean AND / OR
#m_andor = re.fullmatch(r"<\s*(.+?)\s+(and|or)\s+(.+?)\s*>", s, re.IGNORECASE)
m_andor = re.fullmatch(r"\s*(.+?)\s+(and|or)\s+(.+?)\s*", s, re.IGNORECASE)
if m_andor:
cond1_obj = parse_condition(m_andor.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
cond2_obj = parse_condition(m_andor.group(3).strip(), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
op_block = 'operator_and' if m_andor.group(2).lower() == 'and' else 'operator_or'
print(f"The cond1: {cond1_obj} and the cond2: {cond2_obj} [for testing]")
inputs = {"OPERAND1": cond1_obj, "OPERAND2": cond2_obj}
block_id = _register_block(op_block, parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
if cond1_obj.get("kind") == "block": all_generated_blocks[cond1_obj["block"]]["parent"] = block_id
if cond2_obj.get("kind") == "block": all_generated_blocks[cond2_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# 1a) Comparisons with explicit angle wrappers: < (...) op (...) >
m = re.fullmatch(
r"\s*<\s*(.+?)\s*(?P<op><|=|>)\s*(.+?)\s*>\s*",
s,
re.VERBOSE
)
if m:
left_txt, right_txt = m.group(1), m.group(3)
operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}
inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj}
block_id = _register_block(op_map[m.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
# Set parents for nested inputs
if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id
if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# 1b) Simple comparisons without angle wrappers: A op B
m_simple = re.fullmatch(r"\s*(.+?)\s*(?P<op><|=|>)\s*(.+?)\s*", s)
if m_simple:
left_txt, right_txt = m_simple.group(1), m_simple.group(3)
operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks) # Pass parent_key
op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}
inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj}
block_id = _register_block(op_map[m_simple.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id
if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id
return {"kind": "block", "block": block_id}
# 4) Contains: <[list v] contains [item]?>
#m = re.search(r"\[([^\]]+)\s*v\] contains \[(.+?)\]\?", s)
m = re.fullmatch(r"\s*\[(.+?)\]\s+contains\s+\[(.+?)\]\?\s*", s)
if m:
list_name = m.group(1).strip()
item_val = {"kind": "value", "value": m.group(2).strip()} # Item can be a value or a block
# Create the data_list reporter block
list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]}) # Pass parent_key
inputs = {"LIST": {"kind": "block", "block": list_block_id}, "ITEM": item_val}
block_id = _register_block("data_listcontainsitem", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
all_generated_blocks[list_block_id]["parent"] = block_id
return {"kind": "block", "block": block_id}
# 5) Touching object: <touching [edge v]?>
m_touch = re.fullmatch(r"""
\s* # leading space
(?:<\s*)? # optional '<'
touching # literal
\s*\[\s*
(?P<sprite>[^\]]+?) # capture the sprite name
\s*v\]\? # close the [sprite v]?
(?:\s*>)? # optional '>'
""", s, re.IGNORECASE | re.VERBOSE)
if m_touch:
sprite = m_touch.group('sprite').strip()
val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(sprite, sprite)
mid = _register_block(
"sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, # Pass parent_key
fields={"TOUCHINGOBJECTMENU":[val, None]}
)
bid = _register_block(
"sensing_touchingobject", parent_key, False, pick_key_func, all_generated_blocks, # Pass parent_key
inputs={"TOUCHINGOBJECTMENU":[1, mid]}
)
all_generated_blocks[mid]["parent"] = bid
return {"kind":"block","block":bid}
# 6) Touching color: <touching color [#rrggbb]?>
m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s)
if m:
inputs = {"COLOR": [1, [9, m.group(1)]]} # Color input is special, often a list [type, value]
block_id = _register_block("sensing_touchingcolor", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
return {"kind": "block", "block": block_id}
# 7) Color is touching color: <color [#rggbb] is touching [#rrggbb]?>
m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s)
if m:
inputs = {"COLOR1": [1, [9, m.group(1)]], "COLOR2": [1, [9, m.group(2)]]}
block_id = _register_block("sensing_coloristouchingcolor", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
return {"kind": "block", "block": block_id}
# 8) Key pressed: <key [key v] pressed?>
m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", s)
if m:
option = m.group(1).strip()
menu_block_id = _register_block("sensing_keyoptions", parent_key, True, pick_key_func, all_generated_blocks, fields={"KEY_OPTION": [option, None]}) # Pass parent_key
inputs = {"KEY_OPTION": [1, menu_block_id]}
block_id = _register_block("sensing_keypressed", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) # Pass parent_key
all_generated_blocks[menu_block_id]["parent"] = block_id
return {"kind": "block", "block": block_id}
# 9) Mouse down?: mouse down?
if s == "mouse down?":
block_id = _register_block("sensing_mousedown", parent_key, False, pick_key_func, all_generated_blocks) # Pass parent_key
return {"kind": "block", "block": block_id}
val_obj = parse_reporter_or_value(unparen(stmt), parent_key, pick_key_func, all_generated_blocks)
if val_obj:
return val_obj
raise ValueError(f"Can't parse condition: {stmt}")
#################################################################################################################################################################
#--------------------------------------------------[Regular Expression which detect the block type used (single logic on single line)]---------------------------
#################################################################################################################################################################
def classify(line):
"""
Classifies a pseudo-code line into its corresponding Scratch opcode and block type.
Order of checks matters: more specific patterns should come before more general ones.
"""
l = line.lower().strip()
# Ignore comments
if l.startswith("//"): return None, None
# Hat Blocks (most specific first)
if re.match(r"when green flag click(ed)?", l): return "event_whenflagclicked", "hat"
if re.match(r"when (.+?) key press(ed)?", l): return "event_whenkeypressed", "hat"
if re.match(r"when this sprite click(ed)?", l): return "event_whenthisspriteclicked", "hat"
if l.startswith("when backdrop switches to"): return "event_whenbackdropswitchesto", "hat"
if l.startswith("when ") and " > (" in l: return "event_whengreaterthan", "hat"
if l.startswith("when i receive"): return "event_whenbroadcastreceived", "hat"
if re.match(r"when i start as a clo(ne)?", l): return "control_start_as_clone", "hat"
if l.startswith("define "): return "procedures_definition", "hat"
if l.startswith("procedure "): return "procedures_definition", "hat" # For "procedure moveBall"
# Motion Blocks
if l.startswith("go to x:"): return "motion_gotoxy", "stack"
# IMPORTANT: More specific glide block before less specific one
# if l.startswith("glide ") and " secs to x:" in l: return "motion_glidesecstoxy", "stack"
# if l.startswith("glide ") and " secs to " in l: return "motion_glideto", "stack"
if l.startswith("glide ") and (" secs to x:" in l or " seconds to x:" in l): return "motion_glidesecstoxy", "stack"
if l.startswith("glide ") and (" secs to " in l or " seconds to " in l): return "motion_glideto", "stack"
if l.startswith("move "): return "motion_movesteps", "stack"
if l.startswith("turn right "): return "motion_turnright", "stack"
if l.startswith("turn left "): return "motion_turnleft", "stack"
if l.startswith("go to "): return "motion_goto", "stack"
if l.startswith("point in direction"): return "motion_pointindirection", "stack"
if l.startswith("point towards"): return "motion_pointtowards", "stack"
if l.startswith("change x by"): return "motion_changexby", "stack"
if re.match(r"set x to\s*\(.+\)", l): return "motion_setx", "stack" # Specific for set x
if l.startswith("change y by"): return "motion_changeyby", "stack"
if re.match(r"set y to\s*\(.+\)", l): return "motion_sety", "stack" # Specific for set y
#if re.match(r"if on edge, bounce( off edge)?", l): return "motion_ifonedgebounce", "stack"
if re.match(r"if on edge,\s*bounc(e)?(\s+off\s+edge)?", l.strip(), re.IGNORECASE): return "motion_ifonedgebounce", "stack"
if re.match(r"bounce off edg(e)?", l): return "motion_ifonedgebounce", "stack" # Alias
if l.startswith("set rotation style"): return "motion_setrotationstyle", "stack"
# Looks Blocks
if l.startswith("say ") and " for " in l: return "looks_sayforsecs", "stack"
if l.startswith("say "): return "looks_say", "stack"
if l.startswith("think ") and " for " in l: return "looks_thinkforsecs", "stack"
if l.startswith("think "): return "looks_think", "stack"
if l.startswith("switch costume to"): return "looks_switchcostumeto", "stack"
if re.match(r"next costum(e)?", l): return "looks_nextcostume", "stack"
if l.startswith("switch backdrop to ") and " and wait" in l: return "looks_switchbackdroptowait", "stack"
if l.startswith("switch backdrop to"): return "looks_switchbackdropto", "stack"
if l == "next backdrop": return "looks_nextbackdrop", "stack"
if l.startswith("change size by"): return "looks_changesizeby", "stack"
if l.startswith("set size to"): return "looks_setsizeto", "stack"
# Updated regex for change/set effect by/to
if re.match(r"change\s*(\[.+?v\]|\(.+?\))?\s*effect by", l): return "looks_changeeffectby", "stack"
if re.match(r"set\s*(\[.+?v\]|\(.+?\))?\s*effect to", l): return "looks_seteffectto", "stack"
if l == "clear graphic effects": return "looks_cleargraphiceffects", "stack"
if l == "show": return "looks_show", "stack"
if l == "hide": return "looks_hide", "stack"
if l.startswith("go to ") and " layer" in l: return "looks_gotofrontback", "stack"
if l.startswith("go ") and " layers" in l: return "looks_goforwardbackwardlayers", "stack"
# Sound Blocks
if re.match(r"play sound (.+?) until do(ne)?", l): return "sound_playuntildone", "stack"
if l.startswith("start sound "): return "sound_play", "stack"
if l == "stop all sounds": return "sound_stopallsounds", "stack"
if l.startswith("change volume by"): return "sound_changevolumeby", "stack"
if l.startswith("set volume to"): return "sound_setvolumeto", "stack"
# Event Blocks (broadcasts)
if l.startswith("broadcast ") and " and wait" in l: return "event_broadcastandwait", "stack"
if l.startswith("broadcast "): return "event_broadcast", "stack"
# Control Blocks
if re.match(r"wait (.+?) seconds", l): return "control_wait", "stack"
if l.startswith("wait until <"): return "control_wait_until", "stack"
if l.startswith("repeat ("): return "control_repeat", "c_block"
if l == "forever": return "control_forever", "c_block"
if l.startswith("if <") and " then else" in l: return "control_if_else", "c_block"
if l.startswith("if <"): return "control_if", "c_block"
if l.startswith("repeat until <"): return "control_repeat_until", "c_block"
# Updated regex for stop block to handle different options
if re.match(r"stop \[(all|this script|other scripts in sprite)\s*v\]", l): return "control_stop", "cap"
if l.startswith("create clone of"): return "control_create_clone_of", "stack"
if l == "delete this clone": return "control_delete_this_clone", "cap"
# Data Blocks
if l.startswith("set [") and " to " in l: return "data_setvariableto", "stack"
if l.startswith("change [") and " by " in l: return "data_changevariableby", "stack"
if l.startswith("show variable"): return "data_showvariable", "stack"
if l.startswith("hide variable"): return "data_hidevariable", "stack"
if l.startswith("add ") and " to [" in l: return "data_addtolist", "stack"
# Updated regex for delete of list
if re.match(r"delete \((.+?)\) of \[([^\]]+)\s*v\]", l): return "data_deleteoflist", "stack"
if l.startswith("delete all of [" ): return "data_deletealloflist", "stack"
if l.startswith("insert ") and " at " in l: return "data_insertatlist", "stack"
if l.startswith("replace item ") and " of [" in l: return "data_replaceitemoflist", "stack"
if l.startswith("show list"): return "data_showlist", "stack"
if l.startswith("hide list"): return "data_hidelist", "stack"
# Sensing Blocks
if re.match(r"ask (.+?) and wai(t)?", l): return "sensing_askandwait", "stack"
if l == "reset timer": return "sensing_resettimer", "stack"
if l.startswith("set drag mode"): return "sensing_setdragmode", "stack"
# Custom Blocks (procedures_call) - specific rule for "call"
if l.startswith("call "):
return "procedures_call", "stack"
# Custom Blocks (procedures_call) - LAST RESORT (generic match)
# This should be the very last check for stack-type blocks to avoid conflicts.
# It tries to match anything that looks like a function call with or without arguments.
custom_block_match = re.match(r"([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))*\s*$", l)
if custom_block_match:
# Before returning, ensure it's not a known simple reporter or variable name
# that might have been missed or is being used standalone.
# This is a heuristic; a full parser would be more robust.
potential_name = custom_block_match.group(1).strip()
if potential_name not in ["x position", "y position", "direction", "mouse x", "mouse y", "loudness", "timer", "days since 2000", "username", "answer", "size", "volume"] and \
not re.fullmatch(r"\[[^\]]+\]", potential_name) and \
not re.fullmatch(r"\[[^\]]+\]\s*v", potential_name):
return "procedures_call", "stack"
raise ValueError(f"Unknown statement: {line!r}")
#################################################################################################################################################################
#--------------------------------------------------[create the updated skelton json with the Nesting and Staking logic]------------------------------------------
#################################################################################################################################################################
def generate_plan(generated_input, opcode_keys, pseudo_code):
"""
Build a nested “plan” tree from:
• generated_input: dict of block_key -> block_data (pre-generated block definitions)
• opcode_keys: dict of opcode -> list of block_keys (in order)
• pseudo_code: a multiline string, indented with two‑space levels
Returns:
{ "flow": [ ... list of block dictionaries ... ] }
"""
# helper: pick next unused block_key for an opcode
ptrs = defaultdict(int)
def pick_key(opcode):
lst = opcode_keys.get(opcode, [])
idx = ptrs[opcode]
if idx >= len(lst):
# Fallback: if no more pre-generated keys, create a new one.
ptrs[opcode] += 1
return f"{opcode}_{idx + 1}"
ptrs[opcode] += 1
return lst[idx]
all_generated_blocks = {} # Initialize as empty, blocks will be added as they are parsed
# Stack stores (indent, owner_block_id, last_block_in_current_linear_chain_id)
# owner_block_id: The ID of the C-block or Hat block that owns the current substack.
# last_block_in_current_linear_chain_id: The ID of the last block added to the *current linear sequence* within this substack.
stack = [(-1, None, None)] # Sentinel: (indent, owner_block_id, last_block_in_current_linear_chain_id)
top_level_script_keys = []
lines = pseudo_code.splitlines()
i = 0
while i < len(lines):
raw_line = lines[i]
stripped_line = raw_line.strip()
# Skip empty lines and comments
if not stripped_line or stripped_line.startswith("//"):
i += 1
continue
current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2
# Handle 'else' and 'end' first, as they control scope
if stripped_line.lower() == "else":
# Pop the 'then' substack's scope
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
if popped_last_block_in_chain:
all_generated_blocks[popped_last_block_in_chain]["next"] = None
# The 'if-else' block (popped_owner_key) is the owner.
# Push a new scope for the 'else' substack, with the same owner.
stack.append((current_indent, popped_owner_key, None)) # New scope for 'else' part, no last block yet
i += 1
continue
if stripped_line.lower() == "end":
# Pop the current substack's scope
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
if popped_last_block_in_chain:
all_generated_blocks[popped_last_block_in_chain]["next"] = None
# If the substack was empty, ensure the input is [2, None].
if popped_owner_key:
owner_block = all_generated_blocks[popped_owner_key]
if owner_block["block_shape"] == "C-Block" or owner_block["op_code"] == "procedures_definition":
if owner_block["op_code"] == "control_if_else" and \
"SUBSTACK" in owner_block["inputs"] and \
owner_block["inputs"]["SUBSTACK"][1] is not None and \
"SUBSTACK2" not in owner_block["inputs"]:
# If SUBSTACK is already set, this 'end' closes the SUBSTACK2 (else part)
if not owner_block["inputs"].get("SUBSTACK2"): # Only set if not already set by a block
owner_block["inputs"]["SUBSTACK2"] = [2, None]
elif not owner_block["inputs"].get("SUBSTACK"): # Only set if not already set by a block
owner_block["inputs"]["SUBSTACK"] = [2, None]
i += 1
continue
# Adjust stack based on indentation for regular blocks
# Pop scopes whose indentation is greater than or equal to the current line's indentation
while len(stack) > 1 and stack[-1][0] >= current_indent:
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
if popped_last_block_in_chain:
all_generated_blocks[popped_last_block_in_chain]["next"] = None # Terminate the chain
# Get the current active scope from the stack
current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
# Classify the statement and create the block
stmt_for_parse = stripped_line.rstrip("then").strip()
opcode, ntype = classify(stmt_for_parse)
if opcode is None: # Should not happen if classify is robust
i += 1
continue
# Create the new block (and register it in all_generated_blocks)
# _register_block now only sets parent for shadow/input blocks; main block parent/next/topLevel set here.
key = _register_block(opcode, None, False, pick_key, all_generated_blocks)
info = all_generated_blocks[key]
# Set parent, next, and topLevel for the main script blocks
if ntype == "hat":
info["parent"] = None
info["topLevel"] = True
top_level_script_keys.append(key)
# info["next"] = None # REMOVED: This was causing the hat block's 'next' to be None
# Push a new scope for the children of this hat block.
stack.append((current_indent, key, None)) # New scope: owner is this hat, no last block yet
else: # Stack block or C-block (that is part of a linear sequence)
if last_block_in_current_chain:
# This block's parent is the previous block in the chain
info["parent"] = last_block_in_current_chain
all_generated_blocks[last_block_in_current_chain]["next"] = key
else:
# This is the first block in a new linear chain (e.g., first block inside a forever loop)
# Its parent is the owner of the current scope (the C-block or Hat block)
info["parent"] = current_owner_block_id
# If the owner is a C-block or procedure definition, link its SUBSTACK input
if current_owner_block_id and (all_generated_blocks[current_owner_block_id]["block_shape"] == "C-Block" or all_generated_blocks[current_owner_block_id]["op_code"] == "procedures_definition"):
owner_block = all_generated_blocks[current_owner_block_id]
if owner_block["op_code"] == "control_if_else" and \
"SUBSTACK" in owner_block["inputs"] and \
owner_block["inputs"]["SUBSTACK"][1] is not None and \
"SUBSTACK2" not in owner_block["inputs"]:
owner_block["inputs"]["SUBSTACK2"] = [2, key]
else:
owner_block["inputs"]["SUBSTACK"] = [2, key]
elif current_owner_block_id and all_generated_blocks[current_owner_block_id]["block_shape"] == "Hat Block":
# If the owner is a Hat block, this is its first child
all_generated_blocks[current_owner_block_id]["next"] = key
info["topLevel"] = False
info["next"] = None # Default, will be overwritten if there's a next block
# If it's a C-block or define block, it also starts a new inner scope
if ntype == "c_block" or opcode == "procedures_definition":
# Update the current scope's last_block_in_current_chain to this C-block
stack[-1] = (current_scope_indent, current_owner_block_id, key)
# Push a new scope for the C-block's substack
stack.append((current_indent, key, None)) # New scope: owner is this C-block, no last block yet
else:
# For regular stack blocks, just update the last_block_in_current_chain for the current scope
stack[-1] = (current_scope_indent, current_owner_block_id, key)
# Parse inputs and fields (this part remains largely the same, but ensure parse_reporter_or_value/parse_condition
# are passed the *newly created block's ID* as the parent_key for nested inputs)
# Numeric inputs (e.g., move (10) steps, wait (1) seconds)
if opcode == "motion_movesteps":
m = re.search(r"move\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*steps", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["STEPS"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "motion_turnright" or opcode == "motion_turnleft":
m = re.search(r"turn\s*(?:right|left)?\s*\(.*?\)\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*degrees", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["DEGREES"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "motion_gotoxy":
m_x = re.search(r"x:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
m_y = re.search(r"y:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m_x: info["inputs"]["X"] = {"kind": "value", "value": float(m_x.group(1)) if '.' in m_x.group(1) else int(m_x.group(1))}
if m_y: info["inputs"]["Y"] = {"kind": "value", "value": float(m_y.group(1)) if '.' in m_y.group(1) else int(m_y.group(1))}
elif opcode == "motion_glidesecstoxy":
#m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", stmt_for_parse, re.IGNORECASE)
m_secs = re.search(r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*(?:sec(?:s|onds)?)\b ", stmt_for_parse, re.IGNORECASE)
m_x = re.search(r"x:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
m_y = re.search(r"y:\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m_secs: info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))}
if m_x: info["inputs"]["X"] = {"kind": "value", "value": float(m_x.group(1)) if '.' in m_x.group(1) else int(m_x.group(1))}
if m_y: info["inputs"]["Y"] = {"kind": "value", "value": float(m_y.group(1)) if '.' in m_y.group(1) else int(m_y.group(1))}
elif opcode == "motion_pointindirection":
m = re.search(r"direction\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["DIRECTION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode in ["motion_changexby", "motion_changeyby"]:
m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["DX" if opcode == "motion_changexby" else "DY"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode in ["motion_setx", "motion_sety"]:
m = re.search(r"(?:set x to|set y to)\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["X" if opcode == "motion_setx" else "Y"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "looks_changesizeby":
m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["CHANGE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "looks_setsizeto":
m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*%", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["SIZE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode in ["looks_changeeffectby", "sound_changeeffectby"]:
m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["CHANGE" if opcode == "looks_changeeffectby" else "VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode in ["looks_seteffectto", "sound_setvolumeto"]:
m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)(?:\s*%)?", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["VALUE" if opcode == "looks_seteffectto" else "VOLUME"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "looks_goforwardbackwardlayers":
m = re.search(r"go\s*(?:forward|backward)\s*\(\s*(-?\d+)\s*\)\s*layers", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["NUM"] = {"kind": "value", "value": int(m.group(1))}
elif opcode == "control_wait":
m = re.search(r"wait\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["DURATION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "control_repeat":
m = re.search(r"repeat\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["TIMES"] = {"kind": "value", "value": int(m.group(1))}
elif opcode == "data_changevariableby":
m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
elif opcode == "data_deleteoflist":
m = re.search(r"delete\s*\((.+?)\)\s*of", stmt_for_parse, re.IGNORECASE)
if m:
val_str = m.group(1).strip()
if val_str.isdigit():
info["inputs"]["INDEX"] = {"kind": "value", "value": int(val_str)}
else:
info["inputs"]["INDEX"] = {"kind": "menu", "option": val_str}
elif opcode == "data_insertatlist":
m_item = re.search(r"insert\s*\[([^\]]+)\]\s*at", stmt_for_parse, re.IGNORECASE)
m_index = re.search(r"at\s*\(\s*(-?\d+)\s*\)\s*of", stmt_for_parse, re.IGNORECASE)
if m_item: info["inputs"]["ITEM"] = {"kind": "value", "value": m_item.group(1).strip()}
if m_index: info["inputs"]["INDEX"] = {"kind": "value", "value": int(m_index.group(1))}
elif opcode == "data_replaceitemoflist":
m_index = re.search(r"replace item\s*\(\s*(-?\d+)\s*\)\s*of", stmt_for_parse, re.IGNORECASE)
m_item = re.search(r"with\s*\[([^\]]+)\]", stmt_for_parse, re.IGNORECASE)
if m_index: info["inputs"]["INDEX"] = {"kind": "value", "value": int(m_index.group(1))}
if m_item: info["inputs"]["ITEM"] = {"kind": "value", "value": m_item.group(1).strip()}
elif opcode == "event_whengreaterthan":
m = re.search(r">\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
# String inputs
elif opcode == "looks_sayforsecs":
m = re.search(r"say\s*\[([^\]]+)\]\s*for\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE)
if m:
info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()}
info["inputs"]["SECS"] = {"kind": "value", "value": float(m.group(2)) if '.' in m.group(2) else int(m.group(2))}
elif opcode == "looks_say":
m = re.search(r"say\s*(.+)", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["MESSAGE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks)
elif opcode == "looks_thinkforsecs":
m = re.search(r"think\s*\[([^\]]+)\]\s*for\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE)
if m:
info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()}
info["inputs"]["SECS"] = {"kind": "value", "value": float(m.group(2)) if '.' in m.group(2) else int(m.group(2))}
elif opcode == "looks_think":
m = re.search(r"think\s*\[([^\]]+)\]", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["MESSAGE"] = {"kind": "value", "value": m.group(1).strip()}
elif opcode == "sensing_askandwait":
m = re.search(r"ask\s*\[([^\]]+)\]\s*and wait", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["QUESTION"] = {"kind": "value", "value": m.group(1).strip()}
elif opcode == "data_addtolist":
m = re.search(r"add\s*\[([^\]]+)\]\s*to", stmt_for_parse, re.IGNORECASE)
if m: info["inputs"]["ITEM"] = {"kind": "value", "value": m.group(1).strip()}
elif opcode == "data_setvariableto":
m_var = re.search(r"set\s*\[([^\]]+)\s*v\]\s*to\s*(.+)", stmt_for_parse, re.IGNORECASE)
if m_var:
var_name = m_var.group(1).strip()
value_str = m_var.group(2).strip()
info["fields"]["VARIABLE"] = [var_name, None]
info["inputs"]["VALUE"] = parse_reporter_or_value(value_str, key, pick_key, all_generated_blocks)
# Dropdown/Menu inputs (UPDATED)
elif opcode == "motion_goto":
m = re.search(r"go to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
if option == "random position": option_val = "_random_"
elif option == "mouse-pointer": option_val = "_mouse_"
else: option_val = option
menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]})
info["inputs"]["TO"] = [1, menu_block_id]
elif opcode == "motion_glideto":
#m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
#m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs to\s*(?:\[([^\]]+)\s*v\]|\((.+?)\))", stmt_for_parse, re.IGNORECASE)
m_secs = re.search(r"glide\s*\(\s*(-?\d+(?:\.\d\s*\)\s*(?:secs|seconds)\sto\s*(?:\[([^\]]+)\s*v\]|\((.+?)\))", stmt_for_parse, re.IGNORECASE)
if m_secs:
info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))}
# Use group 3 for [random position v] or group 4 for (random position)
option = (m_secs.group(3) or m_secs.group(4)).strip()
if option == "random position": option_val = "_random_"
elif option == "mouse-pointer": option_val = "_mouse_"
else: option_val = option
menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]})
info["inputs"]["TO"] = [1, menu_block_id]
elif opcode == "motion_pointtowards":
m = re.search(r"point towards\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
if option == "mouse-pointer": option_val = "_mouse_"
else: option_val = option
menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks, fields={"TOWARDS": [option_val, None]})
info["inputs"]["TOWARDS"] = [1, menu_block_id]
elif opcode == "sensing_keypressed":
m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks, fields={"KEY_OPTION": [option, None]})
info["inputs"]["KEY_OPTION"] = [1, menu_block_id]
elif opcode == "sensing_touchingobject":
m = re.search(r"touching \[([^\]]+)\s*v\]\?", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
if option == "mouse-pointer": option_val = "_mouse_"
elif option == "edge": option_val = "_edge_"
else: option_val = option
menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks, fields={"TOUCHINGOBJECTMENU": [option_val, None]})
info["inputs"]["TOUCHINGOBJECTMENU"] = [1, menu_block_id]
elif opcode == "control_create_clone_of":
m = re.search(r"create clone of\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
if option == "myself": option_val = "_myself_"
else: option_val = option
menu_block_id = _register_block("control_create_clone_of_menu", key, True, pick_key, all_generated_blocks, fields={"CLONE_OPTION": [option_val, None]})
info["inputs"]["CLONE_OPTION"] = [1, menu_block_id]
elif opcode in ["sound_playuntildone", "sound_play"]:
m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]})
info["inputs"]["SOUND_MENU"] = [1, menu_block_id]
elif opcode == "looks_switchcostumeto":
m = re.search(r"switch costume to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks, fields={"COSTUME": [option, None]})
info["inputs"]["COSTUME"] = [1, menu_block_id]
elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]:
m = re.search(r"switch backdrop to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks, fields={"BACKDROP": [option, None]})
info["inputs"]["BACKDROP"] = [1, menu_block_id]
elif opcode in ["event_broadcast", "event_broadcastandwait"]:
m = re.search(r"broadcast\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m:
option = m.group(1).strip()
# Broadcast input doesn't use a separate menu block in definitions, it's a direct menu field in the input.
# So, it should be [1, [11, "message1", "id"]] or [1, [12, "message1"]]
# For now, let's keep it simple as [1, [11, option, None]] or similar if the definition allows.
# The `all_block_definitions` has `[1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]`
# Let's use that format, but without the specific ID for now.
info["inputs"]["BROADCAST_INPUT"] = [1, [11, option, None]] # Or just [1, [12, option]] if it's a simple string
# Conditional inputs (Boolean blocks)
elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]:
#cond_match = re.search(r"<(.*?)>", stmt_for_parse)
cond_match = extract_condition_balanced(stmt_for_parse)
print(f"[THE CONDA MATCH]------------->{cond_match}")
if cond_match:
# Pass current block's key as parent for nested condition
info["inputs"]["CONDITION"] = parse_condition(cond_match.strip(), key, pick_key, all_generated_blocks)
elif opcode in ["operator_and", "operator_or", "operator_not", "operator_contains",
"sensing_touchingcolor", "sensing_coloristouchingcolor", "sensing_mousedown"]:
pass
# Fields parsing
if "VARIABLE" in info["fields"]:
m = re.search(r"\[([^\]]+)\s*v\]", stmt_for_parse)
if m:
var_name = m.group(1).strip()
info["fields"]["VARIABLE"] = [var_name, None]
if "LIST" in info["fields"]:
m = re.search(r"(?:to|of|in)\s*\[([^\]]+)\s*v\]", stmt_for_parse)
if m: info["fields"]["LIST"] = [m.group(1), None]
if "STOP_OPTION" in info["fields"]:
m = re.search(r"stop \[([^\]]+)\s*v\]", stmt_for_parse)
if m: info["fields"]["STOP_OPTION"] = [m.group(1), None]
if "STYLE" in info["fields"]:
m = re.search(r"set rotation style \[([^\]]+)\s*v\]", stmt_for_parse)
if m: info["fields"]["STYLE"] = [m.group(1), None]
if "DRAG_MODE" in info["fields"]:
m = re.search(r"set drag mode \[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["DRAG_MODE"] = [m.group(1), None]
if "EFFECT" in info["fields"] and opcode in ["looks_changeeffectby", "looks_seteffectto", "sound_changeeffectby", "sound_seteffectto"]:
m = re.search(r"(?:change|set)\s*\[([^\]]+)\s*v\] effect", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["EFFECT"] = [m.group(1).upper(), None]
if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]:
m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["NUMBER_NAME"] = [m.group(1), None]
if "FRONT_BACK" in info["fields"] and opcode == "looks_gotofrontback":
m = re.search(r"go to\s*\[([^\]]+)\s*v\] layer", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["FRONT_BACK"] = [m.group(1), None]
if "FORWARD_BACKWARD" in info["fields"] and opcode == "looks_goforwardbackwardlayers":
m = re.search(r"go\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["FORWARD_BACKWARD"] = [m.group(1), None]
if "OPERATOR" in info["fields"] and opcode == "operator_mathop":
m = re.search(r"\[([^\]]+)\s*v\] of", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["OPERATOR"] = [m.group(1).upper(), None]
if "CURRENTMENU" in info["fields"] and opcode == "sensing_current":
m = re.search(r"current\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["CURRENTMENU"] = [m.group(1).upper(), None]
if "PROPERTY" in info["fields"] and opcode == "sensing_of":
m = re.search(r"\((.+?)\) of", stmt_for_parse, re.IGNORECASE)
if m:
prop = m.group(1).strip()
prop_map = {
"x position": "x position", "y position": "y position", "direction": "direction",
"costume #": "costume number", "costume name": "costume name", "size": "size",
"volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name"
}
info["fields"]["PROPERTY"] = [prop_map.get(prop, prop), None]
if "WHENGREATERTHANMENU" in info["fields"] and opcode == "event_whengreaterthan":
m = re.search(r"when\s*\[([^\]]+)\s*v\] >", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["WHENGREATERTHANMENU"] = [m.group(1).upper(), None]
if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": # For event_whenkeypressed hat block's field
m = re.search(r"when\s*\[([^\]]+)\s*v\] key pressed", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["KEY_OPTION"] = [m.group(1), None]
if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": # For event_whenbackdropswitchesto hat block's field
m = re.search(r"when backdrop switches to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["BACKDROP"] = [m.group(1), None]
if "BROADCAST_OPTION" in info["fields"] and opcode == "event_whenbroadcastreceived": # For event_whenbroadcastreceived hat block's field
m = re.search(r"when i receive\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
if m: info["fields"]["BROADCAST_OPTION"] = [m.group(1), None]
# Custom block specific parsing
if opcode == "procedures_definition":
proc_def_match = re.match(r"(?:define|procedure)\s+([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))?", stmt_for_parse, re.IGNORECASE)
if proc_def_match:
proc_name = proc_def_match.group(1).strip()
args_str = proc_def_match.group(2)
info["procedure_name"] = proc_name
info["is_custom_definition"] = True
mutation_block = {
"tagName": "mutation",
"children": [],
"proccode": proc_name,
"argumentids": [],
"argumentnames": [],
"argumentdefaults": [],
"warp": False # Assuming non-warp by default
}
if args_str:
args = [arg.strip() for arg in args_str.split(',')]
for arg in args:
arg_id = f"%s" # Scratch uses %s for string args, %n for number args
# For simplicity, we'll just use a generic ID for now, or match Scratch's pattern
# For the plan, we just need the names and order.
mutation_block["argumentids"].append(arg_id)
mutation_block["argumentnames"].append(arg)
mutation_block["argumentdefaults"].append("")
info["mutation"] = mutation_block
elif opcode == "procedures_call":
call_match = re.match(r"(?:call\s+)?([a-zA-Z_][a-zA-Z0-9_ ]*)(?:\s*\((.+?)\))*\s*$", stmt_for_parse, re.IGNORECASE)
if call_match:
custom_block_name = call_match.group(1).strip()
args_str = call_match.group(2)
info["custom_block_name"] = custom_block_name
info["mutation"] = {
"tagName": "mutation",
"children": [],
"proccode": custom_block_name,
"argumentids": [],
"argumentnames": [],
"warp": False
}
if args_str:
args = [arg.strip() for arg in args_str.split(',')]
for idx, arg_val_str in enumerate(args):
arg_input_name = f"argument_name_{idx+1}"
info["mutation"]["argumentids"].append(arg_input_name) # Use the input name as argument ID
info["mutation"]["argumentnames"].append(f"arg{idx+1}") # Placeholder name for mutation
info["inputs"][arg_input_name] = parse_reporter_or_value(arg_val_str, key, pick_key, all_generated_blocks) # Pass current block's key
i += 1 # Move to the next line
# Final pass to ensure last blocks have next: None (already handled by stack pops)
# The build_script_flow function will correctly traverse the linked list.
while len(stack) > 1: # Keep the initial sentinel
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
if popped_last_block_in_chain:
all_generated_blocks[popped_last_block_in_chain]["next"] = None
#print(f"[ALL OPCODE BLCOKS KEY 2]: {all_generated_blocks}")
# with open("all_generated_blocks.json", "w") as f:
# json.dump(all_generated_blocks, f, indent=2)
# Construct the final flow output based on the collected top-level keys
# Recursively build the block structure for each top-level script
def build_script_flow(current_block_key, visited=None):
if visited is None:
visited = set()
script_flow = []
current_iter_key = current_block_key
while current_iter_key:
# Detect cyclic reference
if current_iter_key in visited:
script_flow.append({
"block_key": current_iter_key,
"opcode": "control_stop",
"type": "statement",
"inputs": {},
"fields": {},
"comment": "Cycle detected, stopping recursion"
})
break
visited.add(current_iter_key)
block = all_generated_blocks.get(current_iter_key)
if not block:
break # Should not happen if keys are correct
output_block = {
"block_key": block["id"],
"opcode": block["op_code"],
"type": block["block_shape"].replace(" Block", "").lower().replace("c-", "c_"),
"inputs": {},
"fields": {},
"shadow": block.get("shadow"),
"topLevel": block.get("topLevel"),
"parent": block.get("parent"),
"next": block.get("next")
}
# Handle all input types
for inp_name, inp_val in block.get("inputs", {}).items():
if inp_name in ["SUBSTACK", "SUBSTACK2"]:
if inp_val and len(inp_val) > 1 and inp_val[1] in all_generated_blocks:
output_block["inputs"][inp_name] = build_script_flow(inp_val[1], visited.copy())
else:
output_block["inputs"][inp_name] = []
elif inp_name == "PROCCONTAINER" and block.get("is_custom_definition"):
output_block["inputs"][inp_name] = inp_val
elif isinstance(inp_val, dict) and inp_val.get("kind") == "block":
# Recursively build nested reporter/boolean blocks
nested_block_key = inp_val["block"]
if nested_block_key in all_generated_blocks:
output_block["inputs"][inp_name] = build_script_flow(nested_block_key, visited.copy())
else:
output_block["inputs"][inp_name] = inp_val # Keep original if not found (shouldn't happen)
elif isinstance(inp_val, dict) and inp_val.get("kind") == "nested_reporter":
nested_block_key = inp_val["reporter"]["block"]
if nested_block_key in all_generated_blocks:
output_block["inputs"][inp_name] = build_script_flow(nested_block_key, visited.copy())
else:
output_block["inputs"][inp_name] = inp_val
else:
output_block["inputs"][inp_name] = inp_val
for field_name, field_val in block.get("fields", {}).items():
output_block["fields"][field_name] = field_val
if block.get("custom_block_name"):
output_block["custom_block_name"] = block["custom_block_name"]
if block.get("procedure_name"):
output_block["procedure_name"] = block["procedure_name"]
output_block["is_custom_definition"] = True
if "mutation" in block: # Include mutation for custom definitions
output_block["mutation"] = block["mutation"]
script_flow.append(output_block)
# Proceed to the next block in sequence
next_key = block.get("next")
if next_key in visited:
script_flow.append({
"block_key": next_key,
"opcode": "control_stop",
"type": "statement",
"inputs": {},
"fields": {},
"comment": "Cycle detected in 'next' pointer"
})
break
current_iter_key = next_key
return script_flow
final_flow_output = []
for key in top_level_script_keys:
final_flow_output.extend(build_script_flow(key))
return {"flow": final_flow_output}
#################################################################################################################################################################
#--------------------------------------------------[Security key id generation for the better understanding of keys]---------------------------------------------
#################################################################################################################################################################
def generate_secure_token(length=20):
charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
return ''.join(secrets.choice(charset) for _ in range(length))
#################################################################################################################################################################
#--------------------------------------------------[Processed the two Skelton as input and generate refined skelton json]----------------------------------------
#################################################################################################################################################################
def process_scratch_blocks(all_generated_blocks, generated_output_json):
processed_blocks = {}
# Initialize dictionaries to store and reuse generated unique IDs
# This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
variable_id_map = defaultdict(lambda: generate_secure_token(20))
broadcast_id_map = defaultdict(lambda: generate_secure_token(20))
for block_id, gen_block_data in generated_output_json.items():
processed_block = {}
all_gen_block_data = all_generated_blocks.get(block_id, {})
# Copy and update fields, inputs, next, parent, shadow, topLevel, mutation, and opcode
processed_block["opcode"] = all_gen_block_data.get("op_code", gen_block_data.get("op_code"))
processed_block["inputs"] = {}
processed_block["fields"] = {}
processed_block["shadow"] = all_gen_block_data.get("shadow", gen_block_data.get("shadow"))
processed_block["topLevel"] = all_gen_block_data.get("topLevel", gen_block_data.get("topLevel"))
processed_block["parent"] = all_gen_block_data.get("parent", gen_block_data.get("parent"))
processed_block["next"] = all_gen_block_data.get("next", gen_block_data.get("next"))
if "mutation" in all_gen_block_data:
processed_block["mutation"] = all_gen_block_data["mutation"]
# Process inputs
if "inputs" in all_gen_block_data:
for input_name, input_data in all_gen_block_data["inputs"].items():
if input_name in ["SUBSTACK", "CONDITION"]:
# These should always be type 2
if isinstance(input_data, list) and len(input_data) == 2:
processed_block["inputs"][input_name] = [2, input_data[1]]
elif isinstance(input_data, dict) and input_data.get("kind") == "block":
processed_block["inputs"][input_name] = [2, input_data.get("block")]
else: # Fallback for unexpected formats, try to use the original if possible
processed_block["inputs"][input_name] = gen_block_data["inputs"].get(input_name, [2, None])
elif isinstance(input_data, dict):
if input_data.get("kind") == "value":
# Case 1: Direct value input
processed_block["inputs"][input_name] = [
1,
[
4,
str(input_data.get("value", ""))
]
]
elif input_data.get("kind") == "block":
# Case 3: Nested block input
existing_shadow_value = ""
if input_name in gen_block_data.get("inputs", {}) and \
isinstance(gen_block_data["inputs"][input_name], list) and \
len(gen_block_data["inputs"][input_name]) > 2 and \
isinstance(gen_block_data["inputs"][input_name][2], list) and \
len(gen_block_data["inputs"][input_name][2]) > 1:
existing_shadow_value = gen_block_data["inputs"][input_name][2][1]
processed_block["inputs"][input_name] = [
3,
input_data.get("block", ""),
[
10, # Assuming 10 for number/string shadow
existing_shadow_value
]
]
elif input_data.get("kind") == "menu":
# Handle menu inputs like in event_broadcast
menu_option = input_data.get("option", "")
# Generate or retrieve a unique ID for the broadcast message
broadcast_id = broadcast_id_map[menu_option] # Use defaultdict for unique IDs
processed_block["inputs"][input_name] = [
1,
[
11, # This is typically the code for menu dropdowns
menu_option,
broadcast_id
]
]
elif isinstance(input_data, list):
# For cases like TOUCHINGOBJECTMENU, where input_data is a list [1, "block_id"]
processed_block["inputs"][input_name] = input_data
# Process fields
if "fields" in all_gen_block_data:
for field_name, field_value in all_gen_block_data["fields"].items():
if field_name == "VARIABLE" and isinstance(field_value, list) and len(field_value) > 0:
# Generate or retrieve a unique ID for the variable
variable_name = field_value[0]
unique_id = variable_id_map[variable_name] # Use defaultdict for unique IDs
processed_block["fields"][field_name] = [
variable_name,
unique_id
]
elif field_name == "STOP_OPTION":
processed_block["fields"][field_name] = [
field_value[0],
None
]
elif field_name == "TOUCHINGOBJECTMENU":
referenced_menu_block_id = all_gen_block_data["inputs"].get("TOUCHINGOBJECTMENU", [None, None])[1]
if referenced_menu_block_id and referenced_menu_block_id in all_generated_blocks:
menu_block = all_generated_blocks[referenced_menu_block_id]
menu_value = menu_block.get("fields", {}).get("TOUCHINGOBJECTMENU", ["", None])[0]
processed_block["fields"][field_name] = [menu_value, None]
else:
processed_block["fields"][field_name] = [field_value[0], None]
else:
processed_block["fields"][field_name] = field_value
# Remove unwanted keys from the processed block
keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
for key in keys_to_remove:
if key in processed_block:
del processed_block[key]
processed_blocks[block_id] = processed_block
return processed_blocks
#################################################################################################################################################################
#--------------------------------------------------[Unique secret key for skelton json to make sure it donot overwrite each other]-------------------------------
#################################################################################################################################################################
def rename_blocks(block_json: dict, opcode_count: dict) -> tuple[dict, dict]:
"""
Replace each block key in block_json and each identifier in opcode_count
with a newly generated secure token.
Args:
block_json: Mapping of block_key -> block_data.
opcode_count: Mapping of opcode -> list of block_keys.
Returns:
A tuple of (new_block_json, new_opcode_count) with updated keys.
"""
# Step 1: Generate a secure token mapping for every existing block key
token_map = {}
for old_key in block_json.keys():
# Ensure uniqueness in the unlikely event of a collision
while True:
new_key = generate_secure_token()
if new_key not in token_map.values():
break
token_map[old_key] = new_key
# Step 2: Rebuild block_json with new keys
new_block_json = {}
for old_key, block in block_json.items():
new_key = token_map[old_key]
new_block_json[new_key] = block.copy()
# Update parent and next references
if 'parent' in block and block['parent'] in token_map:
new_block_json[new_key]['parent'] = token_map[block['parent']]
if 'next' in block and block['next'] in token_map:
new_block_json[new_key]['next'] = token_map[block['next']]
# Update inputs if they reference blocks
for inp_key, inp_val in block.get('inputs', {}).items():
if isinstance(inp_val, list) and len(inp_val) == 2:
idx, ref = inp_val
if idx in (2, 3) and isinstance(ref, str) and ref in token_map:
new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]]
# Step 3: Update opcode count map
new_opcode_count = {}
for opcode, key_list in opcode_count.items():
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
return new_block_json, new_opcode_count
#################################################################################################################################################################
#--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
#################################################################################################################################################################
def variable_intialization(project_data):
"""
Updates variable and broadcast definitions in a Scratch project JSON,
populating the 'variables' and 'broadcasts' sections of the Stage target
and extracting initial values for variables.
Args:
project_data (dict): The loaded JSON data of the Scratch project.
Returns:
dict: The updated project JSON data.
"""
stage_target = None
for target in project_data['targets']:
if target.get('isStage'):
stage_target = target
break
if stage_target is None:
print("Error: Stage target not found in the project data.")
return project_data
# Ensure 'variables' and 'broadcasts' exist in the Stage target
if "variables" not in stage_target:
stage_target["variables"] = {}
if "broadcasts" not in stage_target:
stage_target["broadcasts"] = {}
# Helper function to recursively find and update variable/broadcast fields
def process_dict(obj):
if isinstance(obj, dict):
# Check for "data_setvariableto" opcode to extract initial values
if obj.get("opcode") == "data_setvariableto":
variable_field = obj.get("fields", {}).get("VARIABLE")
value_input = obj.get("inputs", {}).get("VALUE")
if variable_field and isinstance(variable_field, list) and len(variable_field) == 2:
var_name = variable_field[0]
var_id = variable_field[1]
initial_value = ""
if value_input and isinstance(value_input, list) and len(value_input) > 1 and \
isinstance(value_input[1], list) and len(value_input[1]) > 1:
# Extract value from various formats, e.g., [1, [10, "0"]] or [3, [12, "score", "id"], [10, "0"]]
if value_input[1][0] == 10: # Direct value like [10, "0"]
initial_value = str(value_input[1][1])
elif value_input[1][0] == 12 and len(value_input) > 2 and isinstance(value_input[2], list) and value_input[2][0] == 10: # Variable reference with initial value block
initial_value = str(value_input[2][1])
elif isinstance(value_input[1], (str, int, float)): # For direct number/string inputs
initial_value = str(value_input[1])
# Add/update the variable in the Stage's 'variables' with its initial value
stage_target["variables"][var_id] = [var_name, initial_value]
for key, value in obj.items():
# Process variable definitions in 'fields' (for blocks that define variables like 'show variable')
if key == "VARIABLE" and isinstance(value, list) and len(value) == 2:
var_name = value[0]
var_id = value[1]
# Only add if not already defined with an initial value from set_variableto
if var_id not in stage_target["variables"]:
stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
stage_target["variables"][var_id][0] = var_name
# Process broadcast definitions in 'inputs' (BROADCAST_INPUT)
elif key == "BROADCAST_INPUT" and isinstance(value, list) and len(value) == 2 and \
isinstance(value[1], list) and len(value[1]) == 3 and value[1][0] == 11:
broadcast_name = value[1][1]
broadcast_id = value[1][2]
# Add/update the broadcast in the Stage's 'broadcasts'
stage_target["broadcasts"][broadcast_id] = broadcast_name
# Process broadcast definitions in 'fields' (BROADCAST_OPTION)
elif key == "BROADCAST_OPTION" and isinstance(value, list) and len(value) == 2:
broadcast_name = value[0]
broadcast_id = value[1]
# Add/update the broadcast in the Stage's 'broadcasts'
stage_target["broadcasts"][broadcast_id] = broadcast_name
# Recursively call for nested dictionaries or lists
process_dict(value)
elif isinstance(obj, list):
for i, item in enumerate(obj):
# Process variable references in 'inputs' (like [12, "score", "id"])
if isinstance(item, list) and len(item) == 3 and item[0] == 12:
var_name = item[1]
var_id = item[2]
# Only add if not already defined with an initial value from set_variableto
if var_id not in stage_target["variables"]:
stage_target["variables"][var_id] = [var_name, ""] # Default to empty string if no initial value found yet
elif stage_target["variables"][var_id][0] != var_name: # Update name if ID exists but name is different
stage_target["variables"][var_id][0] = var_name
process_dict(item)
# Iterate through all targets to process their blocks
for target in project_data['targets']:
if "blocks" in target:
for block_id, block_data in target["blocks"].items():
process_dict(block_data)
return project_data
def deduplicate_variables(project_data):
"""
Removes duplicate variable entries in the 'variables' dictionary of the Stage target,
prioritizing entries with non-empty values.
Args:
project_data (dict): The loaded JSON data of the Scratch project.
Returns:
dict: The updated project JSON data with deduplicated variables.
"""
stage_target = None
for target in project_data['targets']:
if target.get('isStage'):
stage_target = target
break
if stage_target is None:
print("Error: Stage target not found in the project data.")
return project_data
if "variables" not in stage_target:
return project_data # No variables to deduplicate
# Use a temporary dictionary to store the preferred variable entry by name
# Format: {variable_name: [variable_id, variable_name, variable_value]}
resolved_variables = {}
for var_id, var_info in stage_target["variables"].items():
var_name = var_info[0]
var_value = var_info[1]
if var_name not in resolved_variables:
# If the variable name is not yet seen, add it
resolved_variables[var_name] = [var_id, var_name, var_value]
else:
# If the variable name is already seen, decide which one to keep
existing_id, existing_name, existing_value = resolved_variables[var_name]
# Prioritize the entry with a non-empty value
if var_value != "" and existing_value == "":
resolved_variables[var_name] = [var_id, var_name, var_value]
# If both have non-empty values, or both are empty, keep the current one (arbitrary choice, but consistent)
# The current logic will effectively keep the last one encountered that has a value,
# or the very last one if all are empty.
elif var_value != "" and existing_value != "":
# If there are multiple non-empty values for the same variable name
# this keeps the one from the most recent iteration.
# For the given example, this will correctly keep "5".
resolved_variables[var_name] = [var_id, var_name, var_value]
elif var_value == "" and existing_value == "":
# If both are empty, just keep the current one (arbitrary)
resolved_variables[var_name] = [var_id, var_name, var_value]
# Reconstruct the 'variables' dictionary using the resolved entries
new_variables_dict = {}
for var_name, var_data in resolved_variables.items():
var_id_to_keep = var_data[0]
var_name_to_keep = var_data[1]
var_value_to_keep = var_data[2]
new_variables_dict[var_id_to_keep] = [var_name_to_keep, var_value_to_keep]
stage_target["variables"] = new_variables_dict
return project_data
def variable_adder_main(project_data):
try:
declare_variable_json= variable_intialization(project_data)
except Exception as e:
print(f"Error error in the variable initialization opcodes: {e}")
try:
processed_json= deduplicate_variables(declare_variable_json)
return
except Exception as e:
print(f"Error error in the variable initialization opcodes: {e}")
#################################################################################################################################################################
#--------------------------------------------------[Helper main function]----------------------------------------------------------------------------------------
#################################################################################################################################################################
def block_builder(opcode_count,pseudo_code):
try:
generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_count, all_block_definitions)
except Exception as e:
print(f"Error generating blocks from opcodes: {e}")
return {}
try:
all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
except Exception as e:
print(f"Error generating plan from blocks: {e}")
return {}
try:
processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
except Exception as e:
print(f"Error processing Scratch blocks: {e}")
return {}
renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences)
return renamed_blocks
#################################################################################################################################################################
#--------------------------------------------------[Example use of the function here]----------------------------------------------------------------------------
#################################################################################################################################################################
# initial_opcode_counts = [
# {
# "opcode": "event_whenflagclicked",
# "count": 1
# },
# {
# "opcode": "data_setvariableto",
# "count": 2
# },
# {
# "opcode": "data_showvariable",
# "count": 2
# },
# {
# "opcode": "event_broadcast",
# "count": 1
# }
# ]
# pseudo_code="""
# when green flag clicked
# set [score v] to (0)
# set [lives v] to (3)
# show variable [score v]
# show variable [lives v]
# broadcast [Game Start v]
# """
# generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(initial_opcode_counts, all_block_definitions)
# all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
# processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
# print(all_generated_blocks)
# print("--------------\n\n")
# print(processed_blocks)
# print("--------------\n\n")
# print(initial_opcode_occurrences)