В общем, анализ текста довольно сложен. Заданная вами грамматика довольно проста, но более сложные грамматики действительно выигрывают от lexing и синтаксического анализа. Вместо того чтобы писать это вручную, в примере я использовал re2c . re2c
принимает в качестве входных данных файл C
с /*!re2c
комментариями и превращает их в быстрый лексер.
x-macro позволяет распечатывать символы и является супер-полезно для отладки, но не требуется в противном случае.
Ccre:
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
/* X-Macro. */
#define PARAM(A) A
#define STRINGISE(A) #A
#define SYMBOL(X) X(END), X(LBRACK), X(RBRACK), X(NUMBER), X(COMMA)
enum Symbol { SYMBOL(PARAM) };
static const char *const symbols[] = { SYMBOL(STRINGISE) };
/* Once the input is read, re2c will lex it; this stores the cursors. */
static struct { const char *from, *to; } scanner;
/*!re2c
re2c:yyfill:enable = 0;
re2c:define:YYCTYPE = char;
re2c:define:YYCURSOR = scanner.to;
eof = "\x00"; // If you git re2c 2, there's a simpler new way.
whitespace = [ \t\v\f\n\r];
digit = [0-9];
number = "-"? digit+; */
static enum Symbol next(void) {
reset:
scanner.from = scanner.to;
/*!re2c
* { return errno = EILSEQ, END; }
eof { return END; }
whitespace+ { goto reset; }
"{" { return LBRACK; }
"}" { return RBRACK; }
number { return NUMBER; }
"," { return COMMA; }
*/
}
/* Define `CharArray`, a vector of characters -- dynamic string in which we
will store the entire input. */
#define ARRAY_NAME Char
#define ARRAY_TYPE char
#include "BasicArray.h"
struct Token {
enum Symbol symbol;
const char *from;
int length;
};
#define ARRAY_NAME Token
#define ARRAY_TYPE struct Token
#include "BasicArray.h"
/** Fills the `token` with `symbol` and the most recent scanner values. Returns
whether it worked. */
static int init_token(struct Token *const token, const enum Symbol symbol) {
assert(token && symbol && scanner.from && scanner.from < scanner.to);
if(scanner.from + INT_MAX < scanner.to) return errno = EILSEQ, 0;
token->symbol = symbol;
token->from = scanner.from;
token->length = (int)(scanner.to - scanner.from);
return 1;
}
#define ARRAY_NAME Int
#define ARRAY_TYPE int
#include "BasicArray.h"
struct Matrix {
struct IntArray ints;
size_t cols;
};
/** Returns true if the `tokens` are a valid matrix, and puts it in `matrix`
which must be empty. It doesn't allow empty matrices. */
static int parse(const struct TokenArray *const tokens,
struct Matrix *const matrix) {
size_t set_col = 0, col = 0;
int is_set_col = 0;
long input;
int *output;
const struct Token *t = 0;
assert(tokens && matrix && !IntArraySize(&matrix->ints));
errno = 0;
/* Left outside. */
t = TokenArrayNext(tokens, t);
if(!t || t->symbol != LBRACK) return errno = EILSEQ, 0;
goto left_middle;
left_middle:
t = TokenArrayNext(tokens, t);
if(!t || t->symbol != LBRACK) return errno = EILSEQ, 0;
col = 0;
goto inside_number;
inside_number:
t = TokenArrayNext(tokens, t);
if(!t || t->symbol != NUMBER) return errno = EILSEQ, 0;
input = strtol(t->from, 0, 0);
if(errno) return 0;
if(input < INT_MIN || input > INT_MAX) return errno = ERANGE, 0;
if(!(output = IntArrayNew(&matrix->ints))) return 0;
*output = input;
col++;
goto end_number;
end_number:
if(!(t = TokenArrayNext(tokens, t))) return errno = EILSEQ, 0;
if(t->symbol == RBRACK) {
if(is_set_col) {
if(set_col != col) return errno = EILSEQ, 0;
} else {
set_col = col;
is_set_col = 1;
}
goto finished_middle;
} else if(t->symbol == COMMA) {
goto inside_number;
} else {
return errno = EILSEQ, 0;
}
finished_middle:
if(!(t = TokenArrayNext(tokens, t))) return errno = EILSEQ, 0;
if(t->symbol == RBRACK) {
goto end_matrix;
} else if(t->symbol == COMMA) {
goto left_middle;
} else {
return errno = EILSEQ, 0;
}
end_matrix:
if((t = TokenArrayNext(tokens, t))) return 0;
assert(is_set_col);
matrix->cols = set_col;
return 1;
}
int main(void) {
struct TokenArray tokens = ARRAY_ZERO;
struct CharArray buffer = ARRAY_ZERO;
struct Matrix matrix = { ARRAY_ZERO, 0 };
enum Symbol symbol;
const size_t granularity = 1024;
size_t nread, i, n;
const int *ints;
int success = EXIT_SUCCESS;
/* Read all contents from `stdin` at once. */
do {
char *read_here;
if(!(read_here = CharArrayBuffer(&buffer, granularity))
|| (nread = fread(read_here, 1, granularity, stdin), ferror(stdin))
|| !CharArrayExpand(&buffer, nread)) goto catch;
} while(nread == granularity);
/* Embed '\0' on the end for simple lexing. */
{
char *zero = CharArrayNew(&buffer);
if(!zero) goto catch;
*zero = '\0';
}
/* We use simplified sentinel method of detecting EOF, no embedded '\0'. */
{
const char *const b = CharArrayGet(&buffer);
const size_t len_to_zero = (size_t)(strchr(b, '\0') - b);
if(len_to_zero != CharArraySize(&buffer) - 1) {
errno = EILSEQ;
fprintf(stderr, "Embedded '\\0' at byte %lu/%lu.\n",
(unsigned long)len_to_zero,
(unsigned long)CharArraySize(&buffer) - 1);
goto catch;
}
}
/* Point the `scanner` to the `buffer`. */
scanner.to = CharArrayGet(&buffer);
/* Scan all input. */
while((symbol = next())) {
struct Token *const token = TokenArrayNew(&tokens);
if(!token || !init_token(token, symbol)) goto catch;
printf("%.*s -> %s\n", token->length, token->from,
symbols[token->symbol]);
}
/* `errno` will be set if a syntax error occurs. */
if(errno) goto catch;
/* Parse input if it is a valid matrix. */
if(!parse(&tokens, &matrix)) goto catch;
/* Print the matrix. */
n = IntArraySize(&matrix.ints);
ints = IntArrayGet(&matrix.ints);
printf("matrix columns %lu.\n", (unsigned long)matrix.cols);
for(i = 0; i < n; i++)
printf("%4d%s", ints[i],
(i % matrix.cols) == (matrix.cols - 1) ? "\n" : ", ");
printf("\n");
goto finally;
catch:
perror("Something went wrong");
success = EXIT_FAILURE;
finally:
IntArray_(&matrix.ints);
TokenArray_(&tokens);
CharArray_(&buffer);
return success;
}
Не совсем уместно на ваш вопрос, но если кто-то хочет попробовать мой код как написано, ему нужно BasicArray.h
. Можно было бы поместить максимальные массивы фиксированного размера, но проект, над которым я работаю, имеет динамические массивы переменного размера, поэтому я использовал это.
#include <stdlib.h> /* realloc free */
#include <assert.h> /* assert */
#include <errno.h> /* errno */
/* Check defines. */
#ifndef ARRAY_NAME /* <-- error */
#error Generic ARRAY_NAME undefined.
#endif /* error --> */
#ifndef ARRAY_TYPE /* <-- error */
#error Generic ARRAY_TYPE undefined.
#endif /* --> */
/* Generics using the preprocessor;
\url{ http://stackoverflow.com/questions/16522341/pseudo-generics-in-c }. */
#ifdef CAT
#undef CAT
#endif
#ifdef CAT_
#undef CAT_
#endif
#ifdef PCAT
#undef PCAT
#endif
#ifdef PCAT_
#undef PCAT_
#endif
#ifdef T
#undef T
#endif
#ifdef T_
#undef T_
#endif
#ifdef PT_
#undef PT_
#endif
#define CAT_(x, y) x ## y
#define CAT(x, y) CAT_(x, y)
#define PCAT_(x, y) x ## _ ## y
#define PCAT(x, y) PCAT_(x, y)
#define T_(thing) CAT(ARRAY_NAME, thing)
#define PT_(thing) PCAT(array, PCAT(ARRAY_NAME, thing))
typedef ARRAY_TYPE PT_(Type);
#define T PT_(Type)
struct T_(Array);
struct T_(Array) {
T *data;
/* Fibonacci; data -> (c0 < c1 || c0 == c1 == max_size). */
size_t capacity, next_capacity;
/* !data -> !size, data -> size <= capacity */
size_t size;
};
/* `{0}` is `C99`. */
#ifndef ARRAY_ZERO /* <-- !zero */
#define ARRAY_ZERO { 0, 0, 0, 0 }
#endif /* !zero --> */
static int PT_(reserve)(struct T_(Array) *const a,
const size_t min_capacity, T **const update_ptr) {
size_t c0, c1;
T *data;
const size_t max_size = (size_t)-1 / sizeof(T *);
assert(a);
if(!a->data) {
if(!min_capacity) return 1;
c0 = 8;
c1 = 13;
} else {
if(min_capacity <= a->capacity) return 1;
c0 = a->capacity;
c1 = a->next_capacity;
}
if(min_capacity > max_size) return errno = ERANGE, 0;
assert(c0 < c1);
/* Fibonacci: c0 ^= c1, c1 ^= c0, c0 ^= c1, c1 += c0; */
while(c0 < min_capacity) {
size_t temp = c0 + c1; c0 = c1; c1 = temp;
if(c1 > max_size || c1 < c0) c1 = max_size;
}
if(!(data = realloc(a->data, c0 * sizeof *a->data))) return 0;
if(update_ptr && a->data != data)
*update_ptr = data + (*update_ptr - a->data);
a->data = data;
a->capacity = c0;
a->next_capacity = c1;
return 1;
}
static void PT_(array)(struct T_(Array) *const a) {
assert(a);
a->data = 0;
a->capacity = 0;
a->next_capacity = 0;
a->size = 0;
}
static void T_(Array_)(struct T_(Array) *const a) {
if(!a) return;
free(a->data);
PT_(array)(a);
}
static void T_(Array)(struct T_(Array) *const a) {
if(!a) return;
PT_(array)(a);
}
static size_t T_(ArraySize)(const struct T_(Array) *const a) {
if(!a) return 0;
return a->size;
}
static T *T_(ArrayGet)(const struct T_(Array) *const a) {
return a ? a->data : 0;
}
static T *T_(ArrayNext)(const struct T_(Array) *const a, const T *const here) {
const T *data;
size_t idx;
if(!a) return 0;
if(!here) {
data = a->data;
idx = 0;
} else {
data = here + 1;
idx = (size_t)(data - a->data);
}
return idx < a->size ? (T *)data : 0;
}
static T *PT_(new)(struct T_(Array) *const a, T **const update_ptr) {
assert(a);
if(!PT_(reserve)(a, a->size + 1, update_ptr)) return 0;
return a->data + a->size++;
}
static T *T_(ArrayNew)(struct T_(Array) *const a) {
if(!a) return 0;
return PT_(new)(a, 0);
}
static T *T_(ArrayBuffer)(struct T_(Array) *const a, const size_t buffer) {
if(!a || !buffer || !PT_(reserve)(a, a->size + buffer, 0)) return 0;
return a->data + a->size;
}
static int T_(ArrayExpand)(struct T_(Array) *const a, const size_t add) {
if(!a) return 0;
if(add > a->capacity || a->size > a->capacity - add)
return errno = ERANGE, 0;
a->size += add;
return 1;
}
/* Prototype. */
static void PT_(unused_coda)(void);
/** This silences unused function warnings from the pre-processor, but allows
optimisation, (hopefully.)
\url{ http://stackoverflow.com/questions/43841780/silencing-unused-static-function-warnings-for-a-section-of-code } */
static void PT_(unused_set)(void) {
T_(Array_)(0);
T_(Array)(0);
T_(ArraySize)(0);
T_(ArrayGet)(0);
T_(ArrayNext)(0, 0);
T_(ArrayNew)(0);
T_(ArrayBuffer)(0, 0);
T_(ArrayExpand)(0, 0);
PT_(unused_coda)();
}
/** {clang}'s pre-processor is not fooled if you have one function. */
static void PT_(unused_coda)(void) { PT_(unused_set)(); }
/* Un-define all macros. */
#undef ARRAY_NAME
#undef ARRAY_TYPE
#undef CAT
#undef CAT_
#undef PCAT
#undef PCAT_
#undef T
#undef T_
#undef PT_
Один компилирует это:
re2c -W -o C.c C.c.re
gcc -Wall -Wextra -O3 -pedantic -ansi C.c
Введите это:
{{77,88,}}
^D
Получите:
{ -> LBRACK
{ -> LBRACK
77 -> NUMBER
, -> COMMA
88 -> NUMBER
, -> COMMA
} -> RBRACK
} -> RBRACK
Something went wrong: Illegal byte sequence
Введите это:
{{9, -8}, {-7, 4}, {2, 0} }
^D
Получите:
{ -> LBRACK
{ -> LBRACK
9 -> NUMBER
, -> COMMA
-8 -> NUMBER
} -> RBRACK
, -> COMMA
{ -> LBRACK
-7 -> NUMBER
, -> COMMA
4 -> NUMBER
} -> RBRACK
, -> COMMA
{ -> LBRACK
2 -> NUMBER
, -> COMMA
0 -> NUMBER
} -> RBRACK
} -> RBRACK
matrix columns 2.
9, -8
-7, 4
2, 0
Оглядываясь назад,это, вероятно, излишне для этой проблемы, но хорошо масштабируется. Кроме того, при комплексном тестировании обычно проще тестировать лексер отдельно от парсера, который, как правило, использует гораздо меньше символов.