来自K& R的练习1-24 - 基本语法检查

时间:2011-08-10 22:50:48

标签: c coding-style kr-c

练习中写着“编写一个程序来检查C程序是否存在基本的语法错误,如不平衡的括号,括号和大括号。不要忘记引号,包括单引号和双引号,转义序列和注释。”

我选择通过将括号,括号和大括号放在堆栈上并确保一切都是LIFO以及各种计数器来标记我们是否在评论,引用等中来解决问题。

问题在于我觉得我的代码虽然有效但结构很差,并不是特别惯用。我尝试在结构中实现状态变量(堆栈,escapedinString等)并将测试分解为子例程。它没有多大帮助。有没有办法以更清洁的方式解决这个问题,同时还能正确处理转义字符等?

#include <stdio.h>
#include <stdlib.h>
#define INITIALSTACK 8
#define FALSE 0
#define TRUE 1

typedef struct {
  int position;
  int maxLength;
  char* array;
} stack;

int match(char, char);

stack create();
void delete(stack*);
void push(stack*, char);
char pop(stack*);

int main() {
  char c, out;
  stack elemStack = create();

  int escaped, inString, inChar, inComment, startComment, i, lineNum;
  int returnValue;

  escaped = inString = inChar = inComment = startComment = 0;
  lineNum = 1;

  while ((c = getchar()) != EOF) {
    if (c == '\n')
      lineNum++;

    /* Test if in escaped state or for escape character */
    if (escaped) {
      escaped = FALSE;
    }
    else if (c == '\\') {
      escaped = TRUE;
    }

    /* Test if currently in double/single quote or a comment */
    else if (inString) {
      if (c == '"' && !escaped) {
        inString = FALSE;
      }
    }
    else if (inChar) {
      if (escaped)
        escaped = FALSE;
      else if (c == '\'' && !escaped) {
        inChar = FALSE;
      }
    }
    else if (inComment) {
      if (c == '*')
        startComment = TRUE;
      else if (c == '/' && startComment)
        inComment = FALSE;
      else
        startComment = FALSE;
    }

    /* Test if we should be starting a comment, quote, or escaped character */
    else if (c == '*' && startComment)
      inComment = TRUE;
    else if (c == '/')
      startComment = TRUE;
    else if (c == '"') {
      inString = TRUE;
    }
    else if (c == '\'') {
      inChar = TRUE;
    }

    /* Accept the character and check braces on the stack */
    else {
      startComment = FALSE;

      if (c == '(' || c == '[' || c == '{')
        push(&elemStack, c);
      else if (c == ')' || c == ']' || c == '}') {
        out = pop(&elemStack);
        if (out == -1 || !match(out, c)) {
          printf("Syntax error on line %d: %c matched with %c\n.", lineNum, out, c);
          return -1;
        }
      }
    }
  }

  if (inString || inChar) {
    printf("Syntax error: Quote not terminated by end of file.\n");
    returnValue = -1;
  }
  else if (!elemStack.position) {
    printf("Syntax check passed on %d line(s).\n", lineNum);
    returnValue = 0;
  }
  else {
    printf("Syntax error: Reached end of file with %d unmatched elements.\n  ",
           elemStack.position);
    for(i = 0; i < elemStack.position; ++i)
      printf(" %c", elemStack.array[i]);
    printf("\n");
    returnValue = -1;
  }

  delete(&elemStack);
  return returnValue;
}

int match(char left, char right) {
  return ((left == '{' && right == '}') ||
          (left == '(' && right == ')') ||
          (left == '[' && right == ']'));
}

stack create() {
  stack newStack;
  newStack.array = malloc(INITIALSTACK * sizeof(char));
  newStack.maxLength = INITIALSTACK;
  newStack.position = 0;
  return newStack;
}

void delete(stack* stack) {
  free(stack -> array);
  stack -> array = NULL;
}

void push(stack* stack, char elem) {
  if (stack -> position >= stack -> maxLength) {
    char* newArray = malloc(2 * (stack -> maxLength) * sizeof(char));
    int i;

    for (i = 0; i < stack -> maxLength; ++i)
      newArray[i] = stack -> array[i];

    free(stack -> array);
    stack -> array = newArray;
  }

  stack -> array[stack -> position] = elem;
  (stack -> position)++;
}

char pop(stack* stack) {
  if (!(stack -> position)) {
    printf("Pop attempted on empty stack.\n");
    return -1;
  }
  else {
    (stack -> position)--;
    return stack -> array[stack -> position];
  }
}

2 个答案:

答案 0 :(得分:3)

你的解决方案并不是那么糟糕。这是非常直接的,这是一件好事。为了从这个练习中学到更多东西,我可能会用状态机来实现它。例如。你有几个状态:codecommentstring等。然后你定义它们之间的转换。它变得更容易,因为你根据状态最终得到逻辑(所以你没有像你的主函数那样的blob代码)。之后,您可以根据状态解析代码。这意味着例如:如果您处于评论状态,则在遇到结束评论字符之前忽略所有内容。然后,您将状态更改为code,例如,等等。

在伪代码中,它可能如下所示:

current_state = CODE

while(...) {

   switch(current_state) {
      case CODE:
         if(input == COMMENT_START) {
            current_state = COMMENT
            break
         }

         if(input == STRING_START) {
            current_state = STRING
            break
         }

         // handle your {, [, ( stuff...

         break

      case COMMENT:
         if(input == COMMENT_END) {
            current_state = CODE
            break
         }

         // handle comment.. i.e. ignore everything

         break
      case STRING:
         // ... string stuff like above with state transitions..
         break
   }

}

当然这可以用例如YACC。但正如我在评论中所说,我不建议你使用它。如果你有足够的时间并希望尽可能多地学习,也许你可以做到这一点,但首先我会“艰难地”实施它。

答案 1 :(得分:2)

我可能通过使用解析器生成器(如yacc)与词法分析器生成器(如lex)结合使用来完全不同。

您可以根据ANSI C的这些工具的现有输入文件。lex specificationyacc grammar例如。可以作为一个起点。或者,K&amp; R在附录A中也包含与yacc兼容的C语法,或者您当然可以直接使用C标准中的语法。

对于本练习,您只会使用您感兴趣的语法部分,而忽略其余部分。语法将确保语法正确(所有大括号匹配等),lex / yacc将负责所有代码生成。这使得您只需要指定一些粘合代码,在这种情况下主要是错误消息。

这将完全重写您的代码,但可能会让您更好地理解C语法,至少,您将学会使用lex / yacc这些伟大的工具,永远不会伤害。