prthm11 commited on
Commit
e2b81eb
·
verified ·
1 Parent(s): b8a1fb5

Update utils/block_relation_builder.py

Browse files
Files changed (1) hide show
  1. utils/block_relation_builder.py +255 -360
utils/block_relation_builder.py CHANGED
@@ -1104,47 +1104,7 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block
1104
  block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
1105
  if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id
1106
  all_generated_blocks[list_block_id]["parent"] = block_id
1107
- return {"kind": "block", "block": block_id}
1108
-
1109
- # # [ORDER NO: ]
1110
- # # (item (index) of [list v]) (data_itemoflist)
1111
- # m = re.search(r"item \((.+?)\) of \((.+?)\)", text)
1112
- # if not m:
1113
- # m = re.search(r"item \((.+?)\) of \[([^\]]+)\s*v\]", text)
1114
- # if m:
1115
- # index_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks)
1116
- # list_name = m.group(2).strip()
1117
- # print("(item (index) of [list v]) : ",index_obj)
1118
- # print("(item (index) of [list v]) : ",list_name)
1119
- # # Create data_list shadow block for the list name
1120
- # list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]})
1121
-
1122
- # inputs = {"INDEX": index_obj, "LIST": {"kind": "block", "block": list_block_id}}
1123
- # fields = {} # No fields in data_itemoflist itself for the list name
1124
- # block_id = _register_block("data_itemoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
1125
- # if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id
1126
- # all_generated_blocks[list_block_id]["parent"] = block_id
1127
- # return {"kind": "block", "block": block_id}
1128
-
1129
- # # [ORDER NO: ]
1130
- # # (item # of [item] in [list v]) (data_itemnumoflist)
1131
- # m = re.search(r"item # of \((.+?)\) in \((.+?)\)", text)
1132
- # if not m:
1133
- # m = re.search(r"item # of \[([^\]]+)\] in \[([^\]]+)\s*v\]", text)
1134
- # if m:
1135
- # item_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks)
1136
- # list_name = m.group(2).strip()
1137
- # print("(item # of [item] in [list v]) : ",index_obj)
1138
- # print("(item # of [item] in [list v]) : ",list_name)
1139
- # # Create data_list shadow block for the list name
1140
- # list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]})
1141
-
1142
- # inputs = {"ITEM": item_obj, "LIST": {"kind": "block", "block": list_block_id}}
1143
- # fields = {} # No fields in data_itemnumoflist itself for the list name
1144
- # block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
1145
- # if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id
1146
- # all_generated_blocks[list_block_id]["parent"] = block_id
1147
- # return {"kind": "block", "block": block_id}
1148
 
1149
  # [ORDER NO: ]
1150
  # (pick random () to ()) (operator_random)
@@ -1243,20 +1203,7 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block
1243
  return {"kind": "block", "block": block_id}
1244
 
1245
  # [ORDER NO: ]
1246
- # (() of ()) (operator_mathop) - handle variable for function type
1247
- # m = re.search(r"\[([^\]]+)\s*v\] of \((.+?)\)", text) # e.g. [sqrt v] of ((x pos) * (x pos))
1248
- # if m:
1249
- # print("(() of ()) (operator_mathop):[left] ",m.group(1).strip())
1250
- # print("(() of ()) (operator_mathop):[rigth] ",m.group(2).strip())
1251
- # func_type = m.group(1).strip()
1252
- # value_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, pick_key_func, all_generated_blocks)
1253
- # inputs = {"NUM": value_obj}
1254
- # fields = {"OPERATOR": [func_type.upper(), None]}
1255
- # block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
1256
- # if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id
1257
- # return {"kind": "block", "block": block_id}
1258
- # (() of ()) (operator_mathop) - handle variable for function type
1259
-
1260
  def extract_mathop_of_expression(text):
1261
  # Match the function like [sqrt v] of
1262
  m = re.search(r"\[([^\]]+)\s*v\]\s+of\s+\(", text)
@@ -1351,41 +1298,6 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block
1351
  block_id = _register_block("looks_backdropnumbername", parent_key, True, pick_key_func, all_generated_blocks, fields=fields)
1352
  return {"kind": "block", "block": block_id}
1353
 
1354
- # # [ORDER NO: ]
1355
- # # (costume ()) (looks_costumenumbername) - handle with or without 'v'
1356
- # #m = re.search(r"costume \((.+?)\)", text)
1357
- # m = re.fullmatch(r"\(?\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?",text,re.IGNORECASE | re.VERBOSE)
1358
- # if m:
1359
- # option = (m.group(1) or m.group(2))
1360
- # if option is None:
1361
- # raise ValueError(f"Unable to extract costume option from: {text}")
1362
- # option = option.strip()
1363
- # print("(costume ()) : ", option)
1364
- # fields = {"NUMBER_NAME": [option, None]}
1365
- # block_id = _register_block(
1366
- # "looks_costumenumbername",
1367
- # parent_key,
1368
- # False,
1369
- # pick_key_func,
1370
- # all_generated_blocks,
1371
- # fields=fields
1372
- # )
1373
- # if block_id is None:
1374
- # raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}")
1375
- # return {"kind": "block", "block": block_id}
1376
-
1377
- # # [ORDER NO: ]
1378
- # # (backdrop ()) (looks_backdropnumbername) - handle with or without 'v'
1379
- # m = re.search(r"backdrop \((.+?)\)", text)
1380
- # if not m:
1381
- # m = re.search(r"backdrop \[([^\]]+)\s*v\]", text)
1382
- # if m:
1383
- # option = m.group(1).strip()
1384
- # print("(backdrop ()) : ",option)
1385
- # fields = {"NUMBER_NAME": [option, None]}
1386
- # block_id = _register_block("looks_backdropnumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields)
1387
- # return {"kind": "block", "block": block_id}
1388
-
1389
  # [ORDER NO: ]
1390
  # (distance to ()) (sensing_distanceto) - handle with or without 'v'
1391
  m = re.search(r"distance to \((.+?)\)", text)
@@ -1458,53 +1370,6 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block
1458
 
1459
 
1460
  # [ORDER NO: ALWAYS LAST]
1461
- # Arithmetic operations: (() + ()), (() - ()), (() * ()), (() / ())
1462
-
1463
-
1464
- #_strip_outer = re.compile(r"^\s*(?P<open>[\(\[]+)\s*(?P<inner>.*\S\s*(?P=open)\s*$", re.VERBOSE)
1465
- # _strip_outer = re.compile(r"^\s*(?P<open>[\(\[]+)\s*(?P<inner>.*\S)\s*(?P=open)\s*$",re.VERBOSE)
1466
- # _expr = re.compile(r"^\s*(?P<left>[^\s\(\)\[\]]+)\s*(?P<op>[+\-*/])\s*(?P<right>[^\s\(\)\[\]]+)\s*$", re.VERBOSE)
1467
- # #m_art = re.compile(r"\(?\s*([^\s()]+)\s*\)?\s*([+\-*/])\s*\(?\s*([^\s()]+)\s*\)?", re.VERBOSE)
1468
- # if text:
1469
- # expr = text
1470
- # # strip *all* matching layers of () or []
1471
- # while True:
1472
- # m = _strip_outer.match(expr)
1473
- # if not m:
1474
- # break
1475
- # expr = m.group("inner")
1476
-
1477
- # # now match the core "left op right"
1478
- # m = _expr.match(expr)
1479
-
1480
- # left_str = m.group("left")
1481
- # operator = m.group("op")
1482
- # right_str = m.group("right")
1483
- # print("arithemetic ops:[left] ",left_str)
1484
- # print("arithemetic ops:[rigth] ",right_str)
1485
- # # your existing parse & block‑creation logic
1486
- # op1_obj = parse_reporter_or_value(left_str, parent_key, pick_key_func, all_generated_blocks)
1487
- # op2_obj = parse_reporter_or_value(right_str, parent_key, pick_key_func, all_generated_blocks)
1488
-
1489
- # opcode_map = {
1490
- # '+': 'operator_add',
1491
- # '-': 'operator_subtract',
1492
- # '*': 'operator_multiply',
1493
- # '/': 'operator_divide',
1494
- # }
1495
- # inputs = {"NUM1": op1_obj, "NUM2": op2_obj}
1496
- # block_id = _register_block(
1497
- # opcode_map[operator], parent_key, False,
1498
- # pick_key_func, all_generated_blocks,
1499
- # inputs=inputs
1500
- # )
1501
-
1502
- # if op1_obj.get("kind") == "block":
1503
- # all_generated_blocks[op1_obj["block"]]["parent"] = block_id
1504
- # if op2_obj.get("kind") == "block":
1505
- # all_generated_blocks[op2_obj["block"]]["parent"] = block_id
1506
-
1507
- # return {"kind": "block", "block": block_id}
1508
  # Function to strip outer parentheses if they enclose the entire expression
1509
  def strip_outer_parentheses(s):
1510
  s = s.strip()
@@ -1613,9 +1478,10 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks):
1613
  boolean operators (and, or, not), and other sensing conditions.
1614
  """
1615
  s = stmt.strip()
 
1616
  s = extract_condition_balanced(s)
1617
  s_lower = s.lower()
1618
- print("the processed text on parse_conditon",s_lower)
1619
  # 1) Boolean NOT: `not <...>`
1620
  m_not = re.fullmatch(r"\s*(?:<\s*)?not\s+(.+?)(?:\s*>)?\s*", s_lower, re.IGNORECASE)
1621
  if m_not:
@@ -1641,40 +1507,7 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks):
1641
  if cond1_obj.get("kind") == "block": all_generated_blocks[cond1_obj["block"]]["parent"] = block_id
1642
  if cond2_obj.get("kind") == "block": all_generated_blocks[cond2_obj["block"]]["parent"] = block_id
1643
  return {"kind": "block", "block": block_id}
1644
-
1645
-
1646
- # # 1a) Comparisons with explicit angle wrappers: < (...) op (...) >
1647
- # m = re.fullmatch(
1648
- # r"\s*<\s*(.+?)\s*(?P<op><|=|>)\s*(.+?)\s*>\s*",
1649
- # s_lower,
1650
- # re.VERBOSE
1651
- # )
1652
- # if m:
1653
- # left_txt, right_txt = m.group(1), m.group(3)
1654
- # operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks)
1655
- # operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks)
1656
- # op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}
1657
-
1658
- # inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj}
1659
- # block_id = _register_block(op_map[m.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
1660
- # # Set parents for nested inputs
1661
- # if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id
1662
- # if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id
1663
- # return {"kind": "block", "block": block_id}
1664
-
1665
- # # 1b) Simple comparisons without angle wrappers: A op B
1666
- # m_simple = re.fullmatch(r"\s*(.+?)\s*(?P<op><|=|>)\s*(.+?)\s*", s_lower)
1667
- # if m_simple:
1668
- # left_txt, right_txt = m_simple.group(1), m_simple.group(3)
1669
- # operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks)
1670
- # operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks)
1671
- # op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'}
1672
-
1673
- # inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj}
1674
- # block_id = _register_block(op_map[m_simple.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs)
1675
- # if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id
1676
- # if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id
1677
- # return {"kind": "block", "block": block_id}
1678
  m_comp = re.fullmatch(r"\s*(?:<\s*)?(?P<left>.+?)\s*(?P<op><|=|>)\s*(?P<right>.+?)(?:\s*>)?\s*",s_lower,re.VERBOSE)
1679
  if m_comp:
1680
  left_txt = m_comp.group('left')
@@ -1716,8 +1549,6 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks):
1716
  return {"kind": "block", "block": block_id}
1717
 
1718
  # 5) Touching object: <touching [edge v]?>
1719
- # m_touch = re.fullmatch(r"""\s*<?\s*touching\s*\[\s*(?P<sprite>[^\]]+?)\s*(?:v)?\s*\]\s*\?\s*>?""", s_lower, re.IGNORECASE | re.VERBOSE)
1720
- #m_touch = re.fullmatch(r"""\s*<?\s*touching\s*\[\s*(?P<sprite>[^\]]+?)\s*(?:v)?\s*\]\s*\?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE)
1721
  m_touch = re.fullmatch(r"""\s*<?\s*touching\s*\[\s*(?P<sprite>[^\]]+?)\s*(?:v)?\s*\]\s*(?:\?)?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE)
1722
  if m_touch:
1723
  sprite = m_touch.group('sprite').strip()
@@ -1735,31 +1566,65 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks):
1735
  return {"kind":"block","block":bid}
1736
 
1737
  # 6) Touching color: <touching color [#rrggbb]?>
1738
- m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s_lower)
 
 
 
1739
  if m:
1740
- inputs = {"COLOR": {"kind": "value", "value": m.group(1)}}
1741
- print("<touching color [#rrggbb]?> : ",inputs)
1742
- block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1743
  return {"kind": "block", "block": block_id}
1744
 
1745
  # 7) Color is touching color: <color [#rggbb] is touching [#rrggbb]?>
1746
- m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s_lower)
1747
  if m:
1748
- inputs = {"COLOR1": {"kind": "value", "value": m.group(1)}, "COLOR2": {"kind": "value", "value": m.group(2)}}
1749
- print("<color [#rggbb] is touching [#rrggbb]?> : ",inputs)
1750
- block_id = _register_block("sensing_coloristouchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs)
1751
- return {"kind": "block", "block": block_id}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1752
 
1753
  # 8) Key pressed: <key [key v] pressed?>
1754
- # m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", s_lower)
1755
- # if m:
1756
- # option = m.group(1).strip()
1757
- # menu_block_id = _register_block("sensing_keyoptions", parent_key, True, pick_key_func, all_generated_blocks, fields={"KEY_OPTION": [option, None]})
1758
- # print("<key [key v] pressed?> : ",option)
1759
- # inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}}
1760
- # block_id = _register_block("sensing_keypressed", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs)
1761
- # all_generated_blocks[menu_block_id]["parent"] = block_id
1762
- # return {"kind": "block", "block": block_id}
1763
  m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?", s_lower, re.IGNORECASE)
1764
  if m:
1765
  option = m.group(1).strip()
@@ -1947,6 +1812,46 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
1947
  return f"{opcode}_{idx + 1}"
1948
  ptrs[opcode] += 1
1949
  return lst[idx]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1950
 
1951
  # Change: Start with an empty dictionary. Blocks will be added only as they are parsed.
1952
  all_generated_blocks = {}
@@ -1955,6 +1860,7 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
1955
  top_level_script_keys = []
1956
 
1957
  lines = pseudo_code.splitlines()
 
1958
  i = 0
1959
  while i < len(lines):
1960
  raw_line = lines[i]
@@ -1976,16 +1882,13 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
1976
  # This check now looks for any C-Block, which is correct for this context.
1977
  if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block":
1978
 
1979
- # This is the crucial step that fixes the issue.
1980
- # We explicitly upgrade the block from a 'control_if' to a 'control_if_else'.
1981
  owner_block = all_generated_blocks[popped_owner_key]
1982
  owner_block["op_code"] = "control_if_else"
1983
 
1984
  # Push a new scope for the 'else' substack, with the same owner.
1985
  stack.append((current_indent, popped_owner_key, None))
1986
  else:
1987
- # This is the error you were getting.
1988
- # It happens because the 'if' block was never correctly upgraded.
1989
  print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
1990
  stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain))
1991
  i += 1
@@ -2016,10 +1919,17 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
2016
 
2017
  current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
2018
 
2019
- opcode, ntype = classify(stripped_line)
 
 
 
 
 
 
 
2020
  stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip()
2021
- print("The opcode here is",opcode)
2022
- print("The ntype here is",ntype)
2023
  if opcode is None:
2024
  i += 1
2025
  continue
@@ -2073,34 +1983,17 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
2073
  # are passed the *newly created block's ID* as the parent_key for nested inputs)
2074
  # Numeric inputs (e.g., move (10) steps, wait (1) seconds)
2075
  if opcode == "motion_movesteps":
2076
- #m = re.search(r"move\s*(?:\(\s*)?(-?\d+(?:\.\d+)?)(?:\s*\))?\s*steps", stmt_for_parse, re.IGNORECASE)
2077
  m = re.search(r"move\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*steps", stmt_for_parse, re.IGNORECASE)
2078
- #if m: info["inputs"]["STEPS"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
2079
  print("motion_movesteps : ",m.group(1).strip())
2080
  if m: info["inputs"]["STEPS"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks)
2081
- # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["STEPS"])
2082
- # print(f"block_id {opcode} : {block_id}")
2083
  elif opcode == "motion_turnright" or opcode == "motion_turnleft":
2084
- # m = re.search(r"turn\s*(?:right|left)?\s*\(.*?\)\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*degrees", stmt_for_parse, re.IGNORECASE)
2085
- #m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(-?\d+(?:\.\d+)?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE)
2086
  m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE)
2087
- # if m: info["inputs"]["DEGREES"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))}
2088
  if m: info["inputs"]["DEGREES"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks)
2089
- # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, info["inputs"]["DEGREES"])
2090
- # print(f"block_id {opcode} : {block_id}")
2091
  elif opcode == "motion_gotoxy":
2092
- # m_x = re.search(r"x:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE)
2093
- # m_y = re.search(r"y:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE)
2094
  m_x = re.search(r"x:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*y:|$)", stmt_for_parse, re.IGNORECASE)
2095
  m_y = re.search(r"y:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*$)", stmt_for_parse, re.IGNORECASE)
2096
  if m_x: info["inputs"]["X"] = parse_reporter_or_value( m_x.group(1).strip("() "), key, pick_key, all_generated_blocks)
2097
  if m_y: info["inputs"]["Y"] = parse_reporter_or_value( m_y.group(1).strip("() "), key, pick_key, all_generated_blocks)
2098
- # print("motion_gotoxy m_x", m_x.group(1) if m_x else None)
2099
- # print("motion_gotoxy m_y", m_y.group(1) if m_y else None)
2100
- # if m_x: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, )
2101
- # print(f"block_id {opcode} : {block_id}")
2102
- # if m_y: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["Y"])
2103
- # print(f"block_id {opcode} : {block_id}")
2104
  elif opcode == "motion_glidesecstoxy":
2105
  stmt = _auto_balance(stmt_for_parse)
2106
  # m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", stmt_for_parse, re.IGNORECASE)
@@ -2244,60 +2137,59 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
2244
 
2245
  # Dropdown/Menu inputs (UPDATED)
2246
  elif opcode == "motion_goto":
2247
- m = re.search(r"go to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
2248
  if m:
2249
- option = m.group(1).strip()
2250
- if option == "random position": option_val = "_random_"
2251
- elif option == "mouse-pointer": option_val = "_mouse_"
2252
- else: option_val = option
2253
-
2254
- menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]})
2255
  info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id}
 
2256
  elif opcode == "motion_glideto":
2257
- m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs to\s*(?:\[([^\]]+)\s*v\]|\((.+?)\))", stmt_for_parse, re.IGNORECASE)
 
2258
  if m_secs:
2259
- info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))}
2260
- # Use group 3 for [random position v] or group 4 for (random position)
2261
- option = (m_secs.group(3) or m_secs.group(4)).strip()
2262
- if option == "random position": option_val = "_random_"
2263
- elif option == "mouse-pointer": option_val = "_mouse_"
2264
- else: option_val = option
2265
-
2266
- menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]})
2267
  info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id}
 
2268
  elif opcode == "motion_pointtowards":
2269
- m = re.search(r"point towards\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
2270
  if m:
2271
- option = m.group(1).strip()
2272
- if option == "mouse-pointer": option_val = "_mouse_"
2273
- else: option_val = option
2274
-
2275
- menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks, fields={"TOWARDS": [option_val, None]})
2276
  info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id}
 
2277
  elif opcode == "sensing_keypressed":
2278
- m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", stmt_for_parse, re.IGNORECASE)
2279
  if m:
2280
- option = m.group(1).strip()
2281
- menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks, fields={"KEY_OPTION": [option, None]})
 
2282
  info["inputs"]["KEY_OPTION"] = {"kind": "block", "block": menu_block_id}
 
2283
  elif opcode == "sensing_touchingobject":
2284
- m = re.search(r"touching \[([^\]]+)\s*v\]\?", stmt_for_parse, re.IGNORECASE)
2285
  if m:
2286
- option = m.group(1).strip()
2287
- if option == "mouse-pointer": option_val = "_mouse_"
2288
- elif option == "edge": option_val = "_edge_"
2289
- else: option_val = option
2290
-
2291
- menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks, fields={"TOUCHINGOBJECTMENU": [option_val, None]})
2292
  info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "block", "block": menu_block_id}
 
2293
  elif opcode == "control_create_clone_of":
2294
- m = re.search(r"create clone of\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
2295
  if m:
2296
- option = m.group(1).strip()
2297
- if option == "myself": option_val = "_myself_"
2298
- else: option_val = option
2299
-
2300
- menu_block_id = _register_block("control_create_clone_of_menu", key, True, pick_key, all_generated_blocks, fields={"CLONE_OPTION": [option_val, None]})
2301
  info["inputs"]["CLONE_OPTION"] = {"kind": "block", "block": menu_block_id}
2302
  elif opcode in ["sound_playuntildone", "sound_play"]:
2303
  # m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
@@ -2309,28 +2201,42 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
2309
  menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]})
2310
  info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id}
2311
  elif opcode == "looks_switchcostumeto":
2312
- m = re.search(r"switch costume to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
2313
  if m:
2314
- option = m.group(1).strip()
2315
- print("option at looks_switchcostumeto: ",option)
2316
- menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks, fields={"COSTUME": [option, None]})
2317
  info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id}
 
2318
  elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]:
2319
- m = re.search(r"switch backdrop to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
2320
  if m:
2321
- option = m.group(1).strip()
2322
- menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks, fields={"BACKDROP": [option, None]})
 
2323
  info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id}
 
2324
  elif opcode in ["event_broadcast", "event_broadcastandwait"]:
2325
- m = re.search(r"broadcast\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
 
 
 
 
 
 
 
2326
  if m:
2327
- option = m.group(1).strip()
2328
- # Broadcast input doesn't use a separate menu block in definitions, it's a direct menu field in the input.
2329
- # So, it should be [1, [11, "message1", "id"]] or [1, [12, "message1"]]
2330
- # For now, let's keep it simple as [1, [11, option, None]] or similar if the definition allows.
2331
- # The `all_block_definitions` has `[1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]`
2332
- # Let's use that format, but without the specific ID for now.
2333
- info["inputs"]["BROADCAST_INPUT"] = {"kind": "value", "value": option} # Store as a value for now
 
 
 
 
2334
 
2335
  # Conditional inputs (Boolean blocks)
2336
  elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]:
@@ -2462,6 +2368,7 @@ def generate_plan(generated_input, opcode_keys, pseudo_code):
2462
  info["mutation"]["argumentnames"].append(f"arg{idx+1}") # Placeholder name for mutation
2463
 
2464
  info["inputs"][arg_input_name] = parse_reporter_or_value(arg_val_str, key, pick_key, all_generated_blocks) # Pass current block's key
 
2465
  i += 1 # Move to the next line
2466
 
2467
  # Final pass to ensure last blocks have next: None (already handled by stack pops)
@@ -2828,6 +2735,18 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
2828
  processed_block["fields"][field_name] = field_value
2829
  else:
2830
  processed_block["fields"][field_name] = field_value
 
 
 
 
 
 
 
 
 
 
 
 
2831
 
2832
  # Remove unwanted keys from the processed block (if somehow present)
2833
  keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
@@ -2837,7 +2756,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json):
2837
 
2838
  processed_blocks[block_id] = processed_block
2839
  return processed_blocks
2840
-
2841
  #################################################################################################################################################################
2842
  #--------------------------------------------------[Unique secret key for skelton json to make sure it donot overwrite each other]-------------------------------
2843
  #################################################################################################################################################################
@@ -3097,7 +3016,8 @@ def _find_all_opcodes(code_block: str) -> list[str]:
3097
  # --- Multi-line Control Blocks (most specific, non-greedy) ---
3098
  (r"if <.+?> then(?:.|\n)+?else(?:.|\n)+?end", "control_if_else"), #(to test muliple stack)
3099
  (r"forever", "control_forever"),
3100
- (r"if <.+?> then", "control_if"),
 
3101
  (r"repeat until <.+?>", "control_repeat_until"),
3102
  (r"repeat\s+(?:\(.+?\)|\[.+?(?:\s+v)?\]|\S+)", "control_repeat"),
3103
  (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])(?!\s+sounds)", "control_stop"),
@@ -3153,7 +3073,7 @@ def _find_all_opcodes(code_block: str) -> list[str]:
3153
  # (r"play sound \[.+? v\] until done", "sound_playuntildone"),
3154
  # (r"start sound \[.+? v\]", "sound_play"),
3155
  (r"play sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)\s+until done", "sound_playuntildone"),
3156
- (r"start sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)", "sound_play"),
3157
  (r"stop all sounds", "sound_stopallsounds"),
3158
  (r"change volume by\s*(?:\((.+?)\)|\[(.+?)\]|(.+))", "sound_changevolumeby"),
3159
  (r"""set\ volume\ to\s+\(?\s*(?:-?\d+(?:\.\d+)?|\[?[a-zA-Z_][\w\s]*\]?(?:\ v)?)\s*\)?\s*%?""", "sound_setvolumeto"),
@@ -3257,75 +3177,49 @@ def analyze_opcode_counts(pseudo_code: str) -> list[dict]:
3257
  #--------------------------------------------------[Helper function to seperate an correct the json]-------------------------------------------------------------
3258
  #################################################################################################################################################################
3259
 
3260
- # def separate_scripts(pseudocode_string):
3261
- # """
3262
- # Separates a block of Scratch pseudocode into a list of individual scripts.
3263
-
3264
- # This function finds the start of each "hat" block and slices the
3265
- # original string to capture the full code block for each script,
3266
- # providing a more robust and reliable separation.
3267
-
3268
- # Args:
3269
- # pseudocode_string (str): A string containing Scratch pseudocode.
3270
-
3271
- # Returns:
3272
- # list: A list of strings, where each string is a complete,
3273
- # separated script.
3274
- # """
3275
- # # Define the "hat" block patterns with more robust regex.
3276
- # # We use a non-capturing group (?:...) for the patterns.
3277
- # # We use a logical OR (|) to combine them into a single pattern.
3278
- # delimiter_patterns = (
3279
- # r"when green flag clicked|when flag clicked|when \S+ key pressed|"
3280
- # r"when this sprite clicked|when backdrop switches to \[.*?\]|"
3281
- # r"when I receive \[.*?\]|when \[.*?\] > \[.*?\]"
3282
- # )
3283
-
3284
- # # Use re.finditer to get an iterator of all hat block matches.
3285
- # # The `re.DOTALL` flag allows the '.' to match newlines.
3286
- # matches = list(re.finditer(delimiter_patterns, pseudocode_string, flags=re.DOTALL | re.IGNORECASE))
3287
-
3288
- # scripts = []
3289
- # # If no matches are found, return an empty list.
3290
- # if not matches:
3291
- # return []
3292
-
3293
- # # Iterate through the matches to slice the original string.
3294
- # for i in range(len(matches)):
3295
- # start = matches[i].start()
3296
- # end = matches[i+1].start() if i + 1 < len(matches) else len(pseudocode_string)
3297
-
3298
- # # Slice the pseudocode string from the start of one match to the start
3299
- # # of the next, or to the end of the string.
3300
- # script = pseudocode_string[start:end]
3301
- # scripts.append(script.strip())
3302
-
3303
- # return scripts
3304
  def separate_scripts(pseudocode_string):
3305
  """
3306
- Split a block of Scratch pseudocode into individual scripts.
3307
- Each script starts at a 'when ...' hat block that appears at the start
3308
- of a line (leading whitespace allowed).
3309
- """
3310
- # Robust, non-capturing hat-block patterns (no capturing groups!)
3311
- patterns = [
3312
- r"when\s+(?:green\s+flag\s+)?click(?:ed)?\b", # when flag clicked / when green flag clicked
3313
- r"when\s+(?:\S+(?:\s+\S+)*)\s+key\s+press(?:ed)?\b", # when space key pressed / when up arrow key press
3314
- r"when\s+this\s+sprite\s+click(?:ed)?\b", # when this sprite clicked
3315
- r"when\s+backdrop\s+switch(?:es|ed)?\s+to\s*\[[^\]]*\]", # when backdrop switches to [..]
3316
- r"when\s+I\s+receive(?:d)?\s*\[[^\]]*\]", # when I receive [..] / when I received [..]
3317
- r"when\s*\[[^\]]*\]\s*>\s*\[[^\]]*\]" # when [sensor] > [value]
3318
- ]
3319
 
3320
- # Build a lookahead that finds positions just before a hat block at line-start
3321
- lookahead = r"(?=^\s*(?:{}))".format("|".join(patterns))
 
3322
 
3323
- # Use MULTILINE so ^ matches start of lines, DOTALL to allow patterns that may include brackets/newlines as needed
3324
- parts = re.split(lookahead, pseudocode_string, flags=re.IGNORECASE | re.MULTILINE | re.DOTALL)
3325
-
3326
- # Filter out empties and strip leading/trailing whitespace/newlines
3327
- scripts = [p.strip() for p in parts if p and p.strip()]
3328
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3329
  return scripts
3330
 
3331
  def transform_logic_to_action_flow(source_data, description=""):
@@ -3393,37 +3287,38 @@ def block_builder(opcode_count,pseudo_code):
3393
  #--------------------------------------------------[Example use of the function here]----------------------------------------------------------------------------
3394
  #################################################################################################################################################################
3395
 
3396
- initial_opcode_counts = [
3397
- {"opcode": "event_whenbroadcastreceived","count": 1},
3398
- {"opcode": "control_forever","count": 1},
3399
- {"opcode": "control_if","count": 1},
3400
- {"opcode": "sensing_istouching","count": 1},
3401
- {"opcode": "looks_hide","count": 1},
3402
- {"opcode": "looks_switchbackdropto","count": 1},
3403
- {"opcode": "event_broadcast","count": 1},
3404
- {"opcode": "control_stop","count": 1}
3405
- ]
3406
- pseudo_code="""
3407
- when I receive [Game Start v]
3408
- forever
3409
- if <touching [obstacle v]?> then
3410
- hide
3411
- switch backdrop to [loose v]
3412
- broadcast [Game Over v]
3413
- stop all
3414
- end
3415
- end
3416
- """
 
3417
  # print(pseudo_code)
3418
  # opcode_counts_result = analyze_opcode_counts(pseudo_code)
3419
  # generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_counts_result, all_block_definitions)
3420
  # all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
3421
  # processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
3422
  # renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences)
3423
- # print(opcode_counts_result)
3424
  # print("--------------\n\n")
3425
  # print(processed_blocks)
3426
  # print("--------------\n\n")
3427
- # print(initial_opcode_occurrences)
3428
  # print("--------------\n\n")
3429
- # print(renamed_blocks)
 
1104
  block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields)
1105
  if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id
1106
  all_generated_blocks[list_block_id]["parent"] = block_id
1107
+ return {"kind": "block", "block": block_id}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1108
 
1109
  # [ORDER NO: ]
1110
  # (pick random () to ()) (operator_random)
 
1203
  return {"kind": "block", "block": block_id}
1204
 
1205
  # [ORDER NO: ]
1206
+
 
 
 
 
 
 
 
 
 
 
 
 
 
1207
  def extract_mathop_of_expression(text):
1208
  # Match the function like [sqrt v] of
1209
  m = re.search(r"\[([^\]]+)\s*v\]\s+of\s+\(", text)
 
1298
  block_id = _register_block("looks_backdropnumbername", parent_key, True, pick_key_func, all_generated_blocks, fields=fields)
1299
  return {"kind": "block", "block": block_id}
1300
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1301
  # [ORDER NO: ]
1302
  # (distance to ()) (sensing_distanceto) - handle with or without 'v'
1303
  m = re.search(r"distance to \((.+?)\)", text)
 
1370
 
1371
 
1372
  # [ORDER NO: ALWAYS LAST]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1373
  # Function to strip outer parentheses if they enclose the entire expression
1374
  def strip_outer_parentheses(s):
1375
  s = s.strip()
 
1478
  boolean operators (and, or, not), and other sensing conditions.
1479
  """
1480
  s = stmt.strip()
1481
+ print("the processed text on parse_conditon-1-->",s )
1482
  s = extract_condition_balanced(s)
1483
  s_lower = s.lower()
1484
+ print("the processed text on parse_conditon-2-->",s_lower)
1485
  # 1) Boolean NOT: `not <...>`
1486
  m_not = re.fullmatch(r"\s*(?:<\s*)?not\s+(.+?)(?:\s*>)?\s*", s_lower, re.IGNORECASE)
1487
  if m_not:
 
1507
  if cond1_obj.get("kind") == "block": all_generated_blocks[cond1_obj["block"]]["parent"] = block_id
1508
  if cond2_obj.get("kind") == "block": all_generated_blocks[cond2_obj["block"]]["parent"] = block_id
1509
  return {"kind": "block", "block": block_id}
1510
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1511
  m_comp = re.fullmatch(r"\s*(?:<\s*)?(?P<left>.+?)\s*(?P<op><|=|>)\s*(?P<right>.+?)(?:\s*>)?\s*",s_lower,re.VERBOSE)
1512
  if m_comp:
1513
  left_txt = m_comp.group('left')
 
1549
  return {"kind": "block", "block": block_id}
1550
 
1551
  # 5) Touching object: <touching [edge v]?>
 
 
1552
  m_touch = re.fullmatch(r"""\s*<?\s*touching\s*\[\s*(?P<sprite>[^\]]+?)\s*(?:v)?\s*\]\s*(?:\?)?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE)
1553
  if m_touch:
1554
  sprite = m_touch.group('sprite').strip()
 
1566
  return {"kind":"block","block":bid}
1567
 
1568
  # 6) Touching color: <touching color [#rrggbb]?>
1569
+ COLOR_MAP = {"red": "#FF0000","yellow": "#FFFF00","pink": "#FFC0CB","blue": "#0000FF","green": "#008000",
1570
+ "black": "#000000","white": "#FFFFFF","orange": "#FFA500","purple": "#800080","gray": "#808080",}
1571
+ # m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s_lower)
1572
+ m = re.search(r"touching color [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower)
1573
  if m:
1574
+ color_str = m.group(1).strip()
1575
+
1576
+ # Remove trailing " v" if present (Scratch dropdown)
1577
+ if color_str.endswith(" v"):
1578
+ color_str = color_str[:-2].strip()
1579
+
1580
+ # Normalize to hex
1581
+ if not color_str.startswith("#"):
1582
+ color_str = COLOR_MAP.get(color_str.lower(), None)
1583
+ if color_str is None:
1584
+ raise ValueError(f"❌ Unknown color name: {m.group(1)}")
1585
+
1586
+ inputs = {"COLOR": {"kind": "value", "value": color_str}}
1587
+ print("<touching color [#rrggbb]?> : ", inputs)
1588
+ block_id = _register_block(
1589
+ "sensing_touchingcolor", parent_key, True,
1590
+ pick_key_func, all_generated_blocks, inputs=inputs
1591
+ )
1592
  return {"kind": "block", "block": block_id}
1593
 
1594
  # 7) Color is touching color: <color [#rggbb] is touching [#rrggbb]?>
1595
+ m = re.search(r"color [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]? is touching [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower)
1596
  if m:
1597
+ c1, c2 = m.group(1).strip(), m.group(2).strip()
1598
+
1599
+ # Cleanup dropdown suffix
1600
+ if c1.endswith(" v"):
1601
+ c1 = c1[:-2].strip()
1602
+ if c2.endswith(" v"):
1603
+ c2 = c2[:-2].strip()
1604
+
1605
+ # Normalize each color
1606
+ if not c1.startswith("#"):
1607
+ c1 = COLOR_MAP.get(c1.lower(), None)
1608
+ if c1 is None:
1609
+ raise ValueError(f"❌ Unknown color name: {m.group(1)}")
1610
+ if not c2.startswith("#"):
1611
+ c2 = COLOR_MAP.get(c2.lower(), None)
1612
+ if c2 is None:
1613
+ raise ValueError(f"❌ Unknown color name: {m.group(2)}")
1614
+
1615
+ inputs = {
1616
+ "COLOR1": {"kind": "value", "value": c1},
1617
+ "COLOR2": {"kind": "value", "value": c2},
1618
+ }
1619
+ print("<color [#rrggbb] is touching [#rrggbb]?> : ", inputs)
1620
+
1621
+ block_id = _register_block(
1622
+ "sensing_coloristouchingcolor", parent_key, True,
1623
+ pick_key_func, all_generated_blocks, inputs=inputs
1624
+ )
1625
+ return {"kind": "block", "block": block_id}
1626
 
1627
  # 8) Key pressed: <key [key v] pressed?>
 
 
 
 
 
 
 
 
 
1628
  m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?", s_lower, re.IGNORECASE)
1629
  if m:
1630
  option = m.group(1).strip()
 
1812
  return f"{opcode}_{idx + 1}"
1813
  ptrs[opcode] += 1
1814
  return lst[idx]
1815
+
1816
+ # Helper: lookahead for `else` that matches this `if`
1817
+ def classify_with_else_context(lines, i, stripped_line):
1818
+ """
1819
+ If stripped_line starts with an if-expression (e.g. "if <...> then"),
1820
+ scan forward to see if a corresponding 'else' exists before the matching 'end'.
1821
+ Returns a tuple (opcode, ntype).
1822
+ If this helper doesn't apply, it returns ("", "") (no None).
1823
+ """
1824
+ l = stripped_line.strip().lower()
1825
+ if not l.startswith("if <"):
1826
+ return ("", "")
1827
+
1828
+ # Lookahead with nesting: nested 'if' increments depth.
1829
+ depth = 0
1830
+ for j in range(i + 1, len(lines)):
1831
+ look = lines[j].strip().lower()
1832
+ # ignore blank lines and comments
1833
+ if not look or look.startswith("//"):
1834
+ continue
1835
+
1836
+ # if we find another 'if <' before an 'end', that's a nested if -> depth+1
1837
+ if look.startswith("if <"):
1838
+ depth += 1
1839
+ continue
1840
+
1841
+ # an 'end' closes the most recent if (nested or this one)
1842
+ if look == "end":
1843
+ if depth == 0:
1844
+ # reached the end for this if without seeing an else
1845
+ break
1846
+ depth -= 1
1847
+ continue
1848
+
1849
+ # an else that occurs when depth == 0 belongs to this if
1850
+ if look == "else" and depth == 0:
1851
+ return ("control_if_else", "c_block")
1852
+
1853
+ # no matching else found before the corresponding end -> plain control_if
1854
+ return ("control_if", "c_block")
1855
 
1856
  # Change: Start with an empty dictionary. Blocks will be added only as they are parsed.
1857
  all_generated_blocks = {}
 
1860
  top_level_script_keys = []
1861
 
1862
  lines = pseudo_code.splitlines()
1863
+ print("The lines here are:\n", lines)
1864
  i = 0
1865
  while i < len(lines):
1866
  raw_line = lines[i]
 
1882
  # This check now looks for any C-Block, which is correct for this context.
1883
  if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block":
1884
 
1885
+ # Explicitly upgrade the block from a 'control_if' to a 'control_if_else'.
 
1886
  owner_block = all_generated_blocks[popped_owner_key]
1887
  owner_block["op_code"] = "control_if_else"
1888
 
1889
  # Push a new scope for the 'else' substack, with the same owner.
1890
  stack.append((current_indent, popped_owner_key, None))
1891
  else:
 
 
1892
  print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}")
1893
  stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain))
1894
  i += 1
 
1919
 
1920
  current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1]
1921
 
1922
+ # RUN the lookahead classifier first; it never returns None.
1923
+ override_opcode, override_ntype = classify_with_else_context(lines, i, stripped_line)
1924
+ if override_opcode:
1925
+ opcode, ntype = override_opcode, override_ntype
1926
+ else:
1927
+ # fallback to your original classify (unchanged)
1928
+ opcode, ntype = classify(stripped_line)
1929
+
1930
  stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip()
1931
+ print("The opcode here is", opcode)
1932
+ print("The ntype here is", ntype)
1933
  if opcode is None:
1934
  i += 1
1935
  continue
 
1983
  # are passed the *newly created block's ID* as the parent_key for nested inputs)
1984
  # Numeric inputs (e.g., move (10) steps, wait (1) seconds)
1985
  if opcode == "motion_movesteps":
 
1986
  m = re.search(r"move\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*steps", stmt_for_parse, re.IGNORECASE)
 
1987
  print("motion_movesteps : ",m.group(1).strip())
1988
  if m: info["inputs"]["STEPS"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks)
 
 
1989
  elif opcode == "motion_turnright" or opcode == "motion_turnleft":
 
 
1990
  m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE)
 
1991
  if m: info["inputs"]["DEGREES"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks)
 
 
1992
  elif opcode == "motion_gotoxy":
 
 
1993
  m_x = re.search(r"x:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*y:|$)", stmt_for_parse, re.IGNORECASE)
1994
  m_y = re.search(r"y:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*$)", stmt_for_parse, re.IGNORECASE)
1995
  if m_x: info["inputs"]["X"] = parse_reporter_or_value( m_x.group(1).strip("() "), key, pick_key, all_generated_blocks)
1996
  if m_y: info["inputs"]["Y"] = parse_reporter_or_value( m_y.group(1).strip("() "), key, pick_key, all_generated_blocks)
 
 
 
 
 
 
1997
  elif opcode == "motion_glidesecstoxy":
1998
  stmt = _auto_balance(stmt_for_parse)
1999
  # m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", stmt_for_parse, re.IGNORECASE)
 
2137
 
2138
  # Dropdown/Menu inputs (UPDATED)
2139
  elif opcode == "motion_goto":
2140
+ m = re.search(r"go to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))", stmt_for_parse, re.IGNORECASE)
2141
  if m:
2142
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2143
+ option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option)
2144
+ menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks,
2145
+ fields={"TO": [option_val, None]})
 
 
2146
  info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id}
2147
+
2148
  elif opcode == "motion_glideto":
2149
+ m_secs = re.search(
2150
+ r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*secs to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2151
  if m_secs:
2152
+ secs = float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))
2153
+ option = (m_secs.group(2) or m_secs.group(3) or m_secs.group(4)).strip()
2154
+ option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option)
2155
+ info["inputs"]["SECS"] = {"kind": "value", "value": secs}
2156
+ menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks,
2157
+ fields={"TO": [option_val, None]})
 
 
2158
  info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id}
2159
+
2160
  elif opcode == "motion_pointtowards":
2161
+ m = re.search(r"point towards\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2162
  if m:
2163
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2164
+ option_val = {"mouse-pointer": "_mouse_"}.get(option, option)
2165
+ menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks,
2166
+ fields={"TOWARDS": [option_val, None]})
 
2167
  info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id}
2168
+
2169
  elif opcode == "sensing_keypressed":
2170
+ m = re.search(r"key\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\s*pressed\?",stmt_for_parse, re.IGNORECASE)
2171
  if m:
2172
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2173
+ menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks,
2174
+ fields={"KEY_OPTION": [option, None]})
2175
  info["inputs"]["KEY_OPTION"] = {"kind": "block", "block": menu_block_id}
2176
+
2177
  elif opcode == "sensing_touchingobject":
2178
+ m = re.search(r"touching\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\?",stmt_for_parse, re.IGNORECASE)
2179
  if m:
2180
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2181
+ option_val = {"mouse-pointer": "_mouse_", "edge": "_edge_"}.get(option, option)
2182
+ menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks,
2183
+ fields={"TOUCHINGOBJECTMENU": [option_val, None]})
 
 
2184
  info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "block", "block": menu_block_id}
2185
+
2186
  elif opcode == "control_create_clone_of":
2187
+ m = re.search(r"create clone of\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2188
  if m:
2189
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2190
+ option_val = {"myself": "_myself_"}.get(option, option)
2191
+ menu_block_id = _register_block("control_create_clone_of_menu", key, True, pick_key, all_generated_blocks,
2192
+ fields={"CLONE_OPTION": [option_val, None]})
 
2193
  info["inputs"]["CLONE_OPTION"] = {"kind": "block", "block": menu_block_id}
2194
  elif opcode in ["sound_playuntildone", "sound_play"]:
2195
  # m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE)
 
2201
  menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]})
2202
  info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id}
2203
  elif opcode == "looks_switchcostumeto":
2204
+ m = re.search(r"switch costume to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2205
  if m:
2206
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2207
+ menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks,
2208
+ fields={"COSTUME": [option, None]})
2209
  info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id}
2210
+
2211
  elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]:
2212
+ m = re.search(r"switch backdrop to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2213
  if m:
2214
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2215
+ menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks,
2216
+ fields={"BACKDROP": [option, None]})
2217
  info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id}
2218
+
2219
  elif opcode in ["event_broadcast", "event_broadcastandwait"]:
2220
+ m = re.search(r"broadcast\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE)
2221
+ if m:
2222
+ option = (m.group(1) or m.group(2) or m.group(3)).strip()
2223
+ info["inputs"]["BROADCAST_INPUT"] = {"kind": "value", "value": option}
2224
+ elif opcode == "control_stop":
2225
+ # m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$",
2226
+ m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$",
2227
+ stmt_for_parse, re.IGNORECASE)
2228
  if m:
2229
+ option = m.group(1).strip().lower()
2230
+ # Normalize casing to match Scratch’s expected field values
2231
+ if option == "all":
2232
+ stop_val = "all"
2233
+ elif option == "this script":
2234
+ stop_val = "this script"
2235
+ else:
2236
+ stop_val = "other scripts in sprite"
2237
+
2238
+ info.setdefault("fields", {})
2239
+ info["fields"]["STOP_OPTION"] = [stop_val, None]
2240
 
2241
  # Conditional inputs (Boolean blocks)
2242
  elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]:
 
2368
  info["mutation"]["argumentnames"].append(f"arg{idx+1}") # Placeholder name for mutation
2369
 
2370
  info["inputs"][arg_input_name] = parse_reporter_or_value(arg_val_str, key, pick_key, all_generated_blocks) # Pass current block's key
2371
+
2372
  i += 1 # Move to the next line
2373
 
2374
  # Final pass to ensure last blocks have next: None (already handled by stack pops)
 
2735
  processed_block["fields"][field_name] = field_value
2736
  else:
2737
  processed_block["fields"][field_name] = field_value
2738
+
2739
+ if opcode == "control_stop":
2740
+ stop_option = processed_block["fields"].get("STOP_OPTION", ["", None])[0]
2741
+ hasnext_val = "true" if stop_option == "other scripts in sprite" else "false"
2742
+ if "mutation" not in processed_block:
2743
+ processed_block["mutation"] = {
2744
+ "tagName": "mutation",
2745
+ "children": [],
2746
+ "hasnext": hasnext_val
2747
+ }
2748
+ else:
2749
+ processed_block["mutation"]["hasnext"] = hasnext_val
2750
 
2751
  # Remove unwanted keys from the processed block (if somehow present)
2752
  keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"]
 
2756
 
2757
  processed_blocks[block_id] = processed_block
2758
  return processed_blocks
2759
+
2760
  #################################################################################################################################################################
2761
  #--------------------------------------------------[Unique secret key for skelton json to make sure it donot overwrite each other]-------------------------------
2762
  #################################################################################################################################################################
 
3016
  # --- Multi-line Control Blocks (most specific, non-greedy) ---
3017
  (r"if <.+?> then(?:.|\n)+?else(?:.|\n)+?end", "control_if_else"), #(to test muliple stack)
3018
  (r"forever", "control_forever"),
3019
+ # (r"if <.+?> then", "control_if"),
3020
+ (r"if <.+?> then(?:(?!else).|\n)+?end", "control_if"),
3021
  (r"repeat until <.+?>", "control_repeat_until"),
3022
  (r"repeat\s+(?:\(.+?\)|\[.+?(?:\s+v)?\]|\S+)", "control_repeat"),
3023
  (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])(?!\s+sounds)", "control_stop"),
 
3073
  # (r"play sound \[.+? v\] until done", "sound_playuntildone"),
3074
  # (r"start sound \[.+? v\]", "sound_play"),
3075
  (r"play sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)\s+until done", "sound_playuntildone"),
3076
+ (r"start sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)", "sound_play"),
3077
  (r"stop all sounds", "sound_stopallsounds"),
3078
  (r"change volume by\s*(?:\((.+?)\)|\[(.+?)\]|(.+))", "sound_changevolumeby"),
3079
  (r"""set\ volume\ to\s+\(?\s*(?:-?\d+(?:\.\d+)?|\[?[a-zA-Z_][\w\s]*\]?(?:\ v)?)\s*\)?\s*%?""", "sound_setvolumeto"),
 
3177
  #--------------------------------------------------[Helper function to seperate an correct the json]-------------------------------------------------------------
3178
  #################################################################################################################################################################
3179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3180
  def separate_scripts(pseudocode_string):
3181
  """
3182
+ Separates a block of Scratch pseudocode into a list of individual scripts.
 
 
 
 
 
 
 
 
 
 
 
 
3183
 
3184
+ This function finds the start of each "hat" block and slices the
3185
+ original string to capture the full code block for each script,
3186
+ providing a more robust and reliable separation.
3187
 
3188
+ Args:
3189
+ pseudocode_string (str): A string containing Scratch pseudocode.
 
 
 
3190
 
3191
+ Returns:
3192
+ list: A list of strings, where each string is a complete,
3193
+ separated script.
3194
+ """
3195
+ # Define the "hat" block patterns with more robust regex.
3196
+ # We use a non-capturing group (?:...) for the patterns.
3197
+ # We use a logical OR (|) to combine them into a single pattern.
3198
+ delimiter_patterns = (
3199
+ r"when green flag clicked|when flag clicked|when \S+ key pressed|"
3200
+ r"when this sprite clicked|when backdrop switches to \[.*?\]|"
3201
+ r"when I receive \[.*?\]|when \[.*?\] > \[.*?\]"
3202
+ )
3203
+
3204
+ # Use re.finditer to get an iterator of all hat block matches.
3205
+ # The `re.DOTALL` flag allows the '.' to match newlines.
3206
+ matches = list(re.finditer(delimiter_patterns, pseudocode_string, flags=re.DOTALL | re.IGNORECASE))
3207
+
3208
+ scripts = []
3209
+ # If no matches are found, return an empty list.
3210
+ if not matches:
3211
+ return []
3212
+
3213
+ # Iterate through the matches to slice the original string.
3214
+ for i in range(len(matches)):
3215
+ start = matches[i].start()
3216
+ end = matches[i+1].start() if i + 1 < len(matches) else len(pseudocode_string)
3217
+
3218
+ # Slice the pseudocode string from the start of one match to the start
3219
+ # of the next, or to the end of the string.
3220
+ script = pseudocode_string[start:end]
3221
+ scripts.append(script.strip())
3222
+
3223
  return scripts
3224
 
3225
  def transform_logic_to_action_flow(source_data, description=""):
 
3287
  #--------------------------------------------------[Example use of the function here]----------------------------------------------------------------------------
3288
  #################################################################################################################################################################
3289
 
3290
+ # initial_opcode_counts = [
3291
+ # {"opcode": "event_whenbroadcastreceived","count": 1},
3292
+ # {"opcode": "control_forever","count": 1},
3293
+ # {"opcode": "control_if","count": 1},
3294
+ # {"opcode": "sensing_istouching","count": 1},
3295
+ # {"opcode": "looks_hide","count": 1},
3296
+ # {"opcode": "looks_switchbackdropto","count": 1},
3297
+ # {"opcode": "event_broadcast","count": 1},
3298
+ # {"opcode": "control_stop","count": 1}
3299
+ # ]
3300
+ # pseudo_code="""
3301
+ # when [space v] key pressed
3302
+ # start sound (Meow)
3303
+ # next costume
3304
+ # repeat (10)
3305
+ # change y by (10)
3306
+ # wait (0.0001) seconds
3307
+ # change y by (-10)
3308
+ # wait (0.0001) seconds
3309
+ # end
3310
+ # next costume
3311
+ # """
3312
  # print(pseudo_code)
3313
  # opcode_counts_result = analyze_opcode_counts(pseudo_code)
3314
  # generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_counts_result, all_block_definitions)
3315
  # all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code)
3316
  # processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json)
3317
  # renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences)
3318
+ # print(generated_output_json)
3319
  # print("--------------\n\n")
3320
  # print(processed_blocks)
3321
  # print("--------------\n\n")
3322
+ # print(all_generated_blocks)
3323
  # print("--------------\n\n")
3324
+ # print(opcode_counts_result)