]> granicus.if.org Git - graphviz/commitdiff
Add mechanism so that a program using graphivz can capture error and
authorerg <devnull@localhost>
Sat, 19 Feb 2011 20:21:59 +0000 (20:21 +0000)
committererg <devnull@localhost>
Sat, 19 Feb 2011 20:21:59 +0000 (20:21 +0000)
warning messages;
remove unused extern variable agerrno;
have agseterr return the previous error level.

lib/cgraph/agerror.c
lib/cgraph/cgraph.3
lib/cgraph/cgraph.h
lib/graph/graph.h
lib/graph/lexer.c

index 12236e7b5103e73e863797f1105b723d6de3dc15..047c42aaeab31394e716641a4858f5548b08216f 100644 (file)
 #include <stdio.h>
 #include <cghdr.h>
 
-agerrlevel_t agerrno;          /* Last error */
+#define MAX(a,b)       ((a)>(b)?(a):(b))
+static agerrlevel_t agerrno;           /* Last error level */
 static agerrlevel_t agerrlevel = AGWARN;       /* Report errors >= agerrlevel */
 static int agerrcnt;
 
 static long aglast;            /* Last message */
 static FILE *agerrout;         /* Message file */
+static agusererrf usererrf;     /* User-set error function */
 
-void agseterr(agerrlevel_t lvl)
+agusererrf
+agseterrf (agusererrf newf)
 {
+    agusererrf oldf = usererrf;
+    usererrf = newf;
+    return oldf;
+}
+
+agerrlevel_t agseterr(agerrlevel_t lvl)
+{
+    agerrlevel_t oldv = agerrlevel;
     agerrlevel = lvl;
+    return oldv;
 }
 
 char *aglasterr()
@@ -46,20 +58,71 @@ char *aglasterr()
     return buf;
 }
 
+/* userout:
+ * Report messages using a user-supplied write function 
+ */
+static void
+userout (agerrlevel_t level, const char *fmt, va_list args)
+{
+    static char* buf;
+    static int bufsz = 1024;
+    char* np;
+    int n;
+
+    if (!buf) {
+       buf = (char*)malloc(bufsz);
+       if (!buf) {
+           fputs("userout: could not allocate memory\n", stderr );
+           return;
+       }
+    }
+
+    if (level != AGPREV) {
+       usererrf ((level == AGERR) ? "Error" : "Warning");
+       usererrf (": ");
+    }
+
+    while (1) {
+       n = vsnprintf(buf, bufsz, fmt, args);
+       if ((n > -1) && (n < bufsz)) {
+           usererrf (buf);
+           break;
+       }
+       bufsz = MAX(bufsz*2,n+1);
+       if ((np = (char*)realloc(buf, bufsz)) == NULL) {
+           fputs("userout: could not allocate memory\n", stderr );
+           return;
+       }
+    }
+    va_end(args);
+}
+
 static int agerr_va(agerrlevel_t level, char *fmt, va_list args)
 {
     agerrlevel_t lvl;
 
+    /* Use previous error level if continuation message;
+     * Convert AGMAX to AGERROR;
+     * else use input level
+     */
     lvl = (level == AGPREV ? agerrno : (level == AGMAX) ? AGERR : level);
 
-       agerrcnt++;
+    agerrcnt++;
+    /* store this error level */
     agerrno = lvl;
+
+    /* We report all messages whose level is bigger than the user set agerrlevel
+     * Setting agerrlevel to AGMAX turns off immediate error reporting.
+     */
     if (lvl >= agerrlevel) {
-       if (level != AGPREV)
-           fprintf(stderr, "%s: ",
-                   (level == AGERR) ? "Error" : "Warning");
-       vfprintf(stderr, fmt, args);
-       va_end(args);
+       if (usererrf)
+           userout (level, fmt, args);
+       else {
+           if (level != AGPREV)
+               fprintf(stderr, "%s: ", (level == AGERR) ? "Error" : "Warning");
+           vfprintf(stderr, fmt, args);
+           va_end(args);
+       }
        return 0;
     }
 
index 70ab62ba1a49b041c0f22c822cee623b85caa393..5508f1681489d2a7350d424be98db28e232e0e48 100644 (file)
@@ -128,6 +128,19 @@ Agrec_t            *AGDATA(void *obj);
 ulong          AGID(void *obj);
 int                    AGTYPE(void *obj);
 .P1
+.SS "ERROR REPORTING"
+.P0
+typedef enum { AGWARN, AGERR, AGMAX, AGPREV } agerrlevel_t;
+typedef int (*agusererrf) (char*);
+agerrlevel_t agerrno;
+agerrlevel_t agseterr(agerrlevel_t);
+char *aglasterr(void);
+int agerr(agerrlevel_t level, char *fmt, ...);
+void agerrorf(char *fmt, ...);
+void agwarningf(char *fmt, ...);
+int agerrors(void);
+agusererrf agseterrf(agusererrf);
+.P1
 .SH "DESCRIPTION"
 Libcgraph supports graph programming by maintaining graphs in memory
 and reading and writing graph files.
@@ -140,7 +153,7 @@ All of Libcgraph's global symbols have the prefix \fBag\fR (case varying).
 .PP
 A ``main'' or ``root'' graph defines a namespace for a collection of
 graph objects (subgraphs, nodes, edges) and their attributes.
-Objects may be named by unique strings or by 32-bit IDs.
+Objects may be named by unique strings or by integer IDs.
 .PP
 \fBagopen\fP creates a new graph with the given name and kind.
 (Graph kinds are \fBAgdirected\fP, \fBAgundirected\fP,
@@ -191,7 +204,7 @@ a graph can be deleted by atomically freeing its entire heap
 without scanning each individual node and edge.
 .SH "NODES"
 A node is created by giving a unique string name or
-programmer defined 32-bit ID, and is represented by a
+programmer defined integer ID, and is represented by a
 unique internal object. (Node equality can checked
 by pointer comparison.)
 .PP
@@ -201,7 +214,7 @@ If not found, if \fBcreateflag\fP is boolean true
 a new node is created and returned, otherwise a nil
 pointer is returned.
 \fBagidnode\fP allows a programmer to specify the node
-by a unique 32-bit ID.
+by a unique integer ID.
 \fBagsubnode\fP performs a similar operation on
 an existing node and a subgraph.
 .PP
@@ -226,7 +239,7 @@ a new edge is created and returned: otherwise
 a nil pointer is returned.  If the \fBname\fP 
 is NULL, then an anonymous internal
 value is generated. \fBagidedge\fP allows a programmer
-to create an edge by giving its unique 32-bit ID.
+to create an edge by giving its unique integer ID.
 \fBagfstin\fP, \fBagnxtint\fP, \fBagfstout\fP, and 
 \fBagnxtout\fP visit directed in- and out- edge lists,
 and ordinarily apply only in directed graphs.
@@ -314,12 +327,14 @@ and edges for efficient operations on values such as marks, weights,
 counts, and pointers needed by algorithms.  Application programmers
 define the fields of these records, but they must be declared with
 a common header as shown below.
+.PP
 .P0
 typedef struct Agrec_s {
     Agrec_t            header;
     /* programmer-defined fields follow */
 } Agrec_t;
 .P1
+.PP
 Records are created and managed by Libcgraph. A programmer must
 explicitly attach them to the objects in a graph, either to
 individual objects one at a time via \fBagbindrec\fP, or to
@@ -359,6 +374,7 @@ from other functions also using the move-to-front convention.
 Programmer-defined disciplines customize certain resources-
 ID namespace, memory, and I/O - needed by Libcgraph.
 A discipline struct (or NIL) is passed at graph creation time.
+.PP
 .P0
 struct Agdisc_s {                      /* user's discipline */
        Agmemdisc_t                     *mem;
@@ -366,11 +382,12 @@ struct Agdisc_s {                 /* user's discipline */
        Agiodisc_t                      *io;
 } ;
 .P1
+.PP
 A default discipline is supplied when NIL is given for
 any of these fields.
 
 An ID allocator discipline allows a client to control assignment
-of IDs (uninterpreted 32-bit values) to objects, and possibly how
+of IDs (uninterpreted integer values) to objects, and possibly how
 they are mapped to and from strings.
 
 .P0
@@ -383,26 +400,26 @@ struct Agiddisc_s {               /* object ID allocator */
        void    (*close)(void *state);
 } ;
 .P1
-
-\f5open\fP permits the ID discipline to initialize any data
+.PP
+\fIopen\fP permits the ID discipline to initialize any data
 structures that maintains per individual graph.
 Its return value is then passed as the first argument to
 all subsequent ID manager calls.
-
-\f5alloc\fP informs the ID manager that Libcgraph is attempting
+.PP
+\fIalloc\fP informs the ID manager that Libcgraph is attempting
 to create an object with a specific ID that was given by a client.
 The ID manager should return TRUE (nonzero) if the ID can be
 allocated, or FALSE (which aborts the operation).
-
-\f5free\fP is called to inform the ID manager that the
+.PP
+\fIfree\fP is called to inform the ID manager that the
 object labeled with the given ID is about to go out of existence.
-
-\f5map\fP is called to create or look-up IDs by string name
+.PP
+\fImap\fP is called to create or look-up IDs by string name
 (if supported by the ID manager).  Returning TRUE (nonzero)
 in all cases means that the request succeeded (with a valid
 ID stored through \f5result\fP.  There are four cases:
 .PP
-\f5name != NULL\fP and \f5createflag == 1\fP:
+\fIname != NULL\fP and \f5createflag == 1\fP:
 This requests mapping a string (e.g. a name in a graph file) into a new ID.
 If the ID manager can comply, then it stores the result and returns TRUE.
 It is then also responsible for being able to \f5print\fP the ID again
@@ -419,7 +436,7 @@ This is a namespace probe.  If the name was previously mapped into
 an allocated ID by the ID manager, then the manager must return this ID.
 Otherwise, the ID manager may either return FALSE, or may store
 any unallocated ID into result. (This is convenient, for example,
-if names are known to be digit strings that are directly converted into 32 bit values.)
+if names are known to be digit strings that are directly converted into integer values.)
 .PP
 \f5name == NULL\fP and \f5createflag == 0\fP: forbidden.
 .PP
@@ -448,6 +465,64 @@ struct Agmemdisc_s {       /* memory allocator */
 } ;
 .P1
 
+.P0
+.SH "GENERIC OBJECTS"
+\fBagroot\fP takes any graph object (graph, subgraph, node, edge) and returns
+the root graph in which it lives. \fBagraphof\fP does the same, except it 
+is the identity function on graphs and subgraphs. Note that there is no 
+function to return the least subgraph containing an object, in part because 
+this is not well-defined as nodes and edges may be in incomparable subgraphs.
+.PP
+\fBagcontains(\fIg\fP,\fIobj\fP)\fP returns non-zero if \fIobj\fP is a member 
+of (sub)graph \fIg\fP. \fBagdelete(\fIg\fP,\fIobj\fP)\fP is equivalent 
+to \fBagclose\fP, \fBagdelnode\fP, and \fBagclose\fP for \fIobj\fP being a 
+graph, node or edge, respectively. It returns -1 if \fIobj\fP does not 
+belong to \fIg\fP.
+.PP
+\fBagnameof\fP returns a string descriptor for the object. It returns the name
+of the node or graph, and the key of an edge. 
+\fBagobjkind\fP is a synonym for \fBAGTYPE\fP.
+.PP
+\fBAGDATA\fP, \fBAGID\fP, and \fBAGTYPE\fP are macros returning the specified
+fields of the argument object. The first is described in the \fBRECORDS\fP
+section above. The second returns the unique integer ID associated with
+the object. The last returns \fBAGRAPH\fP, \fBAGNODE\fP, and \fBAGEDGE\fP
+depending on the type of the object.
+
+typedef int (*agusererrf) (char*);
+agusererrf agseterrf(agusererrf);
+.SH "ERROR REPORTING"
+The library provides a variety of mechanisms to control the reporting
+of errors and warnings. At present, there are basically two types of
+messages: warnings and errors. A message is only written if its
+type has higher priority than a programmer-controlled minimum, which is
+\fBAGWARN\fP by default. The programmer can set this value using
+\fBagseterr\fP, which returns the previous value. Calling
+\fBagseterr(AGMAX)\fP turns off the writing of messages. 
+.PP
+The function \fBagerr\fP if the main entry point for reporting an
+anomaly. The first argument indicates the type of message. Usually,
+the first argument in \fBAGWARN\fP or \fBAGERR\fP to indicate warnings
+and errors, respectively. Sometimes additional context information is
+only available in functions calling the function where the error is
+actually caught. In this case, the calling function can indicate that
+it is continuing the current error by using \fBAGPREV\fP as the first
+argument. The remaining arguments to \fBagerr\fP are the same as
+the arguments to \fBprintf\fP. 
+.PP
+The functions \fBagwarningf\fP and \fBagerrorf\fP are shorthand for
+\fBagerr(AGERR,...)\fP and \fBagerr(AGWARN,...)\fP, respectively.
+.PP
+Some applications desire to directly control the writing of messages.
+Such an application can use the function \fBagseterrf\fP to register
+the function that the library should call to actually write the message.
+The previous error function is returned. By default, the message is
+written to \fBstderr\fP.
+.PP
+Errors not written are stored in a log file. The last recorded error
+can be retreived by calling \fBaglasterr\fP.
+.PP
+The function \fBagerrors\fP returns non-zero if errors have been reported. 
 .SH "EXAMPLE PROGRAM"
 .P0
 #include <graphviz/cgraph.h>
index 5b5938662558ae3d1eaad640d6c3609c074c0e64..be7aa027ac51d85dd7c1eb7c633f3238295c6857 100644 (file)
@@ -388,13 +388,14 @@ extern void aginternalmapclearlocalnames(Agraph_t * g);
 
 /* error handling */
 typedef enum { AGWARN, AGERR, AGMAX, AGPREV } agerrlevel_t;
-extern agerrlevel_t agerrno;
-extern void agseterr(agerrlevel_t);
+typedef int (*agusererrf) (char*);
+extern agerrlevel_t agseterr(agerrlevel_t);
 extern char *aglasterr(void);
 extern int agerr(agerrlevel_t level, char *fmt, ...);
 extern void agerrorf(char *fmt, ...);
 extern void agwarningf(char *fmt, ...);
 extern int agerrors(void);
+extern agusererrf agseterrf(agusererrf);
 
 /* data access macros */
 /* this assumes that e[0] is out and e[1] is inedge, see edgepair in edge.c  */
index 38e2994edae80eee5b7c00486ed806f85d182309..0e656ccb3425a1b9db3b8a343e4cfa6a5fa1051e 100644 (file)
@@ -209,12 +209,14 @@ extern "C" {
     extern int      agcopyattr(void *, void *);        
 
     typedef enum { AGWARN, AGERR, AGMAX, AGPREV } agerrlevel_t;
+    typedef int (*agusererrf) (char*);
     extern agerrlevel_t agerrno;
     extern void agseterr(agerrlevel_t);
     extern char *aglasterr(void);
     extern int agerr(agerrlevel_t level, char *fmt, ...);
     extern void agerrorf(const char *fmt, ...);
     extern void agwarningf(char *fmt, ...);
+    extern agusererrf agseterrf(agusererrf);
 
     extern char *agstrdup(char *);
     extern char *agstrdup_html(char *s);
index 94206526b9e3d853bf9901829a76e9b54003aca1..bab94b3f44d56e1ab861d163d4ab6ab72529dcb0 100644 (file)
@@ -13,6 +13,7 @@
 
 
 #include <stdarg.h>
+#include <stdlib.h>
 #include "libgraph.h"
 #include "parser.h"
 #include "triefa.cP"
@@ -483,6 +484,15 @@ agerrlevel_t agerrno;              /* Last error */
 static agerrlevel_t agerrlevel = AGWARN;       /* Report errors >= agerrlevel */
 static long aglast;            /* Last message */
 static FILE *agerrout;         /* Message file */
+static agusererrf usererrf;     /* User-set error function */
+
+agusererrf 
+agseterrf (agusererrf newf)
+{
+    agusererrf oldf = usererrf;
+    usererrf = newf;
+    return oldf;
+}
 
 void agseterr(agerrlevel_t lvl)
 {
@@ -514,23 +524,75 @@ char *aglasterr()
     return buf;
 }
 
+static void
+userout (agerrlevel_t level, const char *fmt, va_list args)
+{
+    static char* buf;
+    static int bufsz = 1024;
+    char* np;
+    int n;
+
+    if (!buf) {
+       buf = (char*)malloc(bufsz);
+       if (!buf) {
+           fputs("userout: could not allocate memory\n", stderr );
+           return;
+       }
+    }
+
+    if (level != AGPREV) {
+       usererrf ((level == AGERR) ? "Error" : "Warning");
+       usererrf (": ");
+    }
+
+    while (1) {
+       n = vsnprintf(buf, bufsz, fmt, args);
+       if ((n > -1) && (n < bufsz)) {
+           usererrf (buf);
+           break;
+       }
+       bufsz = MAX(bufsz*2,n+1);
+       if ((np = (char*)realloc(buf, bufsz)) == NULL) {
+           fputs("userout: could not allocate memory\n", stderr );
+           return;
+       }
+    }
+    va_end(args);
+}
+
+/* agerr_va:
+ * Main error reporting function
+ */
 static int agerr_va(agerrlevel_t level, const char *fmt, va_list args)
 {
     agerrlevel_t lvl;
 
+    /* Use previous error level if continuation message;
+     * Convert AGMAX to AGERROR;
+     * else use input level
+     */
     lvl = (level == AGPREV ? agerrno : (level == AGMAX) ? AGERR : level);
 
+    /* store this error level and maximum error level used */
     agerrno = lvl;
     agmaxerr = MAX(agmaxerr, agerrno);
+
+    /* We report all messages whose level is bigger than the user set agerrlevel
+     * Setting agerrlevel to AGMAX turns off immediate error reporting.
+     */
     if (lvl >= agerrlevel) {
-       if (level != AGPREV)
-           fprintf(stderr, "%s: ",
-                   (level == AGERR) ? "Error" : "Warning");
-       vfprintf(stderr, fmt, args);
-       va_end(args);
+       if (usererrf)
+           userout (level, fmt, args);
+       else {
+           if (level != AGPREV)
+               fprintf(stderr, "%s: ", (level == AGERR) ? "Error" : "Warning");
+           vfprintf(stderr, fmt, args);
+           va_end(args);
+       }
        return 0;
     }
 
+    /* If error is not immediately reported, store in log file */
     if (!agerrout) {
        agerrout = tmpfile();
        if (!agerrout)
@@ -544,6 +606,9 @@ static int agerr_va(agerrlevel_t level, const char *fmt, va_list args)
     return 0;
 }
 
+/* agerr:
+ * Varargs function for reporting errors with level argument
+ */
 int agerr(agerrlevel_t level, char *fmt, ...)
 {
     va_list args;
@@ -552,6 +617,9 @@ int agerr(agerrlevel_t level, char *fmt, ...)
     return agerr_va(level, fmt, args);
 }
 
+/* agerrorf:
+ * Varargs function for reporting errors
+ */
 void agerrorf(const char *fmt, ...)
 {
     va_list args;
@@ -560,6 +628,9 @@ void agerrorf(const char *fmt, ...)
     agerr_va(AGERR, fmt, args);
 }
 
+/* agwarningf:
+ * Varargs function for reporting warnings
+ */
 void agwarningf(char *fmt, ...)
 {
     va_list args;