1/*
2Copyright (c) 1997-2025, John M. Boyer
3All rights reserved.
4See the LICENSE.TXT file for licensing information.
5*/
6
7#include "planarity.h"
8
9/****************************************************************************
10 Configuration
11 ****************************************************************************/
12
13char Mode = 'r',
14 OrigOut = 'n',
15 OrigOutFormat = 'a',
16 EmbeddableOut = 'n',
17 ObstructedOut = 'n',
18 AdjListsForEmbeddingsOut = 'n';
19
20int Reconfigure(void)
21{
22 int Result = OK;
23
24 char lineBuff[MAXLINE + 1];
25
26 memset(lineBuff, '\0', (MAXLINE + 1));
27
28 while (1)
29 {
30 Prompt("\nDo you want to \n"
31 " Randomly generate graphs (r),\n"
32 " Specify a graph (s),\n"
33 " Randomly generate a maximal planar graph (m), or\n"
34 " Randomly generate a non-planar graph (n)?\n\t");
35
36 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
37 {
38 ErrorMessage("Unable to fetch reconfigure choice from stdin.\n");
39 Result = NOTOK;
40 break;
41 }
42
43 if (strlen(lineBuff) != 1 ||
44 sscanf(lineBuff, " %c", &Mode) != 1 ||
45 !strchr("rsmn", tolower(Mode)))
46 ErrorMessage("Invalid choice for Mode.\n");
47 else
48 {
49 Mode = (char)tolower(Mode);
50 break;
51 }
52 }
53
54 if (Result == OK && Mode == 'r')
55 {
56 Message("\nNOTE: The directories for the graphs you want must exist.\n\n");
57
58 while (1)
59 {
60 Prompt("Do you want original graphs in directory 'random'? (y/n) ");
61 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
62 {
63 ErrorMessage("Unable to fetch choice from stdin.\n");
64 Result = NOTOK;
65 break;
66 }
67
68 if (strlen(lineBuff) != 1 ||
69 sscanf(lineBuff, " %c", &OrigOut) != 1 ||
70 !strchr(YESNOCHOICECHARS, OrigOut))
71 ErrorMessage("Invalid choice.\n");
72 else
73 {
74 OrigOut = (char)tolower(OrigOut);
75 break;
76 }
77 }
78
79 if (Result == OK && OrigOut == 'y')
80 {
81 while (1)
82 {
83 Prompt("Do you want to output generated graphs to Adjacency List (last 10 only) or to G6 (all)? (a/g) ");
84 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
85 {
86 ErrorMessage("Unable to fetch choice from stdin.\n");
87 Result = NOTOK;
88 break;
89 }
90
91 if (strlen(lineBuff) != 1 ||
92 sscanf(lineBuff, " %c", &OrigOutFormat) != 1 ||
93 !strchr("aAgG", OrigOutFormat))
94 ErrorMessage("Invalid choice.\n");
95 else
96 {
97 OrigOutFormat = (char)tolower(OrigOutFormat);
98 break;
99 }
100 }
101 }
102
103 if (Result == OK)
104 {
105 while (1)
106 {
107 Prompt("Do you want adj. matrix of embeddable graphs in directory 'embedded' (last 10 max))? (y/n) ");
108 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
109 {
110 ErrorMessage("Unable to fetch choice from stdin.\n");
111 Result = NOTOK;
112 break;
113 }
114 if (strlen(lineBuff) != 1 ||
115 sscanf(lineBuff, " %c", &EmbeddableOut) != 1 ||
116 !strchr(YESNOCHOICECHARS, EmbeddableOut))
117 ErrorMessage("Invalid choice.\n");
118 else
119 {
120 EmbeddableOut = (char)tolower(EmbeddableOut);
121 break;
122 }
123 }
124 }
125
126 if (Result == OK)
127 {
128 while (1)
129 {
130 Prompt("Do you want adj. matrix of obstructed graphs in directory 'obstructed' (last 10 max)? (y/n) ");
131 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
132 {
133 ErrorMessage("Unable to fetch choice from stdin.\n");
134 Result = NOTOK;
135 break;
136 }
137
138 if (strlen(lineBuff) != 1 ||
139 sscanf(lineBuff, " %c", &ObstructedOut) != 1 ||
140 !strchr(YESNOCHOICECHARS, ObstructedOut))
141 ErrorMessage("Invalid choice.\n");
142 else
143 {
144 ObstructedOut = (char)tolower(ObstructedOut);
145 break;
146 }
147 }
148 }
149
150 if (Result == OK)
151 {
152 while (1)
153 {
154 Prompt("Do you want adjacency list format of embeddings in directory 'adjlist' (last 10 max)? (y/n) ");
155 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
156 {
157 ErrorMessage("Unable to fetch choice from stdin.\n");
158 Result = NOTOK;
159 break;
160 }
161
162 if (strlen(lineBuff) != 1 ||
163 sscanf(lineBuff, " %c", &AdjListsForEmbeddingsOut) != 1 ||
164 !strchr(YESNOCHOICECHARS, AdjListsForEmbeddingsOut))
165 ErrorMessage("Invalid choice.\n");
166 else
167 {
168 AdjListsForEmbeddingsOut = (char)tolower(AdjListsForEmbeddingsOut);
169 break;
170 }
171 }
172 }
173 }
174
175 FlushConsole(stdout);
176
177 return Result;
178}
179
180int GetLineFromStdin(char *lineBuff, int lineBuffSize)
181{
182 if (lineBuff == NULL)
183 {
184 ErrorMessage("Line buffer to populate is NULL.\n");
185 return NOTOK;
186 }
187
188 memset(lineBuff, '\0', lineBuffSize);
189
190 if (fgets(lineBuff, lineBuffSize, stdin) == NULL && ferror(stdin))
191 {
192 ErrorMessage("Call to fgets() from stdin failed.\n");
193 return NOTOK;
194 }
195
196 // From https://stackoverflow.com/a/28462221, strcspn finds the index of the
197 // first char in charset; this way, I replace the char at that index with
198 // the null-terminator
199 // N.B. See https://cplusplus.com/reference/cstring/strcspn/ : if no chars
200 // in str2 appear in str1, will return strlen(str1), so this line is a no-op
201 lineBuff[strcspn(lineBuff, "\n\r")] = '\0'; // works for LF, CR, CRLF, LFCR, ...
202
203 return OK;
204}
205
206void FlushConsole(FILE *f)
207{
208 // N.B. fflush(stdin) constitutes undefined behaviour; see:
209 // https://c-faq.com/stdio/stdinflush.html
210 if (f == stdin)
211 return;
212 fflush(f);
213}
214
215void Prompt(char const *message)
216{
217 Message(message);
218 FlushConsole(stdout);
219}
220
221/****************************************************************************
222 ****************************************************************************/
223
224void SaveAsciiGraph(graphP theGraph, char *filename)
225{
226 int e, EsizeOccupied, vertexLabelFix;
227 FILE *outfile = fopen(filename, WRITETEXT);
228
229 // The filename may specify a directory that doesn't exist
230 if (outfile == NULL)
231 {
232 char messageContents[MAXLINE + 1];
233 char const *messageFormat = "Failed to write to \"%.*s\"\nMake the directory if not present\n";
234 int charsAvailForStrToInclude = (int)(MAXLINE - strlen(messageFormat));
235#pragma GCC diagnostic push
236#pragma GCC diagnostic ignored "-Wformat-nonliteral"
237 sprintf(messageContents, messageFormat, charsAvailForStrToInclude, filename);
238#pragma GCC diagnostic pop
239 ErrorMessage(messageContents);
240 return;
241 }
242
243 // If filename includes path elements, remove them before writing the file's name to the file
244 if (strrchr(filename, FILE_DELIMITER))
245 filename = strrchr(filename, FILE_DELIMITER) + 1;
246
247 fprintf(outfile, "%s\n", filename);
248
249 // This edge list file format uses 1-based vertex numbering, and the current code
250 // internally uses 1-based indexing by default, so this vertex label 'fix' adds zero
251 // But earlier code used 0-based indexing and added one on output, so we replicate
252 // that behavior in case the current code has been compiled with zero-based indexing.
253 vertexLabelFix = 1 - gp_GetFirstVertex(theGraph);
254
255 // Iterate over the edges of the graph
256 EsizeOccupied = gp_EdgeInUseIndexBound(theGraph);
257 for (e = gp_GetFirstEdge(theGraph); e < EsizeOccupied; e += 2)
258 {
259 // Only output edges that haven't been deleted (i.e. skip the edge holes)
260 if (gp_EdgeInUse(theGraph, e))
261 {
262 fprintf(outfile, "%d %d\n",
263 gp_GetNeighbor(theGraph, e) + vertexLabelFix,
264 gp_GetNeighbor(theGraph, e + 1) + vertexLabelFix);
265 }
266 }
267
268 // Since vertex numbers are at least 1, this indicates the end of the edge list
269 fprintf(outfile, "0 0\n");
270
271 fclose(outfile);
272}
273
274/****************************************************************************
275 ReadTextFileIntoString()
276 Reads the file content indicated by infileName using a single fread(), and
277 returns the result in an allocated string. The caller needs to free() the
278 returned string when done with it.
279 Returns NULL on error, or an allocated string containing the file content.
280 ****************************************************************************/
281
282char *ReadTextFileIntoString(char const *infileName)
283{
284 FILE *infile = NULL;
285 char *inputString = NULL;
286
287 if ((infile = fopen(infileName, "r")) == NULL)
288 ErrorMessage("fopen() failed.\n");
289 else
290 {
291 long filePos = ftell(infile);
292 long fileSize;
293
294 fseek(infile, 0, SEEK_END);
295 fileSize = ftell(infile);
296 fseek(infile, filePos, SEEK_SET);
297
298 if ((inputString = (char *)malloc((fileSize + 1) * sizeof(char))) != NULL)
299 {
300 long bytesRead = fread((void *)inputString, 1, fileSize, infile);
301 inputString[bytesRead] = '\0';
302 }
303
304 fclose(infile);
305 }
306
307 return inputString;
308}
309
310/****************************************************************************
311 * TextFileMatchesString()
312 *
313 * Compares the text file content from the file named 'theFilename' with
314 * the content of 'theString'.
315 *
316 * Textual equality is measured as content equality except for suppressing
317 * differences between CRLF and LF-only line delimiters.
318 *
319 * Returns TRUE if the contents are textually equal, FALSE otherwise
320 ****************************************************************************/
321
322int TextFileMatchesString(char const *theFilename, char const *theString)
323{
324 int Result = TRUE;
325
326 FILE *infile = NULL;
327
328 if (theFilename != NULL)
329 infile = fopen(theFilename, "r");
330
331 if (infile == NULL || theString == NULL)
332 Result = FALSE;
333 else
334 {
335 int c1 = 0, c2 = 0, stringIndex = 0;
336
337 // Read the input file to the end
338 while ((c1 = fgetc(infile)) != EOF)
339 {
340 // Want to suppress distinction between lines ending with CRLF versus LF
341 // by looking at only LF characters in the file
342 if (c1 == '\r')
343 continue;
344
345 // Since c1 now has a non-CR, non-EOF from the input file, we now also
346 // get a character from the string, except ignoring CRs again
347 while ((c2 = (int)theString[stringIndex++]) == '\r')
348 ;
349
350 // If c1 doesn't equal c2 (whether c2 is a null terminator or a different character)
351 // then the file content doesn't match the string
352 if (c1 != c2)
353 {
354 Result = FALSE;
355 break;
356 }
357 }
358
359 // If the outer while loop got to the end of the file
360 if (c1 == EOF)
361 {
362 // Then get another character from the string, once again suppressing CRs, and then...
363 while ((c2 = (int)theString[stringIndex++]) == '\r')
364 ;
365 // Test whether or not the second file also ends, same as the first.
366 if (c2 != '\0')
367 Result = FALSE;
368 }
369 }
370
371 if (infile != NULL)
372 fclose(infile);
373
374 return Result;
375}
376
377/****************************************************************************
378 ****************************************************************************/
379
380int TextFilesEqual(char *file1Name, char *file2Name)
381{
382 int Result = TRUE;
383
384 FILE *infile1 = NULL, *infile2 = NULL;
385
386 infile1 = fopen(file1Name, "r");
387 infile2 = fopen(file2Name, "r");
388
389 if (infile1 == NULL || infile2 == NULL)
390 Result = FALSE;
391 else
392 {
393 int c1 = 0, c2 = 0;
394
395 // Read the first file to the end
396 while ((c1 = fgetc(infile1)) != EOF)
397 {
398 // Want to suppress distinction between lines ending with CRLF versus LF
399 if (c1 == '\r')
400 continue;
401
402 // Get a char from the second file, except suppress CR again
403 while ((c2 = fgetc(infile2)) == '\r')
404 ;
405
406 // If we got a char from the first file, but not from the second
407 // then the second file is shorter, so files are not equal
408 if (c2 == EOF)
409 {
410 Result = FALSE;
411 break;
412 }
413
414 // If we got a char from second file, but not equal to char from
415 // first file, then files are not equal
416 if (c1 != c2)
417 {
418 Result = FALSE;
419 break;
420 }
421 }
422
423 // If we got to the end of the first file without breaking the loop...
424 if (c1 == EOF)
425 {
426 // Then, once again, suppress CRs first, and then...
427 while ((c2 = fgetc(infile2)) == '\r')
428 ;
429 // Test whether or not the second file also ends, same as the first.
430 if (fgetc(infile2) != EOF)
431 Result = FALSE;
432 }
433 }
434
435 if (infile1 != NULL)
436 fclose(infile1);
437
438 if (infile2 != NULL)
439 fclose(infile2);
440
441 return Result;
442}
443
444/****************************************************************************
445 ****************************************************************************/
446
447int BinaryFilesEqual(char *file1Name, char *file2Name)
448{
449 int Result = TRUE;
450
451 FILE *infile1 = NULL, *infile2 = NULL;
452
453 infile1 = fopen(file1Name, "r");
454 infile2 = fopen(file2Name, "r");
455
456 if (infile1 == NULL || infile2 == NULL)
457 Result = FALSE;
458 else
459 {
460 int c1 = 0, c2 = 0;
461
462 // Read the first file to the end
463 while ((c1 = fgetc(infile1)) != EOF)
464 {
465 // If we got a char from the first file, but not from the second
466 // then the second file is shorter, so files are not equal
467 if ((c2 = fgetc(infile2)) == EOF)
468 {
469 Result = FALSE;
470 break;
471 }
472
473 // If we got a char from second file, but not equal to char from
474 // first file, then files are not equal
475 if (c1 != c2)
476 {
477 Result = FALSE;
478 break;
479 }
480 }
481
482 // If we got to the end of the first file without breaking the loop...
483 if (c1 == EOF)
484 {
485 // Then attempt to read from the second file to ensure it also ends.
486 if (fgetc(infile2) != EOF)
487 Result = FALSE;
488 }
489 }
490
491 if (infile1 != NULL)
492 fclose(infile1);
493
494 if (infile2 != NULL)
495 fclose(infile2);
496
497 return Result;
498}
499
500/****************************************************************************
501 ALGORITHM FLAGS/SPECIFIERS
502****************************************************************************/
503
504char const *GetAlgorithmFlags(void)
505{
506 return "C = command (algorithm implementation to run)\n"
507 " -p = Planar embedding and Kuratowski subgraph isolation\n"
508 " -d = Planar graph drawing by visibility representation\n"
509 " -o = Outerplanar embedding and obstruction isolation\n"
510 " -2 = Search for subgraph homeomorphic to K_{2,3}\n"
511 " -3 = Search for subgraph homeomorphic to K_{3,3}\n"
512 " -4 = Search for subgraph homeomorphic to K_4\n"
513 "\n";
514}
515
516char const *GetAlgorithmSpecifiers(void)
517{
518 return "P. Planar embedding and Kuratowski subgraph isolation\n"
519 "D. Planar graph drawing by visibility representation\n"
520 "O. Outerplanar embedding and obstruction isolation\n"
521 "2. Search for subgraph homeomorphic to K_{2,3}\n"
522 "3. Search for subgraph homeomorphic to K_{3,3}\n"
523 "4. Search for subgraph homeomorphic to K_4\n";
524}
525
526char const *GetAlgorithmChoices(void)
527{
528 return "pdo234";
529}
530
531/****************************************************************************
532 * GetCommandAndOptionalModifier()
533 *
534 * When provided with a valid commandString, determine the command character
535 * (and optional modifier) and return to the caller via the pointers to chars.
536 *
537 * Note that characters are processed with tolower(), since comparisons are
538 * typically performed only against lower-case letters; when applied to a digit
539 * (or to any other character where such conversion is not possible), tolower()
540 * returns the same character.
541 *
542 * Returns OK when command (and optional modifier) are successfully extracted
543 * from the commandString; no validation of the characters supplied is performed
544 * at this point.
545 *
546 * Returns NOTOK if the commandString is invalid, or if
547 * the pointer to the character to which you wish to assign the extracted
548 * character is NULL.
549 ****************************************************************************/
550
551int GetCommandAndOptionalModifier(const char *commandString, char *command, char *modifier)
552{
553 if (commandString != NULL && commandString[0] == '-')
554 commandString++;
555
556 if (commandString == NULL || strlen(commandString) == 0)
557 {
558 ErrorMessage("Cannot get embed flags for empty command string.\n");
559 return NOTOK;
560 }
561
562 if (command == NULL)
563 {
564 ErrorMessage("Pointer to character to which to write command is NULL.\n");
565 return NOTOK;
566 }
567
568 (*command) = '\0';
569
570 if (modifier != NULL)
571 (*modifier) = '\0';
572
573 if (strlen(commandString) == 1)
574 {
575 (*command) = (char)tolower(commandString[0]);
576 }
577 else if (strlen(commandString) == 2)
578 {
579 (*command) = (char)tolower(commandString[0]);
580 if (modifier != NULL)
581 (*modifier) = (char)tolower(commandString[1]);
582 }
583
584 return OK;
585}
586
587/****************************************************************************
588 * GetEmbedFlags()
589 *
590 * Derives the embedFlags when provided with a valid command character (and
591 * optionally a modifier)
592 *
593 * Returns OK if embedFlags successfully derived, NOTOK if command and/or
594 * modifier character is invalid, or if the pointer to the integer to which you
595 * wish to assign the derived embedFlags is NULL.
596 ****************************************************************************/
597
598int GetEmbedFlags(char command, char modifier, int *embedFlagsP)
599{
600 if (embedFlagsP == NULL)
601 {
602 ErrorMessage("Pointer to embedFlags int is NULL.\n");
603 return NOTOK;
604 }
605
606 switch (command)
607 {
608 case 'p':
609 (*embedFlagsP) = EMBEDFLAGS_PLANAR;
610 break;
611 case 'd':
612 (*embedFlagsP) = EMBEDFLAGS_DRAWPLANAR;
613 break;
614 case 'o':
615 (*embedFlagsP) = EMBEDFLAGS_OUTERPLANAR;
616 break;
617 case '2':
618 (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK23;
619 break;
620 case '3':
621 (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK33;
622 // Future TODO: Add logic: if command == '3' and modifier == 'e',
623 // then embedFlagsP |= EMBEDFLAGS_SEARCHEMBED (or similar)
624 // Future TODO: Error out if modifier char is anything other than 'e'
625 break;
626 case '4':
627 (*embedFlagsP) = EMBEDFLAGS_SEARCHFORK4;
628 break;
629 default:
630 ErrorMessage("Unrecognized algorithm command specifier.\n");
631 return NOTOK;
632 }
633
634 // Future TODO: Currently, there are no graph algorithm extensions for which
635 // we wish to specify a modifier; if an additional character after the
636 // command specifier is given, then error out. In the future, once there are
637 // graph algorithm extensions whose behaviour may be conditioned on a user-
638 // specified modifier character, this check should be removed.
639 if (modifier != '\0')
640 {
641 ErrorMessage("Algorithm modifiers currently not supported.\n");
642 return NOTOK;
643 }
644
645 return OK;
646}
647
648/****************************************************************************
649 * GetAlgorithmName()
650 *
651 * When provided with a valid command char, derives the corresponding algorithm
652 * name returned via char const * to the caller. This memory should not be
653 * freed by the caller.
654 * ****************************************************************************/
655
656char const *GetAlgorithmName(char command)
657{
658 char const *algorithmName = "UnsupportedAlgorithm";
659
660 switch (command)
661 {
662 case 'p':
663 algorithmName = "PlanarEmbed";
664 break;
665 case 'd':
666 algorithmName = DRAWPLANAR_NAME;
667 break;
668 case 'o':
669 algorithmName = "OuterplanarEmbed";
670 break;
671 case '2':
672 algorithmName = K23SEARCH_NAME;
673 break;
674 case '3':
675 algorithmName = K33SEARCH_NAME;
676 break;
677 case '4':
678 algorithmName = K4SEARCH_NAME;
679 break;
680 default:
681 break;
682 }
683
684 return algorithmName;
685}
686
687/****************************************************************************
688 ****************************************************************************/
689
690char const *GetTransformationName(char command)
691{
692 char const *transformationName = "UnsupportedTransformation";
693
694 switch (command)
695 {
696 case 'g':
697 transformationName = "G6";
698 break;
699 case 'a':
700 transformationName = "AdjList";
701 break;
702 case 'm':
703 transformationName = "AdjMat";
704 break;
705 default:
706 break;
707 }
708
709 return transformationName;
710}
711
712char const *GetSupportedOutputChoices(void)
713{
714 return "G. G6 format\n"
715 "A. Adjacency List format\n"
716 "M. Adjacency Matrix format\n";
717}
718
719char const *GetSupportedOutputFormats(void)
720{
721 return "gam";
722}
723
724/****************************************************************************
725 ****************************************************************************/
726
727char const *GetBaseName(int baseFlag)
728{
729 char const *transformationName = baseFlag ? "1-based" : "0-based";
730
731 return transformationName;
732}
733
734/****************************************************************************
735 * AttachAlgorithm()
736 *
737 * Determines the main graph algorithm command indicated by the command char
738 * and attaches the corresponding graph algorithm extension. The modifier is not
739 * required to determine which graph algorithm extension to attach, and is only
740 * used to signal that the behaviour of the main extension should be modified.
741 *
742 * Returns OK if graph algorithm extension corresponding to the command char
743 * was successfully attached. Returns NOTOK if theGraph is not properly
744 * initialized, if attaching the graph algorithm extension failed, or if an
745 * invalid command char was supplied.
746 ****************************************************************************/
747
748int AttachAlgorithm(graphP theGraph, char command)
749{
750 if (theGraph == NULL || theGraph->N <= 0)
751 {
752 ErrorMessage("Unable to attach graph algorithm extension to NULL or uninitialized graphP.\n");
753 return NOTOK;
754 }
755
756 switch (command)
757 {
758 case 'p':
759 // Planarity is attached by default
760 return OK;
761 case 'd':
762 return gp_AttachDrawPlanar(theGraph);
763 case 'o':
764 // Outerplanarity is attached by default
765 return OK;
766 case '2':
767 return gp_AttachK23Search(theGraph);
768 case '3':
769 return gp_AttachK33Search(theGraph);
770 case '4':
771 return gp_AttachK4Search(theGraph);
772 default:
773 break;
774 }
775
776 return NOTOK;
777}
778
779/****************************************************************************
780 A string used to construct input and output filenames.
781
782 The SUFFIXMAXLENGTH is 32 to accommodate ".out.txt" + ".render.txt" + ".test.txt"
783 ****************************************************************************/
784
785char theFileName[FILENAMEMAXLENGTH + 1 + ALGORITHMNAMEMAXLENGTH + 1 + SUFFIXMAXLENGTH + 1];
786
787/****************************************************************************
788 ConstructInputFilename()
789 Returns a string not owned by the caller (do not free string).
790 String contains infileName content if infileName is non-NULL.
791 If infileName is NULL, then the user is asked to supply a name.
792 Returns NULL on error, or a non-NULL string on success.
793 ****************************************************************************/
794
795char *ConstructInputFilename(char const *infileName)
796{
797 int Result = OK;
798
799 int numCharsToReprFILENAMEMAXLENGTH = 0;
800 char const *fileNameFormatFormat = " %%%d[^\r\n]";
801 char *fileNameFormat = NULL;
802 char lineBuff[MAXLINE + 1];
803
804 memset(lineBuff, '\0', (MAXLINE + 1));
805
806 if (GetNumCharsToReprInt(FILENAMEMAXLENGTH, &numCharsToReprFILENAMEMAXLENGTH) != OK)
807 {
808 ErrorMessage("Unable to determine number of characters required to represent FILENAMEMAXLENGTH.\n");
809 return NULL;
810 }
811
812 fileNameFormat = (char *)malloc((strlen(fileNameFormatFormat) + numCharsToReprFILENAMEMAXLENGTH + 1) * sizeof(char));
813 if (fileNameFormat == NULL)
814 {
815 ErrorMessage("Unable to allocate memory for filename format string.\n");
816 return NULL;
817 }
818
819#pragma GCC diagnostic push
820#pragma GCC diagnostic ignored "-Wformat-nonliteral"
821 sprintf(fileNameFormat, fileNameFormatFormat, FILENAMEMAXLENGTH);
822#pragma GCC diagnostic pop
823 if (infileName == NULL)
824 {
825 while (1)
826 {
827 Prompt("Enter graph file name: ");
828 if (GetLineFromStdin(lineBuff, MAXLINE) != OK)
829 {
830 ErrorMessage("Unable to read graph file name from stdin.\n");
831 Result = NOTOK;
832 break;
833 }
834
835#pragma GCC diagnostic push
836#pragma GCC diagnostic ignored "-Wformat-nonliteral"
837 if (strlen(lineBuff) == 0 || strlen(lineBuff) > FILENAMEMAXLENGTH ||
838 sscanf(lineBuff, fileNameFormat, theFileName) != 1)
839 ErrorMessage("Invalid input filename.\n");
840 else
841 {
842 if (strncmp(theFileName, "stdin", strlen("stdin")) != 0 && !strchr(theFileName, '.'))
843 {
844 Message("Graph file name does not have extension; automatically appending \".txt\".\n");
845 if (strcat(theFileName, ".txt") == NULL)
846 {
847 ErrorMessage("Appending \".txt\" extension to theFileName using strcat() failed.\n");
848 Result = NOTOK;
849 }
850 }
851 break;
852 }
853#pragma GCC diagnostic pop
854 }
855 }
856 else
857 {
858 if (strlen(infileName) > FILENAMEMAXLENGTH)
859 {
860 ErrorMessage("Filename is too long.\n");
861 Result = NOTOK;
862 }
863 else if (strlen(infileName) == 0)
864 {
865 ErrorMessage("Filename is empty.\n");
866 Result = NOTOK;
867 }
868
869 if (Result == OK)
870 {
871 if (strcpy(theFileName, infileName) == NULL)
872 {
873 ErrorMessage("Copying infileName into theFileName using strcpy() failed.\n");
874 Result = NOTOK;
875 }
876 }
877 }
878
879 if (fileNameFormat != NULL)
880 {
881 free(fileNameFormat);
882 fileNameFormat = NULL;
883 }
884
885 return Result != OK ? NULL : theFileName;
886}
887
888/****************************************************************************
889 * ConstructPrimaryOutputFilename()
890 *
891 * Returns a string not owned by the caller (do not free string).
892 * Reuses the same memory space as ConstructInputFilename().
893 * If outfileName is non-NULL, then the result string contains its content.
894 * If outfileName is NULL, then the infileName and the command's algorithm name
895 * are used to construct a string.
896 *
897 * Returns non-NULL string
898 ****************************************************************************/
899
900char *ConstructPrimaryOutputFilename(char const *infileName, char const *outfileName, char command)
901{
902 char const *algorithmName = GetAlgorithmName(command);
903
904 if (outfileName == NULL)
905 {
906 // The output filename is based on the input filename
907 if (theFileName != infileName)
908 strcpy(theFileName, infileName);
909
910 // If the primary output filename has not been given, then we use
911 // the input filename + the algorithm name + a simple suffix
912 if (strlen(algorithmName) <= ALGORITHMNAMEMAXLENGTH)
913 {
914 strcat(theFileName, ".");
915 strcat(theFileName, algorithmName);
916 }
917 else
918 ErrorMessage("Algorithm Name is too long, so it will not be used in output filename.\n");
919
920 strcat(theFileName, ".out.txt");
921 }
922 else
923 {
924 if (strlen(outfileName) > FILENAMEMAXLENGTH)
925 {
926 char const *messageFormat = "Outfile filename is too long. Result placed in \"%.*s\"";
927 int charsAvailForStrToInclude = (int)(MAXLINE - strlen(messageFormat));
928 char messageContents[MAXLINE + 1];
929 messageContents[0] = '\0';
930
931 // The output filename is based on the input filename
932 if (theFileName != infileName)
933 strcpy(theFileName, infileName);
934
935 if (strlen(algorithmName) <= ALGORITHMNAMEMAXLENGTH)
936 {
937 strcat(theFileName, ".");
938 strcat(theFileName, algorithmName);
939 }
940 strcat(theFileName, ".out.txt");
941
942#pragma GCC diagnostic push
943#pragma GCC diagnostic ignored "-Wformat-nonliteral"
944 sprintf(messageContents, messageFormat, charsAvailForStrToInclude, theFileName);
945#pragma GCC diagnostic pop
946 ErrorMessage(messageContents);
947 }
948 else
949 {
950 if (theFileName != outfileName)
951 strcpy(theFileName, outfileName);
952 }
953 }
954
955 return theFileName;
956}
957
958/****************************************************************************
959 * ConstructTransformationExpectedResultFilename()
960 *
961 * Returns a string whose ownership will be transferred to the caller (must free
962 * string).
963 * If outfileName is non-NULL, then the result string contains its content
964 * If outfileName is NULL, then the infileName, the command's algorithm name,
965 * and whether or not this output file correspond to the actual (0) or expected
966 * (1) output file from testing are used to construct a string.
967 *
968 * Returns non-NULL string
969 ****************************************************************************/
970
971int ConstructTransformationExpectedResultFilename(char const *infileName, char **outfileName, char command, int baseFlag)
972{
973 int Result = OK;
974
975 char const *baseName = GetBaseName(baseFlag);
976 char const *transformationName = GetTransformationName(command);
977 int infileNameLen = -1;
978
979 if (infileName == NULL || (infileNameLen = strlen(infileName)) < 1)
980 {
981 ErrorMessage("Cannot construct transformation output filename for empty infileName.\n");
982 return NOTOK;
983 }
984
985 if ((*outfileName) == NULL)
986 {
987 (*outfileName) = (char *)calloc(
988 infileNameLen + 1 + strlen(baseName) + 1 + strlen(transformationName) +
989 ((command == 'g') ? strlen(".out.g6") : strlen(".out.txt")) + 1,
990 sizeof(char));
991
992 if ((*outfileName) == NULL)
993 {
994 ErrorMessage("Unable to allocate memory for output filename.\n");
995 return NOTOK;
996 }
997
998 strcpy((*outfileName), infileName);
999 strcat((*outfileName), ".");
1000 strcat((*outfileName), baseName);
1001 strcat((*outfileName), ".");
1002 strcat((*outfileName), transformationName);
1003 strcat((*outfileName), command == 'g' ? ".out.g6" : ".out.txt");
1004 }
1005 else
1006 {
1007 ErrorMessage("outfileName already allocated.\n");
1008 Result = NOTOK;
1009 }
1010
1011 return Result;
1012}
1013
1014/****************************************************************************
1015 * WriteAlgorithmResults()
1016 ****************************************************************************/
1017
1018void WriteAlgorithmResults(graphP theGraph, int Result, char command, platform_time start, platform_time end, char const *infileName)
1019{
1020 char const *messageFormat = NULL;
1021 char messageContents[MAXLINE + 1];
1022 int charsAvailForStr = 0;
1023
1024 memset(messageContents, '\0', (MAXLINE + 1));
1025
1026 if (infileName)
1027 {
1028 messageFormat = "The graph \"%.*s\" ";
1029 charsAvailForStr = (int)(MAXLINE - strlen(messageFormat));
1030#pragma GCC diagnostic push
1031#pragma GCC diagnostic ignored "-Wformat-nonliteral"
1032 sprintf(messageContents, messageFormat, charsAvailForStr, infileName);
1033#pragma GCC diagnostic pop
1034 }
1035 else
1036 sprintf(messageContents, "The graph ");
1037 Message(messageContents);
1038
1039 switch (command)
1040 {
1041 case 'p':
1042 sprintf(messageContents, "is%s planar.\n", Result == OK ? "" : " not");
1043 break;
1044 case 'd':
1045 sprintf(messageContents, "is%s planar.\n", Result == OK ? "" : " not");
1046 break;
1047 case 'o':
1048 sprintf(messageContents, "is%s outerplanar.\n", Result == OK ? "" : " not");
1049 break;
1050 case '2':
1051 sprintf(messageContents, "has %s subgraph homeomorphic to K_{2,3}.\n", Result == OK ? "no" : "a");
1052 break;
1053 case '3':
1054 sprintf(messageContents, "has %s subgraph homeomorphic to K_{3,3}.\n", Result == OK ? "no" : "a");
1055 break;
1056 case '4':
1057 sprintf(messageContents, "has %s subgraph homeomorphic to K_4.\n", Result == OK ? "no" : "a");
1058 break;
1059 default:
1060 sprintf(messageContents, "has not been processed due to unrecognized command.\n");
1061 break;
1062 }
1063 Message(messageContents);
1064
1065 sprintf(messageContents, "Algorithm '%s' executed in %.3lf seconds.\n",
1066 GetAlgorithmName(command), platform_GetDuration(start, end));
1067 Message(messageContents);
1068}
1069