CoreParse@siuying 1.1.0.20140123

CoreParse@siuying 1.1.0.20140123

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

未指派 维护。



CoreParse@siuying 1.1.0.20140123

  • Tom Davie

CoreParse

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 生成更快的解析器。
    • CoreParse 解析器和词法分析器可以使用 NSKeyedArchivers 归档,以保存每次您的应用程序运行时重新生成它们。
    • CoreParse 的解析算法不是递归的,这意味着它理论上可以处理更大的语言结构层次,而不会打爆栈。
  • 与 lex/yacc 或 flex/bison 相比
    • 尽管我没有进行明确的基准测试,但我预计由 lex/yacc 或 flex/bison 生成的解析器将比 CoreParse 的解析器更快。
    • CoreParse 强制你开始之前编译你的解析器(尽管这是推荐的)。
    • CoreParse 提供了让你可以在 Objective-C 源代码中直接指定语法的功能,而不是需要另一种语言,该语言混合了 C/Obj-C。
    • CoreParse 不使用全局状态,可以并行运行多个解析器实例(或者在同一个解析器实例中可以并行解析多个令牌流)。

CoreParse 已经在哪里使用?

CoreParse 已经在至少两个主要项目中得到了应用。

  • Francis Chong 在他的 CSS 选择器转换器 中使用了它来解析 CSS3。
  • 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 提供 three 种类型的解析器 - SLR、LR(1) 和 LALR(1)

  • SLR 解析器覆盖的语言子集最小,并且比 LALR(1) 解析器生成得更快。
  • LR(1) 解析器消耗大量 RAM,且速度慢,但覆盖的语言子集最大。
  • LALR(1) 解析器的运行速度与 SLR 解析器一样快,但生成速度较慢,它们覆盖的语言子集接近 LR(1) 解析器。

建议您从 SLR 解析器开始(除非您知道更好的),如果无法为您自己的语法生成解析器,则转到 LALR(1) 解析器。LR(1) 解析器实际上并不推荐,但在极端情况下可能有用。

如果您有一个需要 LALR(1) 解析器的重大语法,建议您使用 NSKeyedArchiving 将解析器归档到文件中。您应该读取此文件,并在应用程序运行时解档它,以避免每次运行时都生成解析器,因为解析器生成可能需要一些时间。