극비

극비

학습 연습 문제

소개

AST

추상 구문 트리(AST)는 _인용 표현식_이라고도 하며, 코드를 데이터로 표현하는 방법이에요.

AST의 각 노드는 요소가 세 개인 튜플이에요.

# AST representation of:
# 2 + 3
{:+, [], [2, 3]}

첫 번째 요소인 아톰은 연산을 나타내요. 두 번째 요소인 키워드 리스트는 메타데이터예요. 세 번째 요소는 다른 노드들을 담고 있는 인자 목록이에요. 정수, 아톰, 문자열 같은 리터럴 값은 요소가 세 개인 튜플 대신 AST 안에서 자기 자신으로 표현돼요.

코드를 AST로 바꾸기

Elixir 코드를 AST로, AST를 다시 코드로 바꾸는 일은 표준 라이브러리에 포함돼 있어요. AST를 다루는 함수는 Code 모듈(예: 코드가 담긴 문자열을 AST로 바꾸기)과 Macro 모듈(예: AST를 순회하거나 문자열로 바꾸기)에서 찾을 수 있어요.

표준 라이브러리의 모든 함수는 AST를 가리킬 때 "quoted"라는 이름을 사용한다는 점을 기억해 두세요(_quoted expression_의 준말이에요).

코드를 AST로 바꾸는 특수 폼은 quote라고 해요. 코드 블록을 받아서 그 AST를 반환해요.

quote do
  2 + 3 - 1
end

# => {:-, [], [
#      {:+, [], [2, 3]},
#      1
#    ]}

활용 사례

코드를 AST로 표현하는 능력은 Elixir 메타프로그래밍의 핵심이에요. Elixir 코드를 만들어 내는 Elixir 코드를 작성하는 방법인 _매크로_는 결과로 AST를 반환하는 방식으로 동작해요.

AST의 또 다른 활용 사례는 정적 코드 분석이에요. Exercism 자체 도구인 Analyzer가 그 예인데, 여러분의 풀이에 댓글을 남기는 작은 봇이라 이미 익숙할지도 몰라요.

지침

여러분은 기업 스파이 활동에 맞서 싸우는 태스크 포스의 일원이에요. 경쟁사의 비밀을 훔치는 게 의심되는 Shady Company X에 비밀 정보원을 심어 두었어요.

여러분의 정보원인 Agent Ex는 엘릭서 개발자예요. 그녀는 자신의 코드에 비밀 메시지를 인코딩해요.

그녀의 비밀 메시지를 해독하려면:

  • 정의된 순서대로 모든 함수(공개 함수와 비공개 함수)를 가져와요.
  • 각 함수에서 함수 이름의 처음 n개 문자를 가져와요. 여기서 n은 그 함수의 인자 개수예요.

1. 코드를 데이터로 바꾸기

TopSecret.to_ast/1 함수를 구현해요. 엘릭서 코드가 담긴 문자열을 받아 그 AST를 반환해야 해요.

TopSecret.to_ast("div(4, 3)")
# => {:div, [line: 1], [4, 3]}

2. AST 노드 하나 파싱하기

TopSecret.decode_secret_message_part/2 함수를 구현해요. AST 노드와 비밀 메시지를 위한 누산기(리스트)를 받아요. 첫 번째 요소는 그대로 둔 AST 노드이고 두 번째 요소는 누산기인 튜플을 반환해야 해요.

AST 노드의 연산이 함수를 정의하는 것(def 또는 defp)이라면, 함수 이름을 문자열로 바꿔 누산기 앞에 붙여요. 연산이 다른 것이라면 누산기를 그대로 반환해요.

ast_node = TopSecret.to_ast("defp cat(a, b, c), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["cat", "day"]}

ast_node = TopSecret.to_ast("10 + 3")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["day"]}

이 함수는 전체 AST를 확인하기 위해 재귀 호출을 할 필요가 없고, 주어진 노드만 확인하면 돼요. 마지막 단계에서 내장 도구로 전체 AST를 순회할 거예요.

3. 함수 정의에서 비밀 메시지 조각 해독하기

TopSecret.decode_secret_message_part/2 함수를 확장해요. AST 노드의 연산이 함수를 정의하는 것이라면 함수 이름 전체를 반환하지 않아요. 대신 그 함수의 인자 개수를 확인해요. 그런 다음 이름에서 처음 n개 문자만 반환해요. 여기서 n은 인자 개수예요.

ast_node = TopSecret.to_ast("defp cat(a, b), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["ca", "day"]}

ast_node = TopSecret.to_ast("defp cat(), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["", "day"]}

4. 가드가 있는 함수의 해독 수정하기

TopSecret.decode_secret_message_part/2 함수를 확장해요. 가드를 사용하는 함수 정의에서도 함수 이름과 인자 개수가 올바르게 감지되도록 해요.

ast_node = TopSecret.to_ast("defp cat(a, b) when is_nil(a), do: nil")
TopSecret.decode_secret_message_part(ast_node, ["day"])
# => {ast_node, ["ca", "day"]}

5. 전체 비밀 메시지 해독하기

TopSecret.decode_secret_message/1 함수를 구현해요. 엘릭서 코드가 담긴 문자열을 받아, 코드에서 찾은 모든 함수 정의에서 해독한 비밀 메시지를 문자열로 반환해야 해요. 이전 단계에서 정의한 함수들을 재사용해야 해요.

code = """
defmodule MyCalendar do
  def busy?(date, time) do
    Date.day_of_week(date) != 7 and
      time.hour in 10..16
  end

  def yesterday?(date) do
    Date.diff(Date.utc_today, date)
  end
end
"""

TopSecret.decode_secret_message(code)
# => "buy"
GitHub에서 편집 링크가 새 창이나 탭에서 열려요
Elixir Exercism

극비 문제를 시작해 볼 준비가 됐나요?

Exercism에 가입하고 Elixir 트랙을 개념 58개연습 문제 168개, 그리고 실제 사람의 멘토링과 함께 배우고 익혀 보세요. 모두 무료예요.