python macro 换行问题,看完涨知识了!

本文介绍了一种解决使用macro时源代码自动换行的问题。通过调整macro的实现方式,可以有效地避免在源代码中出现不必要的换行现象。

问题:

使用macro时,源代码会自动换行

截图如下:

原因如下:

解决办法:

{% macro static(filename) -%}
    {{ url_for("static",filename=filename) }}
{%- endmacro %}

保存运行代码。再查看源代码,换行就消失不见了。

结果如下:

 

 

 

 

你看看人家import json import os import re import logging import sys from pathlib import Path from shutil import copy2 from datetime import datetime from utils import resource_path # ------------------------------- # 日志配置 # ------------------------------- PROJECT_ROOT = Path(__file__).parent.parent.resolve() LOG_DIR = PROJECT_ROOT / "output" / "log" LOG_DIR.mkdir(parents=True, exist_ok=True) LOG_FILE = LOG_DIR / f"range_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" class CLMRangeSynchronizer: def __init__(self, c_file_path=None, dry_run=False,config_path="config/config.json"): self.logger = logging.getLogger(__name__) # === Step 1: 使用 resource_path 解析所有路径 === self.config_file_path = resource_path(config_path) logging.info(f"配置文件: {self.config_file_path}") if not os.path.exists(self.config_file_path): raise FileNotFoundError(f"配置文件不存在: {self.config_file_path}") try: with open(self.config_file_path, 'r', encoding='utf-8') as f: self.config = json.load(f) print(f"配置文件已加载: {self.config_file_path}") except json.JSONDecodeError as e: raise ValueError(f"配置文件格式错误,JSON 解析失败: {self.config_file_path}") from e except Exception as e: raise RuntimeError(f"读取配置文件时发生未知错误: {e}") from e self.dry_run = dry_run if c_file_path is None: # 使用内置默认 C 文件(被打包进 exe 的) if "target_c_file" not in self.config: raise KeyError(" config 文件缺少 'target_c_file' 字段") internal_c_path = self.config["target_c_file"] logging.info(f"使用内置 C 文件: {internal_c_path}") self.c_file_path = Path(resource_path(internal_c_path)) self._is_internal_c_file = True else: # 用户传入自定义路径 self.c_file_path = Path(c_file_path) self._is_internal_c_file = False if not self.c_file_path.exists(): raise FileNotFoundError(f"找不到 C 源文件: {self.c_file_path}") self.used_ranges = [] self.array_macros = {} # array_name -> [RANGE_xxx, ...] # self.array_macros = { # "channel_ranges_2g_20m": [ # "RANGE_2G_20M_1_11", # "RANGE_2G_20M_6_6" # ], # "channel_ranges_5g_80m": [ # "RANGE_5G_80M_36_48", # "RANGE_5G_80M_149_161" # ] # } self.struct_entries = {} # array_name -> [{"low": int, "high": int}, ...] # self.struct_entries = { # "channel_ranges_2g_20m": [ # {"low": 1, "high": 11}, # {"low": 6, "high": 6} # ], # "channel_ranges_5g_80m": [ # {"low": 36, "high": 48}, # {"low": 149, "high": 161} # ] # } self.enum_to_index = {} # RANGE_xxx -> index (from enum) if "STR_CHANNEL_RANGE" not in self.config: raise KeyError(" config 文件缺少 'STR_CHANNEL_RANGE' 字段") self.start_marker = self.config["STR_CHANNEL_RANGE"] if "END_CHANNEL_RANGE" not in self.config: raise KeyError(" config 文件缺少 'END_CHANNEL_RANGE' 字段") self.end_marker = self.config["END_CHANNEL_RANGE"] def offset_to_lineno(self, content: str, offset: int) -> int: """将字符偏移量转换为行号(从1开始)""" return content.count('\n', 0, offset) + 1 def load_config(self): """加载并解析 config.json""" with open(self.config_file_path, 'r', encoding='utf-8') as f: data = json.load(f) if "used_ranges" not in data: raise KeyError(" config 文件缺少 'used_ranges' 字段") valid_ranges = [] for item in data["used_ranges"]: if isinstance(item, str) and re.match(r'^RANGE_[\w\d_]+_\d+_\d+$', item): valid_ranges.append(item) else: self.logger.warning(f"跳过无效项: {item}") self.used_ranges = sorted(set(valid_ranges)) self.logger.info(f"已从 {self.config_file_path} 加载 {len(self.used_ranges)} 个有效 RANGE ") def parse_c_arrays(self): """解析 C 文件中的 channel_ranges_xxx[] 数组 和 enum range_xxx""" content = self.c_file_path.read_text(encoding='utf-8') start_idx = content.find(self.start_marker) end_idx = content.find(self.end_marker) if start_idx == -1 or end_idx == -1: raise ValueError("未找到 CHANNEL RANGES 注释锚点") block = content[start_idx:end_idx] start_line = self.offset_to_lineno(content, start_idx) end_line = self.offset_to_lineno(content, end_idx) self.logger.info( f"找到标记范围:{self.start_marker} → 第 {start_line} 行, {self.end_marker} → 第 {end_line} 行") # === 1. 解析数组:static const struct clm_channel_range xxx[] = { ... }; array_pattern = re.compile( r'static\s+const\s+struct\s+clm_channel_range\s+(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{(.*?)\}\s*;', re.DOTALL | re.MULTILINE ) for array_name, body in array_pattern.findall(block): entries = [] for low, high in re.findall(r'\{\s*(\d+)\s*,\s*(\d+)\s*\}', body): entries.append({"low": int(low), "high": int(high)}) self.struct_entries[array_name] = entries self.array_macros[array_name] = [] # 先留空,后续从 enum 填充 self.logger.info(f" 解析数组 {array_name}: {len(entries)} 个范围项") # === 2. 解析枚举:enum range_xxx { RANGE_A = 0, ... } enum_pattern = re.compile(r'enum\s+range_([a-z\d_]+)\s*\{([^}]*)\}', re.DOTALL | re.IGNORECASE) if enum_pattern.search(block): self.logger.info("block 中存在匹配的 enum range") else: self.logger.info("未找到匹配的 enum range") #self.logger.info(f"block 片段预览:\n{block[:2500]}") for match in enum_pattern.finditer(block): suffix = match.group(1) # 如 '2g_40m' enum_body = match.group(2) array_name = f"channel_ranges_{suffix}" #self.logger.info(f" 解析 {enum_body} {suffix}") if array_name not in self.struct_entries: self.logger.warning(f" 找到 enum {match.group(0)[:30]}... 但无对应数组") continue # 提取 RANGE_xxx = N for macro, idx_str in re.findall(r'(RANGE_[\w\d_]+)\s*=\s*(\d+)', enum_body): idx = int(idx_str) if idx >= len(self.struct_entries[array_name]): self.logger.warning(f" 索引越界: {macro} = {idx} > 数组长度 {len(self.struct_entries[array_name])}") continue self.enum_to_index[macro] = idx if macro not in self.array_macros[array_name]: self.array_macros[array_name].append(macro) #self.logger.info(f" 关联 {macro} → {array_name}[{idx}]") self.logger.info(f" 总共建立 {len(self.enum_to_index)} 个与数组项的映射关系") def get_array_name_for_range(self, range_macro): """根据 RANGE 推断应属于哪个数组""" match = re.match(r'RANGE_([0-9]+[A-Za-z])_([0-9]+)M_', range_macro, re.IGNORECASE) if not match: self.logger.warning(f"无法推断数组名: {range_macro}") return None band = match.group(1).lower() # '2g' bw = match.group(2) # '20' return f"channel_ranges_{band}_{bw}m" def extract_channels_from_macro(self, macro): """ 从字符串中提取信道范围。 Args: macro (str): 格式如 RANGE_2G_20M_1_11 Returns: tuple: (low, high) 或 (None, None) """ match = re.search(r'_(\d+)_(\d+)$', macro) if match: low = int(match.group(1)) high = int(match.group(2)) return low, high self.logger.warning(f"格式错误,无法提取信道: {macro}") return None, None def validate_and_repair(self): """确保每个 used_range 都在正确的数组中""" modified = False changes = [] for range_macro in self.used_ranges: array_name = self.get_array_name_for_range(range_macro) if not array_name: self.logger.warning(f"无法识别数组类型,跳过: {range_macro}") continue # --- 检查是否已在 enum 映射中 --- existing_idx = None for macro, idx in self.enum_to_index.items(): if macro == range_macro and self.get_array_name_for_range(macro) == array_name: existing_idx = idx break if existing_idx is None: # 新,需分配新索引 next_idx = len(self.struct_entries[array_name]) low, high = self.extract_channels_from_macro(range_macro) if low is not None and high is not None: self.struct_entries[array_name].append({"low": low, "high": high}) #self.logger.info(f"数组{array_name}: {self.struct_entries[array_name]}") self.array_macros[array_name].append(range_macro) #self.logger.info(f"枚举: {array_name}: {self.array_macros[array_name]}") self.enum_to_index[range_macro] = next_idx changes.append(f"扩展枚举: {range_macro} → {{{low}, {high}}} (index={next_idx})") #self.logger.info(f"扩展枚举: {range_macro} → {{{low}, {high}}} (index={next_idx})") modified = True else: self.logger.warning(f"无法解析信道范围: {range_macro}") if modified and not self.dry_run: self._write_back_in_block() self.logger.info("C 文件已更新") elif modified and self.dry_run: self.logger.info("DRY-RUN MODE: 有变更但不会写入文件") else: self.logger.info(" 所有 RANGE 已存在,无需修改") if modified: self.logger.info(f" 共需添加 {len(changes)} 项:\n" + "\n".join(f" → {ch}" for ch in changes)) return modified def _infer_array_from_enum(self, enum_decl): """从 enum 声明推断对应的数组名""" match = re.search(r'enum\s+range_([a-z\d_]+)', enum_decl) if match: return f"channel_ranges_{match.group(1)}" return None def _format_array_body(self, structs, indent=" "): """格式化结构体数组内容,每行最多4个,数字右对齐""" items = [f"{{ {s['low']:>2d}, {s['high']:>2d} }}" for s in structs] lines = [] for i in range(0, len(items), 4): group = items[i:i + 4] lines.append(indent + ", ".join(group)) return "\n".join(lines) def _write_back_in_block(self): """安全地一次性更新 C 文件中的数组和枚举定义""" if self.dry_run: self.logger.info("DRY-RUN: 跳过写入文件") return try: content = self.c_file_path.read_text(encoding='utf-8') start_idx = content.find(self.start_marker) end_idx = content.find(self.end_marker) + len(self.end_marker) if start_idx == -1 or end_idx == -1: raise ValueError("未找到 CHANNEL RANGES 标记块") header = content[:start_idx] footer = content[end_idx:] block = content[start_idx:end_idx] replacements = [] # (start, end, replacement) # === 工具函数:移除注释避免误匹配 === def remove_comments(text): text = re.sub(r'//.*$', '', text, flags=re.MULTILINE) text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) return text # === 1. 更新 channel_ranges_xxx[] 数组:只在末尾添加新项 === array_pattern = re.compile( r'(\b(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{)(.*?)(\}\s*;\s*)', re.DOTALL | re.MULTILINE ) matches = list(array_pattern.finditer(block)) self.logger.info(f" 找到 {len(matches)} 个 channel_ranges 数组") for i, match in enumerate(matches): self.logger.info(f" 匹配 {i + 1}: 数组名={match.group(2)}, 起始位置={match.start()}") for match in array_pattern.finditer(block): array_name = match.group(2) if array_name not in self.struct_entries: self.logger.warning(f" 未找到 {array_name} 数组") continue structs = self.struct_entries[array_name] body_content = match.group(3) # 直接就是 {} 中间的原始内容(含空格换行) original_end = match.end() # 不变,仍是整个声明结束位置 self.logger.info(f" 数组 {array_name} 内容: {body_content}") # 提取第一行缩进(用于新行) first_line = body_content.split('\n')[0] if body_content.strip() else "" indent_match = re.match(r'^(\s*)', first_line) indent = indent_match.group(1) if indent_match else " " # === 智能插入 {low, high}, 条目(支持同行追加 & 对齐)=== existing_items = [] item_pattern = r'\{\s*(\d+)\s*,\s*(\d+)\s*\}' for m in re.finditer(item_pattern, body_content): low, high = int(m.group(1)), int(m.group(2)) existing_items.append((low, high)) all_matches = list(re.finditer(item_pattern, body_content)) if not all_matches: insert_pos_in_body = len(body_content) else: last_match = all_matches[-1] close_brace_pos = body_content.find('}', last_match.start()) insert_pos_in_body = close_brace_pos + 2 if close_brace_pos != -1 else last_match.end() inserted_count = len(existing_items) if inserted_count >= len(structs): continue new_item = structs[inserted_count] low, high = new_item['low'], new_item['high'] if (low, high) in existing_items: self.logger.warning(f"已存在 {low}, {high} 项,跳过") continue # 分析最后一行用于格式继承 lines = [line for line in body_content.split('\n') if line.strip()] last_line = lines[-1] if lines else "" # 推断缩进和对齐位置 indent_match = re.match(r'^(\s*)', last_line) if last_line else None line_indent = (indent_match.group(1) if indent_match else "") if last_line else " " expanded_last = last_line.expandtabs(4) if last_line else "" first_struct_match = re.search(r'\{', remove_comments(last_line)) if last_line else None if first_struct_match: raw_before = last_line[:first_struct_match.start()] expanded_before = raw_before.expandtabs(4) target_struct_start_col = len(expanded_before) else: target_struct_start_col = len(line_indent.replace('\t', ' ')) # 计算当前行已有多少个结构体 clean_last = remove_comments(last_line) if last_line else "" visible_structs = len(re.findall(r'\{\s*\d+\s*,\s*\d+\s*\}', clean_last)) MAX_PER_LINE = 8 formatted_item = f"{{ {low:>2d}, {high:>2d} }}" # 决定插入方式 if visible_structs < MAX_PER_LINE and last_line.strip(): # 同行追加 insertion = f" {formatted_item}," else: # 换行对齐 raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces = max(0, target_struct_start_col - raw_indent_len) padding = ' ' * leading_spaces insertion = f"\n{line_indent}{padding}{formatted_item}," # 记录插入点 insert_offset_in_block = match.start(3) + insert_pos_in_body replacements.append((insert_offset_in_block, insert_offset_in_block, insertion)) self.logger.info(f"插入: {insertion.strip()} → 偏移 {insert_offset_in_block}") range_macro = f"RANGE_{array_name.upper().replace('CHANNEL_RANGES_', '').replace('_', '_')}_{low}_{high}" self.logger.info(f"扩展数组: {range_macro} → {{{low}, {high}}} (index={inserted_count})") # === 2. 更新 enum range_xxx:精确继承上一行名左对齐与 '=' 对齐 === enum_pattern = re.compile(r'(enum\s+range_[\w\d_]+\s*\{)([^}]*)\}\s*;', re.DOTALL) for match in enum_pattern.finditer(block): enum_name_match = re.search(r'range_([a-zA-Z0-9_]+)', match.group(0)) if not enum_name_match: continue inferred_array = f"channel_ranges_{enum_name_match.group(1)}" if inferred_array not in self.array_macros: continue macro_list = self.array_macros[inferred_array] enum_body = match.group(2) # 解析已有及其值 existing_macros = dict(re.findall(r'(RANGE_[\w\d_]+)\s*=\s*(\d+)', remove_comments(enum_body))) next_id = len(existing_macros) if next_id >= len(macro_list): continue new_macro = macro_list[next_id] # 获取非空行 lines = [line for line in enum_body.split('\n') if line.strip()] last_line = lines[-1] if lines else "" if not last_line.strip(): # fallback 缩进 line_indent = " " target_macro_start_col = 4 target_eq_col = 32 else: indent_match = re.match(r'^(\s*)', last_line) line_indent = indent_match.group(1) if indent_match else " " # 展开 tab(统一按 4 空格处理) expanded_last = last_line.expandtabs(4) # 提取第一个 RANGE_xxx 名 first_macro_match = re.search(r'RANGE_[\w\d_]+', remove_comments(last_line)) if not first_macro_match: target_macro_start_col = len(line_indent) target_eq_col = 32 else: macro_text = first_macro_match.group(0) macro_start = first_macro_match.start() # 计算视觉起始列(基于展开后的字符串) raw_before = last_line[:macro_start] expanded_before = raw_before.expandtabs(4) target_macro_start_col = len(expanded_before) # 找第一个 "=" 的视觉列 eq_match = re.search(r'=\s*\d+', last_line[macro_start:]) if eq_match: eq_abs_start = macro_start + eq_match.start() raw_eq_part = last_line[:eq_abs_start] expanded_eq_part = raw_eq_part.expandtabs(4) target_eq_col = len(expanded_eq_part) else: # fallback target_eq_col = target_macro_start_col + len(macro_text) + 8 # 现在我们知道: # - 名应该从第 target_macro_start_col 列开始(视觉) # - `=` 应该出现在 target_eq_col 列 # 计算当前名需要多少前置空格才能对齐 current_visual_len = len(new_macro.replace('\t', ' ')) padding_to_eq = max(1, target_eq_col - target_macro_start_col - current_visual_len) full_padding = ' ' * padding_to_eq formatted_new = f"{new_macro}{full_padding}= {next_id}" # 判断是否同行追加(最多 4 个) clean_last = remove_comments(last_line) visible_macros = len(re.findall(r'RANGE_[\w\d_]+', clean_last)) if visible_macros < 4 and last_line.strip(): # 同行追加:前面加两个空格分隔 separator = " " updated_content = last_line + separator + formatted_new + "," new_body = enum_body.rsplit(last_line, 1)[0] + updated_content else: # 换行:使用原始 indent 开头,然后补足到 target_macro_start_col raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces_needed = max(0, target_macro_start_col - raw_indent_len) prefix_padding = ' ' * leading_spaces_needed new_line = f"{line_indent}{prefix_padding}{formatted_new}," trailing = enum_body.rstrip() maybe_comma = "," if not trailing.endswith(',') else "" new_body = f"{trailing}{maybe_comma}\n{new_line}" # 重建 enum new_enum = f"{match.group(1)}{new_body}\n}};" replacements.append((match.start(), match.end(), new_enum)) self.logger.info(f"扩展枚举: {new_macro} = {next_id}") # === 应用替换:倒序防止 offset 错乱 === replacements.sort(key=lambda x: x[0], reverse=True) result_block = block for start, end, r in replacements: result_block = result_block[:start] + r + result_block[end:] # 写回前备份 backup_path = self.c_file_path.with_suffix('.c.bak') copy2(self.c_file_path, backup_path) self.logger.info(f"已备份 → {backup_path}") # 写入新内容 self.c_file_path.write_text(header + result_block + footer, encoding='utf-8') self.logger.info(f" 成功保存修改: {self.c_file_path}") except Exception as e: self.logger.error(f"写回文件失败: {e}", exc_info=True) raise def run(self): self.logger.info("开始同步 CLM RANGE 定义...") try: self.load_config() self.parse_c_arrays() was_modified = self.validate_and_repair() if was_modified: if self.dry_run: self.logger.info(" 预览模式:检测到变更,但不会写入文件") else: self.logger.info(" 同步完成:已成功更新 C 文件") else: self.logger.info(" 所有 RANGE 已存在,无需修改") return was_modified except Exception as e: self.logger.error(f" 同步失败: {e}", exc_info=True) raise def main(): logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s', handlers=[ logging.FileHandler(LOG_FILE, encoding='utf-8'), logging.StreamHandler(sys.stdout) ], force=True ) logger = logging.getLogger(__name__) # 固定配置 c_file_path = "input/wlc_clm_data_6726b0.c" dry_run = False log_level = "INFO" config_path = "config/config.json" logging.getLogger().setLevel(log_level) print(f" 开始同步 RANGE 定义...") print(f" C 源文件: {c_file_path}") if dry_run: print(" 启用 dry-run 模式:仅预览变更,不修改文件") try: sync = CLMRangeSynchronizer( c_file_path=None, dry_run=dry_run, config_path=config_path, ) sync.run() print(" 同步完成!") print(f" 详细日志已保存至: {LOG_FILE}") except FileNotFoundError as e: logger.error(f"文件未找到: {e}") print(" 请检查文件路径是否正确。") sys.exit(1) except PermissionError as e: logger.error(f"权限错误: {e}") print(" 无法读取或写入文件,请检查权限。") sys.exit(1) except Exception as e: logger.error(f"程序异常退出: {e}", exc_info=True) sys.exit(1) if __name__ == '__main__': main() 这个就能写入读取,参考这个在我现在的代码上更改# power/power_sync.py import json import os import re import logging import sys from pathlib import Path from shutil import copy2 from datetime import datetime from utils import resource_path from typing import Dict, List, Tuple, Any # ------------------------------- # 日志配置 # ------------------------------- PROJECT_ROOT = Path(__file__).parent.parent.resolve() LOG_DIR = PROJECT_ROOT / "output" / "log" LOG_DIR.mkdir(parents=True, exist_ok=True) LOG_FILE = LOG_DIR / f"power_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" class PowerTableSynchronizer: def __init__(self, c_file_path=None, dry_run=False, config_path="config/config.json"): self.logger = logging.getLogger(__name__) # === Step 1: 使用 resource_path 解析所有路径 === self.config_file_path = resource_path(config_path) logging.info(f"配置文件: {self.config_file_path}") if not os.path.exists(self.config_file_path): raise FileNotFoundError(f"配置文件不存在: {self.config_file_path}") try: with open(self.config_file_path, 'r', encoding='utf-8') as f: self.config = json.load(f) print(f"配置文件已加载: {self.config_file_path}") except json.JSONDecodeError as e: raise ValueError(f"配置文件格式错误,JSON 解析失败: {self.config_file_path}") from e except Exception as e: raise RuntimeError(f"读取配置文件时发生未知错误: {e}") from e self.dry_run = dry_run # === Step 2: 目标 C 文件处理 === if c_file_path is None: if "target_c_file" not in self.config: raise KeyError("config 文件缺少 'target_c_file' 字段") internal_c_path = self.config["target_c_file"] logging.info(f"使用内置 C 文件: {internal_c_path}") self.c_file_path =resource_path(internal_c_path) self._is_internal_c_file = True else: self.c_file_path = Path(c_file_path) self._is_internal_c_file = False if not self.c_file_path.exists(): raise FileNotFoundError(f"找不到 C 源文件: {self.c_file_path}") # === Step 3: 初始化数据容器 === self.locale_enums = {} # enum_name -> {"macros": [macro], "values": {macro: idx}} self.power_tables = {} # table_name -> [lines] self.used_locales = [] # ["DEFAULT", "CUSTOM1"] # === Step 4: 加载锚点标记 === for marker_key in ["STR_POWER_LOCALE_ENUM", "END_POWER_LOCALE_ENUM", "STR_POWER_TABLE", "END_POWER_TABLE"]: if marker_key not in self.config: raise KeyError(f"config 文件缺少 '{marker_key}' 字段") self.start_enum_marker = self.config["STR_POWER_LOCALE_ENUM"] self.end_enum_marker = self.config["END_POWER_LOCALE_ENUM"] self.start_table_marker = self.config["STR_POWER_TABLE"] self.end_table_marker = self.config["END_POWER_TABLE"] def offset_to_lineno(self, content: str, offset: int) -> int: """将字符偏移量转换为行号(从1开始)""" return content.count('\n', 0, offset) + 1 def load_used_locales(self): """从 config.json 加载 used_locales 列表,并保持原有顺序""" if "used_locales" not in self.config: raise KeyError("config 文件缺少 'used_locales' 字段") valid_locales = [] seen = set() for item in self.config["used_locales"]: if isinstance(item, str) and re.match(r'^[A-Z0-9_]+$', item): if item not in seen: valid_locales.append(item) seen.add(item) else: self.logger.warning(f"跳过无效 Locale 名称: {item}") self.used_locales = valid_locales self.logger.info(f"已从 config 加载 {len(self.used_locales)} 个有效 Locale(保持顺序): {self.used_locales}") def parse_c_power_definitions(self): """解析 C 文件中的 enum locale_xxx_idx 和 static const unsigned char locales_xxx[]""" content = self.c_file_path.read_text(encoding='utf-8') # --- 解析 ENUM 区域 --- try: enum_start_idx = content.find(self.start_enum_marker) enum_end_idx = content.find(self.end_enum_marker) if enum_start_idx == -1 or enum_end_idx == -1: raise ValueError("未找到 LOCALE ENUM 标记块") enum_block = content[enum_start_idx:enum_end_idx] start_line = self.offset_to_lineno(content, enum_start_idx) end_line = self.offset_to_lineno(content, enum_end_idx) self.logger.info(f"找到 ENUM 标记范围:第 {start_line} 行 → 第 {end_line} 行") enum_pattern = re.compile( r'(enum\s+locale_[\w\d_]+_idx\s*\{)([^}]*)\}\s*;', re.DOTALL | re.IGNORECASE ) for match in enum_pattern.finditer(enum_block): enum_decl = match.group(0) enum_name_match = re.search(r'locale_[\w\d_]+_idx', enum_decl, re.IGNORECASE) if not enum_name_match: continue enum_name = enum_name_match.group(0).lower() body = match.group(2) # 在 parse_c_power_definitions() 中 body_no_comment = re.sub(r'//.*|/\*.*?\*/', '', body, flags=re.DOTALL) # ✅只提取 = 数字 的 valid_assignments = re.findall(r'(LOCALE_[A-Z0-9_]+)\s*=\s*(-?\b\d+\b)', body_no_comment) macro_list = [m[0] for m in valid_assignments] value_map = {m: int(v) for m, v in valid_assignments} self.locale_enums[enum_name] = { "macros": macro_list, "values": value_map, "raw_body": body } self.logger.info(f" 解析枚举 {enum_name}: {len(macro_list)} 个") except Exception as e: self.logger.error(f"解析 ENUM 失败: {e}", exc_info=True) # --- 解析 TABLE 区域 --- try: table_start_idx = content.find(self.start_table_marker) table_end_idx = content.find(self.end_table_marker) if table_start_idx == -1 or table_end_idx == -1: raise ValueError("未找到 POWER TABLE 标记块") table_block = content[table_start_idx:table_end_idx] start_line = self.offset_to_lineno(content, table_start_idx) end_line = self.offset_to_lineno(content, table_end_idx) self.logger.info(f"找到 TABLE 标记范围:第 {start_line} 行 → 第 {end_line} 行") array_pattern = re.compile( r'(static\s+const\s+unsigned\s+char\s+)([\w\d_]+)\s*\[\s*\]\s*=\s*\{(.*?)\}\s*;', re.DOTALL | re.IGNORECASE ) for match in array_pattern.finditer(table_block): table_name = match.group(2) body_content = match.group(3) lines = [line.strip() for line in body_content.split(',') if line.strip()] cleaned_lines = [] for line in lines: clean = re.sub(r'/\*.*?\*/', '', line).strip() if clean and not clean.startswith('//'): cleaned_lines.append(clean) self.power_tables[table_name] = cleaned_lines self.logger.info(f" 解析数组 {table_name}: {len(cleaned_lines)} 行数据") except Exception as e: self.logger.error(f"解析 TABLE 失败: {e}", exc_info=True) def validate_and_repair(self): modified = False changes = [] all_locale_data = self.extract_all_raw_locale_data() missing_locales = [] # === 提前定义映射关系:避免重复逻辑 === locale_targets = [ { "idx": 0, "enum_name": "locale_2g_idx", "table_name": "locales_2g_base", "suffix": "2G_IDX" }, { "idx": 1, "enum_name": "locale_2g_ht_idx", "table_name": "locales_2g_ht", "suffix": "2G_HT_IDX" } ] for target in locale_targets: idx = target["idx"] if idx >= len(self.used_locales): continue locale = self.used_locales[idx] enum_name = target["enum_name"] table_name = target["table_name"] suffix = target["suffix"] if locale not in all_locale_data: missing_locales.append(locale) continue data_lines = all_locale_data[locale] # 统一名格式 macro_name = f"LOCALE_{suffix}_{locale}" # --- 处理 ENUM --- if enum_name not in self.locale_enums: self.logger.warning(f"未找到枚举定义: {enum_name}") continue enum_data = self.locale_enums[enum_name] macros = enum_data["macros"] values = enum_data["values"] # 计算正确 index:从 raw_body 中提取最大数字 next_idx = self._get_next_enum_index(enum_name) if macro_name not in macros: macros.append(macro_name) values[macro_name] = next_idx # ✅ 唯一来源 changes.append(f"ENUM + {macro_name} = {next_idx}") modified = True # --- 处理 TABLE --- if table_name not in self.power_tables: self.power_tables[table_name] = [] changes.append(f"TABLE 初始化: {table_name}") current_entries = self.power_tables[table_name] if not any(d == data_lines[0] for d in current_entries): self.power_tables[table_name].extend(data_lines) changes.append(f"TABLE + {len(data_lines)} 行 → {table_name}") modified = True if missing_locales: raise RuntimeError(f"缺失 Locale 数据: {missing_locales}") if changes: self.logger.info(f"共需添加 {len(changes)} 项:\n" + "\n".join(f" → {ch}" for ch in changes)) return modified def _get_next_enum_index(self, enum_name: str) -> int: """从 enum 的原始 body 中提取所有 LOCALE_xxx = N 的 N,返回 max(N)+1""" enum_data = self.locale_enums.get(enum_name) if not enum_data or "raw_body" not in enum_data: return 0 body = enum_data["raw_body"] body_no_comment = re.sub(r'//.*|/\*.*?\*/', '', body, flags=re.DOTALL) # ✅ 只提取 = 后面是整数的情况,自动忽略 = CLM_LOC_NONE / SAME matches = re.findall(r'LOCALE_[A-Z0-9_]+\s*=\s*(-?\b\d+\b)', body_no_comment) if not matches: self.logger.warning(f"枚举 {enum_name} 中未找到任何 'MACRO = 数字' 的定义,将从 0 开始") return 0 try: nums = [int(x) for x in matches] next_idx = max(nums) + 1 self.logger.debug(f"✅ 枚举 {enum_name} 发现数值: {sorted(nums)} → 下一个 index = {next_idx}") return next_idx except Exception as e: self.logger.error(f"解析枚举数值失败: {e}") return 0 def extract_all_raw_locale_data(self) -> Dict[str, List[str]]: """ 从 output/tx_limit_table.c 中提取所有 /* Locale XXX */ 后面的数据块(直到下一个 Locale 或 EOF) 使用逐行解析,避免正则不匹配问题 """ gen_file = PROJECT_ROOT / "output" / "tx_limit_table.c" if not gen_file.exists(): self.logger.error(f"❌ 找不到生成文件: {gen_file}") raise FileNotFoundError(f"请先运行 excel_to_clm.py 生成 tx_limit_table.c: {gen_file}") try: content = gen_file.read_text(encoding='utf-8') except Exception as e: self.logger.error(f"❌ 读取 {gen_file} 失败: {e}") raise self.logger.debug(f"📄 正在解析文件: {gen_file}") self.logger.debug(f"🔍 前 300 字符:\n{content[:300]}") lines = content.splitlines() locale_data = {} current_locale = None current_block = [] for i, line in enumerate(lines): stripped = line.strip() # 检查是否是新的 Locale 标记 match = re.match(r'/\*\s*Locale\s+([A-Za-z0-9_]+)\s*\*/', stripped, re.IGNORECASE) if match: # 保存上一个 block if current_locale: # 清理 block:去空行、去注释、去尾逗号 cleaned = [ re.sub(r'/\*.*?\*/|//.*', '', ln).strip().rstrip(',') for ln in current_block if re.sub(r'/\*.*?\*/|//.*', '', ln).strip() ] locale_data[current_locale] = cleaned self.logger.debug(f"📌 已提取 Locale {current_locale},共 {len(cleaned)} 行") # 开始新 block current_locale = match.group(1) current_block = [] self.logger.debug(f"🆕 发现 Locale: {current_locale}") continue # 收集当前 locale 的内容 if current_locale is not None: current_block.append(line) # 别忘了最后一个 block if current_locale: cleaned = [ re.sub(r'/\*.*?\*/|//.*', '', ln).strip().rstrip(',') for ln in current_block if re.sub(r'/\*.*?\*/|//.*', '', ln).strip() ] locale_data[current_locale] = cleaned self.logger.debug(f" 已提取最后 Locale {current_locale},共 {len(cleaned)} 行") self.logger.info(f" 成功提取 {len(locale_data)} 个 Locale 数据块: {list(locale_data.keys())}") return locale_data def _write_back_in_blocks(self): """将修改后的 enum 和 table 块写回原 C 文件,精准插入,不破坏原始格式""" if self.dry_run: self.logger.info("DRY-RUN: 跳过写入文件") return try: content = self.c_file_path.read_text(encoding='utf-8') backup_path = self.c_file_path.with_suffix(self.c_file_path.suffix + ".bak") copy2(self.c_file_path, backup_path) self.logger.info(f" 已备份原文件 → {backup_path}") replacements = [] # (start_offset, end_offset, replacement) def remove_comments(text): text = re.sub(r'//.*$', '', text, flags=re.MULTILINE) text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) return text # === 1. 更新 ENUM locale_2g_idx 和 locale_2g_ht_idx === # === 1. 更新 ENUM locale_2g_idx 和 locale_2g_ht_idx === for enum_name_key in ["locale_2g_idx", "locale_2g_ht_idx"]: if enum_name_key not in self.locale_enums: continue enum_data = self.locale_enums[enum_name_key] pattern = re.compile( rf'(enum\s+{re.escape(enum_name_key)}\s*\{{)([^}}]*)\}}\s*;', re.DOTALL | re.IGNORECASE ) match = pattern.search(content) if not match: self.logger.warning(f"未找到枚举定义: {enum_name_key}") continue header = match.group(1) body = match.group(2) full_start = match.start() full_end = match.end() # === 获取目标 locale 和名(与 validate_and_repair 完全一致)=== target_locale = self.used_locales[0] if enum_name_key == "locale_2g_idx" else self.used_locales[1] if enum_name_key == "locale_2g_idx": suffix = "2G_IDX" elif enum_name_key == "locale_2g_ht_idx": suffix = "2G_HT_IDX" else: continue macro_name = f"LOCALE_{suffix}_{target_locale}" # 如果 validate_and_repair 已添加,则 values[macro_name] 一定存在 if macro_name not in enum_data["macros"]: continue # 说明没要加,跳过 # 直接使用预设值,不再计算! next_idx = enum_data["values"][macro_name] # 后续格式化插入逻辑不变... lines = [ln for ln in body.split('\n') if ln.strip()] last_line = lines[-1] if lines else "" clean_last = remove_comments(last_line) indent_match = re.match(r'^(\s*)', last_line) line_indent = indent_match.group(1) if indent_match else " " expanded_last = last_line.expandtabs(4) first_macro_match = re.search(r'LOCALE_[A-Z0-9_]+', clean_last) if first_macro_match: raw_before = last_line[:first_macro_match.start()] expanded_before = raw_before.expandtabs(4) target_macro_col = len(expanded_before) else: target_macro_col = len(line_indent.replace('\t', ' ')) eq_match = re.search(r'=\s*\d+', clean_last) if eq_match and first_macro_match: eq_abs_start = first_macro_match.start() + eq_match.start() raw_eq_part = last_line[:eq_abs_start] expanded_eq_part = raw_eq_part.expandtabs(4) target_eq_col = len(expanded_eq_part) else: target_eq_col = target_macro_col + len(macro_name) + 6 current_visual_len = len(macro_name.replace('\t', ' ')) padding_to_eq = max(1, target_eq_col - target_macro_col - current_visual_len) formatted_macro = f"{macro_name}{' ' * padding_to_eq}= {next_idx}" visible_macros = len(re.findall(r'LOCALE_[A-Z0-9_]+', clean_last)) MAX_PER_LINE = 4 if visible_macros < MAX_PER_LINE and last_line.strip(): insertion = f" {formatted_macro}," updated_last = last_line.rstrip() + insertion new_body = body.rsplit(last_line, 1)[0] + updated_last else: raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces = max(0, target_macro_col - raw_indent_len) prefix_padding = ' ' * leading_spaces new_line = f"\n{line_indent}{prefix_padding}{formatted_macro}," trailing = body.rstrip() maybe_comma = "," if not trailing.endswith(',') else "" new_body = f"{trailing}{maybe_comma}{new_line}" new_enum = f"{header}{new_body}\n}};" replacements.append((full_start, full_end, new_enum)) self.logger.debug(f" 插入 ENUM: {macro_name} = {next_idx}") # === 2. 更新 TABLE locales_2g_base 和 locales_2g_ht === for table_name in ["locales_2g_base", "locales_2g_ht"]: if table_name not in self.power_tables: self.logger.info(f"没有需要更新的表: {table_name}") continue target_locale = self.used_locales[0] if table_name == "locales_2g_base" else self.used_locales[1] all_data = self.extract_all_raw_locale_data() if target_locale not in all_data: continue new_lines = all_data[target_locale] current_count = len(self.power_tables[table_name]) data_to_insert = new_lines[current_count:] if not data_to_insert: continue pattern = re.compile( rf'(\b{re.escape(table_name)}\s*\[\s*\]\s*=\s*\{{)(.*?)(\}}\s*;\s*)', re.DOTALL | re.IGNORECASE ) match = pattern.search(content) if not match: self.logger.warning(f"未找到数组: {table_name}") continue header_part = match.group(1) body_content = match.group(2) footer_part = match.group(3) full_start = match.start() full_end = match.end() # 分析最后一行结构体格式 lines = [ln for ln in body_content.split('\n') if ln.strip()] last_line = lines[-1] if lines else "" indent_match = re.match(r'^(\s*)', last_line) line_indent = indent_match.group(1) if indent_match else " " expanded_last = last_line.expandtabs(4) first_struct_match = re.search(r'\{', remove_comments(last_line)) if first_struct_match: raw_before = last_line[:first_struct_match.start()] expanded_before = raw_before.expandtabs(4) target_struct_col = len(expanded_before) else: target_struct_col = len(line_indent.replace('\t', ' ')) raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces = max(0, target_struct_col - raw_indent_len) prefix_padding = ' ' * leading_spaces # 构造新行 new_block = "" for item in data_to_insert: item_clean = item.rstrip(',').strip() new_block += f"\n{line_indent}{prefix_padding}{item_clean}," new_body = body_content.rstrip() + new_block new_table = f"{header_part}{new_body}\n{footer_part}" replacements.append((full_start, full_end, new_table)) self.logger.debug(f"📌 插入 {len(data_to_insert)} 行到 {table_name}") # === 应用替换(倒序)=== replacements.sort(key=lambda x: x[0], reverse=True) final_content = content for start, end, r in replacements: self.logger.debug(f"📝 替换 [{start}:{end}] → 新内容:\n{r[:200]}...") final_content = final_content[:start] + r + final_content[end:] # === 写回文件 === self.c_file_path.write_text(final_content, encoding='utf-8') self.logger.info(f" 成功写回 C 文件: {self.c_file_path}") self.logger.info(f" 成功写回 {len(replacements)} 个修改块到文件") except Exception as e: self.logger.error(f"写回文件失败: {e}", exc_info=True) raise def run(self): self.logger.info("开始同步 POWER LOCALE 定义...") try: self.load_used_locales() self.parse_c_power_definitions() was_modified = self.validate_and_repair() if was_modified: if self.dry_run: self.logger.info("预览模式:检测到变更,但不会写入文件") else: self.logger.info("同步完成:已成功更新 C 文件") else: self.logger.info("所有 Locale 已存在,无需修改") return was_modified except Exception as e: self.logger.error(f"同步失败: {e}", exc_info=True) raise def main(): logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s', handlers=[ logging.FileHandler(LOG_FILE, encoding='utf-8'), logging.StreamHandler(sys.stdout) ], force=True ) logger = logging.getLogger(__name__) # 固定配置 c_file_path = "input/wlc_clm_data_6726b0.c" dry_run = False log_level = "INFO" config_path = "config/config.json" logging.getLogger().setLevel(log_level) print(f"开始同步 POWER LOCALE 定义...") print(f"C 源文件: {c_file_path}") if dry_run: print("启用 dry-run 模式:仅预览变更,不修改文件") try: sync = PowerTableSynchronizer( c_file_path=None, dry_run=dry_run, config_path=config_path, ) sync.run() print("同步完成!") print(f"详细日志已保存至: {LOG_FILE}") except FileNotFoundError as e: logger.error(f"文件未找到: {e}") print("请检查文件路径是否正确。") sys.exit(1) except PermissionError as e: logger.error(f"权限错误: {e}") print("无法读取或写入文件,请检查权限。") sys.exit(1) except Exception as e: logger.error(f"程序异常退出: {e}", exc_info=True) sys.exit(1) if __name__ == '__main__': main()
10-23
import json import re import logging import sys from pathlib import Path from shutil import copy2 from datetime import datetime from argparse import ArgumentParser # ------------------------------- # 🔧 新增:日志文件配置 # ------------------------------- # 正确获取项目根目录 PROJECT_ROOT = Path(__file__).parent.parent.resolve() LOG_DIR = PROJECT_ROOT / "output" / "log" # ✅ 关键:必须加上 parents=True 才能自动创建 output/ LOG_DIR.mkdir(parents=True, exist_ok=True) # 生成带时间戳的日志文件名 LOG_FILE = LOG_DIR / f"range_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" class CLMRangeSynchronizer: def __init__(self, manifest_path="output/generated_ranges_manifest.json", c_file_path="input/wlc_clm_data_6726b0.c", dry_run=False): self.manifest_path = Path(manifest_path) self.c_file_path = Path(c_file_path) self.dry_run = dry_run # 是否为预览模式 if not self.manifest_path.exists(): raise FileNotFoundError(f"找不到 manifest 文件: {self.manifest_path}") if not self.c_file_path.exists(): raise FileNotFoundError(f"找不到 C 源文件: {self.c_file_path}") self.used_ranges = [] self.array_macros = {} self.struct_entries = {} self.start_marker = "// === START CHANNEL RANGES" self.end_marker = "// === END CHANNEL RANGES" # ✅ 在实例化时获取 logger(避免全局变量) self.logger = logging.getLogger(__name__) def load_manifest(self): """加载并解析 generated_ranges_manifest.json""" with open(self.manifest_path, 'r', encoding='utf-8') as f: data = json.load(f) if "used_ranges" not in data: raise KeyError("❌ manifest 文件缺少 'used_ranges' 字段") valid_ranges = [] for item in data["used_ranges"]: if isinstance(item, str) and re.match(r'^RANGE_[\w\d_]+_\d+_\d+$', item): valid_ranges.append(item) else: self.logger.warning(f"跳过无效项: {item}") self.used_ranges = sorted(set(valid_ranges)) self.logger.info(f"已加载 {len(self.used_ranges)} 个有效 RANGE ") def parse_c_arrays(self): """解析 C 文件中的 channel_ranges_xxx[] 数组""" content = self.c_file_path.read_text(encoding='utf-8') start_idx = content.find(self.start_marker) end_idx = content.find(self.end_marker) if start_idx == -1 or end_idx == -1: raise ValueError("未找到 CHANNEL RANGES 注释锚点") block = content[start_idx:end_idx] pattern = re.compile( r'static\s+const\s+struct\s+clm_channel_range\s+(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{([^}]*)\};', re.DOTALL ) for array_name, body in pattern.findall(block): macros = [m.strip() for m in re.findall(r'RANGE_[\w\d_]+', body)] entries = [] for low, high in re.findall(r'\{\s*(\d+)\s*,\s*(\d+)\s*\}', body): entries.append({"low": int(low), "high": int(high)}) self.array_macros[array_name] = macros self.struct_entries[array_name] = entries self.logger.debug(f"解析数组 {array_name}: {len(macros)} 个") def get_array_name_for_range(self, range_macro): """根据 RANGE 推断应属于哪个数组""" match = re.match(r'RANGE_([0-9]+[A-Za-z])_([0-9]+)M_', range_macro, re.IGNORECASE) if not match: self.logger.warning(f"无法推断数组名: {range_macro}") return None band = match.group(1).lower() # '2g' bw = match.group(2) # '20' return f"channel_ranges_{band}_{bw}m" def extract_channels_from_macro(self, macro): """ 从字符串中提取信道范围。 Args: macro (str): 包含信道范围的字符串,格式应为 "_数字_数字" 形式。 Returns: tuple: 包含两个整数的元组,分别表示信道的低值和高值。如果格式错误,则返回 (None, None)。 """ match = re.search(r'_(\d+)_(\d+)$', macro) if match: low = int(match.group(1)) high = int(match.group(2)) return low, high self.logger.warning(f"格式错误,无法提取信道: {macro}") return None, None def validate_and_repair(self): """确保每个 used_range 都在正确的数组中""" modified = False changes = [] for range_macro in self.used_ranges: array_name = self.get_array_name_for_range(range_macro) if not array_name: self.logger.warning(f"无法识别数组类型,跳过: {range_macro}") continue if array_name not in self.array_macros: self.logger.info(f"创建新数组: {array_name}") self.array_macros[array_name] = [] self.struct_entries[array_name] = [] if range_macro not in self.array_macros[array_name]: low, high = self.extract_channels_from_macro(range_macro) if low is not None and high is not None: self.array_macros[array_name].append(range_macro) self.struct_entries[array_name].append({"low": low, "high": high}) changes.append(f"添加: {range_macro} → {{{low}, {high}}} 到 {array_name}") self.logger.info(f"将添加: {range_macro} → {{{low}, {high}}} 到 {array_name}") modified = True else: self.logger.warning(f"无法解析信道范围: {range_macro}") if modified and not self.dry_run: self._write_back_in_block() self.logger.info("C 文件已更新") elif modified and self.dry_run: self.logger.info("DRY-RUN MODE: 有变更但不会写入文件") else: self.logger.info("所有 RANGE 已存在,无需修改") return modified def _infer_array_from_enum(self, enum_decl): """ 从枚举声明中推断数组名称。 Args: enum_decl (str): 枚举声明字符串。 Returns: Optional[str]: 如果匹配到枚举声明,则返回推断出的数组名称;否则返回None。 """ match = re.search(r'enum\s+range_([a-z\d_]+)', enum_decl) if match: return f"channel_ranges_{match.group(1)}" return None def _format_array_body(self, macros, structs, indent=" "): """ 将定义和结构体信息格式化为字符串。 Args: macros (list): 定义列表。 structs (list): 结构体信息列表,每个元素是一个包含'low'和'high'键的字典。 indent (str, optional): 缩进字符串。默认为" "。 Returns: str: 格式化后的字符串。 """ lines = [] for i in range(len(macros)): line = f"{indent}{{ {structs[i]['low']}, {structs[i]['high']} }}, /* {macros[i]} */" lines.append(line) return "\n".join(lines) def _write_back_in_block(self): """ 安全地一次性更新 C 文件中的数组和枚举定义。 如果是 dry_run 模式,则不进行任何文件 I/O 操作(包括备份和写入)。 """ if self.dry_run: self.logger.info("DRY-RUN: 跳过写入文件(包括备份)") return try: content = self.c_file_path.read_text(encoding='utf-8') start_idx = content.find(self.start_marker) end_idx = content.find(self.end_marker) + len(self.end_marker) if start_idx == -1 or end_idx == -1: raise ValueError("未找到 CHANNEL RANGES 标记块") header = content[:start_idx] footer = content[end_idx:] block = content[start_idx:end_idx] new_block = block # === 更新数组定义 === array_pattern = re.compile( r'(static\s+const\s+struct\s+clm_channel_range\s+(channel_ranges_[\w\d]+)\s*\[\s*\]\s*=\s*\{)([^}]*)\};', re.DOTALL ) for match in array_pattern.finditer(block): array_name = match.group(2) if array_name not in self.array_macros: continue macros = self.array_macros[array_name] structs = self.struct_entries[array_name] formatted_body = self._format_array_body(macros, structs) old_decl = match.group(0) new_decl = f"{match.group(1)}\n{formatted_body}\n}};" new_block = new_block.replace(old_decl, new_decl) # === 更新 enum 定义 === enum_pattern = re.compile(r'(enum\s+range_[\w\d_]+\s*\{)([^}]*)\}(;)', re.DOTALL) final_block = new_block for match in enum_pattern.finditer(new_block): enum_body = match.group(2).strip() existing_macros = dict(re.findall(r'(RANGE_[\w\d_]+)\s*=\s*(\d+)', enum_body)) array_name = self._infer_array_from_enum(match.group(0)) if not array_name or array_name not in self.array_macros: continue expected_macros = self.array_macros[array_name] current_ids = [int(v) for v in existing_macros.values()] next_id = max(current_ids) + 1 if current_ids else 0 missing_macros = [m for m in expected_macros if m not in existing_macros] if not missing_macros: continue insert_lines = [f" {macro} = {next_id + i}," for i, macro in enumerate(missing_macros)] to_insert = "\n" + "\n".join(insert_lines) if enum_body.endswith(","): to_insert = "," + to_insert final_block = final_block[:match.end(2)] + to_insert + final_block[match.end(2):] # === 执行 I/O:仅在此处进行文件操作 === backup_path = self.c_file_path.with_suffix('.c.bak') copy2(self.c_file_path, backup_path) self.logger.info(f"已备份 → {backup_path}") self.c_file_path.write_text(header + final_block + footer, encoding='utf-8') self.logger.info(f"已保存: {self.c_file_path}") except Exception as e: self.logger.error(f"写回文件失败: {e}") raise def run(self): self.logger.info("开始同步 CLM RANGE 定义...") try: self.load_manifest() self.parse_c_arrays() was_modified = self.validate_and_repair() # 🔍 根据 dry_run 和是否需要修改,输出不同信息 if was_modified: if self.dry_run: self.logger.info("🟡 预览模式:检测到变更,但不会写入文件") else: self.logger.info("✅ 同步完成:已成功更新 C 文件") else: self.logger.info("🟢 所有 RANGE 已存在,无需修改") return was_modified except Exception as e: self.logger.error(f"❌ 同步失败: {e}") raise def main(): # ✅ 配置 logging:同时输出到 控制台 和 文件 logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s', handlers=[ logging.FileHandler(LOG_FILE, encoding='utf-8'), # 写入日志文件 logging.StreamHandler(sys.stdout) # 输出到屏幕 ], force=True # 允许重复运行(如 PyCharm 中多次 Run) ) logger = logging.getLogger(__name__) # === 固定配置 === manifest_path = "../output/generated_ranges_manifest.json" c_file_path = "../input/wlc_clm_data_6726b0.c" dry_run = False log_level = "INFO" # 设置运行时日志级别 logging.getLogger().setLevel(log_level) print(f"📌 开始同步 RANGE 定义...") print(f"📊 manifest 文件: {manifest_path}") print(f"📄 C 源文件: {c_file_path}") if dry_run: print("🟡 启用 dry-run 模式:仅预览变更,不修改文件") try: sync = CLMRangeSynchronizer( manifest_path=manifest_path, c_file_path=c_file_path, dry_run=dry_run ) sync.run() print("✅ 同步完成!") print(f"📄 详细日志已保存至: {LOG_FILE}") # 👈 提示用户日志位置 except FileNotFoundError as e: logger.error(f"文件未找到: {e}") print("❌ 请检查文件路径是否正确。") sys.exit(1) except PermissionError as e: logger.error(f"权限错误: {e}") print("❌ 无法读取或写入文件,请检查权限。") sys.exit(1) except Exception as e: logger.error(f"程序异常退出: {e}", exc_info=True) sys.exit(1) if __name__ == '__main__': main() 我发现执行完之后,// === START CHANNEL RANGES === /** 2.4G 20M channel ranges used in locales and restricted sets */ static const struct clm_channel_range channel_ranges_2g_20m[] = { { 1, 1}, { 1, 2}, { 1, 11}, { 1, 13}, { 1, 14}, { 2, 2}, { 2, 7}, { 2, 10}, { 3, 3}, { 3, 9}, { 3, 10}, { 4, 4}, { 4, 8}, { 5, 7}, { 8, 8}, { 9, 9}, { 10, 10}, { 11, 11}, { 12, 12}, { 12, 13}, { 13, 13}, { 14, 14}, }; /** Indices for 2.4G 20M channel ranges */ enum range_2g_20m { RANGE_2G_20M_1_1 = 0, RANGE_2G_20M_1_2 = 1, RANGE_2G_20M_1_11 = 2, RANGE_2G_20M_1_13 = 3, RANGE_2G_20M_1_14 = 4, RANGE_2G_20M_2_2 = 5, RANGE_2G_20M_2_7 = 6, RANGE_2G_20M_2_10 = 7, RANGE_2G_20M_3_3 = 8, RANGE_2G_20M_3_9 = 9, RANGE_2G_20M_3_10 = 10, RANGE_2G_20M_4_4 = 11, RANGE_2G_20M_4_8 = 12, RANGE_2G_20M_5_7 = 13, RANGE_2G_20M_8_8 = 14, RANGE_2G_20M_9_9 = 15, RANGE_2G_20M_10_10 = 16, RANGE_2G_20M_11_11 = 17, RANGE_2G_20M_12_12 = 18, RANGE_2G_20M_12_13 = 19, RANGE_2G_20M_13_13 = 20, RANGE_2G_20M_14_14 = 21, }; /** 2.4G 40M channel ranges used in locales and restricted sets */ static const struct clm_channel_range channel_ranges_2g_40m[] = { { 3, 3}, { 3, 11}, { 4, 4}, { 4, 5}, { 5, 5}, { 5, 6}, { 5, 7}, { 6, 6}, { 6, 7}, { 7, 7}, { 7, 8}, { 8, 8}, { 9, 9}, { 10, 10}, { 11, 11}, }; /** Indices for 2.4G 40M channel ranges */ enum range_2g_40m { RANGE_2G_40M_3_3 = 0, RANGE_2G_40M_3_11 = 1, RANGE_2G_40M_4_4 = 2, RANGE_2G_40M_4_5 = 3, RANGE_2G_40M_5_5 = 4, RANGE_2G_40M_5_6 = 5, RANGE_2G_40M_5_7 = 6, RANGE_2G_40M_6_6 = 7, RANGE_2G_40M_6_7 = 8, RANGE_2G_40M_7_7 = 9, RANGE_2G_40M_7_8 = 10, RANGE_2G_40M_8_8 = 11, RANGE_2G_40M_9_9 = 12, RANGE_2G_40M_10_10 = 13, RANGE_2G_40M_11_11 = 14, , RANGE_2G_40M_4_8 = 15,};只插入了一个RANGE_2G_40M_4_8 = 15,并且还没有在channel_ranges_2g_40m[]里面增加{15 15},但是看日志写的C:\Users\admin\PyCharmMiscProject\.venv\Scripts\python.exe F:\issue\channel\range_sync.py 📌 开始同步 RANGE 定义... 📊 manifest 文件: ../output/generated_ranges_manifest.json 📄 C 源文件: ../input/wlc_clm_data_6726b0.c 2025-10-15 20:32:52,502 [INFO] __main__: 开始同步 CLM RANGE 定义... 2025-10-15 20:32:52,503 [INFO] __main__: 已加载 7 个有效 RANGE 2025-10-15 20:32:52,508 [INFO] __main__: 创建新数组: channel_ranges_2g_20m 2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_11_11 → {11, 11} 到 channel_ranges_2g_20m 2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_1_1 → {1, 1} 到 channel_ranges_2g_20m 2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_1_11 → {1, 11} 到 channel_ranges_2g_20m 2025-10-15 20:32:52,508 [INFO] __main__: 将添加: RANGE_2G_20M_2_10 → {2, 10} 到 channel_ranges_2g_20m 2025-10-15 20:32:52,508 [INFO] __main__: 创建新数组: channel_ranges_2g_40m 2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_3_3 → {3, 3} 到 channel_ranges_2g_40m 2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_4_8 → {4, 8} 到 channel_ranges_2g_40m 2025-10-15 20:32:52,510 [INFO] __main__: 将添加: RANGE_2G_40M_9_9 → {9, 9} 到 channel_ranges_2g_40m 2025-10-15 20:32:52,518 [INFO] __main__: 已备份 → ..\input\wlc_clm_data_6726b0.c.bak 2025-10-15 20:32:52,523 [INFO] __main__: 已保存: ..\input\wlc_clm_data_6726b0.c 2025-10-15 20:32:52,523 [INFO] __main__: C 文件已更新 2025-10-15 20:32:52,523 [INFO] __main__: ✅ 同步完成:已成功更新 C 文件 ✅ 同步完成! 📄 详细日志已保存至: F:\issue\output\log\range_sync_20251015_203252.log 进程已结束,退出代码为 0 我很蒙
10-16
# power/power_sync.py import json import os import re import logging import sys from pathlib import Path from shutil import copy2 from datetime import datetime from utils import resource_path from typing import Dict, List, Tuple, Any # ------------------------------- # 日志配置 # ------------------------------- PROJECT_ROOT = Path(__file__).parent.parent.resolve() LOG_DIR = PROJECT_ROOT / "output" / "log" LOG_DIR.mkdir(parents=True, exist_ok=True) LOG_FILE = LOG_DIR / f"power_sync_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" class PowerTableSynchronizer: def __init__(self, c_file_path=None, dry_run=False, config_path="config/config.json"): self.logger = logging.getLogger(__name__) # === Step 1: 使用 resource_path 解析所有路径 === self.config_file_path = resource_path(config_path) logging.info(f"配置文件: {self.config_file_path}") if not os.path.exists(self.config_file_path): raise FileNotFoundError(f"配置文件不存在: {self.config_file_path}") try: with open(self.config_file_path, 'r', encoding='utf-8') as f: self.config = json.load(f) print(f"配置文件已加载: {self.config_file_path}") except json.JSONDecodeError as e: raise ValueError(f"配置文件格式错误,JSON 解析失败: {self.config_file_path}") from e except Exception as e: raise RuntimeError(f"读取配置文件时发生未知错误: {e}") from e self.dry_run = dry_run # === Step 2: 目标 C 文件处理 === if c_file_path is None: if "target_c_file" not in self.config: raise KeyError("config 文件缺少 'target_c_file' 字段") internal_c_path = self.config["target_c_file"] logging.info(f"使用内置 C 文件: {internal_c_path}") self.c_file_path =resource_path(internal_c_path) self._is_internal_c_file = True else: self.c_file_path = Path(c_file_path) self._is_internal_c_file = False if not self.c_file_path.exists(): raise FileNotFoundError(f"找不到 C 源文件: {self.c_file_path}") # === Step 3: 初始化数据容器 === self.locale_enums = {} # enum_name -> {"macros": [macro], "values": {macro: idx}} self.power_tables = {} # table_name -> [lines] self.table_pending_appends = {} # table_name -> List[str] # === Step 4: 加载锚点标记 === for marker_key in ["STR_POWER_LOCALE_ENUM", "END_POWER_LOCALE_ENUM", "STR_POWER_TABLE", "END_POWER_TABLE"]: if marker_key not in self.config: raise KeyError(f"config 文件缺少 '{marker_key}' 字段") self.start_enum_marker = self.config["STR_POWER_LOCALE_ENUM"] self.end_enum_marker = self.config["END_POWER_LOCALE_ENUM"] self.start_table_marker = self.config["STR_POWER_TABLE"] self.end_table_marker = self.config["END_POWER_TABLE"] # === Step 5: 功率表文件 === gen_file = PROJECT_ROOT / "output" / "tx_limit_table.c" if not gen_file.exists(): self.logger.error(f"❌ 找不到生成文件: {gen_file}") raise FileNotFoundError(f"请先运行 excel_to_clm.py 生成 tx_limit_table.c: {gen_file}") try: self.power = gen_file.read_text(encoding='utf-8') except Exception as e: self.logger.error(f"❌ 读取 {gen_file} 失败: {e}") raise # 加载 locale_targets 配置 if "locale_targets" not in self.config: raise KeyError("config 文件缺少 'locale_targets' 字段") required_keys = {"enum", "table", "suffix"} for i, item in enumerate(self.config["locale_targets"]): if not isinstance(item, dict) or not required_keys.issubset(item.keys()): raise ValueError(f"locale_targets[{i}] 缺少必要字段 {required_keys}: {item}") self.locale_targets = self.config["locale_targets"] self.logger.info(f"已加载 {len(self.locale_targets)} 个 Locale 映射目标") def offset_to_lineno(self, content: str, offset: int) -> int: """将字符偏移量转换为行号(从1开始)""" return content.count('\n', 0, offset) + 1 def parse_c_power_definitions(self): """解析 C 文件中的 enum locale_xxx_idx 和 static const unsigned char locales_xxx[]""" content = self.c_file_path.read_text(encoding='utf-8') # --- 解析 ENUM 区域 --- try: enum_start_idx = content.find(self.start_enum_marker) enum_end_idx = content.find(self.end_enum_marker) if enum_start_idx == -1 or enum_end_idx == -1: raise ValueError("未找到 LOCALE ENUM 标记块") enum_block = content[enum_start_idx:enum_end_idx] start_line = self.offset_to_lineno(content, enum_start_idx) end_line = self.offset_to_lineno(content, enum_end_idx) self.logger.info(f"找到 ENUM 标记范围:第 {start_line} 行 → 第 {end_line} 行") enum_pattern = re.compile( r'(enum\s+locale_[a-zA-Z0-9_]+(?:_[a-zA-Z0-9_]+)*_idx\s*\{)([^}]*)\}\s*;', re.DOTALL | re.IGNORECASE ) for match in enum_pattern.finditer(enum_block): enum_decl = match.group(0) self.logger.info(f" 解析枚举声明: {enum_decl}") enum_name_match = re.search(r'locale_[\w\d_]+_idx', enum_decl, re.IGNORECASE) if not enum_name_match: continue enum_name = enum_name_match.group(0).lower() body = match.group(2) # 在 parse_c_power_definitions() 中 body_no_comment = re.sub(r'//.*|/\*.*?\*/', '', body, flags=re.DOTALL) # 只提取 = 数字 的 valid_assignments = re.findall( r'(LOCALE_[A-Za-z0-9_]+)\s*=\s*(-?\b\d+\b)', body_no_comment ) macro_list = [m[0] for m in valid_assignments] value_map = {m: int(v) for m, v in valid_assignments} self.locale_enums[enum_name] = { "macros": macro_list, "values": value_map, "raw_body": body } self.logger.info(f" 解析枚举 {enum_name}: {len(macro_list)} 个") except Exception as e: self.logger.error(f"解析 ENUM 失败: {e}", exc_info=True) # --- 解析 TABLE 区域 --- try: table_start_idx = content.find(self.start_table_marker) table_end_idx = content.find(self.end_table_marker) if table_start_idx == -1 or table_end_idx == -1: raise ValueError("未找到 POWER TABLE 标记块") table_block = content[table_start_idx:table_end_idx] start_line = self.offset_to_lineno(content, table_start_idx) end_line = self.offset_to_lineno(content, table_end_idx) self.logger.info(f"找到 TABLE 标记范围:第 {start_line} 行 → 第 {end_line} 行") self.logger.info(" 提取的 table_block 示例:") self.logger.info(table_block[:500]) self.logger.info(table_block[-500:]) # === 增强解析 TABLE:按 /* Locale X */ 分块提取 === array_matches = list(re.finditer( r'static\s+const\s+unsigned\s+char\s+([\w\d_]+)\s*\[\s*\]\s*=\s*\{', table_block, re.IGNORECASE )) if not array_matches: self.logger.warning("未在 TABLE 区域找到任何 power table 数组定义") else: for match in array_matches: table_name = match.group(1) start_pos = match.start() brace_start = table_block.find('{', match.end()) if brace_start == -1: continue # 手动匹配大括号内容(支持嵌套) depth = 0 body_content = "" i = brace_start + 1 while i < len(table_block): c = table_block[i] if c == '{': depth += 1 elif c == '}': if depth == 0: break depth -= 1 body_content += c i += 1 # 逐行解析 body_content,按 /* Locale X */ 分块 lines = body_content.splitlines() entries = [] # 存储每一块: {'locale_tag': 'a_359', 'lines': [...]} current_block = [] current_locale = None for line in lines: stripped = line.strip() # 检查是否是新的 Locale 注释 comment_match = re.match(r'/\*\s*Locale\s+([A-Za-z0-9_-]+)\s*\([^)]+\)\s*\*/', stripped, re.IGNORECASE) if comment_match: # 保存上一个 block if current_locale and current_block: entries.append({ 'locale_tag': current_locale.lower(), 'lines': [ln.rstrip(',').rstrip() for ln in current_block] }) # 开始新 block raw_name = comment_match.group(1) # 如 A-359 normalized = raw_name.replace('-', '_').upper() # → A_359 current_locale = normalized current_block = [] continue # 忽略空行、纯注释行 clean_line = re.sub(r'/\*.*?\*/|//.*', '', stripped).strip() if clean_line: current_block.append(stripped) # 保留原始格式(含缩进和逗号) # 保存最后一个 block if current_locale and current_block: entries.append({ 'locale_tag': current_locale.lower(), 'lines': [ln.rstrip(',').rstrip() for ln in current_block] }) self.power_tables[table_name] = entries self.logger.info(f" 解析数组 {table_name}: {len(entries)} 个 Locale 数据块") except Exception as e: self.logger.error(f"解析 TABLE 失败: {e}", exc_info=True) def validate_and_repair(self): modified = False changes = [] all_locale_data = self.extract_all_raw_locale_data() for target in self.locale_targets: enum_name = target["enum"] table_name = target["table"] suffix = target["suffix"] # 关键修改:使用 assigned_locale,而不是 used_locales[idx] if "assigned_locale" not in target: raise KeyError(f"locale_targets 缺少 'assigned_locale': {target}") locale = target["assigned_locale"] macro_name = f"LOCALE_{suffix}_IDX_{locale.replace('-', '_').upper()}" if locale not in all_locale_data: raise RuntimeError(f"❌ 在 tx_limit_table.c 中找不到 Locale 数据: {locale}") data_lines = all_locale_data[locale] # --- 处理 ENUM --- if enum_name not in self.locale_enums: self.logger.warning(f"未找到枚举定义: {enum_name}") continue enum_data = self.locale_enums[enum_name] macros = enum_data["macros"] values = enum_data["values"] next_idx = self._get_next_enum_index(enum_name) if macro_name not in macros: macros.append(macro_name) values[macro_name] = next_idx changes.append(f"ENUM + {macro_name} = {next_idx}") modified = True if "pending_updates" not in enum_data: enum_data["pending_updates"] = [] enum_data["pending_updates"].append((macro_name, next_idx)) # --- 处理 TABLE --- if table_name not in self.power_tables: self.logger.warning(f"未找到 power table 数组: {table_name}") continue current_entries = self.power_tables[table_name] # 已经是 [{'locale_tag': ..., 'lines': [...]}, ...] # 归一化目标 locale 名称:us → US → us(小写用于比较) target_locale_normalized = target["assigned_locale"].replace('-', '_').upper().lower() # 检查是否已存在 already_exists = any( entry['locale_tag'] == target_locale_normalized for entry in current_entries ) if already_exists: self.logger.debug(f"Locale '{target['assigned_locale']}' 已存在于 {table_name},跳过") continue cleaned_new_lines = [ re.sub(r'/\*.*?\*/|//.*', '', ln).strip().rstrip(',') for ln in data_lines if re.sub(r'/\*.*?\*/|//.*', '', ln).strip() ] # 添加到内存结构(保持块结构) current_entries.append({ 'locale_tag': target_locale_normalized, 'lines': cleaned_new_lines }) changes.append(f"TABLE + {len(cleaned_new_lines)} 行 → {table_name}") modified = True # 记录待写入的数据(含原始行,用于生成注释) if table_name not in self.table_pending_appends: self.table_pending_appends[table_name] = [] self.table_pending_appends[table_name].append({ 'locale_tag': target["assigned_locale"], # 原始名(如 us) 'data_lines': data_lines # 原始带注释/缩进的行 }) if changes: self.logger.info(f"共需添加 {len(changes)} 项:\n" + "\n".join(f" → {ch}" for ch in changes)) return modified def _get_next_enum_index(self, enum_name): """基于已解析的 values 获取下一个可用索引""" if enum_name not in self.locale_enums: self.logger.warning(f"未找到枚举定义: {enum_name}") return 0 value_map = self.locale_enums[enum_name]["values"] # ✅ 直接使用已解析的数据 if not value_map: return 0 # 只考虑非负数(排除 CLM_LOC_NONE=-1, CLM_LOC_SAME=-2 等保留值) used_indices = [v for v in value_map.values() if v >= 0] if used_indices: next_idx = max(used_indices) + 1 else: next_idx = 0 # 没有有效数值时从 0 开始 return next_idx def extract_all_raw_locale_data(self) -> Dict[str, List[str]]: """ 从 output/tx_limit_table.c 中提取所有 /* Locale XXX */ 后面的数据块(直到下一个 Locale 或 EOF) 使用逐行解析,避免正则不匹配问题 """ #self.logger.info(f"📄 正在解析文件: {self.power}") #self.logger.info(f"🔍 前 300 字符:\n{self.power[:300]}") lines = self.power.splitlines() locale_data = {} current_locale = None current_block = [] for i, line in enumerate(lines): stripped = line.strip() # 检查是否是新的 Locale 标记 match = re.match(r'/\*\s*Locale\s+([A-Za-z0-9_]+)\s*\*/', stripped, re.IGNORECASE) if match: # 保存上一个 block if current_locale: # 清理 block:去空行、去注释、去尾逗号 cleaned = [ re.sub(r'/\*.*?\*/|//.*', '', ln).strip().rstrip(',') for ln in current_block if re.sub(r'/\*.*?\*/|//.*', '', ln).strip() ] locale_data[current_locale] = cleaned self.logger.debug(f" 已提取 Locale {current_locale},共 {len(cleaned)} 行") # 开始新 block current_locale = match.group(1) current_block = [] self.logger.debug(f" 发现 Locale: {current_locale}") continue # 收集当前 locale 的内容 if current_locale is not None: current_block.append(line) # 别忘了最后一个 block if current_locale: cleaned = [ re.sub(r'/\*.*?\*/|//.*', '', ln).strip().rstrip(',') for ln in current_block if re.sub(r'/\*.*?\*/|//.*', '', ln).strip() ] locale_data[current_locale] = cleaned self.logger.debug(f" 已提取最后 Locale {current_locale},共 {len(cleaned)} 行") self.logger.info(f" 成功提取 {len(locale_data)} 个 Locale 数据块: {list(locale_data.keys())}") return locale_data def _write_back_in_blocks(self): """将修改后的 enum 和 table 块写回原 C 文件,基于锚点 block 精准更新""" self.logger.info("正在写回修改后的数据...") if self.dry_run: self.logger.info("DRY-RUN: 跳过写入文件") return try: content = self.c_file_path.read_text(encoding='utf-8') # === Step 1: 查找所有锚点位置 === enum_start = content.find(self.start_enum_marker) enum_end = content.find(self.end_enum_marker) table_start = content.find(self.start_table_marker) table_end = content.find(self.end_table_marker) if -1 in (enum_start, enum_end, table_start, table_end): missing = [] if enum_start == -1: missing.append(f"起始 ENUM: {self.start_enum_marker}") if enum_end == -1: missing.append(f"结束 ENUM: {self.end_enum_marker}") if table_start == -1: missing.append(f"起始 TABLE: {self.start_table_marker}") if table_end == -1: missing.append(f"结束 TABLE: {self.end_table_marker}") raise ValueError(f"未找到锚点标记: {missing}") # === Step 2: 定义总修改范围 [header][block][footer] === enum_block=content[enum_start:enum_end] table_block=content[table_start:table_end] self.logger.info(f" 修改枚举范围: 第 {self.offset_to_lineno(content, enum_start)} 行 → " f"{self.offset_to_lineno(content, enum_end)} 行") self.logger.info(f" 修改数组范围: 第 {self.offset_to_lineno(content, table_start)} 行 → " f"{self.offset_to_lineno(content, table_end)} 行") replacements = [] # (start_in_block, end_in_block, replacement) def remove_comments(text): text = re.sub(r'//.*$', '', text, flags=re.MULTILINE) text = re.sub(r'/\*.*?\*/', '', text, flags=re.DOTALL) return text.strip() # === Step 3: 更新 ENUMs - 使用 pending_updates 中记录的所有 === for target in self.locale_targets: enum_name_key = target["enum"] enum_data = self.locale_enums.get(enum_name_key) if not enum_data or "pending_updates" not in enum_data: continue insertions = enum_data["pending_updates"] if not insertions: continue pattern = re.compile( rf'(enum\s+{re.escape(enum_name_key)}\s*\{{)([^}}]*)\}}\s*;', re.DOTALL | re.IGNORECASE ) match = pattern.search(enum_block) if not match: self.logger.warning(f"未找到枚举: {enum_name_key}") continue header_part = match.group(1) body_content = match.group(2) full_start = match.start() full_end = match.end() lines = [ln for ln in body_content.split('\n') if ln.strip()] last_line = lines[-1] if lines else "" clean_last = remove_comments(last_line) indent_match = re.match(r'^(\s*)', last_line) line_indent = indent_match.group(1) if indent_match else " " expanded_last = last_line.expandtabs(4) first_macro_match = re.search(r'LOCALE_[A-Z0-9_]+', clean_last) if first_macro_match: raw_before = last_line[:first_macro_match.start()] expanded_before = raw_before.expandtabs(4) target_macro_col = len(expanded_before) else: target_macro_col = len(line_indent.replace('\t', ' ')) eq_match = re.search(r'=\s*\d+', clean_last) if eq_match and first_macro_match: eq_abs_start = first_macro_match.start() + eq_match.start() raw_eq_part = last_line[:eq_abs_start] expanded_eq_part = raw_eq_part.expandtabs(4) target_eq_col = len(expanded_eq_part) else: target_eq_col = target_macro_col + 30 # 合理默认值 new_body = body_content.rstrip() if not new_body.endswith(','): new_body += ',' for macro_name, next_idx in insertions: # 遍历所有 pending 插入项 current_visual_len = len(macro_name.replace('\t', ' ')) padding_to_eq = max(1, target_eq_col - target_macro_col - current_visual_len) formatted_macro = f"{macro_name}{' ' * padding_to_eq}= {next_idx}" visible_macros = len(re.findall(r'LOCALE_[A-Z0-9_]+', clean_last)) MAX_PER_LINE = 4 if visible_macros < MAX_PER_LINE and last_line.strip(): insertion = f" {formatted_macro}," updated_last = last_line.rstrip() + insertion new_body = body_content.rsplit(last_line, 1)[0] + updated_last last_line = updated_last # 更新 last_line 用于下一次判断 clean_last = remove_comments(last_line) else: raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces = max(0, target_macro_col - raw_indent_len) prefix_padding = ' ' * leading_spaces new_line = f"\n{line_indent}{prefix_padding}{formatted_macro}," new_body += new_line last_line = new_line.strip() clean_last = remove_comments(last_line) new_enum = f"{header_part}{new_body}\n}};" # 再计算全局偏移并添加替换任务 full_start = enum_start + match.start() # 全局起始位置 full_end = enum_start + match.end() # 全局结束位置 replacements.append((full_start, full_end, new_enum)) self.logger.debug(f" 插入 ENUM: {dict(insertions)}") # 清除标记防止重复插入 enum_data.pop("pending_updates", None) # === Step 4: 更新 TABLEs —— 动态表名(追加模式)=== seen = set() table_names = [] for target in self.locale_targets: name = target["table"] if name not in seen: table_names.append(name) seen.add(name) for table_name in table_names: if table_name not in self.power_tables: self.logger.info(f" 没有需要更新的表: {table_name}") continue # 查找 assigned_locale source_locale = None for tgt in self.locale_targets: if tgt["table"] == table_name: source_locale = tgt["assigned_locale"] break if not source_locale: self.logger.warning(f" 未在 locale_targets 中找到映射: {table_name}") continue # 使用 pending_appends 判断是否需要追加 if table_name not in self.table_pending_appends: self.logger.debug(f" 跳过无待追加数据的表: {table_name}") continue data_to_insert = self.table_pending_appends[table_name] if not data_to_insert: continue pattern = re.compile( rf'(\b{re.escape(table_name)}\s*\[\s*\]\s*=\s*\{{)(.*?)(\}}\s*;\s*)', re.DOTALL | re.IGNORECASE ) match = pattern.search(table_block) if not match: self.logger.warning(f" 未找到数组定义: {table_name}") continue header_part = match.group(1) body_content = match.group(2) footer_part = match.group(3) lines = [ln for ln in body_content.split('\n') if ln.strip()] last_line = lines[-1] if lines else "" indent_match = re.match(r'^(\s*)', last_line) line_indent = indent_match.group(1) if indent_match else " " expanded_last = last_line.expandtabs(4) first_struct_match = re.search(r'\{', remove_comments(last_line)) if first_struct_match: raw_before = last_line[:first_struct_match.start()] expanded_before = raw_before.expandtabs(4) target_struct_col = len(expanded_before) else: target_struct_col = len(line_indent.replace('\t', ' ')) raw_indent_len = len(line_indent.replace('\t', ' ')) leading_spaces = max(0, target_struct_col - raw_indent_len) prefix_padding = ' ' * leading_spaces new_block = "" for item in data_to_insert: item_clean = item.rstrip(',').strip() new_block += f"\n{line_indent}{prefix_padding}{item_clean}," new_body = body_content.rstrip() + new_block full_start = table_start + match.start() full_end = table_start + match.end() new_table = f"{header_part}{new_body}\n{footer_part}" replacements.append((full_start, full_end, new_table)) self.logger.info(f"【追加】{len(data_to_insert)} 行到 {table_name}") # 写完就清除,防止重复追加 self.table_pending_appends.pop(table_name, None) # === Step 5: 应用所有替换(倒序,避免偏移错乱)=== if not replacements: self.logger.info("无任何修改需要写入") return # 按全局偏移从后往前排序,防止前面修改影响后面位置 replacements.sort(key=lambda x: x[0], reverse=True) final_content = content # 从原内容开始 for start, end, r in replacements: self.logger.info(f"替换 [{start}:{end}] → 新内容:\n{r[:150]}...") final_content = final_content[:start] + r + final_content[end:] # 可选:检查是否真的变了 if content == final_content: self.logger.info(" 文件内容未发生变化,无需写入") return backup_path = self.c_file_path.with_suffix('.c.bak') copy2(self.c_file_path, backup_path) self.logger.info(f" 已备份 → {backup_path}") self.c_file_path.write_text(final_content, encoding='utf-8') self.logger.info(f" 成功写回 C 文件: {self.c_file_path}") self.logger.info(f" 共更新 {len(replacements)} 个区块") except Exception as e: self.logger.error(f" 写回文件失败: {e}", exc_info=True) raise def run(self): self.logger.info("开始同步 POWER LOCALE 定义...") try: self.parse_c_power_definitions() was_modified = self.validate_and_repair() if was_modified: if self.dry_run: self.logger.info("预览模式:检测到变更,但不会写入文件") else: self._write_back_in_blocks() # 关键:执行写入操作 self.logger.info("同步完成:已成功更新 C 文件") else: self.logger.info("所有 Locale 已存在,无需修改") return was_modified except Exception as e: self.logger.error(f"同步失败: {e}", exc_info=True) raise def main(): logging.basicConfig( level=logging.INFO, format='%(asctime)s [%(levelname)s] %(name)s: %(message)s', handlers=[ logging.FileHandler(LOG_FILE, encoding='utf-8'), logging.StreamHandler(sys.stdout) ], force=True ) logger = logging.getLogger(__name__) # 固定配置 c_file_path = "input/wlc_clm_data_6726b0.c" dry_run = False log_level = "INFO" config_path = "config/config.json" logging.getLogger().setLevel(log_level) print(f"开始同步 POWER LOCALE 定义...") print(f"C 源文件: {c_file_path}") if dry_run: print("启用 dry-run 模式:仅预览变更,不修改文件") try: sync = PowerTableSynchronizer( c_file_path=None, dry_run=dry_run, config_path=config_path, ) sync.run() print("同步完成!") print(f"详细日志已保存至: {LOG_FILE}") except FileNotFoundError as e: logger.error(f"文件未找到: {e}") print("请检查文件路径是否正确。") sys.exit(1) except PermissionError as e: logger.error(f"权限错误: {e}") print("无法读取或写入文件,请检查权限。") sys.exit(1) except Exception as e: logger.error(f"程序异常退出: {e}", exc_info=True) sys.exit(1) if __name__ == '__main__': main()
最新发布
10-24
import tkinter as tk from tkinter import scrolledtext, ttk, messagebox import os import logging from datetime import datetime import sys import locale import traceback # 在文件开头添加编码设置 if sys.platform == "win32": # 设置Windows系统的标准输出编码 sys.stdout.reconfigure(encoding='utf-8') # Python 3.7+ # 设置系统区域设置 locale.setlocale(locale.LC_ALL, '') # 对于Python 3.7以下版本 if sys.version_info < (3, 7): import io sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') class SimpleCLexer: def __init__(self): self.tokens = [] def tokenize(self, input_str): tokens = [] pos = 0 line = 1 column = 0 length = len(input_str) # 定义C语言的关键词和类型 keywords = { 'void', 'int', 'char', 'float', 'double', 'short', 'long', 'signed', 'unsigned', 'struct', 'union', 'enum', 'typedef', 'static', 'extern', 'auto', 'register', 'const', 'volatile', 'return', 'if', 'else', 'switch', 'case', 'default', 'for', 'while', 'do', 'break', 'continue', 'goto', 'sizeof' } # 扩展类型别名识别 types = {'U1', 'U2', 'U4', 'S1', 'S2', 'S4', 'BOOL', 'BYTE', 'WORD', 'DWORD'} while pos < length: char = input_str[pos] # 跳过空白字符 if char in ' \t': pos += 1 column += 1 continue # 处理换行 if char == '\n': line += 1 column = 0 pos += 1 continue # 处理单行注释 if pos + 1 < length and input_str[pos:pos+2] == '//': end = input_str.find('\n', pos) if end == -1: end = length pos = end continue # 处理多行注释 if pos + 1 < length and input_str[pos:pos+2] == '/*': end = input_str.find('*/', pos + 2) if end == -1: end = length else: end += 2 pos = end continue # 处理标识符 if char.isalpha() or char == '_': start = pos pos += 1 while pos < length and (input_str[pos].isalnum() or input_str[pos] == '_'): pos += 1 token_text = input_str[start:pos] token_type = 'IDENTIFIER' # 检查是否为关键字或类型 if token_text in keywords: token_type = 'KEYWORD' elif token_text in types: token_type = 'TYPE' tokens.append({ 'type': token_type, 'text': token_text, 'line': line, 'column': column }) column += (pos - start) continue # 处理数字 if char.isdigit(): start = pos pos += 1 while pos < length and (input_str[pos].isdigit() or input_str[pos] in '.xXabcdefABCDEF'): pos += 1 tokens.append({ 'type': 'NUMBER', 'text': input_str[start:pos], 'line': line, 'column': column }) column += (pos - start) continue # 处理字符串 if char == '"': start = pos pos += 1 while pos < length and input_str[pos] != '"': if input_str[pos] == '\\' and pos + 1 < length: pos += 2 else: pos += 1 if pos < length and input_str[pos] == '"': pos += 1 tokens.append({ 'type': 'STRING', 'text': input_str[start:pos], 'line': line, 'column': column }) column += (pos - start) continue # 处理字符 if char == "'": start = pos pos += 1 while pos < length and input_str[pos] != "'": if input_str[pos] == '\\' and pos + 1 < length: pos += 2 else: pos += 1 if pos < length and input_str[pos] == "'": pos += 1 tokens.append({ 'type': 'CHAR', 'text': input_str[start:pos], 'line': line, 'column': column }) column += (pos - start) continue # 处理运算符和标点符号 operators = { '(', ')', '{', '}', '[', ']', ';', ',', '.', '->', '++', '--', '&', '*', '+', '-', '~', '!', '/', '%', '<<', '>>', '<', '>', '<=', '>=', '==', '!=', '^', '|', '&&', '||', '?', ':', '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '&=', '^=', '|=', ',' } # 尝试匹配最长的运算符 matched = False for op_len in range(3, 0, -1): if pos + op_len <= length and input_str[pos:pos+op_len] in operators: tokens.append({ 'type': 'OPERATOR', 'text': input_str[pos:pos+op_len], 'line': line, 'column': column }) pos += op_len column += op_len matched = True break if matched: continue # 无法识别的字符 tokens.append({ 'type': 'UNKNOWN', 'text': char, 'line': line, 'column': column }) pos += 1 column += 1 return tokens class FunctionAnalyzer: def __init__(self): self.function_name = "" self.parameters = set() self.local_vars = [] self.global_vars = [] self.function_calls = [] self.current_function = None self.in_function = False self.in_function_body = False self.brace_depth = 0 self.variable_declarations = {} self.control_structures = {"if", "for", "while", "switch", "return", "else"} self.macro_definitions = set() self.recorded_globals = set() self.storage_classes = {"static", "extern", "auto", "register"} # 定义允许的类型(修复错误) self.basic_types = {'void', 'int', 'char', 'float', 'double', 'short', 'long', 'signed', 'unsigned'} self.type_aliases = {"U1", "U2", "U4", "S1", "S2", "S4"} self.allowed_types = self.basic_types | self.type_aliases self.inside_expression = False # 添加新状态跟踪 self.debug_level = 3 # 1=基本, 2=详细, 3=非常详细 self.all_variables = [] # 记录所有找到的变量 # 添加函数前缀识别 self.function_prefixes = { "vd_": "void", "u1_": "U1", "u2_": "U2", "u4_": "U4", "s1_": "S1", "s2_": "S2", "s4_": "S4" } def log(self, message, level="info", debug_level=1): """增强版日志方法""" if level == "debug" and debug_level > self.debug_level: return prefix = { 1: "[DEBUG1]", 2: "[DEBUG2]", 3: "[DEBUG3]" }.get(debug_level, "[DEBUG]") full_message = f"{prefix} {message}" if self.log_to_gui: self.log_to_gui(full_message, level) else: print(f"{level.upper()}: {full_message}") def analyze(self, tokens): self.tokens = tokens self.pos = 0 self.current_line = 0 self.inside_expression = False # 重置表达式状态 # 第一步:识别定义(全大写标识符) self._identify_macros() self.log("开始分析函数体", "debug", 1) self.log(f"共收到 {len(tokens)} 个token", "debug", 2) # 第二步:分析函数体 while self.pos < len(self.tokens): token = self.tokens[self.pos] self.log(f"处理token: {token['text']} (类型:{token['type']}, 行:{token['line']})", "debug", 3) self.current_line = token['line'] # 检测表达式开始和结束 if token['text'] in {'(', '{', '['}: self.inside_expression = True elif token['text'] in {')', '}', ']'}: self.inside_expression = False # 检测函数定义 if token['text'] in self.storage_classes or token['text'] in self.allowed_types: if self._is_function_definition(): self._handle_function_definition() continue # 在函数体内检测变量声明 if self.in_function_body: if token['text'] in self.allowed_types: self._handle_variable_declaration() continue # 检测函数调用 if token['type'] == 'IDENTIFIER' and self.pos + 1 < len(self.tokens): next_token = self.tokens[self.pos + 1] if next_token['text'] == '(': self._handle_function_call() continue # 检测变量使用 if token['type'] == 'IDENTIFIER': self._handle_identifier_use(token) # 跟踪大括号深度 if token['text'] == '{': self.brace_depth += 1 if self.in_function and self.brace_depth == 1: self.in_function_body = True elif token['text'] == '}': self.brace_depth -= 1 if self.brace_depth == 0 and self.in_function: self.in_function = False self.in_function_body = False self.pos += 1 self.log("分析完成", "debug", 1) self.log(f"找到的总变量数: {len(self.all_variables)}", "debug", 1) return self def _identify_macros(self): """识别定义(全大写标识符)""" for token in self.tokens: if token['type'] == 'IDENTIFIER' and token['text'].isupper(): self.macro_definitions.add(token['text']) def _is_function_definition(self): pos = self.pos storage_class = None # 检测存储类说明符 if self.tokens[pos]['text'] in self.storage_classes: storage_class = self.tokens[pos]['text'] pos += 1 # 检测返回类型 if pos >= len(self.tokens) or self.tokens[pos]['text'] not in self.allowed_types: return False return_type = self.tokens[pos]['text'] pos += 1 # 处理指针声明 if pos < len(self.tokens) and self.tokens[pos]['text'] == '*': return_type += '*' pos += 1 # 检测函数名 if pos < len(self.tokens) and self.tokens[pos]['type'] == 'IDENTIFIER': func_name = self.tokens[pos]['text'] pos += 1 else: return False # 检测参数列表开头的'(' if pos < len(self.tokens) and self.tokens[pos]['text'] == '(': pos += 1 else: return False # 参数列表必须包含至少一个参数或void if pos < len(self.tokens) and self.tokens[pos]['text'] == ')': # 空参数列表 pos += 1 else: # 非空参数列表 depth = 1 while pos < len(self.tokens) and depth > 0: if self.tokens[pos]['text'] == '(': depth += 1 elif self.tokens[pos]['text'] == ')': depth -= 1 pos += 1 # 检测函数体开头的'{' (允许最多5个token的间隔) found_brace = False for i in range(min(5, len(self.tokens) - pos)): if self.tokens[pos + i]['text'] == '{': found_brace = True break return found_brace def _handle_function_definition(self): self.log(">>> 进入函数定义处理", "debug", 2) start_pos = self.pos storage_class = None # 处理存储类说明符 if self.tokens[self.pos]['text'] in self.storage_classes: storage_class = self.tokens[self.pos]['text'] self.pos += 1 # 获取返回类型 return_type = self.tokens[self.pos]['text'] self.pos += 1 # 处理指针声明 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == '*': return_type += '*' self.pos += 1 # 获取函数名 if self.pos < len(self.tokens) and self.tokens[self.pos]['type'] == 'IDENTIFIER': func_name = self.tokens[self.pos]['text'] self.pos += 1 else: self.pos = start_pos return # 记录函数名 self.function_name = func_name self.current_function = func_name self.variable_declarations[func_name] = True # 跳过 '(' if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == '(': self.pos += 1 # 提取参数 params = [] current_param = [] depth = 1 param_line = self.current_line while self.pos < len(self.tokens) and depth > 0: token = self.tokens[self.pos] if token['text'] == '(': depth += 1 elif token['text'] == ')': depth -= 1 if depth == 0: break elif token['text'] == ',' and depth == 1: # 提取参数类型和名称 param_type, param_name = self._extract_param_info(current_param) if param_type and param_name: params.append({ 'type': param_type, 'name': param_name, 'line': param_line }) self.variable_declarations[param_name] = True current_param = [] param_line = token['line'] self.pos += 1 continue current_param.append(token) self.pos += 1 # 处理最后一个参数 if current_param: param_type, param_name = self._extract_param_info(current_param) if param_type and param_name: params.append({ 'type': param_type, 'name': param_name, 'line': param_line }) self.variable_declarations[param_name] = True # 记录参数 self.parameters = params param_names = [p['name'] for p in params] if params else [] # 查找函数体开头的'{' while self.pos < len(self.tokens) and self.tokens[self.pos]['text'] != '{': self.pos += 1 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == '{': self.in_function = True self.in_function_body = False self.brace_depth = 0 self.pos += 1 # 添加函数名到变量列表 func_info = { 'name': func_name, 'type': 'function', 'line': self.current_line, 'scope': 'function' } self.all_variables.append(func_info) self.log(f"添加函数: {func_name}", "info") self.log("<<< 退出函数定义处理", "debug", 2) return param_names def _extract_param_info(self, tokens): """从参数token列表中提取类型和名称""" param_type = [] param_name = None for token in tokens: if token['type'] in ('KEYWORD', 'TYPE') or token['text'] in self.allowed_types: param_type.append(token['text']) elif token['type'] == 'IDENTIFIER' and not token['text'].isupper(): param_name = token['text'] return ' '.join(param_type), param_name def _handle_variable_declaration(self): self.log(">>> 进入变量声明处理", "debug", 2) self.log(f"当前token: {self.tokens[self.pos]['text']}", "debug", 3) # 获取变量类型 var_type = self.tokens[self.pos]['text'] self.log(f"检测到变量声明类型: {var_type}", "debug") self.pos += 1 # 处理指针声明 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == '*': var_type += '*' self.pos += 1 var_names = [] current_line = self.current_line # 处理变量名 while self.pos < len(self.tokens): token = self.tokens[self.pos] # 标识符 - 变量名 if token['type'] == 'IDENTIFIER' and not token['text'].isupper(): var_name = token['text'] # 跳过定义 if var_name not in self.macro_definitions: self.log(f"找到变量名: {var_name}", "debug") var_names.append(var_name) self.variable_declarations[var_name] = True self.pos += 1 continue # 逗号 - 多个变量声明 elif token['text'] == ',': self.pos += 1 # 处理指针声明 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == '*': self.pos += 1 continue # 结束声明 elif token['text'] == ';': self.pos += 1 break # 数组声明 - 跳过数组大小 elif token['text'] == '[': self.pos += 1 depth = 1 while self.pos < len(self.tokens) and depth > 0: t = self.tokens[self.pos] if t['text'] == '[': depth += 1 elif t['text'] == ']': depth -= 1 self.pos += 1 continue # 初始化 - 跳过初始化表达式 elif token['text'] == '=': self.log("跳过初始化表达式", "debug") self.pos += 1 depth = 0 while self.pos < len(self.tokens): t = self.tokens[self.pos] if t['text'] in {'(', '['}: depth += 1 elif t['text'] in {')', ']'}: depth -= 1 elif t['text'] in {',', ';'} and depth == 0: break self.pos += 1 continue # 类型转换 - 跳过 elif token['text'] == '(' and self.pos + 1 < len(self.tokens): # 尝试识别类型转换 next_token = self.tokens[self.pos + 1] if next_token['text'] in self.allowed_types: self.log(f"跳过类型转换: ({next_token['text']})", "debug") # 跳过类型转换 self.pos += 2 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == ')': self.pos += 1 continue # 其他情况 self.log(f"跳过token: {token['text']} (类型: {token['type']})", "debug") self.pos += 1 # 添加到局部变量 (跳过定义和参数) for var_name in var_names: # 检查是否在参数列表中 is_param = False for param in self.parameters: if param['name'] == var_name: is_param = True self.log(f"跳过参数变量: {var_name}", "debug") break if not is_param and var_name not in self.macro_definitions: var_info = { 'type': var_type, 'name': var_name, 'line': current_line, 'scope': 'local' } self.local_vars.append(var_info) self.all_variables.append(var_info) self.log(f"添加局部变量: {var_type} {var_name} (行:{current_line})", "info") self.log("<<< 退出变量声明处理", "debug", 2) def _handle_identifier_use(self, token): var_name = token['text'] line = token['line'] self.log(f"处理标识符: {var_name} (行:{line})", "debug", 3) # 跳过定义(全大写) if var_name in self.macro_definitions or var_name.isupper(): self.log(f"跳过定义: {var_name}", "debug") return # 跳过已声明的变量(局部变量、参数、函数名) if var_name in self.variable_declarations: self.log(f"跳过已声明变量: {var_name}", "debug") return # 跳过函数调用(函数名已经在函数调用处理中记录) for call in self.function_calls: if call['name'] == var_name: self.log(f"跳过函数调用变量: {var_name}", "debug") return # 跳过控制结构 if var_name in self.control_structures: self.log(f"跳过控制结构: {var_name}", "debug") return # 跳过类型别名 if var_name in self.type_aliases: self.log(f"跳过类型别名: {var_name}", "debug") return # 跳过已经记录过的全局变量 if var_name in self.recorded_globals: self.log(f"跳过已记录全局变量: {var_name}", "debug") return # 添加到全局变量 var_info = { 'name': var_name, 'line': line, 'scope': 'global' } self.global_vars.append(var_info) self.all_variables.append(var_info) self.recorded_globals.add(var_name) self.log(f"添加全局变量: {var_name} (行:{line})", "info") def _handle_function_call(self): self.log(">>> 进入函数调用处理", "debug", 2) # 提取函数名 func_name = self.tokens[self.pos]['text'] line = self.current_line self.pos += 2 # 跳过函数名和 '(' # 提取参数 params = [] depth = 1 current_param = [] while self.pos < len(self.tokens) and depth > 0: token = self.tokens[self.pos] if token['text'] == '(': depth += 1 elif token['text'] == ')': depth -= 1 if depth == 0: break elif token['text'] == ',' and depth == 1: params.append(''.join([t['text'] for t in current_param]).strip()) current_param = [] self.pos += 1 continue current_param.append(token) self.pos += 1 if current_param: params.append(''.join([t['text'] for t in current_param]).strip()) # 跳过 ')' 如果还在范围内 if self.pos < len(self.tokens) and self.tokens[self.pos]['text'] == ')': self.pos += 1 # 确定返回类型 return_type = "unknown" if func_name.startswith("vd_"): return_type = "void" elif func_name.startswith(("u1_", "u2_", "u4_", "s1_", "s2_", "s4_")): prefix = func_name.split("_")[0] return_type = prefix.upper() # 添加到函数调用列表 func_info = { 'name': func_name, 'return_type': return_type, 'type': "function_call", 'params': ", ".join(params), # 将参数列表转换为字符串,用逗号分隔 'line': line, 'scope': 'call' } self.function_calls.append(func_info) self.all_variables.append(func_info) self.log(f"添加函数调用: {func_name} (行:{line})", "info") self.log("<<< 退出函数调用处理", "debug", 2) class FunctionParserApp: def __init__(self, root): self.root = root self.root.title("C语言函数解析器 (内置解析器版)") self.root.geometry("900x700") self.setup_logging() # 创建输入区域 input_frame = tk.LabelFrame(root, text="输入C语言函数体", padx=5, pady=5) input_frame.pack(fill="both", expand=True, padx=10, pady=5) self.input_text = scrolledtext.ScrolledText(input_frame, width=100, height=20) self.input_text.pack(fill="both", expand=True, padx=5, pady=5) # 按钮区域 btn_frame = tk.Frame(root) btn_frame.pack(fill="x", padx=10, pady=5) # 解析按钮 parse_btn = tk.Button(btn_frame, text="解析函数", command=self.parse_function, bg="#4CAF50", fg="white") parse_btn.pack(side="left", padx=5) # 保存日志按钮 save_log_btn = tk.Button(btn_frame, text="保存日志", command=self.save_logs) save_log_btn.pack(side="right", padx=5) # 进度条 self.progress = ttk.Progressbar(btn_frame, orient="horizontal", length=300, mode="determinate") self.progress.pack(side="left", padx=10, fill="x", expand=True) # 创建输出区域 output_frame = tk.LabelFrame(root, text="解析结果", padx=5, pady=5) output_frame.pack(fill="both", expand=True, padx=10, pady=5) self.output_text = scrolledtext.ScrolledText(output_frame, width=100, height=15) self.output_text.pack(fill="both", expand=True, padx=5, pady=5) self.output_text.config(state=tk.DISABLED) # 日志区域 log_frame = tk.LabelFrame(root, text="日志信息", padx=5, pady=5) log_frame.pack(fill="both", expand=True, padx=10, pady=5) self.log_text = scrolledtext.ScrolledText(log_frame, width=100, height=8) self.log_text.pack(fill="both", expand=True, padx=5, pady=5) self.log_text.config(state=tk.DISABLED) # 添加调试级别控制 debug_frame = tk.Frame(btn_frame) debug_frame.pack(side="left", padx=10) tk.Label(debug_frame, text="调试级别:").pack(side="left") self.debug_level = tk.IntVar(value=1) ttk.Combobox(debug_frame, textvariable=self.debug_level, values=[1, 2, 3], width=3).pack(side="left") # 添加示例按钮 example_btn = tk.Button(btn_frame, text="加载示例", command=self.load_example) example_btn.pack(side="right", padx=5) # 示例函数体 self.example_code = """static void Diag21_PID_C9(U1 u1_a_num) { U1 u1_t_cmplt; U1 u1_t_cnt; if((U1)DIAG_CNT_ZERO == u1_t_swrstcnt) /* Determine if a software reset is in progress */ { for(u1_t_cnt = (U1)DIAG21_ZERO; u1_t_cnt < (U1)DIAG21_PIDC9_FLAG; u1_t_cnt ++) { u1_t_cmplt = u1_g_InspSoftwareVersion(u4_g_cmd, &u4_g_data, (U1)TRUE); } vd_s_Diag21_U2ToU1(u2_g_buf, u1_g_data, (U1)DIAG21_PIDC9_FLAG); } else { /* Do Nothing */ } }""" def setup_logging(self): """配置日志系统""" self.log_filename = f"parser_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" # 创建文件处理器并指定UTF-8编码 file_handler = logging.FileHandler(self.log_filename, encoding='utf-8') file_handler.setLevel(logging.INFO) file_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) # 配置根日志器 root_logger = logging.getLogger() root_logger.setLevel(logging.INFO) root_logger.addHandler(file_handler) # 添加控制台处理器 console_handler = logging.StreamHandler() console_handler.setLevel(logging.INFO) console_handler.setFormatter(logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")) root_logger.addHandler(console_handler) logging.info("应用程序启动") def log_to_gui(self, message, level="info"): """将日志信息显示在GUI中""" try: self.log_text.config(state=tk.NORMAL) timestamp = datetime.now().strftime("%H:%M:%S") # 确保消息是字符串 if not isinstance(message, str): message = str(message) self.log_text.insert(tk.END, f"[{timestamp}] {message}\n") self.log_text.see(tk.END) self.log_text.config(state=tk.DISABLED) if level == "info": logging.info(message) elif level == "warning": logging.warning(message) elif level == "error": logging.error(message) except Exception as e: # 如果GUI日志失败,回退到控制台日志 print(f"GUI日志错误: {str(e)}") logging.error(f"GUI日志错误: {str(e)}") def save_logs(self): """保存日志到文件""" try: log_content = self.log_text.get("1.0", tk.END) filename = f"saved_log_{datetime.now().strftime('%H%M%S')}.txt" # 使用UTF-8编码保存文件 with open(filename, "w", encoding='utf-8') as f: f.write(log_content) self.log_to_gui(f"日志已保存到: {filename}", "info") messagebox.showinfo("保存成功", f"日志已保存到:\n{filename}") except Exception as e: self.log_to_gui(f"保存日志失败: {str(e)}", "error") messagebox.showerror("保存失败", f"无法保存日志:\n{str(e)}") def update_progress(self, value): """更新进度条""" self.progress['value'] = value self.root.update_idletasks() def load_example(self): """加载示例函数体""" self.input_text.delete(1.0, tk.END) self.input_text.insert(tk.END, self.example_code) self.log_to_gui("已加载示例函数体") def parse_function(self): """使用内置解析器解析C语言函数体""" try: code = self.input_text.get(1.0, tk.END) if not code.strip(): self.log_to_gui("错误: 没有输入函数体", "error") messagebox.showerror("错误", "请输入要解析的C语言函数体") return self.log_to_gui("开始解析函数体...") self.output_text.config(state=tk.NORMAL) self.output_text.delete(1.0, tk.END) self.update_progress(0) # 使用内置词法分析器 self.log_to_gui("执行词法分析...") lexer = SimpleCLexer() tokens = lexer.tokenize(code) self.update_progress(30) # 使用内置语法分析器 self.log_to_gui("执行语法分析...") analyzer = FunctionAnalyzer() analyzer.log_to_gui = self.log_to_gui analyzer.debug_level = self.debug_level.get() # 设置调试级别 analyzer.analyze(tokens) # 显示结果 self.log_to_gui("生成解析报告...") self.display_results( analyzer.local_vars, analyzer.global_vars, analyzer.function_calls, analyzer.function_name, analyzer.parameters ) self.update_progress(100) self.output_text.config(state=tk.DISABLED) self.log_to_gui("解析完成!") messagebox.showinfo("完成", "函数体解析成功完成!") except Exception as e: self.log_to_gui(f"解析错误: {str(e)}", "error") self.log_to_gui(f"错误详情: {traceback.format_exc()}", "error") messagebox.showerror("解析错误", f"发生错误:\n{str(e)}") self.update_progress(0) def display_results(self, local_vars, global_vars, function_calls, func_name, func_params): """增强版结果显示,包含所有变量信息""" # 显示函数签名 if func_name: self.output_text.insert(tk.END, "=== 函数签名 ===\n") self.output_text.insert(tk.END, f"函数名: {func_name}\n") if func_params: param_list = [] for param in func_params: param_list.append(f"{param['type']} {param['name']}") # 添加参数到变量列表 param_info = { 'name': param['name'], 'type': param['type'], 'line': param['line'], 'scope': 'parameter' } self.log_to_gui(f"添加参数: {param['type']} {param['name']} (行:{param['line']})") self.output_text.insert(tk.END, f"参数: {', '.join(param_list)}\n\n") else: self.output_text.insert(tk.END, "参数: 无\n\n") else: self.output_text.insert(tk.END, "=== 函数签名 ===\n") self.output_text.insert(tk.END, "警告: 无法识别函数签名\n\n") self.log_to_gui("无法识别函数签名", "warning") # 显示所有找到的变量 self.output_text.insert(tk.END, "=== 所有变量分析 ===\n") self.output_text.insert(tk.END, "类型 | 名称 | 作用域 | 行号\n") self.output_text.insert(tk.END, "-" * 50 + "\n") # 先显示参数 for param in func_params: self.output_text.insert(tk.END, f"参数 | {param['name']} | 参数 | {param['line']}\n") # 显示局部变量 for var in local_vars: self.output_text.insert(tk.END, f"变量 | {var['name']} | 局部 | {var['line']}\n") # 显示全局变量 for var in global_vars: self.output_text.insert(tk.END, f"变量 | {var['name']} | 全局 | {var['line']}\n") # 显示函数调用 for func in function_calls: self.output_text.insert(tk.END, f"函数调用 | {func['name']} | 调用 | {func['line']}\n") self.output_text.insert(tk.END, "\n") """显示解析结果""" # 显示函数签名 if func_name: self.output_text.insert(tk.END, "=== 函数签名 ===\n") self.output_text.insert(tk.END, f"函数名: {func_name}\n") # 修复参数显示问题 if func_params: param_list = [] for param in func_params: param_list.append(f"{param['type']} {param['name']}") self.output_text.insert(tk.END, f"参数: {', '.join(param_list)}\n\n") else: self.output_text.insert(tk.END, "参数: 无\n\n") else: self.output_text.insert(tk.END, "=== 函数签名 ===\n") self.output_text.insert(tk.END, "警告: 无法识别函数签名\n\n") self.log_to_gui("无法识别函数签名", "warning") # 显示局部变量 if local_vars: self.output_text.insert(tk.END, "=== 局部变量 ===\n") for var in local_vars: self.output_text.insert(tk.END, f"{var['type']} {var['name']} (行号: {var['line']})\n") self.log_to_gui(f"找到局部变量: {var['type']} {var['name']}") self.output_text.insert(tk.END, "\n") else: self.output_text.insert(tk.END, "未找到局部变量\n\n") self.log_to_gui("未找到局部变量", "warning") # 显示使用的全局变量 if global_vars: self.output_text.insert(tk.END, "=== 使用的全局变量 ===\n") for var in global_vars: self.output_text.insert(tk.END, f"{var['name']} (行号: {var['line']})\n") self.log_to_gui(f"找到全局变量: {var['name']}") self.output_text.insert(tk.END, "\n") else: self.output_text.insert(tk.END, "未使用全局变量\n\n") self.log_to_gui("未使用全局变量", "warning") # 显示函数调用 if function_calls: self.output_text.insert(tk.END, "=== 函数调用 ===\n") for func in function_calls: self.output_text.insert(tk.END, f"函数名: {func['name']} (行号: {func['line']})\n") self.output_text.insert(tk.END, f"返回类型: {func['return_type']}\n") self.output_text.insert(tk.END, f"参数: {func['params']}\n") self.output_text.insert(tk.END, "-" * 50 + "\n") self.log_to_gui(f"找到函数调用: {func['name']}") else: self.output_text.insert(tk.END, "未调用任何函数\n\n") self.log_to_gui("未调用任何函数", "warning") # 显示统计信息 self.output_text.insert(tk.END, "=== 解析统计 ===\n") self.output_text.insert(tk.END, f"局部变量数量: {len(local_vars)}\n") self.output_text.insert(tk.END, f"使用的全局变量数量: {len(global_vars)}\n") self.output_text.insert(tk.END, f"函数调用数量: {len(function_calls)}\n") self.output_text.insert(tk.END, "\n") # 添加变量统计 self.output_text.insert(tk.END, "=== 变量统计 ===\n") self.output_text.insert(tk.END, f"参数数量: {len(func_params)}\n") self.output_text.insert(tk.END, f"局部变量数量: {len(local_vars)}\n") self.output_text.insert(tk.END, f"全局变量数量: {len(global_vars)}\n") self.output_text.insert(tk.END, f"函数调用数量: {len(function_calls)}\n") self.output_text.insert(tk.END, f"总变量数量: {len(func_params) + len(local_vars) + len(global_vars) + len(function_calls)}\n") self.output_text.insert(tk.END, "\n") if __name__ == "__main__": root = tk.Tk() app = FunctionParserApp(root) root.mainloop() === 函数签名 === 函数名: Diag21_PID_C9 参数: U1 u1_a_num === 所有变量分析 === 类型 | 名称 | 作用域 | 行号 -------------------------------------------------- 参数 | u1_a_num | 参数 | 1 变量 | u1_t_cmplt | 全局 | 3 变量 | u1_t_cnt | 全局 | 4 变量 | u1_t_swrstcnt | 全局 | 6 函数调用 | u1_g_InspSoftwareVersion | 调用 | 10 函数调用 | vd_s_Diag21_U2ToU1 | 调用 | 12 === 函数签名 === 函数名: Diag21_PID_C9 参数: U1 u1_a_num 未找到局部变量 === 使用的全局变量 === u1_t_cmplt (行号: 3) u1_t_cnt (行号: 4) u1_t_swrstcnt (行号: 6) === 函数调用 === 函数名: u1_g_InspSoftwareVersion (行号: 10) 返回类型: U1 参数: u4_g_cmd, &u4_g_data, (U1)TRUE -------------------------------------------------- 函数名: vd_s_Diag21_U2ToU1 (行号: 12) 返回类型: void 参数: u2_g_buf, u1_g_data, (U1)DIAG21_PIDC9_FLAG -------------------------------------------------- === 解析统计 === 局部变量数量: 0 使用的全局变量数量: 3 函数调用数量: 2 === 变量统计 === 参数数量: 1 局部变量数量: 0 全局变量数量: 3 函数调用数量: 2 总变量数量: 6 [13:43:54] 已加载示例函数体 [13:43:57] 开始解析函数体... [13:43:57] 执行词法分析... [13:43:57] 执行语法分析... [13:43:57] [DEBUG1] 开始分析函数体 [13:43:57] [DEBUG2] 共收到 77 个token [13:43:57] [DEBUG3] 处理token: static (类型:KEYWORD, 行:1) [13:43:57] [DEBUG2] >>> 进入函数定义处理 [13:43:57] [DEBUG1] 添加函数: Diag21_PID_C9 [13:43:57] [DEBUG2] <<< 退出函数定义处理 [13:43:57] [DEBUG3] 处理token: U1 (类型:TYPE, 行:3) [13:43:57] [DEBUG3] 处理token: u1_t_cmplt (类型:IDENTIFIER, 行:3) [13:43:57] [DEBUG3] 处理标识符: u1_t_cmplt (行:3) [13:43:57] [DEBUG1] 添加全局变量: u1_t_cmplt (行:3) [13:43:57] [DEBUG3] 处理token: ; (类型:OPERATOR, 行:3) [13:43:57] [DEBUG3] 处理token: U1 (类型:TYPE, 行:4) [13:43:57] [DEBUG3] 处理token: u1_t_cnt (类型:IDENTIFIER, 行:4) [13:43:57] [DEBUG3] 处理标识符: u1_t_cnt (行:4) [13:43:57] [DEBUG1] 添加全局变量: u1_t_cnt (行:4) [13:43:57] [DEBUG3] 处理token: ; (类型:OPERATOR, 行:4) [13:43:57] [DEBUG3] 处理token: if (类型:KEYWORD, 行:6) [13:43:57] [DEBUG3] 处理token: ( (类型:OPERATOR, 行:6) [13:43:57] [DEBUG3] 处理token: ( (类型:OPERATOR, 行:6) [13:43:57] [DEBUG3] 处理token: U1 (类型:TYPE, 行:6) [13:43:57] [DEBUG3] 处理token: ) (类型:OPERATOR, 行:6) [13:43:57] [DEBUG3] 处理token: DIAG_CNT_ZERO (类型:IDENTIFIER, 行:6) [13:43:57] [DEBUG3] 处理标识符: DIAG_CNT_ZERO (行:6) [13:43:57] [DEBUG1] 跳过定义: DIAG_CNT_ZERO [13:43:57] [DEBUG3] 处理token: == (类型:OPERATOR, 行:6) [13:43:57] [DEBUG3] 处理token: u1_t_swrstcnt (类型:IDENTIFIER, 行:6) [13:43:57] [DEBUG3] 处理标识符: u1_t_swrstcnt (行:6) [13:43:57] [DEBUG1] 添加全局变量: u1_t_swrstcnt (行:6) [13:43:57] [DEBUG3] 处理token: ) (类型:OPERATOR, 行:6) [13:43:57] [DEBUG3] 处理token: { (类型:OPERATOR, 行:7) [13:43:57] [DEBUG3] 处理token: for (类型:KEYWORD, 行:8) [13:43:57] [DEBUG3] 处理token: ( (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: u1_t_cnt (类型:IDENTIFIER, 行:8) [13:43:57] [DEBUG3] 处理标识符: u1_t_cnt (行:8) [13:43:57] [DEBUG1] 跳过已记录全局变量: u1_t_cnt [13:43:57] [DEBUG3] 处理token: = (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: ( (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: U1 (类型:TYPE, 行:8) [13:43:57] [DEBUG2] >>> 进入变量声明处理 [13:43:57] [DEBUG3] 当前token: U1 [13:43:57] [DEBUG1] 检测到变量声明类型: U1 [13:43:57] [DEBUG1] 跳过token: ) (类型: OPERATOR) [13:43:57] [DEBUG1] 跳过token: DIAG21_ZERO (类型: IDENTIFIER) [13:43:57] [DEBUG2] <<< 退出变量声明处理 [13:43:57] [DEBUG3] 处理token: u1_t_cnt (类型:IDENTIFIER, 行:8) [13:43:57] [DEBUG3] 处理标识符: u1_t_cnt (行:8) [13:43:57] [DEBUG1] 跳过已记录全局变量: u1_t_cnt [13:43:57] [DEBUG3] 处理token: < (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: ( (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: U1 (类型:TYPE, 行:8) [13:43:57] [DEBUG2] >>> 进入变量声明处理 [13:43:57] [DEBUG3] 当前token: U1 [13:43:57] [DEBUG1] 检测到变量声明类型: U1 [13:43:57] [DEBUG1] 跳过token: ) (类型: OPERATOR) [13:43:57] [DEBUG1] 跳过token: DIAG21_PIDC9_FLAG (类型: IDENTIFIER) [13:43:57] [DEBUG2] <<< 退出变量声明处理 [13:43:57] [DEBUG3] 处理token: u1_t_cnt (类型:IDENTIFIER, 行:8) [13:43:57] [DEBUG3] 处理标识符: u1_t_cnt (行:8) [13:43:57] [DEBUG1] 跳过已记录全局变量: u1_t_cnt [13:43:57] [DEBUG3] 处理token: ++ (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: ) (类型:OPERATOR, 行:8) [13:43:57] [DEBUG3] 处理token: { (类型:OPERATOR, 行:9) [13:43:57] [DEBUG3] 处理token: u1_t_cmplt (类型:IDENTIFIER, 行:10) [13:43:57] [DEBUG3] 处理标识符: u1_t_cmplt (行:10) [13:43:57] [DEBUG1] 跳过已记录全局变量: u1_t_cmplt [13:43:57] [DEBUG3] 处理token: = (类型:OPERATOR, 行:10) [13:43:57] [DEBUG3] 处理token: u1_g_InspSoftwareVersion (类型:IDENTIFIER, 行:10) [13:43:57] [DEBUG2] >>> 进入函数调用处理 [13:43:57] [DEBUG1] 添加函数调用: u1_g_InspSoftwareVersion (行:10) [13:43:57] [DEBUG2] <<< 退出函数调用处理 [13:43:57] [DEBUG3] 处理token: ; (类型:OPERATOR, 行:10) [13:43:57] [DEBUG3] 处理token: } (类型:OPERATOR, 行:11) [13:43:57] [DEBUG3] 处理token: vd_s_Diag21_U2ToU1 (类型:IDENTIFIER, 行:12) [13:43:57] [DEBUG2] >>> 进入函数调用处理 [13:43:57] [DEBUG1] 添加函数调用: vd_s_Diag21_U2ToU1 (行:12) [13:43:57] [DEBUG2] <<< 退出函数调用处理 [13:43:57] [DEBUG3] 处理token: ; (类型:OPERATOR, 行:12) [13:43:57] [DEBUG3] 处理token: } (类型:OPERATOR, 行:13) [13:43:57] [DEBUG3] 处理token: else (类型:KEYWORD, 行:14) [13:43:57] [DEBUG3] 处理token: { (类型:OPERATOR, 行:15) [13:43:57] [DEBUG3] 处理token: } (类型:OPERATOR, 行:17) [13:43:57] [DEBUG3] 处理token: } (类型:OPERATOR, 行:18) [13:43:57] [DEBUG1] 分析完成 [13:43:57] [DEBUG1] 找到的总变量数: 6 [13:43:57] 生成解析报告... [13:43:57] 添加参数: U1 u1_a_num (行:1) [13:43:57] 未找到局部变量 [13:43:57] 找到全局变量: u1_t_cmplt [13:43:57] 找到全局变量: u1_t_cnt [13:43:57] 找到全局变量: u1_t_swrstcnt [13:43:57] 找到函数调用: u1_g_InspSoftwareVersion [13:43:57] 找到函数调用: vd_s_Diag21_U2ToU1 [13:43:57] 解析完成! 发现变量都没有找全,而且局部变量被识别为了全局变量
07-18
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值