Github의 전체 소스
Stoffle 프로그래밍 언어의 완전한 구현은 GitHub에서 사용할 수 있습니다. 버그를 발견하거나 질문이 있는 경우 언제든지 문제를 열어주세요.
이 블로그 게시물에서 우리는 완전히 Ruby로 구축된 장난감 프로그래밍 언어인 Stoffle용 인터프리터를 계속 구현할 것입니다. 우리는 이전 게시물에서 인터프리터를 시작했습니다. 이 시리즈의 첫 번째 부분에서 이 프로젝트에 대한 자세한 내용을 읽을 수 있습니다.
지난 포스트에서 변수, 조건부, 단항 및 이진 연산자, 데이터 유형, 콘솔로 인쇄 등 Stoffle의 더 간단한 기능을 구현하는 방법을 다루었습니다. 이제 소매를 걷어붙이고 더 어려운 나머지 부분인 함수 정의, 함수 호출, 변수 범위 지정 및 루프를 다룰 때입니다.
이전에 했던 것처럼 이 포스트의 처음부터 끝까지 동일한 예제 프로그램을 사용할 것입니다. Stoffle 예제 프로그램에서 각기 다른 구조를 구현하기 위해 인터프리터에서 필요한 구현을 탐색하면서 한 줄씩 살펴보겠습니다. 마지막으로 인터프리터가 작동하는 모습을 보고 시리즈의 이전 기사에서 만든 CLI를 사용하여 프로그램을 실행합니다.
가우스가 돌아왔습니다
기억력이 좋다면 시리즈의 2부에서 Lexer를 구축하는 방법에 대해 논의했다는 것을 기억할 수 있을 것입니다. 그 게시물에서 우리는 Stoffle의 구문을 설명하기 위해 일련의 숫자를 요약하는 프로그램을 살펴보았습니다. 이 기사의 끝에서 우리는 마침내 앞서 언급한 프로그램을 실행할 수 있게 될 것입니다! 자, 여기 다시 프로그램이 있습니다:
fn sum_integers: first_integer, last_integer
i = first_integer
sum = 0
while i <= last_integer
sum = sum + i
i = i + 1
end
println(sum)
end
sum_integers(1, 100)
정수 합계 프로그램의 추상 구문 트리(AST)는 다음과 같습니다.
<블록 인용>
스토플 샘플 프로그램에 영감을 준 수학자
Carl Friedrich Gauss는 불과 7세에 일련의 숫자를 요약하는 공식을 스스로 알아냈습니다.
눈치채셨겠지만 우리 프로그램은 가우스가 고안한 공식을 사용하지 않습니다. 오늘날 우리는 컴퓨터를 가지고 있기 때문에 이 문제를 "무차별 대입" 방식으로 해결할 수 있습니다. 우리의 실리콘 친구들이 우리를 위해 열심히 일하게 하십시오.
함수 정의
프로그램에서 가장 먼저 하는 일은 sum_integers
를 정의하는 것입니다. 기능. 함수를 선언한다는 것은 무엇을 의미합니까? 짐작할 수 있듯이 이는 변수에 값을 할당하는 것과 유사한 작업입니다. 함수를 정의할 때 이름(즉, 함수 이름, 식별자)을 하나 이상의 표현식(즉, 함수의 본문)과 연결합니다. 또한 함수 호출 중에 전달된 값이 바인딩되어야 하는 이름을 등록합니다. 이러한 식별자는 함수 실행 중에 지역 변수가 되며 매개변수라고 합니다. 함수가 호출될 때 전달되고 매개변수에 바인딩된 값은 인수입니다.
#interpret_function_definition
을 살펴보겠습니다. :
def interpret_function_definition(fn_def)
env[fn_def.function_name_as_str] = fn_def
end
꽤 간단하죠? 이 시리즈의 마지막 게시물에서 기억할 수 있듯이 인터프리터가 인스턴스화될 때 환경을 만듭니다. 이것은 프로그램 상태를 유지하는 데 사용되는 장소이며 우리의 경우 단순히 Ruby 해시입니다. 지난 게시물에서 변수와 이에 바인딩된 값이 env
에 어떻게 저장되는지 살펴보았습니다. . 함수 정의도 거기에 저장됩니다. 키는 함수 이름이고 값은 함수를 정의하는 데 사용되는 AST 노드입니다(Stoffle::AST::FunctionDefinition
). 다음은 이 AST 노드에 대한 정보입니다.
class Stoffle::AST::FunctionDefinition < Stoffle::AST::Expression
attr_accessor :name, :params, :body
def initialize(fn_name = nil, fn_params = [], fn_body = nil)
@name = fn_name
@params = fn_params
@body = fn_body
end
def function_name_as_str
# The instance variable @name is an AST::Identifier.
name.name
end
def ==(other)
children == other&.children
end
def children
[name, params, body]
end
end
Stoffle::AST::FunctionDefinition
과 관련된 함수 이름을 가짐 기능을 실행하는 데 필요한 모든 정보에 액세스할 수 있음을 의미합니다. 예를 들어, 예상되는 인수의 수를 가지고 있으며 함수 호출에서 제공하지 않으면 쉽게 오류를 발생시킬 수 있습니다. 다음으로 함수 호출 해석을 담당하는 코드를 탐색할 때 이 정보와 기타 세부 정보를 볼 수 있습니다.
함수 호출
예제를 계속 진행하면서 이제 함수 호출에 집중하겠습니다. sum_integers
정의 후 함수에서 숫자 1과 100을 인수로 전달하는 것을 호출합니다.
fn sum_integers: first_integer, last_integer
i = first_integer
sum = 0
while i <= last_integer
sum = sum + i
i = i + 1
end
println(sum)
end
sum_integers(1, 100)
함수 호출의 해석은 #interpret_function_call
에서 발생합니다. :
def interpret_function_call(fn_call)
return if println(fn_call)
fn_def = fetch_function_definition(fn_call.function_name_as_str)
stack_frame = Stoffle::Runtime::StackFrame.new(fn_def, fn_call)
assign_function_args_to_params(stack_frame)
# Executing the function body.
call_stack << stack_frame
value = interpret_nodes(fn_def.body.expressions)
call_stack.pop
value
end
이것은 복잡한 기능이므로 여기에서 시간을 할애해야 합니다. 지난 기사에서 설명한 것처럼 첫 번째 줄은 호출되는 함수가 println
인지 확인하는 역할을 합니다. . 여기의 경우와 같이 사용자 정의 함수를 다루는 경우 #fetch_function_definition
을 사용하여 정의를 가져옵니다. . 아래와 같이 이 함수는 평범한 항해이며 기본적으로 Stoffle::AST::FunctionDefinition
을 검색합니다. 이전에 환경에 저장한 AST 노드 또는 함수가 존재하지 않으면 오류가 발생합니다.
def fetch_function_definition(fn_name)
fn_def = env[fn_name]
raise Stoffle::Error::Runtime::UndefinedFunction.new(fn_name) if fn_def.nil?
fn_def
end
#interpret_function_call
로 돌아가기 , 상황이 더 흥미로워지기 시작합니다. 간단한 장난감 언어의 기능에 대해 생각할 때 두 가지 특별한 관심사가 있습니다. 먼저 함수에 로컬인 변수를 추적하는 전략이 필요합니다. return
도 처리해야 합니다. 표현. 이러한 문제를 해결하기 위해 frame이라고 하는 새 개체를 인스턴스화합니다. , 함수가 호출될 때마다. 동일한 함수가 여러 번 호출되더라도 각각의 새 호출은 새 프레임을 인스턴스화합니다. 이 객체는 함수에 로컬인 모든 변수를 보유합니다. 한 함수가 다른 함수를 호출할 수 있기 때문에 프로그램의 실행 흐름을 표시하고 추적하는 방법이 있어야 합니다. 이를 위해 호출 스택이라는 스택 데이터 구조를 사용합니다. . Ruby에서 #push
가 있는 표준 배열 및 #pop
메소드는 스택 구현으로 수행됩니다.
호출 스택 및 스택 프레임
호출 스택 및 스택 프레임이라는 용어를 느슨하게 사용하고 있음을 명심하십시오. 프로세서와 저수준 프로그래밍 언어에도 일반적으로 호출 스택과 스택 프레임이 있지만, 우리가 가지고 있는 장난감 언어와 정확히 일치하지는 않습니다.
이러한 개념이 호기심을 불러일으켰다면 호출 스택과 스택 프레임을 조사하는 것이 좋습니다. 금속에 더 가까이 가고 싶다면 프로세서 호출 스택을 구체적으로 살펴보는 것이 좋습니다.
다음은 Stoffle::Runtime::StackFrame
을 구현하는 코드입니다. :
module Stoffle
module Runtime
class StackFrame
attr_reader :fn_def, :fn_call, :env
def initialize(fn_def_ast, fn_call_ast)
@fn_def = fn_def_ast
@fn_call = fn_call_ast
@env = {}
end
end
end
end
이제 #interpret_function_call
로 돌아갑니다. , 다음 단계는 함수 호출에서 전달된 값을 함수 본문 내에서 로컬 변수로 액세스할 수 있는 각 예상 매개변수에 할당하는 것입니다. #assign_function_args_to_params
이 단계를 담당합니다.
def assign_function_args_to_params(stack_frame)
fn_def = stack_frame.fn_def
fn_call = stack_frame.fn_call
given = fn_call.args.length
expected = fn_def.params.length
if given != expected
raise Stoffle::Error::Runtime::WrongNumArg.new(fn_def.function_name_as_str, given, expected)
end
# Applying the values passed in this particular function call to the respective defined parameters.
if fn_def.params != nil
fn_def.params.each_with_index do |param, i|
if env.has_key?(param.name)
# A global variable is already defined. We assign the passed in value to it.
env[param.name] = interpret_node(fn_call.args[i])
else
# A global variable with the same name doesn't exist. We create a new local variable.
stack_frame.env[param.name] = interpret_node(fn_call.args[i])
end
end
end
end
#assign_function_args_to_params
를 살펴보기 전에 구현하려면 먼저 변수 범위 지정에 대해 간략하게 설명해야 합니다. 이것은 복잡하고 미묘한 주제입니다. Stoffle의 경우 매우 실용적이고 간단한 솔루션을 채택하겠습니다. 우리의 작은 언어에서 새로운 범위를 생성하는 유일한 구조는 함수입니다. 또한 전역 변수가 항상 먼저 옵니다. 결과적으로 함수 외부에서 선언된 모든 변수(즉, 첫 번째 사용)는 전역 변수이며 env
에 저장됩니다. . 함수 내부의 변수는 로컬 변수이며 env
에 저장됩니다. 함수 호출을 해석하는 동안 생성된 스택 프레임의 그러나 한 가지 예외가 있습니다. 기존 전역 변수와 충돌하는 변수 이름입니다. 충돌이 발생하면 지역 변수는 그렇지 않습니다. 생성되고 기존 전역 변수를 읽고 할당합니다.
자, 이제 변수 범위 지정 전략이 명확해졌으므로 #assign_function_args_to_params
로 돌아가 보겠습니다. . 메서드의 첫 번째 부분에서는 먼저 전달된 스택 프레임 개체에서 함수 정의 및 함수 호출 노드를 검색합니다. 이러한 항목이 있으면 제공된 인수의 수가 매개변수의 수와 일치하는지 여부를 쉽게 확인할 수 있습니다. 호출되는 함수가 예상됩니다. 주어진 인수와 예상 매개변수가 일치하지 않으면 오류가 발생합니다. #assign_function_args_to_params
의 마지막 부분에서 , 우리는 함수 호출 중에 제공된 인수(즉, 값)를 각각의 매개변수(즉, 함수 내부의 지역 변수)에 할당합니다. 매개변수 이름이 기존 전역 변수와 충돌하는지 여부를 확인합니다. 앞에서 설명한 것처럼 이 경우 함수의 스택 프레임 내부에 지역 변수를 생성하지 않고 전달된 값을 기존 전역 변수에 적용하기만 하면 됩니다.
#interpret_function_call
로 돌아가기 , 마침내 새로 생성된 스택 프레임을 호출 스택으로 푸시합니다. 그런 다음 오랜 친구를 #interpret_nodes
라고 부릅니다. 함수 본문 해석을 시작하려면:
def interpret_function_call(fn_call)
return if println(fn_call)
fn_def = fetch_function_definition(fn_call.function_name_as_str)
stack_frame = Stoffle::Runtime::StackFrame.new(fn_def, fn_call)
assign_function_args_to_params(stack_frame)
# Executing the function body.
call_stack << stack_frame
value = interpret_nodes(fn_def.body.expressions)
call_stack.pop
value
end
함수 본문 해석
함수 호출 자체를 해석했으므로 이제 함수 본문을 해석할 차례입니다.
fn sum_integers: first_integer, last_integer
i = first_integer
sum = 0
while i <= last_integer
sum = sum + i
i = i + 1
end
println(sum)
end
sum_integers(1, 100)
sum_integers
의 처음 두 줄 함수는 변수 할당입니다. 이 시리즈의 이전 블로그 게시물에서 이 주제를 다뤘습니다. 그러나 이제 우리는 가변 범위를 갖게 되었고 결과적으로 할당을 처리하는 코드가 약간 변경되었습니다. 살펴보겠습니다:
def interpret_var_binding(var_binding)
if call_stack.length > 0
# We are inside a function. If the name points to a global var, we assign the value to it.
# Otherwise, we create and / or assign to a local var.
if env.has_key?(var_binding.var_name_as_str)
env[var_binding.var_name_as_str] = interpret_node(var_binding.right)
else
call_stack.last.env[var_binding.var_name_as_str] = interpret_node(var_binding.right)
end
else
# We are not inside a function. Therefore, we create and / or assign to a global var.
env[var_binding.var_name_as_str] = interpret_node(var_binding.right)
end
end
함수 호출을 위해 생성된 스택 프레임을 call_stack
에 푸시했을 때를 기억하십니까? ? call_stack
길이가 0보다 큽니다(즉, 적어도 하나의 스택 프레임). 현재 해석 중인 코드의 경우와 같이 함수 내부에 있는 경우 현재 값을 바인딩하려는 변수와 이름이 같은 전역 변수가 이미 있는지 확인합니다. 이미 알고 있듯이 충돌이 발생하면 기존 전역 변수에 값을 할당하기만 하고 로컬 변수는 생성되지 않습니다. 이름이 사용되지 않을 때는 새 지역 변수를 만들고 여기에 원하는 값을 할당합니다. call_stack
이후 스택(즉, 후입선출 데이터 구조)인 경우 이 지역 변수가 env
에 저장되어야 함을 알고 있습니다. 마지막 스택 프레임(즉, 현재 처리 중인 함수에 대해 생성된 프레임). 마지막으로 #interpret_var_binding
의 마지막 부분입니다. 기능 외부에서 발생하는 할당을 다룹니다. 함수만 Stoffle에서 새 범위를 생성하기 때문에 함수 외부에서 생성된 변수는 항상 전역적이고 인스턴스 변수 env
에 저장되므로 여기서는 아무 것도 변경되지 않습니다. .
프로그램으로 돌아가서 다음 단계는 정수를 합산하는 루프를 해석하는 것입니다. 기억을 새로고침하고 Stoffle 프로그램의 AST를 다시 살펴보겠습니다.
루프를 나타내는 노드는 Stoffle::AST::Repetition
입니다. :
class Stoffle::AST::Repetition < Stoffle::AST::Expression
attr_accessor :condition, :block
def initialize(cond_expr = nil, repetition_block = nil)
@condition = cond_expr
@block = repetition_block
end
def ==(other)
children == other&.children
end
def children
[condition, block]
end
end
이 AST 노드는 기본적으로 이전 기사에서 살펴본 개념을 결합합니다. 조건의 경우 일반적으로 루트(표현식의 AST 루트 노드에 대해 생각)에 Stoffle::AST::BinaryOperator
가 있는 표현식이 있습니다. (예:'>', '또는' 등). 루프 본문에는 Stoffle::AST::Block
이 있습니다. . 이게 말이 됩니까? 루프의 가장 기본적인 형태는 하나 이상의 표현식(블록 ) 표현식이 참인 동안(즉, 조건부 동안 반복됨) 진실한 값으로 평가).
인터프리터의 해당 메소드는 #interpret_repetition
입니다. :
def interpret_repetition(repetition)
while interpret_node(repetition.condition)
interpret_nodes(repetition.block.expressions)
end
end
여기에서 이 방법의 단순성(그리고 감히 말하지만 아름다움)에 놀랄 수 있습니다. 과거 기사에서 이미 살펴본 방법을 결합하여 루프 해석을 구현할 수 있습니다. Ruby의 while
사용 루프에서 Stoffle 루프를 구성하는 노드를 계속 해석하는지 확인할 수 있습니다(#interpret_nodes
를 반복적으로 호출하여 ) 조건부의 평가가 참인 동안. 조건을 평가하는 작업은 일반적인 용의자 #interpret_node
를 호출하는 것만큼 쉽습니다. 방법.
함수에서 복귀
거의 결승점에 다다랐습니다! 루프 후, 합산 결과를 콘솔에 출력합니다. 시리즈의 마지막 부분에서 이미 다루었으므로 다시 다루지 않습니다. 간단히 요약하자면 println
함수는 Stoffle 자체에서 제공하며 인터프리터 내부에서는 단순히 Ruby의 자체 puts
를 사용하고 있습니다. 방법.
이 게시물을 마치려면 #interpret_nodes
를 다시 방문해야 합니다. . 그것의 최종 버전은 우리가 과거에 보았던 것과 약간 다릅니다. 이제 함수에서 반환 및 호출 스택 해제를 처리하는 코드가 포함됩니다. 다음은 #interpret_nodes
의 완성된 버전입니다. 완전한 영광:
def interpret_nodes(nodes)
last_value = nil
nodes.each do |node|
last_value = interpret_node(node)
if return_detected?(node)
raise Stoffle::Error::Runtime::UnexpectedReturn unless call_stack.length > 0
self.unwind_call_stack = call_stack.length # We store the current stack level to know when to stop returning.
return last_value
end
if unwind_call_stack == call_stack.length
# We are still inside a function that returned, so we keep on bubbling up from its structures (e.g., conditionals, loops etc).
return last_value
elsif unwind_call_stack > call_stack.length
# We returned from the function, so we reset the "unwind indicator".
self.unwind_call_stack = -1
end
end
last_value
end
이미 알고 있듯이 #interpret_nodes
많은 표현을 해석해야 할 때마다 사용됩니다. 이것은 프로그램 해석을 시작하는 데 사용되며 블록과 연결된 노드가 있는 모든 경우에 사용됩니다(예:Stoffle::AST::FunctionDefinition
). 특히, 함수를 다룰 때 두 가지 시나리오가 있습니다:함수 해석과 return
표현식 또는 함수를 끝까지 해석하고 return
을 치지 않음 표현. 두 번째 경우에는 함수에 명시적인 return
이 없음을 의미합니다. 우리가 겪은 표현식이나 코드 경로에 return
이 없습니다. .
계속하기 전에 기억을 새로고침합시다. 위의 몇 단락에서 기억할 수 있듯이 #interpret_nodes
sum_integers
해석을 시작할 때 호출되었습니다. 함수(즉, 프로그램에서 호출되었을 때). 이번에도 우리가 진행 중인 프로그램의 소스 코드는 다음과 같습니다.
fn sum_integers: first_integer, last_integer
i = first_integer
sum = 0
while i <= last_integer
sum = sum + i
i = i + 1
end
println(sum)
end
sum_integers(1, 100)
우리는 함수 해석의 끝에 있습니다. 짐작하시겠지만 우리 함수에는 명시적인 return
이 없습니다. . 이것은 #interpret_nodes
의 가장 쉬운 경로입니다. . 우리는 기본적으로 모든 함수 노드를 반복하며 마지막에 해석된 마지막 표현식의 값을 반환합니다(빠른 알림:Stoffle에는 암시적 반환이 있습니다). 이것은 우리를 결승선에 이르게 하고 우리 프로그램의 해석을 마칩니다.
우리 프로그램이 이제 완전히 해석되었지만 이 기사의 주요 목적은 인터프리터의 구현을 설명하는 것이므로 여기에서 조금 더 시간을 내어 인터프리터가 return
함수 내부.
먼저 return
표현식은 작업 시작 시 평가됩니다. 그 값은 반환되는 것에 대한 평가가 될 것입니다. 다음은 Stoffle::AST::Return
의 코드입니다. :
class Stoffle::AST::Return < Stoffle::AST::Expression
attr_accessor :expression
def initialize(expr)
@expression = expr
end
def ==(other)
children == other&.children
end
def children
[expression]
end
end
그런 다음 return
을 감지하는 간단한 조건이 있습니다. AST 노드. 이 작업을 수행한 후 먼저 온전성 검사를 수행하여 함수 내부에 있는지 확인합니다. 그렇게 하려면 단순히 호출 스택의 길이를 확인할 수 있습니다. 길이가 0보다 크다는 것은 우리가 실제로 함수 안에 있다는 것을 의미합니다. Stoffle에서는 return
의 사용을 허용하지 않습니다. 함수 외부의 표현식이므로 이 검사가 실패하면 오류가 발생합니다. 프로그래머가 의도한 값을 반환하기 전에 먼저 호출 스택의 현재 길이를 기록하여 인스턴스 변수 unwind_call_stack
에 저장합니다. . 이것이 왜 중요한지 이해하기 위해 #interpret_function_call
을 검토해 보겠습니다. :
def interpret_function_call(fn_call)
return if println(fn_call)
fn_def = fetch_function_definition(fn_call.function_name_as_str)
stack_frame = Stoffle::Runtime::StackFrame.new(fn_def, fn_call)
assign_function_args_to_params(stack_frame)
# Executing the function body.
call_stack << stack_frame
value = interpret_nodes(fn_def.body.expressions)
call_stack.pop
value
end
여기, #interpret_function_call
끝에 , 함수를 해석한 후 호출 스택에서 스택 프레임을 팝합니다. 결과적으로 호출 스택의 길이가 1로 줄어듭니다. 반환을 감지한 순간 스택의 길이를 유지했기 때문에 #interpret_nodes
에서 새 노드를 해석할 때마다 이 초기 길이를 비교할 수 있습니다. . #interpret_nodes
의 노드 반복자 내에서 이 작업을 수행하는 세그먼트를 살펴보겠습니다. :
def interpret_nodes(nodes)
# ...
nodes.each do |node|
# ...
if unwind_call_stack == call_stack.length
# We are still inside a function that returned, so we keep on bubbling up from its structures (e.g., conditionals, loops etc).
return last_value
elsif unwind_call_stack > call_stack.length
# We returned from the function, so we reset the "unwind indicator".
self.unwind_call_stack = -1
end
# ...
end
# ...
end
처음에는 이해하기가 다소 어려울 수 있습니다. GitHub에서 전체 구현을 확인하고 인터프리터의 이 마지막 부분을 이해하는 데 도움이 될 수 있다고 생각되면 함께 사용하는 것이 좋습니다. 여기서 염두에 두어야 할 중요한 점은 일반적인 프로그램에는 깊이 중첩된 구조가 많다는 것입니다. 따라서 #interpret_nodes
실행 중 일반적으로 #interpret_nodes
에 대한 새 호출이 발생합니다. , 이로 인해 #interpret_nodes
에 대한 더 많은 호출이 발생할 수 있습니다. 등등! return
을 누르면 함수 내부에서 우리는 깊이 중첩된 구조 내부에 있을 수 있습니다. 예를 들어 return
이 루프의 일부인 조건문 안에 있습니다. 함수에서 반환하려면 모든 #interpret_nodes
에서 반환해야 합니다. #interpret_function_call
에 의해 시작된 것에서 돌아올 때까지 호출 (즉, #interpret_nodes
호출 함수 본문의 해석을 시작했습니다.
위의 코드 부분에서 우리가 이 작업을 수행하는 방법을 정확히 강조합니다. @unwind_call_stack
에서 양수 값을 가짐 그리고 호출 스택의 현재 길이와 같은 하나, 우리는 우리가 함수 안에 있고 여전히 return
하지 않았음을 확실히 알고 있습니다. #interpret_function_call
에 의해 시작된 원래 호출에서 . 이것이 마침내 발생하면 @unwind_call_stack
호출 스택의 현재 길이보다 큽니다. 따라서 반환된 함수를 종료했으며 더 이상 버블링 프로세스를 계속할 필요가 없음을 알고 있습니다. 그런 다음 @unwind_call_stack
을 재설정합니다. . @unwind_call_stack
을 사용하려면 맑은, 가능한 값은 다음과 같습니다.
- -1 , 초기 및 중립 값으로 반환된 함수 내부에 없음을 나타냅니다.
- 호출 스택 길이와 동일한 양수 , 반환된 함수 안에 여전히 있음을 나타냅니다.
- 호출 스택 길이보다 큰 양수 , 반환된 함수 안에 더 이상 존재하지 않음을 나타냅니다.
Stoffle CLI를 사용하여 프로그램 실행
시리즈의 이전 기사에서는 Stoffle 프로그램을 더 쉽게 해석할 수 있도록 간단한 CLI를 만들었습니다. 인터프리터의 구현을 살펴보았으므로 이제 프로그램을 실행하여 인터프리터가 실행되는 것을 보겠습니다. 위의 여러 섹션에서 볼 수 있듯이 우리의 코드는 sum_integers
를 정의한 다음 호출합니다. 1
인수를 전달하는 함수 및 100
. 인터프리터가 제대로 작동하면 5050.0
이 표시됩니다. (1에서 시작하여 100으로 끝나는 정수 집합의 합) 콘솔에 출력됨:
결말 생각
이 게시물에서는 인터프리터를 완성하는 데 필요한 마지막 부분을 구현했습니다. 우리는 함수 정의, 함수 호출, 변수 범위 지정 및 루프를 다루었습니다. 이제 간단하지만 작동하는 프로그래밍 언어가 생겼습니다!
이 시리즈의 다음 부분과 마지막 부분에서는 프로그래밍 언어 구현 연구를 계속하려는 사람들을 위한 훌륭한 옵션으로 간주되는 몇 가지 리소스를 공유할 것입니다. 또한 Stoffle 버전을 개선하면서 학습을 계속할 수 있는 몇 가지 과제를 제안할 것입니다. 나중에 봐요; 챠오!