Update utils/block_relation_builder.py
Browse files- utils/block_relation_builder.py +77 -144
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 |
},
|
|
@@ -1867,7 +1867,7 @@ def classify(line):
|
|
| 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 +1924,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 +1951,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
|
|
@@ -2514,9 +2466,36 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
|
|
| 2514 |
#--------------------------------------------------[Security key id generation for the better understanding of keys]---------------------------------------------
|
| 2515 |
#################################################################################################################################################################
|
| 2516 |
|
| 2517 |
-
def generate_secure_token(length=20):
|
| 2518 |
-
|
| 2519 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2520 |
|
| 2521 |
#################################################################################################################################################################
|
| 2522 |
#--------------------------------------------------[Processed the two Skelton as input and generate refined skelton json]----------------------------------------
|
|
@@ -2527,8 +2506,8 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
| 2527 |
|
| 2528 |
# Initialize dictionaries to store and reuse generated unique IDs
|
| 2529 |
# This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
|
| 2530 |
-
variable_id_map = defaultdict(lambda: generate_secure_token(
|
| 2531 |
-
broadcast_id_map = defaultdict(lambda: generate_secure_token(
|
| 2532 |
|
| 2533 |
# Define the mapping for input field names to their required integer types for shadows
|
| 2534 |
input_type_mapping = {
|
|
@@ -2590,7 +2569,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
|
|
| 2590 |
processed_block["inputs"][input_name] = input_data
|
| 2591 |
else:
|
| 2592 |
# Fallback for unexpected formats, try to use the original if possible
|
| 2593 |
-
processed_block["inputs"][input_name] = gen_block_data["inputs"].get(input_name, [1, [11, "message1", generate_secure_token(
|
| 2594 |
|
| 2595 |
elif isinstance(input_data, dict):
|
| 2596 |
if input_data.get("kind") == "value":
|
|
@@ -2856,53 +2835,7 @@ def rename_blocks(block_json: Dict[str, Any], opcode_count: Dict[str, list]) ->
|
|
| 2856 |
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
| 2857 |
|
| 2858 |
return new_block_json, new_opcode_count
|
| 2859 |
-
|
| 2860 |
-
Replace each block key in block_json and each identifier in opcode_count
|
| 2861 |
-
with a newly generated secure token.
|
| 2862 |
-
|
| 2863 |
-
Args:
|
| 2864 |
-
block_json: Mapping of block_key -> block_data.
|
| 2865 |
-
opcode_count: Mapping of opcode -> list of block_keys.
|
| 2866 |
-
|
| 2867 |
-
Returns:
|
| 2868 |
-
A tuple of (new_block_json, new_opcode_count) with updated keys.
|
| 2869 |
-
"""
|
| 2870 |
-
# Step 1: Generate a secure token mapping for every existing block key
|
| 2871 |
-
token_map = {}
|
| 2872 |
-
for old_key in block_json.keys():
|
| 2873 |
-
# Ensure uniqueness in the unlikely event of a collision
|
| 2874 |
-
while True:
|
| 2875 |
-
new_key = generate_secure_token()
|
| 2876 |
-
if new_key not in token_map.values():
|
| 2877 |
-
break
|
| 2878 |
-
token_map[old_key] = new_key
|
| 2879 |
-
|
| 2880 |
-
# Step 2: Rebuild block_json with new keys
|
| 2881 |
-
new_block_json = {}
|
| 2882 |
-
for old_key, block in block_json.items():
|
| 2883 |
-
new_key = token_map[old_key]
|
| 2884 |
-
new_block_json[new_key] = block.copy()
|
| 2885 |
-
|
| 2886 |
-
# Update parent and next references
|
| 2887 |
-
if 'parent' in block and block['parent'] in token_map:
|
| 2888 |
-
new_block_json[new_key]['parent'] = token_map[block['parent']]
|
| 2889 |
-
if 'next' in block and block['next'] in token_map:
|
| 2890 |
-
new_block_json[new_key]['next'] = token_map[block['next']]
|
| 2891 |
-
|
| 2892 |
-
# Update inputs if they reference blocks
|
| 2893 |
-
for inp_key, inp_val in block.get('inputs', {}).items():
|
| 2894 |
-
if isinstance(inp_val, list) and len(inp_val) == 2:
|
| 2895 |
-
idx, ref = inp_val
|
| 2896 |
-
if idx in (2, 3) and isinstance(ref, str) and ref in token_map:
|
| 2897 |
-
new_block_json[new_key]['inputs'][inp_key] = [idx, token_map[ref]]
|
| 2898 |
-
|
| 2899 |
-
# Step 3: Update opcode count map
|
| 2900 |
-
new_opcode_count = {}
|
| 2901 |
-
for opcode, key_list in opcode_count.items():
|
| 2902 |
-
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
| 2903 |
-
|
| 2904 |
-
return new_block_json, new_opcode_count
|
| 2905 |
-
|
| 2906 |
#################################################################################################################################################################
|
| 2907 |
#--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
|
| 2908 |
#################################################################################################################################################################
|
|
|
|
| 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 |
},
|
|
|
|
| 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 go" in l: return "control_if_else", "c_block"
|
| 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 |
"""
|
| 1925 |
Build a nested “plan” tree from:
|
| 1926 |
• generated_input: dict of block_key -> block_data (pre-generated block definitions)
|
| 1927 |
+
• opcode_keys: dict of opcode -> list of block_keys (in order)
|
| 1928 |
+
• pseudo_code: a multiline string, indented with two-space levels
|
| 1929 |
|
| 1930 |
Returns:
|
| 1931 |
{ "flow": [ ... list of block dictionaries ... ] }
|
| 1932 |
"""
|
|
|
|
| 1933 |
ptrs = defaultdict(int)
|
| 1934 |
def pick_key(opcode):
|
| 1935 |
lst = opcode_keys.get(opcode, [])
|
| 1936 |
idx = ptrs[opcode]
|
| 1937 |
if idx >= len(lst):
|
|
|
|
|
|
|
| 1938 |
ptrs[opcode] += 1
|
| 1939 |
return f"{opcode}_{idx + 1}"
|
| 1940 |
ptrs[opcode] += 1
|
| 1941 |
return lst[idx]
|
| 1942 |
|
| 1943 |
+
# Change: Start with an empty dictionary. Blocks will be added only as they are parsed.
|
| 1944 |
+
all_generated_blocks = {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1945 |
|
| 1946 |
+
stack = [(-2, None, None)]
|
| 1947 |
top_level_script_keys = []
|
| 1948 |
|
| 1949 |
lines = pseudo_code.splitlines()
|
|
|
|
| 1951 |
while i < len(lines):
|
| 1952 |
raw_line = lines[i]
|
| 1953 |
stripped_line = raw_line.strip()
|
| 1954 |
+
|
|
|
|
| 1955 |
if not stripped_line or stripped_line.startswith("//"):
|
| 1956 |
i += 1
|
| 1957 |
continue
|
| 1958 |
|
| 1959 |
current_indent = (len(raw_line) - len(raw_line.lstrip())) // 2
|
| 1960 |
|
|
|
|
| 1961 |
if stripped_line.lower() == "else":
|
| 1962 |
# Pop the 'then' substack's scope
|
| 1963 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
| 1964 |
if popped_last_block_in_chain:
|
| 1965 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
| 1966 |
|
| 1967 |
+
# Check if the popped scope's owner is an IF block.
|
| 1968 |
+
# This check now looks for any C-Block, which is correct for this context.
|
| 1969 |
+
if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block":
|
| 1970 |
+
|
| 1971 |
+
# This is the crucial step that fixes the issue.
|
| 1972 |
+
# We explicitly upgrade the block from a 'control_if' to a 'control_if_else'.
|
| 1973 |
+
owner_block = all_generated_blocks[popped_owner_key]
|
| 1974 |
+
owner_block["op_code"] = "control_if_else"
|
| 1975 |
+
|
| 1976 |
# Push a new scope for the 'else' substack, with the same owner.
|
| 1977 |
+
stack.append((current_indent, popped_owner_key, None))
|
| 1978 |
else:
|
| 1979 |
+
# This is the error you were getting.
|
| 1980 |
+
# It happens because the 'if' block was never correctly upgraded.
|
| 1981 |
print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
|
| 1982 |
+
stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain))
|
|
|
|
| 1983 |
i += 1
|
| 1984 |
continue
|
| 1985 |
|
| 1986 |
if stripped_line.lower() == "end":
|
|
|
|
| 1987 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
| 1988 |
if popped_last_block_in_chain:
|
|
|
|
| 1989 |
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
| 1990 |
|
|
|
|
|
|
|
| 1991 |
if popped_owner_key:
|
| 1992 |
owner_block = all_generated_blocks[popped_owner_key]
|
| 1993 |
if owner_block["block_shape"] == "C-Block" or owner_block["op_code"] == "procedures_definition":
|
|
|
|
|
|
|
| 1994 |
if owner_block["op_code"] == "control_if_else":
|
| 1995 |
+
if owner_block["inputs"].get("SUBSTACK2") and owner_block["inputs"]["SUBSTACK2"][1] is not None:
|
| 1996 |
+
pass
|
| 1997 |
+
else:
|
| 1998 |
+
pass
|
| 1999 |
+
elif owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is None:
|
| 2000 |
+
owner_block["inputs"]["SUBSTACK"] = [2, None]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2001 |
i += 1
|
| 2002 |
continue
|
| 2003 |
|
|
|
|
|
|
|
| 2004 |
while len(stack) > 1 and stack[-1][0] >= current_indent:
|
| 2005 |
popped_indent, popped_owner_key, popped_last_block_in_chain = stack.pop()
|
| 2006 |
if popped_last_block_in_chain:
|
| 2007 |
+
all_generated_blocks[popped_last_block_in_chain]["next"] = None
|
| 2008 |
|
|
|
|
| 2009 |
current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
|
| 2010 |
|
| 2011 |
+
opcode, ntype = classify(stripped_line)
|
| 2012 |
+
stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip()
|
| 2013 |
+
print("The opcode here is",opcode)
|
| 2014 |
+
print("The ntype here is",ntype)
|
| 2015 |
+
if opcode is None:
|
|
|
|
|
|
|
| 2016 |
i += 1
|
| 2017 |
continue
|
| 2018 |
|
|
|
|
|
|
|
| 2019 |
key = pick_key(opcode)
|
| 2020 |
+
|
| 2021 |
+
# Change: The code now relies on this check to create a block only if it's new.
|
| 2022 |
if key not in all_generated_blocks:
|
| 2023 |
+
all_generated_blocks[key] = copy.deepcopy(generated_input.get(key, all_block_definitions.get(opcode, {})))
|
|
|
|
| 2024 |
all_generated_blocks[key]["id"] = key
|
| 2025 |
all_generated_blocks[key]["inputs"] = all_generated_blocks[key].get("inputs", {})
|
| 2026 |
all_generated_blocks[key]["fields"] = all_generated_blocks[key].get("fields", {})
|
|
|
|
| 2027 |
|
| 2028 |
info = all_generated_blocks[key]
|
| 2029 |
+
|
|
|
|
| 2030 |
if ntype == "hat":
|
| 2031 |
info["parent"] = None
|
| 2032 |
info["topLevel"] = True
|
| 2033 |
top_level_script_keys.append(key)
|
| 2034 |
+
stack.append((current_indent, key, None))
|
| 2035 |
+
else:
|
|
|
|
| 2036 |
if last_block_in_current_chain:
|
|
|
|
| 2037 |
info["parent"] = last_block_in_current_chain
|
| 2038 |
all_generated_blocks[last_block_in_current_chain]["next"] = key
|
| 2039 |
else:
|
|
|
|
|
|
|
| 2040 |
info["parent"] = current_owner_block_id
|
| 2041 |
|
|
|
|
| 2042 |
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"):
|
| 2043 |
owner_block = all_generated_blocks[current_owner_block_id]
|
| 2044 |
if owner_block["op_code"] == "control_if_else":
|
|
|
|
| 2045 |
if owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is not None and \
|
| 2046 |
(not owner_block["inputs"].get("SUBSTACK2") or owner_block["inputs"]["SUBSTACK2"][1] is None):
|
| 2047 |
owner_block["inputs"]["SUBSTACK2"] = [2, key]
|
| 2048 |
+
else:
|
| 2049 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
| 2050 |
+
elif not owner_block["inputs"].get("SUBSTACK") or owner_block["inputs"]["SUBSTACK"][1] is None:
|
| 2051 |
owner_block["inputs"]["SUBSTACK"] = [2, key]
|
| 2052 |
elif current_owner_block_id and all_generated_blocks[current_owner_block_id]["block_shape"] == "Hat Block":
|
|
|
|
| 2053 |
all_generated_blocks[current_owner_block_id]["next"] = key
|
| 2054 |
|
| 2055 |
info["topLevel"] = False
|
| 2056 |
+
info["next"] = None
|
| 2057 |
|
|
|
|
| 2058 |
if ntype == "c_block" or opcode == "procedures_definition":
|
|
|
|
| 2059 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
| 2060 |
+
stack.append((current_indent, key, None))
|
|
|
|
| 2061 |
else:
|
|
|
|
| 2062 |
stack[-1] = (current_scope_indent, current_owner_block_id, key)
|
| 2063 |
|
| 2064 |
# Parse inputs and fields (this part remains largely the same, but ensure parse_reporter_or_value/parse_condition
|
|
|
|
| 2466 |
#--------------------------------------------------[Security key id generation for the better understanding of keys]---------------------------------------------
|
| 2467 |
#################################################################################################################################################################
|
| 2468 |
|
| 2469 |
+
# def generate_secure_token(length=20):
|
| 2470 |
+
# charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
|
| 2471 |
+
# return ''.join(secrets.choice(charset) for _ in range(length))
|
| 2472 |
+
# conservative, safe alphabet derived from Scratch-style IDs
|
| 2473 |
+
_DEFAULT_SCRATCH_ALPHABET = string.ascii_letters + string.digits + "_-"
|
| 2474 |
+
_DEFAULT_LENGTH = 20
|
| 2475 |
+
|
| 2476 |
+
# small in-memory cache to avoid duplicates in a single run
|
| 2477 |
+
_generated_ids_cache = set()
|
| 2478 |
+
|
| 2479 |
+
def generate_secure_token() -> str:
|
| 2480 |
+
"""
|
| 2481 |
+
Return a single Scratch-style ID string (20 chars long) using a safe alphabet.
|
| 2482 |
+
No input required.
|
| 2483 |
+
|
| 2484 |
+
Notes:
|
| 2485 |
+
- This does NOT check for collisions inside an existing project.json.
|
| 2486 |
+
If you need to guarantee uniqueness with an existing file, use the
|
| 2487 |
+
project-aware generator shared earlier.
|
| 2488 |
+
- Collisions are astronomically unlikely for random 20-char tokens, but
|
| 2489 |
+
the function still avoids duplicates within the same Python process.
|
| 2490 |
+
"""
|
| 2491 |
+
alphabet = _DEFAULT_SCRATCH_ALPHABET
|
| 2492 |
+
length = _DEFAULT_LENGTH
|
| 2493 |
+
|
| 2494 |
+
while True:
|
| 2495 |
+
token = "".join(secrets.choice(alphabet) for _ in range(length))
|
| 2496 |
+
if token not in _generated_ids_cache:
|
| 2497 |
+
_generated_ids_cache.add(token)
|
| 2498 |
+
return token
|
| 2499 |
|
| 2500 |
#################################################################################################################################################################
|
| 2501 |
#--------------------------------------------------[Processed the two Skelton as input and generate refined skelton json]----------------------------------------
|
|
|
|
| 2506 |
|
| 2507 |
# Initialize dictionaries to store and reuse generated unique IDs
|
| 2508 |
# This prevents creating multiple unique IDs for the same variable/broadcast across different blocks
|
| 2509 |
+
variable_id_map = defaultdict(lambda: generate_secure_token())
|
| 2510 |
+
broadcast_id_map = defaultdict(lambda: generate_secure_token())
|
| 2511 |
|
| 2512 |
# Define the mapping for input field names to their required integer types for shadows
|
| 2513 |
input_type_mapping = {
|
|
|
|
| 2569 |
processed_block["inputs"][input_name] = input_data
|
| 2570 |
else:
|
| 2571 |
# Fallback for unexpected formats, try to use the original if possible
|
| 2572 |
+
processed_block["inputs"][input_name] = gen_block_data["inputs"].get(input_name, [1, [11, "message1", generate_secure_token()]])
|
| 2573 |
|
| 2574 |
elif isinstance(input_data, dict):
|
| 2575 |
if input_data.get("kind") == "value":
|
|
|
|
| 2835 |
new_opcode_count[opcode] = [token_map.get(k, k) for k in key_list]
|
| 2836 |
|
| 2837 |
return new_block_json, new_opcode_count
|
| 2838 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2839 |
#################################################################################################################################################################
|
| 2840 |
#--------------------------------------------------[Helper function to add Variables and Broadcasts [USed in main app file for main projectjson]]----------------
|
| 2841 |
#################################################################################################################################################################
|