Skip to content

Instantly share code, notes, and snippets.

@NSExceptional
Last active July 23, 2016 22:32
Show Gist options
  • Save NSExceptional/71cd9e4bfd9e4c588fee32f6d5c2f47a to your computer and use it in GitHub Desktop.
Save NSExceptional/71cd9e4bfd9e4c588fee32f6d5c2f47a to your computer and use it in GitHub Desktop.
Building a basic parser with NSScanner. In this example, to parse Objective-C interface and protocol definitions.
import Cocoa
public class Model {
public init() {}
public var className: NSString?
public var superclassName: NSString?
public var categoryname: NSString?
public var protocolName: NSString?
public var conformedProtocols: [NSString]?
public var instanceVariables: [NSString]?
public var description: String {
var desc = ""
if let className = className {
desc.appendContentsOf("Class name: " + (className as String))
if let categoryname = categoryname {
desc.appendContentsOf("\nCategory name: " + (categoryname as String))
} else if let superclassName = superclassName {
desc.appendContentsOf("\nSuperclass: " + (superclassName as String))
}
} else if let protocolName = protocolName {
desc.appendContentsOf("Protocol name: " + (protocolName as String))
}
if let instanceVariables = instanceVariables {
desc.appendContentsOf("\nIVars: \(instanceVariables)")
}
if let conformedProtocols = conformedProtocols {
desc.appendContentsOf("\nConforms to: \(conformedProtocols)")
}
return desc
}
}
Class name: Foo
Superclass: NSObject
IVars: [int count]
Class name: NSObject
IVars: [float pi]
Class name: NSObject
Category name: Category
Protocol name: Foo
Conforms to: [Bar]
Protocol name: Foo
Conforms to: [Bar, Baz]
"@interface Foo : @end" is not a valid protocol or interface.
"@interface NSObject { float pi } @end" is not a valid protocol or interface.
"@interface NSObject (Invalid category name) @end" is not a valid protocol or interface.
"@protocol Foo <Bar @end" is not a valid protocol or interface.
"@protocol Foo <Bar, Baz, > @end" is not a valid protocol or interface.
import Cocoa
enum Errors: ErrorType {
case MalformedProtocolConformance
}
var nonAlphaNumericSet = NSCharacterSet.alphanumericCharacterSet().invertedSet
func tryScan(scanner: NSScanner) -> Model? {
let interface = Model()
if scanner.scanString("@interface", intoString: nil) {
// Scan class name
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &interface.className) {
return nil
}
if scanner.scanString(":", intoString: nil) {
// Scan superclass name
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &interface.superclassName) {
return nil
}
} else if scanner.scanString("(", intoString: nil) {
// Scan category name
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &interface.categoryname) ||
!scanner.scanString(")", intoString: nil) {
return nil
}
}
// Conformed protocols
do {
interface.conformedProtocols = try tryScanProtocols(scanner)
} catch {
return nil
}
// Instance variables
if scanner.scanString("{", intoString: nil) {
var ivars = [NSString]()
var ivarType, ivarName: NSString?
while scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &ivarType) {
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &ivarName) {
print("No ivar name")
return nil
}
ivars.append("\(ivarType!) \(ivarName!)")
if !scanner.scanString(";", intoString: nil) {
return nil
}
}
if !scanner.scanString("}", intoString: nil) {
return nil
}
interface.instanceVariables = ivars
}
// Skip to the @end
if !scanner.scanString("@end", intoString: nil) && !scanner.scanUpToString("@end", intoString: nil) {
return nil
}
return interface
} else if scanner.scanString("@protocol", intoString: nil) {
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &interface.protocolName) {
return nil
}
// Conformed protocols
do {
interface.conformedProtocols = try tryScanProtocols(scanner)
} catch {
return nil
}
// Skip to the @end
if !scanner.scanString("@end", intoString: nil) && !scanner.scanUpToString("@end", intoString: nil) {
return nil
}
return interface
}
return nil
}
func tryScanProtocols(scanner: NSScanner) throws -> [NSString]? {
if scanner.scanString("<", intoString: nil) {
var conformed = [NSString]()
var proto: NSString? = nil
repeat {
if !scanner.scanUpToCharactersFromSet(nonAlphaNumericSet, intoString: &proto) {
throw Errors.MalformedProtocolConformance
}
conformed.append(proto!)
} while scanner.scanString(",", intoString: nil)
// Must end with >
if !scanner.scanString(">", intoString: nil) {
throw Errors.MalformedProtocolConformance
}
return conformed
}
return nil
}
var cls = "@interface Foo : NSObject { int count; } @end"
var root = "@interface NSObject { float pi; } @end"
var cat = "@interface NSObject (Category) @end"
var prot1 = "@protocol Foo <Bar> @end"
var prot2 = "@protocol Foo <Bar, Baz> @end"
var ncls = "@interface Foo : @end"
var nroot = "@interface NSObject { float pi } @end"
var ncat = "@interface NSObject (Invalid category name) @end"
var nprot1 = "@protocol Foo <Bar @end"
var nprot2 = "@protocol Foo <Bar, Baz, > @end"
var tests = [cls, root, cat, prot1, prot2]
var invalid = [ncls, nroot, ncat, nprot1, nprot2]
for list in [tests, invalid] {
for test in list {
var scanner = NSScanner(string: test)
if let interface = tryScan(scanner) {
print(interface.description + "\n")
} else {
print("\"\(test)\" is not a valid protocol or interface.\n")
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment