6

追加の依存関係 (パーサー ジェネレーター、ライブラリなど) を使用せずに、C の (サブセット) をゼロからブートストラップしようとしています。また、関数型プログラミングの優れた手法であるパー​​サー コンビネーターのアイデアも活用したいと考えています。このアイデアを関数型の世界から手続き型 C に、簡潔かつ実用的な方法で取り入れたいと思います。

次のおもちゃの文法に必要なパーサーコンビネーターをいくつか実装しようとしました。

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

person文字で始まるトークンはどこにありますか。たとえば、トークン リスト

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

に解析されます

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

(この本は Haskell を使用しており、言語にとらわれないようにするのは難しいですが、アイデアはわかります :-)

これを C で実装しました。コードはこちらで確認できます: https://gist.github.com/4451478

この実装には 200 行以上の C コードが必要であり、本に書かれている Haskell の約 20 行よりもはるかに多くなります。そのため、C でパーサー コンビネーターを実行する正しい軌道に乗っているかどうか、および改善の可能性があるかどうかはわかりません。どんな提案も歓迎します。前もって感謝します。

4

5 に答える 5

2

C でパーサー コンビネーターを実装することは、私も興味を持っているトピックであり、最近、C でパーサー コンビネーターを作成しました: https://github.com/petercommand/cparc

以下は、カンマ区切りの数値を数値のリストに解析しようとするコードのテスト ケースです。パーサーのリストを使用して (コード内で parser_chain を呼び出して「パーサーのリスト」からパーサーを生成します)、Haskell の「do 記法」を模倣しますが、それほどエレガントではありません。

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 に答える
2

C のパーサー コンビネーターの実装であるCesium3を試してください。 (LLVM.)

于 2013-01-04T10:48:11.493 に答える
-3

Yacc や Bison などを使用してみませんか?

私は Erlang で LALR 文法を使った経験があり、とても役に立ちそうです。コードの行数がはるかに少なくなります。

乾杯...

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

于 2013-01-04T10:37:36.513 に答える