Простой консоли в JSON форматирования


Я пишу простого форматирования в формат JSON. Считывает JSON-данные из stdin и записывает форматированный вывод в stdout.

Цели:

  • учитывая допустимый создавать правильно форматированный вывод в формате JSON
  • постоянное использование памяти
  • минимальный жизнеспособный программы (маленькая программа, которая решает определенную проблему)
  • характеристика бесплатно
  • легко читать и понимать
  • быть совместим с C99

Не цели:

  • проверка в JSON
  • обработка аргументов

Я не отношу проверки JSON прямо сейчас (как сказал "праматерии за допустимый JSON").

Я попробовал добавить аргументы (как установка заполнитель строки с -p) но столкнулся с рядом проблем:

  1. непонятно, какие аргументы должны быть реализованы
  2. Я не чувствую себя комфортно, пока пишу код парсинга аргументов.

Так я пропустила эти сейчас.

Последний файл в GitHub: https://github.com/sineemore/juu/blob/master/juu.c

#include <stdio.h>
#include <stdlib.h> /* EXIT_SUCCESS */

#define BUF_SIZE (1024 * 4)

int main(int argc, char **argv) {

    char buf[BUF_SIZE];
    const char placeholder[] = "  ";
    unsigned int indent = 0;
    unsigned int i = 0;
    unsigned int k = 0;
    char is_string = 0;
    char escaped = 0;
    char ch;
    size_t n;

    while (0 < (n = fread(&buf, sizeof(char), BUF_SIZE, stdin))) {
        for (k = 0; k < n; k++) {
            ch = buf[k];

            if (is_string) {
                /* Inside quoted string */
                putchar(ch);
                if (! escaped) {
                    if (ch == '"') {
                        /* Unescaped quote, string just ended */
                        is_string = 0;
                    } else if (ch == '\\') {
                        escaped = 1;
                    }
                } else {
                    escaped = 0;
                }
                continue;
            }

            switch (ch) {

            case ' ':
            case '\t':
            case '\n':
            case '\r':
                /* Ignoring original formatting */
                break;

            case '{':
            case '[':
                putchar(ch);
                putchar('\n');
                i = ++indent;
                while (i-- > 0) fputs(placeholder, stdout);
                break;

            case '}':
            case ']':
                putchar('\n');
                i = --indent;
                while (i-- > 0) fputs(placeholder, stdout);
                putchar(ch);
                if (indent == 0) putchar('\n');
                break;

            case ',':
                putchar(',');
                putchar('\n');
                i = indent;
                while (i-- > 0) fputs(placeholder, stdout);
                break;

            case ':':
                putchar(':');
                putchar(' ');
                break;

            case '"':
                /* String/property key start, see if clause on top (line 20) */
                putchar('"');
                is_string = 1;
                break;

            default:
                /* Numbers, true, false, null */
                putchar(ch);
                break;
            }
        }
    }

    return EXIT_SUCCESS;
}

Пример вывода:

$ wget -qO- 'https://xkcd.com/info.0.json' | juu
{
  "month": "4",
  "num": 1979,
  "link": "",
  "year": "2018",
  "news": "",
  "safe_title": "History",
  "transcript": "",
  "alt": "HISTORIANS: We've decided to trim the past down to make things more manageable. Using BCE/CE, would you rather we lose the odd-numbered or even-numbered years?",
  "img": "https://imgs.xkcd.com/comics/history.png",
  "title": "History",
  "day": "11"
}

Обновление:

  • Я пропустил все ferror() звонки, фиксация этой части.
  • Также программа не справится SIGPIPEпоэтому он может быть убит. Просто проверял его. Я не вижу четкого решения, я должен задать SIG_IGN?


201
7
задан 12 апреля 2018 в 02:04 Источник Поделиться
Комментарии
2 ответа

Мы посмотрим на ваш код от самого верха до самого низа. Нужный отступ делает это легко.

Магия чисел и определяет

Во-первых, это здорово, что вы использовали BUF_SIZE вместо магических чисел, например

char buf[1024 * 4]; // bad!

Однако #defineS может быть подвержен ошибкам. Вы использовали скобки, которые часто необходимы. Кроме того, BUF_SIZE не существует в скомпилированную программу Больше, что может привести к путанице, если вы хотите отлаживать. Итак, рассмотрим возможные альтернативы. В этом случае #define нормально. Но нет никакой необходимости для сокращения:

//! Buffer size for reading from stdin.
#define BUFFER_SIZE (1024 * 4)

Пока мы на него, добавить некоторые документы. BUFFER_SIZE и buf[BUFFER_SIZE] всего несколько строчек друг от друга, но это может измениться позже. В ! после // это Doxygen для документирования конкретных, вы можете игнорировать это, если вы не используйте Doxygen.

Объявления и инициализации

Вы использовать C99 и поэтому можно объявлять переменные как поздно, как вы хотите. Всякий раз, когда вы объявляете переменную, но установить его намного позже, постарайтесь переписать его инициализации, в правильный момент. Например, ch не используется пока ch = buf[k]. Мы должны сохранить это сфера общества. Таким образом, мы не случайно переменные повторного использования.

Если следовать этому предложению, то i, k и ch получают ограниченный в своей области. Мы хоть посмотрим на это позже. И так как мы уже переименовали BUF_SIZE для BUFFER_SIZEмы могли бы также переименовать buf для buffer. Конечно, вы можете выбрать другие имена, но опять же: нет никакой необходимости сокращать. Дисковое пространство не дорого больше, так что выбирайте имена, которые вы до сих пор понять после нескольких месяцев или лет, когда кто-то звонит вам в середине ночи.

Входные и sizeof использование

fread(...,..., SIZE ,...) может не вернуться SIZE. Что может произойти, если вы в конце файла или при возникновении ошибки. Вы должны проверить FILE* С feof или ferror если это произойдет.

Мы остановились в fread. Хотя маловероятно, что вы измените buffer'ы типа, это обычно хорошая практика, чтобы использовать sizeof(*buf) или sizeof(buf[0]). Если вы когда-нибудь изменить char buffer[BUFFER_SIZE] для mychar buffer[BUFFER_SIZE]вы не должны помнить, чтобы изменить sizeof(char) для sizeof(mychar).

Состояние машин и повторения

Ваш цикл-это по сути государственная машина. Сама государственная машина выглядит нормально. Однако, есть много повторов. У нас есть

while (i-- > 0) fputs(placeholder, stdout);

четыре раза. Что действительно запрашивает функцию:

/**
* \brief Puts the given \c str \c count times on \c stream.
* \param str null-terminated character string to be written
* \param count number of times \c str shall be written
* \param stream output stream
* \returns a non-negative value on success
* \returns EOF on error and sets the error indicator
*/
inline void fputs_repeat(const char * str, size_t count, FILE * stream) {
int value = 0;
while (count-- > 0) {
value = fputs(str, stream);
if (value == EOF) {
return EOF;
}
}
return value;
}

Теперь мы можем просто использовать fputs_repeat(placeholder, indentation, stdout) везде, где вы использовали while (i-- > 0) .... Нам бы сейчас в итоге получим следующий вариант:

#include <stdio.h>
#include <stdlib.h> /* EXIT_SUCCESS */

#define BUFFER_SIZE (1024 * 4)

inline void fputs_repeat(const char * str, size_t count, FILE * stream) {
int value = 0;
while (count-- > 0) {
value = fputs(str, stream);
if (value == EOF) {
return EOF;
}
}
return value;
}

int main(int argc, char **argv) {

char buffer[BUF_SIZE] = {0};
const char placeholder[] = " ";
unsigned int indent = 0;
char is_string = 0;
char escaped = 0;
size_t n;

while (0 < (n = fread(&buffer, sizeof(buffer[0]), BUFFER_SIZE, stdin))) {
// exercise: add error handling
for (unsigned int k = 0; k < n; k++) {
char ch = buffer[k];

if (is_string) {
/* Inside quoted string */
putchar(ch);
if (! escaped) {
if (ch == '"') {
/* Unescaped quote, string just ended */
is_string = 0;
} else if (ch == '\\') {
escaped = 1;
}
} else {
escaped = 0;
}
continue;
}

switch (ch) {

case ' ':
case '\t':
case '\n':
case '\r':
/* Ignoring original formatting */
break;

case '{':
case '[':
putchar(ch);
putchar('\n');
fputs_repeat(placeholder, ++indent, stdout);
break;

case '}':
case ']':
putchar('\n');
fputs_repeat(placeholder, --indent, stdout);
putchar(ch);
if (indent == 0) putchar('\n');
break;

case ',':
putchar(',');
putchar('\n');
fputs_repeat(placeholder, indent, stdout);
break;

case ':':
putchar(':');
putchar(' ');
break;

case '"':
/* String/property key start, see if clause on top */
putchar('"');
is_string = 1;
break;

default:
/* Numbers, true, false, null */
putchar(ch);
break;
}
}
}

return EXIT_SUCCESS;
}

Выход

Вы используете putchar довольно часто. В некоторых случаях многократные звонки могут вам заменить puts или fputs. Например, вместо

putchar(',');
putchar('\n');

вы могли бы просто использовать

puts(",");

и вместо того, чтобы

putchar(':');
putchar(' ');

вы могли бы использовать

fputs(": ", stdout);

В любом случае, если вы стремитесь к эффективности, вы хотите сохранить количество вызовов функций минимум, вот и считайте буфер вывода, Если ваш текущий вариант не достаточно быстро, по своему вкусу. Но сначала измерьте вашу программу, прежде чем изменять его.

Цели

Давайте вернемся к вашей цели и проверить их сейчас.



  • учитывая допустимый создавать правильно форматированный вывод в формате JSON


Поскольку вы не вводить дополнительные символы в строках и никогда не удаляет персонажей из оригинальной JSON, за исключением пробела (вне строк), вы достигли этой цели.



  • постоянное использование памяти


Поскольку есть только один буфер, вы достигли этой цели тоже, хотя fputs может буфер.



  • минимальный жизнеспособный программы (маленькая программа, которая решает определенную проблему)


Ах, это проблема определения. Какой минимальный? Что такое "наименьший"? Ваша программа-это короткий, но один дополнительный функции удалены некоторые повторения, которые могут привести к техническим долгом. Эта функция не увеличивает размер вашей программы, хотя.



  • характеристика бесплатно


Проверить.



  • легко читать и понимать


В ! escaped логика немного взял, но, кроме этого, цель достигнута.



  • быть совместим с C99


Да, но использовать те функции, которые я упомянул выше (встроенные функции, опоздание с подачей декларации).


Также программа не справится SIGPIPEпоэтому он может быть убит. Просто проверял его. Я не вижу четкого решения, я должен задать SIG_IGN?

Если входная труба сломана, входной JSON будет неожиданно закончиться и у вас есть недопустимый формат JSON. Вы должны обращаться недопустимый формат JSON в этот момент? Это не цель, как вы сказали.

4
ответ дан 13 апреля 2018 в 05:04 Источник Поделиться

Отвечая на свой собственный вопрос.

Проверка ошибок

Звонки fread, putchar и fputs не тест для ВФ.

Проверить fread ошибка сразу после цикла while:

if (ferror(stdin)) {
return EXIT_FAILURE;
}

все вызовы putchar и fputs может быть заменен на фантики:

static void outc(char ch) {
if (EOF == putchar(ch)) {
exit(EXIT_FAILURE);
}
}

static void outs(const char *str) {
if (EOF == fputs(str, stdout)) {
exit(EXIT_FAILURE);
}
}

1
ответ дан 13 апреля 2018 в 01:04 Источник Поделиться