Last active
July 23, 2016 22:32
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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