파싱 - 1

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

파싱

렉싱한 토큰을 가지고 토큰 사이의 유의미한 관계를 정리하는 것으로 이해함
일단은 LET 파싱부터 한다.

LET문 파싱

let은 monkey언어에서 (저저가 만든 가상의 프로그래밍 언어임)
변수와 표현식을 바인딩한다.

허나 지금은 표현식 파싱은 건너뛰고 let문을 파싱한다.

monkey언어에서 let문은
let <idenrifier> = <expression>; 구조를 띈다.

program 구조체에 statements 배열 포인터로 한줄씩 이어서 연결후
첫번째 statments[0]에는 letStatement가 
letStatement에는 해당 바인딩된 변수명과 표현식등이 이어져 내려오는 방식으로 보임

헤더파일

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<stdbool.h>
#include "token.h"

// Node 인터페이스를 위한 함수 포인터 정의
typedef const char* (*TokenLiteralFunc)(void*);


typedef struct _node {
    TokenLiteralFunc TokenLiteral;
}Node;

// Statement, Expression 구조체 정의 (Node "상속" 역할)
typedef struct _statement {
    Node node;
    void (*statementNode)(void*);      // 더미 메서드
} Statement;

typedef struct _expression {
    Node node;      // TokenLiteral 메서드
    void (*expressionNode)(void*);     // 더미 메서드
} Expression;

// Identifier 구조체 정의
typedef struct _identifier {
    Token* token;                      // Identifier의 토큰
    TokenLiteralFunc TokenLiteral;     // TokenLiteral 메서드
    char* value;                       // Identifier 값
} Identifier;

// LetStatement 구조체 정의
typedef struct _letStatement {
    Node node;              // Statement 포함 (상속 역할)
    Token* token;                      // let 키워드 토큰
    Identifier* name;                  // 변수 이름
    Expression* value;                 // 변수 값
} LetStatement;

// Program 구조체 정의
typedef struct _program {
    Statement** statements;            // Statement 포인터 배열
    int statementsNum;                 // Statement 수
} Program;

// 함수 선언

// LetStatement 관련 함수
LetStatement* newLetStatement();
const char* letTokenLiteral(void* self);

// Identifier 관련 함수
Identifier* newIdentifier();
const char* identifierTokenLiteral(void* self);

// Program 관련 함수
const char* programTokenLiteral(Program* p);

코드

#include "ast.h"
#include<string.h>


const char* letTokenLiteral(void* self) {
    LetStatement* ls = (LetStatement*)self; // self를 LetStatement로 변환
    return ls->token->literal;  // LetStatement에 대한 TokenLiteral 반환
}

const char* identifierTokenLiteral(void* self) {
    Identifier* id = (Identifier*)self; // self를 Identifier로 변환
    return id->token->literal;  // Identifier에 대한 TokenLiteral 반환
}

// Program 메서드 구현 (프로그램에서 첫 번째 Statement의 TokenLiteral 호출)
const char* programTokenLiteral(Program* p) {
    if (p->statementsNum > 0) {
        return p->statements[0]->node.TokenLiteral(p->statements[0]);
    }
    return "";
}

// LetStatement 및 Identifier 객체 생성 함수
LetStatement* newLetStatement() {
    LetStatement* ls = (LetStatement*)malloc(sizeof(LetStatement));
    // LetStatement의 TokenLiteral 함수 포인터를 설정
    ls->node.TokenLiteral = letTokenLiteral;

    return ls;
}

Identifier* newIdentifier() {
    Identifier* id = (Identifier*)malloc(sizeof(Identifier));
   
    id->TokenLiteral = identifierTokenLiteral;
    return id;
}

파서

헤더

#pragma once
#include "ast.h"
#include "lexer.h"
#include "token.h"

typedef struct _parser {
	Lexer* l;
	Token* curToken;
	Token* peekToken;
}Parser;

Parser* NewParser(Lexer* l);
void pNextToken(Parser* p);
Program* ParseProgram(Parser* p); 
Statement* parseStatement(Parser* p);
LetStatement* parseLetStatement(Parser*p);
bool curTokenIs(Parser* p,TokenType t);
bool peekTokenIs(Parser* p, TokenType t);
bool expectPeek(Parser* p, TokenType t);

코드

#include "parser.h"
#include <stdio.h>
#include<stdlib.h>

Parser* NewParser(Lexer* l)
{
    Parser* parser = (Parser*)malloc(sizeof(Parser));  // Parser 메모리 할당
    parser->l = l;  // Parser의 l 필드를 주어진 Lexer 포인터로 초기화
   
    pNextToken(parser);
    pNextToken(parser);//토큰 세팅

    return parser;
}

void pNextToken(Parser* p)
{
    p->curToken = p->peekToken;
    p->peekToken = NextToken(p->l);
}

Program* ParseProgram(Parser* p)
{
    Program* program = (Program*)malloc(sizeof(Program));
    program->statements = NULL;
    program->statementsNum = 0;
    //프로그램 만든후(statements 저장할 구조체) 초기화


    //모든 줄을 다 읽을때까지 반복
    while (p->curToken->type != EOF_TOKEN) {
        Statement *stmt = parseStatement(p);
        if (stmt != NULL) {
            program->statementsNum++;
            program->statements = (Statement**)realloc(program->statements, sizeof(Statement*) * program->statementsNum);
            program->statements[program->statementsNum - 1] = stmt;
        }

        pNextToken(p);
    }
    return program;
}

Statement* parseStatement(Parser* p) {
    const char* type = p->curToken->type;

    if (strcmp(type, LET_TOKEN) == 0)
        return (Statement*)parseLetStatement(p);
    return NULL;
}

LetStatement* parseLetStatement(Parser* p) {
    LetStatement *stmt = newLetStatement();
    stmt->token = p->curToken;
    //stmt->token에 LET 토큰 저장

    if (!expectPeek(p, IDENT_TOKEN)) {
        return NULL;
    }//LET 이후 변수명이 나오는지 검사

    stmt->name = newIdentifier();
    //name은 변수명을 의미함 변수명에 현재 어떤 토큰인지 
    stmt->name->token = p->curToken;
    stmt->name->value = p->curToken->literal;
    
    if (!expectPeek(p, ASSIGN_TOKEN)) {
        return NULL;
    }//만약 다음이 = 인지 검사

    //TODO: 세미콜론을 만날때까지 건너뜀

    while (!curTokenIs(p, SEMICOLON_TOKEN)) {
        pNextToken(p);
    }//세미 콜론까지 일단 제낌


    //test로 Let x = 10;이 들어온다면 현재 Let과 x만 저장되고 뒷부분은 날림
    return stmt;
}

bool curTokenIs(Parser* p, TokenType t)
{
    return p->curToken->type==t;
}

bool peekTokenIs(Parser* p, TokenType t)
{
    return p->peekToken->type==t;
}

bool expectPeek(Parser* p, TokenType t)
{
    if (peekTokenIs(p,t)) {
        pNextToken(p);
        return true;
    }
    return false;
}

일단은 표현식은 제쳐두고 변수만 저장되게함

어려웠던점

아무래도 go언어를 이용하는 프로그램이다 보니 인터페이스와 구현 등을 자주 활용함.
하지만 c에서는 해당부분이 안되서 힘들었음

다른거는 함수 포인터 등을 이용해서 멤버함수 비스무리하게 구현은 했지만
제일 어려웠던 것은 program 구조체의 statements 배열 포인터에 업캐스팅해서 집어넣는 부분

곰곰히 생각도 해보고 구글링도 해보고 지피티도 써봤더니 결론은
어차피 포인터의 크기는 동일 하니 (4또는 8바이트) 형변환이 가능함

문제는 데이터에 접근할때 접근가능한 크기가 형태에 따라 달라진다는 것임

char은 1byte씩 int는 4byte씩 접근이 가능함

그렇다면 statement*는 statement의 구조체 크기만큼 접근이 가능하니
letStatement의 첫번째 구성요소로 statement를 잡아놓고

형변환을 한다면 업캐스팅이 된 영역에서도 둘다 공통으로 가지고 있는
node 구조체에는 접근이 가능해짐

이 방법을 통해서 해결함

아직은 node에 업캐스팅해서 노드에 접근할 일이 없지만 나중에 생길것이라 생각중

또한 잘못 생각했던게 어차피 node가 겹치니까 그냥 node지우고 이걸 statement에 넣은후에
statement를 가지면 그게 상속이 아닌가? 했는데

tStatement ls가 있고 ls -> statement로 접근하게 된다면
statement
s=(statement*)ls;
를 하게 된다면 s->statment라는 말도 안되는 접근이 발생하게 됌

그냥 statement만 두고 node 지운다음에 해도 상관없었음

예를 들어 letStatement에는 statement를 포함한 다른 멤버가있고
statement는 따로 있다고 할때

원래라면 ls->statement->~~ 로 접근해야했던 것들이
업캐스팅후에는 s->~~ 바로 접근이 가능했음.

아마도 결국 멤버에 대한 접근이건 뭐건 메모리 상의 위치와 크기만 같으면 같은 데이터를 뽑으니까
업캐스팅을 한 상태로 접근을 하더라도 해당 영역은 동일한 크기와 동일한 주소임으로
그냥 접근이 가능한 것으로 보임.

혼자 머리 굴린거라 뇌피셜이긴 한데 ....

암튼 그냥 이게 더 깔끔해보여서 node 구조체를 만든후 해당 크기를 같이 잡는 방식으로 간접적으로 상속 느낌을 냄

profile
학생

0개의 댓글