diff --git a/utils/block_relation_builder.py b/utils/block_relation_builder.py index fdde6b22fadea6a9be72148ff89b13583a3b401b..6e6767560e0bf4a2a890a76ffff7ff54e2947fb5 100644 --- 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: ] @@ -1203,67 +1213,51 @@ def parse_reporter_or_value(text, parent_key, pick_key_func, all_generated_block return {"kind": "block", "block": block_id} # [ORDER NO: ] - - 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 '(' + # (() of ()) (operator_mathop) - handle variable for function type + 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) @@ -1272,105 +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: ] - # (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) + # 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 + + # # 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 + + # 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) - 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 - - # 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 - - block_id = _register_block("sensing_of", parent_key, False, pick_key_func, all_generated_blocks, inputs=inputs, fields=fields) + 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] - # 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(")"): @@ -1384,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 @@ -1401,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 == "(": @@ -1415,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) @@ -1427,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": @@ -1549,91 +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*""", 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: 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() + # 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() + # # Remove trailing " v" if present (Scratch dropdown) + # if color_str.endswith(" v"): + # color_str = color_str[:-2].strip() - # Normalize to hex + # # 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: + color_str = m.group(1).strip() + if color_str.endswith(" v"): color_str = color_str[:-2].strip() 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)}") - + 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}} - print(" : ", inputs) - block_id = _register_block( - "sensing_touchingcolor", parent_key, True, - pick_key_func, all_generated_blocks, inputs=inputs - ) + 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}|[a-zA-Z]+(?: v)?)[>\]\)]? is touching [<\[\(]?(#?[0-9A-Fa-f]{6}|[a-zA-Z]+(?: v)?)[>\]\)]?\??", 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: 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} + 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*)?\[([^\]]+?)\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) + # 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,) 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?": @@ -1714,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" @@ -1740,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 @@ -1786,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}") ################################################################################################################################################################# @@ -1983,17 +2099,34 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): # are passed the *newly created block's ID* as the parent_key for nested inputs) # Numeric inputs (e.g., move (10) steps, wait (1) seconds) if opcode == "motion_movesteps": + #m = re.search(r"move\s*(?:\(\s*)?(-?\d+(?:\.\d+)?)(?:\s*\))?\s*steps", stmt_for_parse, re.IGNORECASE) m = re.search(r"move\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*steps", stmt_for_parse, re.IGNORECASE) + #if m: info["inputs"]["STEPS"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} print("motion_movesteps : ",m.group(1).strip()) if m: info["inputs"]["STEPS"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) + # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["STEPS"]) + # print(f"block_id {opcode} : {block_id}") elif opcode == "motion_turnright" or opcode == "motion_turnleft": + # m = re.search(r"turn\s*(?:right|left)?\s*\(.*?\)\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*degrees", stmt_for_parse, re.IGNORECASE) + #m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(-?\d+(?:\.\d+)?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE) m = re.search(r"turn\s*(?:right|left)?\s*(?:\(\s*)?(.+?)(?:\s*\))?\s*degrees", stmt_for_parse, re.IGNORECASE) + # if m: info["inputs"]["DEGREES"] = {"kind": "value", "value": float(m.group(1)) if '.' in m.group(1) else int(m.group(1))} if m: info["inputs"]["DEGREES"] = parse_reporter_or_value(m.group(1).strip(), key, pick_key, all_generated_blocks) + # block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, info["inputs"]["DEGREES"]) + # print(f"block_id {opcode} : {block_id}") elif opcode == "motion_gotoxy": + # m_x = re.search(r"x:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE) + # m_y = re.search(r"y:\s*(\(.+?\)|[^\s)]+)", stmt_for_parse, re.IGNORECASE) m_x = re.search(r"x:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*y:|$)", stmt_for_parse, re.IGNORECASE) m_y = re.search(r"y:\s*(?:\(\s*)?(.+?)(?:\s*\))?(?=\s*$)", stmt_for_parse, re.IGNORECASE) if m_x: info["inputs"]["X"] = parse_reporter_or_value( m_x.group(1).strip("() "), key, pick_key, all_generated_blocks) if m_y: info["inputs"]["Y"] = parse_reporter_or_value( m_y.group(1).strip("() "), key, pick_key, all_generated_blocks) + # print("motion_gotoxy m_x", m_x.group(1) if m_x else None) + # print("motion_gotoxy m_y", m_y.group(1) if m_y else None) + # if m_x: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, ) + # print(f"block_id {opcode} : {block_id}") + # if m_y: block_id = _register_block(opcode, key, False, pick_key, all_generated_blocks, inputs=info["inputs"]["Y"]) + # print(f"block_id {opcode} : {block_id}") elif opcode == "motion_glidesecstoxy": stmt = _auto_balance(stmt_for_parse) # m_secs = re.search(r"glide\s*\(\s*(-?\d+(\.\d+)?)\s*\)\s*secs", stmt_for_parse, re.IGNORECASE) @@ -2134,86 +2267,237 @@ 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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))", 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) 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]}) + 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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",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: - 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) + 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} - menu_block_id = _register_block("motion_glideto_menu", key, True, pick_key, all_generated_blocks, - fields={"TO": [option_val, None]}) + # 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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",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) 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]}) + 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*(?:\[\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 == "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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",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) 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]}) + 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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",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) 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]}) + 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*([^\]]+?)\s*v\]|\(?\s*([^)]+?)\s*\)?|([A-Za-z][^\n]+))",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) 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]}) + 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"]: @@ -2221,20 +2505,35 @@ def generate_plan(generated_input, opcode_keys, pseudo_code): if m: 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*(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) + 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() - # Normalize casing to match Scratch’s expected field values - if option == "all": - stop_val = "all" - elif option == "this script": - stop_val = "this script" + 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: - stop_val = "other scripts in sprite" - + 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] @@ -2250,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) @@ -2264,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] @@ -2322,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": [], @@ -2341,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": @@ -2350,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": [], @@ -2366,7 +2795,6 @@ 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 @@ -2423,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 = { @@ -2457,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")], @@ -2513,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 @@ -2756,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]------------------------------- ################################################################################################################################################################# @@ -3015,11 +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"forever", "control_forever"), # (r"if <.+?> then", "control_if"), (r"if <.+?> then(?:(?!else).|\n)+?end", "control_if"), + (r"forever", "control_forever"), (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"), @@ -3048,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) @@ -3108,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"), @@ -3121,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"), ] @@ -3283,6 +3792,190 @@ 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]---------------------------------------------------------------------------- #################################################################################################################################################################