prthm11 commited on
Commit
7646c3b
·
verified ·
1 Parent(s): 60a4b8b

Update utils/block_relation_builder.py

Browse files
Files changed (1) hide show
  1. 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 else" 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,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: dict of opcode -> list of block_keys (in order)
1928
- • pseudo_code: a multiline string, indented with twospace levels
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
- all_generated_blocks = {} # This will store the final, structured blocks
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
- print("Stripped line here -----1---->", stripped_line)
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
- # The 'if-else' block (popped_owner_key) is the owner.
1982
- # This 'else' must belong to the current owner on top of the stack (which should be the if-else block)
1983
- # Ensure the current_owner_block_id is indeed an if-else block
1984
- if popped_owner_key and all_generated_blocks[popped_owner_key]["op_code"] == "control_if_else":
 
 
 
 
 
1985
  # Push a new scope for the 'else' substack, with the same owner.
1986
- stack.append((current_indent, popped_owner_key, None)) # New scope for 'else' part, no last block yet
1987
  else:
1988
- # Error: 'else' found without a preceding 'if' or incorrect nesting
 
1989
  print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
1990
- # Attempt to recover by treating it as a regular block or skipping
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
- # If we just popped the 'then' branch (SUBSTACK)
2011
- # and SUBSTACK2 is not yet set, then this 'end' closes SUBSTACK.
2012
- # If SUBSTACK2 was already set, this 'end' closes SUBSTACK2.
2013
- # If neither is filled, it's an empty 'then' branch.
2014
- if owner_block["inputs"].get("SUBSTACK") and owner_block["inputs"]["SUBSTACK"][1] is not None and \
2015
- (not owner_block["inputs"].get("SUBSTACK2") or owner_block["inputs"]["SUBSTACK2"][1] is None):
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 # Terminate the chain
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
- # Classify the statement and create the block
2043
- #stmt_for_parse = stripped_line.rstrip("then").strip()
2044
- stmt_for_parse = re.sub(r"\bthen\s*$", "", stripped_line, flags=re.IGNORECASE).strip()
2045
- print("befor the classify hit here: ",stmt_for_parse)
2046
- opcode, ntype = classify(stmt_for_parse)
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
- # Ensure the block exists in all_generated_blocks before trying to access it
 
2056
  if key not in all_generated_blocks:
2057
- # If not pre-generated, create a basic entry. This is a fallback.
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
- # Push a new scope for the children of this hat block.
2072
- stack.append((current_indent, key, None)) # New scope: owner is this hat, no last block yet
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: # This should be the first substack (then part)
2092
  owner_block["inputs"]["SUBSTACK"] = [2, key]
2093
- elif not owner_block["inputs"].get("SUBSTACK") or owner_block["inputs"]["SUBSTACK"][1] is None: # Only set if not already set by a block
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 # Default, will be overwritten if there's a next block
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
- # Push a new scope for the C-block's substack
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
- charset = string.ascii_letters + string.digits + "!@#$%^&*()[]{}=+-_~"
2519
- return ''.join(secrets.choice(charset) for _ in range(length))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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(20))
2531
- broadcast_id_map = defaultdict(lambda: generate_secure_token(20))
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(20)]])
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
  #################################################################################################################################################################