// 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();
}
유효 범위를 정해주기 위해, 시작과 끝에 pushBlock(), popBlock()을 해주었다.
제어 변수를 생성하고,
점프 주소를 기록한다.
jumpAddress는 반복문 순회를 위해 기록한다.
조건문을 생성하고,
거짓일 때 점프하는 명령어를 작성한다.
다음으로 참일 때, 실행할 코드를 생성한다.
i++ 증감식을 생성한다.
지역 변수를 제거하고,
다시 jumpAddress 주소로 가서 조건식을 평가 할 수 있게, jump 코드를 작성한다.
그리고 조건식이 거짓일 때, 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);
}
}
조건문마다 실행 후, 만약 문의 마지막으로 이동해야 한다. Jump 문을 작성하기 위해 list를 생성한다.
코드 작성 전에는 마지막 주소가 어딘지 모르기 때문에 jump 코드 주소를 다 저장해둔다.
conditions부터 작성하고, conditionaljump를 작성한다.
jump하는 주소는 body를 다 적고, patch 해준다.
body를 작성한다.
바디의 스코프 설정을 해줘야한다. pushblock()/popblock()
조건문을 다 적었으면, else 문 케이스에 대해 적어준다.
그리고 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
파라미터를 역순으로 생성한다 -> 스택구조이기 때문
피연산자식 코드를 생성한다.
피연산자는 함수를 뜻한다.
Call 명령을 작성한다.
Function
함수 Call에서 파라미터를 참조할 수 있게, Function::generate에서 local 변수로 등록해준다.
Return
반환식을 생성하고
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);
}
피연산자 식(배열 명)을 먼저 생성하고
인덱스를 작성한다.
그리고 명령어를 작성한다.
원소값 수정
auto SetElement::generate() -> void {
value->generate();
sub->generate();
index->generate();
writeCode(Instruction::SetElement);
}