1# -*- coding: utf-8 -*-
2"""
3Configuration management for RT-Thread build system.
4
5This module handles parsing and managing configuration from rtconfig.h files.
6"""
7
8import re
9import os
10from typing import Dict, List, Any, Optional, Union
11from dataclasses import dataclass
12from enum import Enum
13
14
15class ConfigType(Enum):
16    """Configuration value types."""
17    BOOLEAN = "boolean"
18    INTEGER = "integer"
19    STRING = "string"
20    UNDEFINED = "undefined"
21
22
23@dataclass
24class ConfigOption:
25    """Configuration option with metadata."""
26    name: str
27    value: Any
28    type: ConfigType
29    line_number: int = 0
30    comment: str = ""
31
32    def as_bool(self) -> bool:
33        """Get value as boolean."""
34        if self.type == ConfigType.BOOLEAN:
35            return bool(self.value)
36        elif self.type == ConfigType.INTEGER:
37            return self.value != 0
38        elif self.type == ConfigType.STRING:
39            return bool(self.value)
40        return False
41
42    def as_int(self) -> int:
43        """Get value as integer."""
44        if self.type == ConfigType.INTEGER:
45            return self.value
46        elif self.type == ConfigType.BOOLEAN:
47            return 1 if self.value else 0
48        elif self.type == ConfigType.STRING:
49            try:
50                return int(self.value)
51            except ValueError:
52                return 0
53        return 0
54
55    def as_str(self) -> str:
56        """Get value as string."""
57        if self.type == ConfigType.STRING:
58            return self.value
59        return str(self.value)
60
61
62class ConfigParser:
63    """Parser for rtconfig.h files."""
64
65    # Regular expressions for parsing
66    RE_DEFINE = re.compile(r'^\s*#\s*define\s+(\w+)(?:\s+(.*))?', re.MULTILINE)
67    RE_UNDEF = re.compile(r'^\s*#\s*undef\s+(\w+)', re.MULTILINE)
68    RE_IFDEF = re.compile(r'^\s*#\s*ifdef\s+(\w+)', re.MULTILINE)
69    RE_IFNDEF = re.compile(r'^\s*#\s*ifndef\s+(\w+)', re.MULTILINE)
70    RE_ENDIF = re.compile(r'^\s*#\s*endif', re.MULTILINE)
71    RE_COMMENT = re.compile(r'/\*.*?\*/', re.DOTALL)
72    RE_LINE_COMMENT = re.compile(r'//.*$', re.MULTILINE)
73
74    def __init__(self):
75        self.options: Dict[str, ConfigOption] = {}
76        self.conditions: List[str] = []
77
78    def parse_file(self, filepath: str) -> Dict[str, ConfigOption]:
79        """
80        Parse configuration file.
81
82        Args:
83            filepath: Path to rtconfig.h
84
85        Returns:
86            Dictionary of configuration options
87        """
88        if not os.path.exists(filepath):
89            raise FileNotFoundError(f"Configuration file not found: {filepath}")
90
91        with open(filepath, 'r', encoding='utf-8') as f:
92            content = f.read()
93
94        return self.parse_content(content)
95
96    def parse_content(self, content: str) -> Dict[str, ConfigOption]:
97        """
98        Parse configuration content.
99
100        Args:
101            content: File content
102
103        Returns:
104            Dictionary of configuration options
105        """
106        # Remove comments
107        content = self.RE_COMMENT.sub('', content)
108        content = self.RE_LINE_COMMENT.sub('', content)
109
110        # Parse line by line
111        lines = content.split('\n')
112        for i, line in enumerate(lines):
113            self._parse_line(line, i + 1)
114
115        return self.options
116
117    def _parse_line(self, line: str, line_number: int) -> None:
118        """Parse a single line."""
119        # Check for #define
120        match = self.RE_DEFINE.match(line)
121        if match:
122            name = match.group(1)
123            value = match.group(2) if match.group(2) else '1'
124
125            # Parse value
126            parsed_value, value_type = self._parse_value(value.strip())
127
128            # Create option
129            option = ConfigOption(
130                name=name,
131                value=parsed_value,
132                type=value_type,
133                line_number=line_number
134            )
135
136            self.options[name] = option
137            return
138
139        # Check for #undef
140        match = self.RE_UNDEF.match(line)
141        if match:
142            name = match.group(1)
143            if name in self.options:
144                del self.options[name]
145            return
146
147    def _parse_value(self, value: str) -> tuple:
148        """
149        Parse configuration value.
150
151        Returns:
152            Tuple of (parsed_value, ConfigType)
153        """
154        if not value or value == '1':
155            return (True, ConfigType.BOOLEAN)
156
157        # Try integer
158        try:
159            return (int(value, 0), ConfigType.INTEGER)  # Support hex/octal
160        except ValueError:
161            pass
162
163        # Try string (remove quotes)
164        if value.startswith('"') and value.endswith('"'):
165            return (value[1:-1], ConfigType.STRING)
166
167        # Default to string
168        return (value, ConfigType.STRING)
169
170
171class ConfigManager:
172    """
173    Configuration manager for build system.
174
175    This class manages configuration options and provides dependency checking.
176    """
177
178    def __init__(self):
179        self.parser = ConfigParser()
180        self.options: Dict[str, ConfigOption] = {}
181        self.cache: Dict[str, bool] = {}
182
183    def load_from_file(self, filepath: str) -> None:
184        """
185        Load configuration from file.
186
187        Args:
188            filepath: Path to rtconfig.h
189        """
190        self.options = self.parser.parse_file(filepath)
191        self.cache.clear()  # Clear dependency cache
192
193    def get_option(self, name: str) -> Optional[ConfigOption]:
194        """
195        Get configuration option.
196
197        Args:
198            name: Option name
199
200        Returns:
201            ConfigOption or None
202        """
203        return self.options.get(name)
204
205    def get_value(self, name: str, default: Any = None) -> Any:
206        """
207        Get configuration value.
208
209        Args:
210            name: Option name
211            default: Default value if not found
212
213        Returns:
214            Configuration value
215        """
216        option = self.options.get(name)
217        if option:
218            return option.value
219        return default
220
221    def get_dependency(self, depend: Union[str, List[str]]) -> bool:
222        """
223        Check if dependency is satisfied.
224
225        Args:
226            depend: Single dependency or list of dependencies
227
228        Returns:
229            True if all dependencies are satisfied
230        """
231        # Handle empty dependency
232        if not depend:
233            return True
234
235        # Convert to list
236        if isinstance(depend, str):
237            depend = [depend]
238
239        # Check cache
240        cache_key = ','.join(sorted(depend))
241        if cache_key in self.cache:
242            return self.cache[cache_key]
243
244        # Check all dependencies (AND logic)
245        result = all(self._check_single_dependency(d) for d in depend)
246
247        # Cache result
248        self.cache[cache_key] = result
249        return result
250
251    def _check_single_dependency(self, name: str) -> bool:
252        """Check a single dependency."""
253        option = self.options.get(name)
254        if not option:
255            return False
256
257        # For RT-Thread, any defined macro is considered True
258        # except if explicitly set to 0
259        if option.type == ConfigType.INTEGER:
260            return option.value != 0
261        elif option.type == ConfigType.BOOLEAN:
262            return option.value
263        elif option.type == ConfigType.STRING:
264            return bool(option.value)
265
266        return True
267
268    def get_all_options(self) -> Dict[str, Any]:
269        """
270        Get all configuration options as a simple dictionary.
271
272        Returns:
273            Dictionary of option names to values
274        """
275        return {name: opt.value for name, opt in self.options.items()}
276
277    def validate(self) -> List[str]:
278        """
279        Validate configuration.
280
281        Returns:
282            List of validation errors
283        """
284        errors = []
285
286        # Check for common issues
287        if 'RT_NAME_MAX' in self.options:
288            name_max = self.options['RT_NAME_MAX'].as_int()
289            if name_max < 4:
290                errors.append("RT_NAME_MAX should be at least 4")
291
292        if 'RT_THREAD_PRIORITY_MAX' in self.options:
293            prio_max = self.options['RT_THREAD_PRIORITY_MAX'].as_int()
294            if prio_max not in [8, 32, 256]:
295                errors.append("RT_THREAD_PRIORITY_MAX should be 8, 32, or 256")
296
297        return errors