C中解析器组合器更好的方法?

时间:2013-01-04 10:27:48

标签: c parsing compiler-construction parser-combinators

我正在尝试从头开始引导(一部分)C,而不使用额外的依赖项(解析器生成器,库等)。另外,我想利用parser combinators的概念,这是函数式编程中的一种奇妙技巧。我想以简洁实用的方式将这个想法从功能世界借给程序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中进行解析器组合器的正确轨道,以及是否有任何可能的改进。欢迎任何建议。提前谢谢。

5 个答案:

答案 0 :(得分:5)

我正在研究这个主题,我正在关注Daniel Holden的作品, mpc 的作者,一个写得很好的解析器组合库for C,除其他外,允许在C代码中嵌入 EBNF 正则表达式

  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也写了一篇在线book,他演示了如何使用他的图书馆编写新语言很容易。这本书的标题是“建立你自己的Lisp”。我想你会发现这对你的项目非常有用。最后但并非最不重要的是,在库的示例中,有一个现成的程序,它为C的子集生成解析器。; - )

答案 1 :(得分:2)

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

答案 2 :(得分:1)

在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;
}

答案 3 :(得分:0)

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

即使在我看过的C语言实现中,以及我自己编写的未经定义的C ++实现,我认为解析器组合器是递归下行代码的一种令人满意的替代方法,它在过去一直是我选择的解析方法。

答案 4 :(得分:-1)

想知道,为什么不使用像Yacc或Bison这样的东西?

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

干杯...

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