2

演習では、「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];
  }
}
4

2 に答える 2

3

あなたの解決策はそれほど悪くはありません。それは非常に簡単です、それは良いことです。この演習からもう少し学ぶために、私はおそらくこれをステートマシンで実装するでしょう。codeたとえば、、などのようないくつかの状態があります。次にcommentstringそれらの間の遷移を定義します。状態に応じてロジックが作成されるため、はるかに簡単になります(したがって、メイン関数のようにコードのブロブがなくなります)。その後、状態に応じてコードを解析できます。これは、たとえば次のことを意味します。コメント状態の場合、終了コメント文字に遭遇するまですべてを無視します。次に、状態を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で行うことができます。しかし、コメントで述べたように、それを使用することはお勧めしません。十分な時間があり、できるだけ多くのことを学びたいのであれば、それを行うことができるかもしれませんが、最初に私はそれを「難しい方法」で実装します。

于 2011-08-11T07:33:13.537 に答える
2

おそらく、 yaccのようなパーサージェネレーターをlexのようなレクサージェネレーターと組み合わせて使用​​することで、これとはまったく異なるアプローチをとることになります。

ANSI Cの場合、これらのツールの既存の入力ファイルに基づいて作成できます。このlex仕様yacc文法。出発点になることができます。あるいは、K&Rの付録Aにもyacc互換のC文法が含まれています。または、もちろん、C標準の文法を直接操作することもできます。

この演習では、興味のある文法の部分のみを使用し、残りは無視します。文法は構文が正しいことを保証し(すべての中括弧が一致するなど)、lex/yaccがすべてのコード生成を処理します。これにより、グルーコードを指定するだけで済みます。この場合、ほとんどの場合、エラーメッセージになります。

これはコードを完全に書き直したものになりますが、おそらくC文法をよりよく理解できるようになり、少なくとも、害を及ぼすことのない優れたツールlex/yaccを使用する方法を学ぶことができます。

于 2011-08-11T06:17:12.393 に答える