6

我正在尝试从头开始引导(一个子集)C,而不使用额外的依赖项(解析器生成器、库等)。我还想利用解析器组合器的想法,这是函数式编程中的一项绝妙技术。我想以简洁实用的方式将函数世界中的这个想法借用到程序 C 中。

我尝试为以下玩具语法实现一些必要的解析器组合器,这也是Simon Peyton Jones的《实现功能语言 - 教程》一书中的一个示例。

greeting -> hg person "!"
hg       -> "hello"
          | "goodbye"

person任何以字母开头的标记在哪里。例如,令牌列表

["goodbye", "James", "!"]

被解析成

[(("goodbye", "James"), ["!"])]

(这本书使用 Haskell,很难让它与语言无关,但你明白了 :-)

我用 C 实现了这个,你可以在这里查看代码:https ://gist.github.com/4451478

这个实现需要 200 多行 C 代码,这远远超过了书中所写的大约 20 行 Haskell 代码。所以我不确定我是否在 C 中做解析器组合器的正确轨道,以及是否有任何可能的改进。欢迎任何建议。提前致谢。

4

5 回答 5

7

我自己正在研究这个主题,我正在关注mpc 的作者Daniel Holden的工作, mpc是一个编写得非常好的C解析器组合库,除其他外,它允许在 C 代码中嵌入EBNFRegex :

  mpc_parser_t *Expr  = mpc_new("expression");
  mpc_parser_t *Prod  = mpc_new("product");
  mpc_parser_t *Value = mpc_new("value");
  mpc_parser_t *Maths = mpc_new("maths");

  mpca_lang(MPCA_LANG_PREDICTIVE,
    " expression : <product> (('+' | '-') <product>)*; "
    " product : <value>   (('*' | '/')   <value>)*;    "
    " value : /[0-9]+/ | '(' <expression> ')';         "
    " maths : /^/ <expression> /$/;                    "
    Expr, Prod, Value, Maths, NULL);

Daniel Holden 也写了一本在线书籍,他在其中展示了使用他的库编写一门新语言是多么容易。这本书的标题是“构建你自己的 Lisp”。我认为您会发现这对您的项目非常有用。最后但同样重要的是,在库的示例中,有一个现成的程序可以为 C 的子集生成解析器。;-)

于 2014-09-02T01:57:38.033 回答
2

尝试Cesium3,它是 C 的解析器组合器的实现。(LLVM。)

于 2013-01-04T10:48:11.493 回答
2

在 C 中实现解析器组合器也是我感兴趣的一个话题,最近,我用 C 写了一个解析器组合器:https ://github.com/petercommand/cparc

以下是我的代码中的一个测试用例,它尝试将逗号分隔的数字解析为数字列表。我使用解析器列表(并通过在代码中调用 parser_chain 从“解析器列表”生成解析器)来模仿 Haskell 中的“do notation”,尽管没有那么优雅。

parser_dp_return test_parser7_rest_dp(dynamic_parser_closure* ctx, input_t input) {
  parser_dp_return dp_ret;
  dp_ret.obj = ctx->objs[1]->obj;
  dp_ret.status = PARSER_NORMAL;
  dp_ret.i = input;
  dp_ret.discard_obj_callback = NULL;
  return dp_ret;
}

parser_dp_return test_parser7_full_dp(dynamic_parser_closure* ctx, input_t input) {
  parser_dp_return dp_ret;
  list* result = list_new();
  list_push_back(result, ctx->objs[0]->obj);//num
  if(ctx->objs[1] && ctx->objs[1]->obj) {
    list_append(result, ctx->objs[1]->obj);
  }
  dp_ret.status = PARSER_NORMAL;
  dp_ret.i = input;
  dp_ret.discard_obj_callback = (void (*)(void *))&list_delete;

  dp_ret.obj = result;
  return dp_ret;
}

bool test_parser7() {//comma separated values
  parser* number = num();
  parser* comma = symbol(',');
  parser* rest_parser = parser_chain_final(test_parser7_rest_dp);
  list* parser_chain_list = list_new();
  list_push_back(parser_chain_list, comma);//ctx 0
  list_push_back(parser_chain_list, number);//ctx 1
  list_push_back(parser_chain_list, rest_parser);

  parser* rest = parser_chain(parser_chain_list);
  list_delete(parser_chain_list);
  parser* many_rest = many(rest);

  list* parser_chain_full = list_new();
  list_push_back(parser_chain_full, number);//ctx 0
  list_push_back(parser_chain_full, many_rest);//ctx 1
  parser* full_parser = parser_chain_final(test_parser7_full_dp);
  list_push_back(parser_chain_full, full_parser);
  parser* final = parser_chain(parser_chain_full);

  const char* input = "1,20,300,4000,50000";
  input_t i;
  input_init(&i, input);
  parser_dp_return dp_ret = parse(final, i);
  parser_delete(number);
  parser_delete(comma);
  parser_delete(rest_parser);
  parser_delete(rest);
  parser_delete(many_rest);
  parser_delete(full_parser);
  parser_delete(final);
  bool result = true;
  test_true(&result, dp_ret.status == PARSER_NORMAL);
  list* l = dp_ret.obj;
  list_item* li = l->head;
  test_true(&result, ptr_to_int(li->item) == 1);
  li = li->next;
  test_true(&result, ptr_to_int(li->item) == 20);
  li = li->next;
  test_true(&result, ptr_to_int(li->item) == 300);
  li = li->next;
  test_true(&result, ptr_to_int(li->item) == 4000);
  li = li->next;
  test_true(&result, ptr_to_int(li->item) == 50000);
  return result;
}
于 2016-02-01T18:09:30.083 回答
0

我认为你的 C 代码对于你正在做的事情来说相当紧凑。Haskell 对这类事情更紧凑。从闭包开始。您需要在周围范围内关闭函数才能执行此操作,并且您的代码会部分模拟这些函数。Haskell 对列表有简洁的表示法,并且正确使用它们对于像 AST 这样的树非常好,而 C 要求您构建自己的 AST 库并使用“->left”和“->right”,否则尝试将它们包装在简洁的宏。

即使在我见过的 C 语言实现以及我自己编写的未完善的 C++ 实现中,我认为解析器组合器是递归下降代码的令人满意的替代方案,递归下降代码是我过去选择的解析方法。

于 2013-04-13T19:51:01.850 回答
-3

想知道,为什么不使用 Yacc 或 Bison 之类的东西呢?

我对 Erlang 中的 LALR 语法有一些经验,看起来对我很有用。更少的代码行。

干杯...

http://www.erlang.org/doc/man/yecc.html

于 2013-01-04T10:37:36.513 回答