CoreParse 是一个针对 Mac OS X 和 iOS 的解析库。它支持广泛的语法,多亏了其 shift/reduce 解析方案。目前 CoreParse 支持 SLR、LR(1) 和 LALR(1) 解析器。
有关完整文档,请参阅 http://beelsebob.github.com/CoreParse。
你可能想知道为什么以及何时应该使用 CoreParse。在市场上已经有许多解析器,为什么你应该选择这个呢?
CoreParse 已经在至少两个主要项目中得到了应用。
如果您知道其他任何使用地点,请随时联系。
CoreParse 是一个用于分词和解析的强大框架。本文档解释了如何从头开始创建分词器和解析器,以及如何使用这些解析器为您创建模型数据结构。我们将贯穿整个文档使用相同的示例。这将涉及解析简单的数值表达式并计算结果。
gavineadie 实现了这个整个示例,要查看完整工作的源代码,请访问 https://github.com/beelsebob/ParseTest/。
CoreParse 分词类的名称为 CPTokeniser。为了指定如何构建标记,您必须按照优先级向分词器添加 标记识别器。
我们的示例语言将涉及几个符号、数字、空白和注释。我们将这些添加到分词器
CPTokeniser *tokeniser = [[[CPTokeniser alloc] init] autorelease];
[tokeniser addTokenRecogniser:[CPNumberRecogniser numberRecogniser]];
[tokeniser addTokenRecogniser:[CPWhiteSpaceRecogniser whiteSpaceRecogniser]];
[tokeniser addTokenRecogniser:[CPQuotedRecogniser quotedRecogniserWithStartQuote:@"/*" endQuote:@"*/" name:@"Comment"]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@"+"]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@"-"]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@"*"]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@"/"]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@"("]];
[tokeniser addTokenRecogniser:[CPKeywordRecogniser recogniserForKeyword:@")"]];
请注意,注释标记识别器是在除号关键字识别器之前添加的。这使得它具有更高的优先级,并且意味着注释的第一个正斜杠不会被识别为除法。
接下来,我们作为代理添加我们自己到分词器。我们以这种方式实现分词器代理方法,即尽管空白标记和注释被消耗,但不会出现在分词器的输出中
- (BOOL)tokeniser:(CPTokeniser *)tokeniser shouldConsumeToken:(CPToken *)token
{
return YES;
}
- (void)tokeniser:(CPTokeniser *)tokeniser requestsToken:(CPToken *)token pushedOntoStream:(CPTokenStream *)stream
{
if (![token isWhiteSpaceToken] && ![[token name] isEqualToString:@"Comment"])
{
[stream pushToken:token];
}
}
我们现在可以调用我们的分词器。
CPTokenStream *tokenStream = [tokeniser tokenise:@"5 + (2.0 / 5.0 + 9) * 8"];
我们通过指定它们的语法来构建解析器。我们可以简单地使用类似于 BNF 的语言来构建语法。请注意,语法标签@<非终结符>可以简单地阅读为 <非终结符>,标签可以稍后用来从解析结果中快速提取值
NSString *expressionGrammar =
@"Expression ::= term@<Term> | expr@<Expression> op@<AddOp> term@<Term>;"
@"Term ::= fact@<Factor> | fact@<Factor> op@<MulOp> term@<Term>;"
@"Factor ::= num@'Number' | '(' expr@<Expression> ')';"
@"AddOp ::= '+' | '-';"
@"MulOp ::= '*' | '/';";
NSError *err;
CPGrammar *grammar = [CPGrammar grammarWithStart:@"Expression" backusNaurForm:expressionGrammar error:&err];
if (nil == grammar)
{
NSLog(@"Error creating grammar:");
NSLog(@"%@", err);
}
else
{
CPParser *parser = [CPLALR1Parser parserWithGrammar:grammar];
[parser setDelegate:self];
...
}
当解析器匹配一个规则时,将在相应类的新实例上调用 initWithSyntaxTree:
方法。如果不存在此类,则调用解析器代理的 parser:didProduceSyntaxTree:
方法。为了干净地处理这个问题,我们实现了 3 个类:Expression;Term;和 Factor。AddOp 和 MulOp 非终结符由解析器代理处理。这里是 Expression 类的 initWithSyntaxTree: 方法,这些方法对 Term 和 Factor 类也是类似的
- (id)initWithSyntaxTree:(CPSyntaxTree *)syntaxTree
{
self = [self init];
if (nil != self)
{
Term *t = [syntaxTree valueForTag:@"term"];
Expression *e = [syntaxTree valueForTag:@"expr"];
if (nil == e)
{
[self setValue:[t value]];
}
else if ([[syntaxTree valueForTag:@"op"] isEqualToString:@"+"])
{
[self setValue:[e value] + [t value]];
}
else
{
[self setValue:[e value] - [t value]];
}
}
return self;
}
我们还必须实现处理 AddOps 和 MulOps 的代理方法
- (id)parser:(CPParser *)parser didProduceSyntaxTree:(CPSyntaxTree *)syntaxTree
{
return [(CPKeywordToken *)[syntaxTree childAtIndex:0] keyword];
}
我们现在可以解析我们之前产生的标记流
NSLog(@"%f", [(Expression *)[parser parse:tokenStream] value]);
其输出是
80.2
CoreParse 提供 three 种类型的解析器 - SLR、LR(1) 和 LALR(1)
建议您从 SLR 解析器开始(除非您知道更好的),如果无法为您自己的语法生成解析器,则转到 LALR(1) 解析器。LR(1) 解析器实际上并不推荐,但在极端情况下可能有用。
如果您有一个需要 LALR(1) 解析器的重大语法,建议您使用 NSKeyedArchiving 将解析器归档到文件中。您应该读取此文件,并在应用程序运行时解档它,以避免每次运行时都生成解析器,因为解析器生成可能需要一些时间。