Artifact a5112ff64329e354ff3d5835777cae1b7ad4d7212a36bd20d8925fad8e7a680e:

  • File src/Net.cxx — part of check-in [121229048b] at 2017-04-27 20:23:11 on branch trunk — Net: add `from_stream` static methods. (user: fifr size: 10850)

/*
 * Copyright (c) 2017 Frank Fischer
 *
 * This file is part of KraView.
 *
 * KraView is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * KraView is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with KraView.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "Net.hxx"

#include "Err.hxx"

#include <istream>
#include <vector>
#include <map>
#include <algorithm>
#include <cstdlib>
#include <cstring>

#define xassert_node(nodeid)      \
    if (!(nodeid < d->nodes.size())) \
        throw NetError(std::string("Invalid node id: ") + std::to_string(nodeid));

#define xassert_arc(arcid)    \
    if (!(/*(arcid) >= 0 &&*/ (arcid) < d->arcs.size())) \
        throw NetError(std::string("Invalid arc id: ") + std::to_string(arcid));


namespace Grav
{


struct Net::Data {
    std::vector<Node> nodes;
    std::vector<Arc> arcs;
    std::map<size_type, Net::NodeID> nodes_by_number;
};

Net::Net():
    d(new Data)
{}

Net::Net(const Net& net) :
    d(new Data(*net.d))
{}

Net::~Net() {}

size_type Net::n_nodes() const
{
    return d->nodes.size();
}

Net::NodeID Net::add_node(size_type nodenumber, const Node& node)
{
    NodeID nodeid = d->nodes.size();
    auto ret =  d->nodes_by_number.insert({nodenumber, nodeid});
    if (ret.second) {
        d->nodes.push_back(node);
    } else {
        nodeid = ret.first->second;
        d->nodes[nodeid] = node;
    }
    return nodeid;
}

Node& Net::node(NodeID nodeid)
{
    xassert_node(nodeid);
    return d->nodes[nodeid];
}

Net::NodeID Net::nodeid(size_type nodenumber) const
{
    auto it = d->nodes_by_number.find(nodenumber);
    return it != d->nodes_by_number.end() ? it->second : None;
}

size_type Net::n_arcs() const
{
    return d->arcs.size();
}

Net::ArcID Net::add_arc(const Arc& arc)
{
    xassert_node(arc.src);
    xassert_node(arc.snk);
    d->arcs.push_back(arc);
    return d->arcs.size() - 1;
}


Arc& Net::arc(ArcID arcid)
{
    xassert_arc(arcid);
    return d->arcs[arcid];
}


static void parse_uint(const char* line, unsigned& n)
{
    if (sscanf(line, "%u", &n) != 1)
        throw ReadError(std::string("Expected number, got '") + line + "'");
}


static void parse_dbl(const char* line, double& dbl)
{
    if (sscanf(line, "%lf", &dbl) != 1)
        throw ReadError(std::string("Expected number, got '") + line + "'");
}


static void parse_color(const char* line, Color& color)
{
    unsigned r, g, b;
    double alpha;
    if (sscanf(line, "%u,%u,%u,%lg", &r, &g, &b, &alpha) == 4) {
        if (alpha < 1e-6 || alpha > 1.0 + 1e-6) {
            throw ReadError(std::string("Alpha value outside [0,1], got: '")
                            + std::to_string(alpha) + "'");
        }
        color = Color {
            (unsigned char)r,
            (unsigned char)g,
            (unsigned char)b,
            std::max(std::min(alpha, 1.0), 0.0)
        };
    } else if (sscanf(line, "%u,%u,%u", &r, &g, &b) == 3) {
        color = Color {
            (unsigned char)r,
            (unsigned char)g,
            (unsigned char)b,
            1.0
        };
    } else {
        throw ReadError(std::string("Invalid RGB color: ") + line);
    }
}


static void parse_node_options(Node& node, const char* line, unsigned& desclen)
{
    char option[11], sep[2];
    int len, optpos;
    desclen = 0;
    for (;;) {
        while (isspace(*line)) line++;
        if (*line == '\0') break;
        if (sscanf(line, "%10[a-z]%1[:]%n%*s%n",
                   option, sep, &optpos, &len) >= 2) {
            /*
             * an option with parameter
             */
            if (std::strcmp(option, "x") == 0) {
                parse_dbl(line + optpos, node.x);
            } else if (strcmp(option, "y") == 0) {
                parse_dbl(line + optpos, node.y);
            } else if (strcmp(option, "weight") == 0) {
                parse_dbl(line + optpos, node.weight);
            } else if (strcmp(option, "color") == 0) {
                parse_color(line + optpos, node.color);
            } else if (strcmp(option, "desc") == 0) {
                parse_uint(line + optpos, desclen);
            } else {
                throw ReadError(std::string("Unknown node option: ") + option);
            }
        } else if (sscanf(line, "%10s %n", option, &len) >= 1)  {
            if (strcmp(option, "circ") == 0) {
                node.type = NTCircle;
            } else if (strcmp(option, "disc") == 0)  {
                node.type = NTDisc;
            } else {
                throw ReadError(std::string("Unknown node option: ") + option);
            }
        } else {
            throw ReadError(std::string("Invalid node option: ") + line);
        }

        line += len;
    }
}


static Net::NodeID parse_node(Net& net, const char* line, Node& defaultnode, unsigned& desc)
{
    Net::NodeID nodeid = Net::None;
    int nodenr = -1, len;
    desc = 0;
    if (sscanf(line, "%d %n", &nodenr, &len) >= 1) {
        nodeid = net.add_node(nodenr, defaultnode);
        Node& node = net.node(nodeid);
        if (line[len] != '\0') parse_node_options(node, line + len, desc);
    } else {
        parse_node_options(defaultnode, line, desc);
        if (desc > 0)
            throw ReadError("Description not allowed for default options");
    }
    return nodeid;
}


static void parse_arc_options(Arc& arc, const char* line, unsigned& desclen)
{
    char option[11], sep[2];
    int len, optpos;
    for (;;) {
        while (isspace(*line)) line++;
        if (*line == '\0') break;
        if (sscanf(line, "%10[a-z]%1[:]%n%*s%n",
                   option, sep, &optpos, &len) >= 2) {
            if (strcmp(option, "flow") == 0) {
                parse_dbl(line + optpos, arc.flow);
            } else if (strcmp(option, "cost") == 0) {
                parse_dbl(line + optpos, arc.cost);
            } else if (strcmp(option, "color") == 0) {
                parse_color(line + optpos, arc.color);
            } else if (strcmp(option, "desc") == 0) {
                parse_uint(line + optpos, desclen);
            } else {
                throw ReadError(std::string("Unknown arc option: ") + option);
            }
        } else {
            throw ReadError(std::string("Invalid arc option: ") + option);
        }
        line += len;
    }
}


static Net::ArcID parse_arc(Net& net, const char* line, Arc& defaultarc, unsigned& desc)
{
    int srcnr, snknr;
    int len;
    Net::ArcID arcid = Net::None;
    desc = 0;
    if (sscanf(line, "%d%*[ \t]%d%n", &srcnr, &snknr, &len) >= 2) {

        try {
            defaultarc.src = net.nodeid(srcnr);
            defaultarc.snk = net.nodeid(snknr);
            arcid = net.add_arc(defaultarc);
        } catch (NetError& e) {
            throw ReadError(std::string("Cannot add arc: ") + e.what());
        }
        Arc& arc = net.arc(arcid);
        parse_arc_options(arc, line + len, desc);
    } else {
        parse_arc_options(defaultarc, line, desc);
        if (desc > 0)
            throw ReadError("Description not allowed for default options");
    }
    return arcid;
}


static void parse_desc(std::istream& in, unsigned desclen, std::string& desc)
{
    desc.resize(desclen);
    in.read(&(desc[0]), desclen);
    if (in.gcount() < desclen)
        throw ReadError("Too few bytes for description data");
}


Net Net::from_stream(std::istream& in, Net net)
{
    size_type lineno = 0;
    return Net::from_stream(in, lineno, std::move(net));
}


Net Net::from_stream(std::istream& in, size_type& lineno, Net net)
{
    Node defaultnode;
    defaultnode.x = 0;
    defaultnode.y = 0;
    defaultnode.color = Color {0, 0, 0, 1.0};
    defaultnode.type = NTCircle;
    defaultnode.weight = 0;

    Arc defaultarc;
    defaultarc.src = 0;
    defaultarc.snk = 0;
    defaultarc.color = Color {0, 0, 0, 1.0};
    defaultarc.flow = 0;
    defaultarc.cost = 0;

    std::string line;
    char* locale = setlocale(LC_NUMERIC, "C");
    try {
        while (in.good()) {
            std::getline(in, line);
            lineno += 1;

            std::string::size_type pos = line.find('#');
            if (pos != std::string::npos) pos--;
            pos = line.find_last_not_of(" \t\r");
            if (pos == std::string::npos) continue; // empty line
            line.erase(pos + 1, std::string::npos);
            pos = line.find_first_not_of(" \t\r");
            const char* ptr = line.c_str() + pos;

            char sel[5];
            int len;
            if (sscanf(ptr, "%4[a-z]%*[ \t]%n", sel, &len) == 1) {
                unsigned desclen;
                if (strcmp(sel, "node") == 0) {
                    NodeID nodeid = parse_node(net, ptr + len, defaultnode, desclen);
                    if (desclen > 0) {
                        Node& n = net.node(nodeid);
                        parse_desc(in, desclen, n.desc);
                        lineno += std::count(n.desc.begin(), n.desc.end(), '\n');
                    }
                } else if (strcmp(sel, "arc") == 0 || strcmp(sel, "edge") == 0) {
                    ArcID arcid = parse_arc(net, ptr + len, defaultarc, desclen);
                    if (arcid != Net::None && strcmp(sel, "edge") == 0) {
                        net.arc(arcid).directed = false;
                    }
                    if (desclen > 0) {
                        Arc& a = net.arc(arcid);
                        parse_desc(in, desclen, a.desc);
                        lineno += std::count(a.desc.begin(), a.desc.end(), '\n');
                    }
                } else if (strcmp(sel, "end") == 0) {
                    break;
                } else
                    throw ReadError(std::string("Unknown command: ") + sel);
            } else {
                throw ReadError("Expected 'arc', 'edge' or 'node'");
            }
        }
        if (!in) throw ReadError("Unexpected IO error");
    } catch (ReadError& e) {
        e.set_line(lineno);
        setlocale(LC_NUMERIC, locale);
        throw;
    } catch (...) {
        setlocale(LC_NUMERIC, locale);
        throw;
    }
    setlocale(LC_NUMERIC, locale);

    return net;
}


void Net::read(std::istream& in, size_type& lineno, bool append)
{
    if (!append) {
        d->nodes.clear();
        d->arcs.clear();
    }

    *this = std::move(from_stream(in, lineno, std::move(*this)));
}

void Net::read(std::istream& in, bool append)
{
    size_type lineno = 0;
    read(in, lineno, append);
}

}