Cの単純なJSONパーサー

これは単純な再帰下降JSONパーサーであり、ここで確認した拡張可能なベクトルクラスを使用していますが、多くの追加機能はありません( Cの単純な展開可能なベクトル)。最適化も、より高いレベルのアクセスインターフェイスも実装していません。すべて非常に基本的です。また、JSONエクスポートはなく、インポートするだけです。

ソース全体はgithub( https://github.com/HarryDC/JsonParser )で入手できます。 CMakeとテストコードが含まれています。ここにパーサーファイルを投稿します。

私の主な関心は、Cで物事を書くためのより慣用的な方法があるかどうかです。しかし、もちろん、他の入力も常に評価されます。

ヘッダー

#ifndef HS_JSON_H #define HS_JSON_H #include "vector.h" enum json_value_type { TYPE_NULL, TYPE_BOOL, TYPE_NUMBER, TYPE_OBJECT, // Is a vector with pairwise entries, key, value TYPE_ARRAY, // Is a vector, all entries are plain TYPE_STRING, TYPE_KEY }; typedef struct { int type; union { int boolean; double number; char* string; char* key; vector array; vector object; } value; } json_value; // Parse string into structure of json elements and values // return 1 if successful. int json_parse(const char* input, json_value* root); // Free the structure and all the allocated values void json_free_value(json_value* val); // Convert value to string if possible, asserts if not char* json_value_to_string(json_value* value); // Convert value to double if possible asserts if not double json_value_to_double(json_value* value); // Convert value to bool if possible asserts if not int json_value_to_bool(json_value* value); // Convert value to vector if it"s an array asserts if not vector* json_value_to_array(json_value* value); // Convert value to vector if it"s an object, asserts if not vector* json_value_to_object(json_value* value); // Fetch the value with given index from root, asserts if root is not array json_value* json_value_at(const json_value* root, size_t index); // Fetche the value with the given key from root, asserts if root is not object json_value * json_value_with_key(const json_value * root, const char * key); #ifdef BUILD_TEST void json_test_all(void); #endif #endif 

実装

#include "json.h" #include <assert.h> #include <ctype.h> #include <stddef.h> #include <stdlib.h> #include <string.h> static int json_parse_value(const char ** cursor, json_value * parent); static void skip_whitespace(const char** cursor) { if (**cursor == "\0") return; while (iscntrl(**cursor) || isspace(**cursor)) ++(*cursor); } static int has_char(const char** cursor, char character) { skip_whitespace(cursor); int success = **cursor == character; if (success) ++(*cursor); return success; } static int json_parse_object(const char** cursor, json_value* parent) { json_value result = { .type = TYPE_OBJECT }; vector_init(&result.value.object, sizeof(json_value)); int success = 1; while (success && !has_char(cursor, "}")) { json_value key = { .type = TYPE_NULL }; json_value value = { .type = TYPE_NULL }; success = json_parse_value(cursor, &key); success = success && has_char(cursor, ":"); success = success && json_parse_value(cursor, &value); if (success) { vector_push_back(&result.value.object, &key); vector_push_back(&result.value.object, &value); } else { json_free_value(&key); break; } skip_whitespace(cursor); if (has_char(cursor, "}")) break; else if (has_char(cursor, ",")) continue; else success = 0; } if (success) { *parent = result; } else { json_free_value(&result); } return success; return 1; } static int json_parse_array(const char** cursor, json_value* parent) { int success = 1; if (**cursor == "]") { ++(*cursor); return success; } while (success) { json_value new_value = { .type = TYPE_NULL }; success = json_parse_value(cursor, &new_value); if (!success) break; skip_whitespace(cursor); vector_push_back(&parent->value.array, &new_value); skip_whitespace(cursor); if (has_char(cursor, "]")) break; else if (has_char(cursor, ",")) continue; else success = 0; } return success; } void json_free_value(json_value* val) { if (!val) return; switch (val->type) { case TYPE_STRING: free(val->value.string); val->value.string = NULL; break; case TYPE_ARRAY: case TYPE_OBJECT: vector_foreach(&(val->value.array), (void(*)(void*))json_free_value); vector_free(&(val->value.array)); break; } val->type = TYPE_NULL; } int json_is_literal(const char** cursor, const char* literal) { size_t cnt = strlen(literal); if (strncmp(*cursor, literal, cnt) == 0) { *cursor += cnt; return 1; } return 0; } static int json_parse_value(const char** cursor, json_value* parent) { // Eat whitespace int success = 0; skip_whitespace(cursor); switch (**cursor) { case "\0": // If parse_value is called with the cursor at the end of the string // that"s a failure success = 0; break; case """: ++*cursor; const char* start = *cursor; char* end = strchr(*cursor, """); if (end) { size_t len = end - start; char* new_string = malloc((len + 1) * sizeof(char)); memcpy(new_string, start, len); new_string[len] = "\0"; assert(len == strlen(new_string)); parent->type = TYPE_STRING; parent->value.string = new_string; *cursor = end + 1; success = 1; } break; case "{": ++(*cursor); skip_whitespace(cursor); success = json_parse_object(cursor, parent); break; case "[": parent->type = TYPE_ARRAY; vector_init(&parent->value.array, sizeof(json_value)); ++(*cursor); skip_whitespace(cursor); success = json_parse_array(cursor, parent); if (!success) { vector_free(&parent->value.array); } break; case "t": { success = json_is_literal(cursor, "true"); if (success) { parent->type = TYPE_BOOL; parent->value.boolean = 1; } break; } case "f": { success = json_is_literal(cursor, "false"); if (success) { parent->type = TYPE_BOOL; parent->value.boolean = 0; } break; } case "n": success = json_is_literal(cursor, "null"); break; default: { char* end; double number = strtod(*cursor, &end); if (*cursor != end) { parent->type = TYPE_NUMBER; parent->value.number = number; *cursor = end; success = 1; } } } return success; } int json_parse(const char* input, json_value* result) { return json_parse_value(&input, result); } char* json_value_to_string(json_value* value) { assert(value->type == TYPE_STRING); return (char *)value->value.string; } double json_value_to_double(json_value* value) { assert(value->type == TYPE_NUMBER); return value->value.number; } int json_value_to_bool(json_value* value) { assert(value->type == TYPE_BOOL); return value->value.boolean; } vector* json_value_to_array(json_value* value) { assert(value->type == TYPE_ARRAY); return &value->value.array; } vector* json_value_to_object(json_value* value) { assert(value->type == TYPE_OBJECT); return &value->value.object; } json_value* json_value_at(const json_value* root, size_t index) { assert(root->type == TYPE_ARRAY); if (root->value.array.size < index) { return vector_get_checked(&root->value.array,index); } else { return NULL; } } json_value* json_value_with_key(const json_value* root, const char* key) { assert(root->type == TYPE_OBJECT); json_value* data = (json_value*)root->value.object.data; size_t size = root->value.object.size; for (size_t i = 0; i < size; i += 2) { if (strcmp(data[i].value.string, key) == 0) { return &data[i + 1]; } } return NULL; } #ifdef BUILD_TEST #include <stdio.h> void json_test_value_string(void) { printf("json_parse_value_string: "); // Normal parse, skip whitespace const char* string = " \n\t\"Hello World!\""; json_value result = { .type = TYPE_NULL }; assert(json_parse_value(&string, &result)); assert(result.type == TYPE_STRING); assert(result.value.string != NULL); assert(strlen(result.value.string) == 12); assert(strcmp("Hello World!", result.value.string) == 0); json_free_value(&result); // Empty string string = "\"\""; json_parse_value(&string, &result); assert(result.type == TYPE_STRING); assert(result.value.string != NULL); assert(strlen(result.value.string) == 0); json_free_value(&result); printf(" OK\n"); } void json_test_value_number(void) { printf("json_test_value_number: "); const char* string = " 23.4"; json_value result = { .type = TYPE_NULL }; assert(json_parse_value(&string, &result)); assert(result.type == TYPE_NUMBER); assert(result.value.number == 23.4); json_free_value(&result); printf(" OK\n"); } void json_test_value_invalid(void) { printf("json_test_value_invalid: "); { // not a valid value const char* string = "xxx"; json_value result = { .type = TYPE_NULL }; assert(!json_parse_value(&string, &result)); assert(result.type == TYPE_NULL); json_free_value(&result); } { // parse_value at end should fail const char* string = ""; json_value result = { .type = TYPE_NULL }; assert(!json_parse_value(&string, &result)); assert(result.type == TYPE_NULL); json_free_value(&result); } printf(" OK\n"); } void json_test_value_array(void) { printf("json_test_value_array: "); { // Empty Array const char* string = "[]"; json_value result = { .type = TYPE_NULL }; assert(result.value.array.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_ARRAY); assert(result.value.array.data != NULL); assert(result.value.array.size == 0); json_free_value(&result); } { // One Element const char* string = "[\"Hello World\"]"; json_value result = { .type = TYPE_NULL }; assert(result.value.array.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_ARRAY); assert(result.value.array.data != NULL); assert(result.value.array.size == 1); json_value* string_value = (json_value *)result.value.array.data; assert(string_value->type == TYPE_STRING); assert(strcmp("Hello World", string_value->value.string) == 0);; json_free_value(&result); } { // Mutliple Elements const char* string = "[0, 1, 2, 3]"; json_value result = { .type = TYPE_NULL }; assert(result.value.array.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_ARRAY); assert(result.value.array.data != NULL); assert(result.value.array.size == 4); json_free_value(&result); } { // Failure const char* string = "[0, 2,,]"; json_value result = { .type = TYPE_NULL }; assert(result.value.array.data == NULL); assert(result.type == TYPE_NULL); assert(result.value.array.data == NULL); json_free_value(&result); } { // Failure // Shouldn"t need to free, valgrind shouldn"t show leak const char* string = "[0, 2, 0"; json_value result = { .type = TYPE_NULL }; assert(result.value.array.data == NULL); assert(result.type == TYPE_NULL); assert(result.value.array.data == NULL); } printf(" OK\n"); } void json_test_value_object(void) { printf("json_test_value_object: "); { // Empty Object const char* string = "{}"; json_value result = { .type = TYPE_NULL }; assert(result.value.object.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_OBJECT); assert(result.value.array.data != NULL); assert(result.value.array.size == 0); json_free_value(&result); } { // One Pair const char* string = "{ \"a\" : 1 }"; json_value result = { .type = TYPE_NULL }; assert(result.value.object.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_OBJECT); assert(result.value.array.data != NULL); assert(result.value.array.size == 2); json_value* members = (json_value *)result.value.object.data; assert(strcmp(json_value_to_string(members), "a") == 0); ++members; assert(json_value_to_double(members) == 1.0); json_free_value(&result); } { // Multiple Pairs const char* string = "{ \"a\": 1, \"b\" : 2, \"c\" : 3 }"; json_value result = { .type = TYPE_NULL }; assert(result.value.object.data == NULL); assert(json_parse_value(&string, &result)); assert(result.type = TYPE_OBJECT); assert(result.value.array.data != NULL); assert(result.value.array.size == 6); json_value* members = (json_value *)result.value.object.data; assert(strcmp(json_value_to_string(&members[4]), "c") == 0); assert(json_value_to_double(&members[5]) == 3.0); json_free_value(&result); } printf(" OK\n"); } void json_test_value_literal(void) { printf("json_test_values_literal: "); { const char* string = "true"; json_value result = { .type = TYPE_NULL }; assert(json_parse_value(&string, &result)); assert(result.type == TYPE_BOOL); assert(result.value.boolean); json_free_value(&result); } { const char* string = "false"; json_value result = { .type = TYPE_NULL }; assert(json_parse_value(&string, &result)); assert(result.type == TYPE_BOOL); assert(!result.value.boolean); json_free_value(&result); } { const char* string = "null"; json_value result = { .type = TYPE_NULL }; assert(json_parse_value(&string, &result)); assert(result.type == TYPE_NULL); json_free_value(&result); } printf(" OK\n"); } const char* test_string_valid = " \ { \"item1\" : [1, 2, 3, 4], \ \"item2\" : { \"a\" : 1, \"b\" : 2, \"c\" : 3 }, \ \"item3\" : \"An Item\" \ }"; const char* test_string_invalid = " \ { \"item1\" : [1, 2, 3, 4], \ \"item2\" : { \"a\" : 1, \"b\" : 2, \"c\" : 3 }, \ \"item3\" , \"An Item\" \ }"; void json_test_coarse(void) { printf("json_test_coarse: "); json_value root; assert(json_parse(test_string_valid, &root)); json_value* val = json_value_with_key(&root, "item1"); assert(root.type == TYPE_OBJECT); assert(root.value.object.size == 6); assert(val != NULL); assert(json_value_to_array(val) != NULL); assert(json_value_to_array(val)->size == 4); val = json_value_with_key(&root, "item3"); assert(val != NULL); assert(json_value_to_string(val) != NULL); assert(strcmp(json_value_to_string(val), "An Item") == 0); json_free_value(&root); // valgrind check for releasing intermediary data assert(!json_parse(test_string_invalid, &root)); printf(" OK\n"); } void json_test_all(void) { json_test_value_invalid(); json_test_value_string(); json_test_value_number(); json_test_value_array(); json_test_value_object(); json_test_value_literal(); json_test_coarse(); } #endif 

回答

ヘッダー

Cでは、すべてのenum名が互いに同じ名前空間を共有します(変数名など)。したがって、衝突するリスクを減らすように努めることをお勧めします。

enum json_value_typeの名前には接頭辞、これはかなり一般的です。他のライブラリが同じ名前を使用しようとする場合があります。そのプレフィックスを、たとえばJSON_に変更することをお勧めします。

また、TYPE_KEY何でも。削除するだけです。

実装

Roland Illigが指摘しているように、iv id = “8ebd65d75a”への引数skip_whitespace()関数の>

isspace()unsigned charにキャストする必要があります符号拡張は避けてください。

または、 JSON仕様に厳密に従って、この関数を次のように簡単に書き直すことができます。

static void skip_whitespace(const char** cursor) { while (**cursor == "\t" || **cursor == "\r" || **cursor == "\n" || **cursor == " ") ++(*cursor); } 

多くのstaticヘルパー関数は、重要な組み合わせを実行し、それらが何であるかを説明するコメントがありません。行う。各関数の前に1行または2行のコメント行があると、読みやすさが大幅に向上します。

特に、has_char()関数はさまざまなことを行います。

  1. 空白をスキップします。
  2. 入力に特定の文字が含まれているかどうかを確認します。
  3. 文字が見つかった場合は、自動的にスキップします。

関数名によって明らかに暗示されるのは#2だけです。その他は予期しない副作用であり、少なくとも明確に文書化する必要があります。

実際には、skip_whitespace()への呼び出しを削除する方がよいように思われます。 has_char()から、必要に応じて呼び出す前に、呼び出し元に明示的に空白をスキップさせます。多くの場合、コードはすでにそれを行っており、重複スキップが冗長になっています。

また、効果#3を読者の意外なものにしないために、その関数の名前をもう少し変更することをお勧めします。たとえば、read_char()のようにアクティブです。


json_parse_object()の最後に、次のようになります。

 return success; return 1; } 

確かに冗長です。return 1;を削除してください。

また、汎用のjson_parse_value()関数を使用してオブジェクトキーを解析しているようです。テストして、それらが文字列であることを確認しないでください。これにより、無効なJSONがパーサーを通過できるようになります。明示的な型チェックを追加するか、文字列解析コードを別の関数に分割して(以下で説明するように)、json_parse_object()から直接呼び出すことをお勧めします。


json_parse_array()の上部には、次のものがあります。

if (**cursor == "]") { ++(*cursor); return success; } while (success) { 

次のように書き直すことができます。 json_parse_object()で行います:

while (success && !has_char("]")) { 

(ただし、名前はread_char()の方が良いでしょう。)

また、何らかの理由で、json_parse_array()は呼び出し元が 構造体、json_parse_object()は自動的に実行します。AFAICTには不整合の理由がないため、両方の関数を機能させることができます。同じ方法です。


json_is_literal()関数は、staticとしてマークされていませんが、マークされていません。ヘッダーに表示されます。is_char()のように、I “d alそのため、json_read_literal()read_literal()など、よりアクティブな名前に変更して、カーソルを自動的に移動することを明確にします。一致に成功しました。

(また、記述されているように、この関数は、入力のリテラルが実際に想定どおりに終了することをチェックしないことに注意してください。たとえば、入力nullnullnullnullと正常に照合します。JSONで有効なリテラルはtruefalsenull、どちらも相互のプレフィックスではなく、2つのリテラルは、間に他のトークンがないと有効なJSONに連続して表示されないためです。ただし、少なくともコメントで注目する価値はあります。)


静的ヘルパー関数の一部をinlineとして明示的にマークすることもできます。コンパイラに、それらを呼び出し元のコードにマージしようとするヒントを与えるためです。少なくともskip_whitespace()has_char()およびjson_is_literal()

json_value_to_X()アクセサー関数はすべて、とポインターの逆参照の場合、実装をjson.hに移動し、static inlineとしてマークすることも検討する必要があります。これにより、コンパイラは他の.cファイルでもそれらを呼び出し元のコードにインライン化でき、呼び出し時にassert()を最適化できる可能性があります。コードはとにかくタイプをすでにチェックしています。


メインのjson_parse()関数で、空白だけが残っていることを明示的にチェックすることをお勧めします。ルート値が解析された後の入力。

文字列解析

json_parse_value()の文字列解析コードは壊れていないため、壊れています。バックスラッシュエスケープを処理します。たとえば、次の有効なJSON入力では失敗します。

"I say: \"Hello, World!\"" 

テストケースとして追加することをお勧めします。

あなたまた、コードが\b\f\n\r\t\/、特に\\および\unnnn。これらのテストケースをさらにいくつか示します。

"\"\b\f\n\r\t\/\\" "void main(void) {\r\n\tprintf(\"I say: \\\"Hello, World!\\\"\\n\");\r\n}" "\u0048\u0065\u006C\u006C\u006F\u002C\u0020\u0057\u006F\u0072\u006C\u0064\u0021" "\u3053\u3093\u306B\u3061\u306F\u4E16\u754C" 

JSON文字列には任意のUnicode文字を含めることができるため、それらの処理方法を決定する必要があります。おそらく最も簡単な選択は、入力と出力をUTF-8(またはおそらく WTF-8 )で宣言し、はUTF-8バイトシーケンスにエスケープします(オプションで、その逆も同様です)。ヌル文字で終了する文字列を使用しているため、\u0000長すぎるエンコーディング"\xC0\x80"


メインのjson_parse_value()関数を読み取り可能に保つために、文字列解析コードを別のヘルパー関数に分割することを強くお勧めします。特に、バックスラッシュエスケープを正しく処理するようにすると、かなり複雑になります。

複雑な点の1つは、実際にどのくらいの時間がかかるかわからないことです。文字列は、解析が完了するまで続きます。これに対処する1つの方法は、割り当てられた出力文字列をrealloc()で動的に拡張することです(例:

// resize output buffer *buffer to new_size bytes // return 1 on success, 0 on failure static int resize_buffer(char** buffer, size_t new_size) { char *new_buffer = realloc(*buffer, new_size); if (new_buffer) { *buffer = new_buffer; return 1; } else return 0; } // parse a JSON string value // expects the cursor to point after the initial double quote // return 1 on success, 0 on failure static int json_parse_string(const char** cursor, json_value* parent) { int success = 1; size_t length = 0, allocated = 8; // start with an 8-byte buffer char *new_string = malloc(allocated); if (!new_string) return 0; while (success && **cursor != """) { if (**cursor == "\0") { success = 0; // unterminated string } // we"re going to need at least one more byte of space while (success && length + 1 > allocated) { success = resize_buffer(&new_string, allocated *= 2); } if (!success) break; if (**cursor != "\\") { new_string[length++] = **cursor; // just copy normal bytes to output ++(*cursor); } else switch ((*cursor)[1]) { case "\\":new_string[length++] = "\\"; *cursor += 2; break; case "/": new_string[length++] = "/"; *cursor += 2; break; case """: new_string[length++] = """; *cursor += 2; break; case "b": new_string[length++] = "\b"; *cursor += 2; break; case "f": new_string[length++] = "\f"; *cursor += 2; break; case "n": new_string[length++] = "\n"; *cursor += 2; break; case "r": new_string[length++] = "\r"; *cursor += 2; break; case "t": new_string[length++] = "\t"; *cursor += 2; break; case "u": // TODO: handle Unicode escapes! (decode to UTF-8?) // note that this may require extending the buffer further default: success = 0; break; // invalid escape sequence } } success = success && resize_buffer(&new_string, length+1); if (!success) { free(new_string); return 0; } new_string[length] = "\0"; parent->type = TYPE_STRING; parent->value.string = new_string; ++(*cursor); // move cursor after final double quote return 1; } 

別の解決策は、入力に対して2つの解析パスを実行することです。1つは出力文字列の長さを決定するためだけで、もう1つは実際にデコードするためです。これは最も簡単に実行できます。このようなもの:

static int json_parse_string(const char** cursor, json_value* parent) { char *tmp_cursor = *cursor; size_t length = (size_t)-1; if (!json_string_helper(&tmp_cursor, &length, NULL)) return 0; char *new_string = malloc(length); if (!new_string) return 0; if (!json_string_helper(&tmp_cursor, &length, new_string)) { free(new_string); return 0; } parent->type = TYPE_STRING; parent->value.string = new_string; *cursor = tmp_cursor; return 1; } 

ヘルパー関数:

static int json_parse_helper(const char** cursor, size_t* length, char* new_string) { // ... } 

最大で バイトをnew_stringに書き込み、解析された文字列の実際の長さを*lengthに書き込みます。またはnew_string == NULLの場合、デコードされた出力を実際にどこにも保存せずに、文字列の長さを決定するだけです。

数値解析

現在のjson_parse_value()の実装では、数値がデフォルトのケースとして扱われ、"[{ntまたはfをC標準ライブラリ関数strtod()に追加します。

strtod()は有効なJSON数値リテラルのスーパーセット。これは機能するはずですが、コードがACになる場合があります。無効なJSONを有効として受け入れます。たとえば、コードは+nan-nan+inf-infは有効な数値であり、0xABC123のような16進表記も受け入れます。また、上記のリンク先のstrtod()ドキュメントのように:

標準の「C」以外のロケールまたは「POSIX」ロケールの場合、この関数は追加のロケール依存構文を認識する場合があります。

より厳密にしたい場合は、に対して数値のように見えるものを明示的に検証することをお勧めします。 strtod()に渡す前のdiv> JSON文法

strtod()strtod()を設定する場合もあることに注意してください。 div id = “ca585df5af”> 例入力番号がdoubleの範囲外の場合。おそらくこれをチェックする必要があります。

テスト

テストの詳細は確認していませんが、テストがあることを確認するのは素晴らしいことです(前述のとおりですが)。上記では、カバレッジが改善される可能性があります。

ただし、個人的には、テストを実装から別のソースファイルに移動したいと思います。これには長所と短所の両方があります。

  • 主な短所は、静的ヘルパー関数を直接テストできなくなることです。ただし、パブリックAPIはクリーンで包括的に見え、テストを複雑にする「隠された状態」の問題に悩まされていないことを考えると、APIだけでも良好なテストカバレッジを達成できるはずです。
  • 主な利点(実装コードとテストコードを明確に分離する以外)は、テストでパブリックAPIが自動的にテストされることです。特に、json.hヘッダーの問題は次のように表示されます。また、APIを介してテストを実行すると、APIが実際に十分に完全であり、一般的な使用に柔軟に対応できることを確認できます。

静的関数を直接テストしたい場合は、単純なラッパーを介して、または定義からstaticキーワードを削除するだけで、オプションでテスト用にそれらを公開するプリプロセッサーフラグをいつでも追加できます。

Ps。I json_test_value_number()テストが失敗していることに気づきました(GCC 5.4.0、i386 arch)、おそらくなぜなら、23.4という数字は浮動小数点で正確に表現できるわけではないからです。 23.5に変更すると、テストに合格します。

コメント

  • お疲れ様でした。’ t必要なだけ標準をチェックします。私は意図的にutf-8を無視しましたが(おそらくそれについて言及する必要がありました。
  • OK、十分に公平です。少なくとも\u00XX、一部のエンコーダーはASCII文字に対してもそれらを使用することを選択する可能性があるためです(また、以前にそれを行うのを忘れたので、上記のinlineとしていくつかの関数をマークする提案を追加しました。)
  • 標準のその部分を完全に見逃しました。はい、エスケープシーケンスを正しく解析する必要があります。

回答

これは完全なレビューではありませんが、コードを読んでいるときに気になったいくつかのことを共有します。

コメント

コメントは確かに素晴らしいです、あなたのインラインコメントのいくつかはコードにノイズだけを追加します。

// Eat whitespace int success = 0; skip_whitespace(cursor); 

まず第一に、コメントは1行早すぎます。第二に、関数を見ると、空白が消費されていることがわかります。名前はそれを完全に説明しています。追加のコメントは必要ありません。

case "\0": // If parse_value is called with the cursor at the end of the string // that"s a failure success = 0; break; 

繰り返しますが、このコメントはコード自体が言っていることを繰り返すだけです。


enum json_value_type { TYPE_NULL, TYPE_BOOL, TYPE_NUMBER, TYPE_OBJECT, // Is a vector with pairwise entries, key, value TYPE_ARRAY, // Is a vector, all entries are plain TYPE_STRING, TYPE_KEY }; 

これらのコメントは、各値が何を表すかを文書化しているため、実際には役に立たないわけではありません。しかし、なぜTYPE_OBJECTTYPE_ARRAYのみの場合、すべての値に対してではないのでしょうか。個人的には、その直前に json.org へのリンクを配置しました。enum。タイプは次のようになります。そこにあるものは、TYPE_KEYが何であるかを文書化するだけで済みます。これで次のポイントに進みます…

TYPE_KEY

json.org を見ると、オブジェクトが構成されていることがわかります。 メンバーのリスト。これは文字列で構成されます。つまり、

TYPE_STRING値と任意のタイプの別のjson値で構成される members の新しい構造体を追加するだけで、準備完了です。今すぐできます。たとえば、値のキーとして数値を使用しますが、これは許可されていません。forループのように、オブジェクト関連のロジックの一部も優れたものになります。

for (size_t i = 0; i < size; i += 2) 

皮肉なことに、このforループのステップでは、実際にはコメントを使用できますが(+= 2?)、コメントがありません。

その他

case "\0": // If parse_value is called with the cursor at the end of the string // that"s a failure success = 0; break; 

return 0;だけではないのはなぜですか?


while (iscntrl(**cursor) || isspace(**cursor)) ++(*cursor); 

および

if (success) ++(*cursor); 

および

if (has_char(cursor, "}")) break; else if (has_char(cursor, ",")) continue; 

およびその他のいくつか。私はそうではありません特に、条件とステートメントを同じ行に配置するのが好きです。特に、これを一貫して行っていないためです。if (!something) return;のように、制御フローのためにこれを実行しても大丈夫ですが、それでも「まあ」です。正しく実行し、ステートメントを新しい行に配置することをお勧めします。


また、コードで「領域」または「領域」と呼びたいものを区切るために、さらに空の行を使用できることがわかりました。例:

json_value key = { .type = TYPE_NULL }; json_value value = { .type = TYPE_NULL }; success = json_parse_value(cursor, &key); success = success && has_char(cursor, ":"); success = success && json_parse_value(cursor, &value); if (success) { vector_push_back(&result.value.object, &key); vector_push_back(&result.value.object, &value); } else { json_free_value(&key); break; } skip_whitespace(cursor); if (has_char(cursor, "}")) break; else if (has_char(cursor, ",")) continue; else success = 0; 

setup-and-parse-stuffとcheck-and-returnのものを区切る空の行が1つありますが、それは可能です。

json_value key = { .type = TYPE_NULL }; json_value value = { .type = TYPE_NULL }; success = json_parse_value(cursor, &key); success = success && has_char(cursor, ":"); success = success && json_parse_value(cursor, &value); if (success) { vector_push_back(&result.value.object, &key); vector_push_back(&result.value.object, &value); } else { json_free_value(&key); break; } skip_whitespace(cursor); if (has_char(cursor, "}")) break; else if (has_char(cursor, ",")) continue; else success = 0; 

これはずっときれいだと思います。値を設定するためのブロック、値を解析するためのブロック、値を置くためのブロックがあります。ベクトルに、空白をスキップするためのブロックと現在のアクションを終了するためのブロック。skip_whitespace(cursor);if ...の間の最後の空の行は議論の余地があります。 、しかし私はこの方法が好きです。


それ以外は、コードが読みやすく、理解しやすいことがわかりました。エラーがないか適切にチェックし、適切な命名を使用します。慣用性については、別として私が言ったことから、私が異常または非理想的であるとマークするものは何もありません。

回答

ctype.hの関数は、タイプ、未定義の動作を引き起こす可能性があるため。適切な説明については、 NetBSDのドキュメントを参照してください。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です