练习中写着“编写一个程序来检查C程序是否存在基本的语法错误,如不平衡的括号,括号和大括号。不要忘记引号,包括单引号和双引号,转义序列和注释。”
我选择通过将括号,括号和大括号放在堆栈上并确保一切都是LIFO以及各种计数器来标记我们是否在评论,引用等中来解决问题。
问题在于我觉得我的代码虽然有效但结构很差,并不是特别惯用。我尝试在结构中实现状态变量(堆栈,escaped
,inString
等)并将测试分解为子例程。它没有多大帮助。有没有办法以更清洁的方式解决这个问题,同时还能正确处理转义字符等?
#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];
}
}
答案 0 :(得分:3)
你的解决方案并不是那么糟糕。这是非常直接的,这是一件好事。为了从这个练习中学到更多东西,我可能会用状态机来实现它。例如。你有几个状态:code
,comment
,string
等。然后你定义它们之间的转换。它变得更容易,因为你根据状态最终得到逻辑(所以你没有像你的主函数那样的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 specification和yacc grammar例如。可以作为一个起点。或者,K&amp; R在附录A中也包含与yacc兼容的C语法,或者您当然可以直接使用C标准中的语法。
对于本练习,您只会使用您感兴趣的语法部分,而忽略其余部分。语法将确保语法正确(所有大括号匹配等),lex / yacc将负责所有代码生成。这使得您只需要指定一些粘合代码,在这种情况下主要是错误消息。
这将完全重写您的代码,但可能会让您更好地理解C语法,至少,您将学会使用lex / yacc这些伟大的工具,永远不会伤害。