From 6699fedd051b86b7a58a01762e4f0209332a81ef Mon Sep 17 00:00:00 2001 From: Ewan Mellor Date: Thu, 31 Jul 2014 18:01:04 -0700 Subject: [PATCH] Add a command-line system to ParserGenApp. This extends ParserGenApp so that it can be run with command line arguments, in which case it does not show the configuration window, but it just runs the parser generator instead. This moves some of the generation code out of PGDocument and into a new PGGenerator. (It gets ARCified and tidied up at the same time.) --- PEGKit.xcodeproj/project.pbxproj | 12 +++ ParserGenApp/PGCLI.h | 24 +++++ ParserGenApp/PGCLI.m | 117 ++++++++++++++++++++++++ ParserGenApp/PGDocument.m | 90 +++--------------- ParserGenApp/PGGenerator.h | 30 ++++++ ParserGenApp/PGGenerator.m | 151 +++++++++++++++++++++++++++++++ ParserGenApp/main.m | 9 ++ 7 files changed, 356 insertions(+), 77 deletions(-) create mode 100644 ParserGenApp/PGCLI.h create mode 100644 ParserGenApp/PGCLI.m create mode 100644 ParserGenApp/PGGenerator.h create mode 100644 ParserGenApp/PGGenerator.m diff --git a/PEGKit.xcodeproj/project.pbxproj b/PEGKit.xcodeproj/project.pbxproj index d286c76..b7b78ad 100644 --- a/PEGKit.xcodeproj/project.pbxproj +++ b/PEGKit.xcodeproj/project.pbxproj @@ -8,6 +8,8 @@ /* Begin PBXBuildFile section */ 3D0466A918E1D9770022A1BC /* OCMock.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = D37214CA18DF3B0100525058 /* OCMock.framework */; }; + 40CA4045198C3E8B0094DF1F /* PGCLI.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CA4044198C3E8B0094DF1F /* PGCLI.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; + 40CA4048198C54AB0094DF1F /* PGGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 40CA4047198C54AB0094DF1F /* PGGenerator.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; }; D306298218E1ED5D00EF745E /* TDTestScaffold.m in Sources */ = {isa = PBXBuildFile; fileRef = D306298118E1ED5D00EF745E /* TDTestScaffold.m */; }; D3083AB61705F05C00DA6F95 /* elementsAssign.grammar in Resources */ = {isa = PBXBuildFile; fileRef = D3083AB51705F05C00DA6F95 /* elementsAssign.grammar */; }; D3083AB91705F09B00DA6F95 /* ElementAssignParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D3083AB81705F09B00DA6F95 /* ElementAssignParserTest.m */; }; @@ -478,6 +480,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 40CA4044198C3E8B0094DF1F /* PGCLI.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PGCLI.m; sourceTree = ""; }; + 40CA4046198C3F4C0094DF1F /* PGCLI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PGCLI.h; sourceTree = ""; }; + 40CA4047198C54AB0094DF1F /* PGGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = PGGenerator.m; sourceTree = ""; }; + 40CA4049198C55100094DF1F /* PGGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PGGenerator.h; sourceTree = ""; }; D302272A17020F9400594F16 /* PGClassImplementationTemplate.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = PGClassImplementationTemplate.txt; path = res/PGClassImplementationTemplate.txt; sourceTree = ""; }; D306298118E1ED5D00EF745E /* TDTestScaffold.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = TDTestScaffold.m; path = test/TDTestScaffold.m; sourceTree = ""; }; D3083AB51705F05C00DA6F95 /* elementsAssign.grammar */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = elementsAssign.grammar; path = res/elementsAssign.grammar; sourceTree = ""; }; @@ -1132,9 +1138,13 @@ isa = PBXGroup; children = ( D3383049171C923700CCE513 /* PGMainMenu.xib */, + 40CA4046198C3F4C0094DF1F /* PGCLI.h */, + 40CA4044198C3E8B0094DF1F /* PGCLI.m */, D3383043171C923700CCE513 /* PGDocument.h */, D3383044171C923700CCE513 /* PGDocument.m */, D3383046171C923700CCE513 /* PGDocument.xib */, + 40CA4049198C55100094DF1F /* PGGenerator.h */, + 40CA4047198C54AB0094DF1F /* PGGenerator.m */, D3B22A571703D03F00446945 /* PGTemplates */, D3A1492A16F8C79600770DEE /* visitor */, D325FFBC161E4E3200D4EBCC /* ast */, @@ -1899,6 +1909,7 @@ files = ( D376F6E318D0B5090064C888 /* PEGKitParser.m in Sources */, D376F6D018D0B3990064C888 /* PGDelimitedNode.m in Sources */, + 40CA4045198C3E8B0094DF1F /* PGCLI.m in Sources */, D338303E171C923700CCE513 /* main.m in Sources */, D376F6D418D0B3990064C888 /* PGMultipleNode.m in Sources */, D376F6CC18D0B3990064C888 /* PGConstantNode.m in Sources */, @@ -1912,6 +1923,7 @@ D376F6DF18D0B5020064C888 /* PGParserFactory.m in Sources */, D376F6DA18D0B3990064C888 /* PGReferenceNode.m in Sources */, D366C1AD1A5310F200D69669 /* PGNegationNode.m in Sources */, + 40CA4048198C54AB0094DF1F /* PGGenerator.m in Sources */, D376F6EE18D0B5190064C888 /* PGBaseVisitor.m in Sources */, D376F6DC18D0B3990064C888 /* PGRootNode.m in Sources */, D376F6C818D0B3990064C888 /* PGCollectionNode.m in Sources */, diff --git a/ParserGenApp/PGCLI.h b/ParserGenApp/PGCLI.h new file mode 100644 index 0000000..1393a65 --- /dev/null +++ b/ParserGenApp/PGCLI.h @@ -0,0 +1,24 @@ +// +// PGCLI.h +// PEGKit +// +// Created by Ewan Mellor on 7/29/14. +// +// + +#import + + +@interface PGCLI : NSObject + +/** + * @return true if there are command-line arguments that this class can handle. + */ +-(BOOL)willHandleCommandLine; + +/** + * @return The value that should be returned from main. + */ +-(int)handleCommandLine; + +@end diff --git a/ParserGenApp/PGCLI.m b/ParserGenApp/PGCLI.m new file mode 100644 index 0000000..408cf54 --- /dev/null +++ b/ParserGenApp/PGCLI.m @@ -0,0 +1,117 @@ +// +// PGCLI.m +// PEGKit +// +// Created by Ewan Mellor on 7/29/14. +// +// + +#import "PGGenerator.h" + +#import "PGCLI.h" + + +#define nsfprintf(__fp, ...) \ + fprintf(__fp, "%s\n", [[NSString stringWithFormat:__VA_ARGS__] UTF8String]) + + +@interface PGCLI () + +@property (nonatomic) PGGenerator * generator; +@property (nonatomic) NSString * grammarFile; + +@end + + +@implementation PGCLI + + +-(instancetype)init { + self = [super init]; + if (self) { + _generator = [[PGGenerator alloc] init]; + } + return self; +} + + +-(BOOL)willHandleCommandLine { + [self parseArgs]; + + return (self.grammarFile != nil); +} + + +-(int)handleCommandLine { + if (self.generator.destinationPath.length == 0 || self.grammarFile.length == 0) { + nsfprintf(stderr, + @"\n" + @"Usage:\n" + @"\n" + @" ParserGenApp -grammar -destPath \n" + @"\n" + @"Options are any combination of:\n" + @" -parserName \n" + @" -enableARC 1\n" + @" -enableAutomaticErrorRecovery 1\n" + @" -enableHybridDFA 1\n" + @" -enableMemoization 1\n"); + return 1; + } + + BOOL ok = [self loadGrammar]; + if (!ok) { + return 1; + } + + ok = [self generate]; + if (!ok) { + return 1; + } + + return 0; +} + + +-(void)parseArgs { + NSUserDefaults * ud = [NSUserDefaults standardUserDefaults]; + + self.grammarFile = [ud stringForKey:@"grammar"]; + + self.generator.destinationPath = [ud stringForKey:@"destPath"]; + if (self.generator.destinationPath == nil) { + self.generator.destinationPath = [ud stringForKey:@"destinationPath"]; + } + self.generator.enableARC = [ud boolForKey:@"enableARC"]; + self.generator.enableAutomaticErrorRecovery = [ud boolForKey:@"enableAutomaticErrorRecovery"]; + self.generator.enableHybridDFA = [ud boolForKey:@"enableHybridDFA"]; + self.generator.enableMemoization = [ud boolForKey:@"enableMemoization"]; + self.generator.parserName = [ud stringForKey:@"parserName"]; +} + + +-(BOOL)loadGrammar { + NSString * path = [self.grammarFile stringByExpandingTildeInPath]; + NSError * err = nil; + NSString * grammar = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&err]; + if (grammar == nil) { + nsfprintf(stderr, NSLocalizedString(@"Failed to load grammar: %@", nil), [err localizedDescription]); + return NO; + } + else { + self.generator.grammar = grammar; + return YES; + } +} + + +-(BOOL)generate { + bool ok = [self.generator generate]; + if (!ok) { + nsfprintf(stderr, NSLocalizedString(@"Failed to generate: %@", nil), [self.generator.error localizedDescription]); + } + return ok; +} + + +@end diff --git a/ParserGenApp/PGDocument.m b/ParserGenApp/PGDocument.m index 4f1a243..2d1acab 100644 --- a/ParserGenApp/PGDocument.m +++ b/ParserGenApp/PGDocument.m @@ -23,21 +23,13 @@ #import "PGDocument.h" //#import #import "PGParserGenVisitor.h" - -@interface PGDocument () -@property (nonatomic, retain) PGParserFactory *factory; -@property (nonatomic, retain) PGRootNode *root; -@property (nonatomic, retain) PGParserGenVisitor *visitor; -@end +#import "PGGenerator.h" @implementation PGDocument - (instancetype)init { self = [super init]; if (self) { - self.factory = [PGParserFactory factory]; - _factory.collectTokenKinds = YES; - self.enableARC = YES; self.enableHybridDFA = YES; self.enableMemoization = YES; @@ -63,9 +55,6 @@ - (void)dealloc { self.textView = nil; - self.factory = nil; - self.root = nil; - self.visitor = nil; [super dealloc]; } @@ -154,7 +143,18 @@ - (IBAction)generate:(id)sender { self.error = nil; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [self generateWithDestinationPath:destPath parserName:parserName grammar:grammar]; + PGGenerator * generator = [[PGGenerator alloc] init]; + generator.destinationPath = destPath; + generator.parserName = parserName; + generator.grammar = grammar; + generator.enableARC = self.enableARC; + generator.enableAutomaticErrorRecovery = self.enableAutomaticErrorRecovery; + generator.enableHybridDFA = self.enableHybridDFA; + generator.enableMemoization = self.enableMemoization; + [generator generate]; + dispatch_async(dispatch_get_main_queue(), ^(void){ + [self done]; + }); }); } @@ -223,70 +223,6 @@ - (IBAction)reveal:(id)sender { #pragma mark Private -- (void)generateWithDestinationPath:(NSString *)destPath parserName:(NSString *)parserName grammar:(NSString *)grammar { - NSError *err = nil; - self.root = (id)[_factory ASTFromGrammar:_grammar error:&err]; - if (err) { - self.error = err; - goto done; - } - - NSAssert([_root.startMethodName length], @""); - NSString *className = self.parserName; - NSAssert([className length], @""); - if (![className hasSuffix:@"Parser"]) { - className = [NSString stringWithFormat:@"%@Parser", className]; - } - - _root.grammarName = self.parserName; - - self.visitor = [[[PGParserGenVisitor alloc] init] autorelease]; - _visitor.enableARC = _enableARC; - _visitor.enableHybridDFA = _enableHybridDFA; //NSAssert(_enableHybridDFA, @""); - _visitor.enableMemoization = _enableMemoization; - _visitor.enableAutomaticErrorRecovery = _enableAutomaticErrorRecovery; - _visitor.delegatePreMatchCallbacksOn = _delegatePreMatchCallbacksOn; - _visitor.delegatePostMatchCallbacksOn = _delegatePostMatchCallbacksOn; - - @try { - [_root visit:_visitor]; - } - @catch (NSException *ex) { - id userInfo = @{NSLocalizedFailureReasonErrorKey: [ex reason]}; - NSError *err = [NSError errorWithDomain:[ex name] code:0 userInfo:userInfo]; - self.error = err; - goto done; - } - - NSString *path = [[NSString stringWithFormat:@"%@/%@.h", destPath, className] stringByExpandingTildeInPath]; - err = nil; - if (![_visitor.interfaceOutputString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&err]) { - NSMutableString *str = [NSMutableString stringWithString:[err localizedFailureReason]]; - [str appendFormat:@"\n\n%@", [path stringByDeletingLastPathComponent]]; - id dict = [NSMutableDictionary dictionaryWithDictionary:[err userInfo]]; - dict[NSLocalizedFailureReasonErrorKey] = str; - self.error = [NSError errorWithDomain:[err domain] code:[err code] userInfo:dict]; - goto done; - } - - path = [[NSString stringWithFormat:@"%@/%@.m", destPath, className] stringByExpandingTildeInPath]; - err = nil; - if (![_visitor.implementationOutputString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&err]) { - NSMutableString *str = [NSMutableString stringWithString:[err localizedFailureReason]]; - [str appendFormat:@"\n\n%@", [path stringByDeletingLastPathComponent]]; - id dict = [NSMutableDictionary dictionaryWithDictionary:[err userInfo]]; - dict[NSLocalizedFailureReasonErrorKey] = str; - self.error = [NSError errorWithDomain:[err domain] code:[err code] userInfo:dict]; - goto done; - } - -done: - dispatch_async(dispatch_get_main_queue(), ^(void){ - [self done]; - }); -} - - - (void)done { if (_error) { [[NSSound soundNamed:@"Basso"] play]; diff --git a/ParserGenApp/PGGenerator.h b/ParserGenApp/PGGenerator.h new file mode 100644 index 0000000..640c21e --- /dev/null +++ b/ParserGenApp/PGGenerator.h @@ -0,0 +1,30 @@ +// +// PGGenerator.h +// PEGKit +// +// Created by Ewan Mellor on 8/1/14. +// +// + +#import + +#import "PGParserFactory.h" + + +@interface PGGenerator : NSObject + +@property (nonatomic, strong) NSString * destinationPath; +@property (nonatomic, assign) BOOL enableARC; +@property (nonatomic, assign) BOOL enableAutomaticErrorRecovery; +@property (nonatomic, assign) BOOL enableHybridDFA; +@property (nonatomic, assign) BOOL enableMemoization; +@property (nonatomic, strong) NSString * parserName; +@property (nonatomic, strong) NSString * grammar; +@property (nonatomic, assign) PGParserFactoryDelegateCallbacksOn delegatePreMatchCallbacksOn; +@property (nonatomic, assign) PGParserFactoryDelegateCallbacksOn delegatePostMatchCallbacksOn; + +@property (nonatomic, strong) NSError * error; + +-(BOOL)generate; + +@end diff --git a/ParserGenApp/PGGenerator.m b/ParserGenApp/PGGenerator.m new file mode 100644 index 0000000..4684498 --- /dev/null +++ b/ParserGenApp/PGGenerator.m @@ -0,0 +1,151 @@ +// +// PGGenerator.m +// PEGKit +// +// Created by Ewan Mellor on 7/29/14. +// +// + +#import "PGParserGenVisitor.h" +#import "PGRootNode.h" + +#import "PGGenerator.h" + + +@interface PGGenerator () + +@property (nonatomic) PGParserFactory * factory; +@property (nonatomic) PGRootNode * root; +@property (nonatomic) PGParserGenVisitor * visitor; + +@end + + +@implementation PGGenerator + + +-(instancetype)init { + self = [super init]; + if (self) { + _factory = [PGParserFactory factory]; + _factory.collectTokenKinds = YES; + + _visitor = [[PGParserGenVisitor alloc] init]; + } + return self; +} + + +-(PGParserFactoryDelegateCallbacksOn)delegatePreMatchCallbacksOn { + return self.visitor.delegatePreMatchCallbacksOn; +} + +-(void)setDelegatePreMatchCallbacksOn:(PGParserFactoryDelegateCallbacksOn)delegatePreMatchCallbacksOn { + self.visitor.delegatePreMatchCallbacksOn = delegatePreMatchCallbacksOn; +} + +-(PGParserFactoryDelegateCallbacksOn)delegatePostMatchCallbacksOn { + return self.visitor.delegatePostMatchCallbacksOn; +} + +-(void)setDelegatePostMatchCallbacksOn:(PGParserFactoryDelegateCallbacksOn)delegatePostMatchCallbacksOn { + self.visitor.delegatePostMatchCallbacksOn = delegatePostMatchCallbacksOn; +} + + +-(BOOL)enableARC { + return self.visitor.enableARC; +} + +-(void)setEnableARC:(BOOL)enableARC { + self.visitor.enableARC = enableARC; +} + +-(BOOL)enableAutomaticErrorRecovery { + return self.visitor.enableAutomaticErrorRecovery; +} + +-(void)setEnableAutomaticErrorRecovery:(BOOL)enableAutomaticErrorRecovery { + self.visitor.enableAutomaticErrorRecovery = enableAutomaticErrorRecovery; +} + +-(BOOL)enableHybridDFA { + return self.visitor.enableHybridDFA; +} + +-(void)setEnableHybridDFA:(BOOL)enableHybridDFA { + self.visitor.enableHybridDFA = enableHybridDFA; +} + +-(BOOL)enableMemoization { + return self.visitor.enableMemoization; +} + +-(void)setEnableMemoization:(BOOL)enableMemoization { + self.visitor.enableMemoization = enableMemoization; +} + + +-(BOOL)generate { + assert(self.parserName.length > 0); + assert(self.destinationPath.length > 0); + + NSString *className = self.parserName; + if (![className hasSuffix:@"Parser"]) { + className = [NSString stringWithFormat:@"%@Parser", className]; + } + + NSError *err = nil; + self.root = (id)[self.factory ASTFromGrammar:self.grammar error:&err]; + if (err) { + self.error = err; + return NO; + } + + if (self.root.startMethodName.length == 0) { + NSString * msg = NSLocalizedString(@"Failed to find start method", nil); + self.error = [NSError errorWithDomain:@"PEGKit" code:0 userInfo:@{NSLocalizedFailureReasonErrorKey: msg}]; + return NO; + } + + self.root.grammarName = self.parserName; + + @try { + [self.root visit:self.visitor]; + } + @catch (NSException *ex) { + NSDictionary * userInfo = @{NSLocalizedFailureReasonErrorKey: ex.reason}; + self.error = [NSError errorWithDomain:ex.name code:0 userInfo:userInfo]; + return NO; + } + + NSString *path = [[NSString stringWithFormat:@"%@/%@.h", self.destinationPath, className] stringByExpandingTildeInPath]; + err = nil; + BOOL ok = [self.visitor.interfaceOutputString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&err]; + if (!ok) { + NSString *str = [err.localizedFailureReason stringByAppendingFormat:@"\n\n%@", [path stringByDeletingLastPathComponent]]; + self.error = errorWithReason(err, str); + return NO; + } + + path = [[NSString stringWithFormat:@"%@/%@.m", self.destinationPath, className] stringByExpandingTildeInPath]; + err = nil; + ok = [self.visitor.implementationOutputString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&err]; + if (!ok) { + NSString *str = [err.localizedFailureReason stringByAppendingFormat:@"\n\n%@", [path stringByDeletingLastPathComponent]]; + self.error = errorWithReason(err, str); + return NO; + } + + return YES; +} + + +static NSError * errorWithReason(NSError * err, NSString * reason) { + NSMutableDictionary * userInfo = [NSMutableDictionary dictionaryWithDictionary:err.userInfo]; + userInfo[NSLocalizedFailureReasonErrorKey] = reason; + return [NSError errorWithDomain:err.domain code:err.code userInfo:userInfo]; +} + + +@end diff --git a/ParserGenApp/main.m b/ParserGenApp/main.m index 97fd09d..5b4f015 100644 --- a/ParserGenApp/main.m +++ b/ParserGenApp/main.m @@ -22,6 +22,15 @@ #import +#import "PGCLI.h" + int main(int argc, char *argv[]) { + @autoreleasepool { + PGCLI * cli = [[PGCLI alloc] init]; + if (cli.willHandleCommandLine) { + return [cli handleCommandLine]; + } + } + return NSApplicationMain(argc, (const char **)argv); }