Считайте каждое выражение в строку, затем проанализируйте его, используя sscanf()
и шаблон %n
, чтобы получить длину проанализированной части. (Просто обратите внимание, что %n
не учитывается как преобразование большинством библиотек Си.)
Например:
#include <stdlib.h>
#include <stdio.h>
#define STRINGIFY_(x) #x
#define STRINGIFY(x) STRINGIFY_(x)
#ifndef MAX_WORD_LEN
#define MAX_WORD_LEN 31
#endif
#define SCAN_WORD "%" STRINGIFY(MAX_WORD_LEN) "[^\t\n\v\f\r <,;]"
const char *parse(const char *spec,
void (*callback)(const char *less, const char *greater))
{
char less[MAX_WORD_LEN+1];
char more[MAX_WORD_LEN+1];
int len;
while (1) {
/* Skip an optional semicolon. */
len = -1;
if (sscanf(spec, " ; %n", &len) >= 0 && len > 0)
spec += len;
/* Try parsing the first pair. */
len = -1;
if (sscanf(spec, " " SCAN_WORD " < " SCAN_WORD " %n", less, more, &len) < 2 || len < 0)
break;
/* Report it. */
if (callback)
callback(less, more);
/* Scan additional right sides. */
spec += len;
while (1) {
len = -1;
if (sscanf(spec, " , " SCAN_WORD " %n", more, &len) < 1 || len < 0)
break; /* Only out of this inner while loop. */
/* Report this one too. */
if (callback)
callback(less, more);
spec += len;
}
}
/* Return a pointer to the first unparsed character. */
return spec;
}
Пример программы для применения ее к каждому выражению, указанному в командной строке:
void report(const char *left, const char *right)
{
printf(" %s < %s\n", left, right);
}
int main(int argc, char *argv[])
{
int arg;
const char *end;
for (arg = 1; arg < argc; arg++) {
printf("%s:\n", argv[arg]);
end = parse(argv[arg], report);
if (*end)
printf("but with '%s' not parsed.\n", end);
else
printf("completely parsed.\n");
}
return EXIT_SUCCESS;
}
Если вы скомпилируете вышесказанное, чтобы сказать example
, и вы запустите
./example 'foo < bar foo < baz, war war < bar'
программа выведет
foo < bar foo < baz, war war < bar:
foo < bar
foo < baz
foo < war
war < bar
completely parsed.
То есть функция обратного вызова (report()
, выше) будет вызываться один раз для каждой уникальной пары. Он принимает слова как имена, а не только буквы. Каждое «слово» может быть длиной до 31 символа и может содержать любые символы, кроме пробелов, <
, ,
или ;
.
Для полноты он допускает точки с запятой между подвыражениями и игнорирует их.
Сама логика проста.
Сначала parse()
пытается отсканировать точку с запятой. Любые пробелы вокруг него также будут пропущены.
Затем он пытается отсканировать пару «слов» с <
между ними. Если это не удается, он выходит из бесконечного цикла и возвращает указатель на первый неразобранный символ. Он будет указывать на байт NUL (\0
), если синтаксис выражения был правильным.
Если пара была отсканирована, для нее вызывается функция обратного вызова (если только функция обратного вызова не равна NULL).
Затем внутренний цикл пытается отсканировать запятую, за которой следует «слово». Пока это успешно, для пары будет вызываться функция обратного вызова (используя исходную левую сторону и это новое «слово» в качестве правой стороны). В противном случае мы вырываемся из внутреннего цикла и начинаем новую итерацию внешнего цикла.
Итак, если мы посмотрим, скажем, foo < bar foo < baz, war war < bar
, то foo < bar
будет проанализирован первой итерацией внешнего цикла; foo < baz
будет проанализирован второй итерацией внешнего цикла, а , war
- внутренним циклом; и, наконец, war < bar
будет проанализирован третьей итерацией самого внешнего цикла.