先日勉強会でこの辺のテーマを取り上げたので、字句解析や構文解析(の一部)とスタックの復習も兼ねて作成(required for 1h+)。
実装のポイント
- 閉じ括弧の有無の判定は、ファイルの終端が読み終わった後。
- 開き括弧の判定は、閉じ括弧を読み込んだ際に行い、スタックの最上位に対応する開き括弧があるか否かで。
- 入れ子の(または再帰的な)構造で、どの深さにスレッドがいるか調べるにはスタックを用いる。
ソースコード(C言語) valid_pare.c
#include <stdio.h> #include <stdlib.h> const int MAXSIZE = 128; // スタックサイズ(静的) // スタックするデータ構造(Cell) typedef struct brackets Brackets; struct brackets { int kind; // 括弧の種類 int line; // 位置:行 int pos; // 位置:列 }; Brackets stack[128]; int pnt; Brackets pop(void) { if (pnt < = 0) { printf("error:stack empty.\n"); exit(1); } pnt--; return stack[pnt]; } void push(Brackets b) { if (pnt >= MAXSIZE) { printf("error:stack fullness.\n"); exit(1); } stack[pnt] = b; pnt++; } // stackが空(1)か否(0)か int empty(void) { return (pnt == 0) ? 1 : 0; } // スタックの最上位の文字種を返す int peek() { return stack[pnt-1].kind; } // 括弧の判別 int kind(int ch) { int code; switch (ch) { case '(': code = 1; break; case ')': code = 2; break; case '{': code = 3; break; case '}': code = 4; break; case '[': code = 5; break; case ']': code = 6; break; default: code = 0; // 括弧以外の文字 break; } return code; } int main() { int ch; FILE *fp; char fname[64]; // ファイル名 int k; // 文字の種類 int line = 1, pos = 0; Brackets kakko, temp; pnt = 0; // スタックポインタ(GV)の初期化 printf("Filename:"); scanf("%62s", fname); if ((fp = fopen(fname, "r")) == NULL) { printf("\aCan't be opened.\n"); exit(1); } //printf("%d行目\n", line); // ファイルから一文字ずつ読む while ((ch = fgetc(fp)) != EOF) { //putchar(ch); if (ch == '\n') { line++; pos=0; //printf("%d行目\n", line); continue; } pos++; //printf("%d", pos); k = kind(ch); if (k > 0) { // 文字が括弧の場合 if (k % 2) { // 開き括弧の場合 kakko.kind = k; kakko.line = line; kakko.pos = pos; push(kakko); } else if (!empty() && (k == peek()+1)) { temp = pop(); // 対応する閉じ括弧があった } else { printf("対応する開き括弧がない"); printf("(%d行目の%d文字目)。\n", line, pos); } } } puts("テキスト終端"); fclose(fp); if (!empty()) { printf("対応する閉じ括弧がない。\n"); while (!empty()) { temp = pop(); printf("%d行目の%d文字目。\n", temp.line, temp.pos); } } return 0; }
使用したText
(1+2) ac)c ((3+6)ge)) (([y) ij])(k {5/3}
実行結果
D:\parentheses>gcc valid_pare.c D:\parentheses>a.exe Filename:pare.txt 対応する開き括弧がない(2行目の3文字目)。 対応する開き括弧がない(3行目の10文字目)。 対応する開き括弧がない(4行目の5文字目)。 テキスト終端 対応する閉じ括弧がない。 5行目の5文字目。 4行目の1文字目。 D:\parentheses>
改良するなら
- 関数を他のファイルに分けて、main側でインクルードする。
- スタック領域を動的に割り当てる。(Brackets *)emalloc(sizeof(Brackets))かな。
- スタック配列を伸縮性のある構造・操作関数で実装する。
- 結果の表示をコンパイラのエラー出力っぽくする。該当箇所に「^」をマーク。
- 他の言語で実装してみる(OOPを用いて等等)。
- 字句・構文解析のオープンソースを参考にする。
こういった小さなものを作りつつ、OOPのパターンに基づいて拡張性を考慮する今日この頃。