/*
* 課題: 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-30 • 0.01 MB