raku-type-callframe

class CallFrame {}

CallFrame는 보통 callframe 서브루틴으로 프로그램의 현재 상태에서 잡아낸 프레임이에요.

my $frame = callframe;
say "The above line of code ran at {$frame.file}:{$frame.line}.";

인자 없이 부르면 callframe을 호출한 그 줄에 대한 프레임 정보를 줘요. 이때 파일·줄 주석은 $?FILE$?LINE에 들어 있는 값과 똑같아요.

하지만 숫자를 넘겨서 다른 프레임 단계를 지정할 수도 있어요. 양수를 넘기면 프레임 단계를 위로 올라가고, 음수를 넘기면 callframe 메서드·클래스 자신이 이 정보를 만들기 위해 실행되고 있는 지점 안쪽으로 내려가요.

한 가지 주의할 점은, 프레임이 반드시 메서드나 서브루틴 호출에만 대응하지는 않는다는 거예요. Raku는 블록 같은 것에도 프레임을 만들거든요. 그래서 특정 메서드 호출에 대한 callframe이 필요하다고 해서, 그게 몇 단계 위인지 정해져 있다고 가정하면 안 돼요.

각 프레임은 주석(annotation)을 저장해요. 파일·줄 주석은 편의 메서드로 바로 접근할 수 있고, 현재 실행 중인 프레임의 코드 블록 참조는 code 메서드로 얻을 수 있어요. 또 프레임은 자기와 함께 저장된 레시컬 변수들을 모두 잡아두는데, 그건 프레임 객체에 my를 호출하면 얻을 수 있어요.

callframe 인터페이스로 호출한 루틴을 찾아서 그 호출자의 패키지를 출력하는 짧은 예시를 볼게요.

sub calling-frame() {
    for 1..* -> $level {
        given callframe($level) -> $frame {
            when $frame ~~ CallFrame {
                    next unless $frame.code ~~ Routine;
                    say $frame.code.package;
                    last;
            }
            default {
                    say "no calling routine or method found";
                    last;
            }
        }
    }
}

calling-frame;

호출자 정보만 추적하면 되는 경우라면, Backtrace가 더 나은 방법일 수 있어요. CallFrame는 특정 프레임에 대해 더 많은 정보를 주지만, 호출 스택을 열거하기에는 인터페이스가 번거로워요.

Methods

참고로 6.d 버전부터 CallFrame.raku(2019.11 릴리스 이전에는 .perl)를 호출할 수 있어요.

method code

method code()

현재 블록에 대한 호출 가능한 코드를 돌려줘요. callframe(0)이 돌려준 객체에서 호출하면 &?BLOCK에 들어 있는 값과 같아요.

my $frame;
for ^3 { FIRST $frame = callframe; say $_ * 3 };
say $frame.code()

이 경우 $frame 변수는 루프 안쪽 블록에 대한 Code를 담게 돼요.

method file

method file()

파일 주석을 조회하는 단축 메서드예요. 그래서 아래 코드는 True를 출력해요.

my $frame = callframe(0);
say $frame.file eq $frame.annotations<file>;

method line

method line()

줄 주석을 조회하는 단축 메서드예요. 예를 들어 아래 두 호출은 완전히 같아요.

say callframe(1).line;
say callframe(1).annotations<line>;

method annotations

method annotations()

호출자의 주석, 즉 linefile을 담고 있는 Map을 돌려줘요. 주석 정보를 얻을 때는 편의 메서드들을 쓰는 게 더 간편해요.

say callframe.annotations.^name;                   # OUTPUT: «Map␤»
say callframe.annotations<file> eq callframe.file; # OUTPUT: «True␤»

method my

method my()

프레임의 레시컬 스코프와 연결된 모든 변수와 그 값을 이름으로 담은 Hash를 돌려줘요.

sub some-value {
    my $the-answer = 42;
    callframe(0);
}

my $frame = some-value();
say $frame.my<$the-answer>; # OUTPUT: «42␤»

Routines

sub callframe

sub callframe(Int:D $level = 0)

주어진 단계에 대한 CallFrame 객체를 돌려줘요. 단계를 안 주면 기본값은 0이에요. 양수 단계는 프레임 스택을 위로, 음수 단계는 아래로(callframe 호출과 그 안쪽으로) 이동해요.

해당 단계에 호출 정보가 없으면 Mu를 돌려줘요. 음수 단계는 예외로 이어질 수 있어요.