note description: "Simple Objective-C parser." date: "$Date$" revision: "$Revision$" class OBJC_SIMPLE_PARSER inherit SHARED_CONFIGURATION create make feature {NONE} -- Initialization make -- Initialize Current do create classes.make (0) create protocols.make (0) create current_line.make_empty create types.make (0) create errors.make (0) ensure system_not_parsed: not system_parsed end feature -- Parsing parse_objc_header (a_file: RAW_FILE) -- Parse an Objective-C header. -- This method should be called only once per object due to internal constraints. -- See `system_parsed' boolean attribute in the contracts of `make' and `parse_objc_header'. require a_file_exists: a_file.exists system_not_parsed: not system_parsed local class_name: STRING path_components: LIST [STRING] file_name: STRING executor: EXECUTION_ENVIRONMENT parse_errors: BOOLEAN do if not parse_errors then create executor print("Parsing header (" + a_file.name + ")...%N") -- Preprocess the file file_name := a_file.name path_components := file_name.split ('/') class_name := path_components.i_th (path_components.count).split ('.').i_th (1) executor.system ("gcc -E -F" + configuration.frameworks_path + " " + file_name + " -o " + class_name + ".h") -- Parse preprocessed header create file.make_open_read (class_name + ".h") state := parsing_declarations_state from file.start current_line_number := 0 parse_errors := False until parse_errors or else file.end_of_file loop file.read_line current_line := file.last_string current_line_number := current_line_number + 1 current_position_in_line := 1 inspect state when parsing_declarations_state then -- High level declarations parsing parse_declaration when parsing_class_body_state then -- Class body parsing check current_objective_c_class_not_void: current_objc_class_decl /= Void end parse_class when parsing_optional_protocol_methods, parsing_required_protocol_methods then -- Protocol parsing check current_objective_c_protocol_not_void: current_objc_protocol_decl /= Void end parse_protocol end parse_errors := not errors.is_empty end -- Perform clean up file.close file.delete end -- Check whether there were parsing errors. if not parse_errors then -- Some categories or protocols might be empty because deprecated. Remove them. cleanup_system -- Now that we parsed the full system, update the types. type_system system_parsed := True end ensure system_parsed_iff_no_errors: (errors.is_empty implies system_parsed) and (system_parsed implies errors.is_empty) rescue -- Clear all parsed entities. create classes.make (0) create protocols.make (0) create current_line.make_empty create types.make (0) parse_errors := True retry end feature -- Access system_parsed: BOOLEAN -- Indicate whether the system of classes has been parsed already. classes: HASH_TABLE [OBJC_CLASS_DECL, STRING] -- A representation of classes indexed by their names. protocols: HASH_TABLE [OBJC_PROTOCOL_DECL, STRING] -- A representation of protocols indexed by their names. errors: ARRAYED_LIST [PARSING_ERROR] -- A list containing the parsing errors, if any. feature {NONE} -- Parsing Implementation file: detachable RAW_FILE note option: stable attribute end -- The file that we are currently parsing. types: HASH_TABLE [STRING, STRING] -- Used as a temporary storage for the Objective-C types that need to be resolved. -- Key: Objective-C type name. Example: "NSString *" -- Value: Objective-C type encoding. Example: "@" parse_declaration -- Tries to parse an Objective-C declaration out of the current line. require valid_state: is_parsing_declarations local token: STRING temp_objc_class_decl: OBJC_CLASS_DECL temp_objc_category_decl: detachable OBJC_CATEGORY_DECL parent_protocols_list: ARRAYED_LIST [OBJC_PROTOCOL_DECL] do token := next_token if token.is_equal ({OBJC_TOKEN}.hash_symbol) then -- If we find an inclusion declaration by the preprocessor. if current_line.substring_index (configuration.frameworks_path, 1) > 0 then parse_header_inclusion end elseif token.is_equal ({OBJC_TOKEN}.at_symbol) then -- If we find a possible declaration. token := next_token if token.is_equal ({OBJC_TOKEN}.interface_keyword) then -- If it is a class declaration, -- Parse class name. token := next_token -- Create a temporary symbol to hold the parsed information. check valid_framework: attached current_framework as attached_current_framework not attached_current_framework.is_empty end check valid_header: attached current_header as attached_current_header not attached_current_header.is_empty end create temp_objc_class_decl.make (token, current_framework, current_header) debug ("OBJC_SIMPLE_PARSER_DEBUG") print ("%TParsing class " + token + " (" + current_framework + " framework)%N") end token := next_token if token.is_equal ({OBJC_TOKEN}.colon_symbol) then -- Parse parent class name. token := next_token if attached classes.item (token) as parent_class then temp_objc_class_decl.set_parent_class (parent_class) else add_error_with_message ("Unknown superclass. Make sure the superclass is declared before it is used.") end token := next_token elseif token.is_equal ({OBJC_TOKEN}.open_parenthesis_symbol) then -- Parse category name. create temp_objc_category_decl.make (next_token, temp_objc_class_decl, current_framework, current_header) temp_objc_class_decl.categories.put (temp_objc_category_decl, temp_objc_category_decl.name) token := next_token if not token.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) then add_error_with_token (token) end token := next_token end if token.is_equal ({OBJC_TOKEN}.less_than_symbol) then -- Parse adopting protocols. from token := next_token until token.is_equal ({OBJC_TOKEN}.greater_than_symbol) or else not errors.is_empty loop if attached protocols.item (token) as protocol then -- If we are parsing a category, add the protocols to it. -- Otherwise add them to the current class. if temp_objc_class_decl.categories.count > 0 then check temp_objc_category_decl_not_void: temp_objc_category_decl /= Void end temp_objc_category_decl.protocols.put (protocol, protocol.name) else temp_objc_class_decl.protocols.put (protocol, protocol.name) end else add_error_with_message ("Unknown protocol. Make sure the protocol is declared before it is used.") end token := next_token if token.is_equal ({OBJC_TOKEN}.comma_symbol) then token := next_token elseif not token.is_equal ({OBJC_TOKEN}.greater_than_symbol) then add_error_with_token (token) end end end if attached classes.item (temp_objc_class_decl.name) as existing_class then -- Distinguish two cases: if temp_objc_class_decl.categories.count = 0 then -- This is not a category declaration. -- Moreover, we already parsed this class, therefore we don't have to do -- anything else. state := parsing_declarations_state else -- count > 0. -- This is a category declaration. check temp_objc_category_decl_not_void: temp_objc_category_decl /= Void end if attached existing_class.categories.item (temp_objc_category_decl.name) then -- We already parsed this category declaration. We don't have to do -- anything else. state := parsing_declarations_state else -- We never encountered this category declaration before. Add it to the -- existing class and set the state such that we will parse its methods. existing_class.categories.put (temp_objc_category_decl, temp_objc_category_decl.name) current_objc_category_decl := temp_objc_category_decl state := parsing_class_body_state end end else -- We never encountered this class before, therefore set -- `current_objc_class_decl' to it and set the state such that we will -- parse its methods. current_objc_class_decl := temp_objc_class_decl current_objc_category_decl := Void state := parsing_class_body_state end elseif token.is_equal ({OBJC_TOKEN}.protocol_keyword) then -- If it is a protocol declaration, -- Parse protocol name. token := next_token debug ("OBJC_SIMPLE_PARSER_DEBUG") check current_framework_not_void: current_framework /= Void end check current_header_not_void: current_header /= Void end print ("%TParsing protocol " + token + " (" + current_framework + " framework)%N") end -- Check if the protocol already exists. if attached protocols.item (token) as attached_current_objc_protocol_decl then -- Check if the protocol is declared as a forward reference. if current_line.item (current_line.count).is_equal ({OBJC_TOKEN}.semi_colon_symbol.item (1)) then -- We already parsed this forward reference, do nothing. state := parsing_declarations_state else if attached_current_objc_protocol_decl.is_forward_reference then -- We already parsed a forward reference of this protocol but -- we still need to parse it. current_objc_protocol_decl := attached_current_objc_protocol_decl current_objc_protocol_decl.set_forward_reference (False) check valid_framework: attached current_framework as attached_current_framework not attached_current_framework.is_empty end current_objc_protocol_decl.framework := current_framework parent_protocols_list := parse_parent_protocols_list from parent_protocols_list.start until parent_protocols_list.after loop current_objc_protocol_decl.parent_protocols.put (parent_protocols_list.item, parent_protocols_list.item.name) parent_protocols_list.forth end state := parsing_required_protocol_methods else -- We already parsed this protocol, do nothing. state := parsing_declarations_state end end else -- Check if the protocol is declared as a forward reference. if current_line.item (current_line.count).is_equal ({OBJC_TOKEN}.semi_colon_symbol.item (1)) then -- Parse this protocol forward reference. from until token.is_equal ({OBJC_TOKEN}.semi_colon_symbol) or else not errors.is_empty loop create current_objc_protocol_decl.make (token) current_objc_protocol_decl.set_forward_reference (True) protocols.put (current_objc_protocol_decl, current_objc_protocol_decl.name) token := next_token if token.is_equal ({OBJC_TOKEN}.comma_symbol) then token := next_token end if token.is_empty then add_error_with_token (token) end end state := parsing_declarations_state else -- We never encountered this protocol declaration. Parse it. create current_objc_protocol_decl.make (token) check valid_framework: attached current_framework as attached_current_framework not attached_current_framework.is_empty end current_objc_protocol_decl.framework := current_framework current_objc_protocol_decl.set_forward_reference (False) parent_protocols_list := parse_parent_protocols_list from parent_protocols_list.start until parent_protocols_list.after loop current_objc_protocol_decl.parent_protocols.put (parent_protocols_list.item, parent_protocols_list.item.name) parent_protocols_list.forth end state := parsing_required_protocol_methods end end end end ensure valid_state: is_parsing_declarations or is_parsing_class_body or is_parsing_required_protocol_methods end parse_header_inclusion -- GCC annotates the precompiled header file with comments about the -- origin of the declarations. This procedure parses this information. require valid_state: is_parsing_declarations local token: STRING header_path: STRING splitted_string: LIST [STRING] current_item: STRING do from token := next_token until token.is_equal ({OBJC_TOKEN}.quotation_mark_symbol) or else not errors.is_empty loop token := next_token if token.is_empty then add_error_with_token (token) end end -- Parse header path. from create header_path.make_empty current_position_in_line := current_position_in_line + 1 until current_character.is_equal ({OBJC_TOKEN}.quotation_mark_symbol.item (1)) or else not errors.is_empty loop header_path.extend (current_character) current_position_in_line := current_position_in_line + 1 if current_position_in_line > current_line.count then add_error_with_message ("Unable to parse header path.") end end -- Extract framework and header name. splitted_string := header_path.split ('/') from splitted_string.start until splitted_string.after loop current_item := splitted_string.item if current_item.substring_index (".framework", 1) > 0 then current_framework := current_item.split ('.').i_th (1) elseif current_item.substring_index (".h", 1) > 0 then current_header := current_item end splitted_string.forth end ensure valid_state: is_parsing_declarations end parse_class -- Parse the body of an Objective-C class declaration. require current_objective_c_class_not_void: current_objc_class_decl /= Void valid_state: is_parsing_class_body local current_token: STRING is_class_method, is_instance_method: BOOLEAN return_type: OBJC_TYPE_DECL arguments: ARRAYED_LIST [OBJC_ARGUMENT_DECL] method: OBJC_METHOD_DECL do if not (current_line.substring_index ("__attribute__((", 1) > 0) then current_token := next_token is_class_method := current_token.is_equal ("+") is_instance_method := current_token.is_equal ("-") if is_class_method or is_instance_method then -- Parse the return type. return_type := parse_type -- Parse the argument labels, argument types, argument names. arguments := parse_arguments -- Create the parsed method. create method.make (is_class_method, return_type, arguments) -- Distinguish two cases. if attached current_objc_category_decl as attached_current_objc_category_decl then -- We are parsing the methods of a category declaration. attached_current_objc_category_decl.methods.put (method, method.selector_name) else -- We are parsing the methods of a class declaration. current_objc_class_decl.methods.put (method, method.selector_name) end elseif current_token.is_equal ({OBJC_TOKEN}.at_symbol) then current_token := next_token if current_token.is_equal ({OBJC_TOKEN}.end_keyword) then if current_objc_category_decl = Void then -- Finished parsing this class declaration. -- Add the class in the system if it is not there already. if classes.has (current_objc_class_decl.name) then add_error_with_message ("Duplicate class declaration.") end classes.put (current_objc_class_decl, current_objc_class_decl.name) end state := parsing_declarations_state elseif current_token.is_equal ({OBJC_TOKEN}.property_keyword) then -- Distinguish two cases. if attached parse_property as property then if attached current_objc_category_decl as attached_current_objc_category_decl then -- We are parsing the methods of a category declaration. attached_current_objc_category_decl.properties.put (property, property.name) else -- We are parsing the methods of a class declaration. current_objc_class_decl.properties.put (property, property.name) end end end end end ensure valid_state: is_parsing_declarations or is_parsing_class_body end parse_protocol -- Parse the body of an Objective-C protocol declaration. require current_objective_c_protocol_not_void: current_objc_protocol_decl /= Void valid_state: is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods local token: STRING is_class_method, is_instance_method: BOOLEAN method: OBJC_PROTOCOL_METHOD_DECL return_type: OBJC_TYPE_DECL arguments: ARRAYED_LIST [OBJC_ARGUMENT_DECL] do token := next_token if token.is_empty then -- Do not parse anything. elseif token.is_equal ({OBJC_TOKEN}.at_symbol) then -- We are not parsing a method. token := next_token if token.is_equal ({OBJC_TOKEN}.optional_keyword) then state := parsing_optional_protocol_methods elseif token.is_equal ({OBJC_TOKEN}.required_keyword) then state := parsing_required_protocol_methods elseif token.is_equal ({OBJC_TOKEN}.end_keyword) then -- Finished parsing this protocol declaration. -- Add the protocol in the system if it is not there already. protocols.put (current_objc_protocol_decl, current_objc_protocol_decl.name) state := parsing_declarations_state elseif token.is_equal ({OBJC_TOKEN}.property_keyword) then -- Not supported yet else add_error_with_token (token) end else if not (current_line.substring_index ("__attribute__((", 1) > 0) then is_class_method := token.is_equal ({OBJC_TOKEN}.plus_symbol) is_instance_method := token.is_equal ({OBJC_TOKEN}.minus_symbol) if is_class_method or is_instance_method then -- Parse the return type. return_type := parse_type -- Parse the argument labels, argument types, argument names. arguments := parse_arguments -- Create the parsed method. create method.make (is_class_method, return_type, arguments) method.set_required (state = parsing_required_protocol_methods) current_objc_protocol_decl.methods.put (method, method.selector_name) end end end ensure valid_state: is_parsing_declarations or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods end parse_parent_protocols_list: ARRAYED_LIST [OBJC_PROTOCOL_DECL] -- Parse a list of parent protocols in an Objective-C class or protocol declaration. require valid_state: is_parsing_declarations current_objc_protocol_decl_not_void: current_objc_protocol_decl /= Void local token: STRING do create Result.make (0) token := next_token if token.is_equal ({OBJC_TOKEN}.less_than_symbol) then -- Parse parent protocols. from token := next_token until token.is_equal ({OBJC_TOKEN}.greater_than_symbol) or else not errors.is_empty loop if attached protocols.item (token) as protocol then current_objc_protocol_decl.parent_protocols.put (protocol, protocol.name) else add_error_with_message ("Unkown parent protocol. Make sure to declare it before using it.") end token := next_token if token.is_equal ({OBJC_TOKEN}.comma_symbol) then token := next_token elseif not token.is_equal ({OBJC_TOKEN}.greater_than_symbol) then add_error_with_token (token) end end end ensure valid_state: is_parsing_declarations end parse_type: OBJC_TYPE_DECL -- Parse the type at the current position in the current line. require valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods local current_token: STRING open_parenthesis: NATURAL result_string: STRING old_position_in_line: INTEGER do old_position_in_line := current_position_in_line current_token := next_token if not current_token.is_equal ({OBJC_TOKEN}.open_parenthesis_symbol) then -- Method definitions can omit the type. -- Example: - initWithString:(NSString *)aString; -- When this happens the implicit type is "id". result_string := "id" current_position_in_line := old_position_in_line else from create result_string.make_empty current_token := next_token until open_parenthesis = 0 and current_token.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) or else not errors.is_empty loop if not (current_token.is_equal ({OBJC_TOKEN}.in_keyword) or current_token.is_equal ({OBJC_TOKEN}.inout_keyword) or current_token.is_equal ({OBJC_TOKEN}.out_keyword) or current_token.is_equal ({OBJC_TOKEN}.bycopy_keyword) or current_token.is_equal ({OBJC_TOKEN}.byref_keyword) or current_token.is_equal ({OBJC_TOKEN}.oneway_keyword)) then result_string.append (current_token + " ") -- We have to deal with parenthesis in the case of function pointers. if current_token.is_equal ({OBJC_TOKEN}.open_parenthesis_symbol) then open_parenthesis := open_parenthesis + 1 elseif current_token.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) then open_parenthesis := open_parenthesis - 1 end end current_token := next_token if current_token.is_empty then add_error_with_token ("") end end if result_string.is_empty then add_error_with_message ("No type specified.") end -- Make type prettier. result_string.remove_tail (1) end -- Put the parsed type in a hash table such that we can resolve -- its objective-c encoding later. types.put ("", result_string) create Result.make (result_string) ensure valid_result: not Result.name.is_empty valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods end parse_arguments: ARRAYED_LIST [OBJC_ARGUMENT_DECL] -- Parse arguments of the current method declaration. require valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods local current_argument: OBJC_ARGUMENT_DECL current_token: STRING done: BOOLEAN do from create Result.make (0) -- Parse the argument label. create current_argument.make (next_token) until done or else not errors.is_empty loop current_token := next_token if current_token.is_equal ({OBJC_TOKEN}.semi_colon_symbol) then done := True if Result.is_empty then Result.extend (current_argument) end elseif current_token.is_equal ({OBJC_TOKEN}.colon_symbol) then -- Parse the argument type and name. current_argument.set_type_and_argument_name (parse_type, next_token) Result.extend (current_argument) elseif current_token.is_equal ({OBJC_TOKEN}.comma_symbol) then current_token := next_token if not current_token.is_equal ({OBJC_TOKEN}.dot_symbol) then add_error_with_token (current_token) end current_token := next_token if not current_token.is_equal ({OBJC_TOKEN}.dot_symbol) then add_error_with_token (current_token) end current_token := next_token if not current_token.is_equal ({OBJC_TOKEN}.dot_symbol) then add_error_with_token (current_token) end create current_argument.make ("...") current_argument.is_dotdotdot := True Result.extend (current_argument) else create current_argument.make (current_token) current_token := next_token if not current_token.is_equal ({OBJC_TOKEN}.colon_symbol) then add_error_with_token (current_token) end -- Parse the argument type and name. current_argument.set_type_and_argument_name (parse_type, next_token) Result.extend (current_argument) end end ensure valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods end parse_property: detachable OBJC_PROPERTY_DECL -- Parse property. Return a void result if the property type is a function -- pointer. require valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods local attributes: ARRAYED_LIST [STRING] temp_string: STRING temp_list: LIST [STRING] current_token: STRING name: STRING type_name: STRING getter: detachable STRING setter: detachable STRING readonly: BOOLEAN do current_token := next_token if current_token.is_equal ({OBJC_TOKEN}.open_parenthesis_symbol) then -- Parse property attributes. attributes := parse_property_attributes from attributes.start until attributes.after loop temp_string := attributes.item temp_list := temp_string.split ('=') if temp_string.is_equal ({OBJC_TOKEN}.readonly_keyword) then readonly := True elseif temp_list.i_th (1).is_equal ({OBJC_TOKEN}.getter_keyword) then getter := temp_list.i_th (2) elseif temp_list.i_th (1).is_equal ({OBJC_TOKEN}.setter_keyword) then setter := temp_list.i_th (2) end attributes.forth end current_token := next_token end -- Parse property type. from create {ARRAYED_LIST [STRING]} temp_list.make (0) until current_token.is_equal ({OBJC_TOKEN}.semi_colon_symbol) or else not errors.is_empty loop temp_list.extend (current_token) current_token := next_token if current_token.is_empty then add_error_with_token (current_token) end end if not temp_list.last.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) then -- If we're not parsing a function pointer property. name := temp_list.last temp_list.finish temp_list.remove from temp_list.start create type_name.make_empty until temp_list.after loop type_name.append (temp_list.item + " ") temp_list.forth end type_name.remove_tail (1) types.put ("", type_name) create Result.make (name, create {OBJC_TYPE_DECL}.make (type_name)) if attached getter then Result.getter := getter end if attached setter then Result.setter := setter end Result.readonly := readonly end ensure valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods end parse_property_attributes: ARRAYED_LIST [STRING] -- Parse the attributes of the current Objective-C property declaration. require valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods local current_token: STRING current_attribute: STRING do create Result.make (0) current_token := next_token from until current_token.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) or else not errors.is_empty loop --current_token := next_token from create current_attribute.make_empty until current_token.is_equal ({OBJC_TOKEN}.comma_symbol) or current_token.is_equal ({OBJC_TOKEN}.closed_parenthesis_symbol) or else not errors.is_empty loop current_attribute.append (current_token) current_token := next_token if current_token.is_empty then add_error_with_token (current_token) end end Result.extend (current_attribute) if current_token.is_equal ({OBJC_TOKEN}.comma_symbol) then current_token := next_token end end ensure valid_state: is_parsing_class_body or is_parsing_required_protocol_methods or is_parsing_optional_protocol_methods end cleanup_system -- Some categories or protocols might be empty because deprecated. -- Remove them. require system_not_parsed: not system_parsed local current_categories: HASH_TABLE [OBJC_CATEGORY_DECL, STRING] categories_to_remove: ARRAYED_LIST [OBJC_CATEGORY_DECL] do from classes.start until classes.after loop create categories_to_remove.make (0) current_categories := classes.item_for_iteration.categories -- Find the useless categories. from current_categories.start until current_categories.after loop if current_categories.item_for_iteration.methods.count = 0 then categories_to_remove.extend (current_categories.item_for_iteration) end current_categories.forth end -- Remove the useless categories. from categories_to_remove.start until categories_to_remove.after loop current_categories.remove (categories_to_remove.item.name) categories_to_remove.forth end classes.forth end end type_system -- After parsing, the system has temporary OBJC_TYPE_DECL types. This procedure replaces them -- with the actual types (OBJC_BASIC_TYPE_DECL, OBJC_POINTER_TYPE_DECL and OBJC_STRUCT_TYPE_DECL). require system_not_parsed: not system_parsed local typing_visitor: OBJC_TYPING_VISITOR do get_type_encodings create typing_visitor.make (types) -- Type the classes. from classes.start until classes.after loop classes.item_for_iteration.accept (typing_visitor) classes.forth end -- Type the protocols. from protocols.start until protocols.after loop protocols.item_for_iteration.accept (typing_visitor) protocols.forth end end feature {NONE} -- Implementation current_objc_class_decl: detachable OBJC_CLASS_DECL note option: stable attribute end -- The current Objective-C class declaration being parsed. current_objc_category_decl: detachable OBJC_CATEGORY_DECL -- The current Objective-C category declaration being parsed. current_objc_protocol_decl: detachable OBJC_PROTOCOL_DECL note option: stable attribute end -- The current Objective-C protocol declaration being parsed. current_framework: detachable STRING note option: stable attribute end -- The current Objective-C framework we are parsing the declarations from. current_header: detachable STRING note option: stable attribute end -- The current Objective-C header file we are parsing the declarations from. state: INTEGER -- The current parsing state. add_error_with_token (a_token: STRING) -- Add an error to `errors' with the specified token. do check file_not_void: file /= Void end errors.extend (create {PARSING_ERROR}.make_with_token (file, current_line, current_line_number, current_position_in_line, a_token)) ensure errors_has_one_more_item: errors.count = old errors.count + 1 end add_error_with_message (a_message: STRING) -- Add an error to `errors' with the specified message require a_valid_message: not a_message.is_empty do check file_not_void: file /= Void end errors.extend (create {PARSING_ERROR}.make_with_message (file, current_line, current_line_number, current_position_in_line, a_message)) ensure errors_has_one_more_item: errors.count = old errors.count + 1 end current_line: STRING -- The current line being parsed. current_line_number: INTEGER -- The number of the line being parsed. current_position_in_line: INTEGER -- The current position in the current line being parsed. current_character: CHARACTER -- The character at the current position in the current line being parsed. require valid_current_position: current_position_in_line >= 1 and current_position_in_line <= current_line.count do Result := current_line [current_position_in_line] end end_of_line: BOOLEAN -- Is `current_position_in_line' equal to `curent_line.count' + 1? do Result := current_position_in_line = current_line.count + 1 end next_token: STRING -- Return the next Objective-C token in the current line starting at the current position. -- A token can either be an identifier (i.e. a word composed by letters, numbers and the "_" -- symbol with the only exception that the first character cannot be a number) or a symbol. require system_not_parsed: not system_parsed do create Result.make_empty -- Ignore whitespaces. from until end_of_line or else not (current_character.is_equal ({OBJC_TOKEN}.space_character) or current_character.is_equal ({OBJC_TOKEN}.tab_character)) loop current_position_in_line := current_position_in_line + 1 end if not end_of_line then -- If we're parsing an identifier. if current_character.is_alpha or current_character.is_equal ({OBJC_TOKEN}.underscore_symbol) then Result.append_character (current_character) current_position_in_line := current_position_in_line + 1 from until end_of_line or else not (current_character.is_alpha or current_character.is_digit or current_character.is_equal ({OBJC_TOKEN}.underscore_symbol)) loop Result.append_character (current_character) current_position_in_line := current_position_in_line + 1 end else -- If we're parsing a symbol. Result.append_character (current_character) current_position_in_line := current_position_in_line + 1 end end end feature {NONE} -- Parsing State Constants parsing_declarations_state: INTEGER = 0 -- Parsing declarations state. parsing_class_body_state: INTEGER = 1 -- Parsing class body state. parsing_required_protocol_methods: INTEGER = 2 -- Parsing required protocol methods state. parsing_optional_protocol_methods: INTEGER = 3 -- Parsing optional protocol methods state. feature {NONE} -- Status Report is_parsing_declarations: BOOLEAN -- Is the parser parsing declarations? do Result := state = parsing_declarations_state end is_parsing_class_body: BOOLEAN -- Is the parser parsing a class body? do Result := state = parsing_class_body_state end is_parsing_required_protocol_methods: BOOLEAN -- Is the parser parsing required protocol methods? do Result := state = parsing_required_protocol_methods end is_parsing_optional_protocol_methods: BOOLEAN -- Is the parser parsing optional protocol methods? do Result := state = parsing_optional_protocol_methods end feature {NONE} -- Objective C Support get_type_encodings -- Update the `types' hash table with the type encodings. require system_not_parsed: not system_parsed local lib_source_file_name: STRING framework_name: STRING a_file: RAW_FILE executor: EXECUTION_ENVIRONMENT managed_pointer: MANAGED_POINTER c_string: C_STRING i: INTEGER do lib_source_file_name := "get_type_encodings.m" create a_file.make (lib_source_file_name) if a_file.exists then a_file.delete end create a_file.make_create_read_write (lib_source_file_name) framework_name := configuration.framework_name a_file.put_string ("#import <" + framework_name + "/" + framework_name + ".h>%N") a_file.put_string ("char *encodings[] = {%N"); from types.start until types.after loop a_file.put_string ("%T@encode(" + types.key_for_iteration + ")") types.forth if not types.after then a_file.put_string (",") end a_file.put_string ("%N") end a_file.put_string ("};%N") a_file.put_string ("char** get_type_encodings() {%N") a_file.put_string ("%Treturn encodings;%N") a_file.put_string ("}") a_file.close -- Compile the library. create executor executor.system ("gcc -dynamiclib -std=gnu99 get_type_encodings.m -o get_type_encodings.dylib") --a_file.delete -- Get the encodings. create managed_pointer.share_from_pointer (c_get_type_encodings, types.count * {PLATFORM}.pointer_bytes) from i := 0 types.start until i = types.count and types.after loop create c_string.make_shared_from_pointer (managed_pointer.read_pointer (i * {PLATFORM}.pointer_bytes)) types.force (c_string.string, types.key_for_iteration) i := i + 1 types.forth end end feature {NONE} -- Externals c_get_type_encodings: POINTER -- Load the shared dynamic library and return a pointer to an array of the -- Objective-C type encodings. external "C inline use ," alias "[ void *lib_handle = dlopen("get_type_encodings.dylib", RTLD_NOW); char** (*get_type_encodings)(void) = dlsym(lib_handle, "get_type_encodings"); return get_type_encodings(); ]" end end