반응형
Scanner 만들기
- 한글로 된 언어를 만들거라, Korlang 이라고 이름을 지었다.
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
'CS > 컴파일러' 카테고리의 다른 글
[컴파일러 만들기] 3. Generator 만들기 (0) | 2025.03.26 |
---|---|
[컴파일러 만들기] 2. Parser 만들기 (0) | 2025.03.18 |
[컴파일러 만들기] C++로 컴파일러 만들어보기 (0) | 2025.03.11 |