# 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 int" valid = True if len(definition) >= 6: t = "unsigned long long" valid = False return LiteralVariable( name="", type=t, default=val, literal=definition, literal_valid=valid, ) except SyntaxError: pass return None