1. 개요
Read-Eval-Print-Loop, REPL스크립트 언어 등의 프로그래밍 언어를 개별 줄 단위 또는 표현식 단위로 평가(evaluate)하고 결과를 출력하는 것을 반복하는 해석기 프로그램.
셸(shell), 반응형 셸(interactive shell) 등으로도 불리나, 운영체제의 셸 항목과의 혼동을 피해야 하는 상황에서는 REPL이라는 명칭을 자주 사용한다. 엄밀히는 운영체제의 셸 또한 REPL 방식 구현체의 일종이다.
2. 예시
아래 내용은 Ruby의irb
를 예시로 설명하지만, 기본적인 내용은 언어와 구현체에 무관하게 성립한다.터미널 에뮬레이터에서 다음과 같은 명령어로 Ruby REPL을 실행할 수 있다. 이를 REPL에 진입(enter)한다고 표현하기도 한다.
#!syntax ruby
$ irb⏎
irb(main):001>
컴파일 과정과 다르게, 실행할 소스 코드나 소스 파일을 사전에 전달하지 않았음에 주목하자. 현재 수행할 명령이 없으므로 프롬프트가 코드를 입력받기 위해 무한히 대기하게 된다.간단한 수식을 입력한 후 엔터를 누르면 해당 수식을 코드로 해석하고 평가(evaluate)한 결과를 출력한다.
#!syntax ruby
irb(main):001> 10 + 20⏎
=> 30
irb(main):002>
결과를 출력한 직후 새 프롬프트를 열어 다음 입력을 대기하고 있음을 볼 수 있다.[1] 이렇게 코드 입력(read) -> 평가(eval) -> 결과 출력(print) 과정을 반복(loop)하는 구조의 프로그램을 REPL이라 부른다.평가값은 위 예시에서
=>
로 표시된 값으로, 표준 출력과는 다르다. 표준 출력은 일반적인 실행 과정에서도 프로세스가 화면에 내용을 표시하기 위해 출력하는 내용이지만, 평가값은 일반적으로 개별 표현식을 계산하는 과정에서 메모리에만 존재하는 값으로, REPL을 통해 이를 즉각적으로 확인할 수 있는 것이다. 가령 위 코드를 표현식(expression)이 아닌 문장(statement)으로 만들면 평가값이 출력되지 않는다.#!syntax ruby
irb(main):002> 10 + 20;⏎
irb(main):003>
표현식과 표준 출력 결과가 동시에 보여지는 것도 가능하다. 아래 예제의 경우, 0부터 2까지 순서대로 출력한 다음 3을 반환한다. 따라서 3가 평가값이 된다.#!syntax ruby
irb(main):003> 3.times &method(:puts)⏎
0
1
2
=> 3
irb(main):004>
구현체에 따라 차이가 있지만, 입력된 코드가 완성되지 않고 작성 중인 상태일 경우, 바로 실행하지 않고 코드가 완성될 때까지 무한정 대기한다. 예를 들어 위의 코드를 블럭 형태로 작성하면 다음과 같다.#!syntax ruby
irb(main):004* 3.times do |n|⏎
irb(main):005* puts n⏎
irb(main):006> end⏎
0
1
2
=> 3
irb(main):007>
4번, 5번 줄의 입력 시 코드가 완성되지 않았으므로 실행하지 않고 대기한다. 이후 6번 줄의 입력으로 코드가 완성되었기 때문에 실행되었다.REPL의 또다른 특징 중 하나로, 개별 코드를 독립적인 환경(environment)으로 실행하지 않고 루프가 종료될 때까지 동일한 실행 환경을 공유한다. 다시 말해, 실행 환경이 불변(immutable)이지 않다.
#!syntax ruby
irb(main):007> name = "namu"⏎
=> "namu"
irb(main):008> puts "hello, #{name}wiki!"⏎
hello, namuwiki!
=> nil
irb(main):009>
위 예시의 경우 7번 줄에서 name
이라는 변수를 정의하고 8번 줄에서 해당 변수를 재참조한다. 이게 가능한 이유는 매 줄마다 메모리 상에 실행 환경을 새로 할당하는 것이 아닌 기존 환경을 유지하고 lookup을 수행하기 때문이다. 이를 응용하면 파일에 담긴 내용을 전혀 수정하지 않고도 새 코드를 실행하거나 디버깅을 수행할 할 수 있다.#!syntax ruby
$ irb -r ./src-file.rb
irb(main):001> function_from_source_file⏎
irb(main):002> variable_from_source_file⏎
irb(main):003>
REPL은 기본적으로 무한 루프이며, 강제로 종료시키지 않는 한 영원히 다음 코드 입력을 기다린다. 대부분의 구현체는 EOF 시그널 또는 이에 준하는 입력(Ctrl + D 등)을 받았을 때 정지한다.
3. 특징
- 일반적인 컴파일 과정을 기준으로 파서와 실행 관련 코드를 분리해야 한다. 파싱은 개별 입력 단위로 수행하지만 실행은 누적식으로 진행되기 때문.
- 디버깅에 유리하다. 디버깅을 수행하는 코드를 매번 삽입하고 빌드하는 과정을 거칠 필요 없이, REPL에서 결과를 실시간으로 보면서 판단을 내릴 수 있다. 실제로 많은 디버거의 경우 REPL과 비슷한 기능을 내장하고 있기도 한다.
- 인터프리터 언어, 스크립트 언어에서만 구현 가능할 것 같지만 명세만 잘 따른다면 어떤 언어도 REPL을 구현할 수 있다. 반대로 REPL 구현체가 존재하는 언어가 모두 인터프리터 언어인 것도 아니다. 백엔드만 JIT 등으로 컴파일을 수행하고 인터페이스를 인터프리터 언어와 유사하게 구현가는 것도 가능하며, 대표적인 JIT REPL 구현체로 RubyJIT, Node.js 등이 있다.
- 상당수의 구현체가 언어 명세와 별개로 REPL에서만 쓸 수 있는 특수한 명령어나 기능들을 제공한다. 대표적으로 Python에서 앞 평가값을 참조하는
_
변수가 있다. - 수준이 높은 구현체의 경우 자동완성, 평가값 미리보기, 구문 강조 등 편리한 기능들을 실시간으로 제공하기도 한다.
4. 언어별 상세
5. 관련 문서
[1] irb의 경우 프롬프트에 줄 번호가 표시된다. 구현체에 따라 줄 번호를 보여주는 경우도 있고 그렇지 않은 경우도 있다.