티스토리 뷰

목차

    반응형

    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)

    반응형