1
\$\begingroup\$

I have written the following numeric parser. It may not be as fast as Boost's lexical_cast, nor as convenient, but it does get the job done and it does look good. I'm very proud of the end result, and I believe it may be useful for some! How can I improve it even more?

#include <iostream>
#include <sstream>
#include <string>

using std::cout;
using std::endl;

enum class tag {
    INTEGER,
    BIG_INTEGER,
    DOUBLE,
    ERROR
};

template <typename t>
struct result {
    bool const ok;
    t const value;
};

tag is_number(std::string const & s);
void print_tag(tag const t);

result<int64_t> parse_int(std::string const & s);
result<double> parse_double(std::string const & s);

int main() {

    auto x {parse_int("4")};
    auto y {parse_double(".8")};
    cout << x.value << '\n';
    cout << y.value << '\n';

    auto z {parse_int("abc")};
    if (z.ok) {
        cout << z.value << '\n';
    } else {
        cout << "ERROR" << '\n';
    }

    print_tag(is_number("9223372036854775807"));
    print_tag(is_number("9223372036854775808"));

    print_tag(is_number("-9223372036854775808"));
    print_tag(is_number("-9223372036854775809"));

    print_tag(is_number("    "));

    return 0;
}

tag is_number(std::string const & s) {
    if (s.empty()) {
        return tag::ERROR;
    }
    if (s.size() == 1 && (s[0] == '.' || s[0] == '+' || s[0] == '-')) {
        return tag::ERROR;
    }
    size_t index {s.find_first_not_of(" \t\n\v\f\r")};
    if (index == std::string::npos) {
        return tag::ERROR;
    }
    bool is_int {true};
    bool is_dot {true};
    bool is_pos {true};
    bool is_eul {true};
    switch (s[index]) {
        case '.': is_int = !is_int; is_dot = !is_dot; ++index; break;
        case '+':
        case '-': is_pos = !is_pos; ++index;
    }
    for (size_t i {index}; i < s.size(); ++i) {
        if (isspace(s[i])) {
            if (s[i - 1] == '.' || s[i - 1] == '+' || s[i - 1] == '-') {
                return tag::ERROR;
            }
            break;
        }
        if (s[i] == '.') {
            if (!is_dot) {
                return tag::ERROR;
            }
            is_int = !is_int;
            is_dot = !is_dot;
            continue;
        }
        if (s[i] == 'e' || s[i] == 'E') {
            if (!is_eul) {
                return tag::ERROR;
            }
            is_eul = !is_eul;
            if (i + 1 == s.size()) {
                return tag::ERROR;
            }
            ++i;
            if (s[i] == '+' || s[i] == '-') {
                if (i + 1 == s.size()) {
                    return tag::ERROR;
                }
                continue;
            }
            if (!isdigit(s[i])) {
                return tag::ERROR;
            }
            continue;
        }
        if (!isdigit(s[i])) {
            return tag::ERROR;
        }
    }
    return is_int
        ? s.compare(index, 19, is_pos
                ? "9223372036854775808"
                : "9223372036854775809") < 0
            ? tag::INTEGER
            : tag::BIG_INTEGER
        : tag::DOUBLE;
}

void print_tag(tag const t) {
    switch (t) {
        case tag::INTEGER: cout << "INTEGER\n"; break;
        case tag::BIG_INTEGER: cout << "BIG INTEGER\n"; break;
        case tag::DOUBLE: cout << "DOUBLE\n"; break;
        case tag::ERROR: cout << "ERROR\n"; break;
    }
}

result<int64_t> parse_int(std::string const & s) {
    if (is_number(s) == tag::INTEGER) {
        std::istringstream input {s};
        int64_t output;
        input >> output;
        return {true, output};
    }
    return {false, 0};
}

result<double> parse_double(std::string const & s) {
    tag t {is_number(s)};
    if (t == tag::INTEGER || t == tag::DOUBLE) {
        std::istringstream input {s};
        double output;
        input >> output;
        return {true, output};
    }
    return {false, 0.0};
}
\$\endgroup\$
  • 3
    \$\begingroup\$ I don't mean to be a spoil-sport, but if it isn't as good, as fast, or as convenient as Boost's lexical_cast, what is the advantage? Have you done this just as a learning exercise, or do you plan to use it in real code? \$\endgroup\$ – Cody Gray Jan 3 '17 at 6:20
  • \$\begingroup\$ I did it for a small numeric validator / parser for a simple interpreted language that I'm doing. \$\endgroup\$ – João Pires Jan 3 '17 at 12:21

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.