렉서 - 1

김현우·2025년 1월 25일
0
post-thumbnail

어휘분석

소스코드가 들어오면 이걸 "렉싱" 처리하여 토큰으로 만들어주고
이 토큰을 이용하여 트리를 만든다.

예를 들어 렉서는 let x = 5 + 5; 라는 명령이 들어온다면

[
	LET,
    IDENTIFIER('x'),
    EQUAL_SIGN,
    INTEGER(5),
    PLUS_SIGN,
    INTEGER(5),
    SEMICOLON
]

꼴로 토큰을 나눠서 만들어낸다.

오늘은 이 렉싱을 하는 렉서를 만들어본다.

토큰 정의

첫번째

TOKEN 헤더파일을 정의하자.
토큰 구조체를 만들고 토큰들을 상수 문자열로 만들어 둔다.

#pragma once

const char* ILLEGAL_TOKEN = "ILLEGAL_TOKEN";
const char* EOF_TOKEN = "EOF";
const char* IDENT_TOKEN = "IDENT";
const char* INT = "INT";

//연산자
const char* ASSIGN_TOKEN = "=";
const char* PLUS_TOKEN = "+";

//구분자
const char* COMMA_TOKEN = ",";
const char* SEMICOLON_TOKEN = ";";

const char* LPAREN_TOKEN = "(";
const char* RPAREN_TOKEN = ")";
const char* LBRACE_TOKEN = "{";
const char* RBRACE_TOKEN = "}";

//예약어
const char* FUNCTION_TOKEN = "FUNCTION";
const char* LET_TOKEN = "LET";

typedef const char* TokenType;

typedef struct _token {
	TokenType type;
	char* literal;
	//type은 enum으로 저장해 두다가 나중에 필요하면 TokenType으로 사용하고
	//literal은 어떤건지 특정하게 받아오는것 얘를 들어 5가 저장되고 이걸 INT로 나중에 토큰 타입으로 두기
}Token;

위 와같이 선언시에
ILLEGAL_TOKEN이(가) lexer.obj에 이미 정의되어 있습니다. 와 같은 오류들이 발생함

헤더가 여러 소스에서 선언되면서 선언이 중복되는 오류가 발생한다고 함

내가 헷갈렸던거는 pragma once가 있다면 이게 중복 선언을 방지한다고 배웠는데
왜 이런 오류가 발생하는 건지 헷갈렸음

하지만 pragma once는 a.c 라는 코드 내에서 같은 헤더가 여러번 선언 되는걸 방지해줄 뿐이지
b.c에서 또 헤더를 선언한다면 그건 그대로 포함시켜준다고 함

따라서 현재 헤더에서 변수가 여러번 정의 되는 문제가 생긴것

이를 해결하려면 static으로 선언하거나 extern을 쓰던가
매크로로 만들어야 한다고 해서 나는 매크로로 만듬.

변경본

#pragma once

#define ILLEGAL_TOKEN "ILLEGAL_TOKEN"
#define EOF_TOKEN "EOF"
#define IDENT_TOKEN "IDENT"
#define INT "INT"

// 연산자
#define ASSIGN_TOKEN "="
#define PLUS_TOKEN "+"

// 구분자
#define COMMA_TOKEN ","
#define SEMICOLON_TOKEN ";"

#define LPAREN_TOKEN "("
#define RPAREN_TOKEN ")"
#define LBRACE_TOKEN "{"
#define RBRACE_TOKEN "}"

// 예약어
#define FUNCTION_TOKEN "FUNCTION"
#define LET_TOKEN "LET"

typedef const char* TokenType;

typedef struct _token {
	TokenType type;
	char* literal;
	//literal은 어떤건지 특정하게 받아오는것 얘를 들어 5가 저장되고 이걸 INT로 나중에 토큰 타입으로 두기
}Token;

렉서

테스트 코드

#include<stdio.h>
#include "token.h"
#include "lexer.h"
#include<string.h>

int main() {
	const char* input = "=+(){},;";
	
	Token tests[] = {
		{ASSIGN_TOKEN,"="},
		{PLUS_TOKEN,"+"},
		{LPAREN_TOKEN,"("},
		{RPAREN_TOKEN,")"},
		{LBRACE_TOKEN,"{"},
		{RBRACE_TOKEN,"}"},
		{COMMA_TOKEN,","},
		{SEMICOLON_TOKEN,";"},
		{EOF_TOKEN,""}
	};//테스트케이스 생성

	Lexer* lexer = New(input);
	//렉서를 만든후 분휴 시작

	int len = sizeof(tests) / sizeof(Token);
	
	for (int i = 0; i < len; i++) {
		Token* tok = NextToken(lexer);

		if (tok->type != tests[i].type) {
			printf("tests[%d] - tokentype wrong. expected = %s, got = %d\n", i,tests[i].type, tok->type);
		}

		if (strcmp(tok->literal, tests[i].literal) != 0) {
			printf("tests[%d] - literal wrong. expected = %s, got = %s\n", i, tests[i].literal, tok->literal);
		}

		printf("tests[%d]'s type = %s, literal = %s\n", i, tests[i].type, tok->literal);
		//free(tok->literal);
		free(tok);
	}

	free(lexer->input);
	free(lexer);
	//l은 input을 렉서해서 받아옴
	//받아온다면 tests랑 비교하기
}

테스트 코드를 받아서
렉서를 통해 한 글자씩 돌린다음에 원하는 결과가 제대로 나왔는지 비교할것

하지만 아직 렉서가 없으니 렉서를 만들자

렉서

헤더

#pragma once
#include"token.h"

typedef struct _lexer {
	char* input;
	int position;//입력에서 현재 위치(현재 문자)
	int readPosition;//입력에서 현재 읽는 위치(현재 문자의 다음)
	char ch;//현재 조사하는 문자
}Lexer;

Lexer* New(char* input);
void readChar(Lexer* lexer);
Token* NextToken(Lexer* lexer);
Token* newToken(TokenType token,char *input);

인풋을 받으면 렉서를 만든다음에
한글자씩 읽으면서 알맞은 토큰 구조체를 생성해서 끼워넣을 계획
소스코드
#include "lexer.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>

Lexer* New(char* input)
{
    //인풋 받으면 새롭게 Lexer 구조체 만들어서 input 저장해두는 함수
    Lexer* l = (Lexer*)malloc(sizeof(Lexer));

    l->input = _strdup(input);

    if (!l->input) {
        fprintf(stderr, "Memory allocation failed for input string\n");
        free(l);
        exit(EXIT_FAILURE);
    }//인풋이 없을때 오류메시지 생성과 종료

    l->ch = 0;
    l->position = 0;
    l->readPosition = 0;
    readChar(l);

    return l;
}

void readChar(Lexer* lexer)
{
    if (lexer->readPosition > strlen(lexer->input)){
        lexer->ch = 0;//참조를 넘어가면 ch에 0넣기
    }
    else {
        lexer->ch = lexer->input[lexer->readPosition];
    }

    lexer->position = lexer->readPosition;
    (lexer->readPosition)++;
    //미리 살펴보고 해도 되나 검사
    //또한 현재 문자를 일단 보관을 해야하니
}

Token* NextToken(Lexer* lexer)
{
    Token *tok=NULL;
    char lch = lexer->ch;

    switch (lch)
    {
    case '=':
        tok = newToken(ASSIGN_TOKEN, lch);
        break;
    case ';':
        tok = newToken(SEMICOLON_TOKEN, lch);
        break;
    case '(':
        tok = newToken(LPAREN_TOKEN, lch);
        break;
    case ')':
        tok = newToken(RPAREN_TOKEN, lch);
        break;
    case ',':
        tok = newToken(COMMA_TOKEN, lch);
        break;
    case '+':
        tok = newToken(PLUS_TOKEN, lch);
        break;
    case '{':
        tok = newToken(LBRACE_TOKEN, lch);
        break;
    case '}':
        tok = newToken(RBRACE_TOKEN, lch);
        break;
    case 0:
        tok = (Token*)malloc(sizeof(Token));

        if (tok == NULL) {
            printf("Memory allocation failed for tok.\n");
            exit(1); // 프로그램 종료
        }

        tok->type = EOF_TOKEN;
        tok->literal = _strdup("");
        break;
    }

    readChar(lexer);

    return tok;
}


Token*newToken(TokenType token, char* input)
{
    Token* newToken = (Token*)malloc(sizeof(Token));
    char* str = (char*)malloc(2);
    str[0] = input;  // 입력받은 char를 첫 번째 위치에 저장
    str[1] = '\0';   // 문자열 종료 문자 추가

    //토큰 받으면 input 받아서 리터럴 처리한후에 토큰 객체 돌려주기
    //newToken->literal = _strdup(TokenTypeNames[token]);
    //그냥 어차피 상수 포인터로 가르키기만 할건데 이렇게 해도 될듯...?
    newToken->type = token;
    newToken->literal = _strdup(str);
    return newToken;
}

이때 상수로 받아서 상수 포인터로 연결하면 
나중에 해당 상수가 사라졌을때 문제가 발생할수 있을거 같아서

_strdup을 통해서 복사해서 그만큼 할당해서 넣음

또한 newToken에서 받는 input은 아직은 char형 한 글자이기에 문자열로 바꿔서 저장해줌

테스트 결과

profile
학생

0개의 댓글

관련 채용 정보