def parse_clang_node(cls, cursor: clang.Cursor, parent: typing.Optional[ir.Node] = None) -> ir.Node: """ Parses a Clang AST node identified by a cursor into an IR node. :param cursor: The cursor pointing to the node. :param parent: The parent IR node. :return: The corresponding IR node. """ type_str = getattr(cursor.type, 'spelling', '') # Build label label = f"{type_str}: {cursor.spelling}" if cursor.is_definition() and type_str else cursor.spelling # Default node data kwargs = dict( typ=cursor.kind.name, label=label, ref=cursor.get_usr(), parent=parent, source_range=cls.get_range(cursor), ) # Overrides for root node if parent is None and cursor.kind == clang.CursorKind.TRANSLATION_UNIT: kwargs['label'] = os.path.basename(kwargs['label']) # Overrides for specific kinds of nodes if cursor.kind in cls._customizers: cls._customizers[cursor.kind](cursor, kwargs) # Build node node = ir.Node(**kwargs) return node
def parse_ENUM_DECL(cursor: Cursor) -> ast.Node: if not cursor.is_definition(): return ast.EhEnum(0, ast.Identifier(0, cursor.spelling), None) fields: List[ast.Identifier] = [] expect: bool = False for t in cursor.get_tokens(): if t.spelling == '{' or t.spelling == ',': expect = True elif t.spelling == '}': break elif expect: fields.append(ast.Identifier(0, t.spelling)) expect = False return ast.EhEnum(0, ast.Identifier(0, cursor.spelling), fields)
def convert_decl(c: Cursor): nonlocal utility_mode, utility_mode_start, replacement_mode, replacement_mode_start, metadata_type assert not (replacement_mode and utility_mode) if c.kind in ignored_cursor_kinds: return normal_mode = not replacement_mode and not utility_mode # not (c.kind == CursorKind.VAR_DECL and c.displayname.startswith( # NIGHTWATCH_PREFIX)) and (utility_mode or replacement_mode): included_extent = True if (normal_mode and c.kind == CursorKind.FUNCTION_DECL and c.location.file.name == filename and c.spelling == "ava_metadata"): metadata_type = convert_type(c.result_type.get_pointee(), "ava_metadata", annotation_set(), set()) elif (normal_mode and c.kind == CursorKind.FUNCTION_DECL and c.displayname.startswith(NIGHTWATCH_PREFIX + "category_")): name = strip_unique_suffix( strip_prefix(NIGHTWATCH_PREFIX + "category_", c.displayname)) annotations = extract_annotations(c) attr_annotations = extract_attr_annotations(c) rule_list = default_rules if "default" in attr_annotations else rules annotations.pop("default", None) if name == "type": rule_list.append( Types(c.result_type.get_pointee(), annotations)) elif name == "functions": rule_list.append(Functions(annotations)) elif name == "pointer_types": rule_list.append(PointerTypes(annotations)) elif name == "const_pointer_types": rule_list.append(ConstPointerTypes(annotations)) elif name == "nonconst_pointer_types": rule_list.append(NonconstPointerTypes(annotations)) elif name == "non_transferable_types": rule_list.append(NonTransferableTypes(annotations)) elif normal_mode and c.kind == CursorKind.VAR_DECL and c.storage_class == StorageClass.STATIC: # This is a utility function for the API forwarding code. parse_expects( c.linkage == LinkageKind.INTERNAL, f"at {term.yellow(c.displayname)}", "API utility functions should be static (or similar) since they are included in header files.", loc=convert_location(c.location), ) utility_extents.append((c.extent.start.line, c.extent.end.line)) elif c.kind == CursorKind.VAR_DECL and c.displayname.startswith( NIGHTWATCH_PREFIX): name = strip_unique_suffix(strip_nw(c.displayname)) if name == "begin_utility": parse_requires( not utility_mode, "ava_begin_utility can only be used outside utility mode to enter that mode." ) utility_mode = True utility_mode_start = c.extent.start.line elif name == "end_utility": parse_requires( utility_mode, "ava_end_utility can only be used inside utility mode to exit that mode." ) utility_mode = False parse_assert(utility_mode_start is not None, "Should be unreachable.") utility_extents.append((utility_mode_start, c.extent.end.line)) elif name == "begin_replacement": parse_requires( not replacement_mode, "ava_begin_replacement can only be used outside replacement mode to enter that mode.", ) replacement_mode = True replacement_mode_start = c.extent.start.line elif name == "end_replacement": parse_requires( replacement_mode, "ava_end_replacement can only be used inside replacement mode to exit that mode." ) replacement_mode = False parse_assert(replacement_mode_start is not None, "Should be unreachable.") replacement_extents.append( (replacement_mode_start, c.extent.end.line)) else: global_config[name] = get_string_literal(c) elif (normal_mode and c.kind == CursorKind.VAR_DECL and c.type.spelling.endswith("_resource") and c.type.spelling.startswith("ava_")): # TODO: Use the resource declarations to check resource usage. pass elif c.kind == CursorKind.FUNCTION_DECL and c.location.file.name == filename: if normal_mode and c.is_definition( ) and c.storage_class == StorageClass.STATIC: # This is a utility function for the API forwarding code. parse_expects( c.linkage == LinkageKind.INTERNAL, f"at {term.yellow(c.displayname)}", "API utility functions should be static (or similar) since they are included in header files.", loc=convert_location(c.location), ) utility_extents.append( (c.extent.start.line, c.extent.end.line)) elif normal_mode: # This is an API function. f = convert_function(c) if f: functions[c.mangled_name] = f elif replacement_mode: # Remove the function from the list because it is replaced replaced_functions[c.mangled_name] = c elif (normal_mode and c.kind == CursorKind.FUNCTION_DECL and c.location.file.name in [f.name for f in primary_include_files.values()]): included_extent = False f = convert_function(c, supported=False) if f: include_functions[c.mangled_name] = f elif (normal_mode and c.kind == CursorKind.INCLUSION_DIRECTIVE and not c.displayname.endswith(nightwatch_parser_c_header) and c.location.file.name == filename): try: primary_include_files[c.displayname] = c.get_included_file() except AssertionError as e: parse_assert(not e, str(e), loc=convert_location(c.location)) # elif normal_mode and c.kind == CursorKind.INCLUSION_DIRECTIVE and c.tokens[-1].spelling == '"' \ # and not c.displayname.endswith(nightwatch_parser_c_header): # parse_assert(False, "Including AvA specifications in other specifications is not yet supported.") elif (normal_mode and c.kind in (CursorKind.MACRO_DEFINITION, CursorKind.STRUCT_DECL, CursorKind.TYPEDEF_DECL) and c.location.file and c.location.file.name == filename): # This is a utility macro for the API forwarding code. type_extents.append((c.extent.start.line, c.extent.end.line)) elif ( # pylint: disable=too-many-boolean-expressions (normal_mode or replacement_mode) and c.kind in (CursorKind.UNEXPOSED_DECL, ) and len(c.tokens) and c.tokens[0].spelling == "extern" and c.location.file in primary_include_files.values()): for cc in c.get_children(): convert_decl(cc) return # Skip the extents processing below elif normal_mode: # Default case for normal mode. is_semicolon = len(c.tokens) == 1 and c.tokens[0].spelling == ";" if c.location.file and not is_semicolon: parse_expects( c.location.file.name != filename, f"Ignoring unsupported: {c.kind} {c.spelling}", loc=convert_location(c.location), ) # if len(c.tokens) >= 1 and c.tokens[0].spelling == "extern" and c.kind == CursorKind.UNEXPOSED_DECL: # print(c.kind, c.tokens[0].spelling) else: # Default case for non-normal modes return # Skip the extents processing below if c.location.file in primary_include_files.values(): primary_include_extents.append( (c.location.file, c.extent.start.line, c.extent.end.line, included_extent))
def parse_UNION_DECL(cursor: Cursor) -> ast.Node: if not cursor.is_definition(): return ast.EhUnion(0, ast.Identifier(0, cursor.spelling), None) return ast.EhUnion(0, ast.Identifier(0, cursor.spelling), _parse_container_structure_fields(cursor))