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提供三种类型的解析器 - SLR、LR(1)和LALR(1)
建议您从一个SLR解析器开始(除非您知道更好的情况),当无法为您的语法生成解析器时,转向LALR(1)解析器。实际上,并不推荐使用LR(1)解析器,尽管在极端情况下可能有用。
如果您有一个需要LALR(1)解析器的复杂语法,建议您使用NSKeyedArchiving将解析器存档到文件。您应该读取该文件,并在应用运行时解档,这样可以节省每次运行时都生成解析器的时间,因为解析器的生成可能需要一些时间。