Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion csv_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ typedef struct {
char path[MAX_PATH_LENGTH];
int offset;
bool hasHeader;
char limit;
int limit;
CSVEncoding encoding;
bool writeBOM;
bool strictMode;
Expand Down
26 changes: 26 additions & 0 deletions csv_reader.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include "csv_parser.h"
#include "arena.h"

static void skip_records(CSVReader *reader, int count);

CSVReader* csv_reader_init_with_config(Arena *persistent_arena, Arena *temp_arena, CSVConfig *config) {
void *ptr;
ArenaResult result = arena_alloc(persistent_arena, sizeof(CSVReader), &ptr);
Expand All @@ -26,6 +28,7 @@ CSVReader* csv_reader_init_with_config(Arena *persistent_arena, Arena *temp_aren
reader->cached_headers = NULL;
reader->line_number = 0;
reader->current_record = NULL;
reader->records_returned = 0;
reader->owns_arenas = false;

if (config->hasHeader) {
Expand All @@ -41,6 +44,8 @@ CSVReader* csv_reader_init_with_config(Arena *persistent_arena, Arena *temp_aren
}
}

skip_records(reader, config->offset);

return reader;
}

Expand Down Expand Up @@ -96,6 +101,7 @@ CSVReader* csv_reader_init_standalone(CSVConfig *config) {
reader->cached_headers = NULL;
reader->line_number = 0;
reader->current_record = NULL;
reader->records_returned = 0;
reader->owns_arenas = true;

if (config->hasHeader) {
Expand All @@ -111,14 +117,29 @@ CSVReader* csv_reader_init_standalone(CSVConfig *config) {
}
}

skip_records(reader, config->offset);

return reader;
}

static void skip_records(CSVReader *reader, int count) {
for (int i = 0; i < count; i++) {
arena_reset(reader->temp_arena);
char *line = read_full_record(reader->file, reader->temp_arena);
if (!line) break;
reader->line_number++;
}
}
Comment on lines +125 to +132

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect how skipEmptyLines is actually implemented relative to raw line reads
rg -nP -C5 '\bskipEmptyLines\b' --type=c
rg -nP -C8 'read_full_record\s*\(' --type=c
rg -nP -C8 'csv_parse_line_inplace\s*\(' --type=c

Repository: csvtoolkit/FastCSV-C

Length of output: 158


🏁 Script executed:

git ls-files

Repository: csvtoolkit/FastCSV-C

Length of output: 602


🏁 Script executed:

ast-grep outline csv_reader.c --view expanded
ast-grep outline csv_parser.c --view expanded
ast-grep outline tests/test_csv_reader.c --view expanded
rg -n "empty|skip|limit|offset|record" csv_reader.c csv_parser.c csv_config.c tests/test_csv_reader.c

Repository: csvtoolkit/FastCSV-C

Length of output: 25378


🏁 Script executed:

sed -n '1,220p' csv_reader.c
printf '\n----\n'
sed -n '220,360p' csv_reader.c
printf '\n----\n'
sed -n '1,220p' csv_config.c
printf '\n----\n'
sed -n '330,390p' tests/test_csv_reader.c
printf '\n----\n'
sed -n '470,650p' tests/test_csv_reader.c

Repository: csvtoolkit/FastCSV-C

Length of output: 22351


🏁 Script executed:

sed -n '1,260p' csv_parser.c
printf '\n----\n'
sed -n '1,220p' csv_reader.h
printf '\n----\n'
sed -n '1,220p' csv_config.h
printf '\n----\n'
sed -n '1,220p' README.md

Repository: csvtoolkit/FastCSV-C

Length of output: 19190


🏁 Script executed:

sed -n '260,340p' csv_parser.c
printf '\n----\n'
python3 - <<'PY'
from pathlib import Path
text = Path('csv_parser.c').read_text()
# print the exact blank-line handling branch if present
for needle in ['record_len == 0', 'line comment', 'EOF']:
    pass
PY

Repository: csvtoolkit/FastCSV-C

Length of output: 1111


Make skipEmptyLines consistent in reader navigation csv_reader_get_record_count skips blank lines, but csv_reader_init_*, csv_reader_rewind, csv_reader_seek, skip_records, and csv_reader_next_record still count them as records. With skipEmptyLines enabled, offset/seek can land on the wrong data row in files that contain empty lines.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@csv_reader.c` around lines 125 - 132, The reader navigation paths are not
honoring skipEmptyLines consistently, so empty lines still advance record
position in csv_reader_init_*, csv_reader_rewind, csv_reader_seek, skip_records,
and csv_reader_next_record. Update the record-skipping and iteration logic in
these routines to detect and ignore blank lines when skipEmptyLines is enabled,
matching the behavior already used by csv_reader_get_record_count, so offsets
and seeks land on the correct data row.


CSVRecord* csv_reader_next_record(CSVReader *reader) {
if (!reader || !reader->file) {
return NULL;
}

if (reader->config->limit > 0 && reader->records_returned >= (long)reader->config->limit) {
return NULL;
}

arena_reset(reader->temp_arena);

char *line = read_full_record(reader->file, reader->temp_arena);
Expand All @@ -127,6 +148,7 @@ CSVRecord* csv_reader_next_record(CSVReader *reader) {
}

reader->line_number++;

CSVParseResult result = csv_parse_line_inplace(line, reader->temp_arena, reader->config, reader->line_number);
if (!result.success) {
return NULL;
Expand All @@ -142,6 +164,7 @@ CSVRecord* csv_reader_next_record(CSVReader *reader) {
record->fields = result.fields.fields;
record->field_count = result.fields.count;
reader->current_record = record;
reader->records_returned++;

return record;
}
Expand Down Expand Up @@ -195,13 +218,16 @@ void csv_reader_rewind(CSVReader *reader) {
if (reader && reader->file) {
rewind(reader->file);
reader->line_number = 0;
reader->records_returned = 0;

if (reader->config->hasHeader && reader->headers_loaded) {
char *line = read_full_record(reader->file, reader->persistent_arena);
if (line) {
reader->line_number = 1;
}
}

skip_records(reader, reader->config->offset);
}
}

Expand Down
1 change: 1 addition & 0 deletions csv_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ typedef struct {
int cached_header_count;
char **cached_headers;
long line_number;
long records_returned;
CSVRecord *current_record;
bool owns_arenas;
} CSVReader;
Expand Down
Loading