Skip to content

Commit

Permalink
CSV Parser 2.1.3 Patch 7/28/21 (#179)
Browse files Browse the repository at this point in the history
* Fixed more compilation errors

* Added hex number parsing and a minor bug fix that could cause an assertion failure in C++11

* Fixed some clang issues

* More fixes

* Weird clang issues

* Update test_write_csv.cpp
  • Loading branch information
vincentlaucsb committed Jul 29, 2021
1 parent f7db370 commit ea547fd
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 93 deletions.
15 changes: 6 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ language:
matrix:
include:
- os: linux
env: STD=c++11 CSV_CXX_STANDARD=11 MAIN_BUILD=true CXX_COMPILER=g++-9 C_COMPILER=gcc-9
env: STD=c++11 CSV_CXX_STANDARD=11 CXX_COMPILER=g++-9 C_COMPILER=gcc-9
compiler: gcc
addons:
apt:
Expand All @@ -25,7 +25,7 @@ matrix:
packages: ['g++-9', 'cmake', 'valgrind', 'doxygen']
- os: linux
dist: focal
env: CSV_CXX_STANDARD=11 MAIN_BUILD=true CXX_COMPILER=clang++-11 C_COMPILER=clang-11
env: CSV_CXX_STANDARD=11 CXX_COMPILER=clang++-11 C_COMPILER=clang-11
compiler: clang
addons:
apt:
Expand All @@ -36,7 +36,7 @@ matrix:
- clang-11
- os: linux
dist: focal
env: CSV_CXX_STANDARD=14 MAIN_BUILD=true CXX_COMPILER=clang++-11 C_COMPILER=clang-11
env: CSV_CXX_STANDARD=14 CXX_COMPILER=clang++-11 C_COMPILER=clang-11
compiler: clang
addons:
apt:
Expand All @@ -47,7 +47,7 @@ matrix:
- clang-11
- os: linux
dist: focal
env: CSV_CXX_STANDARD=17 MAIN_BUILD=true CXX_COMPILER=clang++-11 C_COMPILER=clang-11
env: CSV_CXX_STANDARD=17 CXX_COMPILER=clang++-11 C_COMPILER=clang-11
compiler: clang
addons:
apt:
Expand All @@ -58,14 +58,11 @@ matrix:
- clang-11
dist: trusty
sudo: required
before_install:
- pyenv install 3.6.0
- pyenv global 3.6.0
- pip3 install gcovr
script:
- export CSV_TEST_ROOT=$PWD/tests
- cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=$CXX_COMPILER -DCMAKE_C_COMPILER=$C_COMPILER -DCSV_CXX_STANDARD=$CSV_CXX_STANDARD
- make csv_coverage;
- make csv_test
- ./tests/csv_test

# Memory leak check
- if [ "$MAIN_BUILD" == "true" ]; then
Expand Down
28 changes: 14 additions & 14 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ if (CSV_DEVELOPER)
# More error messages.
if (UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} \
-Wall -Werror -Wextra -Wshadow -Wsign-compare \
-Wshadow -Wwrite-strings -Wpointer-arith -Winit-self \
-Wall -Werror -Wextra -Wsign-compare \
-Wwrite-strings -Wpointer-arith -Winit-self \
-Wconversion -Wno-sign-conversion")
endif()

Expand Down Expand Up @@ -98,16 +98,16 @@ if (CSV_DEVELOPER)
add_subdirectory("tests")

# Code coverage
find_program( GCOV_PATH gcov )
if(GCOV_PATH)
set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules")
include(CodeCoverage)
append_coverage_compiler_flags()
set(ENV{CSV_TEST_ROOT} ${CSV_TEST_DIR})
setup_target_for_coverage_gcovr_html(
NAME csv_coverage
EXECUTABLE csv_test
EXCLUDE "tests/*"
)
endif()
#find_program( GCOV_PATH gcov )
#if(GCOV_PATH)
# set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/modules")
# include(CodeCoverage)
# append_coverage_compiler_flags()
# set(ENV{CSV_TEST_ROOT} ${CSV_TEST_DIR})
# setup_target_for_coverage_gcovr_html(
# NAME csv_coverage
# EXECUTABLE csv_test
# EXCLUDE "tests/*"
# )
#endif()
endif()
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,12 @@ for (auto& row: reader) {
// numbers cannot be converted to unsigned types
row["timestamp"].get<int>();

// You can also attempt to parse hex values
int value;
if (row["hexValue"].try_parse_hex(value)) {
std::cout << "Hex value is " << value << std::endl;
}

// ..
}
}
Expand Down
2 changes: 1 addition & 1 deletion include/csv.hpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
CSV for C++, version 2.1.2
CSV for C++, version 2.1.3
https://github.com/vincentlaucsb/csv-parser
MIT License
Expand Down
2 changes: 1 addition & 1 deletion include/internal/basic_csv_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ namespace csv {
if (this->field_length == 0) {
quote_escape = true;
data_pos++;
if (field_start == UNINITIALIZED_FIELD && !ws_flag(in[data_pos]))
if (field_start == UNINITIALIZED_FIELD && data_pos < in.size() && !ws_flag(in[data_pos]))
field_start = (int)(data_pos - current_row_start());
break;
}
Expand Down
2 changes: 1 addition & 1 deletion include/internal/basic_csv_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ namespace csv {
* ASCII number for a character c and, v[i + 128] is true if
* c is a whitespace character
*/
HEDLEY_CONST CONSTEXPR_14 WhitespaceMap make_ws_flags(const char* ws_chars, size_t n_chars) {
HEDLEY_CONST CONSTEXPR_17 WhitespaceMap make_ws_flags(const char* ws_chars, size_t n_chars) {
std::array<bool, 256> ret = {};
for (int i = -128; i < 128; i++) {
const int arr_idx = i + 128;
Expand Down
22 changes: 12 additions & 10 deletions include/internal/common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ namespace csv {
* Intended for functions and methods.
*/

#define STATIC_ASSERT(x) static_assert(x, "Assertion failed")

#if CMAKE_CXX_STANDARD == 17 || __cplusplus >= 201703L
#define CSV_HAS_CXX17
#endif
Expand Down Expand Up @@ -177,22 +179,22 @@ namespace csv {

// Assumed to be true by parsing functions: allows for testing
// if an item is DELIMITER or NEWLINE with a >= statement
static_assert(ParseFlags::DELIMITER < ParseFlags::NEWLINE);
STATIC_ASSERT(ParseFlags::DELIMITER < ParseFlags::NEWLINE);

/** Optimizations for reducing branching in parsing loop
*
* Idea: The meaning of all non-quote characters changes depending
* on whether or not the parser is in a quote-escaped mode (0 or 1)
*/
static_assert(quote_escape_flag(ParseFlags::NOT_SPECIAL, false) == ParseFlags::NOT_SPECIAL);
static_assert(quote_escape_flag(ParseFlags::QUOTE, false) == ParseFlags::QUOTE);
static_assert(quote_escape_flag(ParseFlags::DELIMITER, false) == ParseFlags::DELIMITER);
static_assert(quote_escape_flag(ParseFlags::NEWLINE, false) == ParseFlags::NEWLINE);

static_assert(quote_escape_flag(ParseFlags::NOT_SPECIAL, true) == ParseFlags::NOT_SPECIAL);
static_assert(quote_escape_flag(ParseFlags::QUOTE, true) == ParseFlags::QUOTE_ESCAPE_QUOTE);
static_assert(quote_escape_flag(ParseFlags::DELIMITER, true) == ParseFlags::NOT_SPECIAL);
static_assert(quote_escape_flag(ParseFlags::NEWLINE, true) == ParseFlags::NOT_SPECIAL);
STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, false) == ParseFlags::NOT_SPECIAL);
STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, false) == ParseFlags::QUOTE);
STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, false) == ParseFlags::DELIMITER);
STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, false) == ParseFlags::NEWLINE);

STATIC_ASSERT(quote_escape_flag(ParseFlags::NOT_SPECIAL, true) == ParseFlags::NOT_SPECIAL);
STATIC_ASSERT(quote_escape_flag(ParseFlags::QUOTE, true) == ParseFlags::QUOTE_ESCAPE_QUOTE);
STATIC_ASSERT(quote_escape_flag(ParseFlags::DELIMITER, true) == ParseFlags::NOT_SPECIAL);
STATIC_ASSERT(quote_escape_flag(ParseFlags::NEWLINE, true) == ParseFlags::NOT_SPECIAL);

/** An array which maps ASCII chars to a parsing flag */
using ParseFlagMap = std::array<ParseFlags, 256>;
Expand Down
66 changes: 66 additions & 0 deletions include/internal/csv_row.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,72 @@ namespace csv {
return field_str.substr(0, field.length);
}

CSV_INLINE bool CSVField::try_parse_hex(int& parsedValue) {
size_t start = 0, end = 0;

// Trim out whitespace chars
for (; start < this->sv.size() && this->sv[start] == ' '; start++);
for (end = start; end < this->sv.size() && this->sv[end] != ' '; end++);

unsigned long long int value = 0;

size_t digits = (end - start);
size_t base16_exponent = digits - 1;

if (digits == 0) return false;

for (const auto& ch : this->sv.substr(start, digits)) {
int digit = 0;

switch (ch) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
digit = static_cast<int>(ch - '0');
break;
case 'a':
case 'A':
digit = 10;
break;
case 'b':
case 'B':
digit = 11;
break;
case 'c':
case 'C':
digit = 12;
break;
case 'd':
case 'D':
digit = 13;
break;
case 'e':
case 'E':
digit = 14;
break;
case 'f':
case 'F':
digit = 15;
break;
default:
return false;
}

value += digit * pow(16, base16_exponent);
base16_exponent--;
}

parsedValue = value;
return true;
}

#ifdef _MSC_VER
#pragma region CSVRow Iterator
#endif
Expand Down
5 changes: 4 additions & 1 deletion include/internal/csv_row.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ namespace csv {
return static_cast<T>(this->value);
}

/** Parse a hexadecimal value, returning false if the value is not hex. */
bool try_parse_hex(int& parsedValue);

/** Compares the contents of this field to a numeric value. If this
* field does not contain a numeric value, then all comparisons return
* false.
Expand All @@ -228,7 +231,7 @@ namespace csv {
* @sa csv::CSVField::operator==(csv::string_view other)
*/
template<typename T>
CONSTEXPR bool operator==(T other) const noexcept
CONSTEXPR_14 bool operator==(T other) const noexcept
{
static_assert(std::is_arithmetic<T>::value,
"T should be a numeric value.");
Expand Down
18 changes: 13 additions & 5 deletions include/internal/csv_writer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ namespace csv {
csv::enable_if_t<std::is_floating_point<T>::value, int> = 0
>
inline std::string to_string(T value) {
#ifdef __clang__
return std::to_string(value);
#else
// TODO: Figure out why the below code doesn't work on clang
std::string result;

T integral_part;
Expand All @@ -64,8 +68,9 @@ namespace csv {
result = "0";
}
else {
for (short n_digits = log(integral_part) / log(10); n_digits + 1 > 0; n_digits --) {
short digit = std::fmod(integral_part, pow10(n_digits + 1)) / pow10(n_digits);
for (int n_digits = (int)(std::log(integral_part) / std::log(10));
n_digits + 1 > 0; n_digits --) {
int digit = (int)(std::fmod(integral_part, pow10(n_digits + 1)) / pow10(n_digits));
result += (char)('0' + digit);
}
}
Expand All @@ -74,9 +79,9 @@ namespace csv {
result += ".";

if (fractional_part > 0) {
fractional_part *= pow10(DECIMAL_PLACES);
for (short n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
short digit = std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1);
fractional_part *= (T)(pow10(DECIMAL_PLACES));
for (int n_digits = DECIMAL_PLACES; n_digits > 0; n_digits--) {
int digit = (int)(std::fmod(fractional_part, pow10(n_digits)) / pow10(n_digits - 1));
result += (char)('0' + digit);
}
}
Expand All @@ -85,16 +90,19 @@ namespace csv {
}

return result;
#endif
}
}

/** Sets how many places after the decimal will be written for floating point numbers
*
* @param precision Number of decimal places
*/
#ifndef __clang___
inline static void set_decimal_places(int precision) {
internals::DECIMAL_PLACES = precision;
}
#endif

/** @name CSV Writing */
///@{
Expand Down
2 changes: 1 addition & 1 deletion include/internal/data_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ namespace csv {
return DataType::CSV_STRING;
break;
default:
short digit = current - '0';
short digit = static_cast<short>(current - '0');
if (digit >= 0 && digit <= 9) {
// Process digit
has_digit = true;
Expand Down
Loading

0 comments on commit ea547fd

Please sign in to comment.