From be09f082553bd95ff7a14e87186ee8beb84f5b2c Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Tue, 3 Dec 2024 14:48:05 +0000 Subject: [PATCH 01/13] Create index inside writer and transformation This creates the index while writing the file saving an extra pass --- app/Makefile | 2 +- app/ext_example/Makefile | 18 +++++++++++------- app/sheet/index.c | 3 ++- app/sheet/transformation.c | 24 +++++++++++++++++++----- app/sheet/transformation.h | 1 + app/utils/index.c | 4 +--- app/utils/writer.c | 20 ++++++++++++++++---- include/zsv/utils/index.h | 5 ++--- include/zsv/utils/writer.h | 1 + 9 files changed, 54 insertions(+), 24 deletions(-) diff --git a/app/Makefile b/app/Makefile index 0628f18d..5b27b162 100644 --- a/app/Makefile +++ b/app/Makefile @@ -323,7 +323,7 @@ ${ZSV_UTIL_A}: ${BUILD_DIR}/objs/utils/util.a @mkdir -p `dirname $@` cp -p $< $@ -UTIL_A_OBJ:=writer file dirs-no-jq os ${UTIL_A_OBJ_WIN} +UTIL_A_OBJ:=index writer file dirs-no-jq os ${UTIL_A_OBJ_WIN} UTIL_A_OBJ:=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTIL_A_OBJ})) ${BUILD_DIR}/objs/utils/util.a: ${UTIL_A_OBJ} diff --git a/app/ext_example/Makefile b/app/ext_example/Makefile index c8cd1a67..3d562f85 100644 --- a/app/ext_example/Makefile +++ b/app/ext_example/Makefile @@ -77,7 +77,6 @@ TEST_PASS=printf "${COLOR_BLUE}$@: ${COLOR_GREEN}Passed${COLOR_NONE}\n" TEST_FAIL=(printf "${COLOR_BLUE}$@: ${COLOR_RED}Failed!${COLOR_NONE}\n" && exit 1) TEST_INIT=printf "${COLOR_PINK}$@: ${COLOR_NONE}\n" -UTILS1= CFLAGS_SHARED=-shared ifneq ($(findstring emcc,$(CC)),) # emcc CFLAGS_SHARED=-s SIDE_MODULE=1 -s LINKABLE=1 @@ -86,9 +85,6 @@ else INSTALLED_EXTENSION= endif -UTILS1+=writer -UTILS=$(addprefix ${BUILD_DIR}/objs/utils/,$(addsuffix .o,${UTILS1})) - CFLAGS+= -I${THIS_LIB_BASE}/include -I${PREFIX}/include all: ${TARGET} ${TARGET_SHEET} @@ -213,13 +209,21 @@ clean: ${BUILD_DIR}/objs/%.o : ${THIS_LIB_BASE}/src/%.c ${PARSER_DEPS} ${MAKE} -C ${THIS_LIB_BASE}/src CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG} WIN=${WIN} $@ +LIBZSV=${PREFIX}/lib/libzsv.a +${LIBZSV}: + ${MAKE} -C ${THIS_LIB_BASE}/src CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG} WIN=${WIN} install + +LIBZSVUTIL=${PREFIX}/lib/libzsvutil.a +${LIBZSVUTIL}: + ${MAKE} -C ${THIS_LIB_BASE}/app CONFIGFILE=${CONFIGFILEPATH} DEBUG=${DEBUG} WIN=${WIN} install-util-lib YAJL_SRC_DIR=${THIS_MAKEFILE_DIR}/../external/yajl YAJL_INCLUDE=-I${YAJL_SRC_DIR}/build/yajl-2.1.1/include YAJL_HELPER_INCLUDE=-I${THIS_MAKEFILE_DIR}/../external/yajl_helper -${TARGET_SHEET}: LIBS="../external/sqlite3/sqlite3.c" -lzsv -lzsvutil -L${PREFIX}/lib -ljq -lutf8proc -${TARGET} ${TARGET_SHEET}: ${BUILD_DIR}/bin/zsvext%.${SO} : %_extension.c ${UTILS} +${TARGET_SHEET}: LIBS="../external/sqlite3/sqlite3.c" -lzsvutil -lzsv -L${PREFIX}/lib -ljq -lutf8proc +${TARGET}: LIBS=-lzsvutil -lzsv -L${PREFIX}/lib +${TARGET} ${TARGET_SHEET}: ${BUILD_DIR}/bin/zsvext%.${SO} : %_extension.c ${LIBZSV} ${LIBZSVUTIL} @mkdir -p `dirname "$@"` - ${CC} ${CFLAGS} ${CFLAGS_SHARED} $< ${UTILS} -o $@ ${LIBS} ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} + ${CC} ${CFLAGS} ${CFLAGS_SHARED} $< -o $@ ${LIBS} ${YAJL_INCLUDE} ${YAJL_HELPER_INCLUDE} .PHONY: all test test-% clean install diff --git a/app/sheet/index.c b/app/sheet/index.c index 6f1822a5..56f5bc7a 100644 --- a/app/sheet/index.c +++ b/app/sheet/index.c @@ -12,8 +12,9 @@ static void build_memory_index_row_handler(void *ctx) { struct zsvsheet_indexer *ixr = ctx; struct zsv_index *ix = ixr->ix; zsv_parser parser = ixr->parser; + uint64_t line_end = zsv_cum_scanned_length(parser); - if (zsv_index_add_row(ix, parser) != zsv_index_status_ok) + if (zsv_index_add_row(ix, line_end) != zsv_index_status_ok) zsv_abort(parser); } diff --git a/app/sheet/transformation.c b/app/sheet/transformation.c index 779ee353..c80cd473 100644 --- a/app/sheet/transformation.c +++ b/app/sheet/transformation.c @@ -5,6 +5,7 @@ #include "transformation.h" #include "pthread.h" #include "zsv/utils/file.h" +#include "zsv/utils/index.h" #include "zsv/utils/prop.h" struct zsvsheet_transformation { @@ -60,6 +61,7 @@ enum zsv_status zsvsheet_transformation_new(struct zsvsheet_transformation_opts struct zsv_csv_writer_options writer_opts = { .with_bom = 0, + .index = opts.index, .write = transformation_write, .stream = trn, .table_init = NULL, @@ -153,6 +155,7 @@ static void *zsvsheet_run_buffer_transformation(void *arg) { char *buff_status_old = uib->status; uib->write_progressed = 1; uib->write_done = 1; + uib->index_ready = 1; if (buff_status_old == trn->default_status) uib->status = NULL; pthread_mutex_unlock(mutex); @@ -172,6 +175,7 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, const char *filename = zsvsheet_buffer_data_filename(buff); enum zsvsheet_status stat = zsvsheet_status_error; struct zsvsheet_buffer_info_internal info = zsvsheet_buffer_info_internal(buff); + struct zsv_index *index = NULL; // TODO: Starting a second transformation before the first ends works, but if the second is faster // than the first then it can end prematurely and read a partially written row. @@ -179,12 +183,16 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, if (info.write_in_progress && !info.write_done) return zsvsheet_status_busy; + if (!(index = zsv_index_new())) + return zsvsheet_status_memory; + // TODO: custom_prop_handler is not passed to extensions? struct zsvsheet_transformation_opts trn_opts = { .custom_prop_handler = NULL, .input_filename = filename, .on_done = opts.on_done, .ui_buffer = NULL, + .index = index, }; zsvsheet_transformation trn = NULL; struct zsv_opts zopts = zsvsheet_buffer_get_zsv_opts(buff); @@ -194,13 +202,13 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, zopts.stream = fopen(filename, "rb"); if (!zopts.stream) - goto out; + goto error; trn_opts.zsv_opts = zopts; enum zsv_status zst = zsvsheet_transformation_new(trn_opts, &trn); if (zst != zsv_status_ok) - return stat; + goto error; // Transform part of the file to initially populate the UI buffer // TODO: If the transformation is a reduction that doesn't output for some time this will caus a pause @@ -214,13 +222,13 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, case zsv_status_no_more_input: case zsv_status_cancelled: if (zsv_finish(parser) != zsv_status_ok) - goto out; + goto error; zsv_writer_flush(trn->writer); break; case zsv_status_ok: break; default: - goto out; + goto error; } struct zsvsheet_ui_buffer_opts uibopts = {0}; @@ -230,14 +238,17 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, stat = zsvsheet_open_file_opts(ctx, &uibopts); if (stat != zsvsheet_status_ok) - goto out; + goto error; struct zsvsheet_ui_buffer *nbuff = zsvsheet_buffer_current(ctx); trn->ui_buffer = nbuff; nbuff->write_progressed = 1; + nbuff->index_started = 1; + nbuff->index = index; if (zst != zsv_status_ok) { nbuff->write_done = 1; + nbuff->index_ready = 1; goto out; } @@ -247,6 +258,9 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, zsvsheet_ui_buffer_create_worker(nbuff, zsvsheet_run_buffer_transformation, trn); return stat; +error: + free(index); + out: if (trn && trn->on_done) opts.on_done(trn); diff --git a/app/sheet/transformation.h b/app/sheet/transformation.h index 87ea7226..f5af1a8f 100644 --- a/app/sheet/transformation.h +++ b/app/sheet/transformation.h @@ -15,6 +15,7 @@ struct zsvsheet_transformation_opts { const char *input_filename; void (*on_done)(zsvsheet_transformation trn); struct zsvsheet_ui_buffer *ui_buffer; + struct zsv_index *index; }; enum zsv_status zsvsheet_transformation_new(struct zsvsheet_transformation_opts, zsvsheet_transformation *); diff --git a/app/utils/index.c b/app/utils/index.c index 5cd56635..96720969 100644 --- a/app/utils/index.c +++ b/app/utils/index.c @@ -1,5 +1,4 @@ #include -#include #include #include #include @@ -30,10 +29,9 @@ void zsv_index_delete(struct zsv_index *ix) { } } -enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, zsv_parser parser) { +enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end) { struct zsv_index_array *arr = ix->array; size_t len = arr->len, cap = arr->capacity; - uint64_t line_end = zsv_cum_scanned_length(parser); if (!ix->header_line_end) { ix->header_line_end = line_end; diff --git a/app/utils/writer.c b/app/utils/writer.c index 39be2c30..80b912e5 100644 --- a/app/utils/writer.c +++ b/app/utils/writer.c @@ -6,6 +6,7 @@ * https://opensource.org/licenses/MIT */ +#include "zsv/utils/index.h" #include #include #include @@ -108,6 +109,7 @@ struct zsv_output_buff { size_t (*write)(const void *restrict, size_t size, size_t nitems, void *restrict stream); void *stream; size_t used; + uint64_t written; unsigned char close_on_delete : 1; unsigned char _ : 7; }; @@ -122,6 +124,7 @@ struct zsv_writer_data { void *table_init_ctx; const char *cell_prepend; + struct zsv_index *index; unsigned char with_bom : 1; unsigned char started : 1; @@ -132,6 +135,7 @@ struct zsv_writer_data { static inline void zsv_output_buff_flush(struct zsv_output_buff *b) { b->write(b->buff, b->used, 1, b->stream); + b->written += b->used; b->used = 0; } @@ -141,6 +145,7 @@ static inline void zsv_output_buff_write(struct zsv_output_buff *b, const unsign zsv_output_buff_flush(b); if (n > ZSV_OUTPUT_BUFF_SIZE) { // n too big, so write directly b->write(s, n, 1, b->stream); + b->written += n; return; } } @@ -199,6 +204,7 @@ zsv_csv_writer zsv_writer_new(struct zsv_csv_writer_options *opts) { w->with_bom = opts->with_bom; w->table_init = opts->table_init; w->table_init_ctx = opts->table_init_ctx; + w->index = opts->index; } } return w; @@ -223,17 +229,21 @@ enum zsv_writer_status zsv_writer_delete(zsv_csv_writer w) { if (!w) return zsv_writer_status_missing_handle; + if (w->started) + zsv_output_buff_write(&w->out, (const unsigned char *)"\n", 1); + if (w->out.stream && w->out.write && w->out.buff) zsv_output_buff_flush(&w->out); - if (w->started) - w->out.write("\n", 1, 1, w->out.stream); + if (w->started && w->index) + zsv_index_add_row(w->index, w->out.written); if (w->out.buff) free(w->out.buff); if (w->out.close_on_delete && w->out.stream) fclose(w->out.stream); + free(w); return zsv_writer_status_ok; } @@ -266,9 +276,11 @@ enum zsv_writer_status zsv_writer_cell(zsv_csv_writer w, char new_row, const uns if (w->with_bom) zsv_output_buff_write(&w->out, (const unsigned char *)"\xef\xbb\xbf", 3); w->started = 1; - } else if (new_row) + } else if (new_row) { + if (w->index) + zsv_index_add_row(w->index, (uint64_t)(w->out.used + w->out.written)); zsv_output_buff_write(&w->out, (const unsigned char *)"\n", 1); - else + } else zsv_output_buff_write(&w->out, (const unsigned char *)",", 1); if (VERY_UNLIKELY(w->cell_prepend && *w->cell_prepend)) { diff --git a/include/zsv/utils/index.h b/include/zsv/utils/index.h index b9466a7d..f5805c94 100644 --- a/include/zsv/utils/index.h +++ b/include/zsv/utils/index.h @@ -2,8 +2,7 @@ #define ZSV_UTILS_INDEX_H #include -#include -#include +#include #include "zsv/common.h" @@ -37,7 +36,7 @@ struct zsv_index { struct zsv_index *zsv_index_new(void); void zsv_index_delete(struct zsv_index *ix); -enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, zsv_parser parser); +enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end); enum zsv_index_status zsv_index_row_end_offset(const struct zsv_index *ix, uint64_t row, uint64_t *offset_out, uint64_t *remaining_rows_out); enum zsv_index_status zsv_index_seek_row(const struct zsv_index *ix, struct zsv_opts *opts, uint64_t row); diff --git a/include/zsv/utils/writer.h b/include/zsv/utils/writer.h index e9a0a9d0..7727522e 100644 --- a/include/zsv/utils/writer.h +++ b/include/zsv/utils/writer.h @@ -26,6 +26,7 @@ struct zsv_csv_writer_options { void (*table_init)(void *); void *table_init_ctx; const char *output_path; // if provided, will be created by zsv_writer_new() and closed by zsv_writer_delete() + struct zsv_index *index; }; void zsv_writer_set_default_opts(struct zsv_csv_writer_options opts); From 60f762d9eb3ddb70623b6903be8cb3233d2d7834 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Tue, 3 Dec 2024 17:18:00 +0000 Subject: [PATCH 02/13] Display dlerror when dynamic linking fails Helps with missing symbol errors --- app/cli.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/cli.c b/app/cli.c index 5a8eeb35..40fb1b75 100644 --- a/app/cli.c +++ b/app/cli.c @@ -748,7 +748,11 @@ static struct zsv_ext *zsv_ext_new(const char *dl_name, const char *id, char ver } } if (!h) +#if defined(WIN32) || defined(_WIN32) fprintf(stderr, "Library %s not found\n", dl_name); +#else + fprintf(stderr, "Library %s failed to load: %s\n", dl_name, dlerror()); +#endif // run zsv_ext_init to add to our extension list, even if it's invalid tmp.ok = !zsv_ext_init(h, dl_name, &tmp); From 9213ad30c7deb895e96bfd8c7e9f64334968c8ca Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Wed, 4 Dec 2024 13:35:39 +0000 Subject: [PATCH 03/13] sheet: Allow search on generated buffers --- app/sheet.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/sheet.c b/app/sheet.c index e4fae039..0e7a616c 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -343,7 +343,7 @@ static zsvsheet_status zsvsheet_find(struct zsvsheet_sheet_context *state, bool struct zsvsheet_opts zsvsheet_opts = {0}; int prompt_footer_row = (int)(di->dimensions->rows - di->dimensions->footer_span); - if (!current_ui_buffer->filename) + if (!zsvsheet_buffer_data_filename(current_ui_buffer)) goto out; if (!next) { From 72cd87c6716f26a050a028390bf31db1d872b9df Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Wed, 4 Dec 2024 10:46:18 +0000 Subject: [PATCH 04/13] sheet: Allow index to be used before it is complete Allows the index to be used in a thread safe way so that entries can be read in the main thread while more are being added in the worker thread. --- app/sheet.c | 4 -- app/sheet/index.c | 13 +++++-- app/sheet/read-data.c | 3 +- app/sheet/transformation.c | 41 ++++++++++++-------- app/sheet/ui_buffer.c | 3 +- app/test/Makefile | 2 +- app/utils/index.c | 76 ++++++++++++++++++++++++++------------ include/zsv/utils/index.h | 7 +++- 8 files changed, 95 insertions(+), 54 deletions(-) diff --git a/app/sheet.c b/app/sheet.c index 0e7a616c..07bece3b 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -737,10 +737,6 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op pthread_mutex_lock(&ub->mutex); if (ub->status) zsvsheet_priv_set_status(&display_dims, 1, ub->status); - if (ub->write_progressed) { - handler_state.display_info.update_buffer = true; - ub->write_progressed = 0; - } if (ub->index_ready && ub->dimensions.row_count != ub->index->row_count + 1) { ub->dimensions.row_count = ub->index->row_count + 1; handler_state.display_info.update_buffer = true; diff --git a/app/sheet/index.c b/app/sheet/index.c index 56f5bc7a..98a2eb96 100644 --- a/app/sheet/index.c +++ b/app/sheet/index.c @@ -43,6 +43,8 @@ enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp) { if (!ixr.ix) goto out; + optsp->uib->index = ixr.ix; + char cancelled = 0; while (!cancelled && (zst = zsv_parse_more(ixr.parser)) == zsv_status_ok) { pthread_mutex_lock(&optsp->uib->mutex); @@ -50,16 +52,19 @@ enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp) { cancelled = 1; zst = zsv_status_cancelled; } + zsv_index_commit_rows(ixr.ix); + optsp->uib->index_ready = 1; pthread_mutex_unlock(&optsp->uib->mutex); } zsv_finish(ixr.parser); + pthread_mutex_lock(&optsp->uib->mutex); + zsv_index_commit_rows(ixr.ix); + optsp->uib->index_ready = 1; + pthread_mutex_unlock(&optsp->uib->mutex); - if (zst == zsv_status_no_more_input || zst == zsv_status_cancelled) { + if (zst == zsv_status_no_more_input || zst == zsv_status_cancelled) ret = zsv_index_status_ok; - optsp->uib->index = ixr.ix; - } else - zsv_index_delete(ixr.ix); out: if (ixr.parser) diff --git a/app/sheet/read-data.c b/app/sheet/read-data.c index 77f34b71..9ff7c788 100644 --- a/app/sheet/read-data.c +++ b/app/sheet/read-data.c @@ -204,7 +204,7 @@ static int read_data(struct zsvsheet_ui_buffer **uibufferp, // a new zsvsheet_ return 0; pthread_mutex_lock(&uibuff->mutex); - char need_index = !uibuff->index_started && (!uibuff->write_in_progress || uibuff->write_done); + char need_index = !uibuff->index_started && !uibuff->write_in_progress; pthread_mutex_unlock(&uibuff->mutex); if (need_index) { @@ -258,7 +258,6 @@ static void *get_data_index(void *gdi) { } pthread_mutex_lock(mutexp); - uib->index_ready = 1; uib->status = old_ui_status; uib->ixopts = NULL; pthread_mutex_unlock(mutexp); diff --git a/app/sheet/transformation.c b/app/sheet/transformation.c index c80cd473..0d65d20f 100644 --- a/app/sheet/transformation.c +++ b/app/sheet/transformation.c @@ -15,7 +15,7 @@ struct zsvsheet_transformation { FILE *output_stream; unsigned char *output_buffer; int output_fileno; - size_t output_count; + char writer_wrote; struct zsvsheet_transformation_opts opts; void *user_context; @@ -28,7 +28,7 @@ static size_t transformation_write(const void *restrict ptr, size_t size, size_t struct zsvsheet_transformation *trn = stream; const size_t count = fwrite(ptr, size, nitems, trn->output_stream); - trn->output_count += count; + trn->writer_wrote = count > 0; return count > 0 ? count : 0; } @@ -134,14 +134,17 @@ static void *zsvsheet_run_buffer_transformation(void *arg) { zsv_parser parser = trn->parser; pthread_mutex_t *mutex = &uib->mutex; enum zsv_status zst; + char *default_status = trn->default_status; - size_t c = trn->output_count; char cancelled = 0; while (!cancelled && (zst = zsv_parse_more(parser)) == zsv_status_ok) { pthread_mutex_lock(mutex); cancelled = uib->worker_cancelled; - if (trn->output_count != c) - uib->write_progressed = 1; + if (trn->writer_wrote) { + trn->writer_wrote = 0; + zsv_index_commit_rows(uib->index); + uib->index_ready = 1; + } pthread_mutex_unlock(mutex); } @@ -151,20 +154,22 @@ static void *zsvsheet_run_buffer_transformation(void *arg) { if (trn->on_done) trn->on_done(trn); + if (trn->user_context) + free(trn->user_context); + + zsvsheet_transformation_delete(trn); + pthread_mutex_lock(mutex); char *buff_status_old = uib->status; - uib->write_progressed = 1; uib->write_done = 1; + zsv_index_commit_rows(uib->index); uib->index_ready = 1; - if (buff_status_old == trn->default_status) + if (buff_status_old == default_status) uib->status = NULL; pthread_mutex_unlock(mutex); - if (buff_status_old == trn->default_status) + if (buff_status_old == default_status) free(buff_status_old); - if (trn->user_context) - free(trn->user_context); - zsvsheet_transformation_delete(trn); return NULL; } @@ -214,9 +219,10 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, // TODO: If the transformation is a reduction that doesn't output for some time this will caus a pause zsv_parser parser = zsvsheet_transformation_parser(trn); while ((zst = zsv_parse_more(parser)) == zsv_status_ok) { - if (trn->output_count > 0) + if (trn->writer_wrote) break; } + trn->writer_wrote = 0; switch (zst) { case zsv_status_no_more_input: @@ -242,14 +248,18 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, struct zsvsheet_ui_buffer *nbuff = zsvsheet_buffer_current(ctx); trn->ui_buffer = nbuff; - nbuff->write_progressed = 1; + zsv_index_commit_rows(index); nbuff->index_started = 1; nbuff->index = index; if (zst != zsv_status_ok) { nbuff->write_done = 1; nbuff->index_ready = 1; - goto out; + if (trn->on_done) + opts.on_done(trn); + zsvsheet_transformation_delete(trn); + zsv_index_commit_rows(index); + return stat; } asprintf(&trn->default_status, "(working) Press ESC to cancel "); @@ -259,9 +269,8 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, return stat; error: - free(index); + zsv_index_delete(index); -out: if (trn && trn->on_done) opts.on_done(trn); if (trn) diff --git a/app/sheet/ui_buffer.c b/app/sheet/ui_buffer.c index 7cc2faff..ebf30d1f 100644 --- a/app/sheet/ui_buffer.c +++ b/app/sheet/ui_buffer.c @@ -44,11 +44,10 @@ struct zsvsheet_ui_buffer { unsigned char has_row_num : 1; unsigned char mutex_inited : 1; unsigned char write_in_progress : 1; - unsigned char write_progressed : 1; unsigned char write_done : 1; unsigned char worker_active : 1; unsigned char worker_cancelled : 1; - unsigned char _ : 6; + unsigned char _ : 7; }; void zsvsheet_ui_buffer_create_worker(struct zsvsheet_ui_buffer *ub, void *(*start_func)(void *), void *arg) { diff --git a/app/test/Makefile b/app/test/Makefile index 77050fa0..9f2a027a 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -624,7 +624,7 @@ test-sheet-3: ${BUILD_DIR}/bin/zsv_sheet${EXE} sleep 2 && \ tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || ${TEST_FAIL}) + ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) test-sheet-4: ${BUILD_DIR}/bin/zsv_sheet${EXE} @${TEST_INIT} diff --git a/app/utils/index.c b/app/utils/index.c index 96720969..ef656b6d 100644 --- a/app/utils/index.c +++ b/app/utils/index.c @@ -7,30 +7,34 @@ #include struct zsv_index *zsv_index_new(void) { - struct zsv_index *ix = malloc(sizeof(*ix)); + struct zsv_index *ix = calloc(1, sizeof(*ix)); if (!ix) return ix; - memset(ix, 0, sizeof(*ix)); - - const size_t init_cap = 256; - ix->array = malloc(sizeof(*ix->array) + init_cap * sizeof(ix->array->u64s[0])); - ix->array->capacity = init_cap; - ix->array->len = 0; + const size_t init_cap = 512; + ix->first = calloc(1, sizeof(*ix->first) + init_cap * sizeof(ix->first->u64s[0])); + ix->first->capacity = init_cap; return ix; } void zsv_index_delete(struct zsv_index *ix) { if (ix) { - free(ix->array); + struct zsv_index_array *arr = ix->first; + + while (arr) { + struct zsv_index_array *a = arr; + arr = arr->next; + free(a); + } + free(ix); } } enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end) { - struct zsv_index_array *arr = ix->array; + struct zsv_index_array *arr = ix->first; size_t len = arr->len, cap = arr->capacity; if (!ix->header_line_end) { @@ -38,19 +42,27 @@ enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end) return zsv_index_status_ok; } - ix->row_count++; + ix->row_count_local++; - if ((ix->row_count & (ZSV_INDEX_ROW_N - 1)) != 0) + if ((ix->row_count_local & (ZSV_INDEX_ROW_N - 1)) != 0) return zsv_index_status_ok; - if (len >= cap) { - cap *= 2; - arr = realloc(arr, sizeof(*arr) + cap * sizeof(arr->u64s[0])); - if (!arr) - return zsv_index_status_memory; - - arr->capacity = cap; - ix->array = arr; + while (len >= cap) { + assert(len == cap); + + if (!arr->next) { + len = 0; + cap *= 2; + arr->next = calloc(1, sizeof(*arr) + cap * sizeof(arr->u64s[0])); + arr = arr->next; + if (!arr) + return zsv_index_status_memory; + arr->capacity = cap; + } else { + arr = arr->next; + len = arr->len; + cap = arr->capacity; + } } arr->u64s[len] = line_end; @@ -59,22 +71,38 @@ enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end) return zsv_index_status_ok; } +void zsv_index_commit_rows(struct zsv_index *ix) { + ix->row_count = ix->row_count_local; +} + enum zsv_index_status zsv_index_row_end_offset(const struct zsv_index *ix, uint64_t row, uint64_t *offset_out, uint64_t *remaining_rows_out) { + assert(ix->row_count <= ix->row_count_local); + if (row > ix->row_count) return zsv_index_status_error; if (row < ZSV_INDEX_ROW_N) { *offset_out = ix->header_line_end; *remaining_rows_out = row; - } else { - const size_t i = (row >> ZSV_INDEX_ROW_SHIFT) - 1; - assert(i < ix->array->len); - *offset_out = (long)ix->array->u64s[i]; - *remaining_rows_out = row & (ZSV_INDEX_ROW_N - 1); + return zsv_index_status_ok; + } + + const size_t i = (row >> ZSV_INDEX_ROW_SHIFT) - 1; + struct zsv_index_array *arr = ix->first; + size_t lens = 0; + + while (i >= lens + arr->len) { + assert(arr->next); + + lens += arr->len; + arr = arr->next; } + *offset_out = (long)arr->u64s[i - lens]; + *remaining_rows_out = row & (ZSV_INDEX_ROW_N - 1); + return zsv_index_status_ok; } diff --git a/include/zsv/utils/index.h b/include/zsv/utils/index.h index f5805c94..9735fc40 100644 --- a/include/zsv/utils/index.h +++ b/include/zsv/utils/index.h @@ -23,20 +23,25 @@ enum zsv_index_status { struct zsv_index_array { size_t capacity; size_t len; + struct zsv_index_array *next; uint64_t u64s[]; }; struct zsv_index { uint64_t header_line_end; + // Reading and writing should be protected with a lock uint64_t row_count; + // Should only be updated by the thread building the index + uint64_t row_count_local; // array containing the offsets of every ZSV_INDEX_ROW_N line end - struct zsv_index_array *array; + struct zsv_index_array *first; }; struct zsv_index *zsv_index_new(void); void zsv_index_delete(struct zsv_index *ix); enum zsv_index_status zsv_index_add_row(struct zsv_index *ix, uint64_t line_end); +void zsv_index_commit_rows(struct zsv_index *ix); enum zsv_index_status zsv_index_row_end_offset(const struct zsv_index *ix, uint64_t row, uint64_t *offset_out, uint64_t *remaining_rows_out); enum zsv_index_status zsv_index_seek_row(const struct zsv_index *ix, struct zsv_opts *opts, uint64_t row); From 0dfeed6aa73666ba4bd7a92e913a0a951133ff41 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Thu, 5 Dec 2024 13:08:13 +0000 Subject: [PATCH 05/13] tests: Retry matching expected output to improve test robustness This also reduces test time dramatically on faster hardware. --- app/test/Makefile | 138 +++++++----------- app/test/expected/test-sheet-10-filtered.out | 6 + app/test/expected/test-sheet-10-indexed.out | 6 + app/test/expected/test-sheet-11-indexed.out | 50 +++++++ .../expected/test-sheet-12-filtered-el.out | 6 + app/test/expected/test-sheet-12-indexed.out | 6 + app/test/expected/test-sheet-14-help.out | 25 ++++ app/test/expected/test-sheet-2-indexed.out | 5 + app/test/expected/test-sheet-3-indexed.out | 5 + app/test/expected/test-sheet-4-indexed.out | 5 + app/test/expected/test-sheet-6-indexed.out | 50 +++++++ app/test/expected/test-sheet-7-indexed.out | 5 + app/test/expected/test-sheet-8-filtered.out | 5 + app/test/expected/test-sheet-8-indexed.out | 5 + app/test/expected/test-sheet-9-indexed.out | 6 + scripts/test-expect.sh | 47 ++++++ scripts/test-retry-capture-cmp.sh | 12 ++ 17 files changed, 298 insertions(+), 84 deletions(-) create mode 100644 app/test/expected/test-sheet-10-filtered.out create mode 100644 app/test/expected/test-sheet-10-indexed.out create mode 100644 app/test/expected/test-sheet-11-indexed.out create mode 100644 app/test/expected/test-sheet-12-filtered-el.out create mode 100644 app/test/expected/test-sheet-12-indexed.out create mode 100644 app/test/expected/test-sheet-14-help.out create mode 100644 app/test/expected/test-sheet-2-indexed.out create mode 100644 app/test/expected/test-sheet-3-indexed.out create mode 100644 app/test/expected/test-sheet-4-indexed.out create mode 100644 app/test/expected/test-sheet-6-indexed.out create mode 100644 app/test/expected/test-sheet-7-indexed.out create mode 100644 app/test/expected/test-sheet-8-filtered.out create mode 100644 app/test/expected/test-sheet-8-indexed.out create mode 100644 app/test/expected/test-sheet-9-indexed.out create mode 100755 scripts/test-expect.sh create mode 100755 scripts/test-retry-capture-cmp.sh diff --git a/app/test/Makefile b/app/test/Makefile index 9f2a027a..ec334405 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -42,7 +42,7 @@ endif THIS_LIB_BASE=$(shell cd ../.. && pwd) CCBN=$(shell basename ${CC}) BUILD_DIR=${THIS_LIB_BASE}/build/${BUILD_SUBDIR}/${CCBN} -TMP_DIR=${THIS_LIB_BASE}/tmp +export TMP_DIR=${THIS_LIB_BASE}/tmp TEST_DATA_DIR=${THIS_LIB_BASE}/data SOURCES=echo count count-pull select select-pull sql 2json serialize flatten pretty desc stack 2db 2tsv jq compare overwrite @@ -77,17 +77,22 @@ ifneq ($(LEAKS),) REDIRECT=>${TMP_DIR}/leaks.txt; grep leak ${TMP_DIR}/leaks.txt | grep bytes \# # stop processing at this step REDIRECT1=>${TMP_DIR}/leaks.txt; grep leak ${TMP_DIR}/leaks.txt | grep bytes ) \# # stop processing at this step REDIRECT2=>${TMP_DIR}/leaks.txt; grep leak ${TMP_DIR}/leaks.txt | grep bytes ) \# # stop processing at this step - CMP=\# # don't run this step + export CMP=\# # don't run this step else PREFIX= REDIRECT=> REDIRECT1=> REDIRECT2=-o - CMP=cmp + export CMP=cmp endif +EXPECT=../../scripts/test-expect.sh + MAKE_BIN=$(notdir ${MAKE}) +DATE_TIME:=$(shell date +%F-%H-%M-%S) +export TIMINGS_CSV:=${TMP_DIR}/timings-${DATE_TIME}.csv + help: @echo "To run all tests: ${MAKE_BIN} test [LEAKS=1]" @echo "To run individual test: ${MAKE_BIN} test-xxx" @@ -106,6 +111,10 @@ test: ${TESTS} test-paste: @echo "TO DO: test paste" +${TIMINGS_CSV}: + @mkdir -p ${TMP_DIR} + @echo -n "Test, Stage, Time" > ${TIMINGS_CSV} + .SECONDARY: worldcitiespop_mil.csv .PHONY: help test test-% test-stack clean @@ -607,150 +616,111 @@ test-sheet-1: ${BUILD_DIR}/bin/zsv_sheet${EXE} tmux -v send-keys -t $@ "q" "exit" && \ ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (for x in `ls tmux-*.log` ; do echo "Log $$x:" && cat $$x ; done && ${TEST_FAIL})) -test-sheet-2: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-2: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @(tmux new-session -x 80 -y 5 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "C-d" && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || ${TEST_FAIL}) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-3: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-3: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @(tmux new-session -x 80 -y 5 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "f" "sarmaj" Enter && \ - sleep 2 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-4: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-4: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @(tmux new-session -x 80 -y 5 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "C-d" "C-d" "C-u" && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || ${TEST_FAIL}) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-5: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-5: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 160 -y 5 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || ${TEST_FAIL}) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-6: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-6: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 50 -d -s $@ "${PREFIX} $< -d 3 ${TEST_DATA_DIR}/test/mixed-line-endings.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "G" "C-u" "C-u" "C-u" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-7: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-7: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 5 -d -s $@ "${PREFIX} $< -d 3 ${TEST_DATA_DIR}/test/mixed-line-endings.csv" && \ - sleep 1 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "G" "g" "g" "C-u" "/" "1234" "Enter" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-8: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-8: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 160 -y 5 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "f" "e" "Enter" && \ - sleep 1 && \ + ${EXPECT} $@ filtered && \ tmux send-keys -t $@ "G" "C-u" "k" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-9: ${BUILD_DIR}/bin/zsv_sheet${EXE} +test-sheet-9: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 6 -d -s $@ "${PREFIX} $< -d 3 ${TEST_DATA_DIR}/test/mixed-line-endings.csv" && \ - sleep 1 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "G" && \ tmux send-keys -t $@ -N 4096 "k" && \ - sleep 2 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-10: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.tsv +test-sheet-10: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.tsv ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 6 -d -s $@ "${PREFIX} $< -t worldcitiespop_mil.tsv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "f" "e" "Enter" && \ - sleep 1 && \ + ${EXPECT} $@ filtered && \ tmux send-keys -t $@ "G" && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-11: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv +test-sheet-11: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 50 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 1 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ -N 11 "C-d" && \ - sleep 1 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-12: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv +test-sheet-12: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 6 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "f" "el" "Enter" && \ - sleep 0.5 && \ + ${EXPECT} $@ filtered-el && \ tmux send-keys -t $@ "f" "al" "Enter" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-13: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv - set -x - ${TEST_INIT} - echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf +test-sheet-13: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv ${TIMINGS_CSV} + @${TEST_INIT} + @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ tmux send-keys -t $@ "?" "/" "f" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) -test-sheet-14: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv +test-sheet-14: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv ${TIMINGS_CSV} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s $@ "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ tmux send-keys -t $@ "?" && \ - sleep 0.5 && \ + ${EXPECT} $@ help && \ tmux send-keys -t $@ "Escape" && \ - sleep 1 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-subcommand:\ test-sheet-subcommand-open-file-prompt test-sheet-subcommand-open-file-argument\ diff --git a/app/test/expected/test-sheet-10-filtered.out b/app/test/expected/test-sheet-10-filtered.out new file mode 100644 index 00000000..4beb11a5 --- /dev/null +++ b/app/test/expected/test-sheet-10-filtered.out @@ -0,0 +1,6 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +8 ad andorra-v Andorra-V 07 42.5 1.5166667 +(493039 filtered rows) 1 diff --git a/app/test/expected/test-sheet-10-indexed.out b/app/test/expected/test-sheet-10-indexed.out new file mode 100644 index 00000000..80a6d987 --- /dev/null +++ b/app/test/expected/test-sheet-10-indexed.out @@ -0,0 +1,6 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +? for help 1 diff --git a/app/test/expected/test-sheet-11-indexed.out b/app/test/expected/test-sheet-11-indexed.out new file mode 100644 index 00000000..e1829d4a --- /dev/null +++ b/app/test/expected/test-sheet-11-indexed.out @@ -0,0 +1,50 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +6 pl chomiaza Chomiaza 73 52.7508 17.841793 +7 mg itona Itona 02 -23.86666 47.216666 +8 ad andorra-v Andorra-V 07 42.5 1.5166667 +9 mx la tortug La Tortug 25 25.75 -108.3333 +10 us asaph Asaph PA 41.770833 -77.40527 +11 ad andorre-v Andorre-V 07 42.5 1.5166667 +12 ru dolmatova Dolmatova 71 57.436791 63.279522 +13 ro escu Escu 13 47.133333 23.533333 +14 us la presa La Presa CA 35119 32.708055 -116.9963 +15 pk makam khu Makam Khu 04 33.650863 72.551536 +16 ad aubinya Aubinyà 06 42.45 1.5 +17 mm kiosong Kiosöng 11 22.583333 97.05 +18 tr donencay Dönençay 28 40.266667 38.583333 +19 it roncaglia Roncaglia 05 45.05 9.8 +20 ml kourmouss Kourmouss 04 14.75 -5.033333 +21 id lamogo Lamogo 38 -4.3945 119.9028 +22 ad casas vil Casas Vil 03 42.533333 1.5666667 +23 ru otdeleniy Otdeleniy 86 51.726473 39.714345 +24 ad certes Certés 06 42.466666 1.5 +25 us mound Mound LA 32.339166 -91.02388 +26 ad el pui El Pui 04 42.55 1.5166667 +27 ad els bons Els Bons 03 42.533333 1.5833333 +28 ru roshina Roshina 46 54.641096 43.547234 +29 ad els plans Els Plans 02 42.583333 1.6333333 +30 ru zelenaya Zelënaya 55 52.616667 54.35 +31 mx tejalpa Tejalpa 21 18.35 -98.36666 +32 ad el vilar El Vilar 02 42.566666 1.6 +33 td narweyt Narweyt 02 15.133333 21.95 +34 id babakanme Babakanme 30 -7.5209 108.0738 +35 mx rancho sa Rancho Sa 10 25.4 -105.1166 +36 ad ercz Ercz 04 42.566666 1.5 +37 ad erez Erez 04 42.566666 1.5 +38 id cigaleuh Cigaleuh 30 -6.415833 107.61027 +39 jp ushitaki Ushitaki 03 41.283333 140.8 +40 ml nkalamedo Nkalaméd 07 12.136944 -7.744722 +41 la ban huai Ban Huai 13 17.875556 101.21944 +42 ad fontaneda Fontaneda 06 42.45 1.4666667 +43 kr chinbol Chinbol 13 37.7376 127.2266 +44 ad juverri Juverri 06 42.433333 1.5 +45 ua arginchik Arginchik 11 45.237894 34.648083 +46 il ezuz Ezuz 01 30.792445 34.472671 +47 ad l'aldosa L'Aldosa 02 42.583333 1.6333333 +48 kh phumi tra Phumi Tra 06 10.866666 104.68333 +? for help 1 diff --git a/app/test/expected/test-sheet-12-filtered-el.out b/app/test/expected/test-sheet-12-filtered-el.out new file mode 100644 index 00000000..8ec2a7dc --- /dev/null +++ b/app/test/expected/test-sheet-12-filtered-el.out @@ -0,0 +1,6 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +4 id selingon Selingon 17 -8.8374 116.4914 +23 ru otdeleniy Otdeleniy 86 51.726473 39.714345 +26 ad el pui El Pui 04 42.55 1.5166667 +27 ad els bons Els Bons 03 42.533333 1.5833333 +(59456 filtered rows) 4 diff --git a/app/test/expected/test-sheet-12-indexed.out b/app/test/expected/test-sheet-12-indexed.out new file mode 100644 index 00000000..80a6d987 --- /dev/null +++ b/app/test/expected/test-sheet-12-indexed.out @@ -0,0 +1,6 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +? for help 1 diff --git a/app/test/expected/test-sheet-14-help.out b/app/test/expected/test-sheet-14-help.out new file mode 100644 index 00000000..56fc3c48 --- /dev/null +++ b/app/test/expected/test-sheet-14-help.out @@ -0,0 +1,25 @@ +Key(s) Action Description +q quit Exit the application + escape Leave the current view or +^ first Jump to the first column +$ last Jump to the last column + first Jump to the first column + last Jump to the last column +k up Move up one row +j down Move down one row +h left Move left one column +l right Move right one column + up Move up one row + down Move down one row + left Move left one column + right Move right one column +d pagedown Move down one page +u pageup Move up one page + pagedown Move down one page + pageup Move up one page +g g top Jump to the first row +G bottom Jump to the last row +/ find Set a search term and jum +n next Jump to the next search r +e open Open a another CSV file + to exit help Key(s) diff --git a/app/test/expected/test-sheet-2-indexed.out b/app/test/expected/test-sheet-2-indexed.out new file mode 100644 index 00000000..7c4ad173 --- /dev/null +++ b/app/test/expected/test-sheet-2-indexed.out @@ -0,0 +1,5 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +? for help 1 diff --git a/app/test/expected/test-sheet-3-indexed.out b/app/test/expected/test-sheet-3-indexed.out new file mode 100644 index 00000000..7c4ad173 --- /dev/null +++ b/app/test/expected/test-sheet-3-indexed.out @@ -0,0 +1,5 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +? for help 1 diff --git a/app/test/expected/test-sheet-4-indexed.out b/app/test/expected/test-sheet-4-indexed.out new file mode 100644 index 00000000..7c4ad173 --- /dev/null +++ b/app/test/expected/test-sheet-4-indexed.out @@ -0,0 +1,5 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +? for help 1 diff --git a/app/test/expected/test-sheet-6-indexed.out b/app/test/expected/test-sheet-6-indexed.out new file mode 100644 index 00000000..a89c4b65 --- /dev/null +++ b/app/test/expected/test-sheet-6-indexed.out @@ -0,0 +1,50 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +4 A4 B4 C4 +5 A5 B5 C5 +6 A6 B6 C6 +7 A7 B7 C7 +8 A8 B8 C8 +9 A9 B9 C9 +10 A10 B10 C10 +11 A11 B11 C11 +12 A12 B12 C12 +13 A13 B13 C13 +14 A14 B14 C14 +15 A15 B15 C15 +16 A16 B16 C16 +17 A17 B17 C17 +18 A18 B18 C18 +19 A19 B19 C19 +20 A20 B20 C20 +21 A21 B21 C21 +22 A22 B22 C22 +23 A23 B23 C23 +24 A24 B24 C24 +25 A25 B25 C25 +26 A26 B26 C26 +27 A27 B27 C27 +28 A28 B28 C28 +29 A29 B29 C29 +30 A30 B30 C30 +31 A31 B31 C31 +32 A32 B32 C32 +33 A33 B33 C33 +34 A34 B34 C34 +35 A35 B35 C35 +36 A36 B36 C36 +37 A37 B37 C37 +38 A38 B38 C38 +39 A39 B39 C39 +40 A40 B40 C40 +41 A41 B41 C41 +42 A42 B42 C42 +43 A43 B43 C43 +44 A44 B44 C44 +45 A45 B45 C45 +46 A46 B46 C46 +47 A47 B47 C47 +48 A48 B48 C48 +? for help 1 diff --git a/app/test/expected/test-sheet-7-indexed.out b/app/test/expected/test-sheet-7-indexed.out new file mode 100644 index 00000000..6a018f15 --- /dev/null +++ b/app/test/expected/test-sheet-7-indexed.out @@ -0,0 +1,5 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +? for help 1 diff --git a/app/test/expected/test-sheet-8-filtered.out b/app/test/expected/test-sheet-8-filtered.out new file mode 100644 index 00000000..b00c58ab --- /dev/null +++ b/app/test/expected/test-sheet-8-filtered.out @@ -0,0 +1,5 @@ +Row # Country City AccentCity Region Population Latitude Longitude +1 ir sarmaj-e hoseynkhan Sarmaj-e Hoseynkhan 13 34.3578 47.5207 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +(493039 filtered rows) 1 diff --git a/app/test/expected/test-sheet-8-indexed.out b/app/test/expected/test-sheet-8-indexed.out new file mode 100644 index 00000000..8c4e66a4 --- /dev/null +++ b/app/test/expected/test-sheet-8-indexed.out @@ -0,0 +1,5 @@ +Row # Country City AccentCity Region Population Latitude Longitude +1 ir sarmaj-e hoseynkhan Sarmaj-e Hoseynkhan 13 34.3578 47.5207 +2 ad aixirivali Aixirivali 06 42.4666667 1.5 +3 mm mokho-atwinywa Mokho-atwinywa 09 18.0333333 96.75 +? for help 1 diff --git a/app/test/expected/test-sheet-9-indexed.out b/app/test/expected/test-sheet-9-indexed.out new file mode 100644 index 00000000..fbae507e --- /dev/null +++ b/app/test/expected/test-sheet-9-indexed.out @@ -0,0 +1,6 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +4 A4 B4 C4 +? for help 1 diff --git a/scripts/test-expect.sh b/scripts/test-expect.sh new file mode 100755 index 00000000..5095e2de --- /dev/null +++ b/scripts/test-expect.sh @@ -0,0 +1,47 @@ +#!/bin/sh -eu + +script_dir=$(dirname "$0") + +export TARGET="$1" +if [ -z "${2:-}" ]; then + export STAGE="" +else + export STAGE=-"$2" +fi +export CAPTURE="${TMP_DIR}/$TARGET$STAGE".out +EXPECTED="$(pwd)/expected/$TARGET$STAGE".out +export EXPECTED +matched=false + +cleanup() { + if $matched; then + if [ -z "$STAGE" ]; then + tmux send-keys -t "$TARGET" "q" + fi + exit 0 + fi + + tmux send-keys -t "$TARGET" "q" + echo 'Incorrect output:' + cat "$CAPTURE" + ${CMP} -s "$CAPTURE" "$EXPECTED" + exit 1 +} + +trap cleanup INT TERM QUIT + +printf "\n%s, %s" "$TARGET" "${2:-}" >> "${TIMINGS_CSV}" + +set +e +match_time=$(timeout 5 time -p "${script_dir}"/test-retry-capture-cmp.sh 2>&1) +status=$? +set -e + +if [ $status -eq 0 ]; then + matched=true + match_time=$(echo "$match_time" | head -n 1 | cut -f 2 -d ' ') + echo "$TARGET$STAGE took $match_time" + printf ", %s" "$match_time" >> "${TIMINGS_CSV}" +fi + +cleanup diff --git a/scripts/test-retry-capture-cmp.sh b/scripts/test-retry-capture-cmp.sh new file mode 100755 index 00000000..3fc46073 --- /dev/null +++ b/scripts/test-retry-capture-cmp.sh @@ -0,0 +1,12 @@ +#!/bin/sh -eu + +while true; do + tmux capture-pane -t "$TARGET" -p > "$CAPTURE" + + if ${CMP} -s "$CAPTURE" "$EXPECTED"; then + exit 0 + fi + + sleep 0.025 +done + From 9823b20a356395b8c7d48344614012419bc04c3e Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 09:30:38 +0000 Subject: [PATCH 06/13] sheet: Use buffer status on first display to prevent race in testing Allows us to wait for indexing to finish during testing without needing to sleep to avoid matching the initial status. --- app/sheet.c | 26 +++++++++++++++++--------- app/sheet/index.h | 1 + app/sheet/read-data.c | 30 ++++++++++-------------------- 3 files changed, 28 insertions(+), 29 deletions(-) diff --git a/app/sheet.c b/app/sheet.c index 07bece3b..a7fd303a 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -637,6 +637,19 @@ void zsvsheet_register_builtin_procedures(void) { } } +static void zsvsheet_check_buffer_worker_updates(struct zsvsheet_ui_buffer *ub, + struct zsvsheet_display_dimensions *display_dims, + struct zsvsheet_sheet_context *handler_state) { + pthread_mutex_lock(&ub->mutex); + if (ub->status) + zsvsheet_priv_set_status(display_dims, 1, ub->status); + if (ub->index_ready && ub->dimensions.row_count != ub->index->row_count + 1) { + ub->dimensions.row_count = ub->index->row_count + 1; + handler_state->display_info.update_buffer = true; + } + pthread_mutex_unlock(&ub->mutex); +} + int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *optsp, struct zsv_prop_handler *custom_prop_handler) { if (argc > 1 && (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))) { @@ -695,7 +708,6 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op cbreak(); set_escdelay(30); struct zsvsheet_display_dimensions display_dims = get_display_dimensions(1, 1); - display_buffer_subtable(current_ui_buffer, header_span, &display_dims); zsvsheet_register_builtin_procedures(); @@ -717,6 +729,9 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op zsvsheet_status status; + zsvsheet_check_buffer_worker_updates(current_ui_buffer, &display_dims, &handler_state); + display_buffer_subtable(current_ui_buffer, header_span, &display_dims); + halfdelay(2); // now ncurses getch() will fire every 2-tenths of a second so we can check for status update // while (true) { @@ -734,14 +749,7 @@ int ZSV_MAIN_FUNC(ZSV_COMMAND)(int argc, const char *argv[], struct zsv_opts *op } struct zsvsheet_ui_buffer *ub = current_ui_buffer; - pthread_mutex_lock(&ub->mutex); - if (ub->status) - zsvsheet_priv_set_status(&display_dims, 1, ub->status); - if (ub->index_ready && ub->dimensions.row_count != ub->index->row_count + 1) { - ub->dimensions.row_count = ub->index->row_count + 1; - handler_state.display_info.update_buffer = true; - } - pthread_mutex_unlock(&ub->mutex); + zsvsheet_check_buffer_worker_updates(ub, &display_dims, &handler_state); if (handler_state.display_info.update_buffer && zsvsheet_buffer_data_filename(ub)) { struct zsvsheet_opts zsvsheet_opts = {0}; diff --git a/app/sheet/index.h b/app/sheet/index.h index 647a1f77..25104281 100644 --- a/app/sheet/index.h +++ b/app/sheet/index.h @@ -17,6 +17,7 @@ struct zsvsheet_index_opts { struct zsvsheet_ui_buffer *uib; int *errp; struct zsv_prop_handler *custom_prop_handler; + char *old_ui_status; }; enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp); diff --git a/app/sheet/read-data.c b/app/sheet/read-data.c index 9ff7c788..7ca36a81 100644 --- a/app/sheet/read-data.c +++ b/app/sheet/read-data.c @@ -29,14 +29,15 @@ static char *zsvsheet_found_in_row(zsv_parser parser, size_t col_count, const ch static void *get_data_index(void *d); static void get_data_index_async(struct zsvsheet_ui_buffer *uibuffp, const char *filename, struct zsv_opts *optsp, - struct zsv_prop_handler *custom_prop_handler, pthread_mutex_t *mutexp) { + struct zsv_prop_handler *custom_prop_handler, char *old_ui_status) { struct zsvsheet_index_opts *ixopts = calloc(1, sizeof(*ixopts)); - ixopts->mutexp = mutexp; + ixopts->mutexp = &uibuffp->mutex; ixopts->filename = filename; ixopts->zsv_opts = *optsp; ixopts->custom_prop_handler = custom_prop_handler; ixopts->uib = uibuffp; ixopts->uib->ixopts = ixopts; + ixopts->old_ui_status = old_ui_status; if (uibuffp->worker_active) zsvsheet_ui_buffer_join_worker(uibuffp); @@ -205,15 +206,19 @@ static int read_data(struct zsvsheet_ui_buffer **uibufferp, // a new zsvsheet_ pthread_mutex_lock(&uibuff->mutex); char need_index = !uibuff->index_started && !uibuff->write_in_progress; + char *old_ui_status = uibuff->status; pthread_mutex_unlock(&uibuff->mutex); if (need_index) { + if (asprintf(&uibuff->status, "%s(building index) ", old_ui_status ? old_ui_status : "") == -1) + return -1; + uibuff->buff_used_rows = rows_read; uibuff->dimensions.row_count = rows_read; uibuff->index_started = 1; if (original_row_num > 1 && rows_read > 0) { opts.stream = NULL; - get_data_index_async(uibuff, filename, &opts, custom_prop_handler, &uibuff->mutex); + get_data_index_async(uibuff, filename, &opts, custom_prop_handler, old_ui_status); } } else if (rows_read > uibuff->buff_used_rows) { uibuff->buff_used_rows = rows_read; @@ -229,22 +234,7 @@ static void *get_data_index(void *gdi) { pthread_mutex_t *mutexp = d->mutexp; int *errp = d->errp; struct zsvsheet_ui_buffer *uib = d->uib; - - pthread_mutex_lock(&uib->mutex); - char *old_ui_status = uib->status; - pthread_mutex_unlock(&uib->mutex); - - char *ui_status; - /* I think there was a race between this print and a "? for help" which causes - * ci to fail. Once read-file is called the main thread displays its contents - * and this thread indexes the file. There is no synchronisation between the - * two so the status we end up with is random. - */ - // asprintf(&ui_status, "%s(building index) ", old_ui_status ? old_ui_status : ""); - - pthread_mutex_lock(&uib->mutex); - uib->status = ui_status; - pthread_mutex_unlock(&uib->mutex); + char *ui_status = uib->status; enum zsv_index_status ix_status = build_memory_index(d); @@ -258,7 +248,7 @@ static void *get_data_index(void *gdi) { } pthread_mutex_lock(mutexp); - uib->status = old_ui_status; + uib->status = d->old_ui_status; uib->ixopts = NULL; pthread_mutex_unlock(mutexp); From 3c686f571a43db6b73b5fa386ae5caaae8c93b3e Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 10:57:54 +0000 Subject: [PATCH 07/13] Fix timeout on Alpine/Busybox For some reason timing timeout works but not timing out time --- scripts/test-expect.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/test-expect.sh b/scripts/test-expect.sh index 5095e2de..2ab3f691 100755 --- a/scripts/test-expect.sh +++ b/scripts/test-expect.sh @@ -33,7 +33,7 @@ trap cleanup INT TERM QUIT printf "\n%s, %s" "$TARGET" "${2:-}" >> "${TIMINGS_CSV}" set +e -match_time=$(timeout 5 time -p "${script_dir}"/test-retry-capture-cmp.sh 2>&1) +match_time=$(time -p timeout -k 6 5 "${script_dir}"/test-retry-capture-cmp.sh 2>&1) status=$? set -e From 1eca52ee2a5be9e4208ed7ebfb4f9195008900a5 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 12:03:35 +0000 Subject: [PATCH 08/13] sheet: Convert sheet-subcommand tests to use expect --- app/test/Makefile | 33 ++++++------------- ...-subcommand-filter-file-argument-blank.out | 25 ++++++++++++++ ...et-subcommand-filter-file-prompt-blank.out | 25 ++++++++++++++ ...t-subcommand-filter-file-prompt-filter.out | 25 ++++++++++++++ ...et-subcommand-open-file-argument-blank.out | 25 ++++++++++++++ ...heet-subcommand-open-file-prompt-blank.out | 25 ++++++++++++++ ...sheet-subcommand-open-file-prompt-open.out | 25 ++++++++++++++ 7 files changed, 160 insertions(+), 23 deletions(-) create mode 100644 app/test/expected/test-sheet-subcommand-filter-file-argument-blank.out create mode 100644 app/test/expected/test-sheet-subcommand-filter-file-prompt-blank.out create mode 100644 app/test/expected/test-sheet-subcommand-filter-file-prompt-filter.out create mode 100644 app/test/expected/test-sheet-subcommand-open-file-argument-blank.out create mode 100644 app/test/expected/test-sheet-subcommand-open-file-prompt-blank.out create mode 100644 app/test/expected/test-sheet-subcommand-open-file-prompt-open.out diff --git a/app/test/Makefile b/app/test/Makefile index ec334405..c054aadc 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -730,50 +730,37 @@ test-sheet-subcommand-open-file-prompt: ${BUILD_DIR}/bin/zsv_sheet${EXE} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s "$@" "${PREFIX} $<" && \ - sleep 0.5 && \ + ${EXPECT} $@ blank && \ tmux send-keys -t $@ ":open" "Enter" && \ - sleep 0.5 && \ + ${EXPECT} $@ open && \ tmux send-keys -t $@ "worldcitiespop_mil.csv" "Enter" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-subcommand-open-file-argument: ${BUILD_DIR}/bin/zsv_sheet${EXE} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s "$@" "${PREFIX} $<" && \ - sleep 0.5 && \ + ${EXPECT} $@ blank && \ tmux send-keys -t $@ ":open worldcitiespop_mil.csv" "Enter" && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) - + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-subcommand-filter-file-prompt: ${BUILD_DIR}/bin/zsv_sheet${EXE} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s "$@" "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ blank && \ tmux send-keys -t $@ ":filter" "Enter" && \ - sleep 0.5 && \ + ${EXPECT} $@ filter && \ tmux send-keys -t $@ "ireland" "Enter" && \ - sleep 1.0 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-subcommand-filter-file-argument: ${BUILD_DIR}/bin/zsv_sheet${EXE} @${TEST_INIT} @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf @(tmux new-session -x 80 -y 25 -d -s "$@" "${PREFIX} $< worldcitiespop_mil.csv" && \ - sleep 0.5 && \ + ${EXPECT} $@ blank && \ tmux send-keys -t $@ ":filter \"ireland\"" "Enter" && \ - sleep 1.0 && \ - tmux capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out && \ - tmux send-keys -t $@ "q" && \ - ${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-prop-cmd-opt: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${BUILD_DIR}/bin/zsv_prop${EXE} @echo "TO DO: test-sheet-prop-cmd-opt" diff --git a/app/test/expected/test-sheet-subcommand-filter-file-argument-blank.out b/app/test/expected/test-sheet-subcommand-filter-file-argument-blank.out new file mode 100644 index 00000000..610fb180 --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-filter-file-argument-blank.out @@ -0,0 +1,25 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +6 pl chomiaza Chomiaza 73 52.7508 17.841793 +7 mg itona Itona 02 -23.86666 47.216666 +8 ad andorra-v Andorra-V 07 42.5 1.5166667 +9 mx la tortug La Tortug 25 25.75 -108.3333 +10 us asaph Asaph PA 41.770833 -77.40527 +11 ad andorre-v Andorre-V 07 42.5 1.5166667 +12 ru dolmatova Dolmatova 71 57.436791 63.279522 +13 ro escu Escu 13 47.133333 23.533333 +14 us la presa La Presa CA 35119 32.708055 -116.9963 +15 pk makam khu Makam Khu 04 33.650863 72.551536 +16 ad aubinya Aubinyà 06 42.45 1.5 +17 mm kiosong Kiosöng 11 22.583333 97.05 +18 tr donencay Dönençay 28 40.266667 38.583333 +19 it roncaglia Roncaglia 05 45.05 9.8 +20 ml kourmouss Kourmouss 04 14.75 -5.033333 +21 id lamogo Lamogo 38 -4.3945 119.9028 +22 ad casas vil Casas Vil 03 42.533333 1.5666667 +23 ru otdeleniy Otdeleniy 86 51.726473 39.714345 +? for help 1 diff --git a/app/test/expected/test-sheet-subcommand-filter-file-prompt-blank.out b/app/test/expected/test-sheet-subcommand-filter-file-prompt-blank.out new file mode 100644 index 00000000..610fb180 --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-filter-file-prompt-blank.out @@ -0,0 +1,25 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +6 pl chomiaza Chomiaza 73 52.7508 17.841793 +7 mg itona Itona 02 -23.86666 47.216666 +8 ad andorra-v Andorra-V 07 42.5 1.5166667 +9 mx la tortug La Tortug 25 25.75 -108.3333 +10 us asaph Asaph PA 41.770833 -77.40527 +11 ad andorre-v Andorre-V 07 42.5 1.5166667 +12 ru dolmatova Dolmatova 71 57.436791 63.279522 +13 ro escu Escu 13 47.133333 23.533333 +14 us la presa La Presa CA 35119 32.708055 -116.9963 +15 pk makam khu Makam Khu 04 33.650863 72.551536 +16 ad aubinya Aubinyà 06 42.45 1.5 +17 mm kiosong Kiosöng 11 22.583333 97.05 +18 tr donencay Dönençay 28 40.266667 38.583333 +19 it roncaglia Roncaglia 05 45.05 9.8 +20 ml kourmouss Kourmouss 04 14.75 -5.033333 +21 id lamogo Lamogo 38 -4.3945 119.9028 +22 ad casas vil Casas Vil 03 42.533333 1.5666667 +23 ru otdeleniy Otdeleniy 86 51.726473 39.714345 +? for help 1 diff --git a/app/test/expected/test-sheet-subcommand-filter-file-prompt-filter.out b/app/test/expected/test-sheet-subcommand-filter-file-prompt-filter.out new file mode 100644 index 00000000..53e1b8d7 --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-filter-file-prompt-filter.out @@ -0,0 +1,25 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +4 id selingon Selingon 17 -8.8374 116.4914 +5 ir berimvand Berimvand 13 34.2953 47.1096 +6 pl chomiaza Chomiaza 73 52.7508 17.841793 +7 mg itona Itona 02 -23.86666 47.216666 +8 ad andorra-v Andorra-V 07 42.5 1.5166667 +9 mx la tortug La Tortug 25 25.75 -108.3333 +10 us asaph Asaph PA 41.770833 -77.40527 +11 ad andorre-v Andorre-V 07 42.5 1.5166667 +12 ru dolmatova Dolmatova 71 57.436791 63.279522 +13 ro escu Escu 13 47.133333 23.533333 +14 us la presa La Presa CA 35119 32.708055 -116.9963 +15 pk makam khu Makam Khu 04 33.650863 72.551536 +16 ad aubinya Aubinyà 06 42.45 1.5 +17 mm kiosong Kiosöng 11 22.583333 97.05 +18 tr donencay Dönençay 28 40.266667 38.583333 +19 it roncaglia Roncaglia 05 45.05 9.8 +20 ml kourmouss Kourmouss 04 14.75 -5.033333 +21 id lamogo Lamogo 38 -4.3945 119.9028 +22 ad casas vil Casas Vil 03 42.533333 1.5666667 +23 ru otdeleniy Otdeleniy 86 51.726473 39.714345 +Filter: diff --git a/app/test/expected/test-sheet-subcommand-open-file-argument-blank.out b/app/test/expected/test-sheet-subcommand-open-file-argument-blank.out new file mode 100644 index 00000000..db7e396c --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-open-file-argument-blank.out @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + +? for help diff --git a/app/test/expected/test-sheet-subcommand-open-file-prompt-blank.out b/app/test/expected/test-sheet-subcommand-open-file-prompt-blank.out new file mode 100644 index 00000000..db7e396c --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-open-file-prompt-blank.out @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + +? for help diff --git a/app/test/expected/test-sheet-subcommand-open-file-prompt-open.out b/app/test/expected/test-sheet-subcommand-open-file-prompt-open.out new file mode 100644 index 00000000..3ffe7e9a --- /dev/null +++ b/app/test/expected/test-sheet-subcommand-open-file-prompt-open.out @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + +File to open: From ee8efe5daa710c380e96dca98da1f9c3902cccd3 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 12:17:12 +0000 Subject: [PATCH 09/13] sheet: Fix bad memory access by duplicating filename string UI buffer frees this on closing and the indexing thread will use it so it can't point to a stack variable or memory that gets overwritten after the file is opened. --- app/sheet.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/sheet.c b/app/sheet.c index a7fd303a..0a0c6972 100644 --- a/app/sheet.c +++ b/app/sheet.c @@ -381,14 +381,14 @@ static zsvsheet_status zsvsheet_open_file_handler(struct zsvsheet_proc_context * UNUSED(ctx); if (ctx->num_params > 0) { - filename = ctx->params[0].u.string; + filename = strdup(ctx->params[0].u.string); } else { if (!ctx->invocation.interactive) return zsvsheet_status_error; get_subcommand("File to open", prompt_buffer, sizeof(prompt_buffer), prompt_footer_row); if (*prompt_buffer == '\0') goto no_input; - filename = prompt_buffer; + filename = strdup(prompt_buffer); } if ((err = zsvsheet_ui_buffer_open_file(filename, NULL, state->custom_prop_handler, di->ui_buffers.base, From a155ebb1d2bbe63ce50eea7f51e6e4b215803c4e Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 14:50:29 +0000 Subject: [PATCH 10/13] sheet: Convert sheet extension example tests to use expect --- app/ext_example/Makefile | 35 ++++++++----------- .../test-sheet-extension-1-indexed.out | 5 +++ .../test-sheet-extension-2-indexed.out | 5 +++ .../test-sheet-extension-2-transformed.out | 5 +++ app/test/Makefile | 5 ++- scripts/test-expect.sh | 6 ++-- 6 files changed, 38 insertions(+), 23 deletions(-) create mode 100644 app/ext_example/test/expected/test-sheet-extension-1-indexed.out create mode 100644 app/ext_example/test/expected/test-sheet-extension-2-indexed.out create mode 100644 app/ext_example/test/expected/test-sheet-extension-2-transformed.out diff --git a/app/ext_example/Makefile b/app/ext_example/Makefile index 3d562f85..b024cbf3 100644 --- a/app/ext_example/Makefile +++ b/app/ext_example/Makefile @@ -61,7 +61,7 @@ else CFLAGS += -g endif -THIS_LIB_BASE=$(shell cd ../.. && pwd) +export THIS_LIB_BASE=$(shell cd ../.. && pwd) CCBN=$(shell basename ${CC}) BUILD_DIR=${THIS_LIB_BASE}/build/${BUILD_SUBDIR}/${CCBN} TARGET=${BUILD_DIR}/bin/zsvextmy.${SO} @@ -77,6 +77,9 @@ TEST_PASS=printf "${COLOR_BLUE}$@: ${COLOR_GREEN}Passed${COLOR_NONE}\n" TEST_FAIL=(printf "${COLOR_BLUE}$@: ${COLOR_RED}Failed!${COLOR_NONE}\n" && exit 1) TEST_INIT=printf "${COLOR_PINK}$@: ${COLOR_NONE}\n" +EXPECT=../../scripts/test-expect.sh +export EXPECTED_PATH=test/expected + CFLAGS_SHARED=-shared ifneq ($(findstring emcc,$(CC)),) # emcc CFLAGS_SHARED=-s SIDE_MODULE=1 -s LINKABLE=1 @@ -141,7 +144,10 @@ test-3: test-%: ${CLI} ${TARGET} ../test/worldcitiespop_mil.csv: make -C ../test worldcitiespop_mil.csv -TMP_DIR=/tmp +export TMP_DIR=/tmp +DATE_TIME:=$(shell date +%F-%H-%M-%S) +export TIMINGS_CSV:=${TMP_DIR}/timings-${DATE_TIME}.csv +export CMP=cmp TMUX_TERM=xterm-256color test-sheet-extension-1: ${CLI} ${TARGET} ../test/worldcitiespop_mil.csv @${TEST_INIT} @@ -150,15 +156,10 @@ test-sheet-extension-1: ${CLI} ${TARGET} ../test/worldcitiespop_mil.csv @${RUN_CLI} unregister my 2>/dev/null 1>/dev/null || [ 1==1 ] @${RUN_CLI} register my 2>&1 | grep -v [.]so || [ 1==1 ] @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf - @(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 80 -y 5 -d -s $@ && \ - sleep 0.5 && \ - tmux send-keys -t $@ "${CLI} sheet ../test/worldcitiespop_mil.csv" ENTER && \ - sleep 0.5 && \ + @(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 80 -y 5 -d -s $@ $< sheet ../test/worldcitiespop_mil.csv && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ "t" "hello" Enter && \ - tmux capture-pane -t $@ -p > ${TMP_DIR}/$@.out && \ - tmux kill-session -t $@ && \ - cmp ${TMP_DIR}/$@.out test/expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && \ - if [ -f ${TMP_DIR}/$@.out ]; then cat ${TMP_DIR}/$@.out; fi && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-sheet-extension-2: ${CLI} ${TARGET} @${TEST_INIT} @@ -167,18 +168,12 @@ test-sheet-extension-2: ${CLI} ${TARGET} @${RUN_CLI} unregister my 2>/dev/null 1>/dev/null || [ 1==1 ] @${RUN_CLI} register my 2>&1 | grep -v [.]so || [ 1==1 ] @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf - @(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 120 -y 5 -d -s $@ && \ - sleep 0.5 && \ - tmux send-keys -t $@ "${CLI} sheet -d 3 ../../data/test/mixed-line-endings.csv" ENTER && \ - sleep 0.5 && \ + @(ZSV_CONFIG_DIR=/tmp tmux -v new-session -x 120 -y 5 -d -s $@ $< sheet -d 3 ../../data/test/mixed-line-endings.csv && \ + ${EXPECT} $@ indexed && \ tmux send-keys -t $@ T Enter && \ - sleep 0.5 && \ + ${EXPECT} $@ transformed && \ tmux send-keys -t $@ G && \ - sleep 0.5 && \ - tmux capture-pane -t $@ -p > ${TMP_DIR}/$@.out && \ - tmux kill-session -t $@ && \ - cmp ${TMP_DIR}/$@.out test/expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && \ - if [ -f ${TMP_DIR}/$@.out ]; then cat ${TMP_DIR}/$@.out; fi && ${TEST_FAIL})) + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) test-4: test-%: ${CLI} ${TARGET} @${TEST_INIT} diff --git a/app/ext_example/test/expected/test-sheet-extension-1-indexed.out b/app/ext_example/test/expected/test-sheet-extension-1-indexed.out new file mode 100644 index 00000000..7c4ad173 --- /dev/null +++ b/app/ext_example/test/expected/test-sheet-extension-1-indexed.out @@ -0,0 +1,5 @@ +Row # Country City AccentCit Region Populatio Latitude Longitude +1 ir sarmaj-e Sarmaj-e 13 34.3578 47.5207 +2 ad aixirival Aixirival 06 42.466666 1.5 +3 mm mokho-atw Mokho-atw 09 18.033333 96.75 +? for help 1 diff --git a/app/ext_example/test/expected/test-sheet-extension-2-indexed.out b/app/ext_example/test/expected/test-sheet-extension-2-indexed.out new file mode 100644 index 00000000..7eb68921 --- /dev/null +++ b/app/ext_example/test/expected/test-sheet-extension-2-indexed.out @@ -0,0 +1,5 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +? for help 1 diff --git a/app/ext_example/test/expected/test-sheet-extension-2-transformed.out b/app/ext_example/test/expected/test-sheet-extension-2-transformed.out new file mode 100644 index 00000000..34ae790b --- /dev/null +++ b/app/ext_example/test/expected/test-sheet-extension-2-transformed.out @@ -0,0 +1,5 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 Column count +1 A1 B1 C1 6 +2 A2 B2 C2 9 +3 A3 B3 C3 12 +? for help 1 diff --git a/app/test/Makefile b/app/test/Makefile index c054aadc..c5479d7e 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -39,7 +39,7 @@ else EXE=.exe endif -THIS_LIB_BASE=$(shell cd ../.. && pwd) +export THIS_LIB_BASE=$(shell cd ../.. && pwd) CCBN=$(shell basename ${CC}) BUILD_DIR=${THIS_LIB_BASE}/build/${BUILD_SUBDIR}/${CCBN} export TMP_DIR=${THIS_LIB_BASE}/tmp @@ -86,7 +86,10 @@ else export CMP=cmp endif +BIG_FILE ?= none +EXPECT_TIMEOUT ?= 5 EXPECT=../../scripts/test-expect.sh +export EXPECTED_PATH=expected MAKE_BIN=$(notdir ${MAKE}) diff --git a/scripts/test-expect.sh b/scripts/test-expect.sh index 2ab3f691..92aad22f 100755 --- a/scripts/test-expect.sh +++ b/scripts/test-expect.sh @@ -9,10 +9,12 @@ else export STAGE=-"$2" fi export CAPTURE="${TMP_DIR}/$TARGET$STAGE".out -EXPECTED="$(pwd)/expected/$TARGET$STAGE".out +EXPECTED="$EXPECTED_PATH/$TARGET$STAGE".out export EXPECTED matched=false +t=${EXPECT_TIMEOUT:-5} + cleanup() { if $matched; then if [ -z "$STAGE" ]; then @@ -33,7 +35,7 @@ trap cleanup INT TERM QUIT printf "\n%s, %s" "$TARGET" "${2:-}" >> "${TIMINGS_CSV}" set +e -match_time=$(time -p timeout -k 6 5 "${script_dir}"/test-retry-capture-cmp.sh 2>&1) +match_time=$(time -p timeout -k $(( t + 1 )) $t "${script_dir}"/test-retry-capture-cmp.sh 2>&1) status=$? set -e From 813f29b64387d6f040e534c360094b8b5470c250 Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Mon, 9 Dec 2024 14:50:59 +0000 Subject: [PATCH 11/13] sheet: Add benchmark test --- app/test/Makefile | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/test/Makefile b/app/test/Makefile index c5479d7e..34acf788 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -780,3 +780,15 @@ test-sheet-prop-cmd-opt: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${BUILD_DIR}/bin/zsv_p # @tmux -v capture-pane -t $@ -p ${REDIRECT1} ${TMP_DIR}/$@.out # @tmux send-keys -t $@ "q" # @${CMP} ${TMP_DIR}/$@.out expected/$@.out && ${TEST_PASS} || (echo 'Incorrect output:' && cat ${TMP_DIR}/$@.out && ${TEST_FAIL}) + +benchmark-sheet-index: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} + @${TEST_INIT} + @if [ "${BIG_FILE}" = "none" ]; then \ + echo "Need to set BIG_FILE to a CSV file; ideally 10GB+"; \ + fi + @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf + @(tmux new-session -x 80 -y 50 -d -s "$@" "${PREFIX} $< ${BIG_FILE}" && \ + ${EXPECT} $@ indexed && \ + tmux send-keys -t $@ "G" && \ + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) + From d8cc28bf1c6c677d25d7a0080ffdc36c7768323c Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Tue, 10 Dec 2024 09:07:41 +0000 Subject: [PATCH 12/13] sheet: Only notify the UI of progress after 32MB This reduces the indexing time by 3-4x on a 13GB file with a Ryzen 7 6800U. --- app/sheet/index.c | 5 +++++ app/sheet/transformation.c | 1 + 2 files changed, 6 insertions(+) diff --git a/app/sheet/index.c b/app/sheet/index.c index 98a2eb96..a411888b 100644 --- a/app/sheet/index.c +++ b/app/sheet/index.c @@ -46,7 +46,12 @@ enum zsv_index_status build_memory_index(struct zsvsheet_index_opts *optsp) { optsp->uib->index = ixr.ix; char cancelled = 0; + size_t committed_bytes = 0; while (!cancelled && (zst = zsv_parse_more(ixr.parser)) == zsv_status_ok) { + if (zsv_cum_scanned_length(ixr.parser) - committed_bytes < 32 * 1024 * 1024) + continue; + committed_bytes = zsv_cum_scanned_length(ixr.parser); + pthread_mutex_lock(&optsp->uib->mutex); if (optsp->uib->worker_cancelled) { cancelled = 1; diff --git a/app/sheet/transformation.c b/app/sheet/transformation.c index 0d65d20f..0b654ccd 100644 --- a/app/sheet/transformation.c +++ b/app/sheet/transformation.c @@ -205,6 +205,7 @@ enum zsvsheet_status zsvsheet_push_transformation(zsvsheet_proc_context_t ctx, zopts.ctx = opts.user_context; zopts.row_handler = (void (*)(void *))opts.row_handler; zopts.stream = fopen(filename, "rb"); + zopts.buffsize = 2 * 1024 * 1024; if (!zopts.stream) goto error; From 5dd0e1a9c32c840d4573ea416370af21608c905a Mon Sep 17 00:00:00 2001 From: Richard Palethorpe Date: Wed, 11 Dec 2024 11:53:26 +0000 Subject: [PATCH 13/13] sheet: Fix bug in \r\n line handling Also add a test on a file with only \r\n line endings as the mixed line endings test did not reproduce this bug. --- app/test/Makefile | 10 + app/test/expected/test-sheet-15-bottom.out | 6 + app/test/expected/test-sheet-15-indexed.out | 6 + app/test/expected/test-sheet-15.out | 6 + app/utils/index.c | 8 +- data/test/crlf-line-ending.csv | 2747 +++++++++++++++++++ 6 files changed, 2778 insertions(+), 5 deletions(-) create mode 100644 app/test/expected/test-sheet-15-bottom.out create mode 100644 app/test/expected/test-sheet-15-indexed.out create mode 100644 app/test/expected/test-sheet-15.out create mode 100644 data/test/crlf-line-ending.csv diff --git a/app/test/Makefile b/app/test/Makefile index 34acf788..4e2675bf 100644 --- a/app/test/Makefile +++ b/app/test/Makefile @@ -725,6 +725,16 @@ test-sheet-14: ${BUILD_DIR}/bin/zsv_sheet${EXE} worldcitiespop_mil.csv ${TIMINGS tmux send-keys -t $@ "Escape" && \ ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) +test-sheet-15: ${BUILD_DIR}/bin/zsv_sheet${EXE} ${TIMINGS_CSV} + @${TEST_INIT} + @echo 'set-option default-terminal "${TMUX_TERM}"' > ~/.tmux.conf + @(tmux new-session -x 80 -y 6 -d -s $@ "${PREFIX} $< -d 3 ${TEST_DATA_DIR}/test/crlf-line-ending.csv" && \ + ${EXPECT} $@ indexed && \ + tmux send-keys -t $@ "G" && \ + ${EXPECT} $@ bottom && \ + tmux send-keys -t $@ -N 4096 "k" && \ + ${EXPECT} $@ && ${TEST_PASS} || ${TEST_FAIL}) + test-sheet-subcommand:\ test-sheet-subcommand-open-file-prompt test-sheet-subcommand-open-file-argument\ test-sheet-subcommand-filter-file-prompt test-sheet-subcommand-filter-file-argument diff --git a/app/test/expected/test-sheet-15-bottom.out b/app/test/expected/test-sheet-15-bottom.out new file mode 100644 index 00000000..d9824d9d --- /dev/null +++ b/app/test/expected/test-sheet-15-bottom.out @@ -0,0 +1,6 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +4093 A4093 B4093 C4093 +4094 A4094 B4094 C4094 +4095 A4095 B4095 C4095 +4096 A4096 B4096 C4096 +? for help 4096 diff --git a/app/test/expected/test-sheet-15-indexed.out b/app/test/expected/test-sheet-15-indexed.out new file mode 100644 index 00000000..fbae507e --- /dev/null +++ b/app/test/expected/test-sheet-15-indexed.out @@ -0,0 +1,6 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +4 A4 B4 C4 +? for help 1 diff --git a/app/test/expected/test-sheet-15.out b/app/test/expected/test-sheet-15.out new file mode 100644 index 00000000..3de62ade --- /dev/null +++ b/app/test/expected/test-sheet-15.out @@ -0,0 +1,6 @@ +Row # HA1 HA2 HA3 HB1 HB2 HB3 HC1 HC2 HC3 +1 A1 B1 C1 +2 A2 B2 C2 +3 A3 B3 C3 +4 A4 B4 C4 +? for help Row # diff --git a/app/utils/index.c b/app/utils/index.c index ef656b6d..c5d397ca 100644 --- a/app/utils/index.c +++ b/app/utils/index.c @@ -144,12 +144,10 @@ static enum zsv_index_status seek_and_check_newline(long *offset, struct zsv_opt if (new_line[0] == '\n') { *offset += 1; } else if (new_line[0] == '\r') { - if (new_line[1] == '\n') { - *offset += 1; - return zsv_index_status_ok; - } - *offset += 1; + + if (new_line[1] == '\n') + *offset += 1; } else { return zsv_index_status_error; } diff --git a/data/test/crlf-line-ending.csv b/data/test/crlf-line-ending.csv new file mode 100644 index 00000000..5f5d1888 --- /dev/null +++ b/data/test/crlf-line-ending.csv @@ -0,0 +1,2747 @@ +HA1, HB1, HC1 +HA2, HB2, HC2 +HA3, HB3, HC3 +A1, B1, C1 +A2, B2, C2 +A3, B3, C3 +A4, B4, C4 +A5, B5, C5 A6, B6, C6 +A7, B7, C7 +A8, B8, C8 +A9, B9, C9 A10, B10, C10 +A11, B11, C11 +A12, B12, C12 +A13, B13, C13 +A14, B14, C14 +A15, B15, C15 A16, B16, C16 +A17, B17, C17 +A18, B18, C18 +A19, B19, C19 A20, B20, C20 A21, B21, C21 +A22, B22, C22 +A23, B23, C23 +A24, B24, C24 +A25, B25, C25 +A26, B26, C26 +A27, B27, C27 +A28, B28, C28 +A29, B29, C29 +A30, B30, C30 +A31, B31, C31 +A32, B32, C32 +A33, B33, C33 +A34, B34, C34 A35, B35, C35 +A36, B36, C36 +A37, B37, C37 +A38, B38, C38 +A39, B39, C39 +A40, B40, C40 +A41, B41, C41 +A42, B42, C42 A43, B43, C43 +A44, B44, C44 +A45, B45, C45 A46, B46, C46 +A47, B47, C47 +A48, B48, C48 +A49, B49, C49 +A50, B50, C50 +A51, B51, C51 +A52, B52, C52 +A53, B53, C53 A54, B54, C54 A55, B55, C55 A56, B56, C56 A57, B57, C57 A58, B58, C58 A59, B59, C59 A60, B60, C60 +A61, B61, C61 +A62, B62, C62 +A63, B63, C63 A64, B64, C64 +A65, B65, C65 +A66, B66, C66 +A67, B67, C67 +A68, B68, C68 +A69, B69, C69 A70, B70, C70 +A71, B71, C71 A72, B72, C72 +A73, B73, C73 A74, B74, C74 A75, B75, C75 +A76, B76, C76 +A77, B77, C77 +A78, B78, C78 A79, B79, C79 +A80, B80, C80 A81, B81, C81 +A82, B82, C82 +A83, B83, C83 A84, B84, C84 +A85, B85, C85 +A86, B86, C86 +A87, B87, C87 +A88, B88, C88 A89, B89, C89 +A90, B90, C90 +A91, B91, C91 A92, B92, C92 +A93, B93, C93 +A94, B94, C94 +A95, B95, C95 A96, B96, C96 +A97, B97, C97 +A98, B98, C98 A99, B99, C99 +A100, B100, C100 +A101, B101, C101 +A102, B102, C102 +A103, B103, C103 +A104, B104, C104 +A105, B105, C105 A106, B106, C106 +A107, B107, C107 +A108, B108, C108 +A109, B109, C109 A110, B110, C110 A111, B111, C111 +A112, B112, C112 +A113, B113, C113 +A114, B114, C114 A115, B115, C115 A116, B116, C116 +A117, B117, C117 +A118, B118, C118 A119, B119, C119 A120, B120, C120 +A121, B121, C121 +A122, B122, C122 +A123, B123, C123 A124, B124, C124 +A125, B125, C125 A126, B126, C126 A127, B127, C127 +A128, B128, C128 +A129, B129, C129 A130, B130, C130 +A131, B131, C131 +A132, B132, C132 A133, B133, C133 +A134, B134, C134 +A135, B135, C135 +A136, B136, C136 +A137, B137, C137 +A138, B138, C138 A139, B139, C139 +A140, B140, C140 +A141, B141, C141 +A142, B142, C142 A143, B143, C143 A144, B144, C144 +A145, B145, C145 A146, B146, C146 +A147, B147, C147 +A148, B148, C148 A149, B149, C149 A150, B150, C150 +A151, B151, C151 +A152, B152, C152 A153, B153, C153 +A154, B154, C154 A155, B155, C155 +A156, B156, C156 A157, B157, C157 +A158, B158, C158 A159, B159, C159 A160, B160, C160 +A161, B161, C161 +A162, B162, C162 +A163, B163, C163 +A164, B164, C164 +A165, B165, C165 A166, B166, C166 +A167, B167, C167 +A168, B168, C168 +A169, B169, C169 A170, B170, C170 +A171, B171, C171 +A172, B172, C172 A173, B173, C173 +A174, B174, C174 +A175, B175, C175 A176, B176, C176 +A177, B177, C177 +A178, B178, C178 A179, B179, C179 +A180, B180, C180 A181, B181, C181 +A182, B182, C182 +A183, B183, C183 A184, B184, C184 +A185, B185, C185 +A186, B186, C186 +A187, B187, C187 +A188, B188, C188 +A189, B189, C189 +A190, B190, C190 +A191, B191, C191 +A192, B192, C192 +A193, B193, C193 A194, B194, C194 +A195, B195, C195 +A196, B196, C196 +A197, B197, C197 +A198, B198, C198 +A199, B199, C199 +A200, B200, C200 +A201, B201, C201 A202, B202, C202 A203, B203, C203 +A204, B204, C204 +A205, B205, C205 +A206, B206, C206 +A207, B207, C207 +A208, B208, C208 A209, B209, C209 +A210, B210, C210 +A211, B211, C211 A212, B212, C212 A213, B213, C213 A214, B214, C214 +A215, B215, C215 +A216, B216, C216 A217, B217, C217 +A218, B218, C218 +A219, B219, C219 +A220, B220, C220 A221, B221, C221 +A222, B222, C222 +A223, B223, C223 A224, B224, C224 +A225, B225, C225 +A226, B226, C226 +A227, B227, C227 +A228, B228, C228 +A229, B229, C229 +A230, B230, C230 A231, B231, C231 A232, B232, C232 +A233, B233, C233 +A234, B234, C234 A235, B235, C235 A236, B236, C236 +A237, B237, C237 +A238, B238, C238 +A239, B239, C239 A240, B240, C240 +A241, B241, C241 +A242, B242, C242 +A243, B243, C243 A244, B244, C244 +A245, B245, C245 +A246, B246, C246 A247, B247, C247 A248, B248, C248 A249, B249, C249 A250, B250, C250 +A251, B251, C251 +A252, B252, C252 +A253, B253, C253 +A254, B254, C254 A255, B255, C255 +A256, B256, C256 +A257, B257, C257 +A258, B258, C258 +A259, B259, C259 +A260, B260, C260 +A261, B261, C261 +A262, B262, C262 +A263, B263, C263 +A264, B264, C264 +A265, B265, C265 +A266, B266, C266 +A267, B267, C267 +A268, B268, C268 A269, B269, C269 +A270, B270, C270 +A271, B271, C271 A272, B272, C272 A273, B273, C273 +A274, B274, C274 +A275, B275, C275 +A276, B276, C276 +A277, B277, C277 A278, B278, C278 A279, B279, C279 A280, B280, C280 A281, B281, C281 +A282, B282, C282 +A283, B283, C283 A284, B284, C284 +A285, B285, C285 A286, B286, C286 +A287, B287, C287 +A288, B288, C288 +A289, B289, C289 +A290, B290, C290 A291, B291, C291 +A292, B292, C292 +A293, B293, C293 +A294, B294, C294 +A295, B295, C295 +A296, B296, C296 +A297, B297, C297 A298, B298, C298 +A299, B299, C299 A300, B300, C300 +A301, B301, C301 +A302, B302, C302 +A303, B303, C303 +A304, B304, C304 +A305, B305, C305 A306, B306, C306 +A307, B307, C307 +A308, B308, C308 +A309, B309, C309 A310, B310, C310 +A311, B311, C311 A312, B312, C312 A313, B313, C313 A314, B314, C314 +A315, B315, C315 +A316, B316, C316 +A317, B317, C317 A318, B318, C318 A319, B319, C319 +A320, B320, C320 +A321, B321, C321 A322, B322, C322 +A323, B323, C323 +A324, B324, C324 +A325, B325, C325 +A326, B326, C326 A327, B327, C327 +A328, B328, C328 +A329, B329, C329 A330, B330, C330 +A331, B331, C331 +A332, B332, C332 +A333, B333, C333 +A334, B334, C334 +A335, B335, C335 A336, B336, C336 A337, B337, C337 +A338, B338, C338 A339, B339, C339 A340, B340, C340 +A341, B341, C341 +A342, B342, C342 A343, B343, C343 +A344, B344, C344 +A345, B345, C345 +A346, B346, C346 +A347, B347, C347 A348, B348, C348 A349, B349, C349 +A350, B350, C350 A351, B351, C351 +A352, B352, C352 +A353, B353, C353 +A354, B354, C354 +A355, B355, C355 A356, B356, C356 A357, B357, C357 A358, B358, C358 +A359, B359, C359 +A360, B360, C360 +A361, B361, C361 +A362, B362, C362 A363, B363, C363 +A364, B364, C364 +A365, B365, C365 +A366, B366, C366 +A367, B367, C367 A368, B368, C368 +A369, B369, C369 +A370, B370, C370 +A371, B371, C371 +A372, B372, C372 +A373, B373, C373 +A374, B374, C374 +A375, B375, C375 +A376, B376, C376 A377, B377, C377 +A378, B378, C378 +A379, B379, C379 +A380, B380, C380 A381, B381, C381 A382, B382, C382 +A383, B383, C383 A384, B384, C384 +A385, B385, C385 +A386, B386, C386 +A387, B387, C387 +A388, B388, C388 +A389, B389, C389 A390, B390, C390 +A391, B391, C391 A392, B392, C392 +A393, B393, C393 +A394, B394, C394 +A395, B395, C395 A396, B396, C396 +A397, B397, C397 +A398, B398, C398 A399, B399, C399 +A400, B400, C400 +A401, B401, C401 +A402, B402, C402 +A403, B403, C403 A404, B404, C404 +A405, B405, C405 A406, B406, C406 A407, B407, C407 +A408, B408, C408 +A409, B409, C409 A410, B410, C410 +A411, B411, C411 +A412, B412, C412 +A413, B413, C413 +A414, B414, C414 +A415, B415, C415 +A416, B416, C416 +A417, B417, C417 +A418, B418, C418 +A419, B419, C419 +A420, B420, C420 +A421, B421, C421 A422, B422, C422 +A423, B423, C423 A424, B424, C424 +A425, B425, C425 +A426, B426, C426 A427, B427, C427 A428, B428, C428 A429, B429, C429 +A430, B430, C430 +A431, B431, C431 +A432, B432, C432 +A433, B433, C433 +A434, B434, C434 +A435, B435, C435 +A436, B436, C436 +A437, B437, C437 +A438, B438, C438 +A439, B439, C439 A440, B440, C440 +A441, B441, C441 A442, B442, C442 +A443, B443, C443 +A444, B444, C444 A445, B445, C445 +A446, B446, C446 +A447, B447, C447 A448, B448, C448 A449, B449, C449 +A450, B450, C450 A451, B451, C451 A452, B452, C452 +A453, B453, C453 +A454, B454, C454 +A455, B455, C455 +A456, B456, C456 +A457, B457, C457 +A458, B458, C458 A459, B459, C459 +A460, B460, C460 +A461, B461, C461 A462, B462, C462 A463, B463, C463 +A464, B464, C464 +A465, B465, C465 +A466, B466, C466 +A467, B467, C467 +A468, B468, C468 +A469, B469, C469 +A470, B470, C470 A471, B471, C471 +A472, B472, C472 +A473, B473, C473 +A474, B474, C474 +A475, B475, C475 A476, B476, C476 +A477, B477, C477 +A478, B478, C478 +A479, B479, C479 A480, B480, C480 +A481, B481, C481 +A482, B482, C482 +A483, B483, C483 +A484, B484, C484 +A485, B485, C485 A486, B486, C486 A487, B487, C487 A488, B488, C488 +A489, B489, C489 +A490, B490, C490 +A491, B491, C491 +A492, B492, C492 +A493, B493, C493 +A494, B494, C494 +A495, B495, C495 +A496, B496, C496 +A497, B497, C497 +A498, B498, C498 +A499, B499, C499 +A500, B500, C500 +A501, B501, C501 +A502, B502, C502 +A503, B503, C503 +A504, B504, C504 +A505, B505, C505 +A506, B506, C506 +A507, B507, C507 A508, B508, C508 +A509, B509, C509 +A510, B510, C510 A511, B511, C511 +A512, B512, C512 +A513, B513, C513 A514, B514, C514 +A515, B515, C515 +A516, B516, C516 +A517, B517, C517 A518, B518, C518 +A519, B519, C519 +A520, B520, C520 A521, B521, C521 A522, B522, C522 +A523, B523, C523 +A524, B524, C524 A525, B525, C525 +A526, B526, C526 A527, B527, C527 +A528, B528, C528 A529, B529, C529 +A530, B530, C530 A531, B531, C531 A532, B532, C532 A533, B533, C533 +A534, B534, C534 +A535, B535, C535 A536, B536, C536 +A537, B537, C537 +A538, B538, C538 +A539, B539, C539 +A540, B540, C540 +A541, B541, C541 A542, B542, C542 +A543, B543, C543 A544, B544, C544 +A545, B545, C545 A546, B546, C546 A547, B547, C547 +A548, B548, C548 A549, B549, C549 +A550, B550, C550 +A551, B551, C551 +A552, B552, C552 +A553, B553, C553 A554, B554, C554 A555, B555, C555 A556, B556, C556 +A557, B557, C557 +A558, B558, C558 A559, B559, C559 +A560, B560, C560 +A561, B561, C561 +A562, B562, C562 A563, B563, C563 A564, B564, C564 +A565, B565, C565 +A566, B566, C566 +A567, B567, C567 +A568, B568, C568 +A569, B569, C569 A570, B570, C570 +A571, B571, C571 +A572, B572, C572 +A573, B573, C573 +A574, B574, C574 +A575, B575, C575 A576, B576, C576 +A577, B577, C577 +A578, B578, C578 +A579, B579, C579 +A580, B580, C580 +A581, B581, C581 +A582, B582, C582 +A583, B583, C583 +A584, B584, C584 +A585, B585, C585 +A586, B586, C586 +A587, B587, C587 +A588, B588, C588 +A589, B589, C589 +A590, B590, C590 +A591, B591, C591 +A592, B592, C592 +A593, B593, C593 +A594, B594, C594 +A595, B595, C595 +A596, B596, C596 +A597, B597, C597 +A598, B598, C598 A599, B599, C599 +A600, B600, C600 +A601, B601, C601 A602, B602, C602 +A603, B603, C603 A604, B604, C604 A605, B605, C605 +A606, B606, C606 +A607, B607, C607 A608, B608, C608 +A609, B609, C609 +A610, B610, C610 A611, B611, C611 A612, B612, C612 +A613, B613, C613 +A614, B614, C614 +A615, B615, C615 +A616, B616, C616 +A617, B617, C617 +A618, B618, C618 +A619, B619, C619 +A620, B620, C620 A621, B621, C621 +A622, B622, C622 +A623, B623, C623 A624, B624, C624 A625, B625, C625 +A626, B626, C626 +A627, B627, C627 +A628, B628, C628 +A629, B629, C629 +A630, B630, C630 +A631, B631, C631 A632, B632, C632 A633, B633, C633 +A634, B634, C634 +A635, B635, C635 A636, B636, C636 +A637, B637, C637 A638, B638, C638 A639, B639, C639 +A640, B640, C640 A641, B641, C641 +A642, B642, C642 A643, B643, C643 A644, B644, C644 +A645, B645, C645 +A646, B646, C646 +A647, B647, C647 +A648, B648, C648 +A649, B649, C649 +A650, B650, C650 A651, B651, C651 A652, B652, C652 A653, B653, C653 +A654, B654, C654 +A655, B655, C655 +A656, B656, C656 A657, B657, C657 A658, B658, C658 +A659, B659, C659 A660, B660, C660 +A661, B661, C661 +A662, B662, C662 +A663, B663, C663 +A664, B664, C664 +A665, B665, C665 +A666, B666, C666 +A667, B667, C667 +A668, B668, C668 A669, B669, C669 A670, B670, C670 A671, B671, C671 +A672, B672, C672 A673, B673, C673 A674, B674, C674 +A675, B675, C675 A676, B676, C676 +A677, B677, C677 +A678, B678, C678 A679, B679, C679 +A680, B680, C680 +A681, B681, C681 +A682, B682, C682 A683, B683, C683 A684, B684, C684 +A685, B685, C685 +A686, B686, C686 +A687, B687, C687 +A688, B688, C688 A689, B689, C689 A690, B690, C690 A691, B691, C691 +A692, B692, C692 A693, B693, C693 +A694, B694, C694 +A695, B695, C695 +A696, B696, C696 +A697, B697, C697 +A698, B698, C698 +A699, B699, C699 A700, B700, C700 +A701, B701, C701 +A702, B702, C702 +A703, B703, C703 +A704, B704, C704 +A705, B705, C705 +A706, B706, C706 +A707, B707, C707 +A708, B708, C708 +A709, B709, C709 +A710, B710, C710 +A711, B711, C711 A712, B712, C712 +A713, B713, C713 A714, B714, C714 +A715, B715, C715 A716, B716, C716 A717, B717, C717 +A718, B718, C718 A719, B719, C719 +A720, B720, C720 +A721, B721, C721 +A722, B722, C722 +A723, B723, C723 A724, B724, C724 +A725, B725, C725 +A726, B726, C726 +A727, B727, C727 +A728, B728, C728 +A729, B729, C729 +A730, B730, C730 +A731, B731, C731 A732, B732, C732 +A733, B733, C733 +A734, B734, C734 +A735, B735, C735 +A736, B736, C736 +A737, B737, C737 +A738, B738, C738 +A739, B739, C739 +A740, B740, C740 +A741, B741, C741 +A742, B742, C742 +A743, B743, C743 A744, B744, C744 +A745, B745, C745 +A746, B746, C746 +A747, B747, C747 A748, B748, C748 +A749, B749, C749 +A750, B750, C750 +A751, B751, C751 A752, B752, C752 A753, B753, C753 +A754, B754, C754 A755, B755, C755 +A756, B756, C756 +A757, B757, C757 +A758, B758, C758 A759, B759, C759 +A760, B760, C760 A761, B761, C761 A762, B762, C762 A763, B763, C763 A764, B764, C764 +A765, B765, C765 A766, B766, C766 +A767, B767, C767 +A768, B768, C768 +A769, B769, C769 +A770, B770, C770 +A771, B771, C771 +A772, B772, C772 +A773, B773, C773 A774, B774, C774 +A775, B775, C775 A776, B776, C776 +A777, B777, C777 +A778, B778, C778 A779, B779, C779 +A780, B780, C780 +A781, B781, C781 +A782, B782, C782 +A783, B783, C783 A784, B784, C784 +A785, B785, C785 +A786, B786, C786 A787, B787, C787 +A788, B788, C788 A789, B789, C789 +A790, B790, C790 A791, B791, C791 A792, B792, C792 +A793, B793, C793 +A794, B794, C794 A795, B795, C795 +A796, B796, C796 +A797, B797, C797 +A798, B798, C798 A799, B799, C799 +A800, B800, C800 +A801, B801, C801 A802, B802, C802 +A803, B803, C803 +A804, B804, C804 +A805, B805, C805 +A806, B806, C806 +A807, B807, C807 +A808, B808, C808 A809, B809, C809 +A810, B810, C810 A811, B811, C811 +A812, B812, C812 A813, B813, C813 A814, B814, C814 +A815, B815, C815 A816, B816, C816 +A817, B817, C817 +A818, B818, C818 +A819, B819, C819 +A820, B820, C820 +A821, B821, C821 +A822, B822, C822 +A823, B823, C823 +A824, B824, C824 A825, B825, C825 +A826, B826, C826 +A827, B827, C827 +A828, B828, C828 +A829, B829, C829 +A830, B830, C830 +A831, B831, C831 +A832, B832, C832 +A833, B833, C833 +A834, B834, C834 +A835, B835, C835 +A836, B836, C836 +A837, B837, C837 +A838, B838, C838 A839, B839, C839 +A840, B840, C840 +A841, B841, C841 +A842, B842, C842 +A843, B843, C843 +A844, B844, C844 +A845, B845, C845 +A846, B846, C846 +A847, B847, C847 +A848, B848, C848 +A849, B849, C849 +A850, B850, C850 A851, B851, C851 +A852, B852, C852 A853, B853, C853 A854, B854, C854 +A855, B855, C855 A856, B856, C856 A857, B857, C857 A858, B858, C858 A859, B859, C859 A860, B860, C860 +A861, B861, C861 +A862, B862, C862 +A863, B863, C863 A864, B864, C864 +A865, B865, C865 +A866, B866, C866 +A867, B867, C867 A868, B868, C868 +A869, B869, C869 A870, B870, C870 A871, B871, C871 +A872, B872, C872 A873, B873, C873 A874, B874, C874 +A875, B875, C875 +A876, B876, C876 A877, B877, C877 A878, B878, C878 +A879, B879, C879 +A880, B880, C880 +A881, B881, C881 A882, B882, C882 A883, B883, C883 A884, B884, C884 A885, B885, C885 A886, B886, C886 +A887, B887, C887 +A888, B888, C888 +A889, B889, C889 +A890, B890, C890 +A891, B891, C891 A892, B892, C892 +A893, B893, C893 +A894, B894, C894 +A895, B895, C895 +A896, B896, C896 +A897, B897, C897 +A898, B898, C898 +A899, B899, C899 A900, B900, C900 A901, B901, C901 A902, B902, C902 A903, B903, C903 +A904, B904, C904 A905, B905, C905 +A906, B906, C906 +A907, B907, C907 +A908, B908, C908 +A909, B909, C909 +A910, B910, C910 +A911, B911, C911 A912, B912, C912 A913, B913, C913 +A914, B914, C914 +A915, B915, C915 +A916, B916, C916 A917, B917, C917 A918, B918, C918 +A919, B919, C919 +A920, B920, C920 A921, B921, C921 +A922, B922, C922 +A923, B923, C923 +A924, B924, C924 A925, B925, C925 +A926, B926, C926 +A927, B927, C927 +A928, B928, C928 +A929, B929, C929 A930, B930, C930 +A931, B931, C931 A932, B932, C932 A933, B933, C933 A934, B934, C934 +A935, B935, C935 +A936, B936, C936 +A937, B937, C937 +A938, B938, C938 +A939, B939, C939 +A940, B940, C940 A941, B941, C941 +A942, B942, C942 +A943, B943, C943 +A944, B944, C944 A945, B945, C945 A946, B946, C946 A947, B947, C947 A948, B948, C948 +A949, B949, C949 +A950, B950, C950 A951, B951, C951 +A952, B952, C952 +A953, B953, C953 A954, B954, C954 +A955, B955, C955 +A956, B956, C956 A957, B957, C957 +A958, B958, C958 +A959, B959, C959 A960, B960, C960 A961, B961, C961 +A962, B962, C962 +A963, B963, C963 A964, B964, C964 +A965, B965, C965 +A966, B966, C966 +A967, B967, C967 A968, B968, C968 +A969, B969, C969 A970, B970, C970 A971, B971, C971 +A972, B972, C972 +A973, B973, C973 A974, B974, C974 +A975, B975, C975 +A976, B976, C976 +A977, B977, C977 +A978, B978, C978 +A979, B979, C979 +A980, B980, C980 A981, B981, C981 +A982, B982, C982 +A983, B983, C983 +A984, B984, C984 +A985, B985, C985 A986, B986, C986 +A987, B987, C987 +A988, B988, C988 A989, B989, C989 A990, B990, C990 A991, B991, C991 A992, B992, C992 +A993, B993, C993 A994, B994, C994 A995, B995, C995 A996, B996, C996 A997, B997, C997 A998, B998, C998 A999, B999, C999 +A1000, B1000, C1000 +A1001, B1001, C1001 +A1002, B1002, C1002 +A1003, B1003, C1003 +A1004, B1004, C1004 A1005, B1005, C1005 +A1006, B1006, C1006 A1007, B1007, C1007 A1008, B1008, C1008 +A1009, B1009, C1009 +A1010, B1010, C1010 +A1011, B1011, C1011 +A1012, B1012, C1012 A1013, B1013, C1013 +A1014, B1014, C1014 +A1015, B1015, C1015 +A1016, B1016, C1016 +A1017, B1017, C1017 +A1018, B1018, C1018 +A1019, B1019, C1019 A1020, B1020, C1020 +A1021, B1021, C1021 +A1022, B1022, C1022 +A1023, B1023, C1023 A1024, B1024, C1024 +A1025, B1025, C1025 A1026, B1026, C1026 +A1027, B1027, C1027 +A1028, B1028, C1028 +A1029, B1029, C1029 A1030, B1030, C1030 A1031, B1031, C1031 +A1032, B1032, C1032 A1033, B1033, C1033 +A1034, B1034, C1034 A1035, B1035, C1035 A1036, B1036, C1036 +A1037, B1037, C1037 A1038, B1038, C1038 +A1039, B1039, C1039 A1040, B1040, C1040 A1041, B1041, C1041 A1042, B1042, C1042 +A1043, B1043, C1043 +A1044, B1044, C1044 A1045, B1045, C1045 +A1046, B1046, C1046 A1047, B1047, C1047 +A1048, B1048, C1048 +A1049, B1049, C1049 +A1050, B1050, C1050 +A1051, B1051, C1051 +A1052, B1052, C1052 +A1053, B1053, C1053 A1054, B1054, C1054 +A1055, B1055, C1055 A1056, B1056, C1056 +A1057, B1057, C1057 +A1058, B1058, C1058 A1059, B1059, C1059 +A1060, B1060, C1060 +A1061, B1061, C1061 A1062, B1062, C1062 +A1063, B1063, C1063 +A1064, B1064, C1064 +A1065, B1065, C1065 +A1066, B1066, C1066 A1067, B1067, C1067 +A1068, B1068, C1068 +A1069, B1069, C1069 +A1070, B1070, C1070 +A1071, B1071, C1071 A1072, B1072, C1072 +A1073, B1073, C1073 +A1074, B1074, C1074 +A1075, B1075, C1075 +A1076, B1076, C1076 +A1077, B1077, C1077 +A1078, B1078, C1078 A1079, B1079, C1079 +A1080, B1080, C1080 +A1081, B1081, C1081 +A1082, B1082, C1082 +A1083, B1083, C1083 +A1084, B1084, C1084 +A1085, B1085, C1085 +A1086, B1086, C1086 +A1087, B1087, C1087 +A1088, B1088, C1088 +A1089, B1089, C1089 +A1090, B1090, C1090 +A1091, B1091, C1091 +A1092, B1092, C1092 +A1093, B1093, C1093 +A1094, B1094, C1094 +A1095, B1095, C1095 +A1096, B1096, C1096 +A1097, B1097, C1097 +A1098, B1098, C1098 +A1099, B1099, C1099 +A1100, B1100, C1100 +A1101, B1101, C1101 +A1102, B1102, C1102 A1103, B1103, C1103 +A1104, B1104, C1104 A1105, B1105, C1105 A1106, B1106, C1106 +A1107, B1107, C1107 +A1108, B1108, C1108 +A1109, B1109, C1109 A1110, B1110, C1110 A1111, B1111, C1111 A1112, B1112, C1112 +A1113, B1113, C1113 A1114, B1114, C1114 +A1115, B1115, C1115 A1116, B1116, C1116 +A1117, B1117, C1117 +A1118, B1118, C1118 A1119, B1119, C1119 A1120, B1120, C1120 A1121, B1121, C1121 A1122, B1122, C1122 A1123, B1123, C1123 +A1124, B1124, C1124 +A1125, B1125, C1125 +A1126, B1126, C1126 +A1127, B1127, C1127 A1128, B1128, C1128 +A1129, B1129, C1129 +A1130, B1130, C1130 A1131, B1131, C1131 +A1132, B1132, C1132 +A1133, B1133, C1133 +A1134, B1134, C1134 +A1135, B1135, C1135 +A1136, B1136, C1136 A1137, B1137, C1137 +A1138, B1138, C1138 +A1139, B1139, C1139 A1140, B1140, C1140 A1141, B1141, C1141 +A1142, B1142, C1142 +A1143, B1143, C1143 +A1144, B1144, C1144 A1145, B1145, C1145 +A1146, B1146, C1146 A1147, B1147, C1147 A1148, B1148, C1148 +A1149, B1149, C1149 +A1150, B1150, C1150 +A1151, B1151, C1151 +A1152, B1152, C1152 +A1153, B1153, C1153 A1154, B1154, C1154 +A1155, B1155, C1155 +A1156, B1156, C1156 A1157, B1157, C1157 +A1158, B1158, C1158 +A1159, B1159, C1159 +A1160, B1160, C1160 A1161, B1161, C1161 +A1162, B1162, C1162 +A1163, B1163, C1163 +A1164, B1164, C1164 +A1165, B1165, C1165 A1166, B1166, C1166 +A1167, B1167, C1167 +A1168, B1168, C1168 +A1169, B1169, C1169 +A1170, B1170, C1170 A1171, B1171, C1171 +A1172, B1172, C1172 +A1173, B1173, C1173 +A1174, B1174, C1174 A1175, B1175, C1175 A1176, B1176, C1176 A1177, B1177, C1177 +A1178, B1178, C1178 +A1179, B1179, C1179 +A1180, B1180, C1180 +A1181, B1181, C1181 +A1182, B1182, C1182 +A1183, B1183, C1183 A1184, B1184, C1184 +A1185, B1185, C1185 A1186, B1186, C1186 +A1187, B1187, C1187 A1188, B1188, C1188 A1189, B1189, C1189 +A1190, B1190, C1190 +A1191, B1191, C1191 +A1192, B1192, C1192 +A1193, B1193, C1193 A1194, B1194, C1194 A1195, B1195, C1195 +A1196, B1196, C1196 +A1197, B1197, C1197 +A1198, B1198, C1198 +A1199, B1199, C1199 +A1200, B1200, C1200 A1201, B1201, C1201 A1202, B1202, C1202 +A1203, B1203, C1203 +A1204, B1204, C1204 A1205, B1205, C1205 A1206, B1206, C1206 +A1207, B1207, C1207 +A1208, B1208, C1208 +A1209, B1209, C1209 +A1210, B1210, C1210 +A1211, B1211, C1211 A1212, B1212, C1212 +A1213, B1213, C1213 A1214, B1214, C1214 +A1215, B1215, C1215 +A1216, B1216, C1216 +A1217, B1217, C1217 A1218, B1218, C1218 +A1219, B1219, C1219 A1220, B1220, C1220 +A1221, B1221, C1221 A1222, B1222, C1222 +A1223, B1223, C1223 +A1224, B1224, C1224 +A1225, B1225, C1225 A1226, B1226, C1226 A1227, B1227, C1227 +A1228, B1228, C1228 A1229, B1229, C1229 +A1230, B1230, C1230 +A1231, B1231, C1231 +A1232, B1232, C1232 +A1233, B1233, C1233 +A1234, B1234, C1234 +A1235, B1235, C1235 A1236, B1236, C1236 +A1237, B1237, C1237 +A1238, B1238, C1238 A1239, B1239, C1239 A1240, B1240, C1240 A1241, B1241, C1241 +A1242, B1242, C1242 +A1243, B1243, C1243 +A1244, B1244, C1244 +A1245, B1245, C1245 +A1246, B1246, C1246 +A1247, B1247, C1247 A1248, B1248, C1248 +A1249, B1249, C1249 +A1250, B1250, C1250 A1251, B1251, C1251 +A1252, B1252, C1252 +A1253, B1253, C1253 +A1254, B1254, C1254 A1255, B1255, C1255 +A1256, B1256, C1256 +A1257, B1257, C1257 +A1258, B1258, C1258 +A1259, B1259, C1259 +A1260, B1260, C1260 +A1261, B1261, C1261 +A1262, B1262, C1262 +A1263, B1263, C1263 +A1264, B1264, C1264 A1265, B1265, C1265 A1266, B1266, C1266 +A1267, B1267, C1267 +A1268, B1268, C1268 +A1269, B1269, C1269 A1270, B1270, C1270 +A1271, B1271, C1271 A1272, B1272, C1272 A1273, B1273, C1273 A1274, B1274, C1274 +A1275, B1275, C1275 +A1276, B1276, C1276 A1277, B1277, C1277 +A1278, B1278, C1278 A1279, B1279, C1279 A1280, B1280, C1280 +A1281, B1281, C1281 +A1282, B1282, C1282 +A1283, B1283, C1283 A1284, B1284, C1284 +A1285, B1285, C1285 A1286, B1286, C1286 +A1287, B1287, C1287 A1288, B1288, C1288 A1289, B1289, C1289 +A1290, B1290, C1290 +A1291, B1291, C1291 A1292, B1292, C1292 +A1293, B1293, C1293 +A1294, B1294, C1294 +A1295, B1295, C1295 +A1296, B1296, C1296 A1297, B1297, C1297 +A1298, B1298, C1298 +A1299, B1299, C1299 +A1300, B1300, C1300 +A1301, B1301, C1301 +A1302, B1302, C1302 +A1303, B1303, C1303 +A1304, B1304, C1304 +A1305, B1305, C1305 +A1306, B1306, C1306 +A1307, B1307, C1307 +A1308, B1308, C1308 +A1309, B1309, C1309 A1310, B1310, C1310 A1311, B1311, C1311 +A1312, B1312, C1312 +A1313, B1313, C1313 +A1314, B1314, C1314 A1315, B1315, C1315 +A1316, B1316, C1316 A1317, B1317, C1317 +A1318, B1318, C1318 A1319, B1319, C1319 A1320, B1320, C1320 A1321, B1321, C1321 +A1322, B1322, C1322 A1323, B1323, C1323 A1324, B1324, C1324 +A1325, B1325, C1325 A1326, B1326, C1326 +A1327, B1327, C1327 +A1328, B1328, C1328 A1329, B1329, C1329 +A1330, B1330, C1330 A1331, B1331, C1331 A1332, B1332, C1332 A1333, B1333, C1333 A1334, B1334, C1334 +A1335, B1335, C1335 A1336, B1336, C1336 +A1337, B1337, C1337 +A1338, B1338, C1338 +A1339, B1339, C1339 +A1340, B1340, C1340 +A1341, B1341, C1341 +A1342, B1342, C1342 +A1343, B1343, C1343 +A1344, B1344, C1344 +A1345, B1345, C1345 +A1346, B1346, C1346 +A1347, B1347, C1347 +A1348, B1348, C1348 A1349, B1349, C1349 A1350, B1350, C1350 A1351, B1351, C1351 +A1352, B1352, C1352 A1353, B1353, C1353 +A1354, B1354, C1354 A1355, B1355, C1355 A1356, B1356, C1356 A1357, B1357, C1357 +A1358, B1358, C1358 +A1359, B1359, C1359 A1360, B1360, C1360 +A1361, B1361, C1361 +A1362, B1362, C1362 A1363, B1363, C1363 +A1364, B1364, C1364 +A1365, B1365, C1365 +A1366, B1366, C1366 +A1367, B1367, C1367 +A1368, B1368, C1368 +A1369, B1369, C1369 +A1370, B1370, C1370 A1371, B1371, C1371 +A1372, B1372, C1372 +A1373, B1373, C1373 A1374, B1374, C1374 +A1375, B1375, C1375 +A1376, B1376, C1376 +A1377, B1377, C1377 A1378, B1378, C1378 A1379, B1379, C1379 +A1380, B1380, C1380 +A1381, B1381, C1381 +A1382, B1382, C1382 +A1383, B1383, C1383 +A1384, B1384, C1384 A1385, B1385, C1385 +A1386, B1386, C1386 +A1387, B1387, C1387 +A1388, B1388, C1388 +A1389, B1389, C1389 A1390, B1390, C1390 +A1391, B1391, C1391 +A1392, B1392, C1392 +A1393, B1393, C1393 +A1394, B1394, C1394 +A1395, B1395, C1395 +A1396, B1396, C1396 +A1397, B1397, C1397 +A1398, B1398, C1398 +A1399, B1399, C1399 +A1400, B1400, C1400 +A1401, B1401, C1401 A1402, B1402, C1402 +A1403, B1403, C1403 +A1404, B1404, C1404 A1405, B1405, C1405 A1406, B1406, C1406 +A1407, B1407, C1407 +A1408, B1408, C1408 +A1409, B1409, C1409 A1410, B1410, C1410 +A1411, B1411, C1411 +A1412, B1412, C1412 +A1413, B1413, C1413 +A1414, B1414, C1414 +A1415, B1415, C1415 A1416, B1416, C1416 +A1417, B1417, C1417 A1418, B1418, C1418 +A1419, B1419, C1419 +A1420, B1420, C1420 A1421, B1421, C1421 +A1422, B1422, C1422 A1423, B1423, C1423 +A1424, B1424, C1424 +A1425, B1425, C1425 +A1426, B1426, C1426 A1427, B1427, C1427 A1428, B1428, C1428 +A1429, B1429, C1429 +A1430, B1430, C1430 +A1431, B1431, C1431 A1432, B1432, C1432 A1433, B1433, C1433 +A1434, B1434, C1434 +A1435, B1435, C1435 +A1436, B1436, C1436 +A1437, B1437, C1437 +A1438, B1438, C1438 A1439, B1439, C1439 +A1440, B1440, C1440 +A1441, B1441, C1441 A1442, B1442, C1442 +A1443, B1443, C1443 +A1444, B1444, C1444 +A1445, B1445, C1445 A1446, B1446, C1446 +A1447, B1447, C1447 A1448, B1448, C1448 +A1449, B1449, C1449 A1450, B1450, C1450 A1451, B1451, C1451 +A1452, B1452, C1452 A1453, B1453, C1453 +A1454, B1454, C1454 +A1455, B1455, C1455 A1456, B1456, C1456 +A1457, B1457, C1457 +A1458, B1458, C1458 A1459, B1459, C1459 A1460, B1460, C1460 +A1461, B1461, C1461 A1462, B1462, C1462 +A1463, B1463, C1463 A1464, B1464, C1464 +A1465, B1465, C1465 +A1466, B1466, C1466 +A1467, B1467, C1467 +A1468, B1468, C1468 +A1469, B1469, C1469 +A1470, B1470, C1470 A1471, B1471, C1471 +A1472, B1472, C1472 A1473, B1473, C1473 +A1474, B1474, C1474 A1475, B1475, C1475 +A1476, B1476, C1476 +A1477, B1477, C1477 +A1478, B1478, C1478 +A1479, B1479, C1479 +A1480, B1480, C1480 A1481, B1481, C1481 +A1482, B1482, C1482 A1483, B1483, C1483 +A1484, B1484, C1484 +A1485, B1485, C1485 +A1486, B1486, C1486 A1487, B1487, C1487 A1488, B1488, C1488 +A1489, B1489, C1489 +A1490, B1490, C1490 +A1491, B1491, C1491 A1492, B1492, C1492 A1493, B1493, C1493 +A1494, B1494, C1494 +A1495, B1495, C1495 +A1496, B1496, C1496 +A1497, B1497, C1497 A1498, B1498, C1498 A1499, B1499, C1499 +A1500, B1500, C1500 +A1501, B1501, C1501 +A1502, B1502, C1502 A1503, B1503, C1503 A1504, B1504, C1504 A1505, B1505, C1505 A1506, B1506, C1506 +A1507, B1507, C1507 +A1508, B1508, C1508 +A1509, B1509, C1509 A1510, B1510, C1510 +A1511, B1511, C1511 +A1512, B1512, C1512 +A1513, B1513, C1513 A1514, B1514, C1514 A1515, B1515, C1515 +A1516, B1516, C1516 A1517, B1517, C1517 +A1518, B1518, C1518 +A1519, B1519, C1519 +A1520, B1520, C1520 +A1521, B1521, C1521 +A1522, B1522, C1522 A1523, B1523, C1523 +A1524, B1524, C1524 A1525, B1525, C1525 A1526, B1526, C1526 +A1527, B1527, C1527 +A1528, B1528, C1528 +A1529, B1529, C1529 +A1530, B1530, C1530 +A1531, B1531, C1531 +A1532, B1532, C1532 +A1533, B1533, C1533 +A1534, B1534, C1534 +A1535, B1535, C1535 +A1536, B1536, C1536 A1537, B1537, C1537 +A1538, B1538, C1538 +A1539, B1539, C1539 +A1540, B1540, C1540 +A1541, B1541, C1541 +A1542, B1542, C1542 A1543, B1543, C1543 A1544, B1544, C1544 A1545, B1545, C1545 +A1546, B1546, C1546 +A1547, B1547, C1547 +A1548, B1548, C1548 +A1549, B1549, C1549 +A1550, B1550, C1550 +A1551, B1551, C1551 +A1552, B1552, C1552 +A1553, B1553, C1553 +A1554, B1554, C1554 +A1555, B1555, C1555 +A1556, B1556, C1556 +A1557, B1557, C1557 A1558, B1558, C1558 A1559, B1559, C1559 +A1560, B1560, C1560 A1561, B1561, C1561 A1562, B1562, C1562 +A1563, B1563, C1563 A1564, B1564, C1564 +A1565, B1565, C1565 +A1566, B1566, C1566 A1567, B1567, C1567 A1568, B1568, C1568 +A1569, B1569, C1569 +A1570, B1570, C1570 A1571, B1571, C1571 A1572, B1572, C1572 A1573, B1573, C1573 A1574, B1574, C1574 A1575, B1575, C1575 A1576, B1576, C1576 A1577, B1577, C1577 +A1578, B1578, C1578 +A1579, B1579, C1579 A1580, B1580, C1580 A1581, B1581, C1581 A1582, B1582, C1582 A1583, B1583, C1583 +A1584, B1584, C1584 +A1585, B1585, C1585 +A1586, B1586, C1586 +A1587, B1587, C1587 A1588, B1588, C1588 A1589, B1589, C1589 +A1590, B1590, C1590 +A1591, B1591, C1591 A1592, B1592, C1592 +A1593, B1593, C1593 +A1594, B1594, C1594 +A1595, B1595, C1595 +A1596, B1596, C1596 A1597, B1597, C1597 A1598, B1598, C1598 +A1599, B1599, C1599 A1600, B1600, C1600 +A1601, B1601, C1601 A1602, B1602, C1602 +A1603, B1603, C1603 A1604, B1604, C1604 A1605, B1605, C1605 +A1606, B1606, C1606 +A1607, B1607, C1607 +A1608, B1608, C1608 +A1609, B1609, C1609 +A1610, B1610, C1610 A1611, B1611, C1611 +A1612, B1612, C1612 A1613, B1613, C1613 +A1614, B1614, C1614 +A1615, B1615, C1615 A1616, B1616, C1616 +A1617, B1617, C1617 A1618, B1618, C1618 +A1619, B1619, C1619 A1620, B1620, C1620 +A1621, B1621, C1621 +A1622, B1622, C1622 A1623, B1623, C1623 +A1624, B1624, C1624 +A1625, B1625, C1625 A1626, B1626, C1626 A1627, B1627, C1627 +A1628, B1628, C1628 +A1629, B1629, C1629 +A1630, B1630, C1630 A1631, B1631, C1631 +A1632, B1632, C1632 +A1633, B1633, C1633 +A1634, B1634, C1634 +A1635, B1635, C1635 +A1636, B1636, C1636 A1637, B1637, C1637 +A1638, B1638, C1638 +A1639, B1639, C1639 +A1640, B1640, C1640 A1641, B1641, C1641 +A1642, B1642, C1642 +A1643, B1643, C1643 A1644, B1644, C1644 +A1645, B1645, C1645 +A1646, B1646, C1646 +A1647, B1647, C1647 A1648, B1648, C1648 +A1649, B1649, C1649 +A1650, B1650, C1650 A1651, B1651, C1651 +A1652, B1652, C1652 +A1653, B1653, C1653 +A1654, B1654, C1654 A1655, B1655, C1655 +A1656, B1656, C1656 A1657, B1657, C1657 A1658, B1658, C1658 A1659, B1659, C1659 +A1660, B1660, C1660 A1661, B1661, C1661 A1662, B1662, C1662 +A1663, B1663, C1663 +A1664, B1664, C1664 A1665, B1665, C1665 +A1666, B1666, C1666 A1667, B1667, C1667 +A1668, B1668, C1668 +A1669, B1669, C1669 +A1670, B1670, C1670 +A1671, B1671, C1671 +A1672, B1672, C1672 +A1673, B1673, C1673 +A1674, B1674, C1674 +A1675, B1675, C1675 +A1676, B1676, C1676 +A1677, B1677, C1677 +A1678, B1678, C1678 +A1679, B1679, C1679 +A1680, B1680, C1680 +A1681, B1681, C1681 +A1682, B1682, C1682 A1683, B1683, C1683 A1684, B1684, C1684 A1685, B1685, C1685 +A1686, B1686, C1686 +A1687, B1687, C1687 +A1688, B1688, C1688 +A1689, B1689, C1689 +A1690, B1690, C1690 +A1691, B1691, C1691 +A1692, B1692, C1692 +A1693, B1693, C1693 A1694, B1694, C1694 +A1695, B1695, C1695 +A1696, B1696, C1696 A1697, B1697, C1697 +A1698, B1698, C1698 +A1699, B1699, C1699 A1700, B1700, C1700 +A1701, B1701, C1701 +A1702, B1702, C1702 +A1703, B1703, C1703 +A1704, B1704, C1704 +A1705, B1705, C1705 +A1706, B1706, C1706 +A1707, B1707, C1707 +A1708, B1708, C1708 +A1709, B1709, C1709 +A1710, B1710, C1710 +A1711, B1711, C1711 +A1712, B1712, C1712 +A1713, B1713, C1713 +A1714, B1714, C1714 A1715, B1715, C1715 +A1716, B1716, C1716 +A1717, B1717, C1717 +A1718, B1718, C1718 A1719, B1719, C1719 +A1720, B1720, C1720 A1721, B1721, C1721 A1722, B1722, C1722 +A1723, B1723, C1723 +A1724, B1724, C1724 +A1725, B1725, C1725 +A1726, B1726, C1726 +A1727, B1727, C1727 A1728, B1728, C1728 +A1729, B1729, C1729 +A1730, B1730, C1730 A1731, B1731, C1731 A1732, B1732, C1732 +A1733, B1733, C1733 +A1734, B1734, C1734 +A1735, B1735, C1735 A1736, B1736, C1736 +A1737, B1737, C1737 A1738, B1738, C1738 A1739, B1739, C1739 +A1740, B1740, C1740 +A1741, B1741, C1741 A1742, B1742, C1742 +A1743, B1743, C1743 A1744, B1744, C1744 +A1745, B1745, C1745 +A1746, B1746, C1746 A1747, B1747, C1747 +A1748, B1748, C1748 A1749, B1749, C1749 +A1750, B1750, C1750 +A1751, B1751, C1751 +A1752, B1752, C1752 A1753, B1753, C1753 +A1754, B1754, C1754 +A1755, B1755, C1755 +A1756, B1756, C1756 +A1757, B1757, C1757 A1758, B1758, C1758 +A1759, B1759, C1759 A1760, B1760, C1760 A1761, B1761, C1761 +A1762, B1762, C1762 +A1763, B1763, C1763 +A1764, B1764, C1764 +A1765, B1765, C1765 +A1766, B1766, C1766 A1767, B1767, C1767 A1768, B1768, C1768 +A1769, B1769, C1769 +A1770, B1770, C1770 +A1771, B1771, C1771 +A1772, B1772, C1772 +A1773, B1773, C1773 A1774, B1774, C1774 A1775, B1775, C1775 A1776, B1776, C1776 A1777, B1777, C1777 +A1778, B1778, C1778 +A1779, B1779, C1779 +A1780, B1780, C1780 +A1781, B1781, C1781 +A1782, B1782, C1782 +A1783, B1783, C1783 A1784, B1784, C1784 +A1785, B1785, C1785 +A1786, B1786, C1786 +A1787, B1787, C1787 A1788, B1788, C1788 +A1789, B1789, C1789 A1790, B1790, C1790 +A1791, B1791, C1791 +A1792, B1792, C1792 A1793, B1793, C1793 +A1794, B1794, C1794 +A1795, B1795, C1795 +A1796, B1796, C1796 +A1797, B1797, C1797 A1798, B1798, C1798 +A1799, B1799, C1799 +A1800, B1800, C1800 A1801, B1801, C1801 A1802, B1802, C1802 A1803, B1803, C1803 A1804, B1804, C1804 A1805, B1805, C1805 A1806, B1806, C1806 A1807, B1807, C1807 +A1808, B1808, C1808 A1809, B1809, C1809 +A1810, B1810, C1810 +A1811, B1811, C1811 A1812, B1812, C1812 +A1813, B1813, C1813 +A1814, B1814, C1814 +A1815, B1815, C1815 A1816, B1816, C1816 +A1817, B1817, C1817 +A1818, B1818, C1818 +A1819, B1819, C1819 +A1820, B1820, C1820 +A1821, B1821, C1821 A1822, B1822, C1822 +A1823, B1823, C1823 +A1824, B1824, C1824 A1825, B1825, C1825 A1826, B1826, C1826 +A1827, B1827, C1827 +A1828, B1828, C1828 A1829, B1829, C1829 A1830, B1830, C1830 A1831, B1831, C1831 +A1832, B1832, C1832 +A1833, B1833, C1833 +A1834, B1834, C1834 +A1835, B1835, C1835 A1836, B1836, C1836 A1837, B1837, C1837 +A1838, B1838, C1838 +A1839, B1839, C1839 A1840, B1840, C1840 A1841, B1841, C1841 +A1842, B1842, C1842 A1843, B1843, C1843 +A1844, B1844, C1844 A1845, B1845, C1845 +A1846, B1846, C1846 +A1847, B1847, C1847 A1848, B1848, C1848 A1849, B1849, C1849 +A1850, B1850, C1850 +A1851, B1851, C1851 +A1852, B1852, C1852 +A1853, B1853, C1853 +A1854, B1854, C1854 A1855, B1855, C1855 +A1856, B1856, C1856 +A1857, B1857, C1857 +A1858, B1858, C1858 +A1859, B1859, C1859 +A1860, B1860, C1860 +A1861, B1861, C1861 +A1862, B1862, C1862 +A1863, B1863, C1863 A1864, B1864, C1864 +A1865, B1865, C1865 +A1866, B1866, C1866 +A1867, B1867, C1867 +A1868, B1868, C1868 +A1869, B1869, C1869 +A1870, B1870, C1870 +A1871, B1871, C1871 A1872, B1872, C1872 +A1873, B1873, C1873 +A1874, B1874, C1874 +A1875, B1875, C1875 +A1876, B1876, C1876 A1877, B1877, C1877 A1878, B1878, C1878 +A1879, B1879, C1879 +A1880, B1880, C1880 +A1881, B1881, C1881 A1882, B1882, C1882 +A1883, B1883, C1883 +A1884, B1884, C1884 +A1885, B1885, C1885 +A1886, B1886, C1886 +A1887, B1887, C1887 +A1888, B1888, C1888 A1889, B1889, C1889 +A1890, B1890, C1890 A1891, B1891, C1891 A1892, B1892, C1892 +A1893, B1893, C1893 +A1894, B1894, C1894 +A1895, B1895, C1895 +A1896, B1896, C1896 +A1897, B1897, C1897 +A1898, B1898, C1898 A1899, B1899, C1899 +A1900, B1900, C1900 +A1901, B1901, C1901 A1902, B1902, C1902 A1903, B1903, C1903 +A1904, B1904, C1904 +A1905, B1905, C1905 +A1906, B1906, C1906 +A1907, B1907, C1907 A1908, B1908, C1908 +A1909, B1909, C1909 +A1910, B1910, C1910 +A1911, B1911, C1911 +A1912, B1912, C1912 +A1913, B1913, C1913 +A1914, B1914, C1914 +A1915, B1915, C1915 A1916, B1916, C1916 A1917, B1917, C1917 A1918, B1918, C1918 +A1919, B1919, C1919 +A1920, B1920, C1920 +A1921, B1921, C1921 A1922, B1922, C1922 +A1923, B1923, C1923 A1924, B1924, C1924 +A1925, B1925, C1925 +A1926, B1926, C1926 A1927, B1927, C1927 A1928, B1928, C1928 +A1929, B1929, C1929 A1930, B1930, C1930 +A1931, B1931, C1931 +A1932, B1932, C1932 +A1933, B1933, C1933 A1934, B1934, C1934 +A1935, B1935, C1935 +A1936, B1936, C1936 +A1937, B1937, C1937 +A1938, B1938, C1938 A1939, B1939, C1939 +A1940, B1940, C1940 +A1941, B1941, C1941 A1942, B1942, C1942 +A1943, B1943, C1943 A1944, B1944, C1944 +A1945, B1945, C1945 +A1946, B1946, C1946 +A1947, B1947, C1947 +A1948, B1948, C1948 +A1949, B1949, C1949 +A1950, B1950, C1950 +A1951, B1951, C1951 +A1952, B1952, C1952 +A1953, B1953, C1953 +A1954, B1954, C1954 A1955, B1955, C1955 +A1956, B1956, C1956 +A1957, B1957, C1957 A1958, B1958, C1958 +A1959, B1959, C1959 +A1960, B1960, C1960 +A1961, B1961, C1961 A1962, B1962, C1962 A1963, B1963, C1963 +A1964, B1964, C1964 +A1965, B1965, C1965 +A1966, B1966, C1966 +A1967, B1967, C1967 A1968, B1968, C1968 +A1969, B1969, C1969 +A1970, B1970, C1970 A1971, B1971, C1971 +A1972, B1972, C1972 +A1973, B1973, C1973 A1974, B1974, C1974 +A1975, B1975, C1975 +A1976, B1976, C1976 +A1977, B1977, C1977 +A1978, B1978, C1978 +A1979, B1979, C1979 +A1980, B1980, C1980 +A1981, B1981, C1981 A1982, B1982, C1982 A1983, B1983, C1983 A1984, B1984, C1984 +A1985, B1985, C1985 +A1986, B1986, C1986 A1987, B1987, C1987 A1988, B1988, C1988 +A1989, B1989, C1989 +A1990, B1990, C1990 +A1991, B1991, C1991 A1992, B1992, C1992 A1993, B1993, C1993 +A1994, B1994, C1994 +A1995, B1995, C1995 A1996, B1996, C1996 +A1997, B1997, C1997 +A1998, B1998, C1998 +A1999, B1999, C1999 A2000, B2000, C2000 A2001, B2001, C2001 +A2002, B2002, C2002 +A2003, B2003, C2003 A2004, B2004, C2004 A2005, B2005, C2005 +A2006, B2006, C2006 +A2007, B2007, C2007 +A2008, B2008, C2008 A2009, B2009, C2009 A2010, B2010, C2010 +A2011, B2011, C2011 A2012, B2012, C2012 +A2013, B2013, C2013 A2014, B2014, C2014 +A2015, B2015, C2015 A2016, B2016, C2016 +A2017, B2017, C2017 +A2018, B2018, C2018 +A2019, B2019, C2019 A2020, B2020, C2020 +A2021, B2021, C2021 +A2022, B2022, C2022 +A2023, B2023, C2023 +A2024, B2024, C2024 +A2025, B2025, C2025 +A2026, B2026, C2026 A2027, B2027, C2027 A2028, B2028, C2028 +A2029, B2029, C2029 A2030, B2030, C2030 A2031, B2031, C2031 A2032, B2032, C2032 A2033, B2033, C2033 +A2034, B2034, C2034 A2035, B2035, C2035 +A2036, B2036, C2036 +A2037, B2037, C2037 A2038, B2038, C2038 +A2039, B2039, C2039 A2040, B2040, C2040 A2041, B2041, C2041 +A2042, B2042, C2042 A2043, B2043, C2043 +A2044, B2044, C2044 +A2045, B2045, C2045 +A2046, B2046, C2046 +A2047, B2047, C2047 +A2048, B2048, C2048 +A2049, B2049, C2049 A2050, B2050, C2050 A2051, B2051, C2051 A2052, B2052, C2052 A2053, B2053, C2053 +A2054, B2054, C2054 +A2055, B2055, C2055 +A2056, B2056, C2056 +A2057, B2057, C2057 +A2058, B2058, C2058 +A2059, B2059, C2059 +A2060, B2060, C2060 A2061, B2061, C2061 +A2062, B2062, C2062 A2063, B2063, C2063 +A2064, B2064, C2064 A2065, B2065, C2065 A2066, B2066, C2066 A2067, B2067, C2067 +A2068, B2068, C2068 +A2069, B2069, C2069 +A2070, B2070, C2070 A2071, B2071, C2071 +A2072, B2072, C2072 A2073, B2073, C2073 +A2074, B2074, C2074 +A2075, B2075, C2075 +A2076, B2076, C2076 +A2077, B2077, C2077 +A2078, B2078, C2078 +A2079, B2079, C2079 A2080, B2080, C2080 +A2081, B2081, C2081 A2082, B2082, C2082 +A2083, B2083, C2083 A2084, B2084, C2084 A2085, B2085, C2085 A2086, B2086, C2086 +A2087, B2087, C2087 +A2088, B2088, C2088 +A2089, B2089, C2089 A2090, B2090, C2090 +A2091, B2091, C2091 +A2092, B2092, C2092 +A2093, B2093, C2093 +A2094, B2094, C2094 +A2095, B2095, C2095 +A2096, B2096, C2096 +A2097, B2097, C2097 A2098, B2098, C2098 A2099, B2099, C2099 +A2100, B2100, C2100 +A2101, B2101, C2101 +A2102, B2102, C2102 +A2103, B2103, C2103 A2104, B2104, C2104 A2105, B2105, C2105 +A2106, B2106, C2106 A2107, B2107, C2107 +A2108, B2108, C2108 +A2109, B2109, C2109 +A2110, B2110, C2110 A2111, B2111, C2111 +A2112, B2112, C2112 +A2113, B2113, C2113 +A2114, B2114, C2114 +A2115, B2115, C2115 A2116, B2116, C2116 A2117, B2117, C2117 +A2118, B2118, C2118 +A2119, B2119, C2119 A2120, B2120, C2120 +A2121, B2121, C2121 +A2122, B2122, C2122 A2123, B2123, C2123 A2124, B2124, C2124 A2125, B2125, C2125 +A2126, B2126, C2126 A2127, B2127, C2127 A2128, B2128, C2128 A2129, B2129, C2129 A2130, B2130, C2130 +A2131, B2131, C2131 A2132, B2132, C2132 +A2133, B2133, C2133 +A2134, B2134, C2134 +A2135, B2135, C2135 +A2136, B2136, C2136 +A2137, B2137, C2137 A2138, B2138, C2138 +A2139, B2139, C2139 +A2140, B2140, C2140 +A2141, B2141, C2141 +A2142, B2142, C2142 A2143, B2143, C2143 +A2144, B2144, C2144 A2145, B2145, C2145 +A2146, B2146, C2146 +A2147, B2147, C2147 +A2148, B2148, C2148 +A2149, B2149, C2149 +A2150, B2150, C2150 A2151, B2151, C2151 +A2152, B2152, C2152 +A2153, B2153, C2153 +A2154, B2154, C2154 +A2155, B2155, C2155 +A2156, B2156, C2156 +A2157, B2157, C2157 +A2158, B2158, C2158 +A2159, B2159, C2159 +A2160, B2160, C2160 A2161, B2161, C2161 A2162, B2162, C2162 +A2163, B2163, C2163 +A2164, B2164, C2164 +A2165, B2165, C2165 +A2166, B2166, C2166 +A2167, B2167, C2167 +A2168, B2168, C2168 +A2169, B2169, C2169 A2170, B2170, C2170 +A2171, B2171, C2171 +A2172, B2172, C2172 +A2173, B2173, C2173 +A2174, B2174, C2174 A2175, B2175, C2175 A2176, B2176, C2176 A2177, B2177, C2177 +A2178, B2178, C2178 +A2179, B2179, C2179 A2180, B2180, C2180 +A2181, B2181, C2181 +A2182, B2182, C2182 A2183, B2183, C2183 +A2184, B2184, C2184 +A2185, B2185, C2185 +A2186, B2186, C2186 +A2187, B2187, C2187 +A2188, B2188, C2188 +A2189, B2189, C2189 +A2190, B2190, C2190 +A2191, B2191, C2191 A2192, B2192, C2192 +A2193, B2193, C2193 +A2194, B2194, C2194 +A2195, B2195, C2195 +A2196, B2196, C2196 +A2197, B2197, C2197 +A2198, B2198, C2198 +A2199, B2199, C2199 A2200, B2200, C2200 +A2201, B2201, C2201 +A2202, B2202, C2202 +A2203, B2203, C2203 +A2204, B2204, C2204 +A2205, B2205, C2205 A2206, B2206, C2206 +A2207, B2207, C2207 A2208, B2208, C2208 A2209, B2209, C2209 +A2210, B2210, C2210 +A2211, B2211, C2211 +A2212, B2212, C2212 +A2213, B2213, C2213 +A2214, B2214, C2214 A2215, B2215, C2215 +A2216, B2216, C2216 +A2217, B2217, C2217 +A2218, B2218, C2218 A2219, B2219, C2219 +A2220, B2220, C2220 +A2221, B2221, C2221 +A2222, B2222, C2222 A2223, B2223, C2223 +A2224, B2224, C2224 A2225, B2225, C2225 +A2226, B2226, C2226 +A2227, B2227, C2227 A2228, B2228, C2228 +A2229, B2229, C2229 +A2230, B2230, C2230 +A2231, B2231, C2231 +A2232, B2232, C2232 A2233, B2233, C2233 +A2234, B2234, C2234 A2235, B2235, C2235 +A2236, B2236, C2236 +A2237, B2237, C2237 A2238, B2238, C2238 +A2239, B2239, C2239 +A2240, B2240, C2240 +A2241, B2241, C2241 +A2242, B2242, C2242 A2243, B2243, C2243 +A2244, B2244, C2244 +A2245, B2245, C2245 +A2246, B2246, C2246 +A2247, B2247, C2247 +A2248, B2248, C2248 A2249, B2249, C2249 +A2250, B2250, C2250 +A2251, B2251, C2251 +A2252, B2252, C2252 +A2253, B2253, C2253 +A2254, B2254, C2254 +A2255, B2255, C2255 +A2256, B2256, C2256 +A2257, B2257, C2257 +A2258, B2258, C2258 +A2259, B2259, C2259 +A2260, B2260, C2260 +A2261, B2261, C2261 A2262, B2262, C2262 A2263, B2263, C2263 +A2264, B2264, C2264 +A2265, B2265, C2265 A2266, B2266, C2266 A2267, B2267, C2267 +A2268, B2268, C2268 +A2269, B2269, C2269 +A2270, B2270, C2270 A2271, B2271, C2271 A2272, B2272, C2272 +A2273, B2273, C2273 +A2274, B2274, C2274 A2275, B2275, C2275 +A2276, B2276, C2276 +A2277, B2277, C2277 +A2278, B2278, C2278 +A2279, B2279, C2279 A2280, B2280, C2280 +A2281, B2281, C2281 +A2282, B2282, C2282 +A2283, B2283, C2283 +A2284, B2284, C2284 +A2285, B2285, C2285 +A2286, B2286, C2286 +A2287, B2287, C2287 +A2288, B2288, C2288 A2289, B2289, C2289 A2290, B2290, C2290 A2291, B2291, C2291 +A2292, B2292, C2292 +A2293, B2293, C2293 +A2294, B2294, C2294 +A2295, B2295, C2295 +A2296, B2296, C2296 A2297, B2297, C2297 +A2298, B2298, C2298 +A2299, B2299, C2299 +A2300, B2300, C2300 +A2301, B2301, C2301 +A2302, B2302, C2302 +A2303, B2303, C2303 +A2304, B2304, C2304 A2305, B2305, C2305 +A2306, B2306, C2306 +A2307, B2307, C2307 A2308, B2308, C2308 +A2309, B2309, C2309 +A2310, B2310, C2310 A2311, B2311, C2311 +A2312, B2312, C2312 A2313, B2313, C2313 A2314, B2314, C2314 +A2315, B2315, C2315 +A2316, B2316, C2316 +A2317, B2317, C2317 +A2318, B2318, C2318 +A2319, B2319, C2319 +A2320, B2320, C2320 +A2321, B2321, C2321 +A2322, B2322, C2322 A2323, B2323, C2323 +A2324, B2324, C2324 +A2325, B2325, C2325 +A2326, B2326, C2326 A2327, B2327, C2327 A2328, B2328, C2328 A2329, B2329, C2329 A2330, B2330, C2330 +A2331, B2331, C2331 +A2332, B2332, C2332 A2333, B2333, C2333 +A2334, B2334, C2334 +A2335, B2335, C2335 +A2336, B2336, C2336 A2337, B2337, C2337 +A2338, B2338, C2338 +A2339, B2339, C2339 A2340, B2340, C2340 A2341, B2341, C2341 A2342, B2342, C2342 +A2343, B2343, C2343 A2344, B2344, C2344 +A2345, B2345, C2345 +A2346, B2346, C2346 +A2347, B2347, C2347 A2348, B2348, C2348 +A2349, B2349, C2349 A2350, B2350, C2350 +A2351, B2351, C2351 +A2352, B2352, C2352 +A2353, B2353, C2353 A2354, B2354, C2354 A2355, B2355, C2355 +A2356, B2356, C2356 +A2357, B2357, C2357 A2358, B2358, C2358 +A2359, B2359, C2359 +A2360, B2360, C2360 +A2361, B2361, C2361 +A2362, B2362, C2362 +A2363, B2363, C2363 A2364, B2364, C2364 +A2365, B2365, C2365 +A2366, B2366, C2366 A2367, B2367, C2367 A2368, B2368, C2368 +A2369, B2369, C2369 A2370, B2370, C2370 A2371, B2371, C2371 A2372, B2372, C2372 +A2373, B2373, C2373 A2374, B2374, C2374 A2375, B2375, C2375 A2376, B2376, C2376 A2377, B2377, C2377 A2378, B2378, C2378 A2379, B2379, C2379 A2380, B2380, C2380 +A2381, B2381, C2381 A2382, B2382, C2382 A2383, B2383, C2383 +A2384, B2384, C2384 +A2385, B2385, C2385 +A2386, B2386, C2386 +A2387, B2387, C2387 +A2388, B2388, C2388 A2389, B2389, C2389 +A2390, B2390, C2390 +A2391, B2391, C2391 +A2392, B2392, C2392 +A2393, B2393, C2393 A2394, B2394, C2394 +A2395, B2395, C2395 +A2396, B2396, C2396 +A2397, B2397, C2397 +A2398, B2398, C2398 +A2399, B2399, C2399 +A2400, B2400, C2400 +A2401, B2401, C2401 +A2402, B2402, C2402 +A2403, B2403, C2403 A2404, B2404, C2404 +A2405, B2405, C2405 +A2406, B2406, C2406 A2407, B2407, C2407 A2408, B2408, C2408 +A2409, B2409, C2409 +A2410, B2410, C2410 +A2411, B2411, C2411 +A2412, B2412, C2412 A2413, B2413, C2413 +A2414, B2414, C2414 +A2415, B2415, C2415 A2416, B2416, C2416 A2417, B2417, C2417 +A2418, B2418, C2418 A2419, B2419, C2419 A2420, B2420, C2420 +A2421, B2421, C2421 A2422, B2422, C2422 A2423, B2423, C2423 +A2424, B2424, C2424 +A2425, B2425, C2425 +A2426, B2426, C2426 +A2427, B2427, C2427 +A2428, B2428, C2428 +A2429, B2429, C2429 +A2430, B2430, C2430 +A2431, B2431, C2431 A2432, B2432, C2432 A2433, B2433, C2433 +A2434, B2434, C2434 +A2435, B2435, C2435 +A2436, B2436, C2436 A2437, B2437, C2437 +A2438, B2438, C2438 +A2439, B2439, C2439 +A2440, B2440, C2440 A2441, B2441, C2441 +A2442, B2442, C2442 A2443, B2443, C2443 +A2444, B2444, C2444 A2445, B2445, C2445 A2446, B2446, C2446 +A2447, B2447, C2447 +A2448, B2448, C2448 +A2449, B2449, C2449 +A2450, B2450, C2450 +A2451, B2451, C2451 A2452, B2452, C2452 +A2453, B2453, C2453 A2454, B2454, C2454 +A2455, B2455, C2455 +A2456, B2456, C2456 +A2457, B2457, C2457 +A2458, B2458, C2458 +A2459, B2459, C2459 +A2460, B2460, C2460 +A2461, B2461, C2461 A2462, B2462, C2462 +A2463, B2463, C2463 A2464, B2464, C2464 +A2465, B2465, C2465 +A2466, B2466, C2466 +A2467, B2467, C2467 +A2468, B2468, C2468 +A2469, B2469, C2469 +A2470, B2470, C2470 A2471, B2471, C2471 +A2472, B2472, C2472 +A2473, B2473, C2473 +A2474, B2474, C2474 +A2475, B2475, C2475 A2476, B2476, C2476 +A2477, B2477, C2477 +A2478, B2478, C2478 A2479, B2479, C2479 +A2480, B2480, C2480 +A2481, B2481, C2481 +A2482, B2482, C2482 A2483, B2483, C2483 +A2484, B2484, C2484 +A2485, B2485, C2485 +A2486, B2486, C2486 +A2487, B2487, C2487 +A2488, B2488, C2488 +A2489, B2489, C2489 A2490, B2490, C2490 +A2491, B2491, C2491 +A2492, B2492, C2492 +A2493, B2493, C2493 +A2494, B2494, C2494 +A2495, B2495, C2495 +A2496, B2496, C2496 A2497, B2497, C2497 +A2498, B2498, C2498 +A2499, B2499, C2499 A2500, B2500, C2500 +A2501, B2501, C2501 +A2502, B2502, C2502 +A2503, B2503, C2503 A2504, B2504, C2504 A2505, B2505, C2505 +A2506, B2506, C2506 +A2507, B2507, C2507 +A2508, B2508, C2508 +A2509, B2509, C2509 +A2510, B2510, C2510 A2511, B2511, C2511 +A2512, B2512, C2512 +A2513, B2513, C2513 A2514, B2514, C2514 +A2515, B2515, C2515 +A2516, B2516, C2516 A2517, B2517, C2517 +A2518, B2518, C2518 +A2519, B2519, C2519 +A2520, B2520, C2520 A2521, B2521, C2521 +A2522, B2522, C2522 +A2523, B2523, C2523 +A2524, B2524, C2524 +A2525, B2525, C2525 +A2526, B2526, C2526 +A2527, B2527, C2527 +A2528, B2528, C2528 A2529, B2529, C2529 +A2530, B2530, C2530 +A2531, B2531, C2531 A2532, B2532, C2532 +A2533, B2533, C2533 +A2534, B2534, C2534 A2535, B2535, C2535 A2536, B2536, C2536 +A2537, B2537, C2537 +A2538, B2538, C2538 +A2539, B2539, C2539 A2540, B2540, C2540 +A2541, B2541, C2541 A2542, B2542, C2542 A2543, B2543, C2543 +A2544, B2544, C2544 A2545, B2545, C2545 A2546, B2546, C2546 A2547, B2547, C2547 A2548, B2548, C2548 A2549, B2549, C2549 +A2550, B2550, C2550 +A2551, B2551, C2551 A2552, B2552, C2552 +A2553, B2553, C2553 +A2554, B2554, C2554 +A2555, B2555, C2555 A2556, B2556, C2556 A2557, B2557, C2557 A2558, B2558, C2558 A2559, B2559, C2559 A2560, B2560, C2560 +A2561, B2561, C2561 +A2562, B2562, C2562 +A2563, B2563, C2563 A2564, B2564, C2564 A2565, B2565, C2565 +A2566, B2566, C2566 A2567, B2567, C2567 +A2568, B2568, C2568 +A2569, B2569, C2569 A2570, B2570, C2570 A2571, B2571, C2571 A2572, B2572, C2572 +A2573, B2573, C2573 +A2574, B2574, C2574 A2575, B2575, C2575 A2576, B2576, C2576 +A2577, B2577, C2577 A2578, B2578, C2578 +A2579, B2579, C2579 A2580, B2580, C2580 A2581, B2581, C2581 +A2582, B2582, C2582 A2583, B2583, C2583 +A2584, B2584, C2584 A2585, B2585, C2585 +A2586, B2586, C2586 A2587, B2587, C2587 A2588, B2588, C2588 +A2589, B2589, C2589 +A2590, B2590, C2590 A2591, B2591, C2591 +A2592, B2592, C2592 +A2593, B2593, C2593 +A2594, B2594, C2594 +A2595, B2595, C2595 +A2596, B2596, C2596 +A2597, B2597, C2597 A2598, B2598, C2598 +A2599, B2599, C2599 +A2600, B2600, C2600 A2601, B2601, C2601 +A2602, B2602, C2602 A2603, B2603, C2603 +A2604, B2604, C2604 +A2605, B2605, C2605 +A2606, B2606, C2606 +A2607, B2607, C2607 A2608, B2608, C2608 A2609, B2609, C2609 A2610, B2610, C2610 +A2611, B2611, C2611 A2612, B2612, C2612 A2613, B2613, C2613 A2614, B2614, C2614 +A2615, B2615, C2615 +A2616, B2616, C2616 A2617, B2617, C2617 A2618, B2618, C2618 A2619, B2619, C2619 +A2620, B2620, C2620 A2621, B2621, C2621 +A2622, B2622, C2622 A2623, B2623, C2623 +A2624, B2624, C2624 +A2625, B2625, C2625 +A2626, B2626, C2626 +A2627, B2627, C2627 +A2628, B2628, C2628 +A2629, B2629, C2629 +A2630, B2630, C2630 A2631, B2631, C2631 +A2632, B2632, C2632 +A2633, B2633, C2633 A2634, B2634, C2634 +A2635, B2635, C2635 +A2636, B2636, C2636 A2637, B2637, C2637 A2638, B2638, C2638 A2639, B2639, C2639 +A2640, B2640, C2640 +A2641, B2641, C2641 A2642, B2642, C2642 A2643, B2643, C2643 A2644, B2644, C2644 +A2645, B2645, C2645 A2646, B2646, C2646 +A2647, B2647, C2647 +A2648, B2648, C2648 A2649, B2649, C2649 A2650, B2650, C2650 A2651, B2651, C2651 A2652, B2652, C2652 +A2653, B2653, C2653 +A2654, B2654, C2654 +A2655, B2655, C2655 +A2656, B2656, C2656 +A2657, B2657, C2657 +A2658, B2658, C2658 A2659, B2659, C2659 +A2660, B2660, C2660 +A2661, B2661, C2661 A2662, B2662, C2662 A2663, B2663, C2663 +A2664, B2664, C2664 A2665, B2665, C2665 +A2666, B2666, C2666 +A2667, B2667, C2667 A2668, B2668, C2668 +A2669, B2669, C2669 +A2670, B2670, C2670 +A2671, B2671, C2671 +A2672, B2672, C2672 +A2673, B2673, C2673 A2674, B2674, C2674 +A2675, B2675, C2675 A2676, B2676, C2676 +A2677, B2677, C2677 A2678, B2678, C2678 +A2679, B2679, C2679 A2680, B2680, C2680 +A2681, B2681, C2681 +A2682, B2682, C2682 +A2683, B2683, C2683 A2684, B2684, C2684 +A2685, B2685, C2685 A2686, B2686, C2686 +A2687, B2687, C2687 +A2688, B2688, C2688 +A2689, B2689, C2689 +A2690, B2690, C2690 +A2691, B2691, C2691 A2692, B2692, C2692 +A2693, B2693, C2693 A2694, B2694, C2694 A2695, B2695, C2695 A2696, B2696, C2696 +A2697, B2697, C2697 A2698, B2698, C2698 A2699, B2699, C2699 A2700, B2700, C2700 +A2701, B2701, C2701 A2702, B2702, C2702 +A2703, B2703, C2703 A2704, B2704, C2704 A2705, B2705, C2705 A2706, B2706, C2706 +A2707, B2707, C2707 +A2708, B2708, C2708 +A2709, B2709, C2709 +A2710, B2710, C2710 A2711, B2711, C2711 A2712, B2712, C2712 A2713, B2713, C2713 A2714, B2714, C2714 +A2715, B2715, C2715 +A2716, B2716, C2716 +A2717, B2717, C2717 +A2718, B2718, C2718 A2719, B2719, C2719 +A2720, B2720, C2720 +A2721, B2721, C2721 A2722, B2722, C2722 A2723, B2723, C2723 +A2724, B2724, C2724 +A2725, B2725, C2725 A2726, B2726, C2726 +A2727, B2727, C2727 +A2728, B2728, C2728 +A2729, B2729, C2729 A2730, B2730, C2730 +A2731, B2731, C2731 A2732, B2732, C2732 A2733, B2733, C2733 A2734, B2734, C2734 +A2735, B2735, C2735 +A2736, B2736, C2736 +A2737, B2737, C2737 +A2738, B2738, C2738 +A2739, B2739, C2739 +A2740, B2740, C2740 A2741, B2741, C2741 A2742, B2742, C2742 +A2743, B2743, C2743 A2744, B2744, C2744 A2745, B2745, C2745 +A2746, B2746, C2746 A2747, B2747, C2747 +A2748, B2748, C2748 A2749, B2749, C2749 +A2750, B2750, C2750 A2751, B2751, C2751 A2752, B2752, C2752 +A2753, B2753, C2753 +A2754, B2754, C2754 A2755, B2755, C2755 A2756, B2756, C2756 +A2757, B2757, C2757 +A2758, B2758, C2758 +A2759, B2759, C2759 +A2760, B2760, C2760 +A2761, B2761, C2761 +A2762, B2762, C2762 A2763, B2763, C2763 +A2764, B2764, C2764 A2765, B2765, C2765 A2766, B2766, C2766 +A2767, B2767, C2767 +A2768, B2768, C2768 A2769, B2769, C2769 +A2770, B2770, C2770 A2771, B2771, C2771 +A2772, B2772, C2772 A2773, B2773, C2773 +A2774, B2774, C2774 +A2775, B2775, C2775 +A2776, B2776, C2776 +A2777, B2777, C2777 A2778, B2778, C2778 +A2779, B2779, C2779 +A2780, B2780, C2780 +A2781, B2781, C2781 +A2782, B2782, C2782 +A2783, B2783, C2783 A2784, B2784, C2784 +A2785, B2785, C2785 +A2786, B2786, C2786 A2787, B2787, C2787 A2788, B2788, C2788 A2789, B2789, C2789 A2790, B2790, C2790 +A2791, B2791, C2791 +A2792, B2792, C2792 A2793, B2793, C2793 +A2794, B2794, C2794 +A2795, B2795, C2795 A2796, B2796, C2796 +A2797, B2797, C2797 +A2798, B2798, C2798 A2799, B2799, C2799 A2800, B2800, C2800 +A2801, B2801, C2801 +A2802, B2802, C2802 +A2803, B2803, C2803 A2804, B2804, C2804 A2805, B2805, C2805 +A2806, B2806, C2806 +A2807, B2807, C2807 +A2808, B2808, C2808 A2809, B2809, C2809 A2810, B2810, C2810 +A2811, B2811, C2811 A2812, B2812, C2812 +A2813, B2813, C2813 +A2814, B2814, C2814 +A2815, B2815, C2815 +A2816, B2816, C2816 A2817, B2817, C2817 +A2818, B2818, C2818 A2819, B2819, C2819 +A2820, B2820, C2820 A2821, B2821, C2821 A2822, B2822, C2822 +A2823, B2823, C2823 A2824, B2824, C2824 +A2825, B2825, C2825 A2826, B2826, C2826 +A2827, B2827, C2827 +A2828, B2828, C2828 +A2829, B2829, C2829 +A2830, B2830, C2830 +A2831, B2831, C2831 +A2832, B2832, C2832 +A2833, B2833, C2833 +A2834, B2834, C2834 +A2835, B2835, C2835 +A2836, B2836, C2836 +A2837, B2837, C2837 +A2838, B2838, C2838 +A2839, B2839, C2839 +A2840, B2840, C2840 +A2841, B2841, C2841 A2842, B2842, C2842 +A2843, B2843, C2843 +A2844, B2844, C2844 A2845, B2845, C2845 +A2846, B2846, C2846 +A2847, B2847, C2847 +A2848, B2848, C2848 +A2849, B2849, C2849 +A2850, B2850, C2850 +A2851, B2851, C2851 +A2852, B2852, C2852 +A2853, B2853, C2853 A2854, B2854, C2854 +A2855, B2855, C2855 +A2856, B2856, C2856 +A2857, B2857, C2857 +A2858, B2858, C2858 A2859, B2859, C2859 A2860, B2860, C2860 +A2861, B2861, C2861 +A2862, B2862, C2862 +A2863, B2863, C2863 +A2864, B2864, C2864 A2865, B2865, C2865 +A2866, B2866, C2866 +A2867, B2867, C2867 A2868, B2868, C2868 A2869, B2869, C2869 A2870, B2870, C2870 +A2871, B2871, C2871 A2872, B2872, C2872 +A2873, B2873, C2873 +A2874, B2874, C2874 +A2875, B2875, C2875 A2876, B2876, C2876 +A2877, B2877, C2877 A2878, B2878, C2878 +A2879, B2879, C2879 A2880, B2880, C2880 +A2881, B2881, C2881 A2882, B2882, C2882 A2883, B2883, C2883 A2884, B2884, C2884 +A2885, B2885, C2885 +A2886, B2886, C2886 +A2887, B2887, C2887 +A2888, B2888, C2888 +A2889, B2889, C2889 +A2890, B2890, C2890 +A2891, B2891, C2891 +A2892, B2892, C2892 +A2893, B2893, C2893 +A2894, B2894, C2894 +A2895, B2895, C2895 A2896, B2896, C2896 +A2897, B2897, C2897 +A2898, B2898, C2898 +A2899, B2899, C2899 +A2900, B2900, C2900 +A2901, B2901, C2901 A2902, B2902, C2902 +A2903, B2903, C2903 +A2904, B2904, C2904 A2905, B2905, C2905 +A2906, B2906, C2906 A2907, B2907, C2907 +A2908, B2908, C2908 A2909, B2909, C2909 +A2910, B2910, C2910 +A2911, B2911, C2911 A2912, B2912, C2912 +A2913, B2913, C2913 +A2914, B2914, C2914 A2915, B2915, C2915 +A2916, B2916, C2916 A2917, B2917, C2917 A2918, B2918, C2918 +A2919, B2919, C2919 +A2920, B2920, C2920 +A2921, B2921, C2921 A2922, B2922, C2922 A2923, B2923, C2923 +A2924, B2924, C2924 A2925, B2925, C2925 A2926, B2926, C2926 +A2927, B2927, C2927 A2928, B2928, C2928 A2929, B2929, C2929 A2930, B2930, C2930 A2931, B2931, C2931 +A2932, B2932, C2932 +A2933, B2933, C2933 +A2934, B2934, C2934 +A2935, B2935, C2935 +A2936, B2936, C2936 A2937, B2937, C2937 +A2938, B2938, C2938 +A2939, B2939, C2939 +A2940, B2940, C2940 +A2941, B2941, C2941 A2942, B2942, C2942 +A2943, B2943, C2943 +A2944, B2944, C2944 A2945, B2945, C2945 A2946, B2946, C2946 +A2947, B2947, C2947 +A2948, B2948, C2948 +A2949, B2949, C2949 +A2950, B2950, C2950 +A2951, B2951, C2951 +A2952, B2952, C2952 +A2953, B2953, C2953 +A2954, B2954, C2954 +A2955, B2955, C2955 A2956, B2956, C2956 +A2957, B2957, C2957 +A2958, B2958, C2958 +A2959, B2959, C2959 A2960, B2960, C2960 A2961, B2961, C2961 +A2962, B2962, C2962 +A2963, B2963, C2963 +A2964, B2964, C2964 A2965, B2965, C2965 A2966, B2966, C2966 +A2967, B2967, C2967 A2968, B2968, C2968 +A2969, B2969, C2969 +A2970, B2970, C2970 +A2971, B2971, C2971 A2972, B2972, C2972 A2973, B2973, C2973 +A2974, B2974, C2974 +A2975, B2975, C2975 +A2976, B2976, C2976 A2977, B2977, C2977 +A2978, B2978, C2978 +A2979, B2979, C2979 +A2980, B2980, C2980 +A2981, B2981, C2981 +A2982, B2982, C2982 A2983, B2983, C2983 +A2984, B2984, C2984 A2985, B2985, C2985 +A2986, B2986, C2986 +A2987, B2987, C2987 +A2988, B2988, C2988 +A2989, B2989, C2989 A2990, B2990, C2990 +A2991, B2991, C2991 +A2992, B2992, C2992 +A2993, B2993, C2993 +A2994, B2994, C2994 +A2995, B2995, C2995 +A2996, B2996, C2996 +A2997, B2997, C2997 A2998, B2998, C2998 A2999, B2999, C2999 +A3000, B3000, C3000 A3001, B3001, C3001 A3002, B3002, C3002 A3003, B3003, C3003 A3004, B3004, C3004 A3005, B3005, C3005 A3006, B3006, C3006 A3007, B3007, C3007 A3008, B3008, C3008 A3009, B3009, C3009 +A3010, B3010, C3010 +A3011, B3011, C3011 +A3012, B3012, C3012 A3013, B3013, C3013 A3014, B3014, C3014 A3015, B3015, C3015 +A3016, B3016, C3016 A3017, B3017, C3017 +A3018, B3018, C3018 +A3019, B3019, C3019 A3020, B3020, C3020 +A3021, B3021, C3021 A3022, B3022, C3022 +A3023, B3023, C3023 +A3024, B3024, C3024 A3025, B3025, C3025 +A3026, B3026, C3026 +A3027, B3027, C3027 A3028, B3028, C3028 A3029, B3029, C3029 +A3030, B3030, C3030 A3031, B3031, C3031 +A3032, B3032, C3032 A3033, B3033, C3033 A3034, B3034, C3034 +A3035, B3035, C3035 +A3036, B3036, C3036 +A3037, B3037, C3037 A3038, B3038, C3038 +A3039, B3039, C3039 A3040, B3040, C3040 +A3041, B3041, C3041 +A3042, B3042, C3042 +A3043, B3043, C3043 A3044, B3044, C3044 +A3045, B3045, C3045 +A3046, B3046, C3046 +A3047, B3047, C3047 +A3048, B3048, C3048 +A3049, B3049, C3049 +A3050, B3050, C3050 +A3051, B3051, C3051 +A3052, B3052, C3052 +A3053, B3053, C3053 A3054, B3054, C3054 A3055, B3055, C3055 A3056, B3056, C3056 +A3057, B3057, C3057 +A3058, B3058, C3058 +A3059, B3059, C3059 +A3060, B3060, C3060 A3061, B3061, C3061 +A3062, B3062, C3062 A3063, B3063, C3063 +A3064, B3064, C3064 +A3065, B3065, C3065 A3066, B3066, C3066 A3067, B3067, C3067 +A3068, B3068, C3068 +A3069, B3069, C3069 A3070, B3070, C3070 +A3071, B3071, C3071 +A3072, B3072, C3072 +A3073, B3073, C3073 +A3074, B3074, C3074 A3075, B3075, C3075 A3076, B3076, C3076 +A3077, B3077, C3077 +A3078, B3078, C3078 +A3079, B3079, C3079 +A3080, B3080, C3080 +A3081, B3081, C3081 +A3082, B3082, C3082 A3083, B3083, C3083 +A3084, B3084, C3084 +A3085, B3085, C3085 A3086, B3086, C3086 +A3087, B3087, C3087 +A3088, B3088, C3088 A3089, B3089, C3089 A3090, B3090, C3090 +A3091, B3091, C3091 A3092, B3092, C3092 A3093, B3093, C3093 +A3094, B3094, C3094 +A3095, B3095, C3095 +A3096, B3096, C3096 +A3097, B3097, C3097 A3098, B3098, C3098 +A3099, B3099, C3099 A3100, B3100, C3100 +A3101, B3101, C3101 +A3102, B3102, C3102 A3103, B3103, C3103 +A3104, B3104, C3104 +A3105, B3105, C3105 +A3106, B3106, C3106 A3107, B3107, C3107 +A3108, B3108, C3108 +A3109, B3109, C3109 +A3110, B3110, C3110 A3111, B3111, C3111 +A3112, B3112, C3112 +A3113, B3113, C3113 +A3114, B3114, C3114 A3115, B3115, C3115 +A3116, B3116, C3116 A3117, B3117, C3117 A3118, B3118, C3118 +A3119, B3119, C3119 +A3120, B3120, C3120 +A3121, B3121, C3121 A3122, B3122, C3122 +A3123, B3123, C3123 +A3124, B3124, C3124 +A3125, B3125, C3125 +A3126, B3126, C3126 +A3127, B3127, C3127 +A3128, B3128, C3128 +A3129, B3129, C3129 +A3130, B3130, C3130 +A3131, B3131, C3131 +A3132, B3132, C3132 +A3133, B3133, C3133 A3134, B3134, C3134 A3135, B3135, C3135 +A3136, B3136, C3136 A3137, B3137, C3137 A3138, B3138, C3138 +A3139, B3139, C3139 +A3140, B3140, C3140 +A3141, B3141, C3141 +A3142, B3142, C3142 +A3143, B3143, C3143 A3144, B3144, C3144 A3145, B3145, C3145 A3146, B3146, C3146 +A3147, B3147, C3147 A3148, B3148, C3148 +A3149, B3149, C3149 +A3150, B3150, C3150 +A3151, B3151, C3151 +A3152, B3152, C3152 +A3153, B3153, C3153 A3154, B3154, C3154 A3155, B3155, C3155 +A3156, B3156, C3156 A3157, B3157, C3157 +A3158, B3158, C3158 A3159, B3159, C3159 A3160, B3160, C3160 +A3161, B3161, C3161 +A3162, B3162, C3162 +A3163, B3163, C3163 A3164, B3164, C3164 +A3165, B3165, C3165 A3166, B3166, C3166 A3167, B3167, C3167 A3168, B3168, C3168 +A3169, B3169, C3169 +A3170, B3170, C3170 A3171, B3171, C3171 +A3172, B3172, C3172 +A3173, B3173, C3173 +A3174, B3174, C3174 A3175, B3175, C3175 +A3176, B3176, C3176 +A3177, B3177, C3177 A3178, B3178, C3178 A3179, B3179, C3179 A3180, B3180, C3180 +A3181, B3181, C3181 +A3182, B3182, C3182 +A3183, B3183, C3183 +A3184, B3184, C3184 +A3185, B3185, C3185 A3186, B3186, C3186 +A3187, B3187, C3187 +A3188, B3188, C3188 +A3189, B3189, C3189 +A3190, B3190, C3190 A3191, B3191, C3191 +A3192, B3192, C3192 A3193, B3193, C3193 +A3194, B3194, C3194 +A3195, B3195, C3195 +A3196, B3196, C3196 +A3197, B3197, C3197 A3198, B3198, C3198 A3199, B3199, C3199 +A3200, B3200, C3200 +A3201, B3201, C3201 +A3202, B3202, C3202 +A3203, B3203, C3203 A3204, B3204, C3204 A3205, B3205, C3205 +A3206, B3206, C3206 A3207, B3207, C3207 A3208, B3208, C3208 +A3209, B3209, C3209 A3210, B3210, C3210 +A3211, B3211, C3211 +A3212, B3212, C3212 A3213, B3213, C3213 +A3214, B3214, C3214 A3215, B3215, C3215 +A3216, B3216, C3216 +A3217, B3217, C3217 +A3218, B3218, C3218 +A3219, B3219, C3219 +A3220, B3220, C3220 A3221, B3221, C3221 +A3222, B3222, C3222 A3223, B3223, C3223 A3224, B3224, C3224 +A3225, B3225, C3225 +A3226, B3226, C3226 +A3227, B3227, C3227 +A3228, B3228, C3228 A3229, B3229, C3229 +A3230, B3230, C3230 +A3231, B3231, C3231 +A3232, B3232, C3232 +A3233, B3233, C3233 +A3234, B3234, C3234 A3235, B3235, C3235 +A3236, B3236, C3236 +A3237, B3237, C3237 +A3238, B3238, C3238 +A3239, B3239, C3239 +A3240, B3240, C3240 A3241, B3241, C3241 +A3242, B3242, C3242 +A3243, B3243, C3243 +A3244, B3244, C3244 A3245, B3245, C3245 A3246, B3246, C3246 A3247, B3247, C3247 +A3248, B3248, C3248 +A3249, B3249, C3249 +A3250, B3250, C3250 A3251, B3251, C3251 +A3252, B3252, C3252 +A3253, B3253, C3253 +A3254, B3254, C3254 A3255, B3255, C3255 A3256, B3256, C3256 A3257, B3257, C3257 +A3258, B3258, C3258 +A3259, B3259, C3259 A3260, B3260, C3260 +A3261, B3261, C3261 +A3262, B3262, C3262 A3263, B3263, C3263 +A3264, B3264, C3264 A3265, B3265, C3265 +A3266, B3266, C3266 A3267, B3267, C3267 +A3268, B3268, C3268 +A3269, B3269, C3269 +A3270, B3270, C3270 A3271, B3271, C3271 +A3272, B3272, C3272 A3273, B3273, C3273 +A3274, B3274, C3274 A3275, B3275, C3275 A3276, B3276, C3276 +A3277, B3277, C3277 A3278, B3278, C3278 A3279, B3279, C3279 A3280, B3280, C3280 +A3281, B3281, C3281 +A3282, B3282, C3282 +A3283, B3283, C3283 A3284, B3284, C3284 +A3285, B3285, C3285 +A3286, B3286, C3286 +A3287, B3287, C3287 A3288, B3288, C3288 +A3289, B3289, C3289 +A3290, B3290, C3290 +A3291, B3291, C3291 +A3292, B3292, C3292 +A3293, B3293, C3293 +A3294, B3294, C3294 +A3295, B3295, C3295 +A3296, B3296, C3296 A3297, B3297, C3297 +A3298, B3298, C3298 +A3299, B3299, C3299 A3300, B3300, C3300 +A3301, B3301, C3301 +A3302, B3302, C3302 +A3303, B3303, C3303 +A3304, B3304, C3304 A3305, B3305, C3305 +A3306, B3306, C3306 +A3307, B3307, C3307 +A3308, B3308, C3308 +A3309, B3309, C3309 +A3310, B3310, C3310 +A3311, B3311, C3311 A3312, B3312, C3312 A3313, B3313, C3313 +A3314, B3314, C3314 +A3315, B3315, C3315 +A3316, B3316, C3316 +A3317, B3317, C3317 +A3318, B3318, C3318 +A3319, B3319, C3319 +A3320, B3320, C3320 +A3321, B3321, C3321 +A3322, B3322, C3322 +A3323, B3323, C3323 A3324, B3324, C3324 A3325, B3325, C3325 +A3326, B3326, C3326 +A3327, B3327, C3327 +A3328, B3328, C3328 +A3329, B3329, C3329 +A3330, B3330, C3330 A3331, B3331, C3331 +A3332, B3332, C3332 +A3333, B3333, C3333 +A3334, B3334, C3334 +A3335, B3335, C3335 +A3336, B3336, C3336 +A3337, B3337, C3337 +A3338, B3338, C3338 +A3339, B3339, C3339 +A3340, B3340, C3340 +A3341, B3341, C3341 A3342, B3342, C3342 +A3343, B3343, C3343 A3344, B3344, C3344 A3345, B3345, C3345 +A3346, B3346, C3346 +A3347, B3347, C3347 +A3348, B3348, C3348 A3349, B3349, C3349 +A3350, B3350, C3350 A3351, B3351, C3351 +A3352, B3352, C3352 +A3353, B3353, C3353 A3354, B3354, C3354 +A3355, B3355, C3355 +A3356, B3356, C3356 +A3357, B3357, C3357 +A3358, B3358, C3358 A3359, B3359, C3359 +A3360, B3360, C3360 A3361, B3361, C3361 +A3362, B3362, C3362 +A3363, B3363, C3363 +A3364, B3364, C3364 +A3365, B3365, C3365 +A3366, B3366, C3366 +A3367, B3367, C3367 +A3368, B3368, C3368 +A3369, B3369, C3369 +A3370, B3370, C3370 +A3371, B3371, C3371 A3372, B3372, C3372 +A3373, B3373, C3373 +A3374, B3374, C3374 +A3375, B3375, C3375 +A3376, B3376, C3376 +A3377, B3377, C3377 +A3378, B3378, C3378 +A3379, B3379, C3379 +A3380, B3380, C3380 +A3381, B3381, C3381 A3382, B3382, C3382 A3383, B3383, C3383 +A3384, B3384, C3384 A3385, B3385, C3385 +A3386, B3386, C3386 A3387, B3387, C3387 A3388, B3388, C3388 +A3389, B3389, C3389 +A3390, B3390, C3390 +A3391, B3391, C3391 A3392, B3392, C3392 +A3393, B3393, C3393 A3394, B3394, C3394 A3395, B3395, C3395 +A3396, B3396, C3396 +A3397, B3397, C3397 A3398, B3398, C3398 +A3399, B3399, C3399 +A3400, B3400, C3400 +A3401, B3401, C3401 +A3402, B3402, C3402 +A3403, B3403, C3403 +A3404, B3404, C3404 +A3405, B3405, C3405 +A3406, B3406, C3406 +A3407, B3407, C3407 +A3408, B3408, C3408 +A3409, B3409, C3409 +A3410, B3410, C3410 +A3411, B3411, C3411 A3412, B3412, C3412 +A3413, B3413, C3413 A3414, B3414, C3414 +A3415, B3415, C3415 +A3416, B3416, C3416 A3417, B3417, C3417 +A3418, B3418, C3418 A3419, B3419, C3419 +A3420, B3420, C3420 +A3421, B3421, C3421 +A3422, B3422, C3422 A3423, B3423, C3423 A3424, B3424, C3424 +A3425, B3425, C3425 A3426, B3426, C3426 A3427, B3427, C3427 +A3428, B3428, C3428 +A3429, B3429, C3429 +A3430, B3430, C3430 +A3431, B3431, C3431 +A3432, B3432, C3432 +A3433, B3433, C3433 +A3434, B3434, C3434 A3435, B3435, C3435 A3436, B3436, C3436 +A3437, B3437, C3437 +A3438, B3438, C3438 +A3439, B3439, C3439 +A3440, B3440, C3440 +A3441, B3441, C3441 +A3442, B3442, C3442 +A3443, B3443, C3443 +A3444, B3444, C3444 +A3445, B3445, C3445 +A3446, B3446, C3446 +A3447, B3447, C3447 A3448, B3448, C3448 A3449, B3449, C3449 A3450, B3450, C3450 +A3451, B3451, C3451 +A3452, B3452, C3452 +A3453, B3453, C3453 +A3454, B3454, C3454 +A3455, B3455, C3455 A3456, B3456, C3456 +A3457, B3457, C3457 +A3458, B3458, C3458 +A3459, B3459, C3459 +A3460, B3460, C3460 A3461, B3461, C3461 +A3462, B3462, C3462 +A3463, B3463, C3463 +A3464, B3464, C3464 +A3465, B3465, C3465 +A3466, B3466, C3466 +A3467, B3467, C3467 +A3468, B3468, C3468 A3469, B3469, C3469 A3470, B3470, C3470 +A3471, B3471, C3471 +A3472, B3472, C3472 +A3473, B3473, C3473 A3474, B3474, C3474 +A3475, B3475, C3475 +A3476, B3476, C3476 A3477, B3477, C3477 +A3478, B3478, C3478 A3479, B3479, C3479 +A3480, B3480, C3480 +A3481, B3481, C3481 A3482, B3482, C3482 +A3483, B3483, C3483 +A3484, B3484, C3484 +A3485, B3485, C3485 +A3486, B3486, C3486 A3487, B3487, C3487 +A3488, B3488, C3488 +A3489, B3489, C3489 +A3490, B3490, C3490 +A3491, B3491, C3491 A3492, B3492, C3492 +A3493, B3493, C3493 +A3494, B3494, C3494 +A3495, B3495, C3495 +A3496, B3496, C3496 A3497, B3497, C3497 A3498, B3498, C3498 +A3499, B3499, C3499 +A3500, B3500, C3500 +A3501, B3501, C3501 A3502, B3502, C3502 A3503, B3503, C3503 +A3504, B3504, C3504 +A3505, B3505, C3505 +A3506, B3506, C3506 +A3507, B3507, C3507 +A3508, B3508, C3508 +A3509, B3509, C3509 +A3510, B3510, C3510 +A3511, B3511, C3511 +A3512, B3512, C3512 A3513, B3513, C3513 A3514, B3514, C3514 A3515, B3515, C3515 +A3516, B3516, C3516 +A3517, B3517, C3517 A3518, B3518, C3518 A3519, B3519, C3519 A3520, B3520, C3520 +A3521, B3521, C3521 +A3522, B3522, C3522 A3523, B3523, C3523 A3524, B3524, C3524 +A3525, B3525, C3525 +A3526, B3526, C3526 +A3527, B3527, C3527 +A3528, B3528, C3528 A3529, B3529, C3529 A3530, B3530, C3530 A3531, B3531, C3531 +A3532, B3532, C3532 +A3533, B3533, C3533 A3534, B3534, C3534 +A3535, B3535, C3535 +A3536, B3536, C3536 +A3537, B3537, C3537 +A3538, B3538, C3538 +A3539, B3539, C3539 +A3540, B3540, C3540 +A3541, B3541, C3541 +A3542, B3542, C3542 A3543, B3543, C3543 +A3544, B3544, C3544 +A3545, B3545, C3545 +A3546, B3546, C3546 +A3547, B3547, C3547 +A3548, B3548, C3548 A3549, B3549, C3549 A3550, B3550, C3550 A3551, B3551, C3551 +A3552, B3552, C3552 A3553, B3553, C3553 A3554, B3554, C3554 A3555, B3555, C3555 +A3556, B3556, C3556 +A3557, B3557, C3557 +A3558, B3558, C3558 A3559, B3559, C3559 A3560, B3560, C3560 A3561, B3561, C3561 +A3562, B3562, C3562 +A3563, B3563, C3563 +A3564, B3564, C3564 A3565, B3565, C3565 A3566, B3566, C3566 +A3567, B3567, C3567 +A3568, B3568, C3568 +A3569, B3569, C3569 A3570, B3570, C3570 +A3571, B3571, C3571 A3572, B3572, C3572 A3573, B3573, C3573 +A3574, B3574, C3574 A3575, B3575, C3575 +A3576, B3576, C3576 +A3577, B3577, C3577 +A3578, B3578, C3578 A3579, B3579, C3579 A3580, B3580, C3580 +A3581, B3581, C3581 +A3582, B3582, C3582 +A3583, B3583, C3583 +A3584, B3584, C3584 +A3585, B3585, C3585 +A3586, B3586, C3586 +A3587, B3587, C3587 +A3588, B3588, C3588 +A3589, B3589, C3589 +A3590, B3590, C3590 +A3591, B3591, C3591 +A3592, B3592, C3592 +A3593, B3593, C3593 A3594, B3594, C3594 A3595, B3595, C3595 A3596, B3596, C3596 A3597, B3597, C3597 +A3598, B3598, C3598 A3599, B3599, C3599 +A3600, B3600, C3600 A3601, B3601, C3601 A3602, B3602, C3602 +A3603, B3603, C3603 A3604, B3604, C3604 +A3605, B3605, C3605 +A3606, B3606, C3606 +A3607, B3607, C3607 A3608, B3608, C3608 A3609, B3609, C3609 +A3610, B3610, C3610 +A3611, B3611, C3611 +A3612, B3612, C3612 +A3613, B3613, C3613 +A3614, B3614, C3614 +A3615, B3615, C3615 +A3616, B3616, C3616 +A3617, B3617, C3617 +A3618, B3618, C3618 +A3619, B3619, C3619 A3620, B3620, C3620 A3621, B3621, C3621 +A3622, B3622, C3622 A3623, B3623, C3623 +A3624, B3624, C3624 +A3625, B3625, C3625 A3626, B3626, C3626 +A3627, B3627, C3627 A3628, B3628, C3628 A3629, B3629, C3629 A3630, B3630, C3630 +A3631, B3631, C3631 A3632, B3632, C3632 +A3633, B3633, C3633 +A3634, B3634, C3634 +A3635, B3635, C3635 A3636, B3636, C3636 A3637, B3637, C3637 A3638, B3638, C3638 A3639, B3639, C3639 +A3640, B3640, C3640 +A3641, B3641, C3641 +A3642, B3642, C3642 +A3643, B3643, C3643 +A3644, B3644, C3644 A3645, B3645, C3645 +A3646, B3646, C3646 A3647, B3647, C3647 +A3648, B3648, C3648 A3649, B3649, C3649 +A3650, B3650, C3650 +A3651, B3651, C3651 +A3652, B3652, C3652 +A3653, B3653, C3653 +A3654, B3654, C3654 +A3655, B3655, C3655 +A3656, B3656, C3656 +A3657, B3657, C3657 A3658, B3658, C3658 A3659, B3659, C3659 +A3660, B3660, C3660 +A3661, B3661, C3661 +A3662, B3662, C3662 A3663, B3663, C3663 +A3664, B3664, C3664 A3665, B3665, C3665 +A3666, B3666, C3666 +A3667, B3667, C3667 +A3668, B3668, C3668 +A3669, B3669, C3669 A3670, B3670, C3670 +A3671, B3671, C3671 +A3672, B3672, C3672 +A3673, B3673, C3673 A3674, B3674, C3674 +A3675, B3675, C3675 +A3676, B3676, C3676 A3677, B3677, C3677 +A3678, B3678, C3678 +A3679, B3679, C3679 A3680, B3680, C3680 +A3681, B3681, C3681 +A3682, B3682, C3682 A3683, B3683, C3683 A3684, B3684, C3684 +A3685, B3685, C3685 +A3686, B3686, C3686 A3687, B3687, C3687 A3688, B3688, C3688 +A3689, B3689, C3689 +A3690, B3690, C3690 A3691, B3691, C3691 A3692, B3692, C3692 A3693, B3693, C3693 A3694, B3694, C3694 +A3695, B3695, C3695 +A3696, B3696, C3696 A3697, B3697, C3697 +A3698, B3698, C3698 A3699, B3699, C3699 +A3700, B3700, C3700 +A3701, B3701, C3701 +A3702, B3702, C3702 +A3703, B3703, C3703 +A3704, B3704, C3704 +A3705, B3705, C3705 +A3706, B3706, C3706 A3707, B3707, C3707 +A3708, B3708, C3708 +A3709, B3709, C3709 +A3710, B3710, C3710 A3711, B3711, C3711 +A3712, B3712, C3712 +A3713, B3713, C3713 +A3714, B3714, C3714 +A3715, B3715, C3715 +A3716, B3716, C3716 +A3717, B3717, C3717 A3718, B3718, C3718 A3719, B3719, C3719 A3720, B3720, C3720 A3721, B3721, C3721 +A3722, B3722, C3722 +A3723, B3723, C3723 +A3724, B3724, C3724 +A3725, B3725, C3725 A3726, B3726, C3726 A3727, B3727, C3727 +A3728, B3728, C3728 +A3729, B3729, C3729 +A3730, B3730, C3730 A3731, B3731, C3731 A3732, B3732, C3732 +A3733, B3733, C3733 A3734, B3734, C3734 +A3735, B3735, C3735 A3736, B3736, C3736 A3737, B3737, C3737 A3738, B3738, C3738 A3739, B3739, C3739 A3740, B3740, C3740 +A3741, B3741, C3741 +A3742, B3742, C3742 +A3743, B3743, C3743 +A3744, B3744, C3744 +A3745, B3745, C3745 +A3746, B3746, C3746 A3747, B3747, C3747 +A3748, B3748, C3748 +A3749, B3749, C3749 A3750, B3750, C3750 +A3751, B3751, C3751 +A3752, B3752, C3752 A3753, B3753, C3753 +A3754, B3754, C3754 +A3755, B3755, C3755 +A3756, B3756, C3756 +A3757, B3757, C3757 +A3758, B3758, C3758 +A3759, B3759, C3759 +A3760, B3760, C3760 A3761, B3761, C3761 A3762, B3762, C3762 +A3763, B3763, C3763 A3764, B3764, C3764 +A3765, B3765, C3765 +A3766, B3766, C3766 +A3767, B3767, C3767 A3768, B3768, C3768 +A3769, B3769, C3769 A3770, B3770, C3770 +A3771, B3771, C3771 +A3772, B3772, C3772 A3773, B3773, C3773 +A3774, B3774, C3774 +A3775, B3775, C3775 +A3776, B3776, C3776 +A3777, B3777, C3777 +A3778, B3778, C3778 A3779, B3779, C3779 +A3780, B3780, C3780 A3781, B3781, C3781 +A3782, B3782, C3782 +A3783, B3783, C3783 +A3784, B3784, C3784 A3785, B3785, C3785 +A3786, B3786, C3786 A3787, B3787, C3787 A3788, B3788, C3788 A3789, B3789, C3789 A3790, B3790, C3790 +A3791, B3791, C3791 A3792, B3792, C3792 +A3793, B3793, C3793 +A3794, B3794, C3794 A3795, B3795, C3795 +A3796, B3796, C3796 +A3797, B3797, C3797 +A3798, B3798, C3798 +A3799, B3799, C3799 A3800, B3800, C3800 A3801, B3801, C3801 A3802, B3802, C3802 A3803, B3803, C3803 +A3804, B3804, C3804 +A3805, B3805, C3805 +A3806, B3806, C3806 +A3807, B3807, C3807 A3808, B3808, C3808 A3809, B3809, C3809 +A3810, B3810, C3810 A3811, B3811, C3811 A3812, B3812, C3812 +A3813, B3813, C3813 A3814, B3814, C3814 +A3815, B3815, C3815 A3816, B3816, C3816 A3817, B3817, C3817 +A3818, B3818, C3818 +A3819, B3819, C3819 A3820, B3820, C3820 A3821, B3821, C3821 +A3822, B3822, C3822 +A3823, B3823, C3823 +A3824, B3824, C3824 +A3825, B3825, C3825 +A3826, B3826, C3826 +A3827, B3827, C3827 +A3828, B3828, C3828 A3829, B3829, C3829 +A3830, B3830, C3830 +A3831, B3831, C3831 +A3832, B3832, C3832 +A3833, B3833, C3833 +A3834, B3834, C3834 A3835, B3835, C3835 A3836, B3836, C3836 A3837, B3837, C3837 +A3838, B3838, C3838 +A3839, B3839, C3839 +A3840, B3840, C3840 A3841, B3841, C3841 +A3842, B3842, C3842 +A3843, B3843, C3843 +A3844, B3844, C3844 +A3845, B3845, C3845 +A3846, B3846, C3846 A3847, B3847, C3847 A3848, B3848, C3848 +A3849, B3849, C3849 +A3850, B3850, C3850 +A3851, B3851, C3851 +A3852, B3852, C3852 +A3853, B3853, C3853 +A3854, B3854, C3854 +A3855, B3855, C3855 +A3856, B3856, C3856 +A3857, B3857, C3857 +A3858, B3858, C3858 +A3859, B3859, C3859 +A3860, B3860, C3860 +A3861, B3861, C3861 +A3862, B3862, C3862 A3863, B3863, C3863 A3864, B3864, C3864 A3865, B3865, C3865 A3866, B3866, C3866 +A3867, B3867, C3867 +A3868, B3868, C3868 A3869, B3869, C3869 +A3870, B3870, C3870 +A3871, B3871, C3871 A3872, B3872, C3872 +A3873, B3873, C3873 +A3874, B3874, C3874 A3875, B3875, C3875 +A3876, B3876, C3876 A3877, B3877, C3877 +A3878, B3878, C3878 +A3879, B3879, C3879 +A3880, B3880, C3880 +A3881, B3881, C3881 +A3882, B3882, C3882 +A3883, B3883, C3883 +A3884, B3884, C3884 A3885, B3885, C3885 A3886, B3886, C3886 A3887, B3887, C3887 +A3888, B3888, C3888 +A3889, B3889, C3889 +A3890, B3890, C3890 A3891, B3891, C3891 +A3892, B3892, C3892 +A3893, B3893, C3893 +A3894, B3894, C3894 +A3895, B3895, C3895 A3896, B3896, C3896 +A3897, B3897, C3897 +A3898, B3898, C3898 +A3899, B3899, C3899 +A3900, B3900, C3900 A3901, B3901, C3901 +A3902, B3902, C3902 +A3903, B3903, C3903 A3904, B3904, C3904 +A3905, B3905, C3905 A3906, B3906, C3906 +A3907, B3907, C3907 +A3908, B3908, C3908 +A3909, B3909, C3909 +A3910, B3910, C3910 +A3911, B3911, C3911 A3912, B3912, C3912 +A3913, B3913, C3913 +A3914, B3914, C3914 +A3915, B3915, C3915 +A3916, B3916, C3916 +A3917, B3917, C3917 +A3918, B3918, C3918 +A3919, B3919, C3919 A3920, B3920, C3920 +A3921, B3921, C3921 +A3922, B3922, C3922 +A3923, B3923, C3923 +A3924, B3924, C3924 +A3925, B3925, C3925 +A3926, B3926, C3926 A3927, B3927, C3927 +A3928, B3928, C3928 A3929, B3929, C3929 A3930, B3930, C3930 A3931, B3931, C3931 +A3932, B3932, C3932 A3933, B3933, C3933 +A3934, B3934, C3934 +A3935, B3935, C3935 +A3936, B3936, C3936 +A3937, B3937, C3937 A3938, B3938, C3938 A3939, B3939, C3939 +A3940, B3940, C3940 +A3941, B3941, C3941 A3942, B3942, C3942 +A3943, B3943, C3943 A3944, B3944, C3944 +A3945, B3945, C3945 +A3946, B3946, C3946 +A3947, B3947, C3947 +A3948, B3948, C3948 +A3949, B3949, C3949 +A3950, B3950, C3950 A3951, B3951, C3951 +A3952, B3952, C3952 +A3953, B3953, C3953 A3954, B3954, C3954 +A3955, B3955, C3955 +A3956, B3956, C3956 +A3957, B3957, C3957 A3958, B3958, C3958 A3959, B3959, C3959 A3960, B3960, C3960 +A3961, B3961, C3961 +A3962, B3962, C3962 +A3963, B3963, C3963 A3964, B3964, C3964 +A3965, B3965, C3965 A3966, B3966, C3966 A3967, B3967, C3967 +A3968, B3968, C3968 A3969, B3969, C3969 A3970, B3970, C3970 +A3971, B3971, C3971 +A3972, B3972, C3972 +A3973, B3973, C3973 +A3974, B3974, C3974 +A3975, B3975, C3975 A3976, B3976, C3976 A3977, B3977, C3977 +A3978, B3978, C3978 +A3979, B3979, C3979 +A3980, B3980, C3980 +A3981, B3981, C3981 +A3982, B3982, C3982 +A3983, B3983, C3983 A3984, B3984, C3984 A3985, B3985, C3985 +A3986, B3986, C3986 +A3987, B3987, C3987 A3988, B3988, C3988 +A3989, B3989, C3989 +A3990, B3990, C3990 A3991, B3991, C3991 A3992, B3992, C3992 +A3993, B3993, C3993 A3994, B3994, C3994 +A3995, B3995, C3995 +A3996, B3996, C3996 +A3997, B3997, C3997 A3998, B3998, C3998 A3999, B3999, C3999 +A4000, B4000, C4000 A4001, B4001, C4001 +A4002, B4002, C4002 A4003, B4003, C4003 +A4004, B4004, C4004 +A4005, B4005, C4005 +A4006, B4006, C4006 +A4007, B4007, C4007 +A4008, B4008, C4008 +A4009, B4009, C4009 +A4010, B4010, C4010 A4011, B4011, C4011 +A4012, B4012, C4012 +A4013, B4013, C4013 A4014, B4014, C4014 +A4015, B4015, C4015 +A4016, B4016, C4016 A4017, B4017, C4017 A4018, B4018, C4018 +A4019, B4019, C4019 +A4020, B4020, C4020 +A4021, B4021, C4021 A4022, B4022, C4022 +A4023, B4023, C4023 A4024, B4024, C4024 A4025, B4025, C4025 A4026, B4026, C4026 +A4027, B4027, C4027 +A4028, B4028, C4028 +A4029, B4029, C4029 A4030, B4030, C4030 A4031, B4031, C4031 +A4032, B4032, C4032 +A4033, B4033, C4033 A4034, B4034, C4034 +A4035, B4035, C4035 A4036, B4036, C4036 A4037, B4037, C4037 +A4038, B4038, C4038 A4039, B4039, C4039 +A4040, B4040, C4040 +A4041, B4041, C4041 A4042, B4042, C4042 +A4043, B4043, C4043 A4044, B4044, C4044 +A4045, B4045, C4045 +A4046, B4046, C4046 +A4047, B4047, C4047 +A4048, B4048, C4048 +A4049, B4049, C4049 A4050, B4050, C4050 +A4051, B4051, C4051 +A4052, B4052, C4052 A4053, B4053, C4053 A4054, B4054, C4054 A4055, B4055, C4055 +A4056, B4056, C4056 +A4057, B4057, C4057 A4058, B4058, C4058 A4059, B4059, C4059 A4060, B4060, C4060 A4061, B4061, C4061 A4062, B4062, C4062 +A4063, B4063, C4063 +A4064, B4064, C4064 +A4065, B4065, C4065 +A4066, B4066, C4066 A4067, B4067, C4067 A4068, B4068, C4068 +A4069, B4069, C4069 +A4070, B4070, C4070 +A4071, B4071, C4071 A4072, B4072, C4072 +A4073, B4073, C4073 +A4074, B4074, C4074 +A4075, B4075, C4075 A4076, B4076, C4076 +A4077, B4077, C4077 +A4078, B4078, C4078 A4079, B4079, C4079 +A4080, B4080, C4080 +A4081, B4081, C4081 A4082, B4082, C4082 +A4083, B4083, C4083 +A4084, B4084, C4084 +A4085, B4085, C4085 +A4086, B4086, C4086 +A4087, B4087, C4087 A4088, B4088, C4088 +A4089, B4089, C4089 +A4090, B4090, C4090 +A4091, B4091, C4091 +A4092, B4092, C4092 +A4093, B4093, C4093 +A4094, B4094, C4094 +A4095, B4095, C4095 +A4096, B4096, C4096