Kaleidoscope에 사용자가 직접 자신만의 이항연산자나 단항연산자를 정의할 수 있게 하는 기능
ex) def binary |> 10 (lhs rhs); 등과 같이 |> 새로운 연산자 정의
이렇게 하면 표준 라이브러리의 상당 부분을 언어 자체로 구현
전체적인 줄거리는 다음과 같다.
Parser에서 def binary, def unary 키워드 인식 -> 연산자의 이름과 우선순위를 파싱하여 저장
사용자 정의 연산자는 특별한 이름을 가진 함수 호출로 취급된다.
예를 들어, myoperator|> (x, y) 라는 함수를 호출하는 것으로 여겨짐
함수 호출을 위한 Codgen 로직은 이미 존재함. 따라서 파서가 연산자를 함수호출형태로 변환만 해주면 코드 생성 부분은 수정할게 많이 없음.
연산자는 사실 함수다. 연산자는 일종의 별명
enum Token {
...
tok_binary = -11,
tok_unary = -12
};
if (IdentifierStr == "binary")
return tok_binary;
if (IdentifierStr == "unary")
return tok_unary;
-11, -12는 tok_binary, tok_unary는 "binary" "unary" 키워드를 위한 고유 토큰 값. 마찬가지로 키워드는 음수로 지정하여 기존 문자의 아스키코드인 0~255와 겹치지 않게 구분한다.
기존의 AST에서는 이항 연산자를 아스키 코드를 표현한다. 따라서 새로운 연산자도 아스키 코드로 처리한다면 별도의 확장 없이 binary@ 등을 함수 이름으로 사용할 수 있다.
ex) x@y -> binary@ 함수 호출. @은 아스키 코드 64로 기존 연산자와 똑같이 AST 트리에 저장
함수(여기선 연산자 이름), 인자 정보, 연산자 여부 및 우선순위 등 함수의 기본 정보를 담고 있는 AST
두 가지 정보를 추가한다.
class PrototypeAST {
std::string Name;
std::vector<std::string> Args;
bool IsOperator;
unsigned Precedence; // 이항 연산자 우선순위
// 생성자 및 기타 메서드...
bool isUnaryOp() const { return IsOperator && Args.size() == 1; }
bool isBinaryOp() const { return IsOperator && Args.size() == 2; }
char getOperatorName() const { ... }
unsigned getBinaryPrecedence() const { ... }
};
파서가 def binary |> 5 (LHS RHS) 같은 코드를 읽으면, "아, |> 라는 새로운 연산자가 정의되었구나. 우선순위는 5이고, 인자는 LHS와 RHS 두 개를 받는구나."
이 정보를 1단계에서 만든 데이터 구조(PrototypeAST)에 깔끔하게 저장된다.
prototype ::= id '(' id* ')'
::= binary LETTER number? (id, id)
컴파일러는 코드에서 a|>b를 만난다. 컴파일러는 binary |> 라는 이름의 함수를 찾아 호출한다. getFunction("binary" + "|")
기존 이항 연산자 노드에 내장 연산자가 아니면 사용자 정의 연산자를 함수 호출로 처리한다.
Value *BinaryExprAST::codegen() {
Value *L = LHS->codegen();
Value *R = RHS->codegen();
if (!L || !R)
return nullptr;
switch (Op) {
// 내장 연산자 처리...
default:
break;
}
// 사용자 정의 연산자: "binary" + Op로 함수 이름을 만들어 호출
Function *F = getFunction(std::string("binary") + Op);
Value *Ops[2] = { L, R };
return Builder->CreateCall(F, Ops, "binop");
}
이후 연산자 우선순위 등록하여, 파서가 새로운 연산자와 우선순위를 인삭하고 기존 연산자와 동일하게 처리할 수 있게끔 한다.
파서에 우선순위가 5라고 전달 -> 우선순위에 따라 순서 파악 가능
단항 연산자도 이항 연산자와 동일하게 확장, 사용하면 된다, 이때 하나 다른 것은 순방향으로 계산이 진행되므로 우선순위 정보가 필요 없다는 것이다.