// ----------------------------------------------------------------
static char* test_empty() {
	byte_reader_t* pbr = string_byte_reader_alloc();
	int ok = pbr->popen_func(pbr, NULL, "");
	mu_assert_lf(ok == TRUE);

	peek_file_reader_t* pfr = pfr_alloc(pbr, 7);

	mu_assert_lf(pfr_peek_char(pfr) == (char)EOF); // char defaults to unsigned on some platforms
	mu_assert_lf(pfr_read_char(pfr) == (char)EOF);

	pbr->pclose_func(pbr, NULL);
	pfr_free(pfr);

	return NULL;
}
// ----------------------------------------------------------------
static char* test_non_empty() {
	byte_reader_t* pbr = string_byte_reader_alloc();
	int ok = pbr->popen_func(pbr,

		NULL,

		"ab,cde\n"
		"123,4567\n"
	);
	mu_assert_lf(ok == TRUE);

	peek_file_reader_t* pfr = pfr_alloc(pbr, 7);

	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == 'a');
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == 'a');
	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == 'b');
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == 'b');

	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == ',');
	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == ',');
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == ',');
	pfr_print(pfr); pfr_buffer_by(pfr, 5);
	pfr_print(pfr); pfr_advance_by(pfr, 5);
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == '2');

	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == '3');
	pfr_print(pfr); mu_assert_lf(pfr_peek_char(pfr) == '3');
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == '3');
	pfr_print(pfr); pfr_buffer_by(pfr, 5);
	pfr_print(pfr); pfr_advance_by(pfr, 5);
	pfr_print(pfr); mu_assert_lf(pfr_read_char(pfr) == '\n');

	pbr->pclose_func(pbr, NULL);
	pfr_free(pfr);

	return NULL;
}
static int lrec_reader_stdio_csv_get_fields(lrec_reader_stdio_csv_state_t* pstate, rslls_t* pfields,
	context_t* pctx, int is_header)
{
	int rc, stridx, matchlen, record_done, field_done;
	peek_file_reader_t* pfr = pstate->pfr;
	string_builder_t*   psb = pstate->psb;
	char* field = NULL;
	int field_length = 0;

	if (pfr_peek_char(pfr) == (char)EOF) // char defaults to unsigned on some platforms
		return FALSE;

	// Strip the UTF-8 BOM, if any. This is MUCH simpler for mmap, and for stdio on files.  For mmap
	// we can test the first 3 bytes, then skip past them or not. For stdio on files we can fread
	// the first 3 bytes, then rewind the fp if they're not the UTF-8 BOM. But for stdio on stdin
	// (which is the primary reason we support stdio in Miller), we cannot rewind: stdin is not
	// rewindable.
	if (is_header) {
		pfr_buffer_by(pfr, UTF8_BOM_LENGTH);
		int rc = parse_trie_ring_match(pstate->putf8_bom_parse_trie,
			pfr->peekbuf, pfr->sob, pfr->npeeked, pfr->peekbuflenmask,
			&stridx, &matchlen);
#ifdef DEBUG_PARSER
		printf("RC=%d stridx=0x%04x matchlen=%d\n", rc, stridx, matchlen);
#endif
		if (rc == TRUE && stridx == UTF8_BOM_STRIDX) {
			pfr_advance_by(pfr, matchlen);
		}
	}

	// Loop over fields in record
	record_done = FALSE;
	while (!record_done) {
		// Assumption is dquote is "\""
		if (pfr_peek_char(pfr) != pstate->dquote[0]) { // NOT DOUBLE-QUOTED

			// Loop over characters in field
			field_done = FALSE;
			while (!field_done) {
				pfr_buffer_by(pfr, pstate->pno_dquote_parse_trie->maxlen);

				rc = parse_trie_ring_match(pstate->pno_dquote_parse_trie,
					pfr->peekbuf, pfr->sob, pfr->npeeked, pfr->peekbuflenmask,
					&stridx, &matchlen);
#ifdef DEBUG_PARSER
				pfr_print(pfr);
#endif
				if (rc) {
#ifdef DEBUG_PARSER
					printf("RC=%d stridx=0x%04x matchlen=%d\n", rc, stridx, matchlen);
#endif
					switch(stridx) {
					case EOF_STRIDX: // end of record
						rslls_append(pfields, sb_finish(psb), FREE_ENTRY_VALUE, 0);
						field_done  = TRUE;
						record_done = TRUE;
						break;
					case IFS_EOF_STRIDX:
						fprintf(stderr, "%s: syntax error: record-ending field separator at line %lld.\n",
							MLR_GLOBALS.bargv0, pstate->ilno);
						exit(1);
						break;
					case IFS_STRIDX: // end of field
						rslls_append(pfields, sb_finish(psb), FREE_ENTRY_VALUE, 0);
						field_done  = TRUE;
						break;
					case IRS_STRIDX: // end of record
						field = sb_finish_with_length(psb, &field_length);

						// The line-ending '\n' won't be included in the field buffer.
						if (pstate->do_auto_line_term) {
							if (field_length > 0 && field[field_length-1] == '\r') {
								field[field_length-1] = 0;
								context_set_autodetected_crlf(pctx);
							} else {
								context_set_autodetected_lf(pctx);
							}
						}

						rslls_append(pfields, field, FREE_ENTRY_VALUE, 0);
						field_done  = TRUE;
						record_done = TRUE;
						break;
					case DQUOTE_STRIDX: // CSV syntax error: fields containing quotes must be fully wrapped in quotes
						fprintf(stderr, "%s: syntax error: unwrapped double quote at line %lld.\n",
							MLR_GLOBALS.bargv0, pstate->ilno);
						exit(1);
						break;
					default:
						fprintf(stderr, "%s: internal coding error: unexpected token %d at line %lld.\n",
							MLR_GLOBALS.bargv0, stridx, pstate->ilno);
						exit(1);
						break;
					}
					pfr_advance_by(pfr, matchlen);
				} else {
#ifdef DEBUG_PARSER
					char c = pfr_read_char(pfr);
					printf("CHAR=%c [%02x]\n", isprint((unsigned char)c) ? c : ' ', (unsigned)c);
					sb_append_char(psb, c);
#else
					sb_append_char(psb, pfr_read_char(pfr));
#endif
				}
			}

		} else { // DOUBLE-QUOTED
			pfr_advance_by(pfr, pstate->dquotelen);

			// loop over characters in field
			field_done = FALSE;
			char* field = NULL;
			int field_length = 0;
			while (!field_done) {
				pfr_buffer_by(pfr, pstate->pdquote_parse_trie->maxlen);

				rc = parse_trie_ring_match(pstate->pdquote_parse_trie,
					pfr->peekbuf, pfr->sob, pfr->npeeked, pfr->peekbuflenmask,
					&stridx, &matchlen);

				if (rc) {
					switch(stridx) {
					case EOF_STRIDX: // end of record
						fprintf(stderr, "%s: unmatched double quote at line %lld.\n",
							MLR_GLOBALS.bargv0, pstate->ilno);
						exit(1);
						break;
					case DQUOTE_EOF_STRIDX: // end of record
						rslls_append(pfields, sb_finish(psb), FREE_ENTRY_VALUE, FIELD_QUOTED_ON_INPUT);
						field_done  = TRUE;
						record_done = TRUE;
						break;
					case DQUOTE_IFS_STRIDX: // end of field
						rslls_append(pfields, sb_finish(psb), FREE_ENTRY_VALUE, FIELD_QUOTED_ON_INPUT);
						field_done  = TRUE;
						break;
					case DQUOTE_IRS_STRIDX: // end of record
					case DQUOTE_IRS2_STRIDX: // end of record

						field = sb_finish_with_length(psb, &field_length);

						// The line-ending '\n' won't be included in the field buffer.
						if (pstate->do_auto_line_term) {
							if (field_length > 0 && field[field_length-1] == '\r') {
								field[field_length-1] = 0;
								context_set_autodetected_crlf(pctx);
							} else {
								context_set_autodetected_lf(pctx);
							}
						}

						rslls_append(pfields, field, FREE_ENTRY_VALUE, FIELD_QUOTED_ON_INPUT);
						field_done  = TRUE;
						record_done = TRUE;
						break;
					case DQUOTE_DQUOTE_STRIDX: // RFC-4180 CSV: "" inside a dquoted field is an escape for "
						sb_append_char(psb, pstate->dquote[0]);
						break;
					default:
						fprintf(stderr, "%s: internal coding error: unexpected token %d at line %lld.\n",
							MLR_GLOBALS.bargv0, stridx, pstate->ilno);
						exit(1);
						break;
					}
					pfr_advance_by(pfr, matchlen);
				} else {
					sb_append_char(psb, pfr_read_char(pfr));
				}
			}

		}
	}

	return TRUE;
}