An Objective-C parser implemented in Swift using ANTLR4 - extract and analyze Objective-C code structures directly from Swift.
ObjcParser provides powerful tools to parse and analyze Objective-C code directly within Swift applications. Built on ANTLR4's grammar system, it leverages a two-stage parsing process (handling preprocessor directives first, then the main Objective-C syntax) to enable:
- Swift Migration: Extract Objective-C interface definitions (classes, protocols, enums, type aliases, etc.) for migration to Swift.
- Static Analysis: Identify usage patterns, dependencies, and unused imports by deeply inspecting the code structure.
- Code Generation: Transform Objective-C structures into Swift equivalents or other formats.
- Preprocessor Awareness: Access information from preprocessor directives like
#import,#define, and conditional compilation blocks.
For a detailed technical deep dive, check out my article: Parse Objective-C Syntax with Swift
- Comprehensive Parsing: Handles a wide range of Objective-C syntax, including preprocessor directives.
- Swift-Native: Built in Swift for seamless integration into Swift-based tools and workflows.
- ANTLR4 Powered: Utilizes the robust ANTLR4 parser generator for accurate and efficient parsing.
- Interface Extraction: Specifically designed to extract key interface elements like classes, protocols, enums, and type aliases.
- Command-Line Tool: Includes a CLI (
objc-interface-extractor-cli) for easy extraction of interface definitions from Objective-C header files.
ObjcParser employs a two-stage parsing process:
- Preprocessor Parsing: It first uses
ObjectiveCPreprocessorLexer.g4andObjectiveCPreprocessorParser.g4to handle directives like#import,#define, and conditional compilation. This step gathers information about imports and other preprocessor-specific constructs. - Objective-C Syntax Parsing: After the preprocessor pass, the main Objective-C code is parsed using
ObjectiveCLexer.g4andObjectiveCParser.g4. This stage builds a detailed parse tree of the Objective-C syntax.
This approach allows for a comprehensive understanding of the Objective-C code, including elements that are typically handled by the preprocessor before compilation.
- Swift 5.0+
- Python 3.10+ (for ANTLR4 tools)
# Install ANTLR4 tools
pip install antlr4-toolscd ObjcParser
swift run objc-interface-extractor-cli ObjCSamples/Demo.hSample output:
{
"nsOptions" : [
{
"primitiveType" : "NSUInteger",
"name" : "DirectionOptions"
}
],
"classes" : [
{
"name" : "Vehicle"
},
{
"name" : "Car"
}
],
"typeAliases" : [
{
"name" : "SizeDimensions",
"type" : "struct{floatwidth;floatheight;floatdepth;}"
},
{
"name" : "LocationCoordinate",
"type" : "struct{doublelatitude;doublelongitude;doublealtitude;}"
}
],
"protocols" : [
{
"name" : "LocationTracking"
},
{
"name" : "Drivable"
}
],
"nsEnums" : [
{
"primitiveType" : "NSInteger",
"name" : "VehicleType"
}
]
}Beyond the CLI, you can integrate ObjcParser directly into your Swift projects for more customized parsing and analysis.
The parser relies on the following ANTLR4 grammar files (located in the ObjCGrammar directory or a similar location if you set it up manually):
ObjectiveCLexer.g4: Defines tokens for the Objective-C language.ObjectiveCParser.g4: Defines the grammatical structure of Objective-C.ObjectiveCPreprocessorLexer.g4: Defines tokens for preprocessor directives.ObjectiveCPreprocessorParser.g4: Defines the grammar for preprocessor directives.
If you are setting up the parser manually or modifying the grammars, you'll need to generate the Swift parser code from the .g4 files. Ensure you have the ANTLR4 tool installed (pip install antlr4-tools).
Navigate to the directory containing your .g4 files and run:
antlr4 -Dlanguage=Swift ObjectiveCLexer.g4 ObjectiveCParser.g4 ObjectiveCPreprocessorLexer.g4 ObjectiveCPreprocessorParser.g4This will generate the corresponding .swift files (e.g., ObjectiveCLexer.swift, ObjectiveCParser.swift). These generated files are already included in the ObjCGrammar Swift package if you are using this project as a dependency.
Here's a basic example of how to use the parser in your Swift code:
import Foundation
import ObjCGrammar // Your Swift package for the ANTLR-generated code
import Antlr4
public class MyObjCAnalyzer {
public init() {}
public func parse(filePath: String) throws {
let charStream = try ANTLRFileStream(filePath)
// 1. Preprocessor Pass
let preprocessorLexer = ObjectiveCPreprocessorLexer(charStream)
let preprocessorTokenStream = CommonTokenStream(preprocessorLexer)
let preprocessorParser = try ObjectiveCPreprocessorParser(preprocessorTokenStream)
let preprocessorDocument = try preprocessorParser.objectiveCDocument()
// Extract preprocessor info, e.g., imports
for textElement in preprocessorDocument.text() {
guard let directive = textElement.directive() else {
continue
}
switch directive {
case let ctx as ObjectiveCPreprocessorParser.PreprocessorImportContext:
if ctx.IMPORT() != nil, let importSubject = ctx.directive_text()?.getText() {
print("Found import: \(importSubject)")
// Handle import: e.g., ctx.FILENAME()?.getText(), ctx.framework_name()?.getText()
}
// Add other preprocessor directive handling as needed
default:
break
}
}
// Rewind charStream for the main parser
charStream.reset() // Or use preprocessorTokenStream.seek(0) if using a token stream rewriter approach
// 2. Main Objective-C Syntax Pass
let lexer = ObjectiveCLexer(charStream)
let tokenStream = CommonTokenStream(lexer)
let parser = try ObjectiveCParser(tokenStream)
let translationUnit = try parser.translationUnit() // Top-level parse tree node
// Now you can traverse the translationUnit to analyze the Objective-C code
// For example, to find class interface names:
// let listener = MyClassInterfaceListener()
// try ParseTreeWalker.DEFAULT.walk(listener, translationUnit)
// print(listener.classNames)
}
}
// Example listener (you would define this based on your needs)
/*
class MyClassInterfaceListener: ObjectiveCParserBaseListener {
var classNames: [String] = []
override func enterClassInterface(_ ctx: ObjectiveCParser.ClassInterfaceContext) {
if let className = ctx.className()?.getText() {
classNames.append(className)
}
}
}
*/As shown in the example above, ObjcParser handles preprocessor directives like #import, #define, and conditional compilation (#if, #ifdef, etc.) by first parsing them with ObjectiveCPreprocessorParser. This allows you to access information from these directives before parsing the main Objective-C syntax.
The CollectorTokenSource (found in Sources/ObjCParser/CollectorTokenSource.swift) can be an alternative approach to manage tokens from different channels (e.g., main vs. hidden/preprocessor) if you need finer-grained control over the token stream passed to the main parser after preprocessor analysis.
Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests.
This project is licensed under the MIT License - see the LICENSE file for details.