Spaces:
Sleeping
Sleeping
| import json | |
| import copy | |
| import re | |
| from collections import defaultdict, Counter | |
| import secrets | |
| import string | |
| from typing import Dict, Any, TypedDict,Tuple | |
| import difflib | |
| try: | |
| import enchant | |
| d = enchant.Dict("en_US") | |
| except Exception: | |
| d = None | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[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 opcode == "sensing_touchingobject": # 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]) | |
| # Initialize parent, next, topLevel to None/False. These will be set correctly in generate_plan. | |
| main_block_data["id"] = main_key # Ensure ID is present | |
| main_block_data["parent"] = None | |
| main_block_data["next"] = None | |
| main_block_data["topLevel"] = False # Default to False, Hat blocks will override | |
| 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"] = {} | |
| # Removed sub_stacks from here as it's now part of inputs for C-blocks | |
| generated_blocks[main_key] = main_block_data | |
| # Handle menus (shadow blocks) | |
| 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["id"] = menu_key # Ensure ID is present | |
| menu_block_data["shadow"] = True | |
| menu_block_data["topLevel"] = False | |
| menu_block_data["next"] = None # Shadow blocks never have a 'next' | |
| menu_block_data["parent"] = main_key # Parent is the main block it's an input for | |
| # 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"] = {} | |
| # Link the main block's input to the shadow block's key | |
| # This assumes the input structure is [type_code, block_id_or_value] | |
| 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]) > 0: # Check for at least type_code | |
| # Assuming type code 1 for value input that takes a block | |
| main_block_data["inputs"][input_name][1] = menu_key | |
| else: # If input not defined as list, or empty, set it | |
| main_block_data["inputs"][input_name] = [1, menu_key] # Default to value input type | |
| 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 | |
| }, | |
| "sensing_distanceto_menu": { # Added sensing_distanceto_menu (though its use is now commented out in parse_reporter_or_value) | |
| "block_name": "distance to menu", "block_type": "Sensing", "block_shape": "Reporter Block", "op_code": "sensing_distanceto_menu", | |
| "functionality": "Menu for distance to block.", | |
| "inputs": {}, "fields": {"TARGET": ["_mouse_", None]}, "shadow": True, "topLevel": False | |
| }, | |
| # 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 go 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": {"SUBSTACK": [2, None]}, # Custom blocks can have a substack | |
| "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, sub_stacks=None): | |
| """ | |
| Helper to create and register a block in the all_blocks_dict. | |
| It uses pick_key_func to get a unique ID. | |
| Parent, next, and topLevel are NOT set here for main blocks; they are handled in generate_plan. | |
| For shadow blocks, parent is set here. | |
| """ | |
| key = pick_key_func(opcode) | |
| block_data = copy.deepcopy(all_block_definitions[opcode]) | |
| block_data["id"] = key | |
| block_data["parent"] = parent_key if is_shadow else None # Only set parent for shadow blocks here | |
| block_data["next"] = None # Default, will be set in generate_plan | |
| block_data["topLevel"] = not is_shadow # Shadow blocks are not top-level | |
| block_data["shadow"] = is_shadow | |
| # Ensure inputs, fields, and sub_stacks 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 "sub_stacks" not in block_data or not isinstance(block_data["sub_stacks"], dict): | |
| block_data["sub_stacks"] = {} | |
| if inputs: | |
| for inp_name, inp_val in inputs.items(): | |
| # If the input is a block, ensure it's stored as a block ID reference | |
| if isinstance(inp_val, dict) and inp_val.get("kind") == "block": | |
| block_data["inputs"][inp_name] = [2, inp_val["block"]] # Type 2 for block input | |
| elif isinstance(inp_val, dict) and inp_val.get("kind") == "value": | |
| # For literal values, store as type 1 and the value itself | |
| # Need to map to Scratch's internal value types if necessary, e.g., [4, "10"] for number | |
| # For simplicity, we'll use a generic type 1 and the value for now. | |
| # A more robust solution would map `type` from parse_reporter_or_value to Scratch's internal codes. | |
| block_data["inputs"][inp_name] = [1, str(inp_val["value"])] # Store as string for now | |
| else: | |
| block_data["inputs"][inp_name] = inp_val # Fallback for other formats | |
| if fields: | |
| block_data["fields"].update(fields) | |
| if sub_stacks: | |
| block_data["sub_stacks"].update(sub_stacks) | |
| 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): | |
| print (f"text recived at parse_reporter_or_value _auto_balance here:----------------->: {text}") | |
| text = _auto_balance(text.strip()) | |
| #text = unparen(text.strip()) | |
| print (f"text recived at parse_reporter_or_value unparen here:----------------->: {text}") | |
| text = strip_outer_angle_brackets(text) | |
| text = text.strip() | |
| # Check for numeric literal (including parenthesized numbers like "(0)" or "(10)") | |
| print (f"text recived at parse_reporter_or_value here:----------------->: {text}") | |
| m_num = re.fullmatch(r"\(?\s*(-?\d+(\.\d+)?)\s*\)?", text.strip()) | |
| if m_num: | |
| val_str = m_num.group(1) | |
| print(" the full match at the reportor value:",val_str) | |
| return {"kind": "value", "value": float(val_str) if '.' in val_str else int(val_str)} | |
| #################################################################################################################### | |
| #[NOTE: Keeping all this logic below becasue it returns value before the other if else able to process anything here] | |
| #################################################################################################################### | |
| # [ORDER NO: ] | |
| # --- 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) | |
| m_simple_reporter = re.fullmatch(r"""\(?\s*(x\ position|y\ position|direction|mouse\ x|mouse\ y|loudness|timer|days\ since\ 2000|username|answer|size|volume)\s*\)?""",text,re.IGNORECASE | re.VERBOSE) | |
| #m_cos = re.fullmatch(r"""\(\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)""",text, re.IGNORECASE | re.VERBOSE) | |
| if m_simple_reporter:# and not m_cos: | |
| inner_text = m_simple_reporter.group(1).strip() | |
| print("the reportor block values: ",inner_text) | |
| if inner_text in simple_reporters: | |
| block_id = _register_block(simple_reporters[inner_text], parent_key, True, pick_key_func, all_generated_blocks) | |
| print() | |
| return {"kind": "block", "block": block_id} | |
| # Also check for simple reporters without parentheses (e.g., if passed directly) | |
| # [ORDER NO: ] | |
| if text in simple_reporters: | |
| print("the reportor block text: ",text) | |
| block_id = _register_block(simple_reporters[text], parent_key, False, pick_key_func, all_generated_blocks) | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # 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() | |
| print("the reportor block variable: ",var_name) | |
| 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} | |
| # [ORDER NO: ] | |
| m_paren_var = re.fullmatch(r"\(([^)]+)\)", text) | |
| m_cos = re.fullmatch(r"""\(\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)""",text,re.IGNORECASE | re.VERBOSE) | |
| m_op = re.search(r"""(?:\[\s*([^\]]+?)\s*v\s*\]|\(?\s*([a-z0-9^ ]+?)\s*\)?)\s+of\s+(\(|\[)""",text,re.IGNORECASE | re.VERBOSE,) | |
| m_bac = re.fullmatch(r"""\(?\s*backdrop\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?""",text,re.IGNORECASE | re.VERBOSE,) | |
| m_cur = re.search(r"current\s*\[([^\]]+)\s*v\]", text, re.IGNORECASE) | |
| if m_paren_var and not m_cos and not m_op and not m_bac and not m_cur: | |
| potential = m_paren_var.group(1).strip() | |
| # If it's literally “[name v]”, pull out just “name” | |
| m_bracket_var = re.fullmatch(r"\[\s*([^\]]+?)\s*v\s*\]", potential) | |
| if m_bracket_var: potential = m_bracket_var.group(1).strip() | |
| print("the potential_var_name variable:", potential) | |
| # Only create a data_variable if it’s not a reporter or a number or contains arithmetic | |
| if (potential not in simple_reporters and not re.fullmatch(r"-?\d+(\.\d+)?", potential) and not re.search(r"[+\-*/]", potential)): | |
| block_id = _register_block("data_variable",parent_key,True,pick_key_func,all_generated_blocks,fields={"VARIABLE": [potential, None]}) | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # List reporter: [my list v] | |
| m_list_reporter = re.fullmatch(r"\[([^\]]+)\s*v\]", text.strip()) | |
| if m_list_reporter: | |
| list_name = m_list_reporter.group(1).strip() | |
| print("the simple reporters that List", list_name) | |
| 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} | |
| # [ORDER NO: ] | |
| # 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. | |
| normalized = re.sub(r"\s+v$", "", text).strip() | |
| if normalized not in simple_reporters: | |
| print("the simple reporters that number 1", normalized) | |
| block_id = _register_block("data_variable",parent_key,True,pick_key_func,all_generated_blocks,fields={"VARIABLE": [normalized, None]}) | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # Now catch other bracketed values as literal strings | |
| if text.startswith('[') and text.endswith(']'): | |
| print("the string reportor value [str]:",text[1:-1]) | |
| return {"kind": "value", "value": text[1:-1]} | |
| # [ORDER NO: ] | |
| # (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(), parent_key, pick_key_func, all_generated_blocks) | |
| list_name = m.group(2).strip() | |
| print("(item (index) of [list v]) : ",m.group(1).strip()) | |
| print("(item (index) of [list v]) : ",list_name) | |
| # 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": {"kind": "block", "block": list_block_id}} | |
| fields = {} # No fields in data_itemoflist itself for the list name | |
| block_id = _register_block("data_itemoflist", parent_key, True, 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} | |
| # [ORDER NO: ] | |
| # (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(), parent_key, pick_key_func, all_generated_blocks) | |
| list_name = m.group(2).strip() | |
| print("(item # of [item] in [list v]) : ", m.group(1).strip()) # Changed from index_obj to item_obj for print | |
| print("(item # of [item] in [list v]) : ", list_name) | |
| # 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": {"kind": "block", "block": 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} | |
| # [ORDER NO: ] | |
| # (pick random () to ()) (operator_random) | |
| m = re.search(r"pick\s+random\s+(?:\(\s*)?(-?\d+(?:\.\d+)?|.+?)(?:\s*\))?\s+to\s+(?:\(\s*)?(-?\d+(?:\.\d+)?|.+?)(?:\s*\))?",text.strip(),re.IGNORECASE) | |
| if m: | |
| print("the (pick random () to ()):[left] ",m.group(1).strip()) | |
| print("the (pick random () to ()):[rigth] ",m.group(2).strip()) | |
| min_val_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) # Parent will be set later | |
| max_val_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, 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, True, pick_key_func, all_generated_blocks, inputs=inputs) | |
| print("parent key at operator_random",parent_key) | |
| print("parent key at operator_random",block_id) | |
| # 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} | |
| # [ORDER NO: ] | |
| # (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() | |
| print("the (join ()()) (operator_join):[left] ",part1_txt) | |
| print("the (join ()()) (operator_join):[rigth] ",part2_txt) | |
| str1_obj = parse_reporter_or_value(part1_txt, parent_key, pick_key_func, all_generated_blocks) | |
| str2_obj = parse_reporter_or_value(part2_txt, parent_key, pick_key_func, all_generated_blocks) | |
| inputs = {"STRING1": str1_obj, "STRING2": str2_obj} | |
| block_id = _register_block("operator_join", parent_key, True, | |
| 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} | |
| # [ORDER NO: ] | |
| # 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: | |
| print("the letter () of ():[left] ",m.group(1).strip()) | |
| print("the letter () of ():[rigth] ",m.group(2).strip()) | |
| index_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| string_val_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| inputs = {"LETTER": index_obj, "STRING": string_val_obj} | |
| block_id = _register_block("operator_letterof", parent_key, True, 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} | |
| # [ORDER NO: ] | |
| # (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() | |
| print("the (length of ()) :",arg_txt) | |
| list_or_string_val_obj = parse_reporter_or_value(arg_txt, parent_key, pick_key_func, all_generated_blocks) | |
| #list_or_string_val_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| inputs = {"STRING": list_or_string_val_obj} | |
| block_id = _register_block("operator_length", parent_key, True, 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} | |
| # [ORDER NO: ] | |
| # (() 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(), parent_key, pick_key_func, all_generated_blocks) | |
| num2_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| print("the (() mod ()):[left] ",num1_obj) | |
| print("the (() mod ()):[rigth] ",num2_obj) | |
| inputs = {"NUM1": num1_obj, "NUM2": num2_obj} | |
| block_id = _register_block("operator_mod", parent_key, True, 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} | |
| # [ORDER NO: ] | |
| # (round ()) (operator_round) | |
| m = re.search(r"round \((.+?)\)", text) | |
| if m: | |
| num_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| print("the (() mod ()):[left] ",num_obj) | |
| inputs = {"NUM": num_obj} | |
| block_id = _register_block("operator_round", parent_key, True, 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} | |
| # [ORDER NO: ] | |
| # (() of ()) (operator_mathop) - handle variable for function type | |
| def extract_mathop_of_expression(text: str): | |
| m = re.search(r"""(?:\[\s*([^\]]+?)\s*v\s*\]|\(?\s*([a-z0-9^ ]+?)\s*\)?)\s+of\s+(\(|\[)""",text,re.IGNORECASE | re.VERBOSE,) | |
| if not m: return None | |
| func_type = (m.group(1) or m.group(2)).strip() | |
| start_idx = m.end() - 1 | |
| depth = 0 | |
| end_idx = start_idx | |
| for i in range(start_idx, len(text)): | |
| if text[i] in "([": # opening bracket/paren | |
| depth += 1 | |
| elif text[i] in ")]": # closing bracket/paren | |
| depth -= 1 | |
| if depth == 0: | |
| end_idx = i | |
| break | |
| inner_expr = text[start_idx + 1:end_idx].strip() | |
| return func_type, inner_expr | |
| result = extract_mathop_of_expression(text) | |
| if result: | |
| func_type, expr = result | |
| allowed_math_ops = {"abs", "floor", "ceiling", "sqrt","sin", "cos", "tan","asin", "acos", "atan","ln", "log", "e ^", "10 ^"} | |
| cleaned = re.sub(r"[^a-z0-9^ ]", "", func_type.lower()).strip() | |
| normalized_map = {op.lower(): op for op in allowed_math_ops} | |
| func_val = None | |
| if cleaned in normalized_map: | |
| func_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| func_val = normalized_map[match[0]] | |
| if func_val is not None: | |
| print("(() of ()) (operator_mathop):[left] ", func_val) | |
| print("(() of ()) (operator_mathop):[right] ", expr) | |
| value_obj = parse_reporter_or_value(expr, parent_key, pick_key_func, all_generated_blocks) | |
| inputs = {"NUM": value_obj} | |
| fields = {"OPERATOR": [func_val.upper(), None]} | |
| block_id = _register_block("operator_mathop",parent_key,True,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 | |
| print("block_id at operator_mathop:", block_id) | |
| return {"kind": "block", "block": block_id} | |
| else: | |
| print(f"Skipped operator_mathop: {func_type}") | |
| # [ORDER NO: ] | |
| # (costume ()) (looks_costumenumbername) - handle with or without 'v' | |
| m = re.fullmatch(r"\(?\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?",text,re.IGNORECASE | re.VERBOSE) | |
| if m: | |
| option = (m.group(1) or m.group(2)) | |
| if option is None: | |
| raise ValueError(f"Unable to extract costume option from: {text}") | |
| option = option.strip() | |
| valid_options = ["number", "name"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if cleaned in normalized_map: | |
| option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| option_val = normalized_map[match[0]] | |
| else: | |
| option_val = option # fallback | |
| print("(costume ()) : ", option_val) | |
| fields = {"NUMBER_NAME": [option_val, None]} | |
| block_id = _register_block("looks_costumenumbername",parent_key,True,pick_key_func,all_generated_blocks,fields=fields) | |
| if block_id is None: | |
| raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}") | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # (backdrop ()) (looks_backdropnumbername) - handle with or without 'v' | |
| m = re.fullmatch(r"""\(?\s*backdrop\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?""",text,re.IGNORECASE | re.VERBOSE,) | |
| if m: | |
| option = (m.group(1) or m.group(2)) | |
| if option is None: raise ValueError(f"Unable to extract backdrop option from: {text}") | |
| option = option.strip() | |
| valid_options = ["number", "name"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if cleaned in normalized_map: | |
| option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| option_val = normalized_map[match[0]] | |
| else: | |
| option_val = option # fallback | |
| print("(backdrop ()) : ", option_val) | |
| fields = {"NUMBER_NAME": [option_val, None]} | |
| block_id = _register_block( "looks_backdropnumbername", parent_key, True, pick_key_func, all_generated_blocks, fields=fields) | |
| if block_id is None: | |
| raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}") | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # (current ()) (sensing_current) - handle with or without 'v' | |
| m = re.search(r"distance\s*to\s*[\(\[]\s*([^\]\)]+?)\s*v?\s*[\)\]]", text, re.IGNORECASE) | |
| if not m: | |
| m = re.search(r"distance\s*to\s+([A-Za-z0-9 _\-\']+)", text, re.IGNORECASE) | |
| if m: | |
| target = m.group(1).strip().strip('\'"') | |
| print("(distance to ()) : ", target) | |
| target_val = None | |
| valid_properties = ["mouse-pointer", "mouse pointer"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", target.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: | |
| target_val = "_mouse_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| target_val = "_mouse_" | |
| else: | |
| val, key = process_text(target) | |
| if key == "sprite": | |
| target_val = val | |
| else: | |
| target_val = "_mouse_" | |
| # Register block | |
| if target_val == "_mouse_": | |
| print("-----------------6-------------------", target_val) | |
| object_menu_id = _register_block("sensing_distancetomenu", parent_key, False, pick_key_func,all_generated_blocks, fields={"DISTANCETOMENU": [target_val, None]}) | |
| inputs = {"DISTANCETOMENU": {"kind": "block", "block": object_menu_id}} | |
| block_id = _register_block("sensing_distanceto", parent_key, True, pick_key_func,all_generated_blocks, inputs=inputs, fields={}) | |
| all_generated_blocks[object_menu_id]["parent"] = block_id | |
| print("all_generated_blocks[object_menu_id]['parent']", all_generated_blocks[object_menu_id]["parent"]) | |
| else: | |
| fields = {"DISTANCETOMENU": [target_val, None]} | |
| print("-----------------5-------------------", target_val) | |
| block_id = _register_block("sensing_distanceto", parent_key, False, pick_key_func,all_generated_blocks, fields=fields) | |
| return {"kind": "block", "block": block_id} | |
| # [ORDER NO: ] | |
| # (() 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() | |
| # print("(() of ()) (sensing_of) : ",prop_str) | |
| # print("(() of ()) (sensing_of) : ",obj) | |
| # 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": {"kind": "block", "block": object_menu_id}} # Link input to the shadow block 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} | |
| m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text, re.IGNORECASE) | |
| if m: | |
| prop_str = m.group(1).strip() | |
| obj = (m.group(2) or m.group(3)).strip() | |
| print("(() of ()) (sensing_of) : ", prop_str) | |
| print("(() of ()) (sensing_of) : ", obj) | |
| valid_properties = [ "x position", "y position", "direction", "costume #", "costume name", "size", "volume", "backdrop #", "backdrop name" ] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", prop_str.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: | |
| property_value = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| property_value = normalized_map[match[0]] | |
| else: | |
| property_value = prop_str # fallback | |
| if obj.lower() == "stage": | |
| obj_val = "_stage_" | |
| elif obj.lower() == "myself": | |
| obj_val = "_myself_" | |
| else: | |
| obj_val = obj | |
| object_menu_id = _register_block("sensing_of_object_menu",parent_key,True,pick_key_func,all_generated_blocks,fields={"OBJECT": [obj_val, None]}) | |
| inputs = {"OBJECT": {"kind": "block", "block": object_menu_id}} | |
| fields = {"PROPERTY": [property_value, None]} | |
| 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} | |
| # [ORDER NO: ] w | |
| # 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() | |
| print("the variable reportor value[var v]:",var_name) | |
| 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} | |
| def strip_outer_parentheses(s): | |
| s = s.strip() | |
| while s.startswith("(") and s.endswith(")"): | |
| depth = 0 | |
| for i, ch in enumerate(s): | |
| if ch == "(": | |
| depth += 1 | |
| elif ch == ")": | |
| depth -= 1 | |
| if depth == 0 and i < len(s) - 1: | |
| return s # Outer parentheses don't enclose the entire expression | |
| s = s[1:-1].strip() | |
| return s | |
| def find_main_operator(s): | |
| # First pass: look for + and - at depth 0 (lowest precedence) | |
| depth = 0 | |
| ops = [] | |
| for i, ch in enumerate(s): | |
| if ch == "(": | |
| depth += 1 | |
| elif ch == ")": | |
| depth -= 1 | |
| elif depth == 0 and ch in "+-": | |
| ops.append((i, ch)) | |
| if ops: | |
| # Choose the rightmost lowest-precedence operator for left-associativity | |
| return ops[-1] | |
| depth = 0 | |
| for i, ch in enumerate(s): | |
| if ch == "(": | |
| depth += 1 | |
| elif ch == ")": | |
| depth -= 1 | |
| elif depth == 0 and ch in "*/": | |
| ops.append((i, ch)) | |
| if ops: | |
| return ops[-1] | |
| return None, None | |
| def parse_expression(s): | |
| s = strip_outer_parentheses(s) | |
| idx, op = find_main_operator(s) | |
| if idx is None: | |
| return s, None, None | |
| left = s[:idx].strip() | |
| right = s[idx+1:].strip() | |
| return left, op, right | |
| left_txt, op_sym, right_txt = parse_expression(text) | |
| if op_sym is not None: | |
| print("arithemetic ops:[left] ",left_txt) | |
| print("arithemetic ops:[rigth] ",right_txt) | |
| operand1_obj = parse_reporter_or_value(left_txt,parent_key,pick_key_func,all_generated_blocks) | |
| operand2_obj = parse_reporter_or_value(right_txt,parent_key,pick_key_func,all_generated_blocks) | |
| op_map = {"+": "operator_add","-": "operator_subtract","*": "operator_multiply","/": "operator_divide"} | |
| inputs = {"NUM1": operand1_obj, "NUM2": operand2_obj} | |
| print("inputs",inputs) | |
| # register this arithmetic block | |
| block_id = _register_block(op_map[op_sym],parent_key,True,pick_key_func,all_generated_blocks,inputs=inputs) | |
| print("block_id",block_id) | |
| # hook child blocks back to their paren | |
| if operand1_obj.get("kind") == "block": | |
| all_generated_blocks[operand1_obj["block"]]["parent"] = block_id | |
| print("block_id at arithermatic:",block_id) | |
| if operand2_obj.get("kind") == "block": | |
| all_generated_blocks[operand2_obj["block"]]["parent"] = block_id | |
| print("block_id at arithermatic:",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() | |
| print("the processed text on parse_conditon-1-->",s ) | |
| s = extract_condition_balanced(s) | |
| s_lower = s.lower() | |
| print("the processed text on parse_conditon-2-->",s_lower) | |
| # 1) Boolean NOT: `not <...>` | |
| m_not = re.fullmatch(r"\s*(?:<\s*)?not\s+(.+?)(?:\s*>)?\s*", s_lower, re.IGNORECASE) | |
| if m_not: | |
| inner = m_not.group(1).strip() | |
| print("Boolean NOT--->",m_not.group(1).strip()) | |
| inner_obj = parse_condition(inner, parent_key, pick_key_func, all_generated_blocks) | |
| bid = _register_block("operator_not", parent_key, True, pick_key_func, all_generated_blocks, | |
| 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_lower, re.IGNORECASE) | |
| if m_andor: | |
| cond1_obj = parse_condition(m_andor.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| cond2_obj = parse_condition(m_andor.group(3).strip(), parent_key, pick_key_func, all_generated_blocks) | |
| print("Boolean AND / OR : ",cond1_obj) | |
| print("(Boolean AND / OR : ",cond2_obj) | |
| op_block = 'operator_and' if m_andor.group(2).lower() == 'and' else 'operator_or' | |
| inputs = {"OPERAND1": cond1_obj, "OPERAND2": cond2_obj} | |
| block_id = _register_block(op_block, parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) | |
| 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} | |
| m_comp = re.fullmatch(r"\s*(?:<\s*)?(?P<left>.+?)\s*(?P<op><|=|>)\s*(?P<right>.+?)(?:\s*>)?\s*",s_lower,re.VERBOSE) | |
| if m_comp: | |
| left_txt = m_comp.group('left') | |
| right_txt = m_comp.group('right') | |
| op_sym = m_comp.group('op') | |
| print("left_txt--->",left_txt) | |
| print("op_sym--->",op_sym) | |
| print("right_txt--->",right_txt) | |
| operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks) | |
| operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks) | |
| op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'} | |
| inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj} | |
| block_id = _register_block(op_map[op_sym],parent_key,True,pick_key_func,all_generated_blocks,inputs=inputs) | |
| # link nested blocks back to this parent | |
| for obj in (operand1_obj, operand2_obj): | |
| if obj.get("kind") == "block": | |
| all_generated_blocks[obj["block"]]["parent"] = block_id | |
| print("block_id at Comparisons:",block_id) | |
| return {"kind": "block", "block": block_id} | |
| # 4) Contains: <[list v] contains [item]?> | |
| m = re.fullmatch(r"\s*\[(.+?)\]\s+contains\s+\[(.+?)\]\?\s*", s_lower) | |
| 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 | |
| print("<[list v] contains [item]?> : ",list_name) | |
| print("(<[list v] contains [item]?> : ",item_val) | |
| # 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]}) | |
| 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) | |
| 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*<?\s*touching\s*\[\s*(?P<sprite>[^\]]+?)\s*(?:v)?\s*\]\s*(?:\?)?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE) | |
| # if m_touch: | |
| # sprite = m_touch.group('sprite').strip() | |
| # val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(sprite, sprite) | |
| # print("<touching [edge v]?> : ",sprite) | |
| # mid = _register_block( | |
| # "sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, | |
| # fields={"TOUCHINGOBJECTMENU":[val, None]} | |
| # ) | |
| # bid = _register_block( | |
| # "sensing_touchingobject", parent_key, True, pick_key_func, all_generated_blocks, | |
| # inputs={"TOUCHINGOBJECTMENU":{"kind": "block", "block": mid}} # Link input to the shadow block ID | |
| # ) | |
| # all_generated_blocks[mid]["parent"] = bid | |
| # return {"kind":"block","block":bid} | |
| #m_touch = re.fullmatch(r"""\s*<?\s*touching\s*[\[\(]?\s*(?P<sprite>[^\]\)]+?)\s*(?:v)?\s*[\]\)]?\s*(?:\?)?\s*>?\s*""",s_lower, re.IGNORECASE | re.VERBOSE) | |
| m_touch = re.fullmatch(r"""\s*<?\s*touching(?!\s+color)\s*[\[\(]?\s*(?P<sprite>[^\]\)]+?)\s*(?:v)?\s*[\]\)]?\s*(?:\?)?\s*>?\s*""",s_lower,re.IGNORECASE | re.VERBOSE) | |
| if m_touch: | |
| sprite = m_touch.group('sprite').strip().strip('"\'') | |
| print("<touching [edge v]?> : ", sprite) | |
| target_val = None | |
| valid_properties = ["mouse-pointer", "mouse pointer", "edge"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", sprite.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: | |
| if "edge" in normalized_map[cleaned]: target_val = "_edge_" | |
| else: target_val = "_mouse_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| if "edge" in normalized_map[match[0]]: target_val = "_edge_" | |
| else: target_val = "_mouse_" | |
| else: | |
| val, key = process_text(sprite) | |
| if key == "sprite": target_val = val | |
| else: return None | |
| mid = _register_block("sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks,fields={"TOUCHINGOBJECTMENU": [target_val, None]}) | |
| bid = _register_block("sensing_touchingobject", parent_key, True, pick_key_func, all_generated_blocks,inputs={"TOUCHINGOBJECTMENU": {"kind": "block", "block": mid}}) | |
| all_generated_blocks[mid]["parent"] = bid | |
| return {"kind": "block", "block": bid} | |
| # 6) Touching color: <touching color [#rrggbb]?> | |
| COLOR_MAP = {"red": "#FF0000","yellow": "#FFFF00","pink": "#FFC0CB","blue": "#0000FF","green": "#008000", | |
| "black": "#000000","white": "#FFFFFF","orange": "#FFA500","purple": "#800080","gray": "#808080",} | |
| # m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s_lower) | |
| # m = re.search(r"touching color [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower) | |
| # if m: | |
| # color_str = m.group(1).strip() | |
| # # Remove trailing " v" if present (Scratch dropdown) | |
| # if color_str.endswith(" v"): | |
| # color_str = color_str[:-2].strip() | |
| # # Normalize to hex | |
| # if not color_str.startswith("#"): | |
| # color_str = COLOR_MAP.get(color_str.lower(), None) | |
| # if color_str is None: | |
| # raise ValueError(f"❌ Unknown color name: {m.group(1)}") | |
| # inputs = {"COLOR": {"kind": "value", "value": color_str}} | |
| # print("<touching color [#rrggbb]?> : ", inputs) | |
| # block_id = _register_block( | |
| # "sensing_touchingcolor", parent_key, True, | |
| # pick_key_func, all_generated_blocks, inputs=inputs | |
| # ) | |
| # return {"kind": "block", "block": block_id} | |
| m = re.search(r"touching\s+color\s+[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??",s_lower,re.IGNORECASE,) | |
| if m: | |
| color_str = m.group(1).strip() | |
| if color_str.endswith(" v"): color_str = color_str[:-2].strip() | |
| if not color_str.startswith("#"): | |
| color_name = color_str.lower() | |
| if color_name in COLOR_MAP: | |
| color_str = COLOR_MAP[color_name] | |
| else: | |
| match = difflib.get_close_matches(color_name, COLOR_MAP.keys(), n=1, cutoff=0.6) | |
| if match: | |
| color_str = COLOR_MAP[match[0]] | |
| else: | |
| print(f"⚠️ Unknown color name: {color_str} → skipping block") | |
| return None | |
| inputs = {"COLOR": {"kind": "value", "value": color_str}} | |
| block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks,inputs = {"COLOR": {"kind": "value", "value": color_str}}) | |
| print("<touching color [#rrggbb]?> :", color_str) | |
| return {"kind": "block", "block": block_id} | |
| # if m: | |
| # inputs = {"COLOR": {"kind": "value", "value": m.group(1)}} | |
| # print("<touching color [#rrggbb]?> : ",inputs) | |
| # block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) | |
| # 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}|[a-zA-Z]+(?: v)?)[>\]\)]? is touching [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower) | |
| # if m: | |
| # c1, c2 = m.group(1).strip(), m.group(2).strip() | |
| # # Cleanup dropdown suffix | |
| # if c1.endswith(" v"): | |
| # c1 = c1[:-2].strip() | |
| # if c2.endswith(" v"): | |
| # c2 = c2[:-2].strip() | |
| # # Normalize each color | |
| # if not c1.startswith("#"): | |
| # c1 = COLOR_MAP.get(c1.lower(), None) | |
| # if c1 is None: | |
| # raise ValueError(f"❌ Unknown color name: {m.group(1)}") | |
| # if not c2.startswith("#"): | |
| # c2 = COLOR_MAP.get(c2.lower(), None) | |
| # if c2 is None: | |
| # raise ValueError(f"❌ Unknown color name: {m.group(2)}") | |
| # inputs = { | |
| # "COLOR1": {"kind": "value", "value": c1}, | |
| # "COLOR2": {"kind": "value", "value": c2}, | |
| # } | |
| # print("<color [#rrggbb] is touching [#rrggbb]?> : ", inputs) | |
| # block_id = _register_block( | |
| # "sensing_coloristouchingcolor", parent_key, True, | |
| # pick_key_func, all_generated_blocks, inputs=inputs | |
| # ) | |
| # return {"kind": "block", "block": block_id} | |
| m = re.search(r"color\s+[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\s*is\s*touching\s*[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??",s_lower,re.IGNORECASE,) | |
| if m: | |
| c1, c2 = m.group(1).strip(), m.group(2).strip() | |
| if c1.endswith(" v"): c1 = c1[:-2].strip() | |
| if c2.endswith(" v"): c2 = c2[:-2].strip() | |
| def normalize_color(c_raw): | |
| if c_raw.startswith("#"): return c_raw | |
| cname = c_raw.lower() | |
| if cname in COLOR_MAP: return COLOR_MAP[cname] | |
| match = difflib.get_close_matches(cname, COLOR_MAP.keys(), n=1, cutoff=0.6) | |
| if match: return COLOR_MAP[match[0]] | |
| print(f"⚠️ Unknown color name: {c_raw} → skipping block") | |
| return None | |
| c1_hex = normalize_color(c1) | |
| c2_hex = normalize_color(c2) | |
| if not c1_hex or not c2_hex: return None | |
| block_id = _register_block("sensing_coloristouchingcolor", parent_key, True,pick_key_func, all_generated_blocks,inputs = {"COLOR1": {"kind": "value", "value": c1_hex},"COLOR2": {"kind": "value", "value": c2_hex},}) | |
| print("<color [#rrggbb] is touching [#rrggbb]?> :", c1_hex, c2_hex) | |
| return {"kind": "block", "block": block_id} | |
| # m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s_lower) | |
| # if m: | |
| # inputs = {"COLOR1": {"kind": "value", "value": m.group(1)}, "COLOR2": {"kind": "value", "value": m.group(2)}} | |
| # print("<color [#rggbb] is touching [#rrggbb]?> : ",inputs) | |
| # block_id = _register_block("sensing_coloristouchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) | |
| # return {"kind": "block", "block": block_id} | |
| # 8) Key pressed: <key [key v] pressed?> | |
| # m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?", s_lower, re.IGNORECASE) | |
| # 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]}) | |
| # print("<key [...] pressed?> : ", option) | |
| # inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}} | |
| # block_id = _register_block("sensing_keypressed", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) | |
| # all_generated_blocks[menu_block_id]["parent"] = block_id | |
| # return {"kind": "block", "block": block_id} | |
| m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?",s_lower,re.IGNORECASE,) | |
| if m: | |
| option = m.group(1).strip() | |
| valid_options = [ "space", "up arrow", "down arrow", "right arrow", "left arrow", "any","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] | |
| cleaned = re.sub(r"[^a-z0-9]", "", option.lower()).strip() | |
| normalized_map = { re.sub(r"[^a-z0-9]", "", v.lower()): v for v in valid_options } | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| option_val = normalized_map[match[0]] | |
| else: | |
| option_val = option | |
| # Register shadow menu block | |
| menu_block_id = _register_block("sensing_keyoptions",parent_key, True, pick_key_func, all_generated_blocks,fields={"KEY_OPTION": [option_val, None]}) | |
| print("<key [...] pressed?> :", option_val) | |
| block_id = _register_block("sensing_keypressed",parent_key, True, pick_key_func, all_generated_blocks,inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}}) | |
| all_generated_blocks[menu_block_id]["parent"] = block_id | |
| return {"kind": "block", "block": block_id} | |
| # 9) Mouse down?: mouse down? | |
| if s_lower == "mouse down?": | |
| block_id = _register_block("sensing_mousedown", parent_key, True, pick_key_func, all_generated_blocks) | |
| print("mouse down? : ",s_lower) | |
| 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() | |
| print("the line is this:", l) | |
| # 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.fullmatch(r"when (green )?flag click(ed)?", l.strip(), re.IGNORECASE): 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 re.match(r"glide\s+\d+(\.\d+)?\s+(?:sec|second)(?:s)?\s+to\s+x:", l): return "motion_glidesecstoxy", "stack" | |
| if re.match(r"""^glide\s+(?:\d+(?:\.\d+)?|\(\s*.+?\s*\)|\[\s*.+?\s*\])\s+(?:sec|secs|second|seconds)\s+to\s+x:""",l,re.IGNORECASE | re.VERBOSE): return "motion_glidesecstoxy", "stack" | |
| # if re.match(r"glide\s+\d+(\.\d+)?\s+(?:sec|second)(?:s)?\s+to\s+", l): return "motion_glideto", "stack" | |
| if re.match(r"""^glide\s+(?:\d+(?:\.\d+)?|\(\s*.+?\s*\)|\[\s*.+?\s*\])\s+(?:sec|secs|second|seconds)\s+to""",l,re.IGNORECASE | re.VERBOSE): 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 l.startswith("change y by"): return "motion_changeyby", "stack" | |
| # if re.match(r"set x to\s*\(.+\)", l): return "motion_setx", "stack" # Specific for set x | |
| #if re.match(r"set y to\s*\(.+\)", l): return "motion_sety", "stack" # Specific for set y | |
| if re.match(r"""^\s*set\ x\ to\s*(?:\(\s*.+?\s*\)|\[.+?\]|.+)\s*$""", l, re.IGNORECASE | re.VERBOSE): return "motion_setx", "stack" | |
| if re.match(r"""^\s*set\ y\ to\s*(?:\(\s*.+?\s*\)|\[.+?\]|.+)\s*$""", l, re.IGNORECASE | re.VERBOSE): return "motion_sety", "stack" | |
| #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.startswith("set [") and " effect to " in 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 re.match(r"wait\s+(\(?\[?.+?\]?\)?)\s*(sec(?:ond)?s?)?$", l): return "control_wait", "stack" | |
| # if l.startswith("wait until <"): return "control_wait_until", "stack" | |
| if re.match(r"wait\s+(?!until)(\(?\[?.+?\]?\)?)\s*(sec(?:ond)?s?)?$", l, re.IGNORECASE): return "control_wait", "stack" | |
| if re.match(r"wait\s+until\s*<", l, re.IGNORECASE): 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 re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$", l, re.IGNORECASE): 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 ... ] } | |
| """ | |
| ptrs = defaultdict(int) | |
| def pick_key(opcode): | |
| lst = opcode_keys.get(opcode, []) | |
| idx = ptrs[opcode] | |
| if idx >= len(lst): | |
| ptrs[opcode] += 1 | |
| return f"{opcode}_{idx + 1}" | |
| ptrs[opcode] += 1 | |
| return lst[idx] | |
| # Helper: lookahead for `else` that matches this `if` | |
| def classify_with_else_context(lines, i, stripped_line): | |
| """ | |
| If stripped_line starts with an if-expression (e.g. "if <...> then"), | |
| scan forward to see if a corresponding 'else' exists before the matching 'end'. | |
| Returns a tuple (opcode, ntype). | |
| If this helper doesn't apply, it returns ("", "") (no None). | |
| """ | |
| l = stripped_line.strip().lower() | |
| if not l.startswith("if <"): | |
| return ("", "") | |
| # Lookahead with nesting: nested 'if' increments depth. | |
| depth = 0 | |
| for j in range(i + 1, len(lines)): | |
| look = lines[j].strip().lower() | |
| # ignore blank lines and comments | |
| if not look or look.startswith("//"): | |
| continue | |
| # if we find another 'if <' before an 'end', that's a nested if -> depth+1 | |
| if look.startswith("if <"): | |
| depth += 1 | |
| continue | |
| # an 'end' closes the most recent if (nested or this one) | |
| if look == "end": | |
| if depth == 0: | |
| # reached the end for this if without seeing an else | |
| break | |
| depth -= 1 | |
| continue | |
| # an else that occurs when depth == 0 belongs to this if | |
| if look == "else" and depth == 0: | |
| return ("control_if_else", "c_block") | |
| # no matching else found before the corresponding end -> plain control_if | |
| return ("control_if", "c_block") | |
| # Change: Start with an empty dictionary. Blocks will be added only as they are parsed. | |
| all_generated_blocks = {} | |
| stack = [(-2, None, None)] | |
| top_level_script_keys = [] | |
| lines = pseudo_code.splitlines() | |
| print("The lines here are:\n", lines) | |
| i = 0 | |
| while i < len(lines): | |
| raw_line = lines[i] | |
| stripped_line = raw_line.strip() | |
| if not stripped_line or stripped_line.startswith("//"): | |
| i += 1 | |
| continue | |
| current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2 | |
| 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 | |
| # Check if the popped scope's owner is an IF block. | |
| # This check now looks for any C-Block, which is correct for this context. | |
| if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block": | |
| # Explicitly upgrade the block from a 'control_if' to a 'control_if_else'. | |
| owner_block = all_generated_blocks[popped_owner_key] | |
| owner_block["op_code"] = "control_if_else" | |
| # Push a new scope for the 'else' substack, with the same owner. | |
| stack.append((current_indent, popped_owner_key, None)) | |
| else: | |
| print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}") | |
| stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain)) | |
| i += 1 | |
| continue | |
| if stripped_line.lower() == "end": | |
| 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 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": | |
| if owner_block["inputs"].get("SUBSTACK2") and owner_block["inputs"]["SUBSTACK2"][1] is not None: | |
| pass | |
| else: | |
| pass | |
| elif owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is None: | |
| owner_block["inputs"]["SUBSTACK"] = [2, None] | |
| i += 1 | |
| continue | |
| 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 | |
| current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1] | |
| # RUN the lookahead classifier first; it never returns None. | |
| override_opcode, override_ntype = classify_with_else_context(lines, i, stripped_line) | |
| if override_opcode: | |
| opcode, ntype = override_opcode, override_ntype | |
| else: | |
| # fallback to your original classify (unchanged) | |
| opcode, ntype = classify(stripped_line) | |
| stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip() | |
| print("The opcode here is", opcode) | |
| print("The ntype here is", ntype) | |
| if opcode is None: | |
| i += 1 | |
| continue | |
| key = pick_key(opcode) | |
| # Change: The code now relies on this check to create a block only if it's new. | |
| if key not in all_generated_blocks: | |
| all_generated_blocks[key] = copy.deepcopy(generated_input.get(key, all_block_definitions.get(opcode, {}))) | |
| all_generated_blocks[key]["id"] = key | |
| all_generated_blocks[key]["inputs"] = all_generated_blocks[key].get("inputs", {}) | |
| all_generated_blocks[key]["fields"] = all_generated_blocks[key].get("fields", {}) | |
| info = all_generated_blocks[key] | |
| if ntype == "hat": | |
| info["parent"] = None | |
| info["topLevel"] = True | |
| top_level_script_keys.append(key) | |
| stack.append((current_indent, key, None)) | |
| else: | |
| if last_block_in_current_chain: | |
| info["parent"] = last_block_in_current_chain | |
| all_generated_blocks[last_block_in_current_chain]["next"] = key | |
| else: | |
| info["parent"] = current_owner_block_id | |
| 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": | |
| if owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is not None and \ | |
| (not owner_block["inputs"].get("SUBSTACK2") or owner_block["inputs"]["SUBSTACK2"][1] is None): | |
| owner_block["inputs"]["SUBSTACK2"] = [2, key] | |
| else: | |
| owner_block["inputs"]["SUBSTACK"] = [2, key] | |
| elif not owner_block["inputs"].get("SUBSTACK") or owner_block["inputs"]["SUBSTACK"][1] is None: | |
| owner_block["inputs"]["SUBSTACK"] = [2, key] | |
| elif current_owner_block_id and all_generated_blocks[current_owner_block_id]["block_shape"] == "Hat Block": | |
| all_generated_blocks[current_owner_block_id]["next"] = key | |
| info["topLevel"] = False | |
| info["next"] = None | |
| if ntype == "c_block" or opcode == "procedures_definition": | |
| stack[-1] = (current_scope_indent, current_owner_block_id, key) | |
| stack.append((current_indent, key, None)) | |
| else: | |
| 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) | |
| m = re.search(r"move\s*(?:\(\s*)?(.+?)(?:\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))} | |
| print("motion_movesteps : ",m.group(1).strip()) | |
| if m: info["inputs"]["STEPS"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["STEPS"]) | |
| # print(f"block_id {opcode} : {block_id}") | |
| 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) | |
| #m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(-?\d+(?:\.\d+)?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(.+?)(?:\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))} | |
| if m: info["inputs"]["DEGREES"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, info["inputs"]["DEGREES"]) | |
| # print(f"block_id {opcode} : {block_id}") | |
| elif opcode == "motion_gotoxy": | |
| # m_x = re.search(r"x:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE) | |
| # m_y = re.search(r"y:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE) | |
| m_x = re.search(r"x:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*y:|$)", stmt_for_parse, re.IGNORECASE) | |
| m_y = re.search(r"y:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*$)", stmt_for_parse, re.IGNORECASE) | |
| if m_x: info["inputs"]["X"] = parse_reporter_or_value( m_x.group(1).strip("() "), key, pick_key, all_generated_blocks) | |
| if m_y: info["inputs"]["Y"] = parse_reporter_or_value( m_y.group(1).strip("() "), key, pick_key, all_generated_blocks) | |
| # print("motion_gotoxy m_x", m_x.group(1) if m_x else None) | |
| # print("motion_gotoxy m_y", m_y.group(1) if m_y else None) | |
| # if m_x: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, ) | |
| # print(f"block_id {opcode} : {block_id}") | |
| # if m_y: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["Y"]) | |
| # print(f"block_id {opcode} : {block_id}") | |
| elif opcode == "motion_glidesecstoxy": | |
| stmt = _auto_balance(stmt_for_parse) | |
| # m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", 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) | |
| #m_secs = re.search(r"glide\s+(\d+(?:\.\d+)?)\s+(?:sec|secs|second|seconds)", stmt_for_parse, re.IGNORECASE) | |
| m_secs = re.search(r"glide\s+(?:\(\s*)?(.+?)(?:\s*\))?\s+(?:sec|secs|second|seconds)\s+to",stmt_for_parse,re.IGNORECASE) | |
| m_x = re.search(r"x:\s*(\(.+?\)|\[.+?\]|[^)\s]+)", stmt_for_parse, re.IGNORECASE) | |
| m_y = re.search(r"y:\s*(\(.+?\)|\[.+?\]|[^)\s]+)", stmt_for_parse,re.IGNORECASE) | |
| print("motion_glidesecstoxy m_secs ",m_secs.group(1)) | |
| print("motion_glidesecstoxy m_x ",m_x.group(1)) | |
| print("motion_glidesecstoxy m_y ",m_y.group(1)) | |
| # 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))} | |
| if m_secs: info["inputs"]["SECS"] = parse_reporter_or_value(m_secs.group(1).strip(), key, pick_key, all_generated_blocks) | |
| if m_x: info["inputs"]["X"] = parse_reporter_or_value(m_x.group(1).strip(), key, pick_key, all_generated_blocks) | |
| if m_y: info["inputs"]["Y"] = parse_reporter_or_value(m_y.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "motion_pointindirection": | |
| #m = re.search(r"direction\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| #m = re.search(r"direction\s*(?:\(\s*)?(.+?)(?:\s*\))?", stmt_for_parse, re.IGNORECASE) | |
| print("stmt_for_parse",stmt_for_parse) | |
| m = re.search(r"""point\s+in\s+direction\s*(?:\(\s*)?([^)\r\n]+?)(?:\s*\))?\s*$""",stmt_for_parse,re.IGNORECASE | re.VERBOSE) | |
| print("motion_pointindirection m: ",m.group(1)) | |
| #if m: info["inputs"]["DIRECTION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} | |
| if m: info["inputs"]["DIRECTION"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode in ["motion_changexby", "motion_changeyby"]: | |
| #m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| #m = re.search(r"""by\s*\(?\s*(-?\d+(?:\.\d+)?)\s*\)?""",stmt_for_parse,re.IGNORECASE | re.VERBOSE) | |
| #m = re.search(r"by\s*(?:\(\s*)?(.+?)(?:\s*\))?", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""by\s*(?:\(\s*)?([^)\s]+(?:\s+[^)\s]+)*)(?:\s*\))?""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| #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))} | |
| if m: info["inputs"]["DX" if opcode == "motion_changexby" else "DY"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| 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) | |
| m = re.search(r"""(?:set\ x\ to|set\ y\ to)\s*(?:\(\s*)?([^)\s]+(?:\s+[^)\s]+)*)(?:\s*\))?(?=\s|$)""",stmt_for_parse, re.IGNORECASE | re.VERBOSE ) | |
| #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))} | |
| if m: info["inputs"]["X" if opcode == "motion_setx" else "Y"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "looks_changesizeby": | |
| #m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| #m = re.search(r"""by\s*(\((?:.+?)\)|\[\s*.+?\s*v\s*\]|[^\s]+(?:\s+[^\s]+)*)""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| m = re.search(r"""by\s*(?:\(\s*)?(.*?)?(?:\s*\))?$""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| #if m: info["inputs"]["CHANGE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} | |
| if m: info["inputs"]["CHANGE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "looks_setsizeto": | |
| #m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*%", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""to\s*(?:\(\s*)?(.*?)(?:\s*\))?$""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| if m: info["inputs"]["SIZE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode in ["looks_changeeffectby", "sound_changeeffectby"]: | |
| # m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""by\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*$""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| #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))} | |
| if m: info["inputs"]["CHANGE" if opcode == "looks_changeeffectby" else "VALUE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode in ["looks_seteffectto", "sound_setvolumeto"]: | |
| #m = re.search(r"to\s*\(\s*(-?\d+(\.\d+)?)\s*\)(?:\s*%)?", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""to\s*(?:\(\s*)?(.*?)(?:\s*\))?(?:\s*%)?\s*$""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| #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))} | |
| if m: info["inputs"]["VALUE" if opcode == "looks_seteffectto" else "VOLUME"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "looks_goforwardbackwardlayers": | |
| #m = re.search(r"go\s*(?:forward|backward)\s*\(\s*(-?\d+)\s*\)\s*layers", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"go\s*(?:forward|backward)\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*layers\s*$", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| #if m: info["inputs"]["NUM"] = {"kind": "value", "value": int(m.group(1))} | |
| if m: info["inputs"]["NUM"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "control_wait": | |
| #m = re.search(r"wait\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*seconds", stmt_for_parse, re.IGNORECASE) | |
| #m = re.search(r"""wait\s*\(?\s*(-?\d+(?:\.\d+)?)\s*\)?\s*seconds""",stmt_for_parse,re.IGNORECASE | re.VERBOSE) | |
| m = re.search(r"""wait\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*(?:sec(?:ond)?s?)\b""",stmt_for_parse,re.IGNORECASE | re.VERBOSE,) | |
| #if m: info["inputs"]["DURATION"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} | |
| if m: info["inputs"]["DURATION"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "control_repeat": | |
| #m = re.search(r"repeat\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""repeat\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*$""", stmt_for_parse,re.IGNORECASE | re.VERBOSE,) | |
| #if m: info["inputs"]["TIMES"] = {"kind": "value", "value": int(m.group(1))} | |
| if m: info["inputs"]["TIMES"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "data_changevariableby": | |
| #m = re.search(r"by\s*\(\s*(-?\d+(\.\d+)?)\s*\)", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"""by\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*$""", stmt_for_parse, re.IGNORECASE | re.VERBOSE,) | |
| #if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} | |
| if m: info["inputs"]["VALUE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| 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) | |
| m_index = re.search(r"""at\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*of""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| 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))} | |
| if m_index: info["inputs"]["INDEX"] = parse_reporter_or_value(m_index.group(1).strip(), key, pick_key, all_generated_blocks) | |
| elif opcode == "data_replaceitemoflist": | |
| #m_index = re.search(r"replace item\s*\(\s*(-?\d+)\s*\)\s*of", stmt_for_parse, re.IGNORECASE) | |
| m_index = re.search(r"""replace\ item\s*(?:\(\s*)?(.*?)(?:\s*\))?\s*of""", stmt_for_parse, re.IGNORECASE | re.VERBOSE) | |
| 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_index: info["inputs"]["INDEX"] = parse_reporter_or_value(m_index.group(1).strip(), key, pick_key, all_generated_blocks) | |
| 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) | |
| print(stmt_for_parse) | |
| m = re.search(r">\s*(?:\(\s*(.*?)\s*\)|([^\s\)]+))", stmt_for_parse, re.IGNORECASE) | |
| print("event_whengreaterthan parse",m.group(1).strip()) | |
| #if m: info["inputs"]["VALUE"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} | |
| if m: info["inputs"]["VALUE"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) | |
| # 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() | |
| print("The value at data_setvariableto: ",value_str) | |
| info["fields"]["VARIABLE"] = [var_name, None] | |
| info["inputs"]["VALUE"] = parse_reporter_or_value(value_str, key, pick_key, all_generated_blocks) | |
| # new add ons | |
| elif opcode == "motion_setrotationstyle": | |
| m = re.search(r"set rotation style\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| valid_options = ["left-right", "all around", "don't rotate"] | |
| cleaned = re.sub(r"[^a-z\s]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z\s]", "", v.lower()).strip(): v for v in valid_options} | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| print("motion_setrotationstyle------------>",match) | |
| if match: option_val = normalized_map[match[0]] | |
| else: option_val = "all around" | |
| info["fields"]["STYLE"] = [option_val.strip(), None] | |
| # Dropdown/Menu inputs (UPDATED) | |
| # elif opcode == "motion_goto": | |
| # m = re.search(r"go to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))", stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option) | |
| # menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) | |
| # info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode == "motion_goto": | |
| m = re.search(r"go to\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("<go to []> :", option) | |
| option_val = None | |
| valid_properties = ["random position", "random-position", "mouse-pointer", "mouse pointer"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: | |
| if "random" in normalized_map[cleaned]: option_val = "_random_" | |
| else: option_val = "_mouse_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| if "random" in normalized_map[match[0]]: option_val = "_random_" | |
| else: option_val = "_mouse_" | |
| else: | |
| val, key = process_text(option) | |
| if key == "sprite": option_val = val | |
| else: option_val = "_random_" | |
| menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) | |
| info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "motion_glideto": | |
| # m_secs = re.search( | |
| # r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*secs to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| # if m_secs: | |
| # secs = float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1)) | |
| # option = (m_secs.group(2) or m_secs.group(3) or m_secs.group(4)).strip() | |
| # option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option) | |
| # info["inputs"]["SECS"] = {"kind": "value", "value": secs} | |
| # menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, | |
| # fields={"TO": [option_val, None]}) | |
| # info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode == "motion_glideto": | |
| m_secs = re.search(r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*secs\s*to\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m_secs: | |
| secs = float(m_secs.group(1)) if "." in m_secs.group(1) else int(m_secs.group(1)) | |
| option = m_secs.group(2).strip() | |
| print("<glide secs to []> :", option) | |
| option_val = None | |
| valid_properties = ["random position", "random-position", "mouse-pointer", "mouse pointer"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: | |
| if "random" in normalized_map[cleaned]: option_val = "_random_" | |
| else: option_val = "_mouse_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| if "random" in normalized_map[match[0]]: option_val = "_random_" | |
| else: option_val = "_mouse_" | |
| else: | |
| val, key = process_text(option) | |
| if key == "sprite": option_val = val | |
| else: option_val = "_random_" | |
| # secs input | |
| info["inputs"]["SECS"] = {"kind": "value", "value": secs} | |
| # TO menu block | |
| menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) | |
| info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "motion_pointtowards": | |
| # m = re.search(r"point towards\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # option_val = {"mouse-pointer": "_mouse_"}.get(option, option) | |
| # menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks, | |
| # fields={"TOWARDS": [option_val, None]}) | |
| # info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode == "motion_pointtowards": | |
| m = re.search(r"point\s*towards\s*(?:\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| print("<point towards []> :", option) | |
| option_val = None | |
| valid_properties = ["mouse-pointer", "mouse pointer"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: option_val = "_mouse_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = "_mouse_" | |
| else: | |
| val, key = process_text(option) | |
| if key == "sprite": option_val = val | |
| else: option_val = "_mouse_" | |
| menu_block_id = _register_block( "motion_pointtowards_menu",key,True,pick_key,all_generated_blocks,fields={"TOWARDS": [option_val, None]},) | |
| info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "sensing_keypressed": | |
| # m = re.search(r"key\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\s*pressed\?",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks, | |
| # fields={"KEY_OPTION": [option, None]}) | |
| # info["inputs"]["KEY_OPTION"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "sensing_touchingobject": | |
| # m = re.search(r"touching\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\?",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # option_val = {"mouse-pointer": "_mouse_", "edge": "_edge_"}.get(option, option) | |
| # menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks, | |
| # fields={"TOUCHINGOBJECTMENU": [option_val, None]}) | |
| # info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "control_create_clone_of": | |
| # m = re.search(r"create clone of\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # option_val = {"myself": "_myself_"}.get(option, 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"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode == "control_create_clone_of": | |
| m = re.search(r"create\s*clone\s*of\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("<create clone of []> :", option) | |
| option_val = None | |
| valid_properties = ["myself"] | |
| cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} | |
| if cleaned in normalized_map: option_val = "_myself_" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = "_myself_" | |
| else: | |
| val, key = process_text(option) | |
| if key == "sprite": option_val = val | |
| else: option_val = "_myself_" | |
| 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"] = {"kind": "block", "block": 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) | |
| # m = re.search(r"(?:play sound|start sound)\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+)\s*\)?)",stmt_for_parse,re.IGNORECASE) | |
| # if m: | |
| # # option = m.group(1).strip() | |
| # option = m.group(1) or m.group(2) | |
| # print("sounds option content--------->",option) | |
| # menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]}) | |
| # info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode in ["sound_playuntildone", "sound_play"]: | |
| m = re.search(r"(?:play sound|start sound)\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("sounds option content --------->", option) | |
| option_val = None | |
| val, key = process_text(option) | |
| if key == "sound": option_val = val | |
| else: option_val = option | |
| print("sounds option content --------->", option_val) | |
| menu_block_id = _register_block("sound_sounds_menu",key,True,pick_key,all_generated_blocks,fields={"SOUND_MENU": [option_val, None]},) | |
| info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode == "looks_switchcostumeto": | |
| # m = re.search(r"switch costume to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks, | |
| # fields={"COSTUME": [option, None]}) | |
| # info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode == "looks_switchcostumeto": | |
| m = re.search(r"switch\s*costume\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("<switch costume to []> :", option) | |
| option_val = None | |
| val, key = process_text(option) | |
| if key == "sprite": option_val = val | |
| else: option_val = option | |
| menu_block_id = _register_block("looks_costume",key,True,pick_key,all_generated_blocks,fields={"COSTUME": [option_val, None]},) | |
| info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id} | |
| # elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]: | |
| # m = re.search(r"switch backdrop to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| # menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks, | |
| # fields={"BACKDROP": [option, None]}) | |
| # info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]: | |
| m = re.search(r"switch\s*backdrop\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("<switch backdrop to []> :", option) | |
| option_val = None | |
| defaults = ["next backdrop", "random backdrop", "previous backdrop"] | |
| cleaned = re.sub(r"[^a-z0-9 ]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z0-9 ]", "", v.lower()): v for v in defaults} | |
| if cleaned in normalized_map: | |
| opt = normalized_map[cleaned] | |
| if opt == "next backdrop": option_val = "next backdrop" | |
| elif opt == "random backdrop": option_val = "random backdrop" | |
| elif opt == "previous backdrop": option_val = "previous backdrop" | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: | |
| opt = normalized_map[match[0]] | |
| if opt == "next backdrop": option_val = "next backdrop" | |
| elif opt == "random backdrop": option_val = "random backdrop" | |
| elif opt == "previous backdrop": option_val = "previous backdrop" | |
| else: | |
| val, key = process_text(option) | |
| if key == "backdrop": option_val = val | |
| else: option_val = "random backdrop" | |
| menu_block_id = _register_block("looks_backdrops",key,True,pick_key,all_generated_blocks,fields={"BACKDROP": [option_val, None]},) | |
| info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id} | |
| elif opcode in ["event_broadcast", "event_broadcastandwait"]: | |
| m = re.search(r"broadcast\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3)).strip() | |
| info["inputs"]["BROADCAST_INPUT"] = {"kind": "value", "value": option} | |
| # elif opcode == "control_stop": | |
| # # m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$", | |
| # m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$", | |
| # stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = m.group(1).strip().lower() | |
| # # Normalize casing to match Scratch’s expected field values | |
| # if option == "all": | |
| # stop_val = "all" | |
| # elif option == "this script": | |
| # stop_val = "this script" | |
| # else: | |
| # stop_val = "other scripts in sprite" | |
| # info.setdefault("fields", {}) | |
| # info["fields"]["STOP_OPTION"] = [stop_val, None] | |
| elif opcode == "control_stop": | |
| m = re.match(r"""stop\s*[\[(]?\s*(.+?)\s*(?:v)?\s*[\])]?$""",stmt_for_parse,re.IGNORECASE | re.VERBOSE) | |
| if m: | |
| option = m.group(1).strip().lower() | |
| valid_options = ["all", "this script", "other scripts in sprite"] | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if option in normalized_map: stop_val = normalized_map[option] | |
| else: | |
| match = difflib.get_close_matches(option, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: stop_val = normalized_map[match[0]] | |
| else: stop_val = "all" # fallback (won’t break but may not be a valid field) | |
| print("stop value",stop_val) | |
| info.setdefault("fields", {}) | |
| info["fields"]["STOP_OPTION"] = [stop_val, None] | |
| # Conditional inputs (Boolean blocks) | |
| elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]: | |
| #cond_match_str = stmt_for_parse.replace("if <", "").replace("> then else", "").replace("> then", "").replace("wait until <", "").replace("repeat until <", "").strip() | |
| cond_match_str = extract_condition_balanced(stmt_for_parse) | |
| print(f"The cond match text here:---->{cond_match_str}") | |
| if cond_match_str: | |
| # Pass current block's key as parent for nested condition | |
| condition_obj = parse_condition(cond_match_str, key, pick_key, all_generated_blocks) | |
| info["inputs"]["CONDITION"] = condition_obj | |
| # Ensure the parent of the condition block is set to this control block | |
| if condition_obj.get("kind") == "block": | |
| all_generated_blocks[condition_obj["block"]]["parent"] = key | |
| 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).strip(), 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).strip(), 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).strip(), 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).strip(), None] | |
| if "DRAG_MODE" in info["fields"]: | |
| m = re.search(r"set\s*drag\s*mode\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) | |
| if m: | |
| option = m.group(1).strip() | |
| valid_options = ["draggable", "not draggable"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {re.sub(r"[^a-z]", "", v.lower()): v for v in valid_options} | |
| if cleaned in normalized_map: | |
| option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = normalized_map[match[0]] | |
| else: option_val = "draggable" | |
| info["fields"]["DRAG_MODE"] = [option_val, 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().strip(), 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\]\s*effect", stmt_for_parse, re.IGNORECASE) | |
| m = re.search(r"(?:change|set)\s*(?:\[\s*([^\]]+?)\s*v?\]|\(\s*([^)]+?)\s*\)|([A-Za-z]+))\s*effect\s*to\s*(?:\[\s*([^\]]+?)\s*v?\]|\(\s*([^)]+?)\s*\)|(.+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = m.group(1).strip() | |
| print("option-------------->>",option) | |
| if opcode.startswith("looks_"): valid_options = ["color", "fisheye", "whirl", "pixelate", "mosaic", "brightness", "ghost"] | |
| else: valid_options = ["pitch", "pan left/right"] | |
| cleaned = re.sub(r"[^a-z\s/]", "", option.lower()).strip() | |
| print("cleaned----------->",cleaned) | |
| normalized_map = { re.sub(r"[^a-z\s/]", "", v.lower()).strip(): v for v in valid_options } | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.3) | |
| if match: option_val = normalized_map[match[0]] | |
| elif opcode in ["sound_changeeffectby", "sound_seteffectto"]: option_val = "pitch" | |
| else: option_val = "color" # fallback if nothing close | |
| print("option-------------->>",option_val) | |
| info["fields"]["EFFECT"] = [option_val, 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).strip(), None] | |
| # if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]: | |
| # # Match costume/backdrop dropdown with number/name | |
| # m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = m.group(1).strip() | |
| # valid_options = ["number", "name"] | |
| # cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| # normalized_map = {v.lower(): v for v in valid_options} | |
| # if cleaned in normalized_map: | |
| # option_val = normalized_map[cleaned] | |
| # else: | |
| # match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| # if match: | |
| # option_val = normalized_map[match[0]] | |
| # else: | |
| # option_val = option # fallback | |
| # info["fields"]["NUMBER_NAME"] = [option_val, 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).strip(), None] | |
| if "FRONT_BACK" in info["fields"] and opcode == "looks_gotofrontback": | |
| m = re.search(r"go to\s*\[([^\]]+)\s*v\]\s*layer", stmt_for_parse, re.IGNORECASE) | |
| if m: | |
| option = m.group(1).strip() | |
| valid_options = ["front", "back"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = normalized_map[match[0]] | |
| else: option_val = "front" | |
| info["fields"]["FRONT_BACK"] = [option_val, 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).strip(), 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: | |
| option = m.group(1).strip() | |
| valid_options = ["forward", "backward"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = normalized_map[match[0]] | |
| else: option_val = "forward" | |
| info["fields"]["FORWARD_BACKWARD"] = [option_val, 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().strip(), None] | |
| # if "OPERATOR" in info["fields"] and opcode == "operator_mathop": | |
| # m = re.search(r"\[([^\]]+)\s*v\]\s*of", stmt_for_parse, re.IGNORECASE) | |
| # if m: | |
| # option = m.group(1).strip() | |
| # valid_options = [ | |
| # "abs", "floor", "ceiling", "sqrt", | |
| # "sin", "cos", "tan", | |
| # "asin", "acos", "atan", | |
| # "ln", "log", "e ^", "10 ^" | |
| # ] | |
| # cleaned = re.sub(r"[^a-z0-9^]", "", option.lower()).strip() | |
| # normalized_map = {re.sub(r"[^a-z0-9^]", "", v.lower()): v for v in valid_options} | |
| # if cleaned in normalized_map: | |
| # option_val = normalized_map[cleaned] | |
| # else: | |
| # match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| # if match: | |
| # option_val = normalized_map[match[0]] | |
| # else: | |
| # option_val = option # fallback | |
| # info["fields"]["OPERATOR"] = [option_val, 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().strip(), 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().strip(), None] | |
| if "WHENGREATERTHANMENU" in info["fields"] and opcode == "event_whengreaterthan": | |
| m = re.search(r"when\s*\[([^\]]+)\s*v\]\s*>", stmt_for_parse, re.IGNORECASE) | |
| if m: | |
| option = m.group(1).strip() | |
| valid_options = ["LOUDNESS", "TIMER"] | |
| cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() | |
| normalized_map = {v.lower(): v for v in valid_options} | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned].upper() | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = normalized_map[match[0]].upper() | |
| else: option_val = "LOUDNESS" # fallback | |
| info["fields"]["WHENGREATERTHANMENU"] = [option_val, 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).strip(), None] | |
| if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": | |
| m = re.search(r"when\s*\[([^\]]+)\s*v\]\s*key pressed", stmt_for_parse, re.IGNORECASE) | |
| if m: | |
| option = m.group(1).strip() | |
| # Valid key options | |
| valid_options = [ "space", "up arrow", "down arrow", "right arrow", "left arrow", "any", | |
| "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] # temporary | |
| cleaned = re.sub(r"[^a-z0-9]", "", option.lower()).strip() | |
| normalized_map = { re.sub(r"[^a-z0-9]", "", v.lower()): v for v in valid_options } | |
| if cleaned in normalized_map: option_val = normalized_map[cleaned] | |
| else: | |
| match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) | |
| if match: option_val = normalized_map[match[0]] | |
| else: option_val = "space" | |
| info["fields"]["KEY_OPTION"] = [option_val, 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).strip(), None] | |
| if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": | |
| m = re.search(r"when\s*backdrop\s*switches\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) | |
| if m: | |
| option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() | |
| print("<when backdrop switches to []> :", option) | |
| val, key = process_text(option) | |
| if key == "backdrop": info["fields"]["BACKDROP"] = [val, None] | |
| else: info["fields"]["BACKDROP"] = ["backdrop1", 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).strip(), 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) | |
| 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 | |
| return all_generated_blocks # Return the modified dictionary directly | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[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)) | |
| # conservative, safe alphabet derived from Scratch-style IDs | |
| _DEFAULT_SCRATCH_ALPHABET = string.ascii_letters + string.digits + "_-" | |
| _DEFAULT_LENGTH = 20 | |
| # small in-memory cache to avoid duplicates in a single run | |
| _generated_ids_cache = set() | |
| def generate_secure_token() -> str: | |
| """ | |
| Return a single Scratch-style ID string (20 chars long) using a safe alphabet. | |
| No input required. | |
| Notes: | |
| - This does NOT check for collisions inside an existing project.json. | |
| If you need to guarantee uniqueness with an existing file, use the | |
| project-aware generator shared earlier. | |
| - Collisions are astronomically unlikely for random 20-char tokens, but | |
| the function still avoids duplicates within the same Python process. | |
| """ | |
| alphabet = _DEFAULT_SCRATCH_ALPHABET | |
| length = _DEFAULT_LENGTH | |
| while True: | |
| token = "".join(secrets.choice(alphabet) for _ in range(length)) | |
| if token not in _generated_ids_cache: | |
| _generated_ids_cache.add(token) | |
| return token | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[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)) | |
| # Define the mapping for input field names to their required integer types for shadows | |
| input_type_mapping = { | |
| # Type 4 | |
| "STEPS": 4, "DEGREES": 4, "X": 4, "Y": 4, "SECS": 4, "DX": 4, "DY": 4, | |
| "NUM1": 4, "NUM2": 4, "NUM": 4, "VOLUME": 4, "CHANGE": 4, "SIZE": 4, | |
| "FROM": 4, "TO": 4, # Added for operator_random | |
| "VALUE": 4, # General numeric value | |
| # Type 5 | |
| "DURATION": 5, | |
| # Type 6 | |
| "TIMES": 6, "LETTER": 6, | |
| # Type 7 | |
| "INDEX": 7, | |
| # Type 8 | |
| "DIRECTION": 8, | |
| # Type 9 | |
| "COLOR": 9, "COLOR2": 9, | |
| # Type 10 | |
| "QUESTION": 10, "OPERAND1": 10, "OPERAND2": 10, "STRING1": 10, "STRING2": 10, | |
| "STRING": 10, "ITEM": 10, "MESSAGE": 10, | |
| "VALUE_STRING": 10 # Used internally for VALUE when it should be type 10 | |
| } | |
| # Explicit mapping of opcodes -> expected menu inputs that MUST be simple menu/shadow links ([1, block_id]) | |
| 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")], | |
| "sensing_distanceto": [("DISTANCETOMENU", "sensing_distancetomenu")], | |
| "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")], | |
| } | |
| # helper to check if this opcode+input_name is an explicit menu link | |
| def is_explicit_menu_opcode_input(opcode, input_name): | |
| if not opcode: | |
| return False | |
| pairs = explicit_menu_links.get(opcode, []) | |
| for inp, _menu in pairs: | |
| if inp == input_name: | |
| return True | |
| return False | |
| 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"] | |
| opcode = processed_block["opcode"] # convenience | |
| # Process inputs | |
| if "inputs" in all_gen_block_data: | |
| for input_name, input_data in all_gen_block_data["inputs"].items(): | |
| # --- BROADCAST INPUT special handling (type 11) --- | |
| if input_name == "BROADCAST_INPUT": | |
| if isinstance(input_data, dict) and input_data.get("kind") == "value": | |
| menu_option = input_data.get("value", "message1") | |
| broadcast_id = broadcast_id_map[menu_option] | |
| processed_block["inputs"][input_name] = [ | |
| 1, | |
| [ | |
| 11, | |
| menu_option, | |
| broadcast_id | |
| ] | |
| ] | |
| elif isinstance(input_data, list) and len(input_data) == 2 and isinstance(input_data[1], list) and len(input_data[1]) == 3 and input_data[1][0] == 11: | |
| # Preserve well-formed existing type 11 structure | |
| processed_block["inputs"][input_name] = input_data | |
| else: | |
| # Fallback: try original generated_output_json value if present, else synthesize | |
| fallback = gen_block_data.get("inputs", {}).get(input_name, | |
| [1, [11, "message1", generate_secure_token(20)]]) | |
| processed_block["inputs"][input_name] = fallback | |
| continue | |
| # --- Input described as a dict (value, block, menu) --- | |
| if isinstance(input_data, dict): | |
| kind = input_data.get("kind") | |
| if kind == "value": | |
| # Determine the correct shadow type based on the input_name and opcode | |
| shadow_type = input_type_mapping.get(input_name, 4) # default 4 | |
| # Specific overrides | |
| if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers": | |
| shadow_type = 7 | |
| if input_name == "VALUE" and opcode == "data_setvariableto": | |
| shadow_type = 10 | |
| processed_block["inputs"][input_name] = [ | |
| 1, | |
| [ | |
| shadow_type, | |
| str(input_data.get("value", "")) | |
| ] | |
| ] | |
| continue | |
| if kind == "block": | |
| # nested block reference via dict | |
| nested_block_id = input_data.get("block", "") | |
| if nested_block_id in all_generated_blocks: | |
| # If this opcode+input_name is explicitly a menu -> force [1, nested_block_id] | |
| if is_explicit_menu_opcode_input(opcode, input_name): | |
| processed_block["inputs"][input_name] = [1, nested_block_id] | |
| continue | |
| nested_block_opcode = all_generated_blocks[nested_block_id].get("op_code") | |
| if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]: | |
| processed_block["inputs"][input_name] = [2, nested_block_id] | |
| elif nested_block_opcode in ["data_variable", "data_listcontents"]: | |
| # variable/list reporter inside a reporter -> [1, [12, name, id]] | |
| variable_name = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[0] | |
| variable_id = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[1] | |
| if not variable_id: | |
| variable_id = variable_id_map[variable_name] | |
| processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]] | |
| elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]: | |
| # menu/dropdown inputs that refer to another block/menu -> type 1, refer to block id | |
| processed_block["inputs"][input_name] = [1, nested_block_id] | |
| else: | |
| # other reporter blocks (like motion_xposition, operators) -> nested type 3 | |
| shadow_type_for_nested = input_type_mapping.get(input_name, 10) | |
| processed_block["inputs"][input_name] = [3, nested_block_id, [shadow_type_for_nested, ""]] | |
| else: | |
| # referenced block missing: fallback to original input format if available, else a safe fallback | |
| processed_block["inputs"][input_name] = gen_block_data.get("inputs", {}).get(input_name, [3, nested_block_id, [10, ""]]) | |
| continue | |
| if kind == "menu": | |
| # menu kind: either references a block or a direct value | |
| if "block" in input_data: | |
| # If explicit menu, ensure we store as [1, block_id] | |
| if is_explicit_menu_opcode_input(opcode, input_name): | |
| processed_block["inputs"][input_name] = [1, input_data["block"]] | |
| else: | |
| processed_block["inputs"][input_name] = [1, input_data["block"]] | |
| else: | |
| # direct value in menu -> type 1 with a string shadow (type 10) | |
| menu_option = input_data.get("option", "") | |
| processed_block["inputs"][input_name] = [1, [10, menu_option]] | |
| continue | |
| # --- Input is a list already (various formats) --- | |
| if isinstance(input_data, list): | |
| # Common shapes: | |
| # [1, <value shadow>], [1, <block_id>], [3, <block_id>, [shadow_type, value]], etc. | |
| if len(input_data) == 2: | |
| first_val = input_data[0] | |
| second_val = input_data[1] | |
| # If the second element is a block id that exists | |
| if isinstance(second_val, str) and second_val in all_generated_blocks: | |
| # If explicit menu mapping -> force [1, block_id] | |
| if is_explicit_menu_opcode_input(opcode, input_name): | |
| processed_block["inputs"][input_name] = [1, second_val] | |
| continue | |
| nested_block_opcode = all_generated_blocks[second_val].get("op_code") | |
| if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]: | |
| processed_block["inputs"][input_name] = [2, second_val] | |
| elif nested_block_opcode in ["data_variable", "data_listcontents"]: | |
| variable_name = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[0] | |
| variable_id = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[1] | |
| if not variable_id: | |
| variable_id = variable_id_map[variable_name] | |
| processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]] | |
| elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]: | |
| processed_block["inputs"][input_name] = [1, second_val] | |
| else: | |
| shadow_type_for_nested = input_type_mapping.get(input_name, 10) | |
| # Ensure nested reporters use type 3 | |
| processed_block["inputs"][input_name] = [3, second_val, [shadow_type_for_nested, ""]] | |
| continue | |
| # If the second element is a string literal value -> wrap with appropriate shadow type | |
| if isinstance(second_val, str): | |
| shadow_type = input_type_mapping.get(input_name, 4) # default 4 | |
| if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers": | |
| shadow_type = 7 | |
| if input_name == "VALUE" and opcode == "data_setvariableto": | |
| shadow_type = 10 | |
| processed_block["inputs"][input_name] = [1, [shadow_type, str(second_val)]] | |
| continue | |
| # fallback: preserve the input as-is | |
| processed_block["inputs"][input_name] = input_data | |
| continue | |
| elif len(input_data) == 3 and isinstance(input_data[1], str) and isinstance(input_data[2], list): | |
| # case: [3, "block_id", [shadow_type, shadow_value]] or | |
| # [1, "block_id", [shadow_type, shadow_value]] (rare) | |
| block_ref = input_data[1] | |
| if block_ref in all_generated_blocks: | |
| # If explicit menu mapping -> convert to [1, block_id] | |
| if is_explicit_menu_opcode_input(opcode, input_name): | |
| processed_block["inputs"][input_name] = [1, block_ref] | |
| continue | |
| # keep already well-formed nested reporter [3, ...] | |
| if input_data[0] == 3: | |
| processed_block["inputs"][input_name] = input_data | |
| continue | |
| # If it's a type 1 with a type shadow like [1, [11,...]] or [1, [12,...]] -> preserve | |
| if input_data[0] == 1 and isinstance(input_data[1], list) and input_data[1][0] in [11, 12]: | |
| processed_block["inputs"][input_name] = input_data | |
| continue | |
| # if it's [1, [shadow_type, value]] then re-evaluate shadow type | |
| if input_data[0] == 1 and isinstance(input_data[1], list) and len(input_data[1]) == 2: | |
| shadow_type = input_type_mapping.get(input_name, 4) | |
| if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers": | |
| shadow_type = 7 | |
| if input_name == "VALUE" and opcode == "data_setvariableto": | |
| shadow_type = 10 | |
| processed_block["inputs"][input_name] = [input_data[0], [shadow_type, str(input_data[1][1])]] | |
| continue | |
| # fallback: preserve the list as-is | |
| processed_block["inputs"][input_name] = input_data | |
| continue | |
| else: | |
| # other unexpected list shapes -> preserve for now | |
| processed_block["inputs"][input_name] = input_data | |
| continue | |
| # If input_data didn't match any of the above -> try to fallback to original generated value if available | |
| processed_block["inputs"][input_name] = gen_block_data.get("inputs", {}).get(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 == "LIST" and isinstance(field_value, list) and len(field_value) > 0: | |
| # Generate or retrieve a unique ID for the list | |
| list_name = field_value[0] | |
| unique_id = variable_id_map[list_name] # Using variable_id_map for lists too | |
| processed_block["fields"][field_name] = [ | |
| list_name, | |
| unique_id | |
| ] | |
| elif field_name == "BROADCAST_OPTION" and isinstance(field_value, list) and len(field_value) > 0: | |
| # Generate or retrieve a unique ID for the broadcast message | |
| broadcast_name = field_value[0] | |
| unique_id = broadcast_id_map[broadcast_name] | |
| processed_block["fields"][field_name] = [ | |
| broadcast_name, | |
| unique_id | |
| ] | |
| elif field_name == "STOP_OPTION": | |
| processed_block["fields"][field_name] = [ | |
| field_value[0], | |
| None | |
| ] | |
| elif field_name == "TOUCHINGOBJECTMENU": | |
| # This logic assumes TOUCHINGOBJECTMENU in fields refers to a block ID in inputs | |
| # The original `block_correcter.py` had a specific way to handle this. | |
| # We need to ensure it correctly pulls the value from the referenced block. | |
| # If the input is a direct value, it should be handled there. | |
| # If it's a reference to another block, we need to look up that block. | |
| # Let's assume the input handling takes care of the direct value. | |
| # If this field is populated by a menu block, we should get its value. | |
| # Check if TOUCHINGOBJECTMENU input exists and is a block reference | |
| referenced_block_id = None | |
| if "TOUCHINGOBJECTMENU" in all_gen_block_data.get("inputs", {}): | |
| input_val = all_gen_block_data["inputs"]["TOUCHINGOBJECTMENU"] | |
| if isinstance(input_val, list) and len(input_val) > 1 and isinstance(input_val[1], str): | |
| referenced_block_id = input_val[1] | |
| elif isinstance(input_val, dict) and input_val.get("kind") == "block": | |
| referenced_block_id = input_val.get("block") | |
| if referenced_block_id and referenced_block_id in all_generated_blocks: | |
| menu_block = all_generated_blocks[referenced_block_id] | |
| # Assuming the menu block itself has a field named TOUCHINGOBJECTMENU | |
| menu_value = menu_block.get("fields", {}).get("TOUCHINGOBJECTMENU", ["", None])[0] | |
| processed_block["fields"][field_name] = [menu_value, None] | |
| else: | |
| # Fallback if no valid reference or if it's a direct field value | |
| processed_block["fields"][field_name] = field_value | |
| else: | |
| processed_block["fields"][field_name] = field_value | |
| if opcode == "control_stop": | |
| stop_option = processed_block["fields"].get("STOP_OPTION", ["", None])[0] | |
| hasnext_val = "true" if stop_option == "other scripts in sprite" else "false" | |
| if "mutation" not in processed_block: | |
| processed_block["mutation"] = { | |
| "tagName": "mutation", | |
| "children": [], | |
| "hasnext": hasnext_val | |
| } | |
| else: | |
| processed_block["mutation"]["hasnext"] = hasnext_val | |
| # Remove unwanted keys from the processed block (if somehow present) | |
| 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[str, Any], opcode_count: Dict[str, list]) -> Tuple[Dict[str, Any], Dict[str, list]]: | |
| """ | |
| Replace each block key in block_json and each identifier in opcode_count | |
| with a newly generated secure token, ensuring all references are updated. | |
| 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. | |
| """ | |
| token_map = {} | |
| # Step 1: Generate a secure token mapping for every existing block key | |
| for old_key in block_json.keys(): | |
| while True: | |
| new_key = generate_secure_token() | |
| if new_key not in token_map.values(): | |
| break | |
| token_map[old_key] = new_key | |
| new_block_json = {} | |
| # Step 2: Rebuild block_json with new keys and update all references | |
| 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 that reference blocks | |
| if 'inputs' in block: | |
| for inp_key, inp_val in block['inputs'].items(): | |
| # This handles references like [2, 'block_key'] | |
| if isinstance(inp_val, list) and len(inp_val) == 2: | |
| idx, ref = inp_val | |
| if isinstance(ref, str) and ref in token_map: | |
| new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]] | |
| # This new block handles references like [3, 'block_key', [10, '']] | |
| elif isinstance(inp_val, list) and len(inp_val) == 3: | |
| idx, ref, rest = inp_val | |
| if isinstance(ref, str) and ref in token_map: | |
| new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref], rest] | |
| # 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: | |
| if value_input[1][0] == 10: | |
| 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: | |
| initial_value = str(value_input[2][1]) | |
| elif isinstance(value_input[1], (str, int, float)): | |
| initial_value = str(value_input[1]) | |
| stage_target["variables"][var_id] = [var_name, initial_value] | |
| for key, value in obj.items(): | |
| # Process broadcast definitions in 'inputs' (BROADCAST_INPUT) | |
| if 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] | |
| 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] | |
| 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] | |
| if var_id not in stage_target["variables"]: | |
| stage_target["variables"][var_id] = [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) | |
| print("declare_variable_json------->",declare_variable_json) | |
| except Exception as e: | |
| print(f"Error error in the variable initialization opcodes: {e}") | |
| try: | |
| processed_json= deduplicate_variables(declare_variable_json) | |
| print("processed_json------->",processed_json) | |
| return processed_json | |
| except Exception as e: | |
| print(f"Error error in the variable initialization opcodes: {e}") | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[Helper function to generate Opcode]-------------------------------------------------------------------------- | |
| ################################################################################################################################################################# | |
| def _find_all_opcodes(code_block: str) -> list[str]: | |
| """ | |
| Finds all Scratch opcodes in a given code block using a series of | |
| regex patterns. This function is designed to handle multi-line blocks | |
| by processing the entire code block and finding all matches. The | |
| patterns are ordered from most specific to least specific to prevent | |
| misclassification. | |
| Args: | |
| code_block: A string containing the entire pseudo-code. | |
| Returns: | |
| A list of all detected opcode strings. | |
| """ | |
| opcodes = [] | |
| # Define a list of regex patterns and their corresponding opcodes, | |
| # ordered from most specific to least specific. The re.DOTALL flag | |
| # allows '.' to match newlines, which is crucial for multi-line blocks. | |
| patterns = [ | |
| # --- Multi-line Control Blocks (most specific, non-greedy) --- | |
| (r"if <.+?> then(?:.|\n)+?else(?:.|\n)+?end", "control_if_else"), #(to test muliple stack) | |
| # (r"if <.+?> then", "control_if"), | |
| (r"if <.+?> then(?:(?!else).|\n)+?end", "control_if"), | |
| (r"forever", "control_forever"), | |
| (r"repeat until <.+?>", "control_repeat_until"), | |
| (r"repeat\s+(?:\(.+?\)|\[.+?(?:\s+v)?\]|\S+)", "control_repeat"), | |
| # (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])", "control_stop"), | |
| (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])(?!\s+sounds)", "control_stop"), | |
| (r"when I start as a clone", "control_start_as_clone"), | |
| (r"create clone of \[.+?(?:\s+v)?\]", "control_create_clone_of"), | |
| (r"delete this clone", "control_delete_this_clone"), | |
| (r"wait\s+(?:\(.+?\)|\[.+?(?:\s+v)?\]|\S+)\s+sec(?:ond)?s?", "control_wait"), | |
| (r"wait until <.+?>", "control_wait_until"), | |
| # --- Event Blocks (most specific) --- | |
| # (r"when green flag clicked", "event_whenflagclicked"), | |
| (r"when (green )?flag click(ed)?", "event_whenflagclicked"), | |
| (r"when\s+(?:key\s+\[(.+?)(?:\s+v)?\]|\[(.+?)(?:\s+v)?\]\s+key)\s+pressed", "event_whenkeypressed"), | |
| (r"when this sprite clicked", "event_whenthisspriteclicked"), | |
| (r"when backdrop switches to \[.+?(?:\s+v)?\]", "event_whenbackdropswitchesto"), | |
| (r"when I receive \[.+?(?:\s+v)?\]", "event_whenbroadcastreceived"), | |
| (r"when \[.+?(?:\s+v)?\] > (.+)", "event_whengreaterthan"), | |
| (r"broadcast \[.+?(?:\s+v)?\] and wait", "event_broadcastandwait"), | |
| (r"broadcast \[.+?(?:\s+v)?\]", "event_broadcast"), | |
| # --- Data Blocks (Variables and Lists) - specific block types first --- | |
| (r"set\s*\[\s*.+?(?:\s+v)?\s*\]\s*to\s*\(?\s*.+?\s*\)?", "data_setvariableto"), | |
| (r"change\s*\[\s*.+?(?:\s+v)?\s*\]\s*by\s*\(?\s*.+?\s*\)?", "data_changevariableby"), | |
| (r"show variable \[.+?(?:\s+v)?\]", "data_showvariable"), | |
| (r"hide variable \[.+?(?:\s+v)?\]", "data_hidevariable"), | |
| (r"show list \[.+?(?:\s+v)?\]", "data_showlist"), | |
| (r"hide list \[.+?(?:\s+v)?\]", "data_hidelist"), | |
| (r"add\s+(?:\[.+?\]|\(.+?\)|\w+)\s+to\s+\[.+?(?:\s+v)?\]", "data_addtolist"), | |
| (r"delete\s*\((?!all\)).+?\)\s*of\s*\[.+?(?:\s+v)?\]", "data_deleteoflist"), | |
| (r"delete\s*\(all\)\s*of\s*\[.+?(?:\s+v)?\]", "data_deletealloflist"), | |
| # (r"insert (.+?) at (\(.+?\)|\[.+?\]|\(\[.+?\]\)) of \[.+?(?:\s+v)?\]", "data_insertatlist"), | |
| (r"insert\s+(\(.+?\)|\[.+?\]|\(\[.+?\]\)|[^\s]+)\s+at\s+(\(.+?\)|\[.+?\]|\(\[.+?\]\)|\d+)\s+of\s+\[.+?(?:\s+v)?\]", "data_insertatlist"), | |
| # (r"replace item (.+?) of \[.+?(?:\s+v)?\] with (.+)", "data_replaceitemoflist"), | |
| # (r"replace item\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|.+?)\s+of\s+\[.+?(?:\s+v)?\]\s+with\s+(.+)", "data_replaceitemoflist"), | |
| (r"replace\s+item\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|[^\s]+)\s+of\s+\[.+?(?:\s+v)?\]\s+with\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|.+)","data_replaceitemoflist"), | |
| # (r"\[.+?(?:\s+v)?\] contains \[.+?\]\?", "data_listcontainsitem"), | |
| # (r"<?\s*\[.+?(?:\s+v)?\] contains \(?\[.+?(?:\s+v)?\]\)?\s*\?>?", "data_listcontainsitem"), | |
| (r"[<(]\s*\[[^\]]+?\s+v\]\s*contains\s*\[[^\]]+?\]\s*\??\s*[)>]", "data_listcontainsitem"), | |
| # (r"\(item \# of (.+?) in \[.+?(?:\s+v)?\]\)", "data_itemnumoflist"), | |
| (r"\(item\s+#\s+of\s+\(?(.+?)\)?\s+in\s+\[.+?(?:\s+v)?\]\)", "data_itemnumoflist"), | |
| # (r"\(item (.+?) of \[.+?(?:\s+v)?\]\)", "data_itemoflist"), | |
| # (r"\(?item\s+(.+?)\s+of\s+\[.+?(?:\s+v)?\]\)?", "data_itemoflist"), | |
| # (r"(?<!replace\s)\(?item\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|[^\s]+)\s+of\s+\[.+?(?:\s+v)?\]\)?", "data_itemoflist"), | |
| (r"(?<!replace\s)\(?item(?!\s+#)\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|[^\s]+)\s+of\s+\[.+?(?:\s+v)?\]\)?", "data_itemoflist"), | |
| (r"\(length of \[.+?(?:\s+v)?\]\)", "data_lengthoflist"), | |
| # --- Sensing Blocks --- | |
| (r"ask \[.+?\] and wait", "sensing_askandwait"), | |
| # (r"key \[.+? v\] pressed\??", "sensing_keypressed"), | |
| (r"(?<!when\s)key\s+\[.+?(?:\s+v)?\]\s+pressed\??", "sensing_keypressed"), | |
| (r"mouse down\??", "sensing_mousedown"), | |
| # (r"color #\w{6} is touching #\w{6}\??", "sensing_coloristouchingcolor"), | |
| (r"""\s*<?\s*color\s*\[?\s*(#[0-9A-Fa-f]{6})\s*\]?\s*is\s+touching\s*\[?\s*(#[0-9A-Fa-f]{6})\s*\]?\s*\??\s*>?\s*""", "sensing_coloristouchingcolor"), | |
| # (r"touching color #\w{6}\??", "sensing_touchingcolor"), | |
| # (r"touching\s*(?:color\s*)?#\w{6}\??", "sensing_touchingcolor"), | |
| (r"(?<!is\s)touching\s*(?:color\s*)?\[?\s*#([0-9A-Fa-f]{6})\s*\]?\??", "sensing_touchingcolor"), | |
| (r"touching \[.+? v\]\??", "sensing_touchingobject"), | |
| (r"set drag mode \[.+? v\]", "sensing_setdragmode"), | |
| (r"reset timer", "sensing_resettimer"), | |
| # (r"(?<!at\s)(?<!item\s)\(.+? of \[.+? v]\)", "sensing_of"), | |
| #(r"(?<!at\s)(?<!item\s)(?<!item\s)(?<!delete\s)[(].+? of \[.+? v\]", "sensing_of"), | |
| # (r"(?<!at\s)(?<!delete\s)\((?!item\s*(?:\(|#))\s.+? of \[.+? v\]", "sensing_of"), | |
| #(r"""(?ix)(?<!\bat\s)(?<!\bdelete\s)(?<!\bitem\s)(?<!\binsert\s)(?<!\bcreate\s)(?:\(\s*(?!item\b|\d|\#)[^)]+?\)\s*of\s*\[.+? v\]|\[\s*(?!item\b|\d|\#)[^\]]+?\]\s*of\s*\[.+? v|(?!item\b|\d|\#)[A-Za-z][^\(\[\n]+?\s*of\s*\[.+? v\])""", "sensing_of"), | |
| (r"(?i)(?<!\bat\s)(?<!\bdelete\s)(?<!\binsert\s)(?<!\breplace\s)(?<!\bcreate\s)(?<!\bitem\s)(?<!\bletter\s)"r"(?:"r"\(\s*(?!(?:item\b|\d+|\#|length\b|insert\b|delete\b|replace\b|create\b|letter\b))[^()]{1,200}?\)\s*of\s*\[[^\]]+?(?:\s+v)?\](?!\s*in\s*\[)"r"|"r"\(\s*(?!(?:item\b|\d+|\#|length\b|insert\b|delete\b|replace\b|create\b|letter\b)).*?of\s*\[[^\]]+?(?:\s+v)?\].*?\)"r"|"r"\[\s*(?!(?:item\b|\d+|\#|length\b|letter\b))[^\]]+?\]\s*of\s*\[[^\]]+?(?:\s+v)?\](?!\s*in\s*\[)"r"|"r"\b(?:backdrop|costume|x\s+position|y\s+position|direction|size|volume|loudness|answer|day|month|year|username|timer|mouse\s+x|mouse\s+y)\b\s*of\s*\[[^\]]+?(?:\s+v)?\](?!\s*in\s*\[)"r")","sensing_of"), | |
| #(r"(?<!at\s)(?<!delete\s)\((?!item\s*(?:\(|#)).+?\)\s*of \[.+? v\]", "sensing_of"), | |
| (r"\(current \[.+? v]\)", "sensing_current"), | |
| (r"\(?answer\)?", "sensing_answer"), #(to test muliple bracket and alone should treet as the keyword) | |
| (r"\(?username\)?", "sensing_username"), #(to test muliple bracket and alone should treet as the keyword) | |
| # commented for the mouse operations | |
| (r"distance\s*to\s*[\[\(]\s*([^\]\)]+?)\s*v?\s*[\]\)]", "sensing_distanceto"), | |
| # (r"(?:<\s*)?mouse\s*down\?\s*(?:>)?", "sensing_mousedown"), | |
| # (r"(?:[\[\(])?\s*mouse\s*x\s*(?:v)?\s*(?:[\]\)])?", "sensing_mousex"), | |
| # (r"(?:[\[\(])?\s*mouse\s*y\s*(?:v)?\s*(?:[\]\)])?", "sensing_mousey"), | |
| # --- Sound Blocks --- | |
| # (r"play sound \[.+? v\] until done", "sound_playuntildone"), | |
| # (r"start sound \[.+? v\]", "sound_play"), | |
| (r"play sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)\s+until done", "sound_playuntildone"), | |
| (r"start sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)", "sound_play"), | |
| (r"stop all sounds", "sound_stopallsounds"), | |
| # (r"change volume by (.+?)", "sound_changevolumeby"), | |
| (r"change volume by\s*(?:\((.+?)\)|\[(.+?)\]|(.+))", "sound_changevolumeby"), | |
| # (r"set volume to (.+?) %", "sound_setvolumeto"), | |
| (r"""set\ volume\ to\s+\(?\s*(?:-?\d+(?:\.\d+)?|\[?[a-zA-Z_][\w\s]*\]?(?:\ v)?)\s*\)?\s*%?""", "sound_setvolumeto"), | |
| (r"\(volume\)", "sound_volume"), | |
| # --- Motion Blocks --- | |
| # (r"go to x: (.+?) y: (.+)", "motion_gotoxy"), | |
| (r"go to x:\s*\(?(.+?)\)?\s*y:\s*\(?(.+?)\)?", "motion_gotoxy"), | |
| # (r"set x to (.+)", "motion_setx"), | |
| (r"set x to (.+)", "motion_setx"), | |
| (r"set y to (.+)", "motion_sety"), | |
| # (r"move (.+?) steps", "motion_movesteps"), | |
| (r"move\s*\(?(.+?)\)?\s*(?:steps?)?", "motion_movesteps"), | |
| # (r"turn right (.+?) degrees", "motion_turnright"), | |
| (r"turn right\s*\(?(.+?)\)?\s*(?:degrees?)?", "motion_turnright"), | |
| # (r"turn left (.+?) degrees", "motion_turnleft"), | |
| (r"turn left\s*\(?(.+?)\)?\s*(?:degrees?)?", "motion_turnleft"), | |
| (r"go to\s*(?:random position|mouse-pointer|\[.*?\]|.+)", "motion_goto"), #(to mouse-pointer is not include here for now) | |
| # (r"point in direction (.+)", "motion_pointindirection"), | |
| (r"point in direction\s*\(?(.+?)\)?", "motion_pointindirection"), | |
| (r"point towards \[.+? v\]", "motion_pointtowards"), | |
| # (r"change x by (.+)", "motion_changexby"), | |
| # (r"change y by (.+)", "motion_changeyby"), | |
| (r"change x by\s*\(?(.+?)\)?", "motion_changexby"), | |
| (r"change y by\s*\(?(.+?)\)?", "motion_changeyby"), | |
| # (r"glide (.+?) secs to x: (.+?) y: (.+)", "motion_glidesecstoxy"), | |
| # (r"glide (.+?) secs to \[.+? v\]", "motion_glideto"), | |
| (r"glide\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)\s*to\s*x:\s*\(?(.+?)\)?\s*y:\s*\(?(.+?)\)?", "motion_glidesecstoxy"), | |
| (r"glide\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)\s*to\s*\[.*?\]", "motion_glideto"), | |
| (r"if on edge, bounce", "motion_ifonedgebounce"), | |
| # (r"set rotation style \[.+? v\]", "motion_setrotationstyle"), | |
| (r"set rotation style\s*\[(?:left-right|all around|don't rotate)(?:\s*v)?\]", "motion_setrotationstyle"), | |
| (r"\(?x position\)?", "motion_xposition"), #(to x positon may detect where var is used) | |
| (r"\(?y position\)?", "motion_yposition"), #(to y position may detect where var is used) | |
| (r"\(?direction\)?", "motion_direction"), #(to direction may detect where var is used) | |
| # --- Looks Blocks --- | |
| (r"switch costume to \[.+? v\]", "looks_switchcostumeto"), | |
| (r"next costume", "looks_nextcostume"), | |
| (r"switch backdrop to \[.+? v\] and wait", "looks_switchbackdroptowait"), | |
| (r"switch backdrop to \[.+? v\]", "looks_switchbackdropto"), | |
| (r"next backdrop", "looks_nextbackdrop"), | |
| (r"^\s*show\s*$", "looks_show"), | |
| (r"^\s*hide\s*$", "looks_hide"), | |
| # (r"say \[.+?\] for (.+?) seconds", "looks_sayforsecs"), | |
| # (r"say \[.+?\]", "looks_say"), | |
| # (r"think \[.+?\] for (.+?) seconds", "looks_thinkforsecs"), | |
| # (r"think \[.+?\]", "looks_think"), | |
| # (r"change size by (.+)", "looks_changesizeby"), | |
| # (r"set size to (.+?) %", "looks_setsizeto"), | |
| # (r"say\s*\[.+?\]\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_sayforsecs"), | |
| (r"say\s+(?:\[.+?\]|\(.+?\)|.+?)\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_sayforsecs"), | |
| # (r"say\s*\[.+?\]", "looks_say"), | |
| (r"say\s+(?!.*\bfor\b\s*\(?\d+\)?\s*(?:sec|secs|second|seconds))(?:\[.+?\]|\(.+?\)|.+?)", "looks_say"), | |
| (r"think\s*\[.+?\]\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_thinkforsecs"), | |
| (r"think\s*\[.+?\]", "looks_think"), | |
| (r"change size by\s*\(?(.+?)\)?", "looks_changesizeby"), | |
| # (r"set size to\s*\(?(.+?)\)?\s*%?", "looks_setsizeto"), | |
| (r"set size to\s*\(?(.+?)\)?\s*%?", "looks_setsizeto"), | |
| # (r"change \[.+? v\] effect by (.+)", "looks_changeeffectby"), | |
| # (r"set \[.+? v\] effect to (.+)", "looks_seteffectto"), | |
| (r"change\s*\[(.+?)(?:\s*v)?\]\s*effect by\s*\(?(.+?)\)?", "looks_changeeffectby"), | |
| (r"set\s*\[(.+?)(?:\s*v)?\]\s*effect to\s*\(?(.+?)\)?", "looks_seteffectto"), | |
| (r"clear graphic effects", "looks_cleargraphiceffects"), | |
| (r"\(costume \[.+? v\]\)", "looks_costumenumbername"), | |
| (r"\(backdrop \[.+? v\]\)", "looks_backdropnumbername"), | |
| # --- Operators --- | |
| # (r"<.+?\s*<\s*.+?>", "operator_lt"), | |
| # (r"<.+?\s*=\s*.+?>", "operator_equals"), | |
| # (r"<.+?\s*>\s*.+?>", "operator_gt"), | |
| # (r"<.+? and .+?>", "operator_and"), | |
| # (r"<.+? or .+?>", "operator_or"), | |
| # (r"<not .+?>", "operator_not"), | |
| # (r"\(join (.+?) (.+?)\)", "operator_join"), | |
| # (r"\(.+?\s*\+\s*.+?\)", "operator_add"), | |
| # (r"\(.+?\s*-\s*.+?\)", "operator_subtract"), | |
| # (r"\(.+?\s*\*\s*.+?\)", "operator_multiply"), | |
| # (r"\(.+?\s*/\s*.+?\)", "operator_divide"), | |
| # (r"\(pick random (.+?) to (.+?)\)", "operator_random"), | |
| # (r"\(letter (.+?) of (.+?)\)", "operator_letterof"), | |
| # (r"\(length of (.+?)\)", "operator_length"), | |
| # (r"\(.+? mod (.+?)\)", "operator_mod"), | |
| # (r"\(round (.+?)\)", "operator_round"), | |
| # (r"\(.+? contains (.+?)\)", "operator_contains"), | |
| # (r"\(.+? of (.+?)\)", "operator_mathop"), | |
| # (r"<\s*[^<>]+\s*<\s*[^<>]+\s*>", "operator_lt"), | |
| # (r"<\s*[^<>]+\s*=\s*[^<>]+\s*>", "operator_equals"), | |
| # (r"<\s*[^<>]+\s*>\s*[^<>]+\s*>", "operator_gt"), | |
| (r"<\s*[^<>?]+\s*<\s*[^<>?]+\s*>", "operator_lt"), | |
| (r"<\s*[^<>?]+\s*=\s*[^<>?]+\s*>", "operator_equals"), | |
| (r"<\s*[^<>?]+\s*>\s*[^<>?]+\s*>", "operator_gt"), | |
| (r"<\s*.*?\s+and\s+.*?\s*>", "operator_and"), | |
| (r"<\s*.*?\s+or\s+.*?\s*>", "operator_or"), | |
| (r"<\s*not\s+.*?\s*>", "operator_not"), | |
| # (r"\(join\s+(.+?)\s+(.+?)\)", "operator_join"), | |
| (r"(?:\(join\s+(.+?)\s+(.+?)\)|join\s+(.+?)\s+(.+?))", "operator_join"), | |
| # (r"\(\s*[^()]+\s*\+\s*[^()]+\s*\)", "operator_add"), | |
| # (r"\(\s*[^()]+\s*-\s*[^()]+\s*\)", "operator_subtract"), | |
| # (r"\(\s*[^()]+\s*\*\s*[^()]+\s*\)", "operator_multiply"), | |
| # (r"\(\s*[^()]+\s*/\s*[^()]+\s*\)", "operator_divide"), | |
| # Allow nested expressions inside () | |
| (r"\(\s*.+?\s*\+\s*.+?\s*\)", "operator_add"), | |
| # (r"\(\s*.+?\s*-\s*.+?\s*\)", "operator_subtract"), | |
| (r"\(\s*(?!-\s*\d+(?:\.\d+)?\s*\))(.+?)\s+-\s+(.+?)\)", "operator_subtract"), | |
| (r"\(\s*.+?\s*\*\s*.+?\s*\)", "operator_multiply"), | |
| (r"\(\s*.+?\s*/\s*.+?\s*\)", "operator_divide"), | |
| (r"\(pick random\s+(.+?)\s+to\s+(.+?)\)", "operator_random"), | |
| (r"\(letter\s+(.+?)\s+of\s+(.+?)\)", "operator_letterof"), | |
| (r"\(length of\s+(.+?)\)", "operator_length"), | |
| # (r"\(\s*[^()]+\s+mod\s+[^()]+\s*\)", "operator_mod"), | |
| (r"\(\s*.+?\s+mod\s+.+?\s*\)", "operator_mod"), | |
| (r"\(round\s+(.+?)\)", "operator_round"), | |
| # (r"\(\s*[^()]+\s+contains\s+[^()]+\s*\)", "operator_contains"), | |
| (r"[<(]\s*\[(?![^\]]*\s+v\])[^\]]+?\]\s*contains\s*\[[^\]]+?\]\s*\??\s*[)>]", "operator_contains"), | |
| # (r"\(\s*[^()]+\s+of\s+[^()]+\s*\)", "operator_mathop"), | |
| (r"\(\s*\[?(abs|floor|ceiling|sqrt|sin|cos|tan|asin|acos|atan|ln|log|e \^|10 \^)\s*(?:v)?\]?\s+of\s+.+?\)", "operator_mathop"), | |
| ] | |
| for pattern, opcode in patterns: | |
| for match in re.finditer(pattern, code_block, re.DOTALL): | |
| opcodes.append(opcode) | |
| return opcodes | |
| def analyze_opcode_counts(pseudo_code: str) -> list[dict]: | |
| """ | |
| Analyzes a block of Scratch-like pseudo-code to count the occurrences | |
| of each opcode using a multi-pass, regex-based classifier. | |
| Args: | |
| pseudo_code: A string containing the pseudo-code. | |
| Returns: | |
| A list of dictionaries, where each dictionary contains the opcode | |
| and its count. | |
| """ | |
| opcode_counts = Counter() | |
| opcodes_in_code = _find_all_opcodes(pseudo_code) | |
| for opcode in opcodes_in_code: | |
| opcode_counts[opcode] += 1 | |
| result = [{"opcode": opcode, "count": count} for opcode, count in opcode_counts.items()] | |
| # Sort the result by opcode for consistent output. | |
| result.sort(key=lambda x: x['opcode']) | |
| return result | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[Helper function to seperate an correct the json]------------------------------------------------------------- | |
| ################################################################################################################################################################# | |
| def separate_scripts(pseudocode_string): | |
| """ | |
| Separates a block of Scratch pseudocode into a list of individual scripts. | |
| This function finds the start of each "hat" block and slices the | |
| original string to capture the full code block for each script, | |
| providing a more robust and reliable separation. | |
| Args: | |
| pseudocode_string (str): A string containing Scratch pseudocode. | |
| Returns: | |
| list: A list of strings, where each string is a complete, | |
| separated script. | |
| """ | |
| # Define the "hat" block patterns with more robust regex. | |
| # We use a non-capturing group (?:...) for the patterns. | |
| # We use a logical OR (|) to combine them into a single pattern. | |
| delimiter_patterns = ( | |
| r"when green flag clicked|when flag clicked|when \S+ key pressed|" | |
| r"when this sprite clicked|when backdrop switches to \[.*?\]|" | |
| r"when I receive \[.*?\]|when \[.*?\] > \[.*?\]" | |
| ) | |
| # Use re.finditer to get an iterator of all hat block matches. | |
| # The `re.DOTALL` flag allows the '.' to match newlines. | |
| matches = list(re.finditer(delimiter_patterns, pseudocode_string, flags=re.DOTALL | re.IGNORECASE)) | |
| scripts = [] | |
| # If no matches are found, return an empty list. | |
| if not matches: | |
| return [] | |
| # Iterate through the matches to slice the original string. | |
| for i in range(len(matches)): | |
| start = matches[i].start() | |
| end = matches[i+1].start() if i + 1 < len(matches) else len(pseudocode_string) | |
| # Slice the pseudocode string from the start of one match to the start | |
| # of the next, or to the end of the string. | |
| script = pseudocode_string[start:end] | |
| scripts.append(script.strip()) | |
| return scripts | |
| def transform_logic_to_action_flow(source_data, description=""): | |
| """ | |
| Transforms a 'refined_logic' JSON structure into an 'action_overall_flow' structure. | |
| Args: | |
| source_data (dict): The input dictionary with 'refined_logic', 'name_variable', | |
| and 'pseudocode' keys. | |
| description (str): A description to be added to the output structure. | |
| Returns: | |
| dict: A dictionary in the desired 'action_overall_flow' format. | |
| """ | |
| # Check if the required keys exist in the source data | |
| if "refined_logic" not in source_data or \ | |
| "name_variable" not in source_data["refined_logic"] or \ | |
| "pseudocode" not in source_data["refined_logic"]: | |
| raise ValueError("Input dictionary is missing required keys: 'refined_logic', 'name_variable', or 'pseudocode'.") | |
| # Extract the name and the pseudocode list from the source data | |
| name_variable = source_data["refined_logic"]["name_variable"] | |
| pseudocode_list = source_data["refined_logic"]["pseudocode"] | |
| # Transform the list of pseudocode strings into a list of dictionaries | |
| # with the "logic" key. | |
| plans_list = [{"logic": logic_block} for logic_block in pseudocode_list] | |
| # Construct the final nested dictionary structure | |
| transformed_data = { | |
| "action_overall_flow": { | |
| name_variable: { | |
| "description": description, | |
| "plans": plans_list | |
| } | |
| } | |
| } | |
| return transformed_data | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[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 | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[Helper function to for OCR handling]-------------------------------------------------------------------------- | |
| ################################################################################################################################################################# | |
| def canonicalize(s: str) -> str: | |
| s = s.strip().lower() | |
| s = re.sub(r'^[\[\(\{\'"]+|[\]\)\}\'"]+$', '', s) | |
| s = re.sub(r'[^a-z0-9]+', '-', s) | |
| s = s.strip('-') | |
| return s | |
| def pluralize_word_token(token: str): | |
| forms = set() | |
| if not token: | |
| return forms | |
| forms.add(token + 's') | |
| if token.endswith('y') and len(token) > 1 and token[-2] not in 'aeiou': | |
| forms.add(token[:-1] + 'ies') | |
| if token.endswith('f'): | |
| forms.add(token[:-1] + 'ves') | |
| if token.endswith('fe'): | |
| forms.add(token[:-2] + 'ves') | |
| if token.endswith('o'): | |
| forms.add(token + 'es') | |
| forms.add(token[:-1] + 'oes') | |
| if token.endswith('us'): | |
| forms.add(token[:-2] + 'i') | |
| if token.endswith('is'): | |
| forms.add(token[:-2] + 'es') | |
| if token.endswith(('s','x','z','ch','sh')): | |
| forms.add(token + 'es') | |
| return forms | |
| def plural_variants_for_keyword(keyword: str): | |
| parts = re.split(r'(\s+|-)', keyword) | |
| for i in range(len(parts)-1, -1, -1): | |
| if re.search(r'[A-Za-z0-9]', parts[i]): | |
| last_idx = i | |
| break | |
| else: | |
| return [] | |
| token = re.sub(r'[^A-Za-z0-9]', '', parts[last_idx]) | |
| if not token: | |
| return [] | |
| plurals = pluralize_word_token(token.lower()) | |
| variants = [] | |
| for p in plurals: | |
| new_parts = parts.copy() | |
| new_parts[last_idx] = re.sub(r'[A-Za-z0-9]+', p, new_parts[last_idx], count=1, flags=re.IGNORECASE) | |
| variants.append(''.join(new_parts)) | |
| return variants | |
| def is_valid_word(text: str): | |
| if d is None: | |
| return None | |
| cleaned = re.sub(r'[^a-z]', '', text.lower()) | |
| if not cleaned: | |
| return False | |
| return d.check(cleaned) | |
| # Build canonical_map where each value is a list of (original_keyword, category) | |
| def build_keyword_maps(sprite_keywords, backdrop_keywords, sound_keywords=None): | |
| all_entries = [] | |
| all_entries.extend([(kw, 'sprite') for kw in sprite_keywords]) | |
| all_entries.extend([(kw, 'backdrop') for kw in backdrop_keywords]) | |
| if sound_keywords: | |
| all_entries.extend([(kw, 'sound') for kw in sound_keywords]) | |
| canonical_map = {} # canonical -> list of (keyword, category) | |
| canonical_list = [] # list of canonical keys (for difflib) | |
| for kw, category in all_entries: | |
| ck = canonicalize(kw) | |
| if ck not in canonical_map: | |
| canonical_map[ck] = [] | |
| canonical_list.append(ck) | |
| # keep unique (keyword, category) | |
| if (kw, category) not in canonical_map[ck]: | |
| canonical_map[ck].append((kw, category)) | |
| # add plural variants mapping to same original keyword/category | |
| for variant in plural_variants_for_keyword(kw): | |
| vck = canonicalize(variant) | |
| if vck not in canonical_map: | |
| canonical_map[vck] = [] | |
| canonical_list.append(vck) | |
| if (kw, category) not in canonical_map[vck]: | |
| canonical_map[vck].append((kw, category)) | |
| return canonical_map, canonical_list | |
| # priority chooser when multiple (kw, category) exist for same canonical form | |
| def choose_preferred(kc_list): | |
| # preference order | |
| pref = ('sprite', 'backdrop', 'sound') | |
| for p in pref: | |
| for kw, cat in kc_list: | |
| if cat == p: | |
| return kw, cat | |
| # fallback: return first if none matched preference | |
| return kc_list[0] | |
| def dynamic_cutoff(text: str): | |
| L = len(text) | |
| if L <= 3: | |
| return 0.95 | |
| if L <= 6: | |
| return 0.9 | |
| if L <= 10: | |
| return 0.85 | |
| return 0.75 | |
| def get_best_match(text, canonical_map, canonical_list): | |
| ctext = canonicalize(text) | |
| cutoff = dynamic_cutoff(ctext) | |
| matches = difflib.get_close_matches(ctext, canonical_list, n=1, cutoff=cutoff) | |
| if matches: | |
| kc_list = canonical_map[matches[0]] | |
| kw, cat = choose_preferred(kc_list) | |
| return kw, cat | |
| # attempt singularization fallbacks | |
| for transform in (lambda s: s[:-3] + 'y' if s.endswith('ies') else None, | |
| lambda s: s[:-3] + 'f' if s.endswith('ves') else None, | |
| lambda s: s[:-1] if s.endswith('s') else None): | |
| transformed = transform(ctext) | |
| if transformed: | |
| matches = difflib.get_close_matches(transformed, canonical_list, n=1, cutoff=cutoff) | |
| if matches: | |
| kc_list = canonical_map[matches[0]] | |
| kw, cat = choose_preferred(kc_list) | |
| return kw, cat | |
| return None | |
| def check_and_print_nearest_matches(text, canonical_map, canonical_list, n=3): | |
| ctext = canonicalize(text) | |
| cutoff = 0.6 | |
| matches = difflib.get_close_matches(ctext, canonical_list, n=n, cutoff=cutoff) | |
| if matches: | |
| suggestions = [] | |
| for m in matches: | |
| suggestions.extend([kw for (kw, cat) in canonical_map[m]]) | |
| print("Did you mean one of these? " + ", ".join(suggestions[:8])) | |
| # ----- Main process_text that returns (matched_keyword, category) ----- | |
| def process_text(text): | |
| # --- INSERT your full sprite_keywords & backdrop_keywords lists here --- | |
| sprite_keywords = [ | |
| "Abby", "abby-a", "abby-b", "abby-c", "abby-d", "Amon", "amon", "Andie", "andie-a", "andie-b", "andie-c", "andie-d", "Anina Dance", "anina stance", "anina top stand", "anina top R step", "anina top L step", "anina top freeze", "anina R cross", "anina pop front", "anina pop down", "anina pop left", "anina pop right", "anina pop L arm", "anina pop stand", "anina pop R arm", "Apple", "apple", "Arrow1", "arrow1-a", "arrow1-b", "arrow1-c", "arrow1-d", "Avery Walking", "avery walking-a", "avery walking-b", "avery walking-c", "avery walking-d", "Avery", "avery-a", "avery-b", "Ball", "ball-a", "ball-b", "ball-c", "ball-d", "ball-e", "Ballerina", "ballerina-a", "ballerina-b", "ballerina-c", "ballerina-d", "Balloon1", "balloon1-a", "balloon1-b", "balloon1-c", "Bananas", "bananas", "Baseball", "baseball", "Basketball", "basketball", "Bat", "bat-a", "bat-b", "bat-c", "bat-d", "Batter", "batter-a", "batter-b", "batter-c", "batter-d", "Beachball", "beachball", "Bear-walking", "bear-walk-a", "bear-walk-b", "bear-walk-c", "bear-walk-d", "bear-walk-e", "bear-walk-f", "bear-walk-g", "bear-walk-h", "Bear", "bear-a", "bear-b", "Beetle", "beetle", "Bell", "bell1", "Ben", "ben-a", "ben-b", "ben-c", "ben-d", "Block-A", "Block-a", "Block-B", "Block-b", "Block-C", "Block-c", "Block-D", "Block-d", "Block-E", "Block-e", "Block-F", "Block-f", "Block-G", "Block-g", "Block-H", "Block-h", "Block-I", "Block-i", "Block-J", "Block-j", "Block-K", "Block-k", "Block-L", "Block-l", "Block-M", "Block-m", "Block-N", "Block-n", "Block-O", "Block-o", "Block-P", "Block-p", "Block-Q", "Block-q", "Block-R", "Block-r", "Block-S", "Block-s", "Block-T", "Block-t", "Block-U", "Block-u", "Block-V", "Block-v", "Block-W", "Block-w", "Block-X", "Block-x", "Block-Y", "Block-y", "Block-Z", "Block-z", "Bowl", "bowl-a", "Bowtie", "bowtie", "Bread", "bread", "Broom", "broom", "Buildings", "building-a", "building-b", "building-c", "building-d", "building-e", "building-f", "building-g", "building-h", "building-i", "building-j", "Butterfly 1", "butterfly1-a", "butterfly1-b", "butterfly1-c", "Butterfly 2", "butterfly2-a", "butterfly2-b", "Button1", "button1", "Button2", "button2-a", "button2-b", "Button3", "button3-a", "button3-b", "Button4", "button4-a", "button4-b", "Button5", "button5-a", "button5-b", "Cake", "cake-a", "cake-b", "Calvrett", "calvrett jumping", "calvrett thinking", "Casey", "casey-a", "casey-b", "casey-c", "casey-d", "Cassy Dance", "cassy-a", "cassy-b", "cassy-c", "cassy-d", "Cat 2", "cat 2", "Cat Flying", "cat flying-a", "cat flying-b", "Cat", "cat-a", "cat-b", "Catcher", "catcher-a", "catcher-b", "catcher-c", "catcher-d", "Centaur", "centaur-a", "centaur-b", "centaur-c", "centaur-d", "Champ99", "champ99-a", "champ99-b", "champ99-c", "champ99-d", "champ99-e", "champ99-f", "champ99-g", "Characters 1", "character1-a", "character1-b", "character1-c", "character1-d", "character1-e", "character1-f", "character1-g", "character1-h", "character1-i", "character1-j", "character1-k", "character1-l", "character1-m", "Characters 2", "character2-a", "character2-b", "character2-c", "character2-d", "character2-e", "character2-f", "character2-g", "character2-h", "character2-i", "character2-j", "Cheesy Puffs", "cheesy puffs", "Chick", "chick-a", "chick-b", "chick-c", "City Bus", "City Bus-a", "City Bus-b", "Cloud", "cloud", "Clouds", "cloud-a", "cloud-b", "cloud-c", "cloud-d", "Convertible 2", "convertible 3", "Convertible", "convertible", "Crab", "crab-a", "crab-b", "Crystal", "crystal-a", "crystal-b", "D-Money Dance", "dm stance", "dm top stand", "dm top R leg", "dm top L leg", "dm freeze", "dm pop front", "dm pop down", "dm pop left", "dm pop right", "dm pop L arm", "dm pop stand", "dm pop R arm", "Dan", "dan-a", "dan-b", "Dani", "Dani-a", "Dani-b", "Dani-c", "Dee", "dee-a", "dee-b", "dee-c", "dee-d", "dee-e", "Devin", "devin-a", "devin-b", "devin-c", "devin-d", "Dinosaur1", "dinosaur1-a", "dinosaur1-b", "dinosaur1-c", "dinosaur1-d", "Dinosaur2", "dinosaur2-a", "dinosaur2-b", "dinosaur2-c", "dinosaur2-d", "Dinosaur3", "dinosaur3-a", "dinosaur3-b", "dinosaur3-c", "dinosaur3-d", "dinosaur3-e", "Dinosaur4", "dinosaur4-a", "dinosaur4-b", "dinosaur4-c", "dinosaur4-d", "Dinosaur5", "Dinosaur5-a", "Dinosaur5-b", "Dinosaur5-c", "Dinosaur5-d", "Dinosaur5-e", "Dinosaur5-f", "Dinosaur5-g", "Dinosaur5-h", "Diver1", "diver1", "Diver2", "diver2", "Dog1", "dog1-a", "dog1-b", "Dog2", "dog2-a", "dog2-b", "dog2-c", "Donut", "donut", "Dorian", "dorian-a", "dorian-b", "dorian-c", "dorian-d", "Dot", "dot-a", "dot-b", "dot-c", "dot-d", "Dove", "dove-a", "dove-b", "Dragon", "dragon-a", "dragon-b", "dragon-c", "Dragonfly", "Dragonfly-a", "Dragonfly-b", "Dress", "dress-a", "dress-b", "dress-c", "Drum Kit", "drum-kit", "drum-kit-b", "Drum-cymbal", "drum-cymbal-a", "drum-cymbal-b", "Drum-highhat", "drum-highhat-a", "drum-highhat-b", "Drum-snare", "drum-snare-a", "drum-snare-b", "Drum", "drum-a", "drum-b", "Drums Conga", "Drums Conga-a", "Drums Conga-b", "Drums Tabla", "Tabla-a", "Tabla-b", "Duck", "duck", "Earth", "earth", "Easel", "Easel-a", "Easel-b", "Easel-c", "Egg", "egg-a", "egg-b", "egg-c", "egg-d", "egg-e", "egg-f", "Elephant", "elephant-a", "elephant-b", "Elf", "elf-a", "elf-b", "elf-c", "elf-d", "elf-e", "Fairy", "fairy-a", "fairy-b", "fairy-c", "fairy-d", "fairy-e", "Fish", "fish-a", "fish-b", "fish-c", "fish-d", "Fishbowl", "Fishbowl-a", "Fishbowl-b", "Food Truck", "Food Truck-a", "Food Truck-b", "Food Truck-c", "Football", "football running", "football standing", "Fortune Cookie", "fortune cookie", "Fox", "fox-a", "fox-b", "fox-c", "Frank", "frank-a", "frank-b", "frank-c", "frank-d", "Frog 2 ", "Frog 2-a", "Frog 2-b", "Frog 2-c", "Frog", "frog", "Fruit Platter", "fruit platter", "Fruit Salad", "fruitsalad", "Ghost", "ghost-a", "ghost-b", "ghost-c", "ghost-d", "Gift", "gift-a", "gift-b", "Giga Walking", "Giga walk1", "Giga walk2", "Giga walk3", "Giga", "giga-a", "giga-b", "giga-c", "giga-d", "Giraffe", "giraffe-a", "giraffe-b", "giraffe-c", "Glass Water", "glass water-a", "glass water-b", "Glasses", "glasses-a", "glasses-b", "glasses-c", "glasses-e", "Glow-0", "Glow-0", "Glow-1", "Glow-1", "Glow-2", "Glow-2", "Glow-3", "Glow-3", "Glow-4", "Glow-4", "Glow-5", "Glow-5", "Glow-6", "Glow-6", "Glow-7", "Glow-7", "Glow-8", "Glow-8", "Glow-9", "Glow-9", "Glow-A", "Glow-A", "Glow-B", "Glow-B", "Glow-C", "Glow-C", "Glow-D", "Glow-D", "Glow-E", "Glow-E", "Glow-F", "Glow-F", "Glow-G", "Glow-G", "Glow-H", "Glow-H", "Glow-I", "Glow-I", "Glow-J", "Glow-J", "Glow-K", "Glow-K", "Glow-L", "Glow-L", "Glow-M", "Glow-M", "Glow-N", "Glow-N", "Glow-O", "Glow-O", "Glow-P", "Glow-P", "Glow-Q", "Glow-Q", "Glow-R", "Glow-R", "Glow-S", "Glow-S", "Glow-T", "Glow-T", "Glow-U", "Glow-U", "Glow-V", "Glow-V", "Glow-W", "Glow-W", "Glow-X", "Glow-X", "Glow-Y", "Glow-Y", "Glow-Z", "Glow-Z", "Goalie", "goalie-a", "goalie-b", "goalie-c", "goalie-d", "goalie-e", "Goblin", "goblin-a", "goblin-b", "goblin-c", "goblin-d", "Gobo", "gobo-a", "gobo-b", "gobo-c", "Grasshopper", "Grasshopper-a", "Grasshopper-b", "Grasshopper-c", "Grasshopper-d", "Grasshopper-e", "Grasshopper-f", "Green Flag", "green flag", "Griffin", "Griffin-a", "Griffin-b", "Griffin-c", "Griffin-d", "Guitar-electric1", "guitar-electric1-a", "guitar-electric1-b", "Guitar-electric2", "guitar-electric2-a", "guitar-electric2-b", "Guitar", "guitar-a", "guitar-b", "Hannah", "hannah-a", "hannah-b", "hannah-c", "Hare", "hare-a", "hare-b", "hare-c", "Harper", "harper-a", "harper-b", "harper-c", "Hat1 ", "hat-a", "hat-b", "hat-c", "hat-d", "Hatchling", "hatchling-a", "hatchling-b", "hatchling-c", "Heart Candy", "heart code", "heart love", "heart sweet", "heart smile", "Heart Face", "heart face", "Heart", "heart red", "heart purple", "Hedgehog", "hedgehog-a", "hedgehog-b", "hedgehog-c", "hedgehog-d", "hedgehog-e", "Hen", "hen-a", "hen-b", "hen-c", "hen-d", "Hippo1", "hippo1-a", "hippo1-b", "Home Button", "home button", "Horse", "horse-a", "horse-b", "Jaime", "jaime-a", "jaime-b", "jaime walking-a", "jaime walking-b", "jaime walking-c", "jaime walking-d", "jaime walking-e", "Jamal", "jamal-a", "jamal-b", "jamal-c", "jamal-d", "Jar", "jar-a", "jar-b", "Jellyfish", "jellyfish-a", "jellyfish-b", "jellyfish-c", "jellyfish-d", "Jordyn", "jordyn-a", "jordyn-b", "jordyn-c", "jordyn-d", "Jouvi Dance", "jo stance", "jo top stand", "jo top R leg", "jo top L leg", "jo top R cross", "jo top L cross", "jo pop front", "jo pop down", "jo pop left", "jo pop right", "jo pop L arm", "jo pop stand", "jo pop R arm", "Kai", "kai-a", "kai-b", "Key", "key", "Keyboard", "keyboard-a", "keyboard-b", "Kia", "Kia-a", "Kia-b", "Kia-c", "Kiran", "kiran-a", "kiran-b", "kiran-c", "kiran-d", "kiran-e", "kiran-f", "Knight", "knight", "Ladybug1", "ladybug2", "Ladybug2", "ladybug2-a", "ladybug2-b", "Laptop", "laptop", "LB Dance", "lb stance", "lb top stand", "lb top R leg", "lb top L leg", "lb top L cross", "lb top R cross", "lb pop front", "lb pop down", "lb pop left", "lb pop right", "lb pop L arm", "lb pop stand", "lb pop R arm", "Lightning", "lightning", "Line", "line", "Lion", "lion-a", "lion-b", "lion-c", "Llama", "llama", "llama-b", "llama-c", "Luca", "Luca-a", "Luca-b", "Luca-c", "Magic Wand", "magicwand", "Marian", "Marian-a", "Marian-b", "Marian-c", "Marian-d", "Marian-e", "Max", "max-a", "max-b", "max-c", "max-d", "Mermaid", "mermaid-a", "mermaid-b", "mermaid-c", "mermaid-d", "Microphone", "microphone-a", "microphone-b", "Milk", "milk-a", "milk-b", "milk-c", "milk-d", "milk-e", "Monet", "monet-a", "monet-b", "monet-c", "monet-d", "monet-e", "Monkey", "monkey-a", "monkey-b", "monkey-c", "Motorcycle", "Motorcycle-a", "Motorcycle-b", "Motorcycle-c", "Motorcycle-d", "Mouse1", "mouse1-a", "mouse1-b", "Muffin", "muffin-a", "muffin-b", "Nano", "nano-a", "nano-b", "nano-c", "nano-d", "Neigh Pony", "neigh pony", "Noor", "Noor-a", "Noor-b", "Noor-c", "Octopus", "octopus-a", "octopus-b", "octopus-c", "octopus-d", "octopus-e", "Orange", "orange", "Orange2", "orange2-a", "orange2-b", "Outfielder", "outfielder-a", "outfielder-b", "outfielder-c", "outfielder-d", "Owl", "owl-a", "owl-b", "owl-c", "Paddle", "paddle", "Panther", "panther-a", "panther-b", "panther-c", "Pants", "pants-a", "pants-b", "Parrot", "parrot-a", "parrot-b", "Party Hats", "Party Hat-a", "Party Hat-b", "Party Hat-e", "Pencil", "pencil-a", "pencil-b", "Penguin 2", "penguin2-a", "penguin2-b", "penguin2-c", "penguin2-d", "Penguin", "penguin-a", "penguin-b", "penguin-c", "Pico Walking", "Pico walk1", "Pico walk2", "Pico walk3", "Pico walk4", "Pico", "pico-a", "pico-b", "pico-c", "pico-d", "Pitcher", "pitcher-a", "pitcher-b", "pitcher-c", "pitcher-d", "Planet2", "planet2", "Polar Bear", "polar bear-a", "polar bear-b", "polar bear-c", "Potion", "potion-a", "potion-b", "potion-c", "Prince", "prince", "Princess", "princess-a", "princess-b", "princess-c", "princess-d", "princess-e", "Pufferfish", "pufferfish-a", "pufferfish-b", "pufferfish-c", "pufferfish-d", "Puppy", "puppy right", "puppy sit", "puppy side", "puppy back", "Rabbit", "rabbit-a", "rabbit-b", "rabbit-c", "rabbit-d", "rabbit-e", "Radio", "Radio-a", "Radio-b", "Rainbow", "rainbow", "Referee", "referee-a", "referee-b", "referee-c", "referee-d", "Reindeer", "reindeer", "Retro Robot", "Retro Robot a", "Retro Robot b", "Retro Robot c", "Ripley", "ripley-a", "ripley-b", "ripley-c", "ripley-d", "ripley-e", "ripley-f", "Robot", "robot-a", "robot-b", "robot-c", "robot-d", "Rocketship", "rocketship-a", "rocketship-b", "rocketship-c", "rocketship-d", "rocketship-e", "Rocks", "rocks", "Rooster", "rooster-a", "rooster-b", "rooster-c", "Ruby", "ruby-a", "ruby-b", "Sailboat", "sailboat", "Sam", "sam", "Sasha", "Sasha-a", "Sasha-b", "Sasha-c", "Saxophone", "saxophone-a", "saxophone-b", "Scarf", "Scarf-a", "Scarf-b", "Scarf-c", "Shark 2", "shark2-a", "shark2-b", "shark2-c", "Shark", "shark-a", "shark-b", "Shirt", "shirt-a", "Shoes", "shoes-a", "shoes-b", "shoes-d", "shoes-c", "Shorts", "shorts-a", "shorts-b", "shorts-c", "Singer1", "Singer1", "Skeleton", "skeleton-a", "skeleton-b", "skeleton-d", "skeleton-e", "Snake", "snake-a", "snake-b", "snake-c", "Snowflake", "snowflake", "Snowman", "snowman", "Soccer Ball", "soccer ball", "Speaker", "speaker", "Squirrel", "squirrel", "Star", "star", "Starfish", "starfish-a", "starfish-b ", "Stop", "stop", "Story-A", "story-A-1", "story-A-2", "story-A-3", "Story-B", "story-B-1", "story-B-2", "story-B-3", "Story-C", "story-C-1", "story-C-2", "story-C-3", "Story-D", "story-D-1", "story-D-2", "story-D-3", "Story-E", "story-E-1", "story-E-2", "story-E-3", "Story-F", "story-F-1", "story-F-2", "story-F-3", "Story-G", "story-G-1", "story-G-2", "story-G-3", "Story-H", "story-H-1", "story-H-2", "story-H-3", "Story-I", "story-I-1", "story-I-2", "story-I-3", "Story-J", "story-J-1", "story-J-2", "story-J-3", "Story-K", "story-K-1", "story-K-2", "story-K-3", "Story-L", "story-L-1", "story-L-2", "story-L-3", "Story-M", "story-M-1", "story-M-2", "story-M-3", "Story-N", "story-N-1", "story-N-2", "story-N-3", "Story-O", "story-O-1", "story-O-2", "story-O-3", "Story-P", "story-P-1", "story-P-2", "story-P-3", "Story-Q", "story-Q-1", "story-Q-2", "story-Q-3", "Story-R", "story-R-1", "story-R-2", "story-R-3", "Story-S", "story-S-1", "story-S-2", "story-S-3", "Story-T", "story-T-1", "story-T-2", "story-T-3", "Story-U", "story-U-1", "story-U-2", "story-U-3", "Story-V", "story-V-1", "story-V-2", "story-V-3", "Story-W", "story-W-1", "story-W-2", "story-W-3", "Story-X", "story-X-1", "story-X-2", "story-X-3", "Story-Y", "story-Y-1", "story-Y-2", "story-Y-3", "Story-Z", "story-Z-1", "story-Z-2", "story-Z-3", "Strawberry", "strawberry-a", "strawberry-b", "strawberry-c", "strawberry-d", "strawberry-e", "Sun", "sun", "Sunglasses1", "sunglasses-a", "sunglasses-b", "Taco", "Taco", "Taco-wizard", "Takeout", "takeout-a", "takeout-b", "takeout-c", "takeout-d", "takeout-e", "Tatiana", "Tatiana-a", "Tatiana-b", "Tatiana-c", "Tatiana-d", "Taylor", "Taylor-a", "Taylor-b", "Taylor-c", "Taylor-d", "Ten80 Dance", "Ten80 stance", "Ten80 top stand", "Ten80 top R step", "Ten80 top L step", "Ten80 top freeze", "Ten80 top R cross", "Ten80 pop front", "Ten80 pop down", "Ten80 pop left", "Ten80 pop right", "Ten80 pop L arm", "Ten80 pop stand", "Ten80 pop R arm", "Tennis Ball", "tennisball", "Tera", "tera-a", "tera-b", "tera-c", "tera-d", "Toucan", "toucan-a", "toucan-b", "toucan-c", "Trampoline", "trampoline", "Tree1", "tree1", "Trees", "trees-a", "trees-b", "Trisha", "Trisha-a", "Trisha-b", "Trisha-c", "Trisha-d", "Truck", "Truck-a", "Truck-b", "Truck-c", "Trumpet", "trumpet-a", "trumpet-b", "Unicorn 2", "unicorn 2", "Unicorn Running", "unicorn running-a", "unicorn running-b", "unicorn running-c", "unicorn running-d", "unicorn running-e", "unicorn running-f", "Unicorn", "unicorn", "Wand", "wand", "Wanda", "wanda", "Watermelon", "watermelon-a", "watermelon-b", "watermelon-c", "Winter Hat", "Winter Hat", "Witch", "witch-a", "witch-b", "witch-c", "witch-d", "Wizard Girl", "wizard girl", "Wizard Hat", "Wizard Hat", "Wizard-toad", "wizard-toad-a", "wizard-toad-b", "Wizard", "wizard-a", "wizard-b", "wizard-c", "Zebra", "zebra-a", "zebra-b" | |
| ] | |
| backdrop_keywords = [ | |
| "Arctic", "Baseball 1", "Baseball 2", "Basketball 1", "Basketball 2", "Beach Malibu", "Beach Rio", "Bedroom 1", "Bedroom 2", "Bedroom 3", "Bench With View", "Blue Sky 2", "Blue Sky", "Boardwalk", "Canyon", "Castle 1", "Castle 2", "Castle 3", "Castle 4", "Chalkboard", "Circles", "City With Water", "Colorful City", "Concert", "Desert", "Farm", "Field At Mit", "Flowers", "Forest", "Galaxy", "Garden-rock", "Greek Theater", "Hall", "Hay Field", "Hearts", "Hill", "Jungle", "Jurassic", "Light", "Metro", "Moon", "Mountain", "Mural", "Nebula", "Neon Tunnel", "Night City With Street", "Night City", "Party", "Pathway", "Playground", "Playing Field", "Pool", "Rays", "Refrigerator", "Room 1", "Room 2", "Savanna", "School", "Slopes", "Soccer 2", "Soccer", "Space City 1", "Space City 2", "Space", "Spaceship", "Spotlight", "Stars", "Stripes", "Theater 2", "Theater", "Tree", "Underwater 1", "Underwater 2", "Urban", "Wall 1", "Wall 2", "Water And Rocks", "Wetland", "Winter", "Witch House", "Woods And Bench", "Woods", "Xy-grid-20px", "Xy-grid-30px", "Xy-grid" | |
| ] | |
| sound_keywords = [ | |
| "pop","Basketball Bounce","dance celebrate","dance magic","Chomp","Boing","Pop","Bite","basketball bounce","owl","xylo1","bell toll","Goal Cheer","Referee Whistle","Birthday","dance chill out","dance around","meow2","Meow","snort","Chirp","clown honk","car vroom","Magic Spell","collect","dance funky","bite","dog1","bark","bird","Drum Bass1","Drum Bass2","Drum Bass3","High Tom","Low Tom","crash cymbal","splash cymbal","bell cymbal","roll cymbal","hihat cymbal","tap snare","flam snare","sidestick snare","High Conga","Low Conga","Muted Conga","Tap Conga","Hi Na Tabla","Hi Tun Tabla","Lo Geh Tabla","Lo Gliss Tabla","duck","bubbles","ocean wave","water drop","car horn","wolf howl","space ripple","horse gallop","Water Drop","cheer","fairydust","C Elec Guitar","D Elec Guitar","E Elec Guitar","F Elec Guitar","G Elec Guitar","A Elec Guitar","B Elec Guitar","C2 Elec Guitar","C Guitar","D Guitar","E Guitar","F Guitar","G Guitar","A Guitar","B Guitar","C2 guitar","horse","C Elec Piano","D Elec Piano","E Elec Piano","F Elec Piano","G Elec Piano","A Elec Piano","B Elec Piano","C2 Elec Piano","grunt","Hand Clap","Bass Beatbox","Clap Beatbox","Hi Beatbox","Scratch Beatbox","Snare Beatbox","Snare Beatbox2","Wah Beatbox","Crash Beatbox","Wub Beatbox","glug","Chee Chee","splash","boing","Bird","magic spell","dog2","scratch beatbox","snare beatbox2","wah beatbox","bass beatbox","referee whistle","computer beeps1","computer beeps2","computer beep","buzz whir","laser1","laser2","rooster","C Sax","D Sax","E Sax","F Sax","G Sax","A Sax","B Sax","C2 Sax","Water drop","chomp","rattle","Drive Around","Scratchy Beat","Drum Jam","Cymbal Echo","Drum Satellite","Kick Back","Drum Funky","Footsteps","toy honk","C Trumpet","D Trumpet","E Trumpet","F Trumpet","G Trumpet","A Trumpet","B Trumpet","C2 Trumpet","croak" | |
| ] | |
| # Build canonical maps including sounds | |
| canonical_map, canonical_list = build_keyword_maps(sprite_keywords, backdrop_keywords, sound_keywords) | |
| # exact canonical match check | |
| ctext = canonicalize(text) | |
| if ctext in canonical_map: | |
| kw, cat = choose_preferred(canonical_map[ctext]) | |
| print(kw, cat) | |
| return (kw, cat) | |
| # valid dictionary word -> treat as variable (but show suggestions) | |
| valid = is_valid_word(text) | |
| if valid is True: | |
| check_and_print_nearest_matches(text, canonical_map, canonical_list) | |
| return (text, 'variable') | |
| # fuzzy matching | |
| match = get_best_match(text, canonical_map, canonical_list) | |
| if match: | |
| return match # (keyword, category) | |
| # default -> variable (with suggestions) | |
| check_and_print_nearest_matches(text, canonical_map, canonical_list) | |
| return (text, 'variable') | |
| ################################################################################################################################################################# | |
| #--------------------------------------------------[Example use of the function here]---------------------------------------------------------------------------- | |
| ################################################################################################################################################################# | |
| # initial_opcode_counts = [ | |
| # {"opcode": "event_whenbroadcastreceived","count": 1}, | |
| # {"opcode": "control_forever","count": 1}, | |
| # {"opcode": "control_if","count": 1}, | |
| # {"opcode": "sensing_istouching","count": 1}, | |
| # {"opcode": "looks_hide","count": 1}, | |
| # {"opcode": "looks_switchbackdropto","count": 1}, | |
| # {"opcode": "event_broadcast","count": 1}, | |
| # {"opcode": "control_stop","count": 1} | |
| # ] | |
| # pseudo_code=""" | |
| # when [space v] key pressed | |
| # start sound (Meow) | |
| # next costume | |
| # repeat (10) | |
| # change y by (10) | |
| # wait (0.0001) seconds | |
| # change y by (-10) | |
| # wait (0.0001) seconds | |
| # end | |
| # next costume | |
| # """ | |
| # print(pseudo_code) | |
| # opcode_counts_result = analyze_opcode_counts(pseudo_code) | |
| # generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_counts_result, 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) | |
| # renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences) | |
| # print(generated_output_json) | |
| # print("--------------\n\n") | |
| # print(processed_blocks) | |
| # print("--------------\n\n") | |
| # print(all_generated_blocks) | |
| # print("--------------\n\n") | |
| # print(opcode_counts_result) |