반응형
  • Generator는 목적 코드를 생성한다.
    • 목적코드는 기계가 읽을 수 있는 바이너리 형태의 코드이다. 
  • Parser에서 syntax tree를 만들었었다.
    • 트리 구조는 비선형이라 순회가 비교적 느리다.
    • 트리를 선형 구조로 만들어서 속도를 높인다. 
  • Generator는 비선형 구조인 syntax tree를 선형 구조로 표현하는 과정이다. 
  • 우리는 가상머신을 만들고 그 위에서 동작할 수 있는 목적코드를 생성할 것이다. 

 

명령어 만들기


Code.h

 

#include <any>
using std::any;

enum class Instruction
{
	Exit,
	Call, Alloca, Return,
	Jump, ConditionJump,
	Print,

	LogicalOr, LogicalAnd,
	Add, Subtract,
	Multiply, Divide, Modulo,
	Equal, NotEqual,

	LessThan, GreaterThan,
	LessOrEqual, GreaterOrEqual,
	Absolute, ReverseSign,

	GetElement, SetElement,
	GetGlobal, SetGlobal,
	GetLocal, SetLocal,

	PushNull, PushBoolean,
	PushNumber, PushString,
	PushArray, PushMap,
	PopOperand,
};

struct Code
{
	Instruction instruction;
	any operand;
};

Node.h

struct Statement {
    virtual auto generate() -> void = 0;
};
struct Expression {
    virtual auto generate() -> void = 0;
};

typedef struct Function : Statement {
    wstring name;
    vector<wstring> parameters;
    vector<Statement*> blocks;
    auto generate() -> void;
} Function;

struct Or : Expression {
    Expression* lhs;
    Expression* rhs;
    auto generate() -> void;
};
  • Statement와 Expression에 generate() 가상함수를 만든다.
  • Generator.cpp에 구현체 스켈레톤을 만들어준다.

 

Main.cpp

tuple<vector<Code>, map<string, size_t>> generate(Program* syntaxTree);
auto printObjectCode(tuple<vector<Code>, map<string, size_t>>) -> void;

int main() {
    string sourceCode = R"(
        함수 시작() {
            출력("안녕");
            출력("\n");
            출력(1 + 2 * 3);
            출력("\n");
        }
    )";

    vector<Token> tokenList = scan(sourceCode);
	Program* syntaxTree = parse(tokenList);
	auto objectCode = generate(syntaxTree);
    printObjectCode(objectCode);

    return 0;
}
  • generate 함수를 작성해주었다.

 

Generator.cpp

  • korlang에서는 시작() 이라는 함수가 시작점이다.
  • 함수를 호출하려면, 함수의 주소가 필요하다.
auto generate(Program* program) -> tuple<vector<Code>, map<wstring, size_t>>
{
    wstring main = L"시작";
    writeCode(Instruction::GetGlobal, main);
	//...
}
  • generate함수는 "시작()"함수의 주소를 가져오는 GetGlobal 코드를 생성함으로써 시작한다.
    • 지금의 과정은 프로그램을 해석하고 실행하는 것이 아니다.
    • 목적코드를 작성하는 것. 트리 형태의 프로그램을 선형으로 나타내는 것이다.
static vector<Code> codeList;

auto writeCode(Instruction instruction) -> size_t {

    codeList.push_back({ instruction });
    return codeList.size() - 1;

}

auto writeCode(Instruction instruction, any operand) -> size_t {

    codeList.push_back({ instruction, operand });
    return codeList.size() - 1;

}
  • writeCode()에서는 명령어를 만들어서 codeList에 삽입한다.
  • 반환하는 것은 목적 코드의 상대주소이다. 
auto generate(Program* program) -> tuple<vector<Code>, map<wstring, size_t>>
{
    wstring main = L"시작";
    writeCode(Instruction::GetGlobal, main);
    writeCode(Instruction::Call, static_cast<size_t>(0));
    writeCode(Instruction::Exit);

    for (auto& node : program->functions)
    {
        node->generate();
    }

    return {codeList, functionTable};
}
  • 그 다음으로는 함수를 호출하는 Call 명령을 작성한다.
    • GetGlobal로 가져온 "시작()" 함수를 호출한다는 뜻이다. 
    • 2번째 인자는 매개변수가 0개라는 의미이다. 
  • 그 다음으로 프로그램의 종료, 즉 "시작()" 함수의 종료를 뜻하는 Exit 명령을 작성한다. 
  • 그리고 코드 리스트와 함수 테이블을 반환한다.
"시작()"함수의 바디를 쓰기도 전에 EXIT을 먼저 쓰는 이유가 무엇일까?
  • 앞에서 말했듯이 Generator의 과정은 목적 코드를 생성하는 과정이다.
  • 선형으로 코드를 만든다.
  • 실행할 때는, 순차적으로 실행하는 것이 아닌, Goto(jump)을 통해서 주소를 이동하여 실행할 수 있다.
  • 그래서 "시작()" 함수 안에 모든 코드를 목적코드로 바꾼 다음에 Exit을 호출하는 것이 아닌 미리 적어놓을 수 있는 것이다. 
    • 종료할 때는 Exit 주소로 jump 하면 되기 때문이다. 

 

  • Exit까지 작성한 다음. functions의 노드들을 생성한다.
  • functions는 Program 구조체의 멤버로, Parser에서 생성한 syntax tree이다.
struct Program {
    vector<Function*> functions;
};

 

  • korlang은 최상위에 function만 올 수 있다. 
static map<wstring, size_t> functionTable;

auto Function::generate() -> void {
    functionTable[name] = codeList.size();

    for (auto& node : blocks)
    {
        node->generate();
    }
    writeCode(Instruction::Return);

}
  • generate에서 program의 functions를 생성했을 때는 Function::generate() 로 들어온다.
  • 이제 함수의 바디를 목적 코드로 생성한다.
  • 이 함수에서 첫번째로 생성하는 코드가 함수의 주소가 된다.
    • functionTable에 함수의 주소를 등록해준다.
      • 나중에 프로그램을 시작할 때, functionTable의 함수명과 매핑된 주소를 보고 codeList에서 주소를 찾아간다.
  • 그 다음 함수의 바디를 생성한다.

 

// Print
auto Print::generate() -> void {
    for (int i = arguments.size(); i >= 0; i--)
    {
        arguments[i - 1]->generate();
    }
    writeCode(Instruction::Print, arguments.size());
}
  • "출력()"함수는 이렇게 작성한다.
  • 인자들을 먼저 생성하고, 그 다음 Print 를 작성한다.
    • 2번째 인자로 Print 문 인지의 개수를 전달한다.
      • 이것의 크기만큼 피연산자 스택에서 꺼내서 콘솔에 출력한다.
// NumberLiteral
auto NumberLiteral::generate() -> void {
    writeCode(Instruction::PushNumber, value);
}

// StringLiteral
auto StringLiteral::generate() -> void {
    writeCode(Instruction::PushString, value);
}

// Arithmetic
auto Arithmetic::generate() -> void {
    map<Kind, Instruction> instructions = {
            {Kind::Add, Instruction::Add},
            {Kind::Subtract, Instruction::Subtract},
            {Kind::Multiply, Instruction::Multiply},
            {Kind::Divide, Instruction::Divide},
            {Kind::Modulo, Instruction::Modulo}
    };

    lhs->generate();
    rhs->generate();
    writeCode(instructions[kind]);
}
  • 이렇게 Instruction의 generate()함수도 작성해준다.

 

auto ExpressionStatement::generate() -> void {
    expression->generate();
}
  • ExpressionStatement는 이런 코드가 있을 때 생성된다.
함수 시작()
{
	1 + 2;
}
  • 계산식이지만, 그 자체로 하나의 문이다.

  • 위의 코드로 generate를 수행하면 목적코드가 이렇게 나온다. 
  • 이 상태에서 1+2의 결과인 3은 피연산자 스택에 남아있게 된다.
  • 피연산자 스택에서 같은 가져와 버리는 코드를 작성해야한다.
auto ExpressionStatement::generate() -> void {
    expression->generate();
    writeCode(Instruction::PopOperand);
}

 

변수 선언

  • 변수를 어떻게 관리할 수 있을까?
    • 변수의 이름과 값을 key와 value로 해서 map에 관리 할 수 있다.
    • 변수의 값이 위치한 주소를 사용할 수 있다.
      • 재귀 호출 등을 생각했을 때, 상대주소로 사용해야한다.
static list<map<wstring, size_t>> symbolStack;
static vector<size_t> offsetStack;
static size_t localSize = 0;
  • symbolStack에서 map은 변수이름이 key이고, 상대주소 offset이 value이다.
    • 맵을 리스트로 갖고 있는 이유는, 변수의 유효범위를 관리하기 위함이다.
  • offsetStack은 유효범위 별로 오프셋을 관리한다. 해당 유효범위에서 사용할 수 있는 가장 큰 오프셋 값을 저장해둔다.
  • offsetStack에 담고있는 최대 오프셋 값은 메모리 공간의 크기와 같다.
    • 함수의 실행이 끝나면 지역 변수들 값을 버린다.
    • 지역 변수를 버리기 위해 오프셋 크기를 갖고 있는 변수가 localSize이다.

 

auto patchOperand(size_t codeIndex, size_t operand) -> void
{
	codeList[codeIndex].operand = operand;
}

auto initBlock() -> void
{
    localSize = 0;
	offsetStack.push_back(0);
    symbolStack.emplace_front();
}

auto popBlock() -> void
{
	offsetStack.pop_back();
	symbolStack.pop_front();
}

// Function
auto Function::generate() -> void {
    functionTable[name] = codeList.size();

    auto temp = writeCode(Instruction::Alloca);
    initBlock();
    for (auto& node : blocks)
    {
        node->generate();
    }
    popBlock();
    patchOperand(temp, localSize);

    writeCode(Instruction::Return);
}
  • Function::generate()함수에 이렇게 추가해준다.
    1. Instruction::Alloca 코드를 추가해서 함수 실행 전에 지역변수 용 공간을 확보한다.
    2. initBlock에서는 symbolStack, offsetStack, localSize를 초기화 한다.
    3. 함수 바디를 생성하고 난 후에, popBlock()으로 스택을 정리한다.
    4. 그리고 이제 지역변수의 크기를 알았으므로, Instruction::Alloca 명령의 인자를 업데이트 해준다.
auto setLocal(wstring name) -> void
{
    symbolStack.front()[name] = offsetStack.back();
    offsetStack.back() += 1;
	localSize = max(localSize, offsetStack.back());
}

auto getLocal(wstring name) -> size_t
{
	for (auto& symbolTable : symbolStack)
	{
        if (symbolTable.count(name))
        {
            return symbolTable[name];
        }
	}
	return SIZE_MAX;
}

// Variable
auto Variable::generate() -> void {
    setLocal(name);
    expression->generate();
	writeCode(Instruction::SetLocal, getLocal(name));
    writeCode(Instruction::PopOperand);
}
  • Variable::generate()는 이렇게 작성한다.
  • setLocal에서는 symbolstack에 변수명으로 공간을 만들고, offset을 1 증가시킨다.
  • 값을 생성한 다음, SetLocal 명령어를 작성한다. getLocal함수로 변수의 값을 받아와서 인자로 사용한다.
  • 그리고 피연산자 스택을 비운다.

변수 참조

이 언어에서는 선언이 없는 변수의 참조는 전역변수로 간주한다.

getLocal 함수가 SIZE_MAX를 반환하면 로컬 참조가 없는 것이다.

// GetVariable
auto GetVariable::generate() -> void {
	if (getLocal(name) != SIZE_MAX)
	{
		writeCode(Instruction::GetLocal, getLocal(name));
	}
	else
	{
		writeCode(Instruction::GetGlobal, name);
	}
}

// SetVariable
auto SetVariable::generate() -> void {
    value->generate();
    if (getLocal(name) != SIZE_MAX)
    {
		writeCode(Instruction::SetLocal, getLocal(name));
    }
    else
    {
        writeCode(Instruction::SetGlobal, name);
    }
}

'반복' 문

반복문의 제어변수는 반복문 스코프 안에서만 유효하다.

// For
auto For::generate() -> void {
    pushBlock();
    variable->generate();
    auto jumpAddress = codeList.size();
	condition->generate();

    auto conditionJump = writeCode(Instruction::ConditionJump);

    // 참인 경우에 실행할 코드
    for (auto& node : blocks)
    {
		node->generate();
    }

	expression->generate();
	writeCode(Instruction::PopOperand);
	writeCode(Instruction::Jump, jumpAddress);
	patchAddress(conditionJump);
	popBlock();
}
  1. 유효 범위를 정해주기 위해, 시작과 끝에 pushBlock(), popBlock()을 해주었다.
  2. 제어 변수를 생성하고,
  3. 점프 주소를 기록한다.
    1. jumpAddress는 반복문 순회를 위해 기록한다.
  4. 조건문을 생성하고, 
  5. 거짓일 때 점프하는 명령어를 작성한다.
  6. 다음으로 참일 때, 실행할 코드를 생성한다.
  7. i++ 증감식을 생성한다.
  8. 지역 변수를 제거하고,
  9. 다시 jumpAddress 주소로 가서 조건식을 평가 할 수 있게, jump 코드를 작성한다.
  10. 그리고 조건식이 거짓일 때, jump할 주소를 업데이트 해준다.

 

'만약' 문

  • 만약문은 여러 개의 조건문 (if, elif)과 블럭을 가진다.
  • 하나의 본문을 실행한 뒤 끝으로 점프해야한다.
auto If::generate() -> void {
    vector<size_t> jumpList;
    for (int i = 0; i < conditions.size(); i++)
    {
        conditions[i]->generate();
		auto conditionJump = writeCode(Instruction::ConditionJump);
        pushBlock();
		for (auto& node : blocks[i])
		{
			node->generate();
		}
        popBlock();
        jumpList.push_back(writeCode(Instruction::Jump));
		patchAddress(conditionJump);

    }
    if (!elseBlock.empty())
    {
        pushBlock();
        for (auto& node : elseBlock)
        {
            node->generate();
        }
        popBlock();
    }

	for (auto& jump : jumpList)
	{
		patchAddress(jump);
	}
}
  1. 조건문마다 실행 후, 만약 문의 마지막으로 이동해야 한다. Jump 문을 작성하기 위해 list를 생성한다.
    1. 코드 작성 전에는 마지막 주소가 어딘지 모르기 때문에 jump 코드 주소를 다 저장해둔다.
  2. conditions부터 작성하고, conditionaljump를 작성한다.
    1. jump하는 주소는 body를 다 적고, patch 해준다.
  3. body를 작성한다. 
    1. 바디의 스코프 설정을 해줘야한다. pushblock()/popblock()
  4. 조건문을 다 적었으면, else 문 케이스에 대해 적어준다.
  5. 그리고 if 문에 대한 목적코드를 모두 작성했으므로, jumplist를 patch해준다.

 

계속하기 문

  • 계속하기(continue)문은 for문의 증감식 주소로 점프하는 명령이다.
static vector<vector<size_t>> continueStack;

// Continue
auto Continue::generate() -> void {
    if (continueStack.empty())
    {
        return;
    }

    auto jumpCode = writeCode(Instruction::Jump);
	continueStack.back().push_back(jumpCode);
}
  • continueStack은 jump 코드의 주소를 담는 리스트이다. 
    • 2중 리스트인 이유는
      • for문 안에 continue가 여러개 올 수 있고,
      • for문이 여러개 나올 수 있기 때문이다. 
  • Continue::generate에서는 코드 작성하고 리스트에만 넣어주고 끝이다.
// For
auto For::generate() -> void {
    continueStack.emplace_back();
    pushBlock();
    variable->generate();
    auto jumpAddress = codeList.size();
	condition->generate();

    auto conditionJump = writeCode(Instruction::ConditionJump);

    // 참인 경우에 실행할 코드
    for (auto& node : blocks)
    {
		node->generate();
    }

    auto continueAddress = codeList.size(); // 증감식 주소
	expression->generate();
	writeCode(Instruction::PopOperand);
	writeCode(Instruction::Jump, jumpAddress);
	patchAddress(conditionJump);
	popBlock();

    for (auto& jump : continueStack.back())
    {
		patchOperand(jump, continueAddress);
    }
    continueStack.pop_back();
}
  • for문도 업데이트 해주었다.
  • break문도 같은 논리 이므로 비슷하게 구현된다. 다만 증감식 위치로 가는 것이 아니라, for문 종료 위치로 patch 해줘야한다.

 

함수 호출

// Call
auto Call::generate() -> void {
	for (auto i = parameters.size(); i > 0; i--)
	{
		parameters[i - 1]->generate();
	}
	sub->generate();
	writeCode(Instruction::Call, parameters.size());

}

// Function
auto Function::generate() -> void {
    functionTable[name] = codeList.size();

    auto temp = writeCode(Instruction::Alloca);
    initBlock();
    for (auto& name : parameters)
    {
		setLocal(name);
    }

    for (auto& node : blocks)
    {
        node->generate();
    }
    popBlock();
    patchOperand(temp, localSize);

    writeCode(Instruction::Return);
}

// Return
auto Return::generate() -> void {
    expression->generate();
	writeCode(Instruction::Return);
}
  • Call
    1. 파라미터를 역순으로 생성한다 -> 스택구조이기 때문
    2. 피연산자식 코드를 생성한다.
      1. 피연산자는 함수를 뜻한다. 
    3. Call 명령을 작성한다.
  • Function
    • 함수 Call에서 파라미터를 참조할 수 있게, Function::generate에서 local 변수로 등록해준다.
  • Return
    1. 반환식을 생성하고
    2. Return 코드를 작성한다.

 

배열

// ArrayLiteral
auto ArrayLiteral::generate() -> void {
    for (int i = values.size(); i > 0; i--)
    {
		values[i - 1]->generate();
    }
	writeCode(Instruction::PushArray, values.size());
}
  • 피연산자 스택에 배열을 넣는 코드를 생성한다.
  • 스택을 사용하기 때문에 역순으로 생성한다.

 

원소값 참조

  • 배열이나 맵의 원소를 참조하는 목적 코드이다.
  • arr[1] 이런거
auto GetElement::generate() -> void {
	sub->generate();
	index->generate();
	writeCode(Instruction::GetElement);
}
  1. 피연산자 식(배열 명)을 먼저 생성하고
  2. 인덱스를 작성한다.
  3. 그리고 명령어를 작성한다.

 

원소값 수정

auto SetElement::generate() -> void {
    value->generate();
    sub->generate();
	index->generate();
	writeCode(Instruction::SetElement);
}
  • GetElement에서 대입식 생성하는 코드만 추가되었다.

 

나머지 코드들도 작성해준다.

728x90
반응형

이전글

 

[컴파일러 만들기] 1. Scanner 만들기

Scanner 만들기한글로 된 언어를 만들거라, Korlang 이라고 이름을 지었다.https://github.com/Yoon6/korlang GitHub - Yoon6/korlang: korlang compiler.korlang compiler. Contribute to Yoon6/korlang development by creating an account on GitH

yoonda.tistory.com

 

 

Parser 만들기


Parser란?

  • Parser에서는 syntax analysis를 수행한다.
  • 작성한 소스 코드의 구조를 분석하는 과정이다. 
    • 문법에 맞는지 검사하고,Parser Tree를 만든다.
    • 에러 처리를 한다.
    • symbol 테이블을 만든다.

https://www.geeksforgeeks.org/introduction-to-syntax-analysis-in-compiler-design/

 

Introduction to Syntax Analysis in Compiler Design - GeeksforGeeks

Syntax analysis, or parsing, is a crucial step in compiler design that verifies the grammatical structure of source code by interpreting tokens produced during lexical analysis and constructing a parse tree or abstract syntax tree while detecting syntax er

www.geeksforgeeks.org

  • CFG, LL Parser, LR Parser 등의 개념은 잘 모르겠다. 
    • 더 공부해서 추가 포스팅 올리기

 

문과 식

  • 문 -> Statement
  • 식 -> Expression

Expression

  • 평가가 가능해서 하나의 값으로 표현되는 '식'을 말한다.
// 계산식
1 + 2 * 3 
a + b 

// 배열 연산자
arr[1]

// 함수 콜
getValue()
  • 이런 애들을 Expression이라고 한다.

 

Statement

  • 실행가능한 코드 조각을 말한다.
    • Expression도 Statement라고 볼 수 있다.

  • 복합문과 단일문으로 구분되며, for/if 처럼 다른 문을 포함할 수 있는 문을 복합문, return/continue 처럼 다른 문을 포함할 수 없는 문을 단일문이라고 한다.

 

1. Parser Tree 의 Node 만들기

가장 먼저 root 노드를 만들어주자.

Node.h

#ifndef KORLANG_NODE_H
#define KORLANG_NODE_H

#include "Kind.h"

struct Statement {};
struct Expression {};

typedef struct Function: Statement {
    wstring name;
    vector<wstring> parameters;
    vector<Statement> blocks;
} Function;

struct Program {
    vector<Function*> functions;
};

#endif //KORLANG_NODE_H
  • 책의 사양대로 따라했기 때문에, Program(Root Node)에서는 함수의 정의만 포함하고 있다.
    • 함수 호출은 Expression(식)이고, 함수 정의는 Statement(문)이다.

 

struct Return : Statement {
    Expression* expression;
};
  • 반환문은 반환식을 포함한다.

 

struct Variable : Statement {
    wstring name;
    Expression *expression;
};
  • 변수의 선언식이다.
  • 변수의 이름과 초기화식을 포함한다.

 

struct For : Statement {
    Variable *variable;
    Expression *condition;
    Expression *expression;
    vector<Statement *> blocks;
};
  • 변수 선언, 조건식, 증감식, 실행문 리스트를 포함한다.

 

이런 방식으로 언어에서 사용하는 Statement, Expression들을 추가해준다.

더보기
#ifndef KORLANG_NODE_H
#define KORLANG_NODE_H

#include "Kind.h"

struct Statement {};
struct Expression {};

typedef struct Function : Statement {
    wstring name;
    vector<wstring> parameters;
    vector<Statement> blocks;
} Function;

struct Program {
    vector<Function*> functions;
};

struct Return : Statement {
    Expression* expression;
};

struct Variable : Statement {
    wstring name;
    Expression* expression;
};

struct For : Statement {
    Variable* variable;
    Expression* condition;
    Expression* expression;
    vector<Statement*> blocks;
};

struct Break : Statement {};
struct Continue : Statement {};

struct If : Statement {
    vector<Expression*> conditions;
    vector<Statement*> blocks;
    vector<Statement*> elseBlock;
};

struct Print : Statement {
    vector<Expression*> arguments;
};

struct ExpressionStatement : Statement {
    Expression* expression;
};

struct Or : Expression {
    Expression* lhs;
    Expression* rhs;
};

struct And : Expression {
    Expression* lhs;
    Expression* rhs;
};

struct Relational : Expression {
    Kind kind;
    Expression* lhs;
    Expression* rhs;
};

struct Arithmetic : Expression {
    Kind kind;
    Expression* lhs;
    Expression* rhs;
};

struct Unary : Expression {
    Kind kind;
    Expression* sub;
};

struct Call : Expression {
    Expression* sub;
    vector<Expression*> arguments;
};

// 배열, map 원소의 참조
// arr[0]
struct GetElement : Expression {
    Expression* sub;
    Expression* index;
};

// arr[0] = 3; 
// 할당은 statement 아닌가?
struct SetElement : Expression {
    Expression* sub;
    Expression* index;
    Expression* value;
};

// 변수 참조
struct GetVariable : Expression {
    wstring name;
};

// 변수 수정
struct SetVariable : Expression {
    wstring name;
    Expression* value;
};

struct NullLiteral : Expression {};
struct BooleanLiteral : Expression {
    bool value = false;
};
struct NumberLiteral : Expression {
    double value = 0.0;
};
struct StringLiteral : Expression {
    wstring value;
};
struct ArrayLiteral : Expression {
    vector<Expression*> values;
};
struct MapLiteral : Expression {
    map<wstring, Expression*> values;
};
#endif //KORLANG_NODE_H

 

2. Parse 함수 만들기

main.cpp

vector<Token> scan(string sourceCode);
Program* parse(vector<Token> tokenList);
auto printTokenList(vector<Token>) -> void;
auto printSyntaxTree(Program*) -> void;

int main() {
    string sourceCode = R"(
        함수 시작() {
            출력("안녕");
            출력("\n");
            출력(1 + 2 * 3);
            출력("\n");
        }
    )";

    vector<Token> tokenList = scan(sourceCode);
	Program* syntaxTree = parse(tokenList);

    printSyntaxTree(syntaxTree);
    //printTokenList(tokenList);

    return 0;
}

main 함수에서 parsing을 하고, 트리를 출력해준다.

 

Parser.cpp

#include <vector>

#include "Node.h"
#include "Token.h"

static vector<Token>::iterator currentToken;

Program* parse(vector<Token> tokenList) {
	auto result = new Program();

	currentToken = tokenList.begin();

	while (currentToken->kind != Kind::EndOfLine)
	{
		switch (currentToken->kind)
		{
		case Kind::Function:
			result->functions.push_back(parseFunction());
			break;
		default:
			cout << *currentToken << " 잘못된 구문입니다.";
			exit(1);
		}
	}


	return result;
}
  • 현재 스펙 상, 프로그램에 포함될 수 있는 것은 함수 밖에 없다.
  • 그래서 parse 함수는 이걸로 끝이다.

 

bool skipCurrentIf(Kind kind)
{
	if (currentToken->kind != kind)
	{
		return false;
	}

	currentToken++;
	return true;
}

void skipCurrent(Kind kind)
{
	std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
	if (currentToken->kind != kind)
	{
		cout << converter.to_bytes(toString(kind)) << " 토큰이 필요합니다.";
		exit(1);
	}
	currentToken++;
}

vector<Statement*> parseBlock()
{

}

Function* parseFunction()
{
	auto result = new Function();
	skipCurrent(Kind::Function);

	// function name
	result->name = currentToken->string;
	skipCurrent(Kind::Identifier);

	// (
	skipCurrent(Kind::LeftParen);

	// parameters
	if (currentToken->kind != Kind::RightParen)
	{
		do
		{
			result->parameters.push_back(currentToken->string);
			skipCurrent(Kind::Identifier);
		}
		while (skipCurrentIf(Kind::Comma));
	}

	// )
	skipCurrent(Kind::RightParen);
	// {
	skipCurrent(Kind::LeftBrace);
	// body
	result->blocks = parseBlock();
	// }
	skipCurrent(Kind::RightBrace);

	return result;
}
  • parseFunction() 함수를 작성해주었다.

 

vector<Statement*> parseBlock()
{
	vector<Statement*> result;

	while (currentToken->kind != Kind::RightBrace)
	{
		switch (currentToken->kind)
		{
		case Kind::Variable:
			result.push_back(parseVariable());
			break;
		case Kind::For:
			result.push_back(parseFor());
			break;
		case Kind::If:
			result.push_back(parseIf());
			break;
		case Kind::Print:
			result.push_back(parsePrint());
			break;
		case Kind::Return:
			result.push_back(parseReturn());
			break;
		case Kind::Break:
			result.push_back(parseBreak());
			break;
		case Kind::Continue:
			result.push_back(parseContinue());
			break;
		case Kind::EndOfLine:
		case Kind::Unknown:
			cout << *currentToken << " 잘못된 구문입니다.";
			exit(1);
		}
	}

	return result;
}
  • parseBlock()을 만들어준다.
  • korlang 사양으로는 함수만 프로그램에 포함 가능하고, 모든 동작이 함수 안에서 일어나므로, parseBlock()만 이해하면 된다. 
  • Block 안에 있는 노드들은 모드 Statement(문) 이다.

나머지 statement parse 함수들도 작성해주자.

더보기
Variable* parseVariable()
{
	auto result = new Variable();
	// `변수` 키워드를 건너뛴다.
	skipCurrent(Kind::Variable);

	result->name = currentToken->string;
	// skip variable name
	skipCurrent(Kind::Identifier);

	// skip =
	skipCurrent(Kind::Assignment);

	result->expression = parseExpression();

	// skip ;
	skipCurrent(Kind::Semicolon);

	return result;
}

For* parseFor()
{
	auto result = new For();

	// skip '반복'
	skipCurrent(Kind::For);

	result->variable = new Variable();
	result->variable->name = currentToken->string;

	// skip variable name
	skipCurrent(Kind::Identifier);
	// skip =
	skipCurrent(Kind::Assignment);
	result->variable->expression = parseExpression();

	if (result->variable->expression == nullptr)
	{
		cout << "for문에 초기화 식이 없습니다.";
		exit(1);
	}

	// skip ;
	skipCurrent(Kind::Semicolon);
	result->condition = parseExpression();
	if (result->condition == nullptr)
	{
		cout << "for문에 조건식이 없습니다.";
		exit(1);
	}

	// skip ;
	skipCurrent(Kind::Semicolon);
	result->expression = parseExpression();
	if (result->expression == nullptr)
	{
		cout << "for문에 증감식이 없습니다.";
		exit(1);
	}

	// skip {
	skipCurrent(Kind::LeftBrace);
	result->blocks = parseBlock();
	// skip }
	skipCurrent(Kind::RightBrace);

	return result;
}

If* parseIf()
{
	auto result = new If();
	
	// skip '만약'
	skipCurrent(Kind::If);
	do
	{
		auto condition = parseExpression();
		if (condition == nullptr)
		{
			cout << "if문에 조건식이 없습니다.";
			exit(1);
		}
		result->conditions.push_back(condition);
		// skip {
		skipCurrent(Kind::LeftBrace);
		result->blocks.push_back(parseBlock());
		// skip }
		skipCurrent(Kind::RightBrace);
	}
	while (skipCurrentIf(Kind::Elif));

	if (skipCurrentIf(Kind::Else))
	{
		// skip {
		skipCurrent(Kind::LeftBrace);
		result->elseBlock = parseBlock();
		// skip }
		skipCurrent(Kind::RightBrace);
	}

	return result;
}

Print* parsePrint()
{
	auto result = new Print();
	// skip '출력'
	skipCurrent(Kind::Print);
	// skip (
	skipCurrent(Kind::LeftParen);

	// 하나의 식만 출력 가능
	result->arguments.push_back(parseExpression());

	// skip )
	skipCurrent(Kind::RightParen);
	// skip ;
	skipCurrent(Kind::Semicolon);


	return result;
}

Return* parseReturn()
{
	auto result = new Return();

	// skip '반환'
	skipCurrent(Kind::Return);

	result->expression = parseExpression();
	if (result->expression == nullptr)
	{
		cout << "반환문에 반환값이 없습니다.";
		exit(1);
	}

	// skip ;
	skipCurrent(Kind::Semicolon);

	return result;
}

Break* parseBreak()
{
	auto result = new Break();
	// skip '끊기'
	skipCurrent(Kind::Break);
	// skip ;
	skipCurrent(Kind::Semicolon);

	return result;
}

Continue* parseContinue()
{
	auto result = new Continue();
	// skip '계속하기'
	skipCurrent(Kind::Continue);
	// skip ;
	skipCurrent(Kind::Semicolon);

	return result;
}

ExpressionStatement* parseExpressionStatement()
{
	auto result = new ExpressionStatement();
	result->expression = parseExpression();
	skipCurrent(Kind::Semicolon);
	return result;
}
  • 이렇게 모든 Statement의 parse 함수를 작어하였다.
  • 이제 expression을 파싱해보자.

 

Expression

  • Expression 안에는
    • 연산자
      • 피연산자
      • 연산자
        • 단항 연산자
        • 이항 연산자
    • 피연산자

가 있다.

 

연산자에는 우선순위가 있는데 다음과 같다. 

우선순위가 높은 연산자가 가장 먼저 연산된다.

* 높다 -> 1위가 가장 높음. 6위가 가장 낮음.

우선순위 연산자 종류 결합 방향
1 후위 () [] 연산자 단항연산자 왼쪽
2 전위 + - 연산자 단항연산자 오른쪽
3 산술 * / % 연산자 이항연산자 왼쪽
4 산술 + - 연산자 이항연산자 왼쪽
5 관계 연산자 이항연산자 왼쪽
6 논리 AND 연산자 이항연산자 왼쪽
7 논리 OR 연산자 이항연산자 왼쪽
8 대입 연산자 이항연산자 오른쪽

구문트리를 만들 때는, 말단의 노드부터 차례대로 연산하기 때문에(bottom-up) 우선순위가 낮은 연산자부터 계산해야한다.

 

 

Expression* parseAssignment()
{
	// = 대입 연산자
	// 1. 왼쪽 피연산자부터 파싱
	auto result = parseOr();

	if (currentToken->kind != Kind::Assignment)
	{
		return result;
	}

	// skip =
	skipCurrent(Kind::Assignment);

	// GetVariable -> 참조 변수 타입
	GetVariable* variable = dynamic_cast<GetVariable*>(result);
	if (variable != nullptr)
	{
		SetVariable* result = new SetVariable();
		result->name = variable->name;
		result->value = parseAssignment();
		return result;
	}

	// GetElement -> 배열, map 원소 참조 타입
	GetElement* element = dynamic_cast<GetElement*>(result);
	if (element != nullptr)
	{
		SetElement* result = new SetElement();
		result->sub = element->sub;
		result->index = element->index;
		result->value = parseAssignment();
		return result;
	}

	cout << "대입 연산자의 왼쪽 피연산자가 변수나 배열, map 원소 참조가 아닙니다.";
	exit(1);
}

Expression* parseExpression()
{
	return parseAssignment();
}
  • parseExpression()에서는 순위가 가장 낮은 대입연산자 parseAssignment 처리 부터 해준다.
  • parseAssignment에서는 그 다음으로 순위가 낮은 OR연산자 parseOr을 수행한다.
  • 왼쪽 피연산자를 알아내고, GetVariable(변수), GetElement(배열, 맵)인지 확인한다.
    • 그리고 재귀적으로 parseAssignment를 호출한다.
      • 재귀적으로 호출하는 이유 -> 대입 연산자는 우측 결합 연산자이기 때문.
        • a = arr[1] = b = 3; 
          • 이런 식이 있을 때, b = 3이 가장 먼저 연산되고, arr[1] = b, a = arr[1] 순서대로 연산되기 때문이다.
Expression* parseOr()
{
	auto result = parseAnd();

	while (skipCurrentIf(Kind::LogicalOr))
	{
		auto temp = new Or();
		temp->lhs = result;
		temp->rhs = parseAnd();
		result = temp; // { { parseAnd() || parseAnd() } || parseAnd() } || parseAnd() ...
	}

	return result;
}
  • parseOr()는 이렇게 만든다. 
  • OR연산자가 나올 때 까지 누적해서 Or expression을 만든다.
  • parseAssignment()와 달리 반복문으로 하는 이유
    • OR연산은 왼쪽 결합 연산자이기 때문이다.
      • a 또는 b 또는 c
      • 라는 식이 있을 때, a 또는 b부터 계산이 되어야한다.

 

이렇게 나머지 Expression parsing 함수도 작성해보자.

더보기
Expression* parseElement(Expression* sub)
{
	auto result = new GetElement();
	result->sub = sub;

	// skip [
	skipCurrent(Kind::LeftBraket);

	// index 계산
	result->index = parseExpression();

	// skip ]
	skipCurrent(Kind::RightBraket);

	return result;
}

Expression* parseCall(Expression* sub)
{
	auto result = new Call();
	result->sub = sub;

	// skip (
	skipCurrent(Kind::LeftParen);

	// 파라미터 추가
	if (currentToken->kind != Kind::RightParen)
	{
		do
		{
			result->parameters.push_back(parseExpression());
		} while (skipCurrentIf(Kind::Comma));
	}
	// skip )
	skipCurrent(Kind::RightParen);
	return result;
}

Expression* parsePostfix(Expression* sub)
{
	while (true)
	{
		switch (currentToken->kind)
		{
		case Kind::LeftParen:
			sub = parseCall(sub);
			break;
		case Kind::LeftBraket:
			sub = parseElement(sub);
			break;
		default:
			return sub;
		}
	}
}


Expression* parseNullLiteral()
{
	skipCurrent(Kind::NullLiteral);
	auto result = new NullLiteral();
	return result;
}

Expression* parseBooleanLiteral()
{
	auto result = new BooleanLiteral();
	result->value = currentToken->kind == Kind::TrueLiteral;
	skipCurrent(currentToken->kind);
	return result;
}

Expression* parseNumberLiteral() 
{
	auto result = new NumberLiteral();
	result->value = stod(currentToken->string);
	skipCurrent(Kind::NumberLiteral);
	return result;
}

Expression* parseStringLiteral()
{
	auto result = new StringLiteral();
	result->value = currentToken->string;
	skipCurrent(Kind::StringLiteral);
	return result;
}

Expression* parseListLiteral()
{
	auto result = new ArrayLiteral();
	// skip [
	skipCurrent(Kind::LeftBraket);

	if (currentToken->kind != Kind::RightBraket)
	{
		do
		{
			result->values.push_back(parseExpression());
		} while (skipCurrentIf(Kind::Comma));
	}

	// skip ]
	skipCurrent(Kind::RightBraket);
	return result;
}

Expression* parseMapLiteral()
{
	auto result = new MapLiteral();
	// skip {
	skipCurrent(Kind::LeftBrace);

	if (currentToken->kind != Kind::RightBrace)
	{
		do
		{
			wstring name = currentToken->string;
			skipCurrent(Kind::StringLiteral);
			skipCurrent(Kind::Colon);

			auto value = parseExpression();
			result->values[name] = value;
		} while (skipCurrentIf(Kind::Comma));
	}

	// skip }
	skipCurrent(Kind::RightBrace);
	return result;
}

Expression* parseIdentifier() 
{
	auto result = new GetVariable();
	result->name = currentToken->string;
	skipCurrent(Kind::Identifier);
	return result;
}

Expression* parseInnerExpression()
{
	skipCurrent(Kind::LeftParen);
	auto result = parseExpression();
	skipCurrent(Kind::RightParen);
	return result;
}


Expression* parseOperand()
{
	Expression* result = nullptr;
	switch (currentToken->kind)
	{
	case Kind::NullLiteral:   result = parseNullLiteral();      break;
	case Kind::TrueLiteral:
	case Kind::FalseLiteral:  result = parseBooleanLiteral();   break;
	case Kind::NumberLiteral: result = parseNumberLiteral();    break;
	case Kind::StringLiteral: result = parseStringLiteral();    break;
	case Kind::LeftBraket:    result = parseListLiteral();      break;
	case Kind::LeftBrace:     result = parseMapLiteral();       break;
	case Kind::Identifier:    result = parseIdentifier();       break;
	case Kind::LeftParen:     result = parseInnerExpression();  break;
	default:                  cout << "잘못된 식입니다.";       exit(1);
	}

	return parsePostfix(result);
}

Expression* parseUnary()
{
	set<Kind> operators = {
		Kind::Add,
		Kind::Subtract,
	};
	while (operators.count(currentToken->kind))
	{
		auto temp = new Unary();
		temp->kind = currentToken->kind;
		skipCurrent(currentToken->kind);
		temp->sub = parseUnary();
		return temp;
	}

	return parseOperand();
}

Expression* parseArithmetic2()
{
	set<Kind> operators = {
	Kind::Multiply,
	Kind::Divide,
	Kind::Modulo,
	};
	auto result = parseUnary();

	while (operators.count(currentToken->kind))
	{
		auto temp = new Arithmetic();
		temp->kind = currentToken->kind;
		skipCurrent(currentToken->kind);
		temp->lhs = result;
		temp->rhs = parseUnary();
		result = temp;
	}

	return result;
}

Expression* parseArithmetic1()
{
	set<Kind> operators = {
		Kind::Add,
		Kind::Subtract,
	};
	auto result = parseArithmetic2();

	while (operators.count(currentToken->kind))
	{
		auto temp = new Arithmetic();
		temp->kind = currentToken->kind;
		skipCurrent(currentToken->kind);
		temp->lhs = result;
		temp->rhs = parseArithmetic2();
		result = temp;
	}

	return result;
}

Expression* parseRelational() // <, >, == ...
{
	set<Kind> operators = {
		Kind::Equal,
		Kind::NotEqual,
		Kind::LessThan,
		Kind::GreaterThan,
		Kind::LessOrEqual,
		Kind::GreaterOrEqual,
	};
	auto result = parseArithmetic1();

	while (operators.count(currentToken->kind))
	{
		auto temp = new Relational();
		temp->kind = currentToken->kind;
		skipCurrent(currentToken->kind);
		temp->lhs = result;
		temp->rhs = parseArithmetic1();
		result = temp;
	}

	return result;
}

Expression* parseAnd()
{
	auto result = parseRelational();

	while (skipCurrentIf(Kind::LogicalAnd)) 
	{
		auto temp = new And();
		temp->lhs = result;
		temp->rhs = parseRelational();
		result = temp; // { { parseEquality() && parseEquality() } && parseEquality() } && parseEquality() ...
	}

	return result;

}

 

Print 함수를 만들어서 출력하면 아래와 같이 나온다.

  • 소스코드
함수 시작() {
    출력("안녕");
    출력("\n");
    출력(1 + 2 * 3);
    출력("\n");
}
  • 결과
FUNCTION 시작:
  BLOCK:
    PRINT:
      "안녕"
    PRINT:
      "\n"
    PRINT:
      +:
        LHS:
          1
        RHS:
          *:
            LHS:
              2
            RHS:
              3
    PRINT:
      "\n"

 

 

참고 자료

https://shoark7.github.io/programming/knowledge/expression-vs-statement

 

 

 

 

728x90
반응형

Scanner 만들기


 

GitHub - Yoon6/korlang: korlang compiler.

korlang compiler. Contribute to Yoon6/korlang development by creating an account on GitHub.

github.com

 

scanner란?

  • lexical analysis 라고도 한다.
  • 소스 코드를 토큰으로 분리하는 과정이다.
  • 주의해야할 점은 "의미있는" 토큰으로 분리해야한다는 것이다.
만약(변수1 > 변수2)
{
	출력(123);
}
  • 이런 코드가 있을 때, 
    • '만', '약', (, '변', '수', 1, >, '변', '수', 2, ), ...
    • 이런 식으로 분리한다면 아무 의미가 없다.
  • '만약', (, '변수1', > '변수2', ), {, '출력', (, 123, ), ;, } 
    • 이렇게 토큰화 되어야한다.
  • 토큰으로 올 수 있는 요소들은 아래와 같다. 
    • 키워드, 식별자, 연산자, 구분자, 숫자 리터럴, 문자열 리터럴
토큰 예시
키워드 if, for, function
식별자 변수나 함수의 이름처럼 사용자가 정의한 어휘
연산자 +,-,*,/,%
구분자 (),{}.[],;
숫자 리터럴 123, 1.2 같은 정수 혹은 실수
문자열 리터럴 "Hello", "World" 같이 따옴표로 둘러싸인 문자

 

1. 토큰 정의하기

Kind.h

#ifndef KORLANG_KIND_H
#define KORLANG_KIND_H
#include <string>

using namespace std;

enum class Kind {
    Unknown, EndOfLine,

    NullLiteral,
    TrueLiteral, FalseLiteral,
    NumberLiteral, StringLiteral,
    Identifier,

    Function, Return,
    Variable,
    For, Break, Continue,
    If, Elif, Else,
    Print,

    LogicalAnd, LogicalOr,
    Assignment,
    Add, Subtract,
    Multiply, Divide, Modulo,
    Equal, NotEqual,
    LessThen, GreaterThan,
    LessOrEqual, GreaterOrEqaul,

    Comma, Colon, Semicolon,
    LeftParen, RightParen,
    LeftBrace, RightBrace,
    LeftBraket, RightBraket,
};
#endif //KORLANG_KIND_H
  • 위와 같이 소스코드 안에서 사용가능한 어휘들을 만들어주었다.

 

Kind.cpp

#include "Kind.h"
#include <map>

static map<string, Kind> stringToToken = {
        {"#unknown",    Kind::Unknown},
        {"#끝", Kind::EndOfLine},

        {"빈값",        Kind::NullLiteral},
        {"참",        Kind::TrueLiteral},
        {"거짓",       Kind::FalseLiteral},
        {"#숫자-리터럴",     Kind::NumberLiteral},
        {"#문자열-리터럴",     Kind::StringLiteral},
        {"#식별자",    Kind::Identifier},

        {"함수",    Kind::Function},
        {"반환",      Kind::Return},
        {"변수",         Kind::Variable},
        {"반복",         Kind::For},
        {"끊기",       Kind::Break},
        {"계속하기",    Kind::Continue},
        {"만약",          Kind::If},
        {"그게아니라",        Kind::Elif},
        {"아니면",        Kind::Else},
        {"출력",       Kind::Print},

        {"그리고",         Kind::LogicalAnd},
        {"또는",          Kind::LogicalOr},

        {"=",           Kind::Assignment},

        {"+",           Kind::Add},
        {"-",           Kind::Subtract},
        {"*",           Kind::Multiply},
        {"/",           Kind::Divide},
        {"%",           Kind::Modulo},

        {"==",          Kind::Equal},
        {"!=",          Kind::NotEqual},
        {"<",           Kind::LessThan},
        {">",           Kind::GreaterThan},
        {"<=",          Kind::LessOrEqual},
        {">=",          Kind::GreaterOrEqual},

        {",",           Kind::Comma},
        {":",           Kind::Colon},
        {";",           Kind::Semicolon},
        {"(",           Kind::LeftParen},
        {")",           Kind::RightParen},
        {"{",           Kind::LeftBrace},
        {"}",           Kind::RightBrace},
        {"[",           Kind::LeftBraket},
        {"]",           Kind::RightBraket},
};
  • 소스 코드 문자열과 열거형 멤버를 매핑시켜준다.

 

Token.h

#ifndef KORLANG_TOKEN_H
#define KORLANG_TOKEN_H
#include "Kind.h"

struct Token {
    Kind kind = Kind::Unknown;
    string string;
};

#endif //KORLANG_TOKEN_H
  • Scanning 결과로 반환할 Token 구조체이다.
    • 변수의 이름의 경우
    • kind = Kind:Identifier, string = "변수1" 이렇게 될 것이다.

 

Main.cpp

vector<Token> scan(string sourceCode);
  • Scanner는 입력으로 소스코드를 받고, 출력으로 토큰 리스트를 출력한다.

 

int main() {
    string sourceCode = R"(
        함수 시작() {
            출력("안녕");
            출력("\n");
            출력(1 + 2 * 3);
            출력("\n");
        }
    )";

    vector<Token> tokenList = scan(sourceCode);

    return 0;
}
  • 이런 sourceCode를 lexical analyze 해보자.

 

 

Scanner.cpp

#include <vector>
#include "Token.h"

static wstring::iterator current;

enum class CharType {
    Unknown,
    WhiteSpace,
    NumberLiteral,
    StringLiteral,
    IdentifierAndKeyword,
    OperatorAndPunctuator
};

bool isHangul(wchar_t wc) {
    return (wc >= 0xAC00 && wc <= 0xD7A3);
}

CharType getCharType(wchar_t c) {
    if (c == ' ' || c == '\t' || c == '\n' || c == '\r') {
        return CharType::WhiteSpace;
    }
    if ('0' <= c && c <= '9') {
        return CharType::NumberLiteral;
    }
    if (c == '\"') {
        return CharType::StringLiteral;
    }
    if ('a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || isHangul(c)) {
        return CharType::IdentifierAndKeyword;
    }
    if ('!' <= c && c <= '/' ||
        ':' <= c && c <= '@' ||
        '[' <= c && c <= '`' ||
        '{' <= c && c <= '~') {
        return CharType::OperatorAndPunctuator;
    }
    return CharType::Unknown;
}

bool isCharType(wchar_t wc, CharType type) {
    switch (type) {
        case CharType::NumberLiteral:
            return '0' <= wc && wc <= '9';
        case CharType::StringLiteral:
            return (33 <= wc && wc <= 126 && wc != '\"') || isHangul(wc);
        case CharType::IdentifierAndKeyword:
            return '0' <= wc && wc <= '9' || 'a' <= wc && wc <= 'z' || 'A' <= wc && wc <= 'Z' || isHangul(wc);
        case CharType::OperatorAndPunctuator:
            return '!' <= wc && wc <= '/' || ':' <= wc && wc <= '@' || '[' <= wc && wc <= '`' || '{' <= wc && wc <= '~';
        default:
            return false;
    }
}

Token scanNumberLiteral() {
    wstring string;
    while (isCharType(*current, CharType::NumberLiteral)) {
        string += *(current++);
    }
    if (*current == '.') {
        string += *(current++);
        while (isCharType(*current, CharType::NumberLiteral)) {
            string += *(current++);
        }
    }
    return Token{Kind::NumberLiteral, string};
}

Token scanStringLiteral() {
    wstring string;
    current++;
    while (isCharType(*current, CharType::StringLiteral)) {
        string += *(current++);
    }
    if (*current != '\"') {
        cout << "문자열 종료 문자가 없습니다.";
        exit(1);
    }
    current++;
    return Token{Kind::StringLiteral, string};
}

Token scanIdentifierAndKeyword() {
    wstring string;
    while (isCharType(*current, CharType::IdentifierAndKeyword)) {
        string += *(current++);
    }
    auto kind = toKind(string);
    if (kind == Kind::Unknown) {
        kind = Kind::Identifier;
    }
    return Token{kind, string};
}

Token scanOperatorAndPunctuator() {
    wstring string;
    while (isCharType(*current, CharType::OperatorAndPunctuator))
    {
        string += *(current++);
    }
    while (!string.empty() && toKind(string) == Kind::Unknown)
    {
        string.pop_back();
        current--;
    }
    if (string.empty())
    {
        cout << *current << " 사용할 수 없는 문자입니다.";
        exit(1);
    }
    return Token{toKind(string), string};

}

vector<Token> scan(string sourceCode) {
    vector<Token> result;
    sourceCode += '\0'; // Add null character
    wstring_convert<codecvt_utf8_utf16<wchar_t>> converter;
    std::wstring wSourceCode = converter.from_bytes(sourceCode);

    current = wSourceCode.begin();

    while (*current != '\0') {
        switch (getCharType(*current)) {
            case CharType::WhiteSpace:
                current += 1;
                break;
            case CharType::NumberLiteral:
                result.push_back(scanNumberLiteral());
                break;
            case CharType::StringLiteral:
                result.push_back(scanStringLiteral());
                break;
            case CharType::IdentifierAndKeyword:
                result.push_back(scanIdentifierAndKeyword());
                break;
            case CharType::OperatorAndPunctuator:
                result.push_back(scanOperatorAndPunctuator());
                break;
            default:
                cout << *current << " 사용할 수 없는 문자입니다.";
                exit(1);
        }
    }

    result.push_back({Kind::EndOfLine});
    return result;
}
  • 한글로 된 언어이기 때문에 wstring으로 변환해주었다.

 

Token.h

#include <locale>
#include <codecvt>
auto operator<<(ostream&, Token&)->ostream&;
  • 추가

Kind.h

auto toKind(const wstring&) -> Kind;
auto toString(Kind)->wstring;
  • 추가

Kind.cpp

auto toKind(const wstring& string) -> Kind {
    if (stringToKind.count(string))
    {
        return stringToKind.at(string);
    }
    return Kind::Unknown;
}

static auto kindToString = [] {
    map<Kind, wstring> result;
    for (auto& [key, value] : stringToKind)
        result[value] = key;
    return result;
}();

auto toString(Kind type)->wstring {
    if (kindToString.count(type))
        return kindToString.at(type);
    return L"";
}
  • 추가

Main.cpp

auto printTokenList(vector<Token> tokenList) -> void {
    cout << setw(12) << left << "KIND" << "STRING" << endl;
    cout << string(23, '-') << endl;
    for (auto &token: tokenList)
        cout << token << endl;
}

auto operator<<(ostream &stream, Token &token) -> ostream & {
    std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
    return stream << setw(12) << left << converter.to_bytes(toString(token.kind)) << setw(12) << right << '\t' << converter.to_bytes(token.string);
}

 

출력

  • 위와 같이 작성하면, 출력이 이렇게 나오게 된다.
KIND        STRING
-----------------------
함수                 	함수
#식별자             	시작
(                      	(
)                      	)
{                      	{
출력                 	출력
(                      	(
#문자열-리터럴           	안녕
)                      	)
;                      	;
출력                 	출력
(                      	(
#문자열-리터럴           	\n
)                      	)
;                      	;
출력                 	출력
(                      	(
#숫자-리터럴           	1
+                      	+
#숫자-리터럴           	2
*                      	*
#숫자-리터럴           	3
)                      	)
;                      	;
출력                 	출력
(                      	(
#문자열-리터럴           	\n
)                      	)
;                      	;
}                      	}
#끝
  • 한글도 잘 토큰화 하는 것을 볼 수 있다.
  • 이렇게 되면 lexical analysis 이후에 토큰 리스트를 뽑아내었다.
728x90
반응형

https://yoonda.tistory.com/62

 

[스파크 M300] 배터리 방전됨

당근에서 280만원 주고 산 14년식 스팤크 수동70000키로에 가져와서 74000키로 까지 탔다. 오늘 헬스장 가려고 나갔는데, 차 문이 안열린다...일주일 씩 놔두어도 시동이 잘 걸렸는데,,, 휴대폰 충전

yoonda.tistory.com

 

윗 글에서 배터리 분리를 했었다. 

네이버에서 반납형으로 DIN60L 주문했다.

주문 다음날 아침에 도착했다. 7시 50분 ㄷㄷ

네이버 포인트 치면, 6만 6천원 정도.. 비싼건가?

 

이렇게 택배가 왔고, 공구가 함께 들어있었다.

알리에서 사둔 라쳇이 있어서 쓰진 않았다.

2025년 3월에 주문했는데, 제조년도는 2024년 11월자 였다.

 

+ 부터 붙여주고

- 붙여주었다.

사진에는 앞쪽 고정볼트를 배터리 밑쪽에 걸었는데, 

배터리 트레이(받침대)에 홀이 있어서 그 쪽에 걸어주도록 바꾸었다.

 

문열림/잠금도 안되고, 키온도 안되고 완전 방전이었은데,, 잘된다..!

 

원래 쓰던 배터리는 숨구멍 막아주고 재포장하였다.

특이하게, 클레이로 막아서 주더라,,? 구멍에 남아있을 수 도 있어서 조심해야할듯

728x90

'일상 > 자동차' 카테고리의 다른 글

[스파크 M300] 배터리 방전됨  (0) 2025.03.12

+ Recent posts