Spaces:
Running
Running
Update utils/block_relation_builder.py
Browse files- utils/block_relation_builder.py +185 -225
utils/block_relation_builder.py
CHANGED
@@ -260,7 +260,7 @@ all_block_definitions = {
|
|
260 |
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
|
261 |
},
|
262 |
"control_if_else": {
|
263 |
-
"block_name": "if <> then else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else",
|
264 |
"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]",
|
265 |
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
|
266 |
},
|
@@ -1863,11 +1863,13 @@ def classify(line):
|
|
1863 |
|
1864 |
# Control Blocks
|
1865 |
# if re.match(r"wait (.+?) seconds", l): return "control_wait", "stack"
|
1866 |
-
if re.match(r"wait\s+(\(?\[?.+?\]?\)?)\s*(sec(?:ond)?s?)?$", l): return "control_wait", "stack"
|
1867 |
-
if l.startswith("wait until <"): return "control_wait_until", "stack"
|
|
|
|
|
1868 |
if l.startswith("repeat ("): return "control_repeat", "c_block"
|
1869 |
if l == "forever": return "control_forever", "c_block"
|
1870 |
-
if l.startswith("if <") and " then
|
1871 |
if l.startswith("if <"): return "control_if", "c_block"
|
1872 |
if l.startswith("repeat until <"): return "control_repeat_until", "c_block"
|
1873 |
# Updated regex for stop block to handle different options
|
@@ -1924,38 +1926,26 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
|
|
1924 |
"""
|
1925 |
Build a nested “plan” tree from:
|
1926 |
• generated_input: dict of block_key -> block_data (pre-generated block definitions)
|
1927 |
-
• opcode_keys:
|
1928 |
-
• pseudo_code:
|
1929 |
|
1930 |
Returns:
|
1931 |
{ "flow": [ ... list of block dictionaries ... ] }
|
1932 |
"""
|
1933 |
-
# helper: pick next unused block_key for an opcode
|
1934 |
ptrs = defaultdict(int)
|
1935 |
def pick_key(opcode):
|
1936 |
lst = opcode_keys.get(opcode, [])
|
1937 |
idx = ptrs[opcode]
|
1938 |
if idx >= len(lst):
|
1939 |
-
# Fallback: if no more pre-generated keys, create a new one.
|
1940 |
-
# This should ideally not happen if initial_opcode_counts is comprehensive
|
1941 |
ptrs[opcode] += 1
|
1942 |
return f"{opcode}_{idx + 1}"
|
1943 |
ptrs[opcode] += 1
|
1944 |
return lst[idx]
|
1945 |
|
1946 |
-
|
1947 |
-
|
1948 |
-
# Populate all_generated_blocks with initial blocks, ensuring they have IDs
|
1949 |
-
for key, block_data in generated_input.items():
|
1950 |
-
all_generated_blocks[key] = copy.deepcopy(block_data)
|
1951 |
-
all_generated_blocks[key]["id"] = key # Ensure ID is set
|
1952 |
-
|
1953 |
-
# Stack stores (indent, owner_block_id, last_block_in_current_linear_chain_id)
|
1954 |
-
# owner_block_id: The ID of the C-block or Hat block that owns the current substack.
|
1955 |
-
# last_block_in_current_linear_chain_id: The ID of the last block added to the *current linear sequence* within this substack.
|
1956 |
-
stack = [(-2, None, None)] # Sentinel: (indent, owner_block_id, last_block_in_current_linear_chain_id)
|
1957 |
-
# Using -2 for initial indent to be less than any valid indent (0 or more)
|
1958 |
|
|
|
1959 |
top_level_script_keys = []
|
1960 |
|
1961 |
lines = pseudo_code.splitlines()
|
@@ -1963,150 +1953,114 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
|
|
1963 |
while i < len(lines):
|
1964 |
raw_line = lines[i]
|
1965 |
stripped_line = raw_line.strip()
|
1966 |
-
|
1967 |
-
# Skip empty lines and comments
|
1968 |
if not stripped_line or stripped_line.startswith("//"):
|
1969 |
i += 1
|
1970 |
continue
|
1971 |
|
1972 |
current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2
|
1973 |
|
1974 |
-
# Handle 'else' and 'end' first, as they control scope
|
1975 |
if stripped_line.lower() == "else":
|
1976 |
# Pop the 'then' substack's scope
|
1977 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
1978 |
if popped_last_block_in_chain:
|
1979 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
1980 |
|
1981 |
-
#
|
1982 |
-
# This
|
1983 |
-
|
1984 |
-
|
|
|
|
|
|
|
|
|
|
|
1985 |
# Push a new scope for the 'else' substack, with the same owner.
|
1986 |
-
stack.append((current_indent, popped_owner_key, None))
|
1987 |
else:
|
1988 |
-
#
|
|
|
1989 |
print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
|
1990 |
-
|
1991 |
-
stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain)) # Put back what was popped
|
1992 |
i += 1
|
1993 |
continue
|
1994 |
|
1995 |
if stripped_line.lower() == "end":
|
1996 |
-
# Pop the current substack's scope
|
1997 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
1998 |
if popped_last_block_in_chain:
|
1999 |
-
print("popped_last_block_in_chain is executed here")
|
2000 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
2001 |
|
2002 |
-
# If the popped scope had an owner (C-block or procedure definition),
|
2003 |
-
# and its substack input isn't already filled, set it to None (empty substack).
|
2004 |
if popped_owner_key:
|
2005 |
owner_block = all_generated_blocks[popped_owner_key]
|
2006 |
if owner_block["block_shape"] == "C-Block" or owner_block["op_code"] == "procedures_definition":
|
2007 |
-
# Determine which substack this 'end' is closing
|
2008 |
-
# This logic needs to be smarter for if-else
|
2009 |
if owner_block["op_code"] == "control_if_else":
|
2010 |
-
|
2011 |
-
|
2012 |
-
|
2013 |
-
|
2014 |
-
|
2015 |
-
|
2016 |
-
# This 'end' closes the first substack (then branch)
|
2017 |
-
pass # Already handled by the stack logic
|
2018 |
-
elif owner_block["inputs"].get("SUBSTACK2") and owner_block["inputs"]["SUBSTACK2"][1] is not None:
|
2019 |
-
# This 'end' closes the second substack (else branch)
|
2020 |
-
pass # Already handled by the stack logic
|
2021 |
-
else: # Neither substack was filled, meaning an empty 'then' branch
|
2022 |
-
# For now, ensure it's not set to a block ID if it was empty.
|
2023 |
-
if "SUBSTACK" in owner_block["inputs"] and owner_block["inputs"]["SUBSTACK"][1] is None:
|
2024 |
-
pass # Already None
|
2025 |
-
if "SUBSTACK2" in owner_block["inputs"] and owner_block["inputs"]["SUBSTACK2"][1] is None:
|
2026 |
-
pass # Already None
|
2027 |
-
elif owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is None: # Only set if not already set by a block
|
2028 |
-
owner_block["inputs"]["SUBSTACK"] = [2, None] # No blocks in substack
|
2029 |
i += 1
|
2030 |
continue
|
2031 |
|
2032 |
-
# Adjust stack based on indentation for regular blocks
|
2033 |
-
# Pop scopes whose indentation is greater than or equal to the current line's indentation
|
2034 |
while len(stack) > 1 and stack[-1][0] >= current_indent:
|
2035 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
2036 |
if popped_last_block_in_chain:
|
2037 |
-
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
2038 |
|
2039 |
-
# Get the current active scope from the stack
|
2040 |
current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
|
2041 |
|
2042 |
-
|
2043 |
-
|
2044 |
-
|
2045 |
-
print("
|
2046 |
-
opcode
|
2047 |
-
print("the opcode classify: ",opcode)
|
2048 |
-
if opcode is None: # Should not happen if classify is robust
|
2049 |
i += 1
|
2050 |
continue
|
2051 |
|
2052 |
-
# Create the new block (and register it in all_generated_blocks)
|
2053 |
-
# _register_block now only sets parent for shadow/input blocks; main block parent/next/topLevel set here.
|
2054 |
key = pick_key(opcode)
|
2055 |
-
|
|
|
2056 |
if key not in all_generated_blocks:
|
2057 |
-
|
2058 |
-
all_generated_blocks[key] = copy.deepcopy(all_block_definitions.get(opcode, {}))
|
2059 |
all_generated_blocks[key]["id"] = key
|
2060 |
all_generated_blocks[key]["inputs"] = all_generated_blocks[key].get("inputs", {})
|
2061 |
all_generated_blocks[key]["fields"] = all_generated_blocks[key].get("fields", {})
|
2062 |
-
# Removed sub_stacks from here
|
2063 |
|
2064 |
info = all_generated_blocks[key]
|
2065 |
-
|
2066 |
-
# Set parent, next, and topLevel for the main script blocks
|
2067 |
if ntype == "hat":
|
2068 |
info["parent"] = None
|
2069 |
info["topLevel"] = True
|
2070 |
top_level_script_keys.append(key)
|
2071 |
-
|
2072 |
-
|
2073 |
-
else: # Stack block or C-block (that is part of a linear sequence)
|
2074 |
if last_block_in_current_chain:
|
2075 |
-
# This block's parent is the previous block in the chain
|
2076 |
info["parent"] = last_block_in_current_chain
|
2077 |
all_generated_blocks[last_block_in_current_chain]["next"] = key
|
2078 |
else:
|
2079 |
-
# This is the first block in a new linear chain (e.g., first block inside a forever loop)
|
2080 |
-
# Its parent is the owner of the current scope (the C-block or Hat block)
|
2081 |
info["parent"] = current_owner_block_id
|
2082 |
|
2083 |
-
# If the owner is a C-block or procedure definition, link its SUBSTACK input
|
2084 |
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"):
|
2085 |
owner_block = all_generated_blocks[current_owner_block_id]
|
2086 |
if owner_block["op_code"] == "control_if_else":
|
2087 |
-
# If SUBSTACK is already set, this means we are starting SUBSTACK2 (else part)
|
2088 |
if owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is not None and \
|
2089 |
(not owner_block["inputs"].get("SUBSTACK2") or owner_block["inputs"]["SUBSTACK2"][1] is None):
|
2090 |
owner_block["inputs"]["SUBSTACK2"] = [2, key]
|
2091 |
-
else:
|
2092 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
2093 |
-
elif not owner_block["inputs"].get("SUBSTACK") or owner_block["inputs"]["SUBSTACK"][1] is None:
|
2094 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
2095 |
elif current_owner_block_id and all_generated_blocks[current_owner_block_id]["block_shape"] == "Hat Block":
|
2096 |
-
# If the owner is a Hat block, this is its first child
|
2097 |
all_generated_blocks[current_owner_block_id]["next"] = key
|
2098 |
|
2099 |
info["topLevel"] = False
|
2100 |
-
info["next"] = None
|
2101 |
|
2102 |
-
# If it's a C-block or define block, it also starts a new inner scope
|
2103 |
if ntype == "c_block" or opcode == "procedures_definition":
|
2104 |
-
# Update the current scope's last_block_in_current_chain to this C-block
|
2105 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
2106 |
-
|
2107 |
-
stack.append((current_indent, key, None)) # New scope: owner is this C-block, no last block yet
|
2108 |
else:
|
2109 |
-
# For regular stack blocks, just update the last_block_in_current_chain for the current scope
|
2110 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
2111 |
|
2112 |
# Parse inputs and fields (this part remains largely the same, but ensure parse_reporter_or_value/parse_condition
|
@@ -2554,8 +2508,8 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2554 |
|
2555 |
# Initialize dictionaries to store and reuse generated unique IDs
|
2556 |
# This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
|
2557 |
-
variable_id_map = defaultdict(lambda: generate_secure_token())
|
2558 |
-
broadcast_id_map = defaultdict(lambda: generate_secure_token())
|
2559 |
|
2560 |
# Define the mapping for input field names to their required integer types for shadows
|
2561 |
input_type_mapping = {
|
@@ -2580,6 +2534,31 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2580 |
"VALUE_STRING": 10 # Used internally for VALUE when it should be type 10
|
2581 |
}
|
2582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2583 |
for block_id, gen_block_data in generated_output_json.items():
|
2584 |
processed_block = {}
|
2585 |
all_gen_block_data = all_generated_blocks.get(block_id, {})
|
@@ -2595,13 +2574,14 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2595 |
if "mutation" in all_gen_block_data:
|
2596 |
processed_block["mutation"] = all_gen_block_data["mutation"]
|
2597 |
|
|
|
|
|
2598 |
# Process inputs
|
2599 |
if "inputs" in all_gen_block_data:
|
2600 |
for input_name, input_data in all_gen_block_data["inputs"].items():
|
|
|
2601 |
if input_name == "BROADCAST_INPUT":
|
2602 |
-
# Special handling for BROADCAST_INPUT (type 11)
|
2603 |
if isinstance(input_data, dict) and input_data.get("kind") == "value":
|
2604 |
-
# This is the new logic to handle a direct value for broadcast
|
2605 |
menu_option = input_data.get("value", "message1")
|
2606 |
broadcast_id = broadcast_id_map[menu_option]
|
2607 |
processed_block["inputs"][input_name] = [
|
@@ -2613,23 +2593,26 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2613 |
]
|
2614 |
]
|
2615 |
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:
|
2616 |
-
# Preserve
|
2617 |
processed_block["inputs"][input_name] = input_data
|
2618 |
else:
|
2619 |
-
# Fallback
|
2620 |
-
|
2621 |
-
|
2622 |
-
|
2623 |
-
|
|
|
|
|
|
|
|
|
|
|
2624 |
# Determine the correct shadow type based on the input_name and opcode
|
2625 |
-
shadow_type = input_type_mapping.get(input_name, 4)
|
2626 |
-
|
2627 |
-
# Specific
|
2628 |
-
if input_name == "NUM" and
|
2629 |
shadow_type = 7
|
2630 |
-
|
2631 |
-
# Specific override for "VALUE" in "data_setvariableto"
|
2632 |
-
if input_name == "VALUE" and processed_block["opcode"] == "data_setvariableto":
|
2633 |
shadow_type = 10
|
2634 |
|
2635 |
processed_block["inputs"][input_name] = [
|
@@ -2639,119 +2622,142 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2639 |
str(input_data.get("value", ""))
|
2640 |
]
|
2641 |
]
|
2642 |
-
|
2643 |
-
|
|
|
|
|
2644 |
nested_block_id = input_data.get("block", "")
|
2645 |
-
|
2646 |
if nested_block_id in all_generated_blocks:
|
|
|
|
|
|
|
|
|
|
|
2647 |
nested_block_opcode = all_generated_blocks[nested_block_id].get("op_code")
|
2648 |
-
|
2649 |
if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]:
|
2650 |
-
# These are control flow blocks, should always be type 2
|
2651 |
processed_block["inputs"][input_name] = [2, nested_block_id]
|
2652 |
elif nested_block_opcode in ["data_variable", "data_listcontents"]:
|
2653 |
-
#
|
2654 |
variable_name = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[0]
|
2655 |
variable_id = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[1]
|
2656 |
if not variable_id:
|
2657 |
variable_id = variable_id_map[variable_name]
|
2658 |
processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]]
|
2659 |
elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]:
|
2660 |
-
#
|
2661 |
-
# and should be type 1, referring to a shadow block.
|
2662 |
processed_block["inputs"][input_name] = [1, nested_block_id]
|
2663 |
else:
|
2664 |
-
#
|
2665 |
-
|
2666 |
-
|
2667 |
-
processed_block["inputs"][input_name] = [3, nested_block_id, [shadow_type_for_nested, ""]] # Empty shadow value
|
2668 |
else:
|
2669 |
-
#
|
2670 |
-
|
2671 |
-
|
2672 |
-
|
2673 |
-
|
2674 |
-
#
|
2675 |
-
menu_option = input_data.get("option", "")
|
2676 |
-
# These should typically be type 1 referring to a shadow block, or a direct value if no shadow block
|
2677 |
-
# For simplicity, we'll try to get the block ID if it's a menu block.
|
2678 |
if "block" in input_data:
|
2679 |
-
|
|
|
|
|
|
|
|
|
2680 |
else:
|
2681 |
-
#
|
2682 |
-
|
2683 |
-
processed_block["inputs"][input_name] = [1, [10, menu_option]]
|
2684 |
-
|
2685 |
-
|
2686 |
-
elif isinstance(input_data, list):
|
2687 |
-
# This branch handles existing list structures from all_gen_block_data (or generated_output_json if no all_gen_block_data)
|
2688 |
-
# It needs to decide if it's a direct value, a block reference, or a special type (11, 12)
|
2689 |
|
|
|
|
|
|
|
|
|
2690 |
if len(input_data) == 2:
|
2691 |
first_val = input_data[0]
|
2692 |
second_val = input_data[1]
|
2693 |
|
|
|
2694 |
if isinstance(second_val, str) and second_val in all_generated_blocks:
|
2695 |
-
#
|
|
|
|
|
|
|
|
|
2696 |
nested_block_opcode = all_generated_blocks[second_val].get("op_code")
|
2697 |
|
2698 |
if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]:
|
2699 |
-
# These are control flow blocks, should always be type 2
|
2700 |
processed_block["inputs"][input_name] = [2, second_val]
|
2701 |
elif nested_block_opcode in ["data_variable", "data_listcontents"]:
|
2702 |
-
# It's a variable/list reporter, should be type 12 inside type 1
|
2703 |
variable_name = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[0]
|
2704 |
variable_id = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[1]
|
2705 |
if not variable_id:
|
2706 |
variable_id = variable_id_map[variable_name]
|
2707 |
processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]]
|
2708 |
elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]:
|
2709 |
-
# These are menu/dropdown inputs that refer to another block/menu,
|
2710 |
-
# and should be type 1, referring to a shadow block.
|
2711 |
processed_block["inputs"][input_name] = [1, second_val]
|
2712 |
else:
|
2713 |
-
# For other reporter blocks (like motion_xposition, operators),
|
2714 |
-
# they should be type 3 when nested. Correct the first_val if it's not 3.
|
2715 |
shadow_type_for_nested = input_type_mapping.get(input_name, 10)
|
|
|
2716 |
processed_block["inputs"][input_name] = [3, second_val, [shadow_type_for_nested, ""]]
|
|
|
2717 |
|
2718 |
-
|
2719 |
-
|
2720 |
-
shadow_type = input_type_mapping.get(input_name, 4)
|
2721 |
-
if input_name == "NUM" and
|
2722 |
shadow_type = 7
|
2723 |
-
if input_name == "VALUE" and
|
2724 |
shadow_type = 10
|
2725 |
processed_block["inputs"][input_name] = [1, [shadow_type, str(second_val)]]
|
2726 |
-
|
2727 |
-
|
2728 |
-
|
|
|
|
|
2729 |
|
2730 |
elif len(input_data) == 3 and isinstance(input_data[1], str) and isinstance(input_data[2], list):
|
2731 |
-
#
|
2732 |
-
#
|
2733 |
-
|
2734 |
-
if
|
2735 |
-
#
|
2736 |
-
|
2737 |
-
|
2738 |
-
|
2739 |
-
|
2740 |
-
|
2741 |
-
|
2742 |
-
|
2743 |
-
|
2744 |
-
|
2745 |
-
|
2746 |
-
|
2747 |
-
|
2748 |
-
|
2749 |
-
|
2750 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2751 |
else:
|
2752 |
-
#
|
2753 |
processed_block["inputs"][input_name] = input_data
|
|
|
2754 |
|
|
|
|
|
2755 |
|
2756 |
# Process fields
|
2757 |
if "fields" in all_gen_block_data:
|
@@ -2797,7 +2803,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2797 |
|
2798 |
# Check if TOUCHINGOBJECTMENU input exists and is a block reference
|
2799 |
referenced_block_id = None
|
2800 |
-
if "TOUCHINGOBJECTMENU" in all_gen_block_data
|
2801 |
input_val = all_gen_block_data["inputs"]["TOUCHINGOBJECTMENU"]
|
2802 |
if isinstance(input_val, list) and len(input_val) > 1 and isinstance(input_val[1], str):
|
2803 |
referenced_block_id = input_val[1]
|
@@ -2815,7 +2821,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
2815 |
else:
|
2816 |
processed_block["fields"][field_name] = field_value
|
2817 |
|
2818 |
-
# Remove unwanted keys from the processed block
|
2819 |
keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
|
2820 |
for key in keys_to_remove:
|
2821 |
if key in processed_block:
|
@@ -2883,53 +2889,7 @@ def rename_blocks(block_json: Dict[str, Any], opcode_count: Dict[str, list]) ->
|
|
2883 |
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
2884 |
|
2885 |
return new_block_json, new_opcode_count
|
2886 |
-
|
2887 |
-
Replace each block key in block_json and each identifier in opcode_count
|
2888 |
-
with a newly generated secure token.
|
2889 |
-
|
2890 |
-
Args:
|
2891 |
-
block_json: Mapping of block_key -> block_data.
|
2892 |
-
opcode_count: Mapping of opcode -> list of block_keys.
|
2893 |
-
|
2894 |
-
Returns:
|
2895 |
-
A tuple of (new_block_json, new_opcode_count) with updated keys.
|
2896 |
-
"""
|
2897 |
-
# Step 1: Generate a secure token mapping for every existing block key
|
2898 |
-
token_map = {}
|
2899 |
-
for old_key in block_json.keys():
|
2900 |
-
# Ensure uniqueness in the unlikely event of a collision
|
2901 |
-
while True:
|
2902 |
-
new_key = generate_secure_token()
|
2903 |
-
if new_key not in token_map.values():
|
2904 |
-
break
|
2905 |
-
token_map[old_key] = new_key
|
2906 |
-
|
2907 |
-
# Step 2: Rebuild block_json with new keys
|
2908 |
-
new_block_json = {}
|
2909 |
-
for old_key, block in block_json.items():
|
2910 |
-
new_key = token_map[old_key]
|
2911 |
-
new_block_json[new_key] = block.copy()
|
2912 |
-
|
2913 |
-
# Update parent and next references
|
2914 |
-
if 'parent' in block and block['parent'] in token_map:
|
2915 |
-
new_block_json[new_key]['parent'] = token_map[block['parent']]
|
2916 |
-
if 'next' in block and block['next'] in token_map:
|
2917 |
-
new_block_json[new_key]['next'] = token_map[block['next']]
|
2918 |
-
|
2919 |
-
# Update inputs if they reference blocks
|
2920 |
-
for inp_key, inp_val in block.get('inputs', {}).items():
|
2921 |
-
if isinstance(inp_val, list) and len(inp_val) == 2:
|
2922 |
-
idx, ref = inp_val
|
2923 |
-
if idx in (2, 3) and isinstance(ref, str) and ref in token_map:
|
2924 |
-
new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]]
|
2925 |
-
|
2926 |
-
# Step 3: Update opcode count map
|
2927 |
-
new_opcode_count = {}
|
2928 |
-
for opcode, key_list in opcode_count.items():
|
2929 |
-
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
2930 |
-
|
2931 |
-
return new_block_json, new_opcode_count
|
2932 |
-
|
2933 |
#################################################################################################################################################################
|
2934 |
#--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
|
2935 |
#################################################################################################################################################################
|
|
|
260 |
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
|
261 |
},
|
262 |
"control_if_else": {
|
263 |
+
"block_name": "if <> then go else", "block_type": "Control", "block_shape": "C-Block", "op_code": "control_if_else",
|
264 |
"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]",
|
265 |
"inputs": {"CONDITION": [2, None], "SUBSTACK": [2, None], "SUBSTACK2": [2, None]}, "fields": {}, "shadow": False, "topLevel": True
|
266 |
},
|
|
|
1863 |
|
1864 |
# Control Blocks
|
1865 |
# if re.match(r"wait (.+?) seconds", l): return "control_wait", "stack"
|
1866 |
+
# if re.match(r"wait\s+(\(?\[?.+?\]?\)?)\s*(sec(?:ond)?s?)?$", l): return "control_wait", "stack"
|
1867 |
+
# if l.startswith("wait until <"): return "control_wait_until", "stack"
|
1868 |
+
if re.match(r"wait\s+(?!until)(\(?\[?.+?\]?\)?)\s*(sec(?:ond)?s?)?$", l, re.IGNORECASE): return "control_wait", "stack"
|
1869 |
+
if re.match(r"wait\s+until\s*<", l, re.IGNORECASE): return "control_wait_until", "stack"
|
1870 |
if l.startswith("repeat ("): return "control_repeat", "c_block"
|
1871 |
if l == "forever": return "control_forever", "c_block"
|
1872 |
+
if l.startswith("if <") and " then go" in l: return "control_if_else", "c_block"
|
1873 |
if l.startswith("if <"): return "control_if", "c_block"
|
1874 |
if l.startswith("repeat until <"): return "control_repeat_until", "c_block"
|
1875 |
# Updated regex for stop block to handle different options
|
|
|
1926 |
"""
|
1927 |
Build a nested “plan” tree from:
|
1928 |
• generated_input: dict of block_key -> block_data (pre-generated block definitions)
|
1929 |
+
• opcode_keys: dict of opcode -> list of block_keys (in order)
|
1930 |
+
• pseudo_code: a multiline string, indented with two-space levels
|
1931 |
|
1932 |
Returns:
|
1933 |
{ "flow": [ ... list of block dictionaries ... ] }
|
1934 |
"""
|
|
|
1935 |
ptrs = defaultdict(int)
|
1936 |
def pick_key(opcode):
|
1937 |
lst = opcode_keys.get(opcode, [])
|
1938 |
idx = ptrs[opcode]
|
1939 |
if idx >= len(lst):
|
|
|
|
|
1940 |
ptrs[opcode] += 1
|
1941 |
return f"{opcode}_{idx + 1}"
|
1942 |
ptrs[opcode] += 1
|
1943 |
return lst[idx]
|
1944 |
|
1945 |
+
# Change: Start with an empty dictionary. Blocks will be added only as they are parsed.
|
1946 |
+
all_generated_blocks = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1947 |
|
1948 |
+
stack = [(-2, None, None)]
|
1949 |
top_level_script_keys = []
|
1950 |
|
1951 |
lines = pseudo_code.splitlines()
|
|
|
1953 |
while i < len(lines):
|
1954 |
raw_line = lines[i]
|
1955 |
stripped_line = raw_line.strip()
|
1956 |
+
|
|
|
1957 |
if not stripped_line or stripped_line.startswith("//"):
|
1958 |
i += 1
|
1959 |
continue
|
1960 |
|
1961 |
current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2
|
1962 |
|
|
|
1963 |
if stripped_line.lower() == "else":
|
1964 |
# Pop the 'then' substack's scope
|
1965 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
1966 |
if popped_last_block_in_chain:
|
1967 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
1968 |
|
1969 |
+
# Check if the popped scope's owner is an IF block.
|
1970 |
+
# This check now looks for any C-Block, which is correct for this context.
|
1971 |
+
if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block":
|
1972 |
+
|
1973 |
+
# This is the crucial step that fixes the issue.
|
1974 |
+
# We explicitly upgrade the block from a 'control_if' to a 'control_if_else'.
|
1975 |
+
owner_block = all_generated_blocks[popped_owner_key]
|
1976 |
+
owner_block["op_code"] = "control_if_else"
|
1977 |
+
|
1978 |
# Push a new scope for the 'else' substack, with the same owner.
|
1979 |
+
stack.append((current_indent, popped_owner_key, None))
|
1980 |
else:
|
1981 |
+
# This is the error you were getting.
|
1982 |
+
# It happens because the 'if' block was never correctly upgraded.
|
1983 |
print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
|
1984 |
+
stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain))
|
|
|
1985 |
i += 1
|
1986 |
continue
|
1987 |
|
1988 |
if stripped_line.lower() == "end":
|
|
|
1989 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
1990 |
if popped_last_block_in_chain:
|
|
|
1991 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
1992 |
|
|
|
|
|
1993 |
if popped_owner_key:
|
1994 |
owner_block = all_generated_blocks[popped_owner_key]
|
1995 |
if owner_block["block_shape"] == "C-Block" or owner_block["op_code"] == "procedures_definition":
|
|
|
|
|
1996 |
if owner_block["op_code"] == "control_if_else":
|
1997 |
+
if owner_block["inputs"].get("SUBSTACK2") and owner_block["inputs"]["SUBSTACK2"][1] is not None:
|
1998 |
+
pass
|
1999 |
+
else:
|
2000 |
+
pass
|
2001 |
+
elif owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is None:
|
2002 |
+
owner_block["inputs"]["SUBSTACK"] = [2, None]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2003 |
i += 1
|
2004 |
continue
|
2005 |
|
|
|
|
|
2006 |
while len(stack) > 1 and stack[-1][0] >= current_indent:
|
2007 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
2008 |
if popped_last_block_in_chain:
|
2009 |
+
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
2010 |
|
|
|
2011 |
current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
|
2012 |
|
2013 |
+
opcode, ntype = classify(stripped_line)
|
2014 |
+
stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip()
|
2015 |
+
print("The opcode here is",opcode)
|
2016 |
+
print("The ntype here is",ntype)
|
2017 |
+
if opcode is None:
|
|
|
|
|
2018 |
i += 1
|
2019 |
continue
|
2020 |
|
|
|
|
|
2021 |
key = pick_key(opcode)
|
2022 |
+
|
2023 |
+
# Change: The code now relies on this check to create a block only if it's new.
|
2024 |
if key not in all_generated_blocks:
|
2025 |
+
all_generated_blocks[key] = copy.deepcopy(generated_input.get(key, all_block_definitions.get(opcode, {})))
|
|
|
2026 |
all_generated_blocks[key]["id"] = key
|
2027 |
all_generated_blocks[key]["inputs"] = all_generated_blocks[key].get("inputs", {})
|
2028 |
all_generated_blocks[key]["fields"] = all_generated_blocks[key].get("fields", {})
|
|
|
2029 |
|
2030 |
info = all_generated_blocks[key]
|
2031 |
+
|
|
|
2032 |
if ntype == "hat":
|
2033 |
info["parent"] = None
|
2034 |
info["topLevel"] = True
|
2035 |
top_level_script_keys.append(key)
|
2036 |
+
stack.append((current_indent, key, None))
|
2037 |
+
else:
|
|
|
2038 |
if last_block_in_current_chain:
|
|
|
2039 |
info["parent"] = last_block_in_current_chain
|
2040 |
all_generated_blocks[last_block_in_current_chain]["next"] = key
|
2041 |
else:
|
|
|
|
|
2042 |
info["parent"] = current_owner_block_id
|
2043 |
|
|
|
2044 |
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"):
|
2045 |
owner_block = all_generated_blocks[current_owner_block_id]
|
2046 |
if owner_block["op_code"] == "control_if_else":
|
|
|
2047 |
if owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is not None and \
|
2048 |
(not owner_block["inputs"].get("SUBSTACK2") or owner_block["inputs"]["SUBSTACK2"][1] is None):
|
2049 |
owner_block["inputs"]["SUBSTACK2"] = [2, key]
|
2050 |
+
else:
|
2051 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
2052 |
+
elif not owner_block["inputs"].get("SUBSTACK") or owner_block["inputs"]["SUBSTACK"][1] is None:
|
2053 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
2054 |
elif current_owner_block_id and all_generated_blocks[current_owner_block_id]["block_shape"] == "Hat Block":
|
|
|
2055 |
all_generated_blocks[current_owner_block_id]["next"] = key
|
2056 |
|
2057 |
info["topLevel"] = False
|
2058 |
+
info["next"] = None
|
2059 |
|
|
|
2060 |
if ntype == "c_block" or opcode == "procedures_definition":
|
|
|
2061 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
2062 |
+
stack.append((current_indent, key, None))
|
|
|
2063 |
else:
|
|
|
2064 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
2065 |
|
2066 |
# Parse inputs and fields (this part remains largely the same, but ensure parse_reporter_or_value/parse_condition
|
|
|
2508 |
|
2509 |
# Initialize dictionaries to store and reuse generated unique IDs
|
2510 |
# This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
|
2511 |
+
variable_id_map = defaultdict(lambda: generate_secure_token(20))
|
2512 |
+
broadcast_id_map = defaultdict(lambda: generate_secure_token(20))
|
2513 |
|
2514 |
# Define the mapping for input field names to their required integer types for shadows
|
2515 |
input_type_mapping = {
|
|
|
2534 |
"VALUE_STRING": 10 # Used internally for VALUE when it should be type 10
|
2535 |
}
|
2536 |
|
2537 |
+
# Explicit mapping of opcodes -> expected menu inputs that MUST be simple menu/shadow links ([1, block_id])
|
2538 |
+
explicit_menu_links = {
|
2539 |
+
"motion_goto": [("TO", "motion_goto_menu")],
|
2540 |
+
"motion_glideto": [("TO", "motion_glideto_menu")],
|
2541 |
+
"motion_pointtowards": [("TOWARDS", "motion_pointtowards_menu")],
|
2542 |
+
"sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")],
|
2543 |
+
"sensing_of": [("OBJECT", "sensing_of_object_menu")],
|
2544 |
+
"sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")],
|
2545 |
+
"control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")],
|
2546 |
+
"sound_play": [("SOUND_MENU", "sound_sounds_menu")],
|
2547 |
+
"sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")],
|
2548 |
+
"looks_switchcostumeto": [("COSTUME", "looks_costume")],
|
2549 |
+
"looks_switchbackdropto": [("BACKDROP", "looks_backdrops")],
|
2550 |
+
}
|
2551 |
+
|
2552 |
+
# helper to check if this opcode+input_name is an explicit menu link
|
2553 |
+
def is_explicit_menu_opcode_input(opcode, input_name):
|
2554 |
+
if not opcode:
|
2555 |
+
return False
|
2556 |
+
pairs = explicit_menu_links.get(opcode, [])
|
2557 |
+
for inp, _menu in pairs:
|
2558 |
+
if inp == input_name:
|
2559 |
+
return True
|
2560 |
+
return False
|
2561 |
+
|
2562 |
for block_id, gen_block_data in generated_output_json.items():
|
2563 |
processed_block = {}
|
2564 |
all_gen_block_data = all_generated_blocks.get(block_id, {})
|
|
|
2574 |
if "mutation" in all_gen_block_data:
|
2575 |
processed_block["mutation"] = all_gen_block_data["mutation"]
|
2576 |
|
2577 |
+
opcode = processed_block["opcode"] # convenience
|
2578 |
+
|
2579 |
# Process inputs
|
2580 |
if "inputs" in all_gen_block_data:
|
2581 |
for input_name, input_data in all_gen_block_data["inputs"].items():
|
2582 |
+
# --- BROADCAST INPUT special handling (type 11) ---
|
2583 |
if input_name == "BROADCAST_INPUT":
|
|
|
2584 |
if isinstance(input_data, dict) and input_data.get("kind") == "value":
|
|
|
2585 |
menu_option = input_data.get("value", "message1")
|
2586 |
broadcast_id = broadcast_id_map[menu_option]
|
2587 |
processed_block["inputs"][input_name] = [
|
|
|
2593 |
]
|
2594 |
]
|
2595 |
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:
|
2596 |
+
# Preserve well-formed existing type 11 structure
|
2597 |
processed_block["inputs"][input_name] = input_data
|
2598 |
else:
|
2599 |
+
# Fallback: try original generated_output_json value if present, else synthesize
|
2600 |
+
fallback = gen_block_data.get("inputs", {}).get(input_name,
|
2601 |
+
[1, [11, "message1", generate_secure_token(20)]])
|
2602 |
+
processed_block["inputs"][input_name] = fallback
|
2603 |
+
continue
|
2604 |
+
|
2605 |
+
# --- Input described as a dict (value, block, menu) ---
|
2606 |
+
if isinstance(input_data, dict):
|
2607 |
+
kind = input_data.get("kind")
|
2608 |
+
if kind == "value":
|
2609 |
# Determine the correct shadow type based on the input_name and opcode
|
2610 |
+
shadow_type = input_type_mapping.get(input_name, 4) # default 4
|
2611 |
+
|
2612 |
+
# Specific overrides
|
2613 |
+
if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers":
|
2614 |
shadow_type = 7
|
2615 |
+
if input_name == "VALUE" and opcode == "data_setvariableto":
|
|
|
|
|
2616 |
shadow_type = 10
|
2617 |
|
2618 |
processed_block["inputs"][input_name] = [
|
|
|
2622 |
str(input_data.get("value", ""))
|
2623 |
]
|
2624 |
]
|
2625 |
+
continue
|
2626 |
+
|
2627 |
+
if kind == "block":
|
2628 |
+
# nested block reference via dict
|
2629 |
nested_block_id = input_data.get("block", "")
|
|
|
2630 |
if nested_block_id in all_generated_blocks:
|
2631 |
+
# If this opcode+input_name is explicitly a menu -> force [1, nested_block_id]
|
2632 |
+
if is_explicit_menu_opcode_input(opcode, input_name):
|
2633 |
+
processed_block["inputs"][input_name] = [1, nested_block_id]
|
2634 |
+
continue
|
2635 |
+
|
2636 |
nested_block_opcode = all_generated_blocks[nested_block_id].get("op_code")
|
2637 |
+
|
2638 |
if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]:
|
|
|
2639 |
processed_block["inputs"][input_name] = [2, nested_block_id]
|
2640 |
elif nested_block_opcode in ["data_variable", "data_listcontents"]:
|
2641 |
+
# variable/list reporter inside a reporter -> [1, [12, name, id]]
|
2642 |
variable_name = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[0]
|
2643 |
variable_id = all_generated_blocks[nested_block_id].get("fields", {}).get("VARIABLE", ["", ""])[1]
|
2644 |
if not variable_id:
|
2645 |
variable_id = variable_id_map[variable_name]
|
2646 |
processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]]
|
2647 |
elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]:
|
2648 |
+
# menu/dropdown inputs that refer to another block/menu -> type 1, refer to block id
|
|
|
2649 |
processed_block["inputs"][input_name] = [1, nested_block_id]
|
2650 |
else:
|
2651 |
+
# other reporter blocks (like motion_xposition, operators) -> nested type 3
|
2652 |
+
shadow_type_for_nested = input_type_mapping.get(input_name, 10)
|
2653 |
+
processed_block["inputs"][input_name] = [3, nested_block_id, [shadow_type_for_nested, ""]]
|
|
|
2654 |
else:
|
2655 |
+
# referenced block missing: fallback to original input format if available, else a safe fallback
|
2656 |
+
processed_block["inputs"][input_name] = gen_block_data.get("inputs", {}).get(input_name, [3, nested_block_id, [10, ""]])
|
2657 |
+
continue
|
2658 |
+
|
2659 |
+
if kind == "menu":
|
2660 |
+
# menu kind: either references a block or a direct value
|
|
|
|
|
|
|
2661 |
if "block" in input_data:
|
2662 |
+
# If explicit menu, ensure we store as [1, block_id]
|
2663 |
+
if is_explicit_menu_opcode_input(opcode, input_name):
|
2664 |
+
processed_block["inputs"][input_name] = [1, input_data["block"]]
|
2665 |
+
else:
|
2666 |
+
processed_block["inputs"][input_name] = [1, input_data["block"]]
|
2667 |
else:
|
2668 |
+
# direct value in menu -> type 1 with a string shadow (type 10)
|
2669 |
+
menu_option = input_data.get("option", "")
|
2670 |
+
processed_block["inputs"][input_name] = [1, [10, menu_option]]
|
2671 |
+
continue
|
|
|
|
|
|
|
|
|
2672 |
|
2673 |
+
# --- Input is a list already (various formats) ---
|
2674 |
+
if isinstance(input_data, list):
|
2675 |
+
# Common shapes:
|
2676 |
+
# [1, <value shadow>], [1, <block_id>], [3, <block_id>, [shadow_type, value]], etc.
|
2677 |
if len(input_data) == 2:
|
2678 |
first_val = input_data[0]
|
2679 |
second_val = input_data[1]
|
2680 |
|
2681 |
+
# If the second element is a block id that exists
|
2682 |
if isinstance(second_val, str) and second_val in all_generated_blocks:
|
2683 |
+
# If explicit menu mapping -> force [1, block_id]
|
2684 |
+
if is_explicit_menu_opcode_input(opcode, input_name):
|
2685 |
+
processed_block["inputs"][input_name] = [1, second_val]
|
2686 |
+
continue
|
2687 |
+
|
2688 |
nested_block_opcode = all_generated_blocks[second_val].get("op_code")
|
2689 |
|
2690 |
if input_name in ["SUBSTACK", "CONDITION", "SUBSTACK2"]:
|
|
|
2691 |
processed_block["inputs"][input_name] = [2, second_val]
|
2692 |
elif nested_block_opcode in ["data_variable", "data_listcontents"]:
|
|
|
2693 |
variable_name = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[0]
|
2694 |
variable_id = all_generated_blocks[second_val].get("fields", {}).get("VARIABLE", ["", ""])[1]
|
2695 |
if not variable_id:
|
2696 |
variable_id = variable_id_map[variable_name]
|
2697 |
processed_block["inputs"][input_name] = [1, [12, variable_name, variable_id]]
|
2698 |
elif input_name in ["TOUCHINGOBJECTMENU", "TO", "TOWARDS", "CLONE_OPTION", "SOUND_MENU", "KEY_OPTION", "OBJECT"]:
|
|
|
|
|
2699 |
processed_block["inputs"][input_name] = [1, second_val]
|
2700 |
else:
|
|
|
|
|
2701 |
shadow_type_for_nested = input_type_mapping.get(input_name, 10)
|
2702 |
+
# Ensure nested reporters use type 3
|
2703 |
processed_block["inputs"][input_name] = [3, second_val, [shadow_type_for_nested, ""]]
|
2704 |
+
continue
|
2705 |
|
2706 |
+
# If the second element is a string literal value -> wrap with appropriate shadow type
|
2707 |
+
if isinstance(second_val, str):
|
2708 |
+
shadow_type = input_type_mapping.get(input_name, 4) # default 4
|
2709 |
+
if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers":
|
2710 |
shadow_type = 7
|
2711 |
+
if input_name == "VALUE" and opcode == "data_setvariableto":
|
2712 |
shadow_type = 10
|
2713 |
processed_block["inputs"][input_name] = [1, [shadow_type, str(second_val)]]
|
2714 |
+
continue
|
2715 |
+
|
2716 |
+
# fallback: preserve the input as-is
|
2717 |
+
processed_block["inputs"][input_name] = input_data
|
2718 |
+
continue
|
2719 |
|
2720 |
elif len(input_data) == 3 and isinstance(input_data[1], str) and isinstance(input_data[2], list):
|
2721 |
+
# case: [3, "block_id", [shadow_type, shadow_value]] or
|
2722 |
+
# [1, "block_id", [shadow_type, shadow_value]] (rare)
|
2723 |
+
block_ref = input_data[1]
|
2724 |
+
if block_ref in all_generated_blocks:
|
2725 |
+
# If explicit menu mapping -> convert to [1, block_id]
|
2726 |
+
if is_explicit_menu_opcode_input(opcode, input_name):
|
2727 |
+
processed_block["inputs"][input_name] = [1, block_ref]
|
2728 |
+
continue
|
2729 |
+
|
2730 |
+
# keep already well-formed nested reporter [3, ...]
|
2731 |
+
if input_data[0] == 3:
|
2732 |
+
processed_block["inputs"][input_name] = input_data
|
2733 |
+
continue
|
2734 |
+
|
2735 |
+
# If it's a type 1 with a type shadow like [1, [11,...]] or [1, [12,...]] -> preserve
|
2736 |
+
if input_data[0] == 1 and isinstance(input_data[1], list) and input_data[1][0] in [11, 12]:
|
2737 |
+
processed_block["inputs"][input_name] = input_data
|
2738 |
+
continue
|
2739 |
+
|
2740 |
+
# if it's [1, [shadow_type, value]] then re-evaluate shadow type
|
2741 |
+
if input_data[0] == 1 and isinstance(input_data[1], list) and len(input_data[1]) == 2:
|
2742 |
+
shadow_type = input_type_mapping.get(input_name, 4)
|
2743 |
+
if input_name == "NUM" and opcode == "looks_goforwardbackwardlayers":
|
2744 |
+
shadow_type = 7
|
2745 |
+
if input_name == "VALUE" and opcode == "data_setvariableto":
|
2746 |
+
shadow_type = 10
|
2747 |
+
processed_block["inputs"][input_name] = [input_data[0], [shadow_type, str(input_data[1][1])]]
|
2748 |
+
continue
|
2749 |
+
|
2750 |
+
# fallback: preserve the list as-is
|
2751 |
+
processed_block["inputs"][input_name] = input_data
|
2752 |
+
continue
|
2753 |
+
|
2754 |
else:
|
2755 |
+
# other unexpected list shapes -> preserve for now
|
2756 |
processed_block["inputs"][input_name] = input_data
|
2757 |
+
continue
|
2758 |
|
2759 |
+
# If input_data didn't match any of the above -> try to fallback to original generated value if available
|
2760 |
+
processed_block["inputs"][input_name] = gen_block_data.get("inputs", {}).get(input_name, input_data)
|
2761 |
|
2762 |
# Process fields
|
2763 |
if "fields" in all_gen_block_data:
|
|
|
2803 |
|
2804 |
# Check if TOUCHINGOBJECTMENU input exists and is a block reference
|
2805 |
referenced_block_id = None
|
2806 |
+
if "TOUCHINGOBJECTMENU" in all_gen_block_data.get("inputs", {}):
|
2807 |
input_val = all_gen_block_data["inputs"]["TOUCHINGOBJECTMENU"]
|
2808 |
if isinstance(input_val, list) and len(input_val) > 1 and isinstance(input_val[1], str):
|
2809 |
referenced_block_id = input_val[1]
|
|
|
2821 |
else:
|
2822 |
processed_block["fields"][field_name] = field_value
|
2823 |
|
2824 |
+
# Remove unwanted keys from the processed block (if somehow present)
|
2825 |
keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
|
2826 |
for key in keys_to_remove:
|
2827 |
if key in processed_block:
|
|
|
2889 |
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
2890 |
|
2891 |
return new_block_json, new_opcode_count
|
2892 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2893 |
#################################################################################################################################################################
|
2894 |
#--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
|
2895 |
#################################################################################################################################################################
|