← Home

TC_24412014.c

/*
 * 課題: Tiny C言語 パーサ (構文解析プログラム)
 * 学籍番号: 24412014
 * 氏名: 北村 友哉
 * 提出日: 2026/01/30
 */

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

/* * =========================================================
 * 配布された define_token.h の内容をここにコピー
 * =========================================================
 */
#define START_TOKEN	0
#define EOD		1
#define UNKNOWN		2
#define NAME		10
#define NUMERIC		11
#define BOOLEAN_VAL	12

#define REV_TRUE	21
#define REV_FALSE	22

#define REV_PERCENT_D	23
#define REV_PERCENT_T	24

#define DEC_INT		31
#define REV_MAIN	32
#define REV_IF		33
#define REV_ELSE	34
#define REV_WHILE	35
#define SCANF		36
#define PRINTF		37

#define L_BRACKET	51 // {
#define R_BRACKET	52 // }
#define COMMA		53 // ,
#define SEMICOLON	54 // ;
#define EQUAL		55 // =
#define PLUS		56 // +
#define MINUS		57 // -
#define ESUAS		58 // !
#define ASTERRISK	59 // *
#define SLASH		60 // /
#define L_PAREN		61 // (
#define R_PAREN		62 // )

#define LOGICAL_AND	81
#define LOGICAL_OR	82
#define GREATER		83
#define GREATER_EQUAL	84
#define LESS		85
#define LESS_EQUAL	86
#define RQUAL		87
#define NOT_EQUAL	88
#define AMPERSAND	89

#define PERCENT_D	101
#define PERCENT_DN	102
#define PERCENT_DT	103

#define BLANK ' '

// 予約語の構造体
struct rev_word_typ {
	int  token_num;
	char rev_word[16];
};

// 予約語の定義
struct rev_word_typ rev_word[] = {
	{REV_TRUE,  "true"},
	{REV_FALSE, "false"},
	{DEC_INT,   "int"},
	{REV_MAIN,  "main"},
	{REV_IF,    "if"},
	{REV_ELSE,  "else"},
	{REV_WHILE, "while"},
	{SCANF,     "scanf"},
	{PRINTF,    "printf"}
};

char *formatter[] = {"%d", "%d\\n", "%d\\t"};

// トークン情報を格納する構造体
struct token_obj_typ {
	char id[256];
	int  num;
	int  sym;
};

// 現在読み込んでいるトークン
struct token_obj_typ current_token;

// エラー表示用に行数を数える変数 (追加)
int line_count = 0;

/* * =========================================================
 * 字句解析部 (getsym)
 * 配布された getsym_p.c をベースに、行番号カウントを追加
 * =========================================================
 */
#define ONE_LINE_LENGTH	256
#define REV_WORD_LENGTH 9
#define EOL -1
#define NULL_CHARACTER '\0'
#define BLANK_CHARACTER ' '
#define SLASH_CHARACTER '/'

// 予約語かどうか判定する関数
int identify_rev_word(char *p) {
	int i, rtn;
	for(i=0; i < REV_WORD_LENGTH && strcmp(p, rev_word[i].rev_word); i++);
	if (i == REV_WORD_LENGTH) rtn = 0; else rtn = rev_word[i].token_num;
	return rtn;
}

// 記号を判定する関数
int identify_symbol(char **ptr) {
	char cc  = **ptr;
	char *cp = *ptr;
	char *bptr;
	int sm = UNKNOWN;
	int i;

	switch (cc) {
	case '{': sm = L_BRACKET; (*ptr)++; break;
	case '}': sm = R_BRACKET; (*ptr)++; break;
	case '(': sm = L_PAREN; (*ptr)++; break;
	case ')': sm = R_PAREN; (*ptr)++; break;
	case ',': sm = COMMA; (*ptr)++; break;
	case ';': sm = SEMICOLON; (*ptr)++; break;
	case '=':
		(*ptr)++;
		if (**ptr == '=') { sm = RQUAL; (*ptr)++; } else { sm = EQUAL; }
		break;
	case '&':
		(*ptr)++;
		if (**ptr == '&') { sm = LOGICAL_AND; (*ptr)++; } else { sm = AMPERSAND; }
		break;
	case '|':
		(*ptr)++;
		if (**ptr == '|') { sm = LOGICAL_OR; (*ptr)++; } else { sm = UNKNOWN; }
		break;
	case '>':
		(*ptr)++;
		if (**ptr == '=') { sm = GREATER_EQUAL; (*ptr)++; } else { sm = GREATER; }
		break;
	case '<':
		(*ptr)++;
		if (**ptr == '=') { sm = LESS_EQUAL; (*ptr)++; } else { sm = LESS; }
		break;
	case '+': sm = PLUS; (*ptr)++; break;
	case '-': sm = MINUS; (*ptr)++; break;
	case '!':
		(*ptr)++;
		if (**ptr == '=') { sm = NOT_EQUAL; (*ptr)++; } else { sm = ESUAS; }
		break;
	case '*': sm = ASTERRISK; (*ptr)++; break;
	case '/': sm = SLASH; (*ptr)++; break;
	case '"':
		(*ptr)++;
		bptr = *ptr;
		while (**ptr != '"' && **ptr != NULL_CHARACTER) (*ptr)++;
		if (**ptr == NULL_CHARACTER) {
			sm = UNKNOWN;
		} else {
			for(i=0; i<3; i++) {
				if (!strncmp(bptr, formatter[i], (int)(*ptr - bptr))) break;
			}
			if (i == 3) sm = UNKNOWN; else sm = PERCENT_D + i;
		}
		(*ptr)++;
		break;
	}
	return sm;
}

// 構造体に値をセットする
void set_token_obj(char *buffer, int ssym) {
	switch (ssym) {
	case NAME:
		strcpy(current_token.id, buffer);
		current_token.sym = ssym;
		break;
	case NUMERIC:
		current_token.sym = ssym;
		current_token.num = atoi(buffer);
		break;
    default:
        current_token.sym = ssym;
        break;
	}
}

// 1行読み込む関数
int get_line(char *buffer) {
	int ch='\0';
	char *ptr = buffer;
	int counter=0;

	while ((ch = getchar()) != EOF) {
		if (ch == '\n' || ch == 0x0a) {
			*ptr = ' ';
			ptr++;
			counter++;
			*ptr = NULL_CHARACTER;
            // 課題要件:エラー箇所表示のため行数をカウント
            line_count++; 
			break;
		} else {
			*ptr = ch;
			ptr++;
			counter++;
		}
	}
	if (counter) return counter; else return EOL;
}

// トークンを取得するメイン関数
int getsym() {
	static int ssym = START_TOKEN;
	static char one_line[ONE_LINE_LENGTH];
	static char *ptr = one_line;
	char buffer[ONE_LINE_LENGTH], *buffer_p=buffer;
	int res=0;

    // バッファが空なら新しい行を読む
	while (ptr == one_line && res == 0) {
		res = get_line(one_line);
		if (res == EOL) {
			ptr = one_line;
			return EOD; // ファイル終了
		}
		// コメントスキップ処理
		while (*ptr != NULL_CHARACTER) {
			if (*ptr == BLANK_CHARACTER) {ptr++; continue;}
			if (*ptr == SLASH_CHARACTER && *(ptr+1) == SLASH_CHARACTER) *ptr = NULL_CHARACTER; else break;
		}
		if (*ptr == NULL_CHARACTER) {
			ptr = one_line;
			res = 0;
		}
	}

    // 英字なら変数か予約語
	if (isalpha(*ptr)) {
		while (isalpha(*ptr) || isdigit(*ptr)) {
			*buffer_p = *ptr;
			buffer_p++;
			ptr++;
		}
		*buffer_p = NULL_CHARACTER;
		if ((ssym = identify_rev_word(buffer)) == 0) ssym = NAME;
		set_token_obj(buffer, ssym);
	} 
    // 数字なら数値
    else if (isdigit(*ptr)) {
		while (isdigit(*ptr)) {
			*buffer_p = *ptr;
			ptr++;
			buffer_p++;
		}
		*buffer_p = NULL_CHARACTER;
		ssym = NUMERIC;
		set_token_obj(buffer, ssym);
	} 
    // それ以外は記号
    else {
		ssym = identify_symbol(&ptr);
		set_token_obj(buffer, ssym);
	}
	while (*ptr == BLANK_CHARACTER) ptr++;
	if (*ptr == NULL_CHARACTER || (*ptr == SLASH_CHARACTER && *(ptr+1) == SLASH_CHARACTER)) ptr = one_line;
	return ssym;
}

/* * =========================================================
 * 構文解析部 (ここから作成)
 * Tiny C言語仕様書(構文図)に基づいて実装
 * =========================================================
 */

// プロトタイプ宣言
void program();         // プログラム全体
void var_def();         // 変数定義
void statement();       // 文
void expression();      // 式

// エラーが発生した時に呼び出す関数
// エラー内容と行番号を表示して終了する
void error_exit(char *msg) {
    printf("Syntax Error (Line: %d): %s\n", line_count, msg);
    // 念のため終了コード1で終わる
    exit(1);
}

// 期待するトークンかどうかチェックする関数
// 合っていれば次のトークンを読み、違えばエラーにする
void check_symbol(int expected, char *err_msg) {
    if (current_token.sym == expected) {
        getsym(); // 次へ進む
    } else {
        error_exit(err_msg);
    }
}

/*
 * 式 (Expression) の解析
 * 式 -> 左辺値式 | 式 二項演算子 式 | 単項演算子 式 ... など
 * ※構文図では左再帰があるが、単純なループと再帰で実装する
 */
void expression() {
    // 最初の要素をチェック
    // 単項演算子 (+, -, !)
    if (current_token.sym == PLUS || current_token.sym == MINUS || current_token.sym == ESUAS) {
        getsym(); // 演算子を読み飛ばす
        expression(); // 再帰的に式を読む
    } 
    // カッコ ( 式 )
    else if (current_token.sym == L_PAREN) {
        getsym(); // ( を読む
        expression(); // 中身の式を読む
        check_symbol(R_PAREN, "')' が必要です");
    }
    // 定数 (数字 or true/false)
    else if (current_token.sym == NUMERIC || current_token.sym == REV_TRUE || current_token.sym == REV_FALSE) {
        getsym(); // 定数を読み飛ばす
    }
    // 変数
    else if (current_token.sym == NAME) {
        getsym(); // 変数名を読み飛ばす
    }
    else {
        // どれにも当てはまらない場合はエラー
        error_exit("式の書き方が間違っています");
    }

    // 後ろに演算子が続く場合の処理 (例: a + b の + b の部分)
    // 二項演算子かどうかチェック
    while ((current_token.sym >= PLUS && current_token.sym <= SLASH && current_token.sym != ESUAS) || 
           (current_token.sym >= LOGICAL_AND && current_token.sym <= NOT_EQUAL)) {
        getsym(); // 演算子を読む
        expression(); // 右側の式を読む
    }
}

/*
 * 文 (Statement) の解析
 * 代入文、if文、while文、複文、入出力文を判定する
 */
void statement() {
    // 代入文 (変数 = 式 ;)
    if (current_token.sym == NAME) {
        getsym(); // 変数
        check_symbol(EQUAL, "代入には '=' が必要です");
        expression(); // 右辺の式
        check_symbol(SEMICOLON, "文の末尾には ';' が必要です");
    }
    // if文 (if ( 式 ) 文 [else 文])
    else if (current_token.sym == REV_IF) {
        getsym(); // if
        check_symbol(L_PAREN, "ifの後に '(' が必要です");
        expression(); // 条件式
        check_symbol(R_PAREN, "条件式の後に ')' が必要です");
        statement(); // ifの中身
        
        // elseがある場合
        if (current_token.sym == REV_ELSE) {
            getsym(); // else
            statement(); // elseの中身
        }
    }
    // while文 (while ( 式 ) 文)
    else if (current_token.sym == REV_WHILE) {
        getsym(); // while
        check_symbol(L_PAREN, "whileの後に '(' が必要です");
        expression(); // 条件式
        check_symbol(R_PAREN, "条件式の後に ')' が必要です");
        statement(); // ループの中身
    }
    // 複文 (ブロック { ... })
    else if (current_token.sym == L_BRACKET) {
        getsym(); // {
        // } が来るまで文を繰り返す
        while (current_token.sym != R_BRACKET && current_token.sym != EOD) {
            statement();
        }
        check_symbol(R_BRACKET, "ブロックの終わりに '}' が必要です");
    }
    // scanf文 (scanf ( "%d" , & 変数 ) ;)
    else if (current_token.sym == SCANF) {
        getsym(); // scanf
        check_symbol(L_PAREN, "scanfの後に '(' が必要です");
        check_symbol(PERCENT_D, "scanfには \"%d\" が必要です");
        check_symbol(COMMA, "',' が必要です");
        check_symbol(AMPERSAND, "変数ポインタの '&' が必要です");
        
        if (current_token.sym == NAME) {
             getsym(); // 変数名
        } else {
             error_exit("scanfには変数名が必要です");
        }
        
        check_symbol(R_PAREN, "')' が必要です");
        check_symbol(SEMICOLON, "';' が必要です");
    }
    // printf文 (printf ( 書式 , 変数 ) ;)
    else if (current_token.sym == PRINTF) {
        getsym(); // printf
        check_symbol(L_PAREN, "printfの後に '(' が必要です");
        
        // 書式のチェック (%d, %d\n, %d\t)
        if (current_token.sym == PERCENT_D || current_token.sym == PERCENT_DN || current_token.sym == PERCENT_DT) {
            getsym();
        } else {
            error_exit("printfの書式指定が間違っています");
        }
        
        check_symbol(COMMA, "',' が必要です");
        
        if (current_token.sym == NAME) {
            getsym(); // 変数名
        } else { 
            error_exit("printfには変数名が必要です");
        }
        
        check_symbol(R_PAREN, "')' が必要です");
        check_symbol(SEMICOLON, "';' が必要です");
    }
    // 空文 (セミコロンのみ)
    else if (current_token.sym == SEMICOLON) {
        getsym();
    }
    // それ以外はエラー
    else {
        error_exit("解釈できない文です");
    }
}

/*
 * 変数定義 (Variable Definition)
 * int 変数名, 変数名 ... ;
 */
void var_def() {
    // 変数定義は int で始まる
    if (current_token.sym == DEC_INT) {
        getsym(); // int を読み飛ばす
        
        // 変数名の列挙を処理
        while(1) {
            if (current_token.sym == NAME) {
                getsym(); // 変数名
            } else {
                error_exit("変数定義には変数名が必要です");
            }
            
            // カンマがあれば次の変数が続く
            if (current_token.sym == COMMA) {
                getsym(); 
                continue;
            } else {
                break; // カンマがなければ終了
            }
        }
        // 最後にセミコロン
        check_symbol(SEMICOLON, "変数定義の最後に ';' が必要です");
    }
}

/*
 * プログラム全体 (Program)
 * int main ( ) { 変数定義 文... }
 */
void program() {
    // main関数の定義チェック
    check_symbol(DEC_INT, "プログラムは 'int' で始まる必要があります");
    check_symbol(REV_MAIN, "'main' 関数が必要です");
    check_symbol(L_PAREN, "mainの後に '(' が必要です");
    // voidは省略されることが多いのでチェックしない(サンプル準拠)
    check_symbol(R_PAREN, "引数の後に ')' が必要です");
    check_symbol(L_BRACKET, "本体の始まりに '{' が必要です");

    // 変数定義がある場合 (intで始まる場合)
    if (current_token.sym == DEC_INT) {
        var_def();
    }

    // 文の繰り返し処理
    // } が来るまで続ける
    while (current_token.sym != R_BRACKET && current_token.sym != EOD) {
        statement();
    }

    // 最後に } があるか
    check_symbol(R_BRACKET, "プログラムの終わりに '}' が必要です");
}

// メイン関数
int main() {
    // 最初のトークンを読み込んで準備
    getsym();

    // データの終わりでなければ解析開始
    if (current_token.sym != EOD) {
        program();
    }

    // ここまでエラーなく来れたら成功
    printf("No error.\n");

    return 0;
}

Uploaded by ともや

2026-01-300.01 MB