I've been starting to migrate some C++ code from imperative to more of a functional style. I'm new to functional programming so I'm trying to get a sense of how well this code fits into the functional programming paradigm.
The purpose of the code is to scan tokens from CSV files, and currently passes all the written tests I have.
Here's the header.
#pragma once
#include <cstddef>
/// Enumerates the several types of CSV tokens.
enum class csvTokenType {
/// A null token.
Null,
/// A newline sequence.
Newline,
/// A separator character.
Separator,
/// A quoted cell.
QuotedCell,
/// An unquoted cell.
UnquotedCell
};
/// Contains data belonging to a token.
class csvToken final {
/// The type of this token.
csvTokenType type;
/// The character data belonging to the token.
const char* data;
/// The number of characters in the token.
size_t size;
public:
/// Constructs a null token.
csvToken() noexcept : type(csvTokenType::Null), data(""), size(0) { }
/// Constructs a CSV token.
/// @param t The type to assign the token.
/// @param d The data to assign the token.
/// @param s The size of the data being assigned to the token.
csvToken(csvTokenType t, const char* d, size_t s) noexcept : type(t), data(d), size(s) { }
/// Indicates if this is a valid token or not.
/// @returns True if it's a valid token, false otherwise.
operator bool () const noexcept {
return type != csvTokenType::Null;
}
/// Checks for token type equality.
/// @param otherType The type of token to check for.
/// @returns True if the types are equal, false otherwise.
bool operator == (csvTokenType otherType) const noexcept {
return type == otherType;
}
/// Checks for equality with string data.
/// @param str The string to check.
/// @returns True if the strings are equal, false otherwise.
bool operator == (const char* str) const noexcept;
};
/// Scans for a token.
/// @param data The data to scan.
/// @param size The size of the data to scan.
/// @returns The resultant token.
csvToken csvScan(const char* data, size_t size) noexcept;
Here's the implementation.
#include "csvScan.hpp"
#include <algorithm>
#include <string.h>
namespace {
/// A simple class for quick and safe
/// string scanning.
struct csvStringView final {
/// The string data.
const char* data;
/// The size of the string.
size_t size;
/// Constructs the string view.
csvStringView(const char* d, size_t s) noexcept : data(d), size(s) { }
/// Gets a character at a specified offset.
/// @param i The offset of the character to get.
/// @returns The character at the specified offset.
/// If the offset is out of bounds, then zero is returned instead.
char operator [] (size_t i) const noexcept {
return (i < size) ? data[i] : 0;
}
/// Returns a beginning iterator.
/// @returns The beginning iterator.
const char* begin() const noexcept {
return data;
}
/// Returns the ending iterator.
/// @returns The ending iterator.
const char* end() const noexcept {
return data + size;
}
};
/// Returns the first non-null token
/// generated by one of the functions in the parameter list.
/// @tparam FirstMatcher The type of the first callable token scanner.
/// @tparam OtherMatchers The type of the other callable token scanners.
/// @param strView The string view to scan.
/// @param firstMatcher The first token scanner.
/// @param otherMatchers The other token scanners.
/// @returns The first non-null token from the given matchers.
template <typename FirstMatcher, typename... OtherMatchers>
csvToken csvFirstMatch(const csvStringView& strView, FirstMatcher firstMatcher, OtherMatchers... otherMatchers) {
auto token = firstMatcher(strView);
return token ? token : csvFirstMatch(strView, otherMatchers...);
}
/// The last resort matching function.
/// @tparam LastMatcher The type of the last token matcher.
/// @param strView The string view being scanned.
/// @param lastMatcher The last token matcher.
/// @returns The resultant token.
template <typename LastMatcher>
csvToken csvFirstMatch(const csvStringView& strView, LastMatcher lastMatcher) {
return lastMatcher(strView);
}
/// Indicates if a character is
/// a separator or not.
/// @param c The character to check.
/// @returns True if the character is
/// a separator, false if it is not.
bool csvIsSeparator(char c) noexcept {
return (c == ',') || (c == ';') || (c == '\t');
}
/// Indicates if the specified character
/// can be part of a newline sequence.
/// @param c The character to check.
/// @returns True if the character can
/// be part of a newline sequence, false otherwise.
bool csvIsNewline(char c) noexcept {
return (c == '\n') || (c == '\r');
}
/// Indicates if the specified
/// character can terminate an unquoted cell.
/// @param c The character to check.
/// @returns True if the character can terminate
/// the call, false otherwise.
bool csvIsCellTerminal(char c) noexcept {
return csvIsSeparator(c) || csvIsNewline(c);
}
/// Attempts to scan a separator character.
/// @param strView The string view to scan.
/// @returns The resultant token.
csvToken csvScanSeparator(const csvStringView& strView) noexcept {
bool match = csvIsSeparator(strView[0]);
csvToken goodToken(csvTokenType::Separator, strView.data, 1);
return match ? goodToken : csvToken();
}
/// Attempts to scan a line feed.
/// @param strView The string being scanned.
/// @returns The resultant token.
csvToken csvScanLF(const csvStringView& strView) noexcept {
csvToken goodToken(csvTokenType::Newline, strView.data, 1);
auto match = (strView[0] == '\n');
return match ? goodToken : csvToken();
}
/// Attempts to scan a line feed.
/// @param strView The string being scanned.
/// @returns The resultant token.
csvToken csvScanCRLF(const csvStringView& strView) noexcept {
csvToken goodToken(csvTokenType::Newline, strView.data, 2);
auto match = (strView[0] == '\r') && (strView[1] == '\n');
return match ? goodToken : csvToken();
}
/// Attempts to scan any known newline sequence.
/// @param strView The string being scanned.
/// @returns The resultant token.
csvToken csvScanNewline(const csvStringView& strView) noexcept {
return csvFirstMatch(strView, csvScanCRLF, csvScanLF);
}
/// Scans the contents of an unquoted cell.
/// @param strView The string view to scan.
/// @returns The resultant token.
csvToken csvScanUnquotedCell(const csvStringView& strView) noexcept {
const auto* it = std::find_if(strView.begin(), strView.end(), csvIsCellTerminal);
auto match = (it != strView.end());
csvToken goodToken(csvTokenType::UnquotedCell, strView.begin(), it - strView.begin());
return match ? goodToken : csvToken();
}
/// Scans a string view.
/// @param strView The string view to scan.
/// @returns The resultant token.
csvToken csvScan(const csvStringView& strView) noexcept {
return csvFirstMatch(strView, csvScanSeparator, csvScanNewline, csvScanUnquotedCell);
}
} // namespace
bool csvToken::operator == (const char* str) const noexcept {
size_t strSize = strlen(str);
return (size == strSize) && (memcmp(str, data, strSize) == 0);
}
csvToken csvScan(const char* data, size_t size) noexcept {
csvStringView strView(data, size);
return csvScan(strView);
}