June 22, 2019
The following code using a x86 C++/D project and a BP in the save routine produces "Unexpected symbol reader error" and the BP won't work. In x64 it works and is hit without problem.


module main;

import std.stdio;
import std.format;

import dgraphviz;

struct A {
    auto toString() {
        return "A\n\"struct\"";
    }
}

void main()
{
    auto g = new Directed;
    A a;
    with (g) {
        node(a, ["shape": "box", "color": "#ff0000"]);
        edge(a, true);
        edge(a, 1, ["style": "dashed", "label": "a-to-1"]);
        edge(true, "foo");
    }
    g.save("simple.dot");
}




module dgraphviz;

import std.format : format;

string GraphVizBin = `C:\GraphViz\bin\`;

struct Option {
    string[string] option;
    alias option this;

    auto toString() {
        if (option.length == 0) return "";
        auto s = " [ ";
        foreach (k, v; option) {
            s ~= "%s = \"%s\", ".format(k, v);
        }
        s = s[0 .. $-2] ~ " ]";
        return s;
    }
}

private struct Edge {
    string ark;
    Node src, dst;
    Option option;

    auto toString() {
        return "\"%s\" %s \"%s\" %s;\n".format(src.label, ark, dst.label, option);
    }
}

private class Node {
    string label;
    Option option;
    size_t nIn = 0, nOut = 0;

    this(string label) {
        import std.string : replace;
        this.label = label.replace("\"", "\\\"");
    }

    this(string label, Option option) {
        this(label);
        this.option = option;
    }

    auto info() {
        if (option.length == 0) return "";
        auto s = "\"%s\" %s;\n".format(label, option);
        return s;
    }
}


abstract class Graph {
    import std.conv : to;

    // TODO use Set
    Node[string] nodes;
    Edge[string] edges;
    Option graphOpt, nodeOpt, edgeOpt;

    ref auto node(ref Node d) { return d; }

    ref auto node(T)(T t) {
        string[string] opt;
        return node(t, opt);
    }

    ref auto node(T)(T t, string[string] option) {
        auto key = t.to!string;
        if (key !in this.nodes) {
            this.nodes[key] = new Node(t.to!string, Option(option));
        }
        return this.nodes[key];
    }

    auto edge(S, D)(S src, D dst,) {
        string[string] opt;
        return edge(src, dst, opt);
    }

    auto edge(S, D)(S src, D dst, string[string] option) {
        auto s = node(src);
        auto d = node(dst);
        auto e = Edge(this.ark, s, d, Option(option));
        ++s.nOut;
        ++d.nIn;
        this.edges[e.to!string] = e;
        return e;
    }

    abstract string typename();
    abstract string ark();

    override string toString() {
        import std.array : array;
        import std.algorithm : uniq, map, sort;
        auto s = this.typename ~ " g{\n";

        if (graphOpt.length > 0) s ~= "graph %s;\n".format(graphOpt);
        if (nodeOpt.length > 0) s ~= "node %s;\n".format(nodeOpt);
        if (edgeOpt.length > 0) s ~= "edge %s;\n".format(edgeOpt);

        foreach (k, n; this.nodes) {
            s ~= n.info;
        }
        foreach (k, e; this.edges) {
            s ~= k;
        }
        s ~= "}\n";
        return s;
    }

    void save(string fn) {
        import std.stdio : File;
	import std.process;
        auto f = File(fn, "w"); // BP here fails.

        f.write(this.toString());
	auto o = executeShell(GraphVizBin~`dot.exe `~fn~` -Tpng -O`);
        f.detach();
    }
}

class Undirected : Graph {
    override string typename() { return "graph"; }
    override string ark() { return "--"; }
}

class Directed : Graph {
    override string typename() { return "digraph"; }
    override string ark() { return "->"; }
}


///
unittest {
    import std.stdio;
    import std.format;
    import dgraphviz;

    struct A {
        auto toString() {
            return "A\n\"struct\"";
        }
    }

    auto g = new Directed;
    A a;
    with (g) {
        node(a, ["shape": "box", "color": "#ff0000"]);
        edge(a, true);
        edge(a, 1, ["style": "dashed", "label": "a-to-1"]);
        edge(true, "foo");
    }
    g.save("simple.dot");
}

Directed libraryDependency(string root, string prefix="",
                           bool verbose=false, size_t maxDepth=3) {
    import std.file : dirEntries, SpanMode, readText;
    import std.format : formattedRead;
    import std.string : split, strip, join, endsWith, replace, startsWith;
    import std.algorithm : map, canFind, min, any, filter;
    import std.stdio : writefln;

    auto g = new Directed;

    with (g) {
        enum invalidTokens = ["\"", "$", "/", "\\"];
        auto removeSub(string s) {
            return s.split(".")[0..min($, maxDepth)].join(".");
        }

        void registerEdge(string src, string dst) {
            dst = dst.strip;
            // FIXME follow import expr spec.
            if (invalidTokens.map!(i => dst.canFind(i)).any) {
                return;
            } else if (dst.canFind(":")) {
                registerEdge(src, dst.split(":")[0]);
            } else if (dst.canFind(",")) {
                foreach (d; split(dst, ",")) {
                    registerEdge(src, d);
                }
            } else if (dst.canFind(" ")) {
                return;
            } else if (dst.canFind("std.")) {
                if (verbose) writefln("%s -> %s", src, dst);
                edge(removeSub(src), removeSub(dst));
            }
        }

        auto dfiles = dirEntries(root, SpanMode.depth)
            .filter!(f => f.name.startsWith(root ~ prefix) && f.name.endsWith(".d"));
        foreach (dpath; dfiles) {
            auto src = dpath[root.length .. $].replace("/", ".")[0 .. $-2];
            try {
                foreach (txt; dpath.readText.split("import")[1..$]) {
                    txt = "import " ~ txt;
                    string dst, rest;
                    txt.formattedRead!"import %s;%s"(dst, rest);
                    if (verbose) writefln("%s ---------> %s", src, dst);
                    registerEdge(src, dst);
                }
            } catch (Exception e) {
                // FIXME display warnings
            }
        }
    }
    return g;
}

///
unittest {
    import std.path;
    import std.process;

    auto dc = environment.get("DC");
    assert(dc != "", "use DUB or set DC enviroment variable");
    auto which = executeShell("which " ~ dc);
    assert(which.status == 0);
    version(DigitalMars) {
        auto root = which.output.dirName ~ "/../../src/phobos/";
    }
    version(LDC) {
        auto root = which.output.dirName ~ "/../import/";
    }

    auto g = libraryDependency(root, "std/range", true);
    g.save("range.dot");
}

June 23, 2019

On 22/06/2019 15:11, Bart wrote:
> The following code using a x86 C++/D project and a BP in the save routine produces "Unexpected symbol reader error" and the BP won't work. In x64 it works and is hit without problem.
> 

Thanks for providing a working example. It doesn't produce the described issue here, though.

What versions of dmd/VS/Visual D are you using?

Maybe the PDB file in the output folder is corrupted in some way, try deleting it.