[FrontPage] [TitleIndex] [WordIndex

Note: You are looking at a static copy of the former PineWiki site, used for class notes by James Aspnes from 2003 to 2012. Many mathematical formulas are broken, and there are likely to be other bugs as well. These will most likely not be fixed. You may be able to find more up-to-date versions of some of these notes at http://www.cs.yale.edu/homes/aspnes/#classes.

For this assignment, you are to write a program balance that tests for matching parentheses, braces, etc.

To make the program as general as possible, the set of matching characters is provided on the command line. Specifically, argv[1] will contain a sequence of pairs of characters, where the first character in each pair is an opener that must be match by the second character, the corresponding closer. This sequence is well-formed if it contains an even number of characters and no opener or closer appears twice; your program should exit with a nonzero exit code if either of these conditions is violated.

The input to be tested is taken from stdin. If the openers are closers are nested correctly, the program should produce no output and return a zero exit code. Otherwise, it should report (by printing to stdout) the line and character position within the line of the first unmatched closer (or end-of-file if there are unmatched openers at end-of-file), followed by the line and character position of each unmatched opener, one line each. You program should exit with a nonzero return code immediately after printing this report (you do not need to check later parts of the input).

The format for these reports is line:position:offending character, where the offending character is the unmatched opener or closer or the string EOF for end-of-file. Line numbers and character positions should be reported counting from 1, for consistency with gcc output.

To complicate things, some opener/closer pairs may be the same character; for example, argv[1] might be ""'' to check for properly nested quotation marks. In such cases, a character should be treated as a closer if possible.

You may assume that each closer matches exactly one opener; so abcc is an acceptable control string but abcb is not.

Clarification added 2012-02-11:: Nothing prevents a newline (or any other character except '\0') from being an opener or closer. If you need to report a newline, you should report its line as the line it ends and its position as one greater than that of the last non-newline character on the line (or 1 if there are no characters on the line).

1. Examples

Here is an example of testing matching '<' and '>' characters. Note the use of single quotes to keep the shell from interpreting the '<' and '>' characters as redirects. These characters are mostly balanced, but there are two extra '<' openers at the start of the line. The output indicates an unexpected EOF followed by the unmatched openers.

$ echo '<<<><><<>><><>' | ./balance '<>'
2:1:EOF
1:2:<
1:1:<

If we add in the missing closers, we get no output:

$ echo '<<<><><<>><><>>>' | ./balance '<>'

This example shows the program detecting improper nesting between double quotes and parentheses:

$ echo '"I do not think so (or maybe I do," he said.)' | ./balance '""()'
1:45:)
1:35:"
1:20:(
1:1:"

If we move the closing parenthesis, the example works:

$ echo '"I do not think so (or maybe I do)," he said.' | ./balance '""()'

More examples (used by the /c/cs223/Hwk5/test.public script) can be found in /c/cs223/Hwk5/tests. Files ending in .in are balanced; files ending in .bad_in are not, and the expected output is in the matching .bad_in.out file. The list of openers and closers is shown in the filename after the first character up to the first .; so 0ab.in could be tested with ./balance ab < 0ab.in.

2. Submitting your assignment

Submit a Makefile and all .c and .h files used to build your program using /c/cs223/bin/submit 5. The Makefile should produce a command balance when make is run with no arguments. The command /c/cs223/bin/makeit 5 can be used to build your program in the submission directory to see if all the needed files are there. The command /c/cs223/bin/testit 5 public will run /c/cs223/Hwk5/test.public on your submitted files.

3. Sample solution

Makefile

   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 #include <assert.h>
   4 #include <string.h>
   5 #include <limits.h>
   6 
   7 struct elt {
   8     int line;         /* line for opener */
   9     int position;     /* character position */
  10     char opener;      /* opener */
  11     struct elt *prev; /* previous unmatched opener */
  12 };
  13 
  14 void
  15 stackPush(struct elt **stack, int line, int position, int c)
  16 {
  17     struct elt *new;
  18 
  19     new = malloc(sizeof(struct elt));
  20     assert(new);
  21 
  22     new->line = line;
  23     new->position = position;
  24     new->opener = c;
  25     new->prev = *stack;
  26 
  27     *stack = new;
  28 }
  29 
  30 void
  31 stackPop(struct elt **stack)
  32 {
  33     struct elt *prev;
  34 
  35     assert(*stack);
  36 
  37     prev = (*stack)->prev;
  38 
  39     free(*stack);
  40 
  41     *stack = prev;
  42 }
  43 
  44 void
  45 stackDump(struct elt **stack, int line, int position, int c)
  46 {
  47     if(c == EOF) {
  48         printf("%d:%d:EOF\n", line, position);
  49     } else {
  50         printf("%d:%d:%c\n", line, position, c);
  51     }
  52 
  53     while(*stack) {
  54         printf("%d:%d:%c\n", (*stack)->line, (*stack)->position, (*stack)->opener);
  55         stackPop(stack);
  56     }
  57 }
  58 
  59 int
  60 main(int argc, char **argv)
  61 {
  62     char match[UCHAR_MAX+1];     /* table of matching openers indexed by closers */
  63     int is_opener[UCHAR_MAX+1];  /* flags for opener or not */
  64     int i;
  65     struct elt *stack;
  66     int c;
  67     int line;
  68     int position;
  69     unsigned char opener;
  70     unsigned char closer;
  71 
  72     if(argc != 2 || strlen(argv[1]) % 2 != 0) {
  73         fprintf(stderr, "Usage: %s match-list\n", argv[0]);
  74         return 1;
  75     }
  76 
  77     /* construct opener table */
  78     for(i = 0; i <= UCHAR_MAX; i++) {
  79         match[i] = 0;
  80         is_opener[i] = 0;
  81     }
  82 
  83     for(i = 0; argv[1][i] != '\0'; i += 2) {
  84         opener = argv[1][i];
  85         closer = argv[1][i+1];
  86 
  87         /* have we seen this closer before? */
  88         if(match[closer] != 0) {
  89             fprintf(stderr, "%s: duplicate closer %c\n", argv[0], closer);
  90             return 2;
  91         }
  92 
  93         /* how about the opener? */
  94         if(is_opener[opener]) {
  95             fprintf(stderr, "%s: duplicate opener %c\n", argv[0], closer);
  96             return 2;
  97         }
  98 
  99         match[closer] = opener;
 100         is_opener[opener] = 1;
 101     }
 102 
 103     /* initialize stack */
 104     stack = 0;
 105 
 106     /* initialize position */
 107     line = 1;
 108     position = 1;
 109 
 110     while((c = getchar()) != EOF) {
 111 
 112         if(match[c] != 0 && stack != 0 && stack->opener == match[c]) {
 113             /* we just matched an opener at top of stack */
 114             stackPop(&stack);
 115         } else if(is_opener[c]) {
 116             /* push it */
 117             stackPush(&stack, line, position, c);
 118         } else if(match[c] != 0) {
 119             /* unmatched closer that is not an opener */
 120             stackDump(&stack, line, position, c);
 121             return 1;
 122         }
 123 
 124         /* update position */
 125         if(c == '\n') {
 126             /* end of line */
 127             line++;
 128             position = 1;
 129         } else {
 130             position++;
 131         }
 132     }
 133 
 134     /* stack should be empty */
 135     if(stack) {
 136         stackDump(&stack, line, position, EOF);
 137         return 1;
 138     }
 139 
 140     return 0;
 141 }
balance.c

2014-06-17 11:58