#encoding: utf-8 import ast import re from collections import defaultdict from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional, Set from .cxxparser import CXXFileParser, CXXParseResult, Class, LiteralVariable, Method, Variable from .type import array_base, base_types, is_array_type class CallbackType(Enum): NotCallback = 0 # not a callback Direct = 1 Async = 2 """ 42 - dec 0b101010 - bin 052 - oct 0xaa - hex 0Xaa - hex 1234u - suffix 1234ull - suffix 145'920 - with single quotes 1.0 - double 1.0f - float ignore: 5.9604644775390625e-8F16 '123123' unsuportted: 1e10 - science 1E10 - science 1e+10 1e-10 1E-10 1E+10 """ cpp_digit_re = re.compile( "(0b[01]+|0[0-7]+|0[Xx][0-9a-fA-F]+|[0-9']*[0-9]+)((ull)|(ULL)|(llu)|(LLU)|(ul)|(UL)|(ll)|(LL)|[UuLl])?$") cpp_digit_suffix_types = { 'u': 'unsigned int', 'l': 'long', 'ul': 'usngined long', 'll': 'long long', 'ull': 'unsigned long long', 'llu': 'unsigned long long', 'f': 'float' } cpp_digit_suffix_types.update({k.upper(): v for k, v in cpp_digit_suffix_types.items()}) @dataclass class PreprocessedMethod(Method): has_overload: bool = False @dataclass class PreprocessedClass(Class): functions: Dict[str, List[PreprocessedMethod]] = field( default_factory=(lambda: defaultdict(list))) need_wrap: bool = False # if need_wrap is true, wrap this to dict is_pure_virtual: bool = False # generator will not assign python constructor for pure virtual class PreProcessorResult: def __init__(self): super().__init__() self.dict_classes: Set[str] = set() self.const_macros: Dict[str, Variable] = {} self.classes: Dict[str, PreprocessedClass] = {} class PreProcessor: def __init__(self, parse_result: CXXParseResult): self.parser_result = parse_result def process(self)->PreProcessorResult: result = PreProcessorResult() # all pod struct to dict # todo: generator doesn't support dict class currently # result.dict_classes = self._find_dict_classes() # all error written macros to constant result.const_macros = self._pre_process_constant_macros() result.classes = self._pre_process_classes(result.dict_classes) return result def _pre_process_classes(self, dict_classes: Set[str]): classes: Dict[str, PreprocessedClass] = {} for c in self.parser_result.classes.values(): gc = PreprocessedClass(**c.__dict__) gc.functions = { name: [PreprocessedMethod(**m.__dict__) for m in ms] for name, ms in gc.functions.items() } if c.is_polymorphic: gc.need_wrap = True classes[gc.name] = gc for c in classes.values(): for ms in c.functions.values(): # check overload if len(ms) >= 2: for m in ms: m.has_overload = True # check pure virtual for m in ms: if m.is_pure_virtual: c.is_pure_virtual = True return classes def _pre_process_constant_macros(self): macros = {} for name, definition in self.parser_result.macros.items(): value = PreProcessor._try_convert_to_constant(definition) if value is not None: value.name = name macros[name] = value return macros def _find_dict_classes(self): dict_classes = set() for c in self.parser_result.classes.values(): if self._can_convert_to_dict(c): dict_classes.add(c.name) return dict_classes def _to_basic_type_combination(self, t: str): try: return self._to_basic_type_combination(self.parser_result.typedefs[t]) except KeyError: return t def _is_basic_type(self, t: str): basic_combination = self._to_basic_type_combination(t) # just a basic type, such as int, char, short, double etc. if basic_combination in base_types: return True # array of basic type, such as int[], char[] if is_array_type(basic_combination) \ and array_base(basic_combination) in base_types: return True print(basic_combination) return False def _can_convert_to_dict(self, c: Class): # first: no functions if c.functions: return False # second: all variables are basic for v in c.variables.values(): if not self._is_basic_type(v.type): return False return True @staticmethod def _try_parse_cpp_digit_literal(literal: str): m = cpp_digit_re.match(literal) if m: digit = m.group(1) suffix = m.group(2) val = ast.literal_eval(digit.replace("'", "")) t = 'int' if suffix: t = cpp_digit_suffix_types[suffix] return LiteralVariable(name='', type=t, default=val, literal=literal) return None @staticmethod def _try_convert_to_constant(definition: str) -> Optional[Variable]: definition = definition.strip() try: if definition: var = PreProcessor._try_parse_cpp_digit_literal(definition) if var: return var val = None if definition.startswith('"') and definition.endswith('"'): val = ast.literal_eval(definition) return LiteralVariable(name='', type='const char *', default=val, literal=definition) if definition.startswith("'") and definition.endswith("'"): val = CXXFileParser.character_literal_to_int(definition[1:-1]) t = 'unsigned' valid = True if len(definition) >= 6: t = 'unsigned long long' valid = False return LiteralVariable(name='', type='int', default=val, literal=definition, literal_valid=valid) except SyntaxError: pass return None