vnpy/binding/generator/autocxxpy/preprocessor.py
nanoric ed1758a26a [Add] use flake8
[Mod] use black instead of yapf
2019-01-24 09:06:31 -04:00

239 lines
6.7 KiB
Python

# 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