diff --git "a/utils/block_relation_builder.py" "b/utils/block_relation_builder.py" --- "a/utils/block_relation_builder.py" +++ "b/utils/block_relation_builder.py" @@ -5,6 +5,13 @@ from collections import defaultdict, Counter import secrets import string from typing import Dict, Any, TypedDict,Tuple +import difflib +try: + import enchant + d = enchant.Dict("en_US") +except Exception: + d = None + ################################################################################################################################################################# @@ -1029,7 +1036,10 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block # [ORDER NO: ] m_paren_var = re.fullmatch(r"\(([^)]+)\)", text) m_cos = re.fullmatch(r"""\(\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)""",text,re.IGNORECASE | re.VERBOSE) - if m_paren_var and not m_cos: + m_op = re.search(r"""(?:\[\s*([^\]]+?)\s*v\s*\]|\(?\s*([a-z0-9^ ]+?)\s*\)?)\s+of\s+(\(|\[)""",text,re.IGNORECASE | re.VERBOSE,) + m_bac = re.fullmatch(r"""\(?\s*backdrop\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?""",text,re.IGNORECASE | re.VERBOSE,) + m_cur = re.search(r"current\s*\[([^\]]+)\s*v\]", text, re.IGNORECASE) + if m_paren_var and not m_cos and not m_op and not m_bac and not m_cur: potential = m_paren_var.group(1).strip() # If it's literally “[name v]”, pull out just “name” m_bracket_var = re.fullmatch(r"\[\s*([^\]]+?)\s*v\s*\]", potential) @@ -1052,11 +1062,11 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block # [ORDER NO: ] # Handle plain variable names like "score", "number 1", "total score" - if re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_ ]*", text): # Allow spaces for "number 1" etc. - # Exclude known simple reporters that don't have 'v' or parentheses - if text not in simple_reporters: - print("the simple reporters that number 1", text) - block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, fields={"VARIABLE": [text, None]}) + if re.fullmatch(r"[a-zA-Z_][a-zA-Z0-9_ ]*", text): # Allow spaces for "number 1" etc. + normalized = re.sub(r"\s+v$", "", text).strip() + if normalized not in simple_reporters: + print("the simple reporters that number 1", normalized) + block_id = _register_block("data_variable",parent_key,True,pick_key_func,all_generated_blocks,fields={"VARIABLE": [normalized, None]}) return {"kind": "block", "block": block_id} # [ORDER NO: ] @@ -1104,47 +1114,7 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id all_generated_blocks[list_block_id]["parent"] = block_id - return {"kind": "block", "block": block_id} - - # # [ORDER NO: ] - # # (item (index) of [list v]) (data_itemoflist) - # m = re.search(r"item \((.+?)\) of \((.+?)\)", text) - # if not m: - # m = re.search(r"item \((.+?)\) of \[([^\]]+)\s*v\]", text) - # if m: - # index_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) - # list_name = m.group(2).strip() - # print("(item (index) of [list v]) : ",index_obj) - # print("(item (index) of [list v]) : ",list_name) - # # Create data_list shadow block for the list name - # list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]}) - - # inputs = {"INDEX": index_obj, "LIST": {"kind": "block", "block": list_block_id}} - # fields = {} # No fields in data_itemoflist itself for the list name - # block_id = _register_block("data_itemoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) - # if index_obj.get("kind") == "block": all_generated_blocks[index_obj["block"]]["parent"] = block_id - # all_generated_blocks[list_block_id]["parent"] = block_id - # return {"kind": "block", "block": block_id} - - # # [ORDER NO: ] - # # (item # of [item] in [list v]) (data_itemnumoflist) - # m = re.search(r"item # of \((.+?)\) in \((.+?)\)", text) - # if not m: - # m = re.search(r"item # of \[([^\]]+)\] in \[([^\]]+)\s*v\]", text) - # if m: - # item_obj = parse_reporter_or_value(m.group(1).strip(), parent_key, pick_key_func, all_generated_blocks) - # list_name = m.group(2).strip() - # print("(item # of [item] in [list v]) : ",index_obj) - # print("(item # of [item] in [list v]) : ",list_name) - # # Create data_list shadow block for the list name - # list_block_id = _register_block("data_list", parent_key, True, pick_key_func, all_generated_blocks, fields={"LIST": [list_name, None]}) - - # inputs = {"ITEM": item_obj, "LIST": {"kind": "block", "block": list_block_id}} - # fields = {} # No fields in data_itemnumoflist itself for the list name - # block_id = _register_block("data_itemnumoflist", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) - # if item_obj.get("kind") == "block": all_generated_blocks[item_obj["block"]]["parent"] = block_id - # all_generated_blocks[list_block_id]["parent"] = block_id - # return {"kind": "block", "block": block_id} + return {"kind": "block", "block": block_id} # [ORDER NO: ] # (pick random () to ()) (operator_random) @@ -1244,79 +1214,50 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block # [ORDER NO: ] # (() of ()) (operator_mathop) - handle variable for function type - # m = re.search(r"\[([^\]]+)\s*v\] of \((.+?)\)", text) # e.g. [sqrt v] of ((x pos) * (x pos)) - # if m: - # print("(() of ()) (operator_mathop):[left] ",m.group(1).strip()) - # print("(() of ()) (operator_mathop):[rigth] ",m.group(2).strip()) - # func_type = m.group(1).strip() - # value_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, pick_key_func, all_generated_blocks) - # inputs = {"NUM": value_obj} - # fields = {"OPERATOR": [func_type.upper(), None]} - # block_id = _register_block("operator_mathop", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) - # if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id - # return {"kind": "block", "block": block_id} - # (() of ()) (operator_mathop) - handle variable for function type - - def extract_mathop_of_expression(text): - # Match the function like [sqrt v] of - m = re.search(r"\[([^\]]+)\s*v\]\s+of\s+\(", text) - if not m: - return None - - func_type = m.group(1).strip().lower() - - # Start parsing from the first '(' after 'of ' - start_idx = m.end() - 1 # position of the '(' + def extract_mathop_of_expression(text: str): + m = re.search(r"""(?:\[\s*([^\]]+?)\s*v\s*\]|\(?\s*([a-z0-9^ ]+?)\s*\)?)\s+of\s+(\(|\[)""",text,re.IGNORECASE | re.VERBOSE,) + if not m: return None + func_type = (m.group(1) or m.group(2)).strip() + start_idx = m.end() - 1 depth = 0 end_idx = start_idx - for i in range(start_idx, len(text)): - if text[i] == '(': + if text[i] in "([": # opening bracket/paren depth += 1 - elif text[i] == ')': + elif text[i] in ")]": # closing bracket/paren depth -= 1 if depth == 0: end_idx = i break - inner_expr = text[start_idx + 1:end_idx].strip() return func_type, inner_expr result = extract_mathop_of_expression(text) if result: func_type, expr = result - allowed_math_ops = { - "abs", "floor", "ceiling", "sqrt", "sin", "cos", "tan", - "asin", "acos", "atan", "ln", "log", "e ^", "10 ^" - } - - if func_type in allowed_math_ops: - print("(() of ()) (operator_mathop):[left] ", func_type) + allowed_math_ops = {"abs", "floor", "ceiling", "sqrt","sin", "cos", "tan","asin", "acos", "atan","ln", "log", "e ^", "10 ^"} + cleaned = re.sub(r"[^a-z0-9^ ]", "", func_type.lower()).strip() + normalized_map = {op.lower(): op for op in allowed_math_ops} + func_val = None + if cleaned in normalized_map: + func_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + func_val = normalized_map[match[0]] + if func_val is not None: + print("(() of ()) (operator_mathop):[left] ", func_val) print("(() of ()) (operator_mathop):[right] ", expr) - value_obj = parse_reporter_or_value(expr, parent_key, pick_key_func, all_generated_blocks) inputs = {"NUM": value_obj} - fields = {"OPERATOR": [func_type.upper(), None]} - block_id = _register_block("operator_mathop", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) + fields = {"OPERATOR": [func_val.upper(), None]} + block_id = _register_block("operator_mathop",parent_key,True,pick_key_func,all_generated_blocks,inputs=inputs,fields=fields) if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id - print("block_id at operator_mathop:",block_id) + print("block_id at operator_mathop:", block_id) return {"kind": "block", "block": block_id} + else: + print(f"Skipped operator_mathop: {func_type}") - # [ORDER NO: ] - # Also handle direct string for function type (e.g., "abs of (x)") - m = re.search(r"([a-zA-Z]+)\s*of\s*\((.+?)\)", text) - if m: - print("abs of (x):[left] ",m.group(1).strip()) - print("abs of (x):[rigth] ",m.group(2).strip()) - func_type = m.group(1).strip() - value_obj = parse_reporter_or_value(m.group(2).strip(), parent_key, pick_key_func, all_generated_blocks) - inputs = {"NUM": value_obj} - fields = {"OPERATOR": [func_type.upper(), None]} - block_id = _register_block("operator_mathop", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) - if value_obj.get("kind") == "block": all_generated_blocks[value_obj["block"]]["parent"] = block_id - return {"kind": "block", "block": block_id} - - # [ORDER NO: ] # (costume ()) (looks_costumenumbername) - handle with or without 'v' m = re.fullmatch(r"\(?\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?",text,re.IGNORECASE | re.VERBOSE) @@ -1325,187 +1266,156 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block if option is None: raise ValueError(f"Unable to extract costume option from: {text}") option = option.strip() - print("(costume ()) : ", option) - fields = {"NUMBER_NAME": [option, None]} - block_id = _register_block( - "looks_costumenumbername", - parent_key, - True, - pick_key_func, - all_generated_blocks, - fields=fields - ) + valid_options = ["number", "name"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {v.lower(): v for v in valid_options} + if cleaned in normalized_map: + option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + option_val = normalized_map[match[0]] + else: + option_val = option # fallback + print("(costume ()) : ", option_val) + fields = {"NUMBER_NAME": [option_val, None]} + block_id = _register_block("looks_costumenumbername",parent_key,True,pick_key_func,all_generated_blocks,fields=fields) if block_id is None: raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}") return {"kind": "block", "block": block_id} # [ORDER NO: ] # (backdrop ()) (looks_backdropnumbername) - handle with or without 'v' - m = re.search(r"backdrop \((.+?)\)", text) - if not m: - m = re.search(r"backdrop \[([^\]]+)\s*v\]", text) + m = re.fullmatch(r"""\(?\s*backdrop\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?""",text,re.IGNORECASE | re.VERBOSE,) if m: - option = m.group(1).strip() - print("(backdrop ()) : ",option) - fields = {"NUMBER_NAME": [option, None]} - block_id = _register_block("looks_backdropnumbername", parent_key, True, pick_key_func, all_generated_blocks, fields=fields) - return {"kind": "block", "block": block_id} - - # # [ORDER NO: ] - # # (costume ()) (looks_costumenumbername) - handle with or without 'v' - # #m = re.search(r"costume \((.+?)\)", text) - # m = re.fullmatch(r"\(?\s*costume\s+(?:\(\s*(.+?)\s*\)|\[\s*([^\]]+?)\s*v\s*\])\s*\)?",text,re.IGNORECASE | re.VERBOSE) - # if m: - # option = (m.group(1) or m.group(2)) - # if option is None: - # raise ValueError(f"Unable to extract costume option from: {text}") - # option = option.strip() - # print("(costume ()) : ", option) - # fields = {"NUMBER_NAME": [option, None]} - # block_id = _register_block( - # "looks_costumenumbername", - # parent_key, - # False, - # pick_key_func, - # all_generated_blocks, - # fields=fields - # ) - # if block_id is None: - # raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}") - # return {"kind": "block", "block": block_id} - - # # [ORDER NO: ] - # # (backdrop ()) (looks_backdropnumbername) - handle with or without 'v' - # m = re.search(r"backdrop \((.+?)\)", text) - # if not m: - # m = re.search(r"backdrop \[([^\]]+)\s*v\]", text) - # if m: - # option = m.group(1).strip() - # print("(backdrop ()) : ",option) - # fields = {"NUMBER_NAME": [option, None]} - # block_id = _register_block("looks_backdropnumbername", parent_key, False, pick_key_func, all_generated_blocks, fields=fields) - # return {"kind": "block", "block": block_id} - - # [ORDER NO: ] - # (distance to ()) (sensing_distanceto) - handle with or without 'v' - m = re.search(r"distance to \((.+?)\)", text) - if not m: - m = re.search(r"distance to \[([^\]]+)\s*v\]", text) - if m: - target = m.group(1).strip() - print("(distance to ()) : ",target) - if target == "mouse-pointer": target_val = "_mouse_" - elif target == "edge": target_val = "_edge_" - else: target_val = target - - # This block has a dropdown FIELD, not an input that links to a shadow block - fields = {"TARGET": [target_val, None]} - block_id = _register_block("sensing_distanceto", parent_key, True, pick_key_func, all_generated_blocks, fields=fields) + option = (m.group(1) or m.group(2)) + if option is None: raise ValueError(f"Unable to extract backdrop option from: {text}") + option = option.strip() + valid_options = ["number", "name"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {v.lower(): v for v in valid_options} + if cleaned in normalized_map: + option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + option_val = normalized_map[match[0]] + else: + option_val = option # fallback + print("(backdrop ()) : ", option_val) + fields = {"NUMBER_NAME": [option_val, None]} + block_id = _register_block( "looks_backdropnumbername", parent_key, True, pick_key_func, all_generated_blocks, fields=fields) + if block_id is None: + raise RuntimeError(f"_register_block failed for: {text} with fields: {fields}") return {"kind": "block", "block": block_id} # [ORDER NO: ] # (current ()) (sensing_current) - handle with or without 'v' - m = re.search(r"current \((.+?)\)", text) + m = re.search(r"distance\s*to\s*[\(\[]\s*([^\]\)]+?)\s*v?\s*[\)\]]", text, re.IGNORECASE) if not m: - m = re.search(r"current \[([^\]]+)\s*v\]", text) + m = re.search(r"distance\s*to\s+([A-Za-z0-9 _\-\']+)", text, re.IGNORECASE) if m: - unit = m.group(1).strip() - print("(current ()) : ",target) - fields = {"CURRENTMENU": [unit.upper(), None]} - block_id = _register_block("sensing_current", parent_key, True, pick_key_func, all_generated_blocks, fields=fields) + target = m.group(1).strip().strip('\'"') + print("(distance to ()) : ", target) + target_val = None + valid_properties = ["mouse-pointer", "mouse pointer"] + cleaned = re.sub(r"[^a-z0-9#]", "", target.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: + target_val = "_mouse_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + target_val = "_mouse_" + else: + val, key = process_text(target) + if key == "sprite": + target_val = val + else: + target_val = "_mouse_" + # Register block + if target_val == "_mouse_": + print("-----------------6-------------------", target_val) + object_menu_id = _register_block("sensing_distancetomenu", parent_key, False, pick_key_func,all_generated_blocks, fields={"DISTANCETOMENU": [target_val, None]}) + inputs = {"DISTANCETOMENU": {"kind": "block", "block": object_menu_id}} + block_id = _register_block("sensing_distanceto", parent_key, True, pick_key_func,all_generated_blocks, inputs=inputs, fields={}) + all_generated_blocks[object_menu_id]["parent"] = block_id + print("all_generated_blocks[object_menu_id]['parent']", all_generated_blocks[object_menu_id]["parent"]) + else: + fields = {"DISTANCETOMENU": [target_val, None]} + print("-----------------5-------------------", target_val) + block_id = _register_block("sensing_distanceto", parent_key, False, pick_key_func,all_generated_blocks, fields=fields) return {"kind": "block", "block": block_id} - + # [ORDER NO: ] # (() of ()) (sensing_of) - Corrected logic #m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text) - m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text) - if m: - prop_str = m.group(1).strip() - obj = (m.group(2) or m.group(3)).strip() - print("(() of ()) (sensing_of) : ",prop_str) - print("(() of ()) (sensing_of) : ",obj) - prop_map = { - "x position": "x position", "y position": "y position", "direction": "direction", - "costume #": "costume number", "costume name": "costume name", "size": "size", - "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" - } - property_value = prop_map.get(prop_str, prop_str) + # m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text) + # if m: + # prop_str = m.group(1).strip() + # obj = (m.group(2) or m.group(3)).strip() + # print("(() of ()) (sensing_of) : ",prop_str) + # print("(() of ()) (sensing_of) : ",obj) + # prop_map = { + # "x position": "x position", "y position": "y position", "direction": "direction", + # "costume #": "costume number", "costume name": "costume name", "size": "size", + # "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" + # } + # property_value = prop_map.get(prop_str, prop_str) - if obj.lower() == "stage": obj_val = "_stage_" - elif obj.lower() == "myself": obj_val = "_myself_" - else: obj_val = obj + # if obj.lower() == "stage": obj_val = "_stage_" + # elif obj.lower() == "myself": obj_val = "_myself_" + # else: obj_val = obj - # Create the sensing_of_object_menu shadow block - object_menu_id = _register_block("sensing_of_object_menu", parent_key, True, pick_key_func, all_generated_blocks, fields={"OBJECT": [obj_val, None]}) + # # Create the sensing_of_object_menu shadow block + # object_menu_id = _register_block("sensing_of_object_menu", parent_key, True, pick_key_func, all_generated_blocks, fields={"OBJECT": [obj_val, None]}) - # Create the main sensing_of block - inputs = {"OBJECT": {"kind": "block", "block": object_menu_id}} # Link input to the shadow block ID - fields = {"PROPERTY": [property_value, None]} # PROPERTY is a field of the main block + # # Create the main sensing_of block + # inputs = {"OBJECT": {"kind": "block", "block": object_menu_id}} # Link input to the shadow block ID + # fields = {"PROPERTY": [property_value, None]} # PROPERTY is a field of the main block - block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) + # block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) + # all_generated_blocks[object_menu_id]["parent"] = block_id + # return {"kind": "block", "block": block_id} + m = re.search(r"\((.+?)\)\s*of\s*(?:\((.+?)\)|\[(.+?)\s*v\])", text, re.IGNORECASE) + if m: + prop_str = m.group(1).strip() + obj = (m.group(2) or m.group(3)).strip() + print("(() of ()) (sensing_of) : ", prop_str) + print("(() of ()) (sensing_of) : ", obj) + valid_properties = [ "x position", "y position", "direction", "costume #", "costume name", "size", "volume", "backdrop #", "backdrop name" ] + cleaned = re.sub(r"[^a-z0-9#]", "", prop_str.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: + property_value = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + property_value = normalized_map[match[0]] + else: + property_value = prop_str # fallback + if obj.lower() == "stage": + obj_val = "_stage_" + elif obj.lower() == "myself": + obj_val = "_myself_" + else: + obj_val = obj + object_menu_id = _register_block("sensing_of_object_menu",parent_key,True,pick_key_func,all_generated_blocks,fields={"OBJECT": [obj_val, None]}) + inputs = {"OBJECT": {"kind": "block", "block": object_menu_id}} + fields = {"PROPERTY": [property_value, None]} + block_id = _register_block("sensing_of",parent_key,False,pick_key_func,all_generated_blocks,inputs=inputs,fields=fields) all_generated_blocks[object_menu_id]["parent"] = block_id return {"kind": "block", "block": block_id} - # [ORDER NO: ] + # [ORDER NO: ] w # Variable reporter: [score v], [health v], etc. m_var = re.fullmatch(r"\[([^\]]+)\s*v\]", text) if m_var: var_name = m_var.group(1).strip() print("the variable reportor value[var v]:",var_name) - block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks, - fields={"VARIABLE": [var_name, None]}) + block_id = _register_block("data_variable", parent_key, True, pick_key_func, all_generated_blocks,fields={"VARIABLE": [var_name, None]}) return {"kind": "block", "block": block_id} - - # [ORDER NO: ALWAYS LAST] - # Arithmetic operations: (() + ()), (() - ()), (() * ()), (() / ()) - - - #_strip_outer = re.compile(r"^\s*(?P[\(\[]+)\s*(?P.*\S\s*(?P=open)\s*$", re.VERBOSE) - # _strip_outer = re.compile(r"^\s*(?P[\(\[]+)\s*(?P.*\S)\s*(?P=open)\s*$",re.VERBOSE) - # _expr = re.compile(r"^\s*(?P[^\s\(\)\[\]]+)\s*(?P[+\-*/])\s*(?P[^\s\(\)\[\]]+)\s*$", re.VERBOSE) - # #m_art = re.compile(r"\(?\s*([^\s()]+)\s*\)?\s*([+\-*/])\s*\(?\s*([^\s()]+)\s*\)?", re.VERBOSE) - # if text: - # expr = text - # # strip *all* matching layers of () or [] - # while True: - # m = _strip_outer.match(expr) - # if not m: - # break - # expr = m.group("inner") - - # # now match the core "left op right" - # m = _expr.match(expr) - - # left_str = m.group("left") - # operator = m.group("op") - # right_str = m.group("right") - # print("arithemetic ops:[left] ",left_str) - # print("arithemetic ops:[rigth] ",right_str) - # # your existing parse & block‑creation logic - # op1_obj = parse_reporter_or_value(left_str, parent_key, pick_key_func, all_generated_blocks) - # op2_obj = parse_reporter_or_value(right_str, parent_key, pick_key_func, all_generated_blocks) - - # opcode_map = { - # '+': 'operator_add', - # '-': 'operator_subtract', - # '*': 'operator_multiply', - # '/': 'operator_divide', - # } - # inputs = {"NUM1": op1_obj, "NUM2": op2_obj} - # block_id = _register_block( - # opcode_map[operator], parent_key, False, - # pick_key_func, all_generated_blocks, - # inputs=inputs - # ) - - # if op1_obj.get("kind") == "block": - # all_generated_blocks[op1_obj["block"]]["parent"] = block_id - # if op2_obj.get("kind") == "block": - # all_generated_blocks[op2_obj["block"]]["parent"] = block_id - - # return {"kind": "block", "block": block_id} - # Function to strip outer parentheses if they enclose the entire expression def strip_outer_parentheses(s): s = s.strip() while s.startswith("(") and s.endswith(")"): @@ -1519,8 +1429,7 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block return s # Outer parentheses don't enclose the entire expression s = s[1:-1].strip() return s - - # Function to find the main operator based on precedence: +,- (lowest) then *,/ + def find_main_operator(s): # First pass: look for + and - at depth 0 (lowest precedence) depth = 0 @@ -1536,7 +1445,6 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block # Choose the rightmost lowest-precedence operator for left-associativity return ops[-1] - # Second pass: look for * and / at depth 0 (higher precedence) depth = 0 for i, ch in enumerate(s): if ch == "(": @@ -1550,7 +1458,6 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block return None, None - # Recursive parse function def parse_expression(s): s = strip_outer_parentheses(s) idx, op = find_main_operator(s) @@ -1562,35 +1469,15 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block left_txt, op_sym, right_txt = parse_expression(text) if op_sym is not None: - # recursively build the two operand sub-blocks print("arithemetic ops:[left] ",left_txt) print("arithemetic ops:[rigth] ",right_txt) - operand1_obj = parse_reporter_or_value(left_txt, - parent_key, - pick_key_func, - all_generated_blocks) - operand2_obj = parse_reporter_or_value(right_txt, - parent_key, - pick_key_func, - all_generated_blocks) - - # map arithmetic symbols to your block-types - op_map = { - "+": "operator_add", - "-": "operator_subtract", - "*": "operator_multiply", - "/": "operator_divide" - } - inputs = {"NUM1": operand1_obj, - "NUM2": operand2_obj} + operand1_obj = parse_reporter_or_value(left_txt,parent_key,pick_key_func,all_generated_blocks) + operand2_obj = parse_reporter_or_value(right_txt,parent_key,pick_key_func,all_generated_blocks) + op_map = {"+": "operator_add","-": "operator_subtract","*": "operator_multiply","/": "operator_divide"} + inputs = {"NUM1": operand1_obj, "NUM2": operand2_obj} print("inputs",inputs) # register this arithmetic block - block_id = _register_block(op_map[op_sym], - parent_key, - True, - pick_key_func, - all_generated_blocks, - inputs=inputs) + block_id = _register_block(op_map[op_sym],parent_key,True,pick_key_func,all_generated_blocks,inputs=inputs) print("block_id",block_id) # hook child blocks back to their paren if operand1_obj.get("kind") == "block": @@ -1613,9 +1500,10 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks): boolean operators (and, or, not), and other sensing conditions. """ s = stmt.strip() + print("the processed text on parse_conditon-1-->",s ) s = extract_condition_balanced(s) s_lower = s.lower() - print("the processed text on parse_conditon",s_lower) + print("the processed text on parse_conditon-2-->",s_lower) # 1) Boolean NOT: `not <...>` m_not = re.fullmatch(r"\s*(?:<\s*)?not\s+(.+?)(?:\s*>)?\s*", s_lower, re.IGNORECASE) if m_not: @@ -1641,40 +1529,7 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks): if cond1_obj.get("kind") == "block": all_generated_blocks[cond1_obj["block"]]["parent"] = block_id if cond2_obj.get("kind") == "block": all_generated_blocks[cond2_obj["block"]]["parent"] = block_id return {"kind": "block", "block": block_id} - - - # # 1a) Comparisons with explicit angle wrappers: < (...) op (...) > - # m = re.fullmatch( - # r"\s*<\s*(.+?)\s*(?P<|=|>)\s*(.+?)\s*>\s*", - # s_lower, - # re.VERBOSE - # ) - # if m: - # left_txt, right_txt = m.group(1), m.group(3) - # operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks) - # operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks) - # op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'} - - # inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj} - # block_id = _register_block(op_map[m.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) - # # Set parents for nested inputs - # if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id - # if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id - # return {"kind": "block", "block": block_id} - # # 1b) Simple comparisons without angle wrappers: A op B - # m_simple = re.fullmatch(r"\s*(.+?)\s*(?P<|=|>)\s*(.+?)\s*", s_lower) - # if m_simple: - # left_txt, right_txt = m_simple.group(1), m_simple.group(3) - # operand1_obj = parse_reporter_or_value(unparen(left_txt), parent_key, pick_key_func, all_generated_blocks) - # operand2_obj = parse_reporter_or_value(unparen(right_txt), parent_key, pick_key_func, all_generated_blocks) - # op_map = {'<': 'operator_lt', '=': 'operator_equals', '>': 'operator_gt'} - - # inputs = {"OPERAND1": operand1_obj, "OPERAND2": operand2_obj} - # block_id = _register_block(op_map[m_simple.group('op')], parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs) - # if operand1_obj.get("kind") == "block": all_generated_blocks[operand1_obj["block"]]["parent"] = block_id - # if operand2_obj.get("kind") == "block": all_generated_blocks[operand2_obj["block"]]["parent"] = block_id - # return {"kind": "block", "block": block_id} m_comp = re.fullmatch(r"\s*(?:<\s*)?(?P.+?)\s*(?P<|=|>)\s*(?P.+?)(?:\s*>)?\s*",s_lower,re.VERBOSE) if m_comp: left_txt = m_comp.group('left') @@ -1716,59 +1571,185 @@ def parse_condition(stmt, parent_key, pick_key_func, all_generated_blocks): return {"kind": "block", "block": block_id} # 5) Touching object: - # m_touch = re.fullmatch(r"""\s*[^\]]+?)\s*(?:v)?\s*\]\s*\?\s*>?""", s_lower, re.IGNORECASE | re.VERBOSE) - #m_touch = re.fullmatch(r"""\s*[^\]]+?)\s*(?:v)?\s*\]\s*\?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE) - m_touch = re.fullmatch(r"""\s*[^\]]+?)\s*(?:v)?\s*\]\s*(?:\?)?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE) + # m_touch = re.fullmatch(r"""\s*[^\]]+?)\s*(?:v)?\s*\]\s*(?:\?)?\s*>?\s*""", s_lower, re.IGNORECASE | re.VERBOSE) + # if m_touch: + # sprite = m_touch.group('sprite').strip() + # val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(sprite, sprite) + # print(" : ",sprite) + # mid = _register_block( + # "sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, + # fields={"TOUCHINGOBJECTMENU":[val, None]} + # ) + # bid = _register_block( + # "sensing_touchingobject", parent_key, True, pick_key_func, all_generated_blocks, + # inputs={"TOUCHINGOBJECTMENU":{"kind": "block", "block": mid}} # Link input to the shadow block ID + # ) + # all_generated_blocks[mid]["parent"] = bid + # return {"kind":"block","block":bid} + #m_touch = re.fullmatch(r"""\s*[^\]\)]+?)\s*(?:v)?\s*[\]\)]?\s*(?:\?)?\s*>?\s*""",s_lower, re.IGNORECASE | re.VERBOSE) + m_touch = re.fullmatch(r"""\s*[^\]\)]+?)\s*(?:v)?\s*[\]\)]?\s*(?:\?)?\s*>?\s*""",s_lower,re.IGNORECASE | re.VERBOSE) if m_touch: - sprite = m_touch.group('sprite').strip() - val = {'mouse-pointer':'_mouse_', 'edge':'_edge_'}.get(sprite, sprite) - print(" : ",sprite) - mid = _register_block( - "sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks, - fields={"TOUCHINGOBJECTMENU":[val, None]} - ) - bid = _register_block( - "sensing_touchingobject", parent_key, True, pick_key_func, all_generated_blocks, - inputs={"TOUCHINGOBJECTMENU":{"kind": "block", "block": mid}} # Link input to the shadow block ID - ) + sprite = m_touch.group('sprite').strip().strip('"\'') + print(" : ", sprite) + target_val = None + valid_properties = ["mouse-pointer", "mouse pointer", "edge"] + cleaned = re.sub(r"[^a-z0-9#]", "", sprite.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: + if "edge" in normalized_map[cleaned]: target_val = "_edge_" + else: target_val = "_mouse_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + if "edge" in normalized_map[match[0]]: target_val = "_edge_" + else: target_val = "_mouse_" + else: + val, key = process_text(sprite) + if key == "sprite": target_val = val + else: return None + mid = _register_block("sensing_touchingobjectmenu", parent_key, True, pick_key_func, all_generated_blocks,fields={"TOUCHINGOBJECTMENU": [target_val, None]}) + bid = _register_block("sensing_touchingobject", parent_key, True, pick_key_func, all_generated_blocks,inputs={"TOUCHINGOBJECTMENU": {"kind": "block", "block": mid}}) all_generated_blocks[mid]["parent"] = bid - return {"kind":"block","block":bid} + return {"kind": "block", "block": bid} # 6) Touching color: - m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s_lower) + COLOR_MAP = {"red": "#FF0000","yellow": "#FFFF00","pink": "#FFC0CB","blue": "#0000FF","green": "#008000", + "black": "#000000","white": "#FFFFFF","orange": "#FFA500","purple": "#800080","gray": "#808080",} + # m = re.search(r"touching color \[(#[0-9A-Fa-f]{6})\]\?", s_lower) + # m = re.search(r"touching color [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower) + # if m: + # color_str = m.group(1).strip() + + # # Remove trailing " v" if present (Scratch dropdown) + # if color_str.endswith(" v"): + # color_str = color_str[:-2].strip() + + # # Normalize to hex + # if not color_str.startswith("#"): + # color_str = COLOR_MAP.get(color_str.lower(), None) + # if color_str is None: + # raise ValueError(f"❌ Unknown color name: {m.group(1)}") + + # inputs = {"COLOR": {"kind": "value", "value": color_str}} + # print(" : ", inputs) + # block_id = _register_block( + # "sensing_touchingcolor", parent_key, True, + # pick_key_func, all_generated_blocks, inputs=inputs + # ) + # return {"kind": "block", "block": block_id} + m = re.search(r"touching\s+color\s+[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??",s_lower,re.IGNORECASE,) if m: - inputs = {"COLOR": {"kind": "value", "value": m.group(1)}} - print(" : ",inputs) - block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) + color_str = m.group(1).strip() + if color_str.endswith(" v"): color_str = color_str[:-2].strip() + if not color_str.startswith("#"): + color_name = color_str.lower() + if color_name in COLOR_MAP: + color_str = COLOR_MAP[color_name] + else: + match = difflib.get_close_matches(color_name, COLOR_MAP.keys(), n=1, cutoff=0.6) + if match: + color_str = COLOR_MAP[match[0]] + else: + print(f"⚠️ Unknown color name: {color_str} → skipping block") + return None + inputs = {"COLOR": {"kind": "value", "value": color_str}} + block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks,inputs = {"COLOR": {"kind": "value", "value": color_str}}) + print(" :", color_str) return {"kind": "block", "block": block_id} - + + # if m: + # inputs = {"COLOR": {"kind": "value", "value": m.group(1)}} + # print(" : ",inputs) + # block_id = _register_block("sensing_touchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) + # return {"kind": "block", "block": block_id} # 7) Color is touching color: - m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s_lower) + # m = re.search(r"color [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]? is touching [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", s_lower) + # if m: + # c1, c2 = m.group(1).strip(), m.group(2).strip() + + # # Cleanup dropdown suffix + # if c1.endswith(" v"): + # c1 = c1[:-2].strip() + # if c2.endswith(" v"): + # c2 = c2[:-2].strip() + + # # Normalize each color + # if not c1.startswith("#"): + # c1 = COLOR_MAP.get(c1.lower(), None) + # if c1 is None: + # raise ValueError(f"❌ Unknown color name: {m.group(1)}") + # if not c2.startswith("#"): + # c2 = COLOR_MAP.get(c2.lower(), None) + # if c2 is None: + # raise ValueError(f"❌ Unknown color name: {m.group(2)}") + + # inputs = { + # "COLOR1": {"kind": "value", "value": c1}, + # "COLOR2": {"kind": "value", "value": c2}, + # } + # print(" : ", inputs) + + # block_id = _register_block( + # "sensing_coloristouchingcolor", parent_key, True, + # pick_key_func, all_generated_blocks, inputs=inputs + # ) + # return {"kind": "block", "block": block_id} + m = re.search(r"color\s+[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\s*is\s*touching\s*[<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??",s_lower,re.IGNORECASE,) if m: - inputs = {"COLOR1": {"kind": "value", "value": m.group(1)}, "COLOR2": {"kind": "value", "value": m.group(2)}} - print(" : ",inputs) - block_id = _register_block("sensing_coloristouchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) + c1, c2 = m.group(1).strip(), m.group(2).strip() + if c1.endswith(" v"): c1 = c1[:-2].strip() + if c2.endswith(" v"): c2 = c2[:-2].strip() + def normalize_color(c_raw): + if c_raw.startswith("#"): return c_raw + cname = c_raw.lower() + if cname in COLOR_MAP: return COLOR_MAP[cname] + match = difflib.get_close_matches(cname, COLOR_MAP.keys(), n=1, cutoff=0.6) + if match: return COLOR_MAP[match[0]] + print(f"⚠️ Unknown color name: {c_raw} → skipping block") + return None + c1_hex = normalize_color(c1) + c2_hex = normalize_color(c2) + if not c1_hex or not c2_hex: return None + block_id = _register_block("sensing_coloristouchingcolor", parent_key, True,pick_key_func, all_generated_blocks,inputs = {"COLOR1": {"kind": "value", "value": c1_hex},"COLOR2": {"kind": "value", "value": c2_hex},}) + print(" :", c1_hex, c2_hex) return {"kind": "block", "block": block_id} + + # m = re.search(r"color \[(#[0-9A-Fa-f]{6})\] is touching \[(#[0-9A-Fa-f]{6})\]\?", s_lower) + # if m: + # inputs = {"COLOR1": {"kind": "value", "value": m.group(1)}, "COLOR2": {"kind": "value", "value": m.group(2)}} + # print(" : ",inputs) + # block_id = _register_block("sensing_coloristouchingcolor", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) + # return {"kind": "block", "block": block_id} # 8) Key pressed: - # m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", s_lower) + # m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?", s_lower, re.IGNORECASE) # if m: # option = m.group(1).strip() # menu_block_id = _register_block("sensing_keyoptions", parent_key, True, pick_key_func, all_generated_blocks, fields={"KEY_OPTION": [option, None]}) - # print(" : ",option) + # print(" : ", option) # inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}} # block_id = _register_block("sensing_keypressed", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) # all_generated_blocks[menu_block_id]["parent"] = block_id # return {"kind": "block", "block": block_id} - m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?", s_lower, re.IGNORECASE) + m = re.search(r"(?:<)?(?:key\s*)?\[([^\]]+?)\s*v\](?:\s*key)?\s*pressed\?(?:>)?",s_lower,re.IGNORECASE,) if m: option = m.group(1).strip() - menu_block_id = _register_block("sensing_keyoptions", parent_key, True, pick_key_func, all_generated_blocks, fields={"KEY_OPTION": [option, None]}) - print(" : ", option) - inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}} - block_id = _register_block("sensing_keypressed", parent_key, True, pick_key_func, all_generated_blocks, inputs=inputs) + valid_options = [ "space", "up arrow", "down arrow", "right arrow", "left arrow", "any","a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] + cleaned = re.sub(r"[^a-z0-9]", "", option.lower()).strip() + normalized_map = { re.sub(r"[^a-z0-9]", "", v.lower()): v for v in valid_options } + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + option_val = normalized_map[match[0]] + else: + option_val = option + # Register shadow menu block + menu_block_id = _register_block("sensing_keyoptions",parent_key, True, pick_key_func, all_generated_blocks,fields={"KEY_OPTION": [option_val, None]}) + print(" :", option_val) + block_id = _register_block("sensing_keypressed",parent_key, True, pick_key_func, all_generated_blocks,inputs = {"KEY_OPTION": {"kind": "block", "block": menu_block_id}}) all_generated_blocks[menu_block_id]["parent"] = block_id - return {"kind": "block", "block": block_id} + return {"kind": "block", "block": block_id} # 9) Mouse down?: mouse down? if s_lower == "mouse down?": @@ -1849,7 +1830,8 @@ def classify(line): if l.startswith("set size to"): return "looks_setsizeto", "stack" # Updated regex for change/set effect by/to if re.match(r"change\s*(\[.+?v\]|\(.+?\))?\s*effect by", l): return "looks_changeeffectby", "stack" - if re.match(r"set\s*(\[.+?v\]|\(.+?\))?\s*effect to", l): return "looks_seteffectto", "stack" + # if re.match(r"set\s*(\[.+?v\]|\(.+?\))?\s*effect to", l): return "looks_seteffectto", "stack" + if l.startswith("set [") and " effect to " in l: return "looks_seteffectto", "stack" if l == "clear graphic effects": return "looks_cleargraphiceffects", "stack" if l == "show": return "looks_show", "stack" if l == "hide": return "looks_hide", "stack" @@ -1875,7 +1857,7 @@ def classify(line): if re.match(r"wait\s+until\s*<", l, re.IGNORECASE): return "control_wait_until", "stack" if l.startswith("repeat ("): return "control_repeat", "c_block" if l == "forever": return "control_forever", "c_block" - if l.startswith("if <") and " then go" in l: return "control_if_else", "c_block" + if l.startswith("if <") and " then else" in l: return "control_if_else", "c_block" if l.startswith("if <"): return "control_if", "c_block" if l.startswith("repeat until <"): return "control_repeat_until", "c_block" # Updated regex for stop block to handle different options @@ -1921,7 +1903,6 @@ def classify(line): not re.fullmatch(r"\[[^\]]+\]\s*v", potential_name): return "procedures_call", "stack" - raise ValueError(f"Unknown statement: {line!r}") ################################################################################################################################################################# @@ -1947,6 +1928,46 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): return f"{opcode}_{idx + 1}" ptrs[opcode] += 1 return lst[idx] + + # Helper: lookahead for `else` that matches this `if` + def classify_with_else_context(lines, i, stripped_line): + """ + If stripped_line starts with an if-expression (e.g. "if <...> then"), + scan forward to see if a corresponding 'else' exists before the matching 'end'. + Returns a tuple (opcode, ntype). + If this helper doesn't apply, it returns ("", "") (no None). + """ + l = stripped_line.strip().lower() + if not l.startswith("if <"): + return ("", "") + + # Lookahead with nesting: nested 'if' increments depth. + depth = 0 + for j in range(i + 1, len(lines)): + look = lines[j].strip().lower() + # ignore blank lines and comments + if not look or look.startswith("//"): + continue + + # if we find another 'if <' before an 'end', that's a nested if -> depth+1 + if look.startswith("if <"): + depth += 1 + continue + + # an 'end' closes the most recent if (nested or this one) + if look == "end": + if depth == 0: + # reached the end for this if without seeing an else + break + depth -= 1 + continue + + # an else that occurs when depth == 0 belongs to this if + if look == "else" and depth == 0: + return ("control_if_else", "c_block") + + # no matching else found before the corresponding end -> plain control_if + return ("control_if", "c_block") # Change: Start with an empty dictionary. Blocks will be added only as they are parsed. all_generated_blocks = {} @@ -1955,6 +1976,7 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): top_level_script_keys = [] lines = pseudo_code.splitlines() + print("The lines here are:\n", lines) i = 0 while i < len(lines): raw_line = lines[i] @@ -1976,16 +1998,13 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): # This check now looks for any C-Block, which is correct for this context. if popped_owner_key and all_generated_blocks[popped_owner_key]["block_shape"] == "C-Block": - # This is the crucial step that fixes the issue. - # We explicitly upgrade the block from a 'control_if' to a 'control_if_else'. + # Explicitly upgrade the block from a 'control_if' to a 'control_if_else'. owner_block = all_generated_blocks[popped_owner_key] owner_block["op_code"] = "control_if_else" # Push a new scope for the 'else' substack, with the same owner. stack.append((current_indent, popped_owner_key, None)) else: - # This is the error you were getting. - # It happens because the 'if' block was never correctly upgraded. print(f"Error: 'else' found without a corresponding 'if-else' block at line {i+1}") stack.append((popped_indent, popped_owner_key, popped_last_block_in_chain)) i += 1 @@ -2016,10 +2035,17 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): current_scope_indent, current_owner_block_id, last_block_in_current_chain = stack[-1] - opcode, ntype = classify(stripped_line) + # RUN the lookahead classifier first; it never returns None. + override_opcode, override_ntype = classify_with_else_context(lines, i, stripped_line) + if override_opcode: + opcode, ntype = override_opcode, override_ntype + else: + # fallback to your original classify (unchanged) + opcode, ntype = classify(stripped_line) + stmt_for_parse = re.sub(r"\bthen(\s+go)?\s*$", "", stripped_line, flags=re.IGNORECASE).strip() - print("The opcode here is",opcode) - print("The ntype here is",ntype) + print("The opcode here is", opcode) + print("The ntype here is", ntype) if opcode is None: i += 1 continue @@ -2241,96 +2267,275 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): print("The value at data_setvariableto: ",value_str) info["fields"]["VARIABLE"] = [var_name, None] info["inputs"]["VALUE"] = parse_reporter_or_value(value_str, key, pick_key, all_generated_blocks) - + + # new add ons + elif opcode == "motion_setrotationstyle": + m = re.search(r"set rotation style\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE) + if m: + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + valid_options = ["left-right", "all around", "don't rotate"] + cleaned = re.sub(r"[^a-z\s]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z\s]", "", v.lower()).strip(): v for v in valid_options} + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + print("motion_setrotationstyle------------>",match) + if match: option_val = normalized_map[match[0]] + else: option_val = "all around" + info["fields"]["STYLE"] = [option_val.strip(), None] # Dropdown/Menu inputs (UPDATED) + # elif opcode == "motion_goto": + # m = re.search(r"go to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))", stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option) + # menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) + # info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} elif opcode == "motion_goto": - m = re.search(r"go to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"go to\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - option = m.group(1).strip() - if option == "random position": option_val = "_random_" - elif option == "mouse-pointer": option_val = "_mouse_" - else: option_val = option - - menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]}) + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print(" :", option) + option_val = None + valid_properties = ["random position", "random-position", "mouse-pointer", "mouse pointer"] + cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: + if "random" in normalized_map[cleaned]: option_val = "_random_" + else: option_val = "_mouse_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + if "random" in normalized_map[match[0]]: option_val = "_random_" + else: option_val = "_mouse_" + else: + val, key = process_text(option) + if key == "sprite": option_val = val + else: option_val = "_random_" + menu_block_id = _register_block("motion_goto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} + + # elif opcode == "motion_glideto": + # m_secs = re.search( + # r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*secs to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) + # if m_secs: + # secs = float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1)) + # option = (m_secs.group(2) or m_secs.group(3) or m_secs.group(4)).strip() + # option_val = {"random position": "_random_", "mouse-pointer": "_mouse_"}.get(option, option) + # info["inputs"]["SECS"] = {"kind": "value", "value": secs} + # menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, + # fields={"TO": [option_val, None]}) + # info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} elif opcode == "motion_glideto": - m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs to\s*(?:\[([^\]]+)\s*v\]|\((.+?)\))", stmt_for_parse, re.IGNORECASE) + m_secs = re.search(r"glide\s*\(\s*(-?\d+(?:\.\d+)?)\s*\)\s*secs\s*to\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)|\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(\s*([^)]+?)\s*\)|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m_secs: - info["inputs"]["SECS"] = {"kind": "value", "value": float(m_secs.group(1)) if '.' in m_secs.group(1) else int(m_secs.group(1))} - # Use group 3 for [random position v] or group 4 for (random position) - option = (m_secs.group(3) or m_secs.group(4)).strip() - if option == "random position": option_val = "_random_" - elif option == "mouse-pointer": option_val = "_mouse_" - else: option_val = option - - menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, fields={"TO": [option_val, None]}) + secs = float(m_secs.group(1)) if "." in m_secs.group(1) else int(m_secs.group(1)) + option = m_secs.group(2).strip() + print(" :", option) + option_val = None + valid_properties = ["random position", "random-position", "mouse-pointer", "mouse pointer"] + cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: + if "random" in normalized_map[cleaned]: option_val = "_random_" + else: option_val = "_mouse_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + if "random" in normalized_map[match[0]]: option_val = "_random_" + else: option_val = "_mouse_" + else: + val, key = process_text(option) + if key == "sprite": option_val = val + else: option_val = "_random_" + # secs input + info["inputs"]["SECS"] = {"kind": "value", "value": secs} + # TO menu block + menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks,fields={"TO": [option_val, None]}) info["inputs"]["TO"] = {"kind": "block", "block": menu_block_id} + # elif opcode == "motion_pointtowards": + # m = re.search(r"point towards\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # option_val = {"mouse-pointer": "_mouse_"}.get(option, option) + # menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks, + # fields={"TOWARDS": [option_val, None]}) + # info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id} elif opcode == "motion_pointtowards": - m = re.search(r"point towards\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"point\s*towards\s*(?:\[\s*([^\]]+?)(?:\s*v)?\s*\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - option = m.group(1).strip() - if option == "mouse-pointer": option_val = "_mouse_" - else: option_val = option - - menu_block_id = _register_block("motion_pointtowards_menu", key, True, pick_key, all_generated_blocks, fields={"TOWARDS": [option_val, None]}) + option = (m.group(1) or m.group(2) or m.group(3)).strip() + print(" :", option) + option_val = None + valid_properties = ["mouse-pointer", "mouse pointer"] + cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: option_val = "_mouse_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = "_mouse_" + else: + val, key = process_text(option) + if key == "sprite": option_val = val + else: option_val = "_mouse_" + menu_block_id = _register_block( "motion_pointtowards_menu",key,True,pick_key,all_generated_blocks,fields={"TOWARDS": [option_val, None]},) info["inputs"]["TOWARDS"] = {"kind": "block", "block": menu_block_id} - elif opcode == "sensing_keypressed": - m = re.search(r"key \[([^\]]+)\s*v\] pressed\?", stmt_for_parse, re.IGNORECASE) - if m: - option = m.group(1).strip() - menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks, fields={"KEY_OPTION": [option, None]}) - info["inputs"]["KEY_OPTION"] = {"kind": "block", "block": menu_block_id} - elif opcode == "sensing_touchingobject": - m = re.search(r"touching \[([^\]]+)\s*v\]\?", stmt_for_parse, re.IGNORECASE) - if m: - option = m.group(1).strip() - if option == "mouse-pointer": option_val = "_mouse_" - elif option == "edge": option_val = "_edge_" - else: option_val = option - - menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks, fields={"TOUCHINGOBJECTMENU": [option_val, None]}) - info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "block", "block": menu_block_id} + + # elif opcode == "sensing_keypressed": + # m = re.search(r"key\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\s*pressed\?",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # menu_block_id = _register_block("sensing_keyoptions", key, True, pick_key, all_generated_blocks, + # fields={"KEY_OPTION": [option, None]}) + # info["inputs"]["KEY_OPTION"] = {"kind": "block", "block": menu_block_id} + + # elif opcode == "sensing_touchingobject": + # m = re.search(r"touching\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))\?",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # option_val = {"mouse-pointer": "_mouse_", "edge": "_edge_"}.get(option, option) + # menu_block_id = _register_block("sensing_touchingobjectmenu", key, True, pick_key, all_generated_blocks, + # fields={"TOUCHINGOBJECTMENU": [option_val, None]}) + # info["inputs"]["TOUCHINGOBJECTMENU"] = {"kind": "block", "block": menu_block_id} + + # elif opcode == "control_create_clone_of": + # m = re.search(r"create clone of\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # option_val = {"myself": "_myself_"}.get(option, option) + # menu_block_id = _register_block("control_create_clone_of_menu", key, True, pick_key, all_generated_blocks, + # fields={"CLONE_OPTION": [option_val, None]}) + # info["inputs"]["CLONE_OPTION"] = {"kind": "block", "block": menu_block_id} elif opcode == "control_create_clone_of": - m = re.search(r"create clone of\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"create\s*clone\s*of\s*(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - option = m.group(1).strip() - if option == "myself": option_val = "_myself_" - else: option_val = option - - menu_block_id = _register_block("control_create_clone_of_menu", key, True, pick_key, all_generated_blocks, fields={"CLONE_OPTION": [option_val, None]}) + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print(" :", option) + option_val = None + valid_properties = ["myself"] + cleaned = re.sub(r"[^a-z0-9#]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9#]", "", v.lower()): v for v in valid_properties} + if cleaned in normalized_map: option_val = "_myself_" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = "_myself_" + else: + val, key = process_text(option) + if key == "sprite": option_val = val + else: option_val = "_myself_" + menu_block_id = _register_block("control_create_clone_of_menu",key,True,pick_key,all_generated_blocks,fields={"CLONE_OPTION": [option_val, None]},) info["inputs"]["CLONE_OPTION"] = {"kind": "block", "block": menu_block_id} + # elif opcode in ["sound_playuntildone", "sound_play"]: + # # m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # m = re.search(r"(?:play sound|start sound)\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+)\s*\)?)",stmt_for_parse,re.IGNORECASE) + # if m: + # # option = m.group(1).strip() + # option = m.group(1) or m.group(2) + # print("sounds option content--------->",option) + # menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]}) + # info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id} elif opcode in ["sound_playuntildone", "sound_play"]: - # m = re.search(r"(?:play sound|start sound)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - m = re.search(r"(?:play sound|start sound)\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+)\s*\)?)",stmt_for_parse,re.IGNORECASE) + m = re.search(r"(?:play sound|start sound)\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - # option = m.group(1).strip() - option = m.group(1) or m.group(2) - print("sounds option content--------->",option) - menu_block_id = _register_block("sound_sounds_menu", key, True, pick_key, all_generated_blocks, fields={"SOUND_MENU": [option, None]}) + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print("sounds option content --------->", option) + option_val = None + val, key = process_text(option) + if key == "sound": option_val = val + else: option_val = option + print("sounds option content --------->", option_val) + menu_block_id = _register_block("sound_sounds_menu",key,True,pick_key,all_generated_blocks,fields={"SOUND_MENU": [option_val, None]},) info["inputs"]["SOUND_MENU"] = {"kind": "block", "block": menu_block_id} + # elif opcode == "looks_switchcostumeto": + # m = re.search(r"switch costume to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks, + # fields={"COSTUME": [option, None]}) + # info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id} elif opcode == "looks_switchcostumeto": - m = re.search(r"switch costume to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"switch\s*costume\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - option = m.group(1).strip() - print("option at looks_switchcostumeto: ",option) - menu_block_id = _register_block("looks_costume", key, True, pick_key, all_generated_blocks, fields={"COSTUME": [option, None]}) + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print(" :", option) + option_val = None + val, key = process_text(option) + if key == "sprite": option_val = val + else: option_val = option + menu_block_id = _register_block("looks_costume",key,True,pick_key,all_generated_blocks,fields={"COSTUME": [option_val, None]},) info["inputs"]["COSTUME"] = {"kind": "block", "block": menu_block_id} + + # elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]: + # m = re.search(r"switch backdrop to\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) + # if m: + # option = (m.group(1) or m.group(2) or m.group(3)).strip() + # menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks, + # fields={"BACKDROP": [option, None]}) + # info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id} elif opcode in ["looks_switchbackdropto", "looks_switchbackdroptowait"]: - m = re.search(r"switch backdrop to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"switch\s*backdrop\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) if m: - option = m.group(1).strip() - menu_block_id = _register_block("looks_backdrops", key, True, pick_key, all_generated_blocks, fields={"BACKDROP": [option, None]}) + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print(" :", option) + option_val = None + defaults = ["next backdrop", "random backdrop", "previous backdrop"] + cleaned = re.sub(r"[^a-z0-9 ]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z0-9 ]", "", v.lower()): v for v in defaults} + if cleaned in normalized_map: + opt = normalized_map[cleaned] + if opt == "next backdrop": option_val = "next backdrop" + elif opt == "random backdrop": option_val = "random backdrop" + elif opt == "previous backdrop": option_val = "previous backdrop" + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: + opt = normalized_map[match[0]] + if opt == "next backdrop": option_val = "next backdrop" + elif opt == "random backdrop": option_val = "random backdrop" + elif opt == "previous backdrop": option_val = "previous backdrop" + else: + val, key = process_text(option) + if key == "backdrop": option_val = val + else: option_val = "random backdrop" + menu_block_id = _register_block("looks_backdrops",key,True,pick_key,all_generated_blocks,fields={"BACKDROP": [option_val, None]},) info["inputs"]["BACKDROP"] = {"kind": "block", "block": menu_block_id} + elif opcode in ["event_broadcast", "event_broadcastandwait"]: - m = re.search(r"broadcast\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + m = re.search(r"broadcast\s*(?:\[\s*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",stmt_for_parse, re.IGNORECASE) if m: - option = m.group(1).strip() - # Broadcast input doesn't use a separate menu block in definitions, it's a direct menu field in the input. - # So, it should be [1, [11, "message1", "id"]] or [1, [12, "message1"]] - # For now, let's keep it simple as [1, [11, option, None]] or similar if the definition allows. - # The `all_block_definitions` has `[1, [11, "message1", "5O!nei;S$!c!=hCT}0:a"]]` - # Let's use that format, but without the specific ID for now. - info["inputs"]["BROADCAST_INPUT"] = {"kind": "value", "value": option} # Store as a value for now + option = (m.group(1) or m.group(2) or m.group(3)).strip() + info["inputs"]["BROADCAST_INPUT"] = {"kind": "value", "value": option} + # elif opcode == "control_stop": + # # m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$", + # m = re.match(r"stop\s*[\[(]?\s*(all|this script|other scripts in sprite)\s*(?:v)?\s*[\])]?$", + # stmt_for_parse, re.IGNORECASE) + # if m: + # option = m.group(1).strip().lower() + # # Normalize casing to match Scratch’s expected field values + # if option == "all": + # stop_val = "all" + # elif option == "this script": + # stop_val = "this script" + # else: + # stop_val = "other scripts in sprite" + + # info.setdefault("fields", {}) + # info["fields"]["STOP_OPTION"] = [stop_val, None] + + elif opcode == "control_stop": + m = re.match(r"""stop\s*[\[(]?\s*(.+?)\s*(?:v)?\s*[\])]?$""",stmt_for_parse,re.IGNORECASE | re.VERBOSE) + if m: + option = m.group(1).strip().lower() + valid_options = ["all", "this script", "other scripts in sprite"] + normalized_map = {v.lower(): v for v in valid_options} + if option in normalized_map: stop_val = normalized_map[option] + else: + match = difflib.get_close_matches(option, normalized_map.keys(), n=1, cutoff=0.5) + if match: stop_val = normalized_map[match[0]] + else: stop_val = "all" # fallback (won’t break but may not be a valid field) + print("stop value",stop_val) + info.setdefault("fields", {}) + info["fields"]["STOP_OPTION"] = [stop_val, None] # Conditional inputs (Boolean blocks) elif opcode in ["control_if", "control_if_else", "control_wait_until", "control_repeat_until"]: @@ -2344,11 +2549,9 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): # Ensure the parent of the condition block is set to this control block if condition_obj.get("kind") == "block": all_generated_blocks[condition_obj["block"]]["parent"] = key - elif opcode in ["operator_and", "operator_or", "operator_not", "operator_contains", - "sensing_touchingcolor", "sensing_coloristouchingcolor", "sensing_mousedown"]: + elif opcode in ["operator_and", "operator_or", "operator_not", "operator_contains", "sensing_touchingcolor", "sensing_coloristouchingcolor", "sensing_mousedown"]: pass - # Fields parsing if "VARIABLE" in info["fields"]: m = re.search(r"\[([^\]]+)\s*v\]", stmt_for_parse) @@ -2358,52 +2561,186 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): if "LIST" in info["fields"]: m = re.search(r"(?:to|of|in)\s*\[([^\]]+)\s*v\]", stmt_for_parse) if m: info["fields"]["LIST"] = [m.group(1).strip(), None] - if "STOP_OPTION" in info["fields"]: - m = re.search(r"stop \[([^\]]+)\s*v\]", stmt_for_parse) - if m: info["fields"]["STOP_OPTION"] = [m.group(1).strip(), None] - if "STYLE" in info["fields"]: - m = re.search(r"set rotation style \[([^\]]+)\s*v\]", stmt_for_parse) - if m: info["fields"]["STYLE"] = [m.group(1).strip(), None] + # if "STOP_OPTION" in info["fields"]: + # m = re.search(r"stop \[([^\]]+)\s*v\]", stmt_for_parse) + # if m: info["fields"]["STOP_OPTION"] = [m.group(1).strip(), None] + # if "STYLE" in info["fields"]: + # m = re.search(r"set rotation style \[([^\]]+)\s*v\]", stmt_for_parse) + # if m: info["fields"]["STYLE"] = [m.group(1).strip(), None] + # if "DRAG_MODE" in info["fields"]: + # m = re.search(r"set drag mode \[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["DRAG_MODE"] = [m.group(1).strip(), None] if "DRAG_MODE" in info["fields"]: - m = re.search(r"set drag mode \[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["DRAG_MODE"] = [m.group(1).strip(), None] + m = re.search(r"set\s*drag\s*mode\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + if m: + option = m.group(1).strip() + valid_options = ["draggable", "not draggable"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {re.sub(r"[^a-z]", "", v.lower()): v for v in valid_options} + if cleaned in normalized_map: + option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = normalized_map[match[0]] + else: option_val = "draggable" + info["fields"]["DRAG_MODE"] = [option_val, None] + # if "EFFECT" in info["fields"] and opcode in ["looks_changeeffectby", "looks_seteffectto", "sound_changeeffectby", "sound_seteffectto"]: + # m = re.search(r"(?:change|set)\s*\[([^\]]+)\s*v\] effect", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["EFFECT"] = [m.group(1).upper().strip(), None] if "EFFECT" in info["fields"] and opcode in ["looks_changeeffectby", "looks_seteffectto", "sound_changeeffectby", "sound_seteffectto"]: - m = re.search(r"(?:change|set)\s*\[([^\]]+)\s*v\] effect", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["EFFECT"] = [m.group(1).upper().strip(), None] - if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]: - m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["NUMBER_NAME"] = [m.group(1).strip(), None] + # m = re.search(r"(?:change|set)\s*\[([^\]]+)\s*v\]\s*effect", stmt_for_parse, re.IGNORECASE) + m = re.search(r"(?:change|set)\s*(?:\[\s*([^\]]+?)\s*v?\]|\(\s*([^)]+?)\s*\)|([A-Za-z]+))\s*effect\s*to\s*(?:\[\s*([^\]]+?)\s*v?\]|\(\s*([^)]+?)\s*\)|(.+))",stmt_for_parse,re.IGNORECASE,) + if m: + option = m.group(1).strip() + print("option-------------->>",option) + if opcode.startswith("looks_"): valid_options = ["color", "fisheye", "whirl", "pixelate", "mosaic", "brightness", "ghost"] + else: valid_options = ["pitch", "pan left/right"] + cleaned = re.sub(r"[^a-z\s/]", "", option.lower()).strip() + print("cleaned----------->",cleaned) + normalized_map = { re.sub(r"[^a-z\s/]", "", v.lower()).strip(): v for v in valid_options } + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.3) + if match: option_val = normalized_map[match[0]] + elif opcode in ["sound_changeeffectby", "sound_seteffectto"]: option_val = "pitch" + else: option_val = "color" # fallback if nothing close + print("option-------------->>",option_val) + info["fields"]["EFFECT"] = [option_val, None] + # if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]: + # m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["NUMBER_NAME"] = [m.group(1).strip(), None] + # if "NUMBER_NAME" in info["fields"] and opcode in ["looks_costumenumbername", "looks_backdropnumbername"]: + # # Match costume/backdrop dropdown with number/name + # m = re.search(r"(?:costume|backdrop)\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: + # option = m.group(1).strip() + # valid_options = ["number", "name"] + # cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + # normalized_map = {v.lower(): v for v in valid_options} + # if cleaned in normalized_map: + # option_val = normalized_map[cleaned] + # else: + # match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + # if match: + # option_val = normalized_map[match[0]] + # else: + # option_val = option # fallback + # info["fields"]["NUMBER_NAME"] = [option_val, None] + # if "FRONT_BACK" in info["fields"] and opcode == "looks_gotofrontback": + # m = re.search(r"go to\s*\[([^\]]+)\s*v\] layer", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["FRONT_BACK"] = [m.group(1).strip(), None] if "FRONT_BACK" in info["fields"] and opcode == "looks_gotofrontback": - m = re.search(r"go to\s*\[([^\]]+)\s*v\] layer", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["FRONT_BACK"] = [m.group(1).strip(), None] + m = re.search(r"go to\s*\[([^\]]+)\s*v\]\s*layer", stmt_for_parse, re.IGNORECASE) + if m: + option = m.group(1).strip() + valid_options = ["front", "back"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {v.lower(): v for v in valid_options} + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = normalized_map[match[0]] + else: option_val = "front" + info["fields"]["FRONT_BACK"] = [option_val, None] + # if "FORWARD_BACKWARD" in info["fields"] and opcode == "looks_goforwardbackwardlayers": + # m = re.search(r"go\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["FORWARD_BACKWARD"] = [m.group(1).strip(), None] if "FORWARD_BACKWARD" in info["fields"] and opcode == "looks_goforwardbackwardlayers": m = re.search(r"go\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["FORWARD_BACKWARD"] = [m.group(1).strip(), None] - if "OPERATOR" in info["fields"] and opcode == "operator_mathop": - m = re.search(r"\[([^\]]+)\s*v\] of", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["OPERATOR"] = [m.group(1).upper().strip(), None] - if "CURRENTMENU" in info["fields"] and opcode == "sensing_current": - m = re.search(r"current\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["CURRENTMENU"] = [m.group(1).upper().strip(), None] - if "PROPERTY" in info["fields"] and opcode == "sensing_of": - m = re.search(r"\((.+?)\) of", stmt_for_parse, re.IGNORECASE) if m: - prop = m.group(1).strip() - prop_map = { - "x position": "x position", "y position": "y position", "direction": "direction", - "costume #": "costume number", "costume name": "costume name", "size": "size", - "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" - } - info["fields"]["PROPERTY"] = [prop_map.get(prop, prop), None] + option = m.group(1).strip() + valid_options = ["forward", "backward"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {v.lower(): v for v in valid_options} + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = normalized_map[match[0]] + else: option_val = "forward" + info["fields"]["FORWARD_BACKWARD"] = [option_val, None] + # if "OPERATOR" in info["fields"] and opcode == "operator_mathop": + # m = re.search(r"\[([^\]]+)\s*v\] of", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["OPERATOR"] = [m.group(1).upper().strip(), None] + # if "OPERATOR" in info["fields"] and opcode == "operator_mathop": + # m = re.search(r"\[([^\]]+)\s*v\]\s*of", stmt_for_parse, re.IGNORECASE) + # if m: + # option = m.group(1).strip() + # valid_options = [ + # "abs", "floor", "ceiling", "sqrt", + # "sin", "cos", "tan", + # "asin", "acos", "atan", + # "ln", "log", "e ^", "10 ^" + # ] + # cleaned = re.sub(r"[^a-z0-9^]", "", option.lower()).strip() + # normalized_map = {re.sub(r"[^a-z0-9^]", "", v.lower()): v for v in valid_options} + # if cleaned in normalized_map: + # option_val = normalized_map[cleaned] + # else: + # match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + # if match: + # option_val = normalized_map[match[0]] + # else: + # option_val = option # fallback + # info["fields"]["OPERATOR"] = [option_val, None] + # if "CURRENTMENU" in info["fields"] and opcode == "sensing_current": + # m = re.search(r"current\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["CURRENTMENU"] = [m.group(1).upper().strip(), None] + + # if "PROPERTY" in info["fields"] and opcode == "sensing_of": + # m = re.search(r"\((.+?)\) of", stmt_for_parse, re.IGNORECASE) + # if m: + # prop = m.group(1).strip() + # prop_map = { + # "x position": "x position", "y position": "y position", "direction": "direction", + # "costume #": "costume number", "costume name": "costume name", "size": "size", + # "volume": "volume", "backdrop #": "backdrop number", "backdrop name": "backdrop name" + # } + # info["fields"]["PROPERTY"] = [prop_map.get(prop, prop), None] + # if "WHENGREATERTHANMENU" in info["fields"] and opcode == "event_whengreaterthan": + # m = re.search(r"when\s*\[([^\]]+)\s*v\] >", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["WHENGREATERTHANMENU"] = [m.group(1).upper().strip(), None] if "WHENGREATERTHANMENU" in info["fields"] and opcode == "event_whengreaterthan": - m = re.search(r"when\s*\[([^\]]+)\s*v\] >", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["WHENGREATERTHANMENU"] = [m.group(1).upper().strip(), None] - if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": # For event_whenkeypressed hat block's field - m = re.search(r"when\s*\[([^\]]+)\s*v\] key pressed", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["KEY_OPTION"] = [m.group(1).strip(), None] - if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": # For event_whenbackdropswitchesto hat block's field - m = re.search(r"when backdrop switches to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) - if m: info["fields"]["BACKDROP"] = [m.group(1).strip(), None] + m = re.search(r"when\s*\[([^\]]+)\s*v\]\s*>", stmt_for_parse, re.IGNORECASE) + if m: + option = m.group(1).strip() + valid_options = ["LOUDNESS", "TIMER"] + cleaned = re.sub(r"[^a-z]", "", option.lower()).strip() + normalized_map = {v.lower(): v for v in valid_options} + if cleaned in normalized_map: option_val = normalized_map[cleaned].upper() + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = normalized_map[match[0]].upper() + else: option_val = "LOUDNESS" # fallback + info["fields"]["WHENGREATERTHANMENU"] = [option_val, None] + # if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": # For event_whenkeypressed hat block's field + # m = re.search(r"when\s*\[([^\]]+)\s*v\] key pressed", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["KEY_OPTION"] = [m.group(1).strip(), None] + if "KEY_OPTION" in info["fields"] and opcode == "event_whenkeypressed": + m = re.search(r"when\s*\[([^\]]+)\s*v\]\s*key pressed", stmt_for_parse, re.IGNORECASE) + if m: + option = m.group(1).strip() + # Valid key options + valid_options = [ "space", "up arrow", "down arrow", "right arrow", "left arrow", "any", + "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","0","1","2","3","4","5","6","7","8","9"] # temporary + cleaned = re.sub(r"[^a-z0-9]", "", option.lower()).strip() + normalized_map = { re.sub(r"[^a-z0-9]", "", v.lower()): v for v in valid_options } + if cleaned in normalized_map: option_val = normalized_map[cleaned] + else: + match = difflib.get_close_matches(cleaned, normalized_map.keys(), n=1, cutoff=0.5) + if match: option_val = normalized_map[match[0]] + else: option_val = "space" + info["fields"]["KEY_OPTION"] = [option_val, None] + # if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": # For event_whenbackdropswitchesto hat block's field + # m = re.search(r"when backdrop switches to\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) + # if m: info["fields"]["BACKDROP"] = [m.group(1).strip(), None] + if "BACKDROP" in info["fields"] and opcode == "event_whenbackdropswitchesto": + m = re.search(r"when\s*backdrop\s*switches\s*to\s*"r"(?:\(\s*\[\s*([^\]]+?)(?:\s*v)?\s*\]\s*\)"r"|\[\s*([^\]]+?)(?:\s*v)?\s*\]"r"|\(\s*([^)]+?)\s*\)"r"|([A-Za-z][^\n]+))",stmt_for_parse,re.IGNORECASE,) + if m: + option = (m.group(1) or m.group(2) or m.group(3) or m.group(4)).strip() + print(" :", option) + val, key = process_text(option) + if key == "backdrop": info["fields"]["BACKDROP"] = [val, None] + else: info["fields"]["BACKDROP"] = ["backdrop1", None] if "BROADCAST_OPTION" in info["fields"] and opcode == "event_whenbroadcastreceived": # For event_whenbroadcastreceived hat block's field m = re.search(r"when i receive\s*\[([^\]]+)\s*v\]", stmt_for_parse, re.IGNORECASE) if m: info["fields"]["BROADCAST_OPTION"] = [m.group(1).strip(), None] @@ -2416,7 +2753,6 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): args_str = proc_def_match.group(2) info["procedure_name"] = proc_name info["is_custom_definition"] = True - mutation_block = { "tagName": "mutation", "children": [], @@ -2435,7 +2771,6 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): mutation_block["argumentids"].append(arg_id) mutation_block["argumentnames"].append(arg) mutation_block["argumentdefaults"].append("") - info["mutation"] = mutation_block elif opcode == "procedures_call": @@ -2444,7 +2779,7 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): custom_block_name = call_match.group(1).strip() args_str = call_match.group(2) info["custom_block_name"] = custom_block_name - + info["mutation"] = { "tagName": "mutation", "children": [], @@ -2460,8 +2795,8 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): arg_input_name = f"argument_name_{idx+1}" info["mutation"]["argumentids"].append(arg_input_name) # Use the input name as argument ID info["mutation"]["argumentnames"].append(f"arg{idx+1}") # Placeholder name for mutation - info["inputs"][arg_input_name] = parse_reporter_or_value(arg_val_str, key, pick_key, all_generated_blocks) # Pass current block's key + i += 1 # Move to the next line # Final pass to ensure last blocks have next: None (already handled by stack pops) @@ -2516,8 +2851,8 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json): # Initialize dictionaries to store and reuse generated unique IDs # This prevents creating multiple unique IDs for the same variable/broadcast across different blocks - variable_id_map = defaultdict(lambda: generate_secure_token()) - broadcast_id_map = defaultdict(lambda: generate_secure_token()) + variable_id_map = defaultdict(lambda: generate_secure_token(20)) + broadcast_id_map = defaultdict(lambda: generate_secure_token(20)) # Define the mapping for input field names to their required integer types for shadows input_type_mapping = { @@ -2550,6 +2885,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json): "sensing_keypressed": [("KEY_OPTION", "sensing_keyoptions")], "sensing_of": [("OBJECT", "sensing_of_object_menu")], "sensing_touchingobject": [("TOUCHINGOBJECTMENU", "sensing_touchingobjectmenu")], + "sensing_distanceto": [("DISTANCETOMENU", "sensing_distancetomenu")], "control_create_clone_of": [("CLONE_OPTION", "control_create_clone_of_menu")], "sound_play": [("SOUND_MENU", "sound_sounds_menu")], "sound_playuntildone": [("SOUND_MENU", "sound_sounds_menu")], @@ -2606,7 +2942,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json): else: # Fallback: try original generated_output_json value if present, else synthesize fallback = gen_block_data.get("inputs", {}).get(input_name, - [1, [11, "message1", generate_secure_token()]]) + [1, [11, "message1", generate_secure_token(20)]]) processed_block["inputs"][input_name] = fallback continue @@ -2828,6 +3164,18 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json): processed_block["fields"][field_name] = field_value else: processed_block["fields"][field_name] = field_value + + if opcode == "control_stop": + stop_option = processed_block["fields"].get("STOP_OPTION", ["", None])[0] + hasnext_val = "true" if stop_option == "other scripts in sprite" else "false" + if "mutation" not in processed_block: + processed_block["mutation"] = { + "tagName": "mutation", + "children": [], + "hasnext": hasnext_val + } + else: + processed_block["mutation"]["hasnext"] = hasnext_val # Remove unwanted keys from the processed block (if somehow present) keys_to_remove = ["functionality", "block_shape", "id", "block_name", "block_type"] @@ -2837,7 +3185,7 @@ def process_scratch_blocks(all_generated_blocks, generated_output_json): processed_blocks[block_id] = processed_block return processed_blocks - + ################################################################################################################################################################# #--------------------------------------------------[Unique secret key for skelton json to make sure it donot overwrite each other]------------------------------- ################################################################################################################################################################# @@ -3096,10 +3444,12 @@ def _find_all_opcodes(code_block: str) -> list[str]: patterns = [ # --- Multi-line Control Blocks (most specific, non-greedy) --- (r"if <.+?> then(?:.|\n)+?else(?:.|\n)+?end", "control_if_else"), #(to test muliple stack) + # (r"if <.+?> then", "control_if"), + (r"if <.+?> then(?:(?!else).|\n)+?end", "control_if"), (r"forever", "control_forever"), - (r"if <.+?> then", "control_if"), (r"repeat until <.+?>", "control_repeat_until"), (r"repeat\s+(?:\(.+?\)|\[.+?(?:\s+v)?\]|\S+)", "control_repeat"), + # (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])", "control_stop"), (r"stop\s+(?:all|this script|other scripts in sprite|\[(?:all|this script|other scripts in sprite)(?:\s+v)?\])(?!\s+sounds)", "control_stop"), (r"when I start as a clone", "control_start_as_clone"), (r"create clone of \[.+?(?:\s+v)?\]", "control_create_clone_of"), @@ -3128,53 +3478,89 @@ def _find_all_opcodes(code_block: str) -> list[str]: (r"add\s+(?:\[.+?\]|\(.+?\)|\w+)\s+to\s+\[.+?(?:\s+v)?\]", "data_addtolist"), (r"delete\s*\((?!all\)).+?\)\s*of\s*\[.+?(?:\s+v)?\]", "data_deleteoflist"), (r"delete\s*\(all\)\s*of\s*\[.+?(?:\s+v)?\]", "data_deletealloflist"), + # (r"insert (.+?) at (\(.+?\)|\[.+?\]|\(\[.+?\]\)) of \[.+?(?:\s+v)?\]", "data_insertatlist"), (r"insert\s+(\(.+?\)|\[.+?\]|\(\[.+?\]\)|[^\s]+)\s+at\s+(\(.+?\)|\[.+?\]|\(\[.+?\]\)|\d+)\s+of\s+\[.+?(?:\s+v)?\]", "data_insertatlist"), + # (r"replace item (.+?) of \[.+?(?:\s+v)?\] with (.+)", "data_replaceitemoflist"), + # (r"replace item\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|.+?)\s+of\s+\[.+?(?:\s+v)?\]\s+with\s+(.+)", "data_replaceitemoflist"), (r"replace\s+item\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|[^\s]+)\s+of\s+\[.+?(?:\s+v)?\]\s+with\s+(\(.+?\)|\[\s*.+?\s*(?:v)?\]|.+)","data_replaceitemoflist"), + # (r"\[.+?(?:\s+v)?\] contains \[.+?\]\?", "data_listcontainsitem"), + # (r"?", "data_listcontainsitem"), (r"[<(]\s*\[[^\]]+?\s+v\]\s*contains\s*\[[^\]]+?\]\s*\??\s*[)>]", "data_listcontainsitem"), + # (r"\(item \# of (.+?) in \[.+?(?:\s+v)?\]\)", "data_itemnumoflist"), (r"\(item\s+#\s+of\s+\(?(.+?)\)?\s+in\s+\[.+?(?:\s+v)?\]\)", "data_itemnumoflist"), + # (r"\(item (.+?) of \[.+?(?:\s+v)?\]\)", "data_itemoflist"), + # (r"\(?item\s+(.+?)\s+of\s+\[.+?(?:\s+v)?\]\)?", "data_itemoflist"), + # (r"(??\s*""", "sensing_coloristouchingcolor"), + # (r"touching color #\w{6}\??", "sensing_touchingcolor"), + # (r"touching\s*(?:color\s*)?#\w{6}\??", "sensing_touchingcolor"), (r"(?)?", "sensing_mousedown"), + # (r"(?:[\[\(])?\s*mouse\s*x\s*(?:v)?\s*(?:[\]\)])?", "sensing_mousex"), + # (r"(?:[\[\(])?\s*mouse\s*y\s*(?:v)?\s*(?:[\]\)])?", "sensing_mousey"), # --- Sound Blocks --- # (r"play sound \[.+? v\] until done", "sound_playuntildone"), # (r"start sound \[.+? v\]", "sound_play"), (r"play sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)\s+until done", "sound_playuntildone"), - (r"start sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)", "sound_play"), + (r"start sound\s+(?:\[\s*.+?\s*v\]|\(?\s*.+?\s*\)?)", "sound_play"), (r"stop all sounds", "sound_stopallsounds"), + # (r"change volume by (.+?)", "sound_changevolumeby"), (r"change volume by\s*(?:\((.+?)\)|\[(.+?)\]|(.+))", "sound_changevolumeby"), + # (r"set volume to (.+?) %", "sound_setvolumeto"), (r"""set\ volume\ to\s+\(?\s*(?:-?\d+(?:\.\d+)?|\[?[a-zA-Z_][\w\s]*\]?(?:\ v)?)\s*\)?\s*%?""", "sound_setvolumeto"), (r"\(volume\)", "sound_volume"), # --- Motion Blocks --- + # (r"go to x: (.+?) y: (.+)", "motion_gotoxy"), (r"go to x:\s*\(?(.+?)\)?\s*y:\s*\(?(.+?)\)?", "motion_gotoxy"), + # (r"set x to (.+)", "motion_setx"), (r"set x to (.+)", "motion_setx"), (r"set y to (.+)", "motion_sety"), + # (r"move (.+?) steps", "motion_movesteps"), (r"move\s*\(?(.+?)\)?\s*(?:steps?)?", "motion_movesteps"), + # (r"turn right (.+?) degrees", "motion_turnright"), (r"turn right\s*\(?(.+?)\)?\s*(?:degrees?)?", "motion_turnright"), + # (r"turn left (.+?) degrees", "motion_turnleft"), (r"turn left\s*\(?(.+?)\)?\s*(?:degrees?)?", "motion_turnleft"), (r"go to\s*(?:random position|mouse-pointer|\[.*?\]|.+)", "motion_goto"), #(to mouse-pointer is not include here for now) + # (r"point in direction (.+)", "motion_pointindirection"), (r"point in direction\s*\(?(.+?)\)?", "motion_pointindirection"), (r"point towards \[.+? v\]", "motion_pointtowards"), + # (r"change x by (.+)", "motion_changexby"), + # (r"change y by (.+)", "motion_changeyby"), (r"change x by\s*\(?(.+?)\)?", "motion_changexby"), (r"change y by\s*\(?(.+?)\)?", "motion_changeyby"), + # (r"glide (.+?) secs to x: (.+?) y: (.+)", "motion_glidesecstoxy"), + # (r"glide (.+?) secs to \[.+? v\]", "motion_glideto"), (r"glide\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)\s*to\s*x:\s*\(?(.+?)\)?\s*y:\s*\(?(.+?)\)?", "motion_glidesecstoxy"), (r"glide\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)\s*to\s*\[.*?\]", "motion_glideto"), (r"if on edge, bounce", "motion_ifonedgebounce"), + # (r"set rotation style \[.+? v\]", "motion_setrotationstyle"), (r"set rotation style\s*\[(?:left-right|all around|don't rotate)(?:\s*v)?\]", "motion_setrotationstyle"), (r"\(?x position\)?", "motion_xposition"), #(to x positon may detect where var is used) (r"\(?y position\)?", "motion_yposition"), #(to y position may detect where var is used) @@ -3188,12 +3574,23 @@ def _find_all_opcodes(code_block: str) -> list[str]: (r"next backdrop", "looks_nextbackdrop"), (r"^\s*show\s*$", "looks_show"), (r"^\s*hide\s*$", "looks_hide"), + # (r"say \[.+?\] for (.+?) seconds", "looks_sayforsecs"), + # (r"say \[.+?\]", "looks_say"), + # (r"think \[.+?\] for (.+?) seconds", "looks_thinkforsecs"), + # (r"think \[.+?\]", "looks_think"), + # (r"change size by (.+)", "looks_changesizeby"), + # (r"set size to (.+?) %", "looks_setsizeto"), + # (r"say\s*\[.+?\]\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_sayforsecs"), (r"say\s+(?:\[.+?\]|\(.+?\)|.+?)\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_sayforsecs"), + # (r"say\s*\[.+?\]", "looks_say"), (r"say\s+(?!.*\bfor\b\s*\(?\d+\)?\s*(?:sec|secs|second|seconds))(?:\[.+?\]|\(.+?\)|.+?)", "looks_say"), (r"think\s*\[.+?\]\s*for\s*\(?(.+?)\)?\s*(?:sec|secs|second|seconds)", "looks_thinkforsecs"), (r"think\s*\[.+?\]", "looks_think"), (r"change size by\s*\(?(.+?)\)?", "looks_changesizeby"), + # (r"set size to\s*\(?(.+?)\)?\s*%?", "looks_setsizeto"), (r"set size to\s*\(?(.+?)\)?\s*%?", "looks_setsizeto"), + # (r"change \[.+? v\] effect by (.+)", "looks_changeeffectby"), + # (r"set \[.+? v\] effect to (.+)", "looks_seteffectto"), (r"change\s*\[(.+?)(?:\s*v)?\]\s*effect by\s*\(?(.+?)\)?", "looks_changeeffectby"), (r"set\s*\[(.+?)(?:\s*v)?\]\s*effect to\s*\(?(.+?)\)?", "looks_seteffectto"), (r"clear graphic effects", "looks_cleargraphiceffects"), @@ -3201,23 +3598,55 @@ def _find_all_opcodes(code_block: str) -> list[str]: (r"\(backdrop \[.+? v\]\)", "looks_backdropnumbername"), # --- Operators --- + # (r"<.+?\s*<\s*.+?>", "operator_lt"), + # (r"<.+?\s*=\s*.+?>", "operator_equals"), + # (r"<.+?\s*>\s*.+?>", "operator_gt"), + # (r"<.+? and .+?>", "operator_and"), + # (r"<.+? or .+?>", "operator_or"), + # (r"", "operator_not"), + # (r"\(join (.+?) (.+?)\)", "operator_join"), + # (r"\(.+?\s*\+\s*.+?\)", "operator_add"), + # (r"\(.+?\s*-\s*.+?\)", "operator_subtract"), + # (r"\(.+?\s*\*\s*.+?\)", "operator_multiply"), + # (r"\(.+?\s*/\s*.+?\)", "operator_divide"), + # (r"\(pick random (.+?) to (.+?)\)", "operator_random"), + # (r"\(letter (.+?) of (.+?)\)", "operator_letterof"), + # (r"\(length of (.+?)\)", "operator_length"), + # (r"\(.+? mod (.+?)\)", "operator_mod"), + # (r"\(round (.+?)\)", "operator_round"), + # (r"\(.+? contains (.+?)\)", "operator_contains"), + # (r"\(.+? of (.+?)\)", "operator_mathop"), + # (r"<\s*[^<>]+\s*<\s*[^<>]+\s*>", "operator_lt"), + # (r"<\s*[^<>]+\s*=\s*[^<>]+\s*>", "operator_equals"), + # (r"<\s*[^<>]+\s*>\s*[^<>]+\s*>", "operator_gt"), (r"<\s*[^<>?]+\s*<\s*[^<>?]+\s*>", "operator_lt"), (r"<\s*[^<>?]+\s*=\s*[^<>?]+\s*>", "operator_equals"), (r"<\s*[^<>?]+\s*>\s*[^<>?]+\s*>", "operator_gt"), (r"<\s*.*?\s+and\s+.*?\s*>", "operator_and"), (r"<\s*.*?\s+or\s+.*?\s*>", "operator_or"), (r"<\s*not\s+.*?\s*>", "operator_not"), + + # (r"\(join\s+(.+?)\s+(.+?)\)", "operator_join"), (r"(?:\(join\s+(.+?)\s+(.+?)\)|join\s+(.+?)\s+(.+?))", "operator_join"), + # (r"\(\s*[^()]+\s*\+\s*[^()]+\s*\)", "operator_add"), + # (r"\(\s*[^()]+\s*-\s*[^()]+\s*\)", "operator_subtract"), + # (r"\(\s*[^()]+\s*\*\s*[^()]+\s*\)", "operator_multiply"), + # (r"\(\s*[^()]+\s*/\s*[^()]+\s*\)", "operator_divide"), + # Allow nested expressions inside () (r"\(\s*.+?\s*\+\s*.+?\s*\)", "operator_add"), + # (r"\(\s*.+?\s*-\s*.+?\s*\)", "operator_subtract"), (r"\(\s*(?!-\s*\d+(?:\.\d+)?\s*\))(.+?)\s+-\s+(.+?)\)", "operator_subtract"), (r"\(\s*.+?\s*\*\s*.+?\s*\)", "operator_multiply"), (r"\(\s*.+?\s*/\s*.+?\s*\)", "operator_divide"), (r"\(pick random\s+(.+?)\s+to\s+(.+?)\)", "operator_random"), (r"\(letter\s+(.+?)\s+of\s+(.+?)\)", "operator_letterof"), (r"\(length of\s+(.+?)\)", "operator_length"), + # (r"\(\s*[^()]+\s+mod\s+[^()]+\s*\)", "operator_mod"), (r"\(\s*.+?\s+mod\s+.+?\s*\)", "operator_mod"), (r"\(round\s+(.+?)\)", "operator_round"), + # (r"\(\s*[^()]+\s+contains\s+[^()]+\s*\)", "operator_contains"), (r"[<(]\s*\[(?![^\]]*\s+v\])[^\]]+?\]\s*contains\s*\[[^\]]+?\]\s*\??\s*[)>]", "operator_contains"), + # (r"\(\s*[^()]+\s+of\s+[^()]+\s*\)", "operator_mathop"), (r"\(\s*\[?(abs|floor|ceiling|sqrt|sin|cos|tan|asin|acos|atan|ln|log|e \^|10 \^)\s*(?:v)?\]?\s+of\s+.+?\)", "operator_mathop"), ] @@ -3257,75 +3686,49 @@ def analyze_opcode_counts(pseudo_code: str) -> list[dict]: #--------------------------------------------------[Helper function to seperate an correct the json]------------------------------------------------------------- ################################################################################################################################################################# -# def separate_scripts(pseudocode_string): -# """ -# Separates a block of Scratch pseudocode into a list of individual scripts. - -# This function finds the start of each "hat" block and slices the -# original string to capture the full code block for each script, -# providing a more robust and reliable separation. - -# Args: -# pseudocode_string (str): A string containing Scratch pseudocode. - -# Returns: -# list: A list of strings, where each string is a complete, -# separated script. -# """ -# # Define the "hat" block patterns with more robust regex. -# # We use a non-capturing group (?:...) for the patterns. -# # We use a logical OR (|) to combine them into a single pattern. -# delimiter_patterns = ( -# r"when green flag clicked|when flag clicked|when \S+ key pressed|" -# r"when this sprite clicked|when backdrop switches to \[.*?\]|" -# r"when I receive \[.*?\]|when \[.*?\] > \[.*?\]" -# ) - -# # Use re.finditer to get an iterator of all hat block matches. -# # The `re.DOTALL` flag allows the '.' to match newlines. -# matches = list(re.finditer(delimiter_patterns, pseudocode_string, flags=re.DOTALL | re.IGNORECASE)) - -# scripts = [] -# # If no matches are found, return an empty list. -# if not matches: -# return [] - -# # Iterate through the matches to slice the original string. -# for i in range(len(matches)): -# start = matches[i].start() -# end = matches[i+1].start() if i + 1 < len(matches) else len(pseudocode_string) - -# # Slice the pseudocode string from the start of one match to the start -# # of the next, or to the end of the string. -# script = pseudocode_string[start:end] -# scripts.append(script.strip()) - -# return scripts def separate_scripts(pseudocode_string): """ - Split a block of Scratch pseudocode into individual scripts. - Each script starts at a 'when ...' hat block that appears at the start - of a line (leading whitespace allowed). - """ - # Robust, non-capturing hat-block patterns (no capturing groups!) - patterns = [ - r"when\s+(?:green\s+flag\s+)?click(?:ed)?\b", # when flag clicked / when green flag clicked - r"when\s+(?:\S+(?:\s+\S+)*)\s+key\s+press(?:ed)?\b", # when space key pressed / when up arrow key press - r"when\s+this\s+sprite\s+click(?:ed)?\b", # when this sprite clicked - r"when\s+backdrop\s+switch(?:es|ed)?\s+to\s*\[[^\]]*\]", # when backdrop switches to [..] - r"when\s+I\s+receive(?:d)?\s*\[[^\]]*\]", # when I receive [..] / when I received [..] - r"when\s*\[[^\]]*\]\s*>\s*\[[^\]]*\]" # when [sensor] > [value] - ] - - # Build a lookahead that finds positions just before a hat block at line-start - lookahead = r"(?=^\s*(?:{}))".format("|".join(patterns)) + Separates a block of Scratch pseudocode into a list of individual scripts. - # Use MULTILINE so ^ matches start of lines, DOTALL to allow patterns that may include brackets/newlines as needed - parts = re.split(lookahead, pseudocode_string, flags=re.IGNORECASE | re.MULTILINE | re.DOTALL) + This function finds the start of each "hat" block and slices the + original string to capture the full code block for each script, + providing a more robust and reliable separation. - # Filter out empties and strip leading/trailing whitespace/newlines - scripts = [p.strip() for p in parts if p and p.strip()] + Args: + pseudocode_string (str): A string containing Scratch pseudocode. + Returns: + list: A list of strings, where each string is a complete, + separated script. + """ + # Define the "hat" block patterns with more robust regex. + # We use a non-capturing group (?:...) for the patterns. + # We use a logical OR (|) to combine them into a single pattern. + delimiter_patterns = ( + r"when green flag clicked|when flag clicked|when \S+ key pressed|" + r"when this sprite clicked|when backdrop switches to \[.*?\]|" + r"when I receive \[.*?\]|when \[.*?\] > \[.*?\]" + ) + + # Use re.finditer to get an iterator of all hat block matches. + # The `re.DOTALL` flag allows the '.' to match newlines. + matches = list(re.finditer(delimiter_patterns, pseudocode_string, flags=re.DOTALL | re.IGNORECASE)) + + scripts = [] + # If no matches are found, return an empty list. + if not matches: + return [] + + # Iterate through the matches to slice the original string. + for i in range(len(matches)): + start = matches[i].start() + end = matches[i+1].start() if i + 1 < len(matches) else len(pseudocode_string) + + # Slice the pseudocode string from the start of one match to the start + # of the next, or to the end of the string. + script = pseudocode_string[start:end] + scripts.append(script.strip()) + return scripts def transform_logic_to_action_flow(source_data, description=""): @@ -3389,41 +3792,226 @@ def block_builder(opcode_count,pseudo_code): renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences) return renamed_blocks +################################################################################################################################################################# +#--------------------------------------------------[Helper function to for OCR handling]-------------------------------------------------------------------------- +################################################################################################################################################################# + +def canonicalize(s: str) -> str: + s = s.strip().lower() + s = re.sub(r'^[\[\(\{\'"]+|[\]\)\}\'"]+$', '', s) + s = re.sub(r'[^a-z0-9]+', '-', s) + s = s.strip('-') + return s + +def pluralize_word_token(token: str): + forms = set() + if not token: + return forms + forms.add(token + 's') + if token.endswith('y') and len(token) > 1 and token[-2] not in 'aeiou': + forms.add(token[:-1] + 'ies') + if token.endswith('f'): + forms.add(token[:-1] + 'ves') + if token.endswith('fe'): + forms.add(token[:-2] + 'ves') + if token.endswith('o'): + forms.add(token + 'es') + forms.add(token[:-1] + 'oes') + if token.endswith('us'): + forms.add(token[:-2] + 'i') + if token.endswith('is'): + forms.add(token[:-2] + 'es') + if token.endswith(('s','x','z','ch','sh')): + forms.add(token + 'es') + return forms + +def plural_variants_for_keyword(keyword: str): + parts = re.split(r'(\s+|-)', keyword) + for i in range(len(parts)-1, -1, -1): + if re.search(r'[A-Za-z0-9]', parts[i]): + last_idx = i + break + else: + return [] + token = re.sub(r'[^A-Za-z0-9]', '', parts[last_idx]) + if not token: + return [] + plurals = pluralize_word_token(token.lower()) + variants = [] + for p in plurals: + new_parts = parts.copy() + new_parts[last_idx] = re.sub(r'[A-Za-z0-9]+', p, new_parts[last_idx], count=1, flags=re.IGNORECASE) + variants.append(''.join(new_parts)) + return variants + +def is_valid_word(text: str): + if d is None: + return None + cleaned = re.sub(r'[^a-z]', '', text.lower()) + if not cleaned: + return False + return d.check(cleaned) + +# Build canonical_map where each value is a list of (original_keyword, category) +def build_keyword_maps(sprite_keywords, backdrop_keywords, sound_keywords=None): + all_entries = [] + all_entries.extend([(kw, 'sprite') for kw in sprite_keywords]) + all_entries.extend([(kw, 'backdrop') for kw in backdrop_keywords]) + if sound_keywords: + all_entries.extend([(kw, 'sound') for kw in sound_keywords]) + + canonical_map = {} # canonical -> list of (keyword, category) + canonical_list = [] # list of canonical keys (for difflib) + + for kw, category in all_entries: + ck = canonicalize(kw) + if ck not in canonical_map: + canonical_map[ck] = [] + canonical_list.append(ck) + # keep unique (keyword, category) + if (kw, category) not in canonical_map[ck]: + canonical_map[ck].append((kw, category)) + + # add plural variants mapping to same original keyword/category + for variant in plural_variants_for_keyword(kw): + vck = canonicalize(variant) + if vck not in canonical_map: + canonical_map[vck] = [] + canonical_list.append(vck) + if (kw, category) not in canonical_map[vck]: + canonical_map[vck].append((kw, category)) + + return canonical_map, canonical_list + +# priority chooser when multiple (kw, category) exist for same canonical form +def choose_preferred(kc_list): + # preference order + pref = ('sprite', 'backdrop', 'sound') + for p in pref: + for kw, cat in kc_list: + if cat == p: + return kw, cat + # fallback: return first if none matched preference + return kc_list[0] + +def dynamic_cutoff(text: str): + L = len(text) + if L <= 3: + return 0.95 + if L <= 6: + return 0.9 + if L <= 10: + return 0.85 + return 0.75 + +def get_best_match(text, canonical_map, canonical_list): + ctext = canonicalize(text) + cutoff = dynamic_cutoff(ctext) + matches = difflib.get_close_matches(ctext, canonical_list, n=1, cutoff=cutoff) + if matches: + kc_list = canonical_map[matches[0]] + kw, cat = choose_preferred(kc_list) + return kw, cat + + # attempt singularization fallbacks + for transform in (lambda s: s[:-3] + 'y' if s.endswith('ies') else None, + lambda s: s[:-3] + 'f' if s.endswith('ves') else None, + lambda s: s[:-1] if s.endswith('s') else None): + transformed = transform(ctext) + if transformed: + matches = difflib.get_close_matches(transformed, canonical_list, n=1, cutoff=cutoff) + if matches: + kc_list = canonical_map[matches[0]] + kw, cat = choose_preferred(kc_list) + return kw, cat + return None + +def check_and_print_nearest_matches(text, canonical_map, canonical_list, n=3): + ctext = canonicalize(text) + cutoff = 0.6 + matches = difflib.get_close_matches(ctext, canonical_list, n=n, cutoff=cutoff) + if matches: + suggestions = [] + for m in matches: + suggestions.extend([kw for (kw, cat) in canonical_map[m]]) + print("Did you mean one of these? " + ", ".join(suggestions[:8])) + +# ----- Main process_text that returns (matched_keyword, category) ----- +def process_text(text): + # --- INSERT your full sprite_keywords & backdrop_keywords lists here --- + sprite_keywords = [ + "Abby", "abby-a", "abby-b", "abby-c", "abby-d", "Amon", "amon", "Andie", "andie-a", "andie-b", "andie-c", "andie-d", "Anina Dance", "anina stance", "anina top stand", "anina top R step", "anina top L step", "anina top freeze", "anina R cross", "anina pop front", "anina pop down", "anina pop left", "anina pop right", "anina pop L arm", "anina pop stand", "anina pop R arm", "Apple", "apple", "Arrow1", "arrow1-a", "arrow1-b", "arrow1-c", "arrow1-d", "Avery Walking", "avery walking-a", "avery walking-b", "avery walking-c", "avery walking-d", "Avery", "avery-a", "avery-b", "Ball", "ball-a", "ball-b", "ball-c", "ball-d", "ball-e", "Ballerina", "ballerina-a", "ballerina-b", "ballerina-c", "ballerina-d", "Balloon1", "balloon1-a", "balloon1-b", "balloon1-c", "Bananas", "bananas", "Baseball", "baseball", "Basketball", "basketball", "Bat", "bat-a", "bat-b", "bat-c", "bat-d", "Batter", "batter-a", "batter-b", "batter-c", "batter-d", "Beachball", "beachball", "Bear-walking", "bear-walk-a", "bear-walk-b", "bear-walk-c", "bear-walk-d", "bear-walk-e", "bear-walk-f", "bear-walk-g", "bear-walk-h", "Bear", "bear-a", "bear-b", "Beetle", "beetle", "Bell", "bell1", "Ben", "ben-a", "ben-b", "ben-c", "ben-d", "Block-A", "Block-a", "Block-B", "Block-b", "Block-C", "Block-c", "Block-D", "Block-d", "Block-E", "Block-e", "Block-F", "Block-f", "Block-G", "Block-g", "Block-H", "Block-h", "Block-I", "Block-i", "Block-J", "Block-j", "Block-K", "Block-k", "Block-L", "Block-l", "Block-M", "Block-m", "Block-N", "Block-n", "Block-O", "Block-o", "Block-P", "Block-p", "Block-Q", "Block-q", "Block-R", "Block-r", "Block-S", "Block-s", "Block-T", "Block-t", "Block-U", "Block-u", "Block-V", "Block-v", "Block-W", "Block-w", "Block-X", "Block-x", "Block-Y", "Block-y", "Block-Z", "Block-z", "Bowl", "bowl-a", "Bowtie", "bowtie", "Bread", "bread", "Broom", "broom", "Buildings", "building-a", "building-b", "building-c", "building-d", "building-e", "building-f", "building-g", "building-h", "building-i", "building-j", "Butterfly 1", "butterfly1-a", "butterfly1-b", "butterfly1-c", "Butterfly 2", "butterfly2-a", "butterfly2-b", "Button1", "button1", "Button2", "button2-a", "button2-b", "Button3", "button3-a", "button3-b", "Button4", "button4-a", "button4-b", "Button5", "button5-a", "button5-b", "Cake", "cake-a", "cake-b", "Calvrett", "calvrett jumping", "calvrett thinking", "Casey", "casey-a", "casey-b", "casey-c", "casey-d", "Cassy Dance", "cassy-a", "cassy-b", "cassy-c", "cassy-d", "Cat 2", "cat 2", "Cat Flying", "cat flying-a", "cat flying-b", "Cat", "cat-a", "cat-b", "Catcher", "catcher-a", "catcher-b", "catcher-c", "catcher-d", "Centaur", "centaur-a", "centaur-b", "centaur-c", "centaur-d", "Champ99", "champ99-a", "champ99-b", "champ99-c", "champ99-d", "champ99-e", "champ99-f", "champ99-g", "Characters 1", "character1-a", "character1-b", "character1-c", "character1-d", "character1-e", "character1-f", "character1-g", "character1-h", "character1-i", "character1-j", "character1-k", "character1-l", "character1-m", "Characters 2", "character2-a", "character2-b", "character2-c", "character2-d", "character2-e", "character2-f", "character2-g", "character2-h", "character2-i", "character2-j", "Cheesy Puffs", "cheesy puffs", "Chick", "chick-a", "chick-b", "chick-c", "City Bus", "City Bus-a", "City Bus-b", "Cloud", "cloud", "Clouds", "cloud-a", "cloud-b", "cloud-c", "cloud-d", "Convertible 2", "convertible 3", "Convertible", "convertible", "Crab", "crab-a", "crab-b", "Crystal", "crystal-a", "crystal-b", "D-Money Dance", "dm stance", "dm top stand", "dm top R leg", "dm top L leg", "dm freeze", "dm pop front", "dm pop down", "dm pop left", "dm pop right", "dm pop L arm", "dm pop stand", "dm pop R arm", "Dan", "dan-a", "dan-b", "Dani", "Dani-a", "Dani-b", "Dani-c", "Dee", "dee-a", "dee-b", "dee-c", "dee-d", "dee-e", "Devin", "devin-a", "devin-b", "devin-c", "devin-d", "Dinosaur1", "dinosaur1-a", "dinosaur1-b", "dinosaur1-c", "dinosaur1-d", "Dinosaur2", "dinosaur2-a", "dinosaur2-b", "dinosaur2-c", "dinosaur2-d", "Dinosaur3", "dinosaur3-a", "dinosaur3-b", "dinosaur3-c", "dinosaur3-d", "dinosaur3-e", "Dinosaur4", "dinosaur4-a", "dinosaur4-b", "dinosaur4-c", "dinosaur4-d", "Dinosaur5", "Dinosaur5-a", "Dinosaur5-b", "Dinosaur5-c", "Dinosaur5-d", "Dinosaur5-e", "Dinosaur5-f", "Dinosaur5-g", "Dinosaur5-h", "Diver1", "diver1", "Diver2", "diver2", "Dog1", "dog1-a", "dog1-b", "Dog2", "dog2-a", "dog2-b", "dog2-c", "Donut", "donut", "Dorian", "dorian-a", "dorian-b", "dorian-c", "dorian-d", "Dot", "dot-a", "dot-b", "dot-c", "dot-d", "Dove", "dove-a", "dove-b", "Dragon", "dragon-a", "dragon-b", "dragon-c", "Dragonfly", "Dragonfly-a", "Dragonfly-b", "Dress", "dress-a", "dress-b", "dress-c", "Drum Kit", "drum-kit", "drum-kit-b", "Drum-cymbal", "drum-cymbal-a", "drum-cymbal-b", "Drum-highhat", "drum-highhat-a", "drum-highhat-b", "Drum-snare", "drum-snare-a", "drum-snare-b", "Drum", "drum-a", "drum-b", "Drums Conga", "Drums Conga-a", "Drums Conga-b", "Drums Tabla", "Tabla-a", "Tabla-b", "Duck", "duck", "Earth", "earth", "Easel", "Easel-a", "Easel-b", "Easel-c", "Egg", "egg-a", "egg-b", "egg-c", "egg-d", "egg-e", "egg-f", "Elephant", "elephant-a", "elephant-b", "Elf", "elf-a", "elf-b", "elf-c", "elf-d", "elf-e", "Fairy", "fairy-a", "fairy-b", "fairy-c", "fairy-d", "fairy-e", "Fish", "fish-a", "fish-b", "fish-c", "fish-d", "Fishbowl", "Fishbowl-a", "Fishbowl-b", "Food Truck", "Food Truck-a", "Food Truck-b", "Food Truck-c", "Football", "football running", "football standing", "Fortune Cookie", "fortune cookie", "Fox", "fox-a", "fox-b", "fox-c", "Frank", "frank-a", "frank-b", "frank-c", "frank-d", "Frog 2 ", "Frog 2-a", "Frog 2-b", "Frog 2-c", "Frog", "frog", "Fruit Platter", "fruit platter", "Fruit Salad", "fruitsalad", "Ghost", "ghost-a", "ghost-b", "ghost-c", "ghost-d", "Gift", "gift-a", "gift-b", "Giga Walking", "Giga walk1", "Giga walk2", "Giga walk3", "Giga", "giga-a", "giga-b", "giga-c", "giga-d", "Giraffe", "giraffe-a", "giraffe-b", "giraffe-c", "Glass Water", "glass water-a", "glass water-b", "Glasses", "glasses-a", "glasses-b", "glasses-c", "glasses-e", "Glow-0", "Glow-0", "Glow-1", "Glow-1", "Glow-2", "Glow-2", "Glow-3", "Glow-3", "Glow-4", "Glow-4", "Glow-5", "Glow-5", "Glow-6", "Glow-6", "Glow-7", "Glow-7", "Glow-8", "Glow-8", "Glow-9", "Glow-9", "Glow-A", "Glow-A", "Glow-B", "Glow-B", "Glow-C", "Glow-C", "Glow-D", "Glow-D", "Glow-E", "Glow-E", "Glow-F", "Glow-F", "Glow-G", "Glow-G", "Glow-H", "Glow-H", "Glow-I", "Glow-I", "Glow-J", "Glow-J", "Glow-K", "Glow-K", "Glow-L", "Glow-L", "Glow-M", "Glow-M", "Glow-N", "Glow-N", "Glow-O", "Glow-O", "Glow-P", "Glow-P", "Glow-Q", "Glow-Q", "Glow-R", "Glow-R", "Glow-S", "Glow-S", "Glow-T", "Glow-T", "Glow-U", "Glow-U", "Glow-V", "Glow-V", "Glow-W", "Glow-W", "Glow-X", "Glow-X", "Glow-Y", "Glow-Y", "Glow-Z", "Glow-Z", "Goalie", "goalie-a", "goalie-b", "goalie-c", "goalie-d", "goalie-e", "Goblin", "goblin-a", "goblin-b", "goblin-c", "goblin-d", "Gobo", "gobo-a", "gobo-b", "gobo-c", "Grasshopper", "Grasshopper-a", "Grasshopper-b", "Grasshopper-c", "Grasshopper-d", "Grasshopper-e", "Grasshopper-f", "Green Flag", "green flag", "Griffin", "Griffin-a", "Griffin-b", "Griffin-c", "Griffin-d", "Guitar-electric1", "guitar-electric1-a", "guitar-electric1-b", "Guitar-electric2", "guitar-electric2-a", "guitar-electric2-b", "Guitar", "guitar-a", "guitar-b", "Hannah", "hannah-a", "hannah-b", "hannah-c", "Hare", "hare-a", "hare-b", "hare-c", "Harper", "harper-a", "harper-b", "harper-c", "Hat1 ", "hat-a", "hat-b", "hat-c", "hat-d", "Hatchling", "hatchling-a", "hatchling-b", "hatchling-c", "Heart Candy", "heart code", "heart love", "heart sweet", "heart smile", "Heart Face", "heart face", "Heart", "heart red", "heart purple", "Hedgehog", "hedgehog-a", "hedgehog-b", "hedgehog-c", "hedgehog-d", "hedgehog-e", "Hen", "hen-a", "hen-b", "hen-c", "hen-d", "Hippo1", "hippo1-a", "hippo1-b", "Home Button", "home button", "Horse", "horse-a", "horse-b", "Jaime", "jaime-a", "jaime-b", "jaime walking-a", "jaime walking-b", "jaime walking-c", "jaime walking-d", "jaime walking-e", "Jamal", "jamal-a", "jamal-b", "jamal-c", "jamal-d", "Jar", "jar-a", "jar-b", "Jellyfish", "jellyfish-a", "jellyfish-b", "jellyfish-c", "jellyfish-d", "Jordyn", "jordyn-a", "jordyn-b", "jordyn-c", "jordyn-d", "Jouvi Dance", "jo stance", "jo top stand", "jo top R leg", "jo top L leg", "jo top R cross", "jo top L cross", "jo pop front", "jo pop down", "jo pop left", "jo pop right", "jo pop L arm", "jo pop stand", "jo pop R arm", "Kai", "kai-a", "kai-b", "Key", "key", "Keyboard", "keyboard-a", "keyboard-b", "Kia", "Kia-a", "Kia-b", "Kia-c", "Kiran", "kiran-a", "kiran-b", "kiran-c", "kiran-d", "kiran-e", "kiran-f", "Knight", "knight", "Ladybug1", "ladybug2", "Ladybug2", "ladybug2-a", "ladybug2-b", "Laptop", "laptop", "LB Dance", "lb stance", "lb top stand", "lb top R leg", "lb top L leg", "lb top L cross", "lb top R cross", "lb pop front", "lb pop down", "lb pop left", "lb pop right", "lb pop L arm", "lb pop stand", "lb pop R arm", "Lightning", "lightning", "Line", "line", "Lion", "lion-a", "lion-b", "lion-c", "Llama", "llama", "llama-b", "llama-c", "Luca", "Luca-a", "Luca-b", "Luca-c", "Magic Wand", "magicwand", "Marian", "Marian-a", "Marian-b", "Marian-c", "Marian-d", "Marian-e", "Max", "max-a", "max-b", "max-c", "max-d", "Mermaid", "mermaid-a", "mermaid-b", "mermaid-c", "mermaid-d", "Microphone", "microphone-a", "microphone-b", "Milk", "milk-a", "milk-b", "milk-c", "milk-d", "milk-e", "Monet", "monet-a", "monet-b", "monet-c", "monet-d", "monet-e", "Monkey", "monkey-a", "monkey-b", "monkey-c", "Motorcycle", "Motorcycle-a", "Motorcycle-b", "Motorcycle-c", "Motorcycle-d", "Mouse1", "mouse1-a", "mouse1-b", "Muffin", "muffin-a", "muffin-b", "Nano", "nano-a", "nano-b", "nano-c", "nano-d", "Neigh Pony", "neigh pony", "Noor", "Noor-a", "Noor-b", "Noor-c", "Octopus", "octopus-a", "octopus-b", "octopus-c", "octopus-d", "octopus-e", "Orange", "orange", "Orange2", "orange2-a", "orange2-b", "Outfielder", "outfielder-a", "outfielder-b", "outfielder-c", "outfielder-d", "Owl", "owl-a", "owl-b", "owl-c", "Paddle", "paddle", "Panther", "panther-a", "panther-b", "panther-c", "Pants", "pants-a", "pants-b", "Parrot", "parrot-a", "parrot-b", "Party Hats", "Party Hat-a", "Party Hat-b", "Party Hat-e", "Pencil", "pencil-a", "pencil-b", "Penguin 2", "penguin2-a", "penguin2-b", "penguin2-c", "penguin2-d", "Penguin", "penguin-a", "penguin-b", "penguin-c", "Pico Walking", "Pico walk1", "Pico walk2", "Pico walk3", "Pico walk4", "Pico", "pico-a", "pico-b", "pico-c", "pico-d", "Pitcher", "pitcher-a", "pitcher-b", "pitcher-c", "pitcher-d", "Planet2", "planet2", "Polar Bear", "polar bear-a", "polar bear-b", "polar bear-c", "Potion", "potion-a", "potion-b", "potion-c", "Prince", "prince", "Princess", "princess-a", "princess-b", "princess-c", "princess-d", "princess-e", "Pufferfish", "pufferfish-a", "pufferfish-b", "pufferfish-c", "pufferfish-d", "Puppy", "puppy right", "puppy sit", "puppy side", "puppy back", "Rabbit", "rabbit-a", "rabbit-b", "rabbit-c", "rabbit-d", "rabbit-e", "Radio", "Radio-a", "Radio-b", "Rainbow", "rainbow", "Referee", "referee-a", "referee-b", "referee-c", "referee-d", "Reindeer", "reindeer", "Retro Robot", "Retro Robot a", "Retro Robot b", "Retro Robot c", "Ripley", "ripley-a", "ripley-b", "ripley-c", "ripley-d", "ripley-e", "ripley-f", "Robot", "robot-a", "robot-b", "robot-c", "robot-d", "Rocketship", "rocketship-a", "rocketship-b", "rocketship-c", "rocketship-d", "rocketship-e", "Rocks", "rocks", "Rooster", "rooster-a", "rooster-b", "rooster-c", "Ruby", "ruby-a", "ruby-b", "Sailboat", "sailboat", "Sam", "sam", "Sasha", "Sasha-a", "Sasha-b", "Sasha-c", "Saxophone", "saxophone-a", "saxophone-b", "Scarf", "Scarf-a", "Scarf-b", "Scarf-c", "Shark 2", "shark2-a", "shark2-b", "shark2-c", "Shark", "shark-a", "shark-b", "Shirt", "shirt-a", "Shoes", "shoes-a", "shoes-b", "shoes-d", "shoes-c", "Shorts", "shorts-a", "shorts-b", "shorts-c", "Singer1", "Singer1", "Skeleton", "skeleton-a", "skeleton-b", "skeleton-d", "skeleton-e", "Snake", "snake-a", "snake-b", "snake-c", "Snowflake", "snowflake", "Snowman", "snowman", "Soccer Ball", "soccer ball", "Speaker", "speaker", "Squirrel", "squirrel", "Star", "star", "Starfish", "starfish-a", "starfish-b ", "Stop", "stop", "Story-A", "story-A-1", "story-A-2", "story-A-3", "Story-B", "story-B-1", "story-B-2", "story-B-3", "Story-C", "story-C-1", "story-C-2", "story-C-3", "Story-D", "story-D-1", "story-D-2", "story-D-3", "Story-E", "story-E-1", "story-E-2", "story-E-3", "Story-F", "story-F-1", "story-F-2", "story-F-3", "Story-G", "story-G-1", "story-G-2", "story-G-3", "Story-H", "story-H-1", "story-H-2", "story-H-3", "Story-I", "story-I-1", "story-I-2", "story-I-3", "Story-J", "story-J-1", "story-J-2", "story-J-3", "Story-K", "story-K-1", "story-K-2", "story-K-3", "Story-L", "story-L-1", "story-L-2", "story-L-3", "Story-M", "story-M-1", "story-M-2", "story-M-3", "Story-N", "story-N-1", "story-N-2", "story-N-3", "Story-O", "story-O-1", "story-O-2", "story-O-3", "Story-P", "story-P-1", "story-P-2", "story-P-3", "Story-Q", "story-Q-1", "story-Q-2", "story-Q-3", "Story-R", "story-R-1", "story-R-2", "story-R-3", "Story-S", "story-S-1", "story-S-2", "story-S-3", "Story-T", "story-T-1", "story-T-2", "story-T-3", "Story-U", "story-U-1", "story-U-2", "story-U-3", "Story-V", "story-V-1", "story-V-2", "story-V-3", "Story-W", "story-W-1", "story-W-2", "story-W-3", "Story-X", "story-X-1", "story-X-2", "story-X-3", "Story-Y", "story-Y-1", "story-Y-2", "story-Y-3", "Story-Z", "story-Z-1", "story-Z-2", "story-Z-3", "Strawberry", "strawberry-a", "strawberry-b", "strawberry-c", "strawberry-d", "strawberry-e", "Sun", "sun", "Sunglasses1", "sunglasses-a", "sunglasses-b", "Taco", "Taco", "Taco-wizard", "Takeout", "takeout-a", "takeout-b", "takeout-c", "takeout-d", "takeout-e", "Tatiana", "Tatiana-a", "Tatiana-b", "Tatiana-c", "Tatiana-d", "Taylor", "Taylor-a", "Taylor-b", "Taylor-c", "Taylor-d", "Ten80 Dance", "Ten80 stance", "Ten80 top stand", "Ten80 top R step", "Ten80 top L step", "Ten80 top freeze", "Ten80 top R cross", "Ten80 pop front", "Ten80 pop down", "Ten80 pop left", "Ten80 pop right", "Ten80 pop L arm", "Ten80 pop stand", "Ten80 pop R arm", "Tennis Ball", "tennisball", "Tera", "tera-a", "tera-b", "tera-c", "tera-d", "Toucan", "toucan-a", "toucan-b", "toucan-c", "Trampoline", "trampoline", "Tree1", "tree1", "Trees", "trees-a", "trees-b", "Trisha", "Trisha-a", "Trisha-b", "Trisha-c", "Trisha-d", "Truck", "Truck-a", "Truck-b", "Truck-c", "Trumpet", "trumpet-a", "trumpet-b", "Unicorn 2", "unicorn 2", "Unicorn Running", "unicorn running-a", "unicorn running-b", "unicorn running-c", "unicorn running-d", "unicorn running-e", "unicorn running-f", "Unicorn", "unicorn", "Wand", "wand", "Wanda", "wanda", "Watermelon", "watermelon-a", "watermelon-b", "watermelon-c", "Winter Hat", "Winter Hat", "Witch", "witch-a", "witch-b", "witch-c", "witch-d", "Wizard Girl", "wizard girl", "Wizard Hat", "Wizard Hat", "Wizard-toad", "wizard-toad-a", "wizard-toad-b", "Wizard", "wizard-a", "wizard-b", "wizard-c", "Zebra", "zebra-a", "zebra-b" + ] + + backdrop_keywords = [ + "Arctic", "Baseball 1", "Baseball 2", "Basketball 1", "Basketball 2", "Beach Malibu", "Beach Rio", "Bedroom 1", "Bedroom 2", "Bedroom 3", "Bench With View", "Blue Sky 2", "Blue Sky", "Boardwalk", "Canyon", "Castle 1", "Castle 2", "Castle 3", "Castle 4", "Chalkboard", "Circles", "City With Water", "Colorful City", "Concert", "Desert", "Farm", "Field At Mit", "Flowers", "Forest", "Galaxy", "Garden-rock", "Greek Theater", "Hall", "Hay Field", "Hearts", "Hill", "Jungle", "Jurassic", "Light", "Metro", "Moon", "Mountain", "Mural", "Nebula", "Neon Tunnel", "Night City With Street", "Night City", "Party", "Pathway", "Playground", "Playing Field", "Pool", "Rays", "Refrigerator", "Room 1", "Room 2", "Savanna", "School", "Slopes", "Soccer 2", "Soccer", "Space City 1", "Space City 2", "Space", "Spaceship", "Spotlight", "Stars", "Stripes", "Theater 2", "Theater", "Tree", "Underwater 1", "Underwater 2", "Urban", "Wall 1", "Wall 2", "Water And Rocks", "Wetland", "Winter", "Witch House", "Woods And Bench", "Woods", "Xy-grid-20px", "Xy-grid-30px", "Xy-grid" + ] + + sound_keywords = [ + "pop","Basketball Bounce","dance celebrate","dance magic","Chomp","Boing","Pop","Bite","basketball bounce","owl","xylo1","bell toll","Goal Cheer","Referee Whistle","Birthday","dance chill out","dance around","meow2","Meow","snort","Chirp","clown honk","car vroom","Magic Spell","collect","dance funky","bite","dog1","bark","bird","Drum Bass1","Drum Bass2","Drum Bass3","High Tom","Low Tom","crash cymbal","splash cymbal","bell cymbal","roll cymbal","hihat cymbal","tap snare","flam snare","sidestick snare","High Conga","Low Conga","Muted Conga","Tap Conga","Hi Na Tabla","Hi Tun Tabla","Lo Geh Tabla","Lo Gliss Tabla","duck","bubbles","ocean wave","water drop","car horn","wolf howl","space ripple","horse gallop","Water Drop","cheer","fairydust","C Elec Guitar","D Elec Guitar","E Elec Guitar","F Elec Guitar","G Elec Guitar","A Elec Guitar","B Elec Guitar","C2 Elec Guitar","C Guitar","D Guitar","E Guitar","F Guitar","G Guitar","A Guitar","B Guitar","C2 guitar","horse","C Elec Piano","D Elec Piano","E Elec Piano","F Elec Piano","G Elec Piano","A Elec Piano","B Elec Piano","C2 Elec Piano","grunt","Hand Clap","Bass Beatbox","Clap Beatbox","Hi Beatbox","Scratch Beatbox","Snare Beatbox","Snare Beatbox2","Wah Beatbox","Crash Beatbox","Wub Beatbox","glug","Chee Chee","splash","boing","Bird","magic spell","dog2","scratch beatbox","snare beatbox2","wah beatbox","bass beatbox","referee whistle","computer beeps1","computer beeps2","computer beep","buzz whir","laser1","laser2","rooster","C Sax","D Sax","E Sax","F Sax","G Sax","A Sax","B Sax","C2 Sax","Water drop","chomp","rattle","Drive Around","Scratchy Beat","Drum Jam","Cymbal Echo","Drum Satellite","Kick Back","Drum Funky","Footsteps","toy honk","C Trumpet","D Trumpet","E Trumpet","F Trumpet","G Trumpet","A Trumpet","B Trumpet","C2 Trumpet","croak" + ] + + # Build canonical maps including sounds + canonical_map, canonical_list = build_keyword_maps(sprite_keywords, backdrop_keywords, sound_keywords) + + # exact canonical match check + ctext = canonicalize(text) + if ctext in canonical_map: + kw, cat = choose_preferred(canonical_map[ctext]) + print(kw, cat) + return (kw, cat) + + # valid dictionary word -> treat as variable (but show suggestions) + valid = is_valid_word(text) + if valid is True: + check_and_print_nearest_matches(text, canonical_map, canonical_list) + return (text, 'variable') + + # fuzzy matching + match = get_best_match(text, canonical_map, canonical_list) + if match: + return match # (keyword, category) + + # default -> variable (with suggestions) + check_and_print_nearest_matches(text, canonical_map, canonical_list) + return (text, 'variable') + ################################################################################################################################################################# #--------------------------------------------------[Example use of the function here]---------------------------------------------------------------------------- ################################################################################################################################################################# -initial_opcode_counts = [ - {"opcode": "event_whenbroadcastreceived","count": 1}, - {"opcode": "control_forever","count": 1}, - {"opcode": "control_if","count": 1}, - {"opcode": "sensing_istouching","count": 1}, - {"opcode": "looks_hide","count": 1}, - {"opcode": "looks_switchbackdropto","count": 1}, - {"opcode": "event_broadcast","count": 1}, - {"opcode": "control_stop","count": 1} - ] -pseudo_code=""" -when I receive [Game Start v] - forever - if then - hide - switch backdrop to [loose v] - broadcast [Game Over v] - stop all - end -end -""" +# initial_opcode_counts = [ +# {"opcode": "event_whenbroadcastreceived","count": 1}, +# {"opcode": "control_forever","count": 1}, +# {"opcode": "control_if","count": 1}, +# {"opcode": "sensing_istouching","count": 1}, +# {"opcode": "looks_hide","count": 1}, +# {"opcode": "looks_switchbackdropto","count": 1}, +# {"opcode": "event_broadcast","count": 1}, +# {"opcode": "control_stop","count": 1} +# ] +# pseudo_code=""" +# when [space v] key pressed +# start sound (Meow) +# next costume +# repeat (10) +# change y by (10) +# wait (0.0001) seconds +# change y by (-10) +# wait (0.0001) seconds +# end +# next costume +# """ # print(pseudo_code) # opcode_counts_result = analyze_opcode_counts(pseudo_code) # generated_output_json, initial_opcode_occurrences = generate_blocks_from_opcodes(opcode_counts_result, all_block_definitions) # all_generated_blocks = generate_plan(generated_output_json, initial_opcode_occurrences, pseudo_code) # processed_blocks= process_scratch_blocks(all_generated_blocks, generated_output_json) # renamed_blocks, renamed_counts = rename_blocks(processed_blocks, initial_opcode_occurrences) -# print(opcode_counts_result) +# print(generated_output_json) # print("--------------\n\n") # print(processed_blocks) # print("--------------\n\n") -# print(initial_opcode_occurrences) +# print(all_generated_blocks) # print("--------------\n\n") -# print(renamed_blocks) \ No newline at end of file +# print(opcode_counts_result) \ No newline at end of file