본문 바로가기
C++ 200제/코딩 IT 정보

TCC 컴파일러로 구현한 스캐너 문법 예제 (Tiny C Compiler)

by vicddory 2018. 9. 26.

TCC 컴파일러로 구현한 스캐너 문법 예제 (Tiny C Compiler)


이 자료는 제가 대학원생 시절에 과제로 했던 내용으로 아래에 삽입된 그림들을 보시면 아시겠지만, 윈도우 XP에서 작업했었습니다. 최근에 윈도우 7에서 10으로 넘어가며 Tiny-C 컴파일러에 대한 몇 가지 이슈를 들었습니다.


대표적인 것은 tcc 파일을 인식하지 못한다거나, 인식하더라도 오동작을 한다는 것이죠. 이건 임베디드 쪽에서도 마찬가집니다. 우분투를 비롯해 최근에 32비트를 버리는 쪽으로 시대가 기울고 있기에 상대적으로 지원이 미비해지는 점도 있긴 합니다만, 자세한 건 저희로선 알 수가 없어요.


이 자료는 TCC 컴파일러 감을 잡거나, 아직 XP를 사용하는 분들에게 적합합니다. 그동안 시간이 많이 지나 곳곳에서 변화한 것이 많습니다.


다만, 아직도 TCC 컴파일러에 대해 자세히 설명하고 응용까지 보여주는 사이트가 없다는 건 많이 아쉽습니다. 정말 우리가 IT 강국으로 다가서려면 이런 낮은 수준의 기술을 다루는 단체와 개인이 늘어나야 합니다.


어쨌든, Tiny-C가 뭔지 컴파일러가 뭔지 기타 등등의 서론은 생략하고 바로 본론으로 갑니다. 


Understand Tiny Scanner


1. Tiny 환경 설정

1-1 Tiny-C 컴파일러 (TCC 컴파일러)


아래 사이트로 이동하여 사용자 환경에 맞는 파일을 다운로드합니다.



Tiny-C 컴파일러 zip 파일 등록 정보[윈도우 개발환경 구축] zip 파일 등록 정보


Tiny-C 컴파일러 zip 파일 구성 요소[윈도우 개발환경 구축] zip 파일 구성 요소


압축을 해제한 뒤 해당 폴더에서는 아래와 같은 명령어로 컴파일을 통한 실행 파일 생성 가능


ex) > tcc main.c


1-2 Tiny Scanner 실행


TCC 컴파일러 교재(고급 컴파일러 구성론(Advanced Compiler Construction))에서 제공하는 소스 코드 자체는 바로 실행을 할 수 없기에 Scan.c 파일에 대한 수정이 필요합니다.


1
2
3
4
5
6
7
8
9
10
11
12
#include<stdio.h>    // 추가
 
static int lineno = 0// 입력 라인을 카운팅하는 전역 변수를 추가
 
void main()
{
     while(1
     {
          TokenType a;
          a = getToken();
     } // Scanner 사용을 위하여 main() 추가
}    
cs


1-3 실행 화면


Tiny-C 예제 실행[윈도우 개발환경 구축] 예제 실행


입력 값

출력 값

enum 설정 값

비고

 기타 특수문자

 1

 ERROR

 

 if

 2

 IF

 Reserved Words

 then

 3

 THEN

 else

 4

 ELSE

 end

 5

 END

 repeat

 6

 REPEAT

 until

 7

 UNTIL

 read

 8

 READ

 write

 9

 WRITE

 문자열

 10

 ID

 Multicharacter tokens

 숫자

 11

 NUM

 :=

 12

 ASSIGN

 Special Symbols

 =

 13

 EQ

 <

 14

 LT

 +

 15

 PLUS

 -

 16

 MINUS

 *

 17

 TIMES

 /

 18

 OVER

 (

 19

 LPAREN

 )

 20

 RPAREN

 ;

 21

 SEMI


표) 입력 값에 따른 출력 값과 헤더 파일의 enum에 설정된 값의 비교


C-Minus Scanner 구현


1. C-의 어휘 규칙

1-1 키워드

else - if - int - return - void - while


1-2 특수 심볼

+ - * / < <= > >= == != = ; , ( ) [ ] { } /* */


1-3 문자

소문자와 대문자는 서로 다른 문자로 취급


1-4 공백 문자

빈자리, 줄 바꿈, 탭이 존재하며 보통은 무시하나 ID와 NUM 가운데 공백 문자가 있어야 분리됨


1-5 주석

/*와 */로 둘러싸며, 한 줄 이상이어도 되지만 중첩될 수는 없음


2. 프로그램 소스


-Scanner.c-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<ctype.h>
 
#define NO_KEYWORDS 6
#define ID_LENGTH 12
 
enum tsymbol{
    tnull = -1, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13, s14, s15, s16, s17, s18, s19, 
               s20, s21, snumber, sident, selse, sif, sint, sreturn, svoid, swhile };
    // 20개의 특수 심볼, 숫자, 문자열, 6개의 키워드 순
 
struct tokenType {
    int number;    // 입력된 숫자를 저장
    char sym[2];    // 특수 심볼 지정
    union{
        char id[ID_LENGTH];
        int num;
    } value;        // 키워드 지정
} a;
 
char *keyword[NO_KEYWORDS] = {
    "else""if""int""return""void""while" };
 
enum tsymbol tnum[NO_KEYWORDS] = {
    selse, sif, sint, sreturn, svoid, swhile };
    // 키워드의 순서와 순서에 맞는 문자열 선택을 위한 코드
 
int superLetter(char ch) {
    if(isalpha(ch) || ch == '_'return 1;
    else return 0; }
    // 대문자(1), 소문자(2)일 경우 1을 리턴
 
int superLetterOrDigit(char ch){
    if(isalnum(ch) || ch == '_'return 1;
    else return 0; }
     // 대문자(1), 소문자(2), 숫자(4)일 경우 1을 리턴
 
int getIntNum(char firstCharacter) {
    int num = 0;
    char ch;
 
    if(firstCharacter != '0'){
        ch = firstCharacter;
        do{
            num = 10 * num + (int)(ch - '0');
            ch = getchar();
        } while(isdigit(ch)); 
    } // 숫자 여부 확인 후 반환
 
    else { num = 0; }
    ungetc(ch, stdin);
    return num; }
 
struct tokenType scanner() {
    struct tokenType token;
    int i=0, index=0;
    char ch, id[ID_LENGTH];
    token.number = tnull;
 
do {
    while (isspace(ch = getchar()));
    // 입력 받는 값이 공백이 아니면 while 탈출
 
    if(superLetter(ch)){ // 대문자, 소문자일 경우
        i = 0;
        do{
            if(i < ID_LENGTH) {
                id[i++= ch; }
            ch = getchar();
        } while(superLetterOrDigit(ch));
    // 특수문자, 숫자 여부 판별
 
        id[i] = '\0';
        ungetc(ch, stdin); // 읽혀진 문자를 다시 스트림으로 반환
        index = 0;
        while(index > NO_KEYWORDS) {
            if(!strcmp(id, keyword[index])) break// 키워드 검색
            if(index < NO_KEYWORDS) {      // 키워드의 위치와 문자열 복사
                token.number = tnum[index];
                strcpy(token.value.id, id); }
            else{
                token.number = sident;
                strcpy(token.value.id, id); }
            // 찾지 못하면 변수로서 문자열 복사
            index++;
        }
 
    else if(isdigit(ch)){ // 숫자일 경우 - 숫자면 true, 아니면 false
        token.number = snumber;
        token.value.num = getIntNum(ch);
    }
 
    else switch(ch){    
    // 심볼일 경우 enum에서의 위치와 해당 심볼의 문자열을 구조체 변수에 복사
        case '*' :
            ch = getchar();
            if(ch == '/') {
                token.number = s21;
                strcpy(token.sym, "*/"); }
            else{
                token.number = s3;
                strcpy(token.sym, "* ");
                ungetc(ch, stdin); } break;
        case '/' :
            ch = getchar();
            if(ch == '*') {
                strcpy(token.sym, "/*");
                token.number = s20; }
            else{
                strcpy(token.sym, "/ ");
                token.number = s4;
                ungetc(ch, stdin); } break;
        case '<' :
            ch = getchar();
            if(ch == '=') {
                strcpy(token.sym, "<=");
                token.number = s6; }
            else{
                strcpy(token.sym, "< ");
                token.number = s5;
                ungetc(ch, stdin); } break;
        case '>' :
            ch = getchar();
            if(ch == '=') {
                strcpy(token.sym, ">=");
                token.number = s8; }
            else{
                strcpy(token.sym, "> ");
                token.number = s7;
                ungetc(ch, stdin); } break;
        case '=' :
            ch = getchar();
            if(ch == '=') {
                strcpy(token.sym, "==");
                token.number = s9; }
            else{
                strcpy(token.sym, "= ");
                token.number = s11;
                ungetc(ch, stdin); } break;
        case '!' :
            ch = getchar();
            if(ch == '=') {
                strcpy(token.sym, "!=");
                token.number = s12; }
            else printf("Error\r\n"); break;
 
        case '+' : token.number = s1;    strcpy(token.sym, "+ ");    break;
        case '-' : token.number = s2;    strcpy(token.sym, "- ");    break;
        case ';' : token.number = s12;    strcpy(token.sym, "; ");    break;
        case ',' : token.number = s13;    strcpy(token.sym, ", ");    break;
        case '(' : token.number = s14;    strcpy(token.sym, "( ");    break;
        case ')' : token.number = s15;    strcpy(token.sym, ") ");    break;
        case '[' : token.number = s16;    strcpy(token.sym, "[ ");    break;
        case ']' : token.number = s17;    strcpy(token.sym, "] ");    break;
        case '{' : token.number = s18;    strcpy(token.sym, "{ ");    break;
        case '}' : token.number = s19;    strcpy(token.sym, "} ");    break;
        case EOF : token.number = s21;    strcpy(token.sym, "!=");    break;
 
        default: {
            printf("Current character : %c", ch);
            printf("Error\r\n");
            break; }
        }
    } while(token.number == tnull);
    return token; };
    
void main() {
    printf("Input String : \n");
 
    while(1) {        
        a = scanner();
        if(a.number < 21printf("Symbol : %s\n", a.sym);
        else if(a.number > 22 && a.number < 29printf("Keyword : %s\n", a.value.id);
        else if (a.number == 21printf("Number : %d \n", a.value.num);
        else if (a.number == 22printf("Variable : %s\n", a.value.id);
        else { }
        // 각각의 경우에 맞는 출력문
    }
}
cs


아래는 위 TCC 컴파일러로 구현한 스캐너 코드의 실행 결과.


C-Minus 스캐너 결과[윈도우 개발환경 구축] 결과


스캐너가 정상적으로 작동했음이 확인됩니다. 제가 이전에 Tiny Scanner을 응용한 (C-Minus 스캐너 소스 [링크])를 올린 적이 있는데, 그때 비하면 이 포스트는 일종의 프리퀄이랄까요?


C-Minus 스캐너를 만들기 위한 기초 지식에 해당합니다.


Tiny Scanner를 이해하기 좋은 책은 제가 예전에 교재로 봤었던, 고급 컴파일러 구성론이 괜찮습니다. 시간이 지나도 종이에 적힌 글의 질은 떨어지질 않으니깐요.


TCC 컴파일러로 구현한 스캐너 문법 예제 (Tiny C Compiler)

댓글