Упражнение 1-24 из K & R - элементарная проверка синтаксиса - PullRequest
2 голосов
/ 11 августа 2011

Упражнение гласит: «Напишите программу для проверки программы на C на наличие элементарных синтаксических ошибок, таких как несбалансированные скобки, скобки и фигурные скобки. Не забывайте о кавычках, одинарных и двойных, escape-последовательностях и комментариях».

Я решил заняться решением проблемы, поместив скобки, скобки и фигурные скобки в стек и убедившись, что все было 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];
  }
}

Ответы [ 2 ]

3 голосов
/ 11 августа 2011

Ваше решение не так уж и плохо. Это очень просто, и это хорошо. Чтобы узнать немного больше из этого упражнения, я бы, вероятно, реализовал это с помощью конечного автомата. Например. у вас есть несколько состояний, таких как: code, comment, string и т. д., а затем вы определяете переходы между ними. Это становится намного проще, потому что вы получаете логику, зависящую от состояния (так что у вас нет куска кода, как в вашей основной функции). После этого вы можете разобрать свой код в зависимости от состояния. Это означает, например: если вы находитесь в состоянии комментария, вы игнорируете все, пока не встретите конечный символ комментария. Затем вы меняете состояние, например, на 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. Но, как я сказал в комментарии, я бы не советовал вам использовать это. Возможно, вы могли бы сделать это, если у вас есть достаточно времени и вы хотите узнать как можно больше, но сначала я бы реализовал это «трудным путем».

2 голосов
/ 11 августа 2011

Вероятно, я бы подошел к этому совсем по-другому, используя генератор синтаксических анализаторов, например yacc , в сочетании с генератором лексеров, например lex .

Youможет основываться на существующих входных файлах для этих инструментов, для ANSI C. Это lex спецификация и yacc грамматика например.может быть отправной точкой.Кроме того, K & R также содержит грамматику C, совместимую с yacc, в приложении A, или вы, конечно, можете работать непосредственно с грамматикой в ​​стандарте C.

Для этого упражнения вы будете использовать только те части грамматики, которыевас интересует, а остальные игнорируют.Грамматика будет гарантировать, что синтаксис правильный (все фигурные скобки совпадают и т. Д.), А lex / yacc позаботится о генерации кода.В результате вам остается только указать некоторый клейкий код, который в большинстве случаев будет содержать сообщения об ошибках.

Это будет полная перезапись вашего кода, но, вероятно, даст вам лучшее пониманиеС грамматикой C, и, по крайней мере, вы научитесь работать с великолепными инструментами lex / yacc, которые никогда не причиняют вреда.

...