#include #include #include "argo.h" #include "global.h" #include "debug.h" ARGO_VALUE *argo_get_next_value(); char argo_get_next_char(FILE *f); int argo_get_prev_char(FILE *f); int print_error_helper(); int argo_read_string(ARGO_STRING *s, FILE *f); int argo_read_number(ARGO_NUMBER *n, FILE *f); ARGO_STRING *argo_read_number_helper(ARGO_CHAR *s, size_t length); int argo_read_true(ARGO_BASIC *b, FILE *f); int argo_read_false(ARGO_BASIC *b, FILE *f); int argo_read_null(ARGO_BASIC *b, FILE *f); int argo_read_object(ARGO_OBJECT *o, FILE *f); int argo_read_object_helper(ARGO_VALUE *v, FILE *f); int argo_read_array(ARGO_ARRAY *a, FILE *f); int argo_write_basic(ARGO_BASIC *b, FILE *f); int argo_write_object(ARGO_VALUE *o, FILE *f); int argo_write_array(ARGO_VALUE *a, FILE *f); int argo_write_number_helper(ARGO_STRING *s, FILE *f); int print_indent(FILE *f); char latest_char; int argo_chars_read_prev; /** * @brief Read JSON input from a specified input stream, parse it, * and return a data structure representing the corresponding value. * @details This function reads a sequence of 8-bit bytes from * a specified input stream and attempts to parse it as a JSON value, * according to the JSON syntax standard. If the input can be * successfully parsed, then a pointer to a data structure representing * the corresponding value is returned. See the assignment handout for * information on the JSON syntax standard and how parsing can be * accomplished. As discussed in the assignment handout, the returned * pointer must be to one of the elements of the argo_value_storage * array that is defined in the const.h header file. * In case of an error (these include failure of the input to conform * to the JSON standard, premature EOF on the input stream, as well as * other I/O errors), a one-line error message is output to standard error * and a NULL pointer value is returned. * * @param f Input stream from which JSON is to be read. * @return A valid pointer if the operation is completely successful, * NULL if there is any error. */ ARGO_VALUE *argo_read_value(FILE *f) { ARGO_VALUE *v = argo_get_next_value(); int i = argo_get_next_char(f); while (i != EOF) { if (argo_is_whitespace(i)) { i = argo_get_next_char(f); continue; } else if (argo_is_control(i)) { print_error_helper(i); return NULL; } else if (argo_is_digit(i) || i == '-') { argo_get_prev_char(f); if (argo_read_number(&v->content.number, f)) { return NULL; } else { v->type = ARGO_NUMBER_TYPE; return v; } } else if (i == '{') { if (argo_read_object(&v->content.object, f)) { return NULL; } else { v->type = ARGO_OBJECT_TYPE; return v; }; } else if (i == '[') { if (argo_read_array(&v->content.array, f)) { return NULL; } else { v->type = ARGO_ARRAY_TYPE; return v; }; } else if (i == '"') { if (argo_read_string(&v->content.string, f)) { return NULL; } else { v->type = ARGO_STRING_TYPE; return v; }; } else if (i == 't') { if (argo_read_true(&v->content.basic, f)) { return NULL; } else { v->type = ARGO_BASIC_TYPE; return v; }; } else if (i == 'f') { if (argo_read_false(&v->content.basic, f)) { return NULL; } else { v->type = ARGO_BASIC_TYPE; return v; } } else if (i == 'n') { if (argo_read_null(&v->content.basic, f)) { return NULL; } else { v->type = ARGO_BASIC_TYPE; return v; } } else { print_error_helper(); return NULL; } argo_get_next_char(f); } return NULL; } ARGO_VALUE *argo_get_next_value() { ARGO_VALUE *v = argo_value_storage; if (argo_next_value >= NUM_ARGO_VALUES) { fprintf(stderr, "Nums of values exceed the maxium \"%d\" values.", NUM_ARGO_VALUES); abort(); } v += argo_next_value++; return v; } int print_error_helper() { fprintf(stderr, "Unexpected charactor %c at [%d, %d].", latest_char, argo_lines_read, argo_chars_read); return -1; } char argo_get_next_char(FILE *f) { char c = fgetc(f); latest_char = c; argo_chars_read_prev = argo_chars_read; argo_chars_read++; if (c == '\n') { argo_lines_read++; argo_chars_read = 0; } return c; } int argo_get_prev_char(FILE *f) { char c = ungetc(latest_char, f); argo_chars_read--; if (c == '\n') { argo_lines_read--; argo_chars_read = argo_chars_read_prev; } return 0; } /** * @brief Read JSON input from a specified input stream, attempt to * parse it as a JSON string literal, and return a data structure * representing the corresponding string. * @details This function reads a sequence of 8-bit bytes from * a specified input stream and attempts to parse it as a JSON string * literal, according to the JSON syntax standard. If the input can be * successfully parsed, then a pointer to a data structure representing * the corresponding value is returned. * In case of an error (these include failure of the input to conform * to the JSON standard, premature EOF on the input stream, as well as * other I/O errors), a one-line error message is output to standard error * and a NULL pointer value is returned. * * @param f Input stream from which JSON is to be read. * @return Zero if the operation is completely successful, * nonzero if there is any error. */ int argo_read_string(ARGO_STRING *s, FILE *f) { ARGO_CHAR c = argo_get_next_char(f); while (c != EOF) { if (argo_is_control(c)) { return print_error_helper(); } if (c == '"') { break; } if (c == '\\') { c = argo_get_next_char(f); int tmp = 0, value = 0; switch (c) { case '/': c = '/'; break; case '"': c = '"'; break; case '\\': c = '\\'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'u': for (size_t i = 0; i < 4; i++) { c = argo_get_next_char(f); if (!argo_is_hex(c)) { return print_error_helper(); } if (argo_is_digit(c)) { tmp = c - '0'; } else if (c <= 'f' && c >= 'a') { tmp = c - 'a' + 10; } else { tmp = c - 'A' + 10; } value = value * 16 + tmp; } c = value; break; default: break; } } argo_append_char(s, c); c = argo_get_next_char(f); } return 0; } /** * @brief Read JSON input from a specified input stream, attempt to * parse it as a JSON number, and return a data structure representing * the corresponding number. * @details This function reads a sequence of 8-bit bytes from * a specified input stream and attempts to parse it as a JSON numeric * literal, according to the JSON syntax standard. If the input can be * successfully parsed, then a pointer to a data structure representing * the corresponding value is returned. The returned value must contain * (1) a string consisting of the actual sequence of characters read from * the input stream; (2) a floating point representation of the corresponding * value; and (3) an integer representation of the corresponding value, * in case the input literal did not contain any fraction or exponent parts. * In case of an error (these include failure of the input to conform * to the JSON standard, premature EOF on the input stream, as well as * other I/O errors), a one-line error message is output to standard error * and a NULL pointer value is returned. * * @param f Input stream from which JSON is to be read. * @return Zero if the operation is completely successful, * nonzero if there is any error. */ int argo_read_number(ARGO_NUMBER *n, FILE *f) { int has_exp = 0, num_started = 0, exp_started = 0, has_separator = 0, valid_int = 1, valid_float = 1, valid_string = 1, num_is_positive = 1, digits_after_separator = 0; long int_value = 0; double float_value = 0; ARGO_CHAR *string_value = argo_digits; size_t length = 0; char c = argo_get_next_char(f); while (1) { if (!(argo_is_digit(c) || c == '+' || c == '-' || c == 'e' || c == 'E' || c == '.')) { break; } if (length < 9) { ARGO_CHAR *ptr = string_value; ptr += length; if (c == 'E') { c = 'e'; } *ptr = c; length++; } else { valid_string = 0; } if (argo_is_digit(c)) { if (valid_int) { int_value = int_value * 10 + (c - '0'); if (num_started && int_value == 0 && c == '0') { return print_error_helper(); } } if (valid_float) { if (has_separator) { digits_after_separator++; int divide_num = 1; for (size_t i = 0; i < digits_after_separator; i++) { divide_num *= 10; } float_value += ((double)(c - '0') / (divide_num)); } else { float_value = float_value * 10 + (c - '0'); } } } switch (c) { case '-': if (!num_started) { num_is_positive = -1; c = argo_get_next_char(f); continue; } else if (has_exp && !exp_started) { } else { return print_error_helper(); } break; case '+': if (has_exp && !exp_started) { } else { return print_error_helper(); } break; case 'e': case 'E': if (has_exp) { return print_error_helper(); } valid_float = 0; valid_int = 0; has_exp = 1; c = argo_get_next_char(f); continue; break; case '.': if (has_separator) { return print_error_helper(); } valid_int = 0; has_separator = 1; break; default: break; } num_started = 1; if (has_exp) { exp_started = 1; } c = argo_get_next_char(f); } if (!valid_int && !valid_float && !valid_string) { fprintf(stderr, "num at [%d, %d] is in exponential format and too long(larger than 10 digits", argo_lines_read, argo_chars_read); } int_value *= num_is_positive; float_value *= num_is_positive; n->int_value = int_value; n->float_value = float_value; for (size_t i = 0; i < length; i++) { ARGO_CHAR *tmp = string_value; tmp += (i); argo_append_char(&n->string_value, *tmp); } ARGO_STRING *tmp = argo_read_number_helper(n->string_value.content, n->string_value.length); n->string_value.capacity = tmp->capacity; n->string_value.length = tmp->length; n->string_value.content = tmp->content; n->valid_int = valid_int; n->valid_float = valid_float; n->valid_string = valid_string; argo_get_prev_char(f); return 0; } ARGO_STRING *argo_read_number_helper(ARGO_CHAR *s, size_t length) { ARGO_VALUE *tmp = argo_get_next_value(); int *c = s; int num = 0, exp = 0, exp_tmp = 0, is_before_separator = 1, num_is_plus = 1, started = 0, exp_is_plus = 1, is_num = 1, integer_is_zero = 0, fractional_is_zero = 1; for (size_t index = 0; index < length; index++, c++) { if (is_num) { if (argo_is_digit(*c)) { num = num * 10 + (*c - '0'); if (is_before_separator && num) { exp += 1; } if (integer_is_zero && fractional_is_zero) { if (*c == '0') { exp -= 1; } else { fractional_is_zero = 0; } } } else if (argo_is_exponent(*c)) { started = 0, is_num = 0; continue; } else if (*c == '-' && !started) { num_is_plus = -1; } else if (*c == '+' && !started) { } else if (*c == '.' && is_before_separator) { is_before_separator = 0; if (!num) { integer_is_zero = 1; } } else { return NULL; } started = 1; } else { if (argo_is_digit(*c)) { exp_tmp = exp_tmp * 10 + (*c - '0'); } else if (*c == '-' && !started) { exp_is_plus = -1; } else if (*c == '+' && !started) { } else { return NULL; } started = 1; } } exp = exp_is_plus * exp_tmp + exp; if (num_is_plus == -1) { argo_append_char(&tmp->content.string, '-'); } while (num % 10 == 0 && num != 0) { num /= 10; } argo_append_char(&tmp->content.string, '0'); argo_append_char(&tmp->content.string, '.'); int rev = 0; while (num > 0) { int lsb = num % 10; num /= 10; rev = rev * 10 + lsb; } while (rev > 0) { int lsb = rev % 10; rev /= 10; argo_append_char(&tmp->content.string, (lsb + '0')); } if (exp) { argo_append_char(&tmp->content.string, 'e'); if (exp < 0) { argo_append_char(&tmp->content.string, '-'); exp *= -1; } while (exp > 0) { int lsb = exp % 10; exp /= 10; rev = rev * 10 + lsb; } while (rev > 0) { int lsb = rev % 10; rev /= 10; argo_append_char(&tmp->content.string, (lsb + '0')); } } return &tmp->content.string; } int argo_read_true(ARGO_BASIC *b, FILE *f) { char c = argo_get_next_char(f); if (c == 'r') { c = argo_get_next_char(f); if (c == 'u') { c = argo_get_next_char(f); if (c == 'e') { *b = ARGO_TRUE; return 0; } } } return print_error_helper(); } int argo_read_false(ARGO_BASIC *b, FILE *f) { char c = argo_get_next_char(f); if (c == 'a') { c = argo_get_next_char(f); if (c == 'l') { c = argo_get_next_char(f); if (c == 's') { c = argo_get_next_char(f); if (c == 'e') { *b = ARGO_FALSE; return 0; } } } } return print_error_helper(); } int argo_read_null(ARGO_BASIC *b, FILE *f) { char c = argo_get_next_char(f); if (c == 'u') { c = argo_get_next_char(f); if (c == 'l') { c = argo_get_next_char(f); if (c == 'l') { *b = ARGO_NULL; return 0; } } } return print_error_helper(); } int argo_read_object(ARGO_OBJECT *o, FILE *f) { char c = argo_get_next_char(f); o->member_list = argo_get_next_value(); ARGO_VALUE *starter = o->member_list; starter->type = ARGO_NO_TYPE; starter->next = starter; starter->prev = starter; ARGO_VALUE *prev = starter; ARGO_VALUE *next = argo_get_next_value(); while (1) { while (argo_is_whitespace(c)) { c = argo_get_next_char(f); } if (c == '}') { break; } else if (c == '\"') { if (argo_read_string(&next->name, f)) { return -1; } } else { return -1; } c = argo_get_next_char(f); while (argo_is_whitespace(c)) { c = argo_get_next_char(f); } if (c != ':') { return -1; } if (argo_read_object_helper(next, f)) { return -1; } next->prev = prev; prev->next = next; next->next = starter; starter->prev = next; prev = next; next = argo_get_next_value(); c = argo_get_next_char(f); while (argo_is_whitespace(c)) { c = argo_get_next_char(f); } if (c == '}') { break; } else if (c == ',') { c = argo_get_next_char(f); continue; } } return 0; } int argo_read_object_helper(ARGO_VALUE *v, FILE *f) { ARGO_VALUE *tmp = argo_read_value(f); if (tmp == NULL) { return -1; } v->type = tmp->type; v->content = tmp->content; return 0; } int argo_read_array(ARGO_ARRAY *a, FILE *f) { char c = argo_get_next_char(f); a->element_list = argo_get_next_value(); ARGO_VALUE *starter = a->element_list; starter->type = ARGO_NO_TYPE; starter->next = starter; starter->prev = starter; ARGO_VALUE *prev = starter; ARGO_VALUE *next = argo_get_next_value(); while (1) { while (argo_is_whitespace(c)) { c = argo_get_next_char(f); } if (c == ']') { break; } else { argo_get_prev_char(f); } if (argo_read_object_helper(next, f)) { return -1; } next->prev = prev; prev->next = next; next->next = starter; starter->prev = next; prev = next; next = argo_get_next_value(); c = argo_get_next_char(f); while (argo_is_whitespace(c)) { c = argo_get_next_char(f); } if (c == ']') { break; } else if (c == ',') { c = argo_get_next_char(f); continue; } } return 0; } /** * @brief Write canonical JSON representing a specified value to * a specified output stream. * @details Write canonical JSON representing a specified value * to specified output stream. See the assignment document for a * detailed discussion of the data structure and what is meant by * canonical JSON. * * @param v Data structure representing a value. * @param f Output stream to which JSON is to be written. * @return Zero if the operation is completely successful, * nonzero if there is any error. */ int argo_write_value(ARGO_VALUE *v, FILE *f) { switch (v->type) { case ARGO_BASIC_TYPE: return argo_write_basic(&v->content.basic, f); break; case ARGO_NUMBER_TYPE: return argo_write_number(&v->content.number, f); break; case ARGO_STRING_TYPE: return argo_write_string(&v->content.string, f); break; case ARGO_OBJECT_TYPE: return argo_write_object(v->content.object.member_list, f); break; case ARGO_ARRAY_TYPE: return argo_write_array(v->content.array.element_list, f); break; default: return 1; break; } return 0; } /** * @brief Write canonical JSON representing a specified string * to a specified output stream. * @details Write canonical JSON representing a specified string * to specified output stream. See the assignment document for a * detailed discussion of the data structure and what is meant by * canonical JSON. The argument string may contain any sequence of * Unicode code points and the output is a JSON string literal, * represented using only 8-bit bytes. Therefore, any Unicode code * with a value greater than or equal to U+00FF cannot appear directly * in the output and must be represented by an escape sequence. * There are other requirements on the use of escape sequences; * see the assignment handout for details. * * @param v Data structure representing a string (a sequence of * Unicode code points). * @param f Output stream to which JSON is to be written. * @return Zero if the operation is completely successful, * nonzero if there is any error. */ int argo_write_basic(ARGO_BASIC *b, FILE *f) { switch (*b) { case ARGO_NULL: fprintf(f, ARGO_NULL_TOKEN); break; case ARGO_TRUE: fprintf(f, ARGO_TRUE_TOKEN); break; case ARGO_FALSE: fprintf(f, ARGO_FALSE_TOKEN); break; default: return -1; break; } if (indent_level == 0 && global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); } return 0; } /** * @brief Write canonical JSON representing a specified number * to a specified output stream. * @details Write canonical JSON representing a specified number * to specified output stream. See the assignment document for a * detailed discussion of the data structure and what is meant by * canonical JSON. The argument number may contain representations * of the number as any or all of: string conforming to the * specification for a JSON number (but not necessarily canonical), * integer value, or floating point value. This function should * be able to work properly regardless of which subset of these * representations is present. * * @param v Data structure representing a number. * @param f Output stream to which JSON is to be written. * @return Zero if the operation is completely successful, * nonzero if there is any error. */ int argo_write_number(ARGO_NUMBER *n, FILE *f) { if (n->valid_int) { fprintf(f, "%ld", n->int_value); } else if (n->valid_float && n->float_value < 1 && n->float_value >= 0.1) { fprintf(f, "%f", n->float_value); } else if (n->valid_string) { return argo_write_number_helper(&n->string_value, f); } else { return -1; } if (indent_level == 0 && global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); } return 0; } int argo_write_number_helper(ARGO_STRING *s, FILE *f) { int *c = s->content; int num = 0, exp = 0, exp_tmp = 0, is_before_separator = 1, num_is_plus = 1, started = 0, exp_is_plus = 1, is_num = 1, integer_is_zero = 0, fractional_is_zero = 1; for (size_t index = 0; index < s->length; index++, c++) { if (is_num) { if (argo_is_digit(*c)) { num = num * 10 + (*c - '0'); if (is_before_separator && num) { exp += 1; } if (integer_is_zero && fractional_is_zero) { if (*c == '0') { exp -= 1; } else { fractional_is_zero = 0; } } } else if (argo_is_exponent(*c)) { started = 0, is_num = 0; continue; } else if (*c == '-' && !started) { num_is_plus = -1; } else if (*c == '+' && !started) { } else if (*c == '.' && is_before_separator) { is_before_separator = 0; if (!num) { integer_is_zero = 1; } } else { return -1; } started = 1; } else { if (argo_is_digit(*c)) { exp_tmp = exp_tmp * 10 + (*c - '0'); } else if (*c == '-' && !started) { exp_is_plus = -1; } else if (*c == '+' && !started) { } else { return -1; } started = 1; } } exp = exp_is_plus * exp_tmp + exp; if (num_is_plus == -1) { fprintf(f, "-"); } while (num % 10 == 0 && num != 0) { num /= 10; } fprintf(f, "0.%d", num); if (exp && num != 0) { fprintf(f, "e%d", exp); } return 0; } int argo_write_string(ARGO_STRING *s, FILE *f) { fprintf(f, "\""); int *c = s->content; for (size_t index = 0; index < s->length; index++, c++) { switch (*c) { case ARGO_BS: fprintf(f, "\\b"); break; case ARGO_FF: fprintf(f, "\\f"); break; case ARGO_LF: fprintf(f, "\\n"); break; case ARGO_CR: fprintf(f, "\\r"); break; case ARGO_HT: fprintf(f, "\\t"); break; case ARGO_BSLASH: fprintf(f, "\\\\"); break; case ARGO_QUOTE: fprintf(f, "\\\""); break; default: if (argo_is_control(*c)) { int tmp = 0xf; char lsb = *c & 0xff; if (lsb <= 0xf) { fprintf(f, "\\u000%x", lsb); } else { fprintf(f, "\\u00%x", lsb); } } else { if (*c <= 0xff) { fprintf(f, "%c", *c); } else if (*c <= 0xfff) { fprintf(f, "\\u0%x", *c); } else { fprintf(f, "\\u%x", *c); } } break; } } fprintf(f, "\""); if (indent_level == 0 && global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); } return 0; } int argo_write_object(ARGO_VALUE *o, FILE *f) { fprintf(f, "{"); indent_level++; ARGO_VALUE *ptr = o; while (ptr->next->type != ARGO_NO_TYPE) { print_indent(f); argo_write_string(&ptr->next->name, f); fprintf(f, ":"); if (global_options & PRETTY_PRINT_OPTION) { fprintf(f, " "); } argo_write_value(ptr->next, f); ptr = ptr->next; if (ptr->next->type != ARGO_NO_TYPE) { fprintf(f, ","); } } indent_level--; print_indent(f); fprintf(f, "}"); if (indent_level == 0 && global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); } return 0; } int argo_write_array(ARGO_VALUE *a, FILE *f) { fprintf(f, "["); indent_level++; ARGO_VALUE *ptr = a; while (ptr->next->type != ARGO_NO_TYPE) { print_indent(f); argo_write_value(ptr->next, f); ptr = ptr->next; if (ptr->next->type != ARGO_NO_TYPE) { fprintf(f, ","); } } indent_level--; print_indent(f); fprintf(f, "]"); if (indent_level == 0 && global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); } return 0; } int print_indent(FILE *f) { if (global_options & PRETTY_PRINT_OPTION) { fprintf(f, "\n"); for (size_t i = 0; i < indent_level; i++) { for (size_t j = 0; j < (global_options & 0x0fffffff); j++) { fprintf(f, " "); } } } return 0; }