CoreParse 1.1.0.20140123

CoreParse 1.1.0.20140123

测试已测试
语言语言 Obj-CObjective C
许可证 MIT
发布最后发布2014年5月

未声明的所有人维护。



CoreParse 1.1.0.20140123

  • 作者:
  • Tom Davie

CoreParse 是一个用于 Mac OS X 和 iOS 的解析库。它由于 shift/reduce 解析方案支持广泛的语言。目前 CoreParse 支持 SLR、LR(1) 和 LALR(1) 解析器。

完整的文档请见 http://beelsebob.github.com/CoreParse

为什么你应该使用 CoreParse

你可能想知道为什么以及何时应该使用 CoreParse。已有许多解析器可用,为什么你要使用这个呢?

  • 与 ParseKit 相比
    • CoreParse 支持更多的语言(LR(1) 语言涵盖所有的 LL(1) 语言以及更多)。在实践中,LALR(1) 语法涵盖了大多数有用的语言。
    • CoreParse 产生更快的解析器。
    • 使用 NSKeyedArchivers,可以将 CoreParse 解析器和分词器存档,以避免每次应用运行时再生成。
    • CoreParse 的解析算法不是递归的,这意味着理论上它可以处理大型语言结构层次而不会耗尽堆栈。
  • 与 lex/yacc 或 flex/bison 相比
    • 尽管我没有明确基准测试,但我预计由 lex/yacc 或 flex/bison 生成的解析器比 CoreParse 的解析器更快。
    • CoreParse 要求你在开始之前编译解析器(尽管推荐这样做)。
    • CoreParse 允许你直接在你的 Objective-C 源文件中指定语法,而不是需要另一种语言,这会使 C/Obj-C 混合。
    • CoreParse 不使用全局状态,可以在并行运行多个解析器实例(或相同的解析器实例可以并行解析多个词流)。

CoreParse 已在哪里使用?

CoreParse 已在至少两个项目中以非常重要的方式使用

  • Matt Mower 在他的 statec 项目中使用它来解析他的状态机规范。
  • 我在 OpenStreetPad 项目中使用它来解析 MapCSS。

如果你知道它被用于其他地方,请随时联系。

解析指南

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)解析器消耗大量的RAM,较慢,但覆盖的语言集最多。
  • LALR(1)解析器在运行时与SLR解析器一样快,但生成速度较慢,覆盖的语言集几乎与LR(1)解析器一样多。

建议您从一个SLR解析器开始(除非您知道更好的情况),当无法为您的语法生成解析器时,转向LALR(1)解析器。实际上,并不推荐使用LR(1)解析器,尽管在极端情况下可能有用。

如果您有一个需要LALR(1)解析器的复杂语法,建议您使用NSKeyedArchiving将解析器存档到文件。您应该读取该文件,并在应用运行时解档,这样可以节省每次运行时都生成解析器的时间,因为解析器的生成可能需要一些时间。