C的单头文件解析组合器
在C语言中解析存在问题:手写解析器(递归下降、状态机等)难以维护。使用Flex或Bison生成的代码同样难以维护,并使构建变得复杂。CParseC(C解析组合器)提供了一种灵活且高效的解析解决方案:可组合的、富有表现力的解析器,使用纯C99编写(受到Haskell的Parsec启发)。该库只需一个头文件(cparsec.h),没有依赖项(默认情况下不假设有libc),支持零拷贝解析,没有隐含分配,用户提供的内存区;支持内联友好,在热路径中使用宏代替函数指针;SIMD专用组合器。演示程序:一个CSV解析器如下所示: #include <stdio.h> #include <stdlib.h> #define CPC_USE_MEMCHR #define CPC_USE_UNNAMED #include "cparsec.h" CPC_TAKE_QUOTED ( quotedField , '"' , '"' ) CPC_TAKE_TILL_ONE_OF ( unquotedField , ",\r\n" ) CPC_ALT ( field , quotedField , unquotedField ) CPC_SEP_BY_1 ( record , field , CPC_STRING_ ( "," )) CPC_ALT ( lineEnd , CPC_END_OF_LINE_ , CPC_EOF_ ) CPC_LEFT ( parse_csv_row , record , lineEnd ) int main ( void ) { CpcArena arena ; CpcValue arena_storage [ 8192 ]; cpc_arena_init ( & arena , arena_storage , sizeof ( arena_storage ) / sizeof ( arena_storage [ 0 ]), NULL ); const char csv [] = "alpha,\"beta\",\"ga,mm,a\",d\"\"elta\n" ; CpcSlice input = cpc_slice_from_cstr ( csv ); CpcResult result = parse_csv_row ( & arena , input ); for ( size_t i = 0 ; i < result . out . as . list . len ; ++ i ) { const CpcValue * cell = cpc_val_list_at ( & arena , & result . out , i ); CpcSlice slice = cell -> as . slice ; printf ( "%.*s " , ( int ) slice . len , slice . ptr ); //alpha "beta" "ga,mm,a" delta } return EXIT_SUCCESS ; } 当解析1百万个CSV行时,上面的解析器的速度比BurntSushi/rust-csv快约1.25倍,比attoparsec-csv快约20倍。请查看CI上的持续基准测试以确认结果。 API 基本组合器 所有宏基本上生成可以内联的函数,这些函数可以接受其他可内联的函数作为参数。它们返回CpcValue,可以是一个切片(CpcSlice)或一个列表(CpcList,这需要CpcArena来存储)。 宏描述标签 Unnamed CIP_STRING(name, lit) 解析确切的字符串字面量lit并将其作为切片返回。 CPC_STRING_LABEL CPC_STRING_ * CPC_ALT(name, x, y) 尝试解析器x,如果失败,则在相同输入上尝试解析器y。 N/A N/A CPC_RIGHT(name, x, y) 运行x然后运行y,只返回y的输出。 N/A N/A CPC_LEFT(name, x, y) 运行x然后运行y,只返回x的输出。 N/A N/A CPC_APPLY(name, x, y) 运行x然后运行y,返回两个输出的列表。 N/A N/A CPC_TAKE_WHILE_1(name, pred) 在pred为真的情况下消耗一个或多个字符,并返回消耗的切片。 CPC_TAKE_WHILE_1_LABEL N/A CPC_MANY_1(name, parser) 运行解析器一次或多次,返回输出的列表。 CPC_MANY_1_LABEL N/A CPC_SEP_BY_1(name, item, sep) 解析由sep分隔的一个或多个项值,返回列表。 CPC_SEP_BY_1_LABEL N/A CPC_PURE(name, value) 在不消耗输入的情况下成功并返回值。 N/A N/A CPC_MAP(name, parser, fn) 运行解析器并用fn转换其输出。 N/A N/A CPC_MANY(name, parser) 运行解析器零次或多次,返回输出的列表。 N/A N/A CPC_SEP_BY(name, item, sep) 解析零个或多个由sep分隔的项值,返回列表。 N/A N/A CPC_TAKE_WHILE(name, pred) 在pred为真的情况下消耗零个或多个字符,并返回消耗的切片。 N/A N/A CPC_MANY_TILL(name, parser, end) 重复解析器,直到end成功,返回收集的输出的列表。 N/A N/A CPC_TAKE_TILL(name, pred) 消耗输入直到pred为真,并返回消耗的切片。 N/A N/A CPC_MATCH(name, parser) 运行解析器并返回确切消耗的输入作为切片,而不是解析的值。 N/A N/A CPC_ONE_OF(name, chars) 如果下一个字符是chars中的一个字符,则成功并将其作为切片返回。 CPC_ONE_OF_LABEL N/A CPC_END_OF_LINE(name) 解析\n或\r\n并返回匹配的切片。 CPC_END_OF_LINE_LABEL CPC_END_OF_LINE_ CPC_ANY(name) 消耗并返回任何单个字符作为切片。 CPC_ANY_LABEL CPC_ANY_ CPC_EOF(name) 仅在输入结束时成功。 CPC_EOF_LABEL CPC_EOF_ CPC_BETWEEN(name, open, parser, close) 解析open,然后解析器,然后解析close,只返回解析器的输出。 N/A N/A 为了方便,可以将某些解析器标记为未命名,以减少对每个函数进行命名的开销。那些标记为*的解析器(如CPC_STRING_)需要#define CPC_USE_UNNAMED,因为它们需要非标准的C99行为(嵌套函数、语句表达式和__COUNTER__)。_LABEL变体需要额外的标签参数来更改内置错误消息。注意:内部错误消息显示了超出内存区大小的条件和没有进展的情况(例如,对于写得不好的解析器导致无限循环),不能被覆盖。SIMD组合器:这些解析器是专门的版本,利用memchr来实现SIMD启用。由于memchr不可用...
本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。
☕请我喝杯咖啡