Add korean translation

pull/69/head
hg-pyun 5 years ago
parent 727928d9c9
commit bd47571f8a

@ -76,16 +76,15 @@
*/
/**
* Today we're going to write a compiler together. But not just any compiler... A
* super duper teeny tiny compiler! A compiler that is so small that if you
* remove all the comments this file would only be ~200 lines of actual code.
* 오늘 우리는 함께 컴파일러를 작성할 것입니다. 하지만 여러분이 알고있는 컴파일러가 아니라... 매우
* 기능이 간소하고 작은 컴파일러입니다! 너무 작아서 주석을 모두 제거하면 파일의 실제 코드는 200 밖에
* 되지 않습니다.
*
* We're going to compile some lisp-like function calls into some C-like
* function calls.
* 우리는 어떤 lisp과 같은 함수 호출을 C와 같은 함수호출로 컴파일 것입니다.
*
* If you are not familiar with one or the other. I'll just give you a quick intro.
* 만약 여러분이 언어에 익숙하지 않을 있습니다. 그래서 간단하게 소개하도록 하겠습니다.
*
* If we had two functions `add` and `subtract` they would be written like this:
* 만약 `add` `subtract`라는 함수가 있다면 다음과 같이 작성될 것입니다:
*
* LISP C
*
@ -93,55 +92,47 @@
* 4 - 2 (subtract 4 2) subtract(4, 2)
* 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2))
*
* Easy peezy right?
* 쉽죠?
*
* Well good, because this is exactly what we are going to compile. While this
* is neither a complete LISP or C syntax, it will be enough of the syntax to
* demonstrate many of the major pieces of a modern compiler.
* 좋습니다. 이것들이 바로 오늘 우리가 컴파일 것들입니다. 함수들은 완전한 LISP이나 C 구문은
* 아니지만, 현대 컴파일러의 주요한 부분을 많이 증명하기엔 충분합니다.
*/
/**
* Most compilers break down into three primary stages: Parsing, Transformation,
* and Code Generation
* 대부분의 컴파일러들은 세가지 중요한 단계를 가지고 있습니다: Parsing, Transformation,
* 그리고 Code Generation 입니다.
*
* 1. *Parsing* is taking raw code and turning it into a more abstract
* representation of the code.
* 1. *Parsing* 원시 코드를 추상적인 코드로 바꿔주는 단계입니다.
*
* 2. *Transformation* takes this abstract representation and manipulates to do
* whatever the compiler wants it to.
* 2. *Transformation* 추상적인 코드를 가지고, 컴파일러가 원하는 것은 무엇이든지
* 있도록 변형합니다.
*
* 3. *Code Generation* takes the transformed representation of the code and
* turns it into new code.
* 3. *Code Generation* 변형된 추상젹인 코드를 새로운 코드로 바꿉니다.
*/
/**
* Parsing
* -------
*
* Parsing typically gets broken down into two phases: Lexical Analysis and
* Syntactic Analysis.
* 구문 분석은 일반적으로 단게로 분류됩니다: Lexical Analysis and Syntactic Analysis.
*
* 1. *Lexical Analysis* takes the raw code and splits it apart into these things
* called tokens by a thing called a tokenizer (or lexer).
* 1. *Lexical Analysis* 원시 코드를 가져다가 tokenizer(또는 lexer)라고 불리는 것에 의헤
* 토큰이라고 불리는 것으로 분리됩니다.
*
* Tokens are an array of tiny little objects that describe an isolated piece
* of the syntax. They could be numbers, labels, punctuation, operators,
* whatever.
* 토큰은 구문의 분리된 부분을 설명하는 작은 물체들의 배열입니다. 숫자, 라벨, 구두점, 조작자,
* 무엇이든 있습니다.
*
* 2. *Syntactic Analysis* takes the tokens and reformats them into a
* representation that describes each part of the syntax and their relation
* to one another. This is known as an intermediate representation or
* Abstract Syntax Tree.
* 2. *Syntactic Analysis* 토큰을 구문의 부분과 관계를 설명하는 표현으로 재구성합니다.
* 이것은 중간 표현 또는 추상 구문 트리로 알려져 있습니다.
*
* An Abstract Syntax Tree, or AST for short, is a deeply nested object that
* represents code in a way that is both easy to work with and tells us a lot
* of information.
* 추상 구문 트리, 줄여서 AST는 코드를 다루기 쉽고 우리에게 많은 정보를 알려주는 방식으로
* 표현된 중첩된 객체 입니다.
*
* For the following syntax:
* 다음 구문은:
*
* (add 2 (subtract 4 2))
*
* Tokens might look something like this:
* 토큰을 이용하면 다음과 같이 보일지도 모릅니다:
*
* [
* { type: 'paren', value: '(' },
@ -155,7 +146,7 @@
* { type: 'paren', value: ')' },
* ]
*
* And an Abstract Syntax Tree (AST) might look like this:
* 그리고 추상 구문 트리 (AST) 아마 아래와 같이 보일겁니다:
*
* {
* type: 'Program',
@ -184,26 +175,23 @@
* Transformation
* --------------
*
* The next type of stage for a compiler is transformation. Again, this just
* takes the AST from the last step and makes changes to it. It can manipulate
* the AST in the same language or it can translate it into an entirely new
* language.
* 컴파일러의 다음 스테이지 유형은 변환입니다. 다시 말하지만, 이건 그냥 마지막 단계에서 AST를 가지고
* 변경하는것 뿐입니다. 같은 언어로 AST를 조작하거나 완전히 새로운 언어로 바꿀 수도 있습니다.
*
* Lets look at how we would transform an AST.
* AST를 어떻게 변화시킬지 살펴보도록 할까요.
*
* You might notice that our AST has elements within it that look very similar.
* There are these objects with a type property. Each of these are known as an
* AST Node. These nodes have defined properties on them that describe one
* isolated part of the tree.
* 여러분은 AST의 구성 요소들이 매우 비슷하게 생겼다는 것을 눈치챘을 겁니다.
* 오브젝트들은 타입 속성을 가지고 있습니다. 이것들 각각은 AST 노드라고 알려져 있습니다. 노드들은
* 트리의 분리된 부분을 설명하는 속성들을 가지고 있습니다.
*
* We can have a node for a "NumberLiteral":
* 우리는 "NumberLiteral" 위한 노드를 가질 있습니다:
*
* {
* type: 'NumberLiteral',
* value: '2',
* }
*
* Or maybe a node for a "CallExpression":
* 또는 "CallExpression"노드도 있을 있습니다:
*
* {
* type: 'CallExpression',
@ -211,20 +199,16 @@
* params: [...nested nodes go here...],
* }
*
* When transforming the AST we can manipulate nodes by
* adding/removing/replacing properties, we can add new nodes, remove nodes, or
* we could leave the existing AST alone and create an entirely new one based
* on it.
* AST를 변환할 우리는 속성을 추가/제거/교체하여 노드를 조작할 있고, 새로운 노드를 추가하거나,
* 노드를 제거하거나, 기존 AST를 그대로 두고 그것에 기반한 완전히 새로운 노드를 만들 있습니다.
*
* Since were targeting a new language, were going to focus on creating an
* entirely new AST that is specific to the target language.
* 우리는 새로운 언어를 목표로 하고 있기 때문에, 완전히 새로운 AST를 만드는 초점을 맞출 것입니다.
*
* Traversal
* ---------
*
* In order to navigate through all of these nodes, we need to be able to
* traverse through them. This traversal process goes to each node in the AST
* depth-first.
* 모든 노드를 탐색하기 위해서는 우리는 노드들을 순회할 필요성이 있습니다. 순회 과정은 AST의 각각의
* 노드들을 깊이를 우선하여 탐색합니다.
*
* {
* type: 'Program',
@ -248,46 +232,43 @@
* }]
* }
*
* So for the above AST we would go:
* AST를 위해 우리는:
*
* 1. Program - Starting at the top level of the AST
* 2. CallExpression (add) - Moving to the first element of the Program's body
* 3. NumberLiteral (2) - Moving to the first element of CallExpression's params
* 4. CallExpression (subtract) - Moving to the second element of CallExpression's params
* 5. NumberLiteral (4) - Moving to the first element of CallExpression's params
* 6. NumberLiteral (2) - Moving to the second element of CallExpression's params
* 1. Program - AST의 최상위 레벨에서 시작
* 2. CallExpression (add) - Program의 body에서 첫번째 요소로 이동
* 3. NumberLiteral (2) - CallExpression의 번째 요소로 이동
* 4. CallExpression (subtract) - CallExpression의 번째 요소로 이동
* 5. NumberLiteral (4) - CallExpression의 번째 요소로 이동
* 6. NumberLiteral (2) - CallExpression의 번째 요소로 이동
*
* If we were manipulating this AST directly, instead of creating a separate AST,
* we would likely introduce all sorts of abstractions here. But just visiting
* each node in the tree is enough for what we're trying to do.
* 만약 우리가 AST를 직접 조작한다면, 별도의 AST를 만드는 대신에 다양한 종류의 추상화를 도입할 겁니다.
* 하지만 트리안에 있는 각각의 노드들을 방문하는 것만으로도 우리가 하려는 일을 있습니다.
*
* The reason I use the word "visiting" is because there is this pattern of how
* to represent operations on elements of an object structure.
* "방문"이라는 단어를 사용하는 이유는, 객체 구조의 요소에 대한 작업을 어떻게 표현해야 하는지에 대해
* 패턴이 있기 때문입니다.
*
* Visitors
* --------
*
* The basic idea here is that we are going to create a visitor object that
* has methods that will accept different node types.
* 기본적인 아이디어는 다른 노드 타입을 가지고 있는 방문자 객체를 만드는 것입니다.
*
* var visitor = {
* NumberLiteral() {},
* CallExpression() {},
* };
*
* When we traverse our AST, we will call the methods on this visitor whenever we
* "enter" a node of a matching type.
* 우리가 AST를 순회할 , 매칭되는 타입의 노드에 방문했을 경우, 해당하는 메서드를 visitor에서 호출
* 것입니다.
*
* In order to make this useful we will also pass the node and a reference to
* the parent node.
* 이것을 유용하게 만들기 위해 우리는 노드와 상위 노드에 대한 참조를 전달할 것입니다.
*
* var visitor = {
* NumberLiteral(node, parent) {},
* CallExpression(node, parent) {},
* };
*
* However, there also exists the possibility of calling things on "exit". Imagine
* our tree structure from before in list form:
* 그러나, 우리는 "exit" 호출할 가능성에 대해서도 고려해야합니다. 리스트로 부터 우리의 트리 구조에 대해
* 상상해 볼까요:
*
* - Program
* - CallExpression
@ -296,9 +277,8 @@
* - NumberLiteral
* - NumberLiteral
*
* As we traverse down, we're going to reach branches with dead ends. As we
* finish each branch of the tree we "exit" it. So going down the tree we
* "enter" each node, and going back up we "exit".
* 계속 순회를 하다보면, 언젠가는 마지막 가지에 닿을 것입니다. 가지의 끝에 도달했을 , 우리는 해당 트리에서
* "exit" 합니다. 그래서 각각의 트리를 내려가는것을 "enter", 다시 위로 올라가는 것을 "exit" 합니다.
*
* -> Program (enter)
* -> CallExpression (enter)
@ -313,7 +293,7 @@
* <- CallExpression (exit)
* <- Program (exit)
*
* In order to support that, the final form of our visitor will look like this:
* 이를 위해 visitor의 최종 형태는 다음과 같습니다:
*
* var visitor = {
* NumberLiteral: {
@ -327,36 +307,33 @@
* Code Generation
* ---------------
*
* The final phase of a compiler is code generation. Sometimes compilers will do
* things that overlap with transformation, but for the most part code
* generation just means take our AST and string-ify code back out.
* 컴파일러의 마지막 단계는 코드 생성입니다. 때때로 컴파일러는 변환과 겹치는 일을 하곤 하지만, 대부분의
* 부분 코드 생성은 우리의 AST와 문자열 식별 코드를 다시 빼내는 것을 의미합니다.
*
* Code generators work several different ways, some compilers will reuse the
* tokens from earlier, others will have created a separate representation of
* the code so that they can print node linearly, but from what I can tell most
* will use the same AST we just created, which is what were going to focus on.
* 코드 제너레이터는 여러가지 다른 방식으로 작동하며, 어떤 컴파일러는 이전과 다른 토큰을 재사용할 것이고,
* 다른 컴파일러는 노드를 선형적으로 출력할 있도록 코드의 별도 표현을 만들 것입니다. 그러나 여기서
* 말할 있는 것은 우리가 방금 만든 것과 동일한 AST를 사용하게 것이며, 이것이 우리가 집중해야
* 부분 입니다.
*
* Effectively our code generator will know how to print all of the different
* node types of the AST, and it will recursively call itself to print nested
* nodes until everything is printed into one long string of code.
* 효과적으로 우리의 코드 제너레이터는 AST의 모든 다양한 노드 유형을 "출력"하는 방법을 알게 것이며,
* 모든 것이 하나의 코드 문자열로 인쇄될 때까지 중첩된 노드를 인쇄하기 위해 스스로를 재귀적으로 호출할
* 것입니다.
*/
/**
* And that's it! That's all the different pieces of a compiler.
* 이게 다입니다! 이것들을 조합하면 컴파일러가 됩니다.
*
* Now that isnt to say every compiler looks exactly like I described here.
* Compilers serve many different purposes, and they might need more steps than
* I have detailed.
* 그렇다고 해서 모든 컴파일러들이 내가 여기서 말한 것과 똑같이 생겼다는 뜻은 아닙니다. 컴파일러는 여러 가지
* 목적을 가지고 있으며, 여기서 상세히 설명한 것보다 많은 단계가 필요할지도 모릅니다.
*
* But now you should have a general high-level idea of what most compilers look
* like.
* 그러나 이제 여러분들은 대부분의 컴파일러가 어떻게 생겼는지 대해 일반사람들보다 높은 수준의 생각을
* 가져야 합니다.
*
* Now that Ive explained all of this, youre all good to go write your own
* compilers right?
* 이제 모든것을 설명했으니까, 여러분들은 여러분들만의 컴파일러를 작성할 있습니다. 그렇죠?
*
* Just kidding, that's what I'm here to help with :P
* 농담입니다, 그것이 바로 이글을 작성한 이유니까요 :P
*
* So let's begin...
* 그러면 시작해 볼까요...
*/
/**
@ -367,59 +344,55 @@
*/
/**
* We're gonna start off with our first phase of parsing, lexical analysis, with
* the tokenizer.
*
* We're just going to take our string of code and break it down into an array
* of tokens.
* 우리는 첫번재 단계로 parsing, lexical analysis를 tokenizer를 이용하여 시작할 겁니다.
*
* 우리는 앞에서 보았던 코드를 가지고 토큰의 배열로 분해할 것입니다.
*
* (add 2 (subtract 4 2)) => [{ type: 'paren', value: '(' }, ...]
*/
// We start by accepting an input string of code, and we're gonna set up two
// things...
// 코드 문자열을 받는것으로 부터 시작합니다.
// 그리고 두가지 것들을 설정합니다.
function tokenizer(input) {
// A `current` variable for tracking our position in the code like a cursor.
// `current` 변수는 위치를 추적합니다. 마치 커서처럼요.
let current = 0;
// And a `tokens` array for pushing our tokens to.
// `tokens` 배열은 토큰들이 들어가는 배열입니다.
let tokens = [];
// We start by creating a `while` loop where we are setting up our `current`
// variable to be incremented as much as we want `inside` the loop.
// `current` 변수를 내부에서 원하는 만큼 증가시키도록 설정하는 "while" 반목문을 만드는 것으로
// 시작합니다.
//
// We do this because we may want to increment `current` many times within a
// single loop because our tokens can be any length.
// 토큰의 길이가 어느 정도 될 수 있으므로 하나의 반복문 안에서 여러 번 `current`를 증가시키고자
// 할 수도 있기 때문입니다.
while (current < input.length) {
// We're also going to store the `current` character in the `input`.
// 현재 문자를 char에 저장합니다.
let char = input[current];
// The first thing we want to check for is an open parenthesis. This will
// later be used for `CallExpression` but for now we only care about the
// character.
// 우리가 가장 먼저 확인하고 싶은 것은 개방된 괄호 입니다. 나중에 `CallExpression`에
// 쓰이겠지만 현재로스는 문자에만 신경을 쓰도록 하겠습니다.
//
// We check to see if we have an open parenthesis:
// 개방된 괄호가 있는지 체크합니다:
if (char === '(') {
// If we do, we push a new token with the type `paren` and set the value
// to an open parenthesis.
// 만약 if을 통과한다면 개방된 괄호는 `paren` type과 값을 새 토큰으로 정의합니다.
tokens.push({
type: 'paren',
value: '(',
});
// Then we increment `current`
// `current를 증가시킵니다`
current++;
// And we `continue` onto the next cycle of the loop.
// `continue`를 통해 새로운 반복문을 시작합니다.
continue;
}
// Next we're going to check for a closing parenthesis. We do the same exact
// thing as before: Check for a closing parenthesis, add a new token,
// increment `current`, and `continue`.
// 다음으로 우리는 닫힌 괄호를 확인합니다. 우리는 이전과 같은 방법을 사용합니다: 닫힌 괄호를 확인하면
// 새로운 토큰을 추가하고 `current`를 증가시키고, `continue`를 실행합니다.
if (char === ')') {
tokens.push({
type: 'paren',
@ -429,83 +402,73 @@ function tokenizer(input) {
continue;
}
// Moving on, we're now going to check for whitespace. This is interesting
// because we care that whitespace exists to separate characters, but it
// isn't actually important for us to store as a token. We would only throw
// it out later.
// 이제 공백에 대해 확인해 볼 겁니다. 우리는 공백을 문자 분리를 위해 사용하지만, 실제로 토큰으로
// 저장하는 것은 중요하지 않습니다. 여기서는 공백은 거릅니다.
//
// So here we're just going to test for existence and if it does exist we're
// going to just `continue` on.
// 따라서 여기서는 단지 공백인지 체크한 후, `continue`를 호출합니다.
let WHITESPACE = /\s/;
if (WHITESPACE.test(char)) {
current++;
continue;
}
// The next type of token is a number. This is different than what we have
// seen before because a number could be any number of characters and we
// want to capture the entire sequence of characters as one token.
// 다음은 숫자 토큰 타입입니다. 이것은 앞에서 보았던 것과는 다릅니다. 왜냐하면 숫자는 어떤 숫자의
// 문자가 될 수 있기 때문이고 우리는 모든 숫자들을 하나의 숫자로 저장하기를 원합니다.
//
// (add 123 456)
// ^^^ ^^^
// Only two separate tokens
// 오직 2개의 토큰으로 분리되기를 원합니다.
//
// So we start this off when we encounter the first number in a sequence.
// 따라서 우리는 숫자의 가장 첫부분을 만났을 때부터 시작합니다.
let NUMBERS = /[0-9]/;
if (NUMBERS.test(char)) {
// We're going to create a `value` string that we are going to push
// characters to.
// `value`변수는 문자들을 저장합니다.
let value = '';
// Then we're going to loop through each character in the sequence until
// we encounter a character that is not a number, pushing each character
// that is a number to our `value` and incrementing `current` as we go.
// 이제 문자가 숫자가 아닐때까지 계속해서 `value`에 더하며 `current`값을 증가시킵니다.
while (NUMBERS.test(char)) {
value += char;
char = input[++current];
}
// After that we push our `number` token to the `tokens` array.
// 그 후, 완성된 `number` 토큰을 `tokens`배열에 넣습니다.
tokens.push({ type: 'number', value });
// And we continue on.
// 계속 진행합니다.
continue;
}
// We'll also add support for strings in our language which will be any
// text surrounded by double quotes (").
// 우리는 또한 큰따옴표(")로 둘러쌓여진 텍스트 문자열도 지원해야합니다.
//
// (concat "foo" "bar")
// ^^^ ^^^ string tokens
// ^^^ ^^^ 문자열 토큰들
//
// We'll start by checking for the opening quote:
// 따옴표로 시작하는지 체크합니다:
if (char === '"') {
// Keep a `value` variable for building up our string token.
// 문자열 토큰을 저장할 `value`변수를 만듭니다.
let value = '';
// We'll skip the opening double quote in our token.
// 큰 따옴표는 무시합니다.
char = input[++current];
// Then we'll iterate through each character until we reach another
// double quote.
// 큰 따옴표가 나오기 전까지 문자들을 저장합니다.
while (char !== '"') {
value += char;
char = input[++current];
}
// Skip the closing double quote.
// 닫는 큰따옴표는 무시합니다.
char = input[++current];
// And add our `string` token to the `tokens` array.
// 완성된 `string` 토큰을 `tokens` 배열에 추가합니다.
tokens.push({ type: 'string', value });
continue;
}
// The last type of token will be a `name` token. This is a sequence of
// letters instead of numbers, that are the names of functions in our lisp
// syntax.
// 마지막 토큰 타입은 `name` 입니다.
// 이것은 숫자 대신 글자의 순서, 즉 우리의 lisp 구문에 있는 함수의 이름입니다.
//
// (add 2 4)
// ^^^
@ -515,25 +478,23 @@ function tokenizer(input) {
if (LETTERS.test(char)) {
let value = '';
// Again we're just going to loop through all the letters pushing them to
// a value.
// 계속 반복하면서 value에 문자들을 저장합니다.
while (LETTERS.test(char)) {
value += char;
char = input[++current];
}
// And pushing that value as a token with the type `name` and continuing.
// 그리고 value를 `name` 타입으로 저장한 후 계속 진행합니다.
tokens.push({ type: 'name', value });
continue;
}
// Finally if we have not matched a character by now, we're going to throw
// an error and completely exit.
// 마지막으로, 매칭되지않는 문자들이 있다면, error를 던지고 완전히 종료합니다.
throw new TypeError('I dont know what this character is: ' + char);
}
// Then at the end of our `tokenizer` we simply return the tokens array.
// 그리고 `tokenizer`는 간단하게 tokens 배열을 반환하고 종료합니다.
return tokens;
}
@ -545,44 +506,41 @@ function tokenizer(input) {
*/
/**
* For our parser we're going to take our array of tokens and turn it into an
* AST.
* 우리들의 parser는, 토큰 배열을 가져다가 AST로 바꿉니다.
*
* [{ type: 'paren', value: '(' }, ...] => { type: 'Program', body: [...] }
*/
// Okay, so we define a `parser` function that accepts our array of `tokens`.
// 자 이제 `tokens` 배열을 인자로 받는 `parser`를 정의합시다.
function parser(tokens) {
// Again we keep a `current` variable that we will use as a cursor.
// `current` 변수는 위치를 저장하는 커서로 사용됩니다.
let current = 0;
// But this time we're going to use recursion instead of a `while` loop. So we
// define a `walk` function.
// 그러나 여기서는 `while` 반복문 대신 재귀를 사용합니다. 그래서 우리는 `walk` 함수를 정의할
// 겁니다.
function walk() {
// Inside the walk function we start by grabbing the `current` token.
// walk 함수 안에서 `current` 토큰을 가져와서 시작합니다.
let token = tokens[current];
// We're going to split each type of token off into a different code path,
// starting off with `number` tokens.
// 우리는 `number` 토큰을 시작으로 각 토큰을 다른 코드 경로로 나눌 것입니다.
//
// We test to see if we have a `number` token.
// `number` 토큰인지 확인합니다.
if (token.type === 'number') {
// If we have one, we'll increment `current`.
// 만약 우리가 그 코튼을 가지고 있다면 `current`를 증가시킵니다.
current++;
// And we'll return a new AST node called `NumberLiteral` and setting its
// value to the value of our token.
// 그리고 우리는 토큰 값과 `NumberLiteral`로 불리는 타입을 가진 새로운 AST노드를 반환
// 합니다.
return {
type: 'NumberLiteral',
value: token.value,
};
}
// If we have a string we will do the same as number and create a
// `StringLiteral` node.
// 만약 우리가 문자열을 가지고 있다면 `StringLiteral` 노드를 만듭니다.
if (token.type === 'string') {
current++;
@ -592,44 +550,38 @@ function parser(tokens) {
};
}
// Next we're going to look for CallExpressions. We start this off when we
// encounter an open parenthesis.
// 다음으로 우리는 CallExpressions을 찾을 겁니다. 이 작업은 열린 괄호를 만났을 때 실행 됩니다.
if (
token.type === 'paren' &&
token.value === '('
) {
// We'll increment `current` to skip the parenthesis since we don't care
// about it in our AST.
// AST에서 괄호를 생략하도록 `current`를 증가시킵니다.
token = tokens[++current];
// We create a base node with the type `CallExpression`, and we're going
// to set the name as the current token's value since the next token after
// the open parenthesis is the name of the function.
// `CallExpression` 타입의 기본 노드를 만듭니다. 그리고 열린 괄호 뒤에 있는 토큰이
// 함수의 이름이기 때문에 현재 토큰의 값으로 설정합니다.
let node = {
type: 'CallExpression',
name: token.value,
params: [],
};
// We increment `current` *again* to skip the name token.
// 이제 이름 토큰을 건너뛰기 위해서 `current`를 증가시킵니다.
token = tokens[++current];
// And now we want to loop through each token that will be the `params` of
// our `CallExpression` until we encounter a closing parenthesis.
// 그리고 이제 우리는 닫힌 괄호를 만날 때까지 각각의 코튼들이 `CallExpression`의 `params`이
// 되도록 순회하고자 한다.\
//
// Now this is where recursion comes in. Instead of trying to parse a
// potentially infinitely nested set of nodes we're going to rely on
// recursion to resolve things.
// 이제 재귀안으로 들어가 보자. 무한히 중첩될 수 있는 노드 집합을 분석하려고 시도하는 대신,
// 문제를 해결하기 위해 재귀 작업에 의존할 겁니다.
//
// To explain this, let's take our Lisp code. You can see that the
// parameters of the `add` are a number and a nested `CallExpression` that
// includes its own numbers.
// 이것에 대해 설명하기 위해서, Lisp 코드를 가져와보자. 여러분들은 addd 내부에 포함된
// 숫자들과 `CallExpression`을 볼 수 있을 것입니다.
//
// (add 2 (subtract 4 2))
//
// You'll also notice that in our tokens array we have multiple closing
// parenthesis.
// 여러분들은 또한 여러개의 닫힌 괄호를 가지고 있다는 것도 눈치 챘을 것입니다.
//
// [
// { type: 'paren', value: '(' },
@ -639,51 +591,47 @@ function parser(tokens) {
// { type: 'name', value: 'subtract' },
// { type: 'number', value: '4' },
// { type: 'number', value: '2' },
// { type: 'paren', value: ')' }, <<< Closing parenthesis
// { type: 'paren', value: ')' }, <<< Closing parenthesis
// { type: 'paren', value: ')' }, <<< 닫힌 괄호
// { type: 'paren', value: ')' }, <<< 닫힌 괄호
// ]
//
// We're going to rely on the nested `walk` function to increment our
// `current` variable past any nested `CallExpression`.
//
// 우리는 중첩된 `walk` 함수에 의존하여 중첩된 CallExpression을 넘어 `current` 변수를
// 증가시킬 것입니다.
// So we create a `while` loop that will continue until it encounters a
// token with a `type` of `'paren'` and a `value` of a closing
// parenthesis.
// 따라서 우리는 `paren` 타입과 닫힌 괄호의 `value`를 가진 토큰을 만날 때까지 반복되는
// `while` 반복문을 만들 것입니다.
while (
(token.type !== 'paren') ||
(token.type === 'paren' && token.value !== ')')
) {
// we'll call the `walk` function which will return a `node` and we'll
// push it into our `node.params`.
// `walk` 함수를 호출한 후 반횐되는 `node`를 `node.params`에 넣습니다.
node.params.push(walk());
token = tokens[current];
}
// Finally we will increment `current` one last time to skip the closing
// parenthesis.
// 마지막으로 우리는 `current`를 증가시켜 닫힌 괄호를 생략합니다.
current++;
// And return the node.
// 그리고 node를 반환합니다.
return node;
}
// Again, if we haven't recognized the token type by now we're going to
// throw an error.
// 다시 말씀드리지만, 토큰의 종류를 인식하지 못했을 경우, error를 던집니다.
throw new TypeError(token.type);
}
// Now, we're going to create our AST which will have a root which is a
// `Program` node.
// 이제, 우리들은 `Program`을 노드로 갖는 새로운 AST를 만들었습니다.
let ast = {
type: 'Program',
body: [],
};
// And we're going to kickstart our `walk` function, pushing nodes to our
// `ast.body` array.
// 그리고 우리는 `walk`함수를 이용하여 `ast.body` 함수를 빠르게 채워나갈 수 있습니다.
//
// The reason we are doing this inside a loop is because our program can have
// `CallExpression` after one another instead of being nested.
// 반복문 안에서 하는 이유는 우리 프로그램이 중첩이 아닌 `CallExpression`를 연달아 가질
// 수 있기 때문입니다.
//
// (add 2 2)
// (subtract 4 2)
@ -692,7 +640,7 @@ function parser(tokens) {
ast.body.push(walk());
}
// At the end of our parser we'll return the AST.
// 마지막으로 ast를 반환합니다.
return ast;
}
@ -704,9 +652,8 @@ function parser(tokens) {
*/
/**
* So now we have our AST, and we want to be able to visit different nodes with
* a visitor. We need to be able to call the methods on the visitor whenever we
* encounter a node with a matching type.
* 우리는 이제 AST를 가지고 visitor와 함께 다른 노드를 방문하려고 합니다. 우리는 일치하는 유형의
* 노드를 만날 때마다 visitor에 대해 메서드를 호출할 있어야 합니다.
*
* traverse(ast, {
* Program: {
@ -738,71 +685,66 @@ function parser(tokens) {
* });
*/
// So we define a traverser function which accepts an AST and a
// visitor. Inside we're going to define two functions...
// 따라서 우리는 AST를 순회하는 함수를 정의합니다. 이 함수 안에 두 함수를
// 정의할 것입니다.
function traverser(ast, visitor) {
// A `traverseArray` function that will allow us to iterate over an array and
// call the next function that we will define: `traverseNode`.
// `traverseArray` 함수는 배열로 반복할 수 있는 함수이며, 다음에 정의될 함수를
// 호출합니다: `traverseNode`.
function traverseArray(array, parent) {
array.forEach(child => {
traverseNode(child, parent);
});
}
// `traverseNode` will accept a `node` and its `parent` node. So that it can
// pass both to our visitor methods.
// `traverseNode`는 `node`와 그것의 `perent`노드를 받습니다. 따라서 visitor 매서드들을 모두
// 넘길 수 있습니다.
function traverseNode(node, parent) {
// We start by testing for the existence of a method on the visitor with a
// matching `type`.
// 우리는 먼저 visitor에 대한 메서드의 존재 여부를 'type'을 테스트하는것으로 시작합니다.
let methods = visitor[node.type];
// If there is an `enter` method for this node type we'll call it with the
// `node` and its `parent`.
// 이 노드 타입에 대한 `enter` 메서드가 있다면 `node`와 `parent`와 함께 호출할 것입니다.
if (methods && methods.enter) {
methods.enter(node, parent);
}
// Next we are going to split things up by the current node type.
// 다음으로 현재 노드들을 유형별로 구분합니다.
switch (node.type) {
// We'll start with our top level `Program`. Since Program nodes have a
// property named body that has an array of nodes, we will call
// `traverseArray` to traverse down into them.
// 우리는 최상위 경로인 `Program`에서 시작합니다. Program 노드들은 노드들의 배열을 가지고
// 있는 body라는 속성을 가지고 있기 때문에, 우리는 `traverseArray`를 호출하여 그들을
// 순회할 것입니다.
//
// (Remember that `traverseArray` will in turn call `traverseNode` so we
// are causing the tree to be traversed recursively)
// (`traverseArray`는 `traverseNode`를 호출하므로, 따라서 우리는 트리를 재귀적으로 돌고
// 있다는 것을 기억해야합니다)
case 'Program':
traverseArray(node.body, node);
break;
// Next we do the same with `CallExpression` and traverse their `params`.
// 다음으로 `CallExpression`는 `params`과 함께 똑같은 작업을 합니다.
case 'CallExpression':
traverseArray(node.params, node);
break;
// In the cases of `NumberLiteral` and `StringLiteral` we don't have any
// child nodes to visit, so we'll just break.
// `NumberLiteral`과 `StringLiteral`의 경우 방문해야할 자식 노드가 없으므로 넘어
// 갑니다.
case 'NumberLiteral':
case 'StringLiteral':
break;
// And again, if we haven't recognized the node type then we'll throw an
// error.
// 노드 타입을 인식할수 없다면, error를 던집니다.
default:
throw new TypeError(node.type);
}
// If there is an `exit` method for this node type we'll call it with the
// `node` and its `parent`.
// 만약 'exit' 메서드가 존재한다면, `node`와 `parent`와 함께 호출합니다.
if (methods && methods.exit) {
methods.exit(node, parent);
}
}
// Finally we kickstart the traverser by calling `traverseNode` with our ast
// with no `parent` because the top level of the AST doesn't have a parent.
// 마지막으로 traverser 함수는 `parent`가 없는 ast와 함쎄 `traverseNode`를 호출합니다.
// 왜냐하면 최상위 AST는 부모를 가지고 있지 않기 때문입니다.
traverseNode(ast, null);
}
@ -814,9 +756,8 @@ function traverser(ast, visitor) {
*/
/**
* Next up, the transformer. Our transformer is going to take the AST that we
* have built and pass it to our traverser function with a visitor and will
* create a new ast.
* 다음으로, transformer입니다. transformer는 AST를 가지고 visitor와 함께 새로운 ast를
* 만들 것입니다.
*
* ----------------------------------------------------------------------------
* Original AST | Transformed AST
@ -847,18 +788,17 @@ function traverser(ast, visitor) {
* | type: 'NumberLiteral',
* | value: '2'
* | }]
* (sorry the other one is longer.) | }
* (죄송하지만 너무 길어서 생략합니다. ) | }
* | }
* | }]
* | }
* ----------------------------------------------------------------------------
*/
// So we have our transformer function which will accept the lisp ast.
// 따라서 transformer 함수는 lisp ast를 받습니다.
function transformer(ast) {
// We'll create a `newAst` which like our previous AST will have a program
// node.
// 이전 AST와 마찬가지로 Program 노드를 갖는 newAst를 만들 것입니다.
let newAst = {
type: 'Program',
body: [],
@ -873,15 +813,14 @@ function transformer(ast) {
// new ast.
ast._context = newAst.body;
// We'll start by calling the traverser function with our ast and a visitor.
// 우리는 ast 그리고 visitor와 함께 traverser 함수를 호출하면서 시작합니다.
traverser(ast, {
// The first visitor method accepts any `NumberLiteral`
// 첫번째 visitor 메서드는 `NumberLiteral` 입니다.
NumberLiteral: {
// We'll visit them on enter.
// 방문할때 메서드를 정의합니다.
enter(node, parent) {
// We'll create a new node also named `NumberLiteral` that we will push to
// the parent context.
// `NumberLiteral` 이름의 새 노드를 만든 후 부모의 context에 추가합니다.
parent._context.push({
type: 'NumberLiteral',
value: node.value,
@ -889,7 +828,7 @@ function transformer(ast) {
},
},
// Next we have `StringLiteral`
// 다음으로 `StringLiteral` 입니다.
StringLiteral: {
enter(node, parent) {
parent._context.push({
@ -899,7 +838,7 @@ function transformer(ast) {
},
},
// Next up, `CallExpression`.
// 다음은 `CallExpression`.
CallExpression: {
enter(node, parent) {
@ -939,8 +878,7 @@ function transformer(ast) {
}
});
// At the end of our transformer function we'll return the new ast that we
// just created.
// transformer 함수가 새로운 newAst를 반환함으로써 완성됩니다.
return newAst;
}
@ -952,35 +890,32 @@ function transformer(ast) {
*/
/**
* Now let's move onto our last phase: The Code Generator.
* 이제 마지막 단계로 넘어갑시다: 코드 제너레이터.
*
* Our code generator is going to recursively call itself to print each node in
* the tree into one giant string.
* 코드제너레이터는 재귀적으로 호출하여 트리의 노드들을 하나의 거대한 문자열로 출력할 것입니다.
*/
function codeGenerator(node) {
// We'll break things down by the `type` of the `node`.
// `node`의 타입에 따라 진행합니다.
switch (node.type) {
// If we have a `Program` node. We will map through each node in the `body`
// and run them through the code generator and join them with a newline.
// `Program` 노드일 경우입니다. `body`의 각 노드를 맵으로 돌면서 코드 제너레이터를 거치면서
// 새로운 줄로 합칩니다.
case 'Program':
return node.body.map(codeGenerator)
.join('\n');
// For `ExpressionStatement` we'll call the code generator on the nested
// expression and we'll add a semicolon...
// `ExpressionStatement`의 경우, 중첩된 코드 제네레이터를 호출합니다. 그리고 세미콜론을
// 추가합니다...
case 'ExpressionStatement':
return (
codeGenerator(node.expression) +
';' // << (...because we like to code the *correct* way)
';' // << (...우리는 *올바른* 방식으로 코드화 하려하기 때문입니다)
);
// For `CallExpression` we will print the `callee`, add an open
// parenthesis, we'll map through each node in the `arguments` array and run
// them through the code generator, joining them with a comma, and then
// we'll add a closing parenthesis.
// `CallExpression`의 경우, `callee`를 출력한 후 괄호를 열고, `arguments`를 맵으로
// 돌면서 코드 제네레이터를 통과시킨 후, 콤마와 함께 join한후 괄호를 닫습니다.
case 'CallExpression':
return (
codeGenerator(node.callee) +
@ -990,19 +925,19 @@ function codeGenerator(node) {
')'
);
// For `Identifier` we'll just return the `node`'s name.
// `Identifier`의 경우 `node`의 이름을 반환합니다.
case 'Identifier':
return node.name;
// For `NumberLiteral` we'll just return the `node`'s value.
// `NumberLiteral`의 경우, `node`의 값을 반환합니다.
case 'NumberLiteral':
return node.value;
// For `StringLiteral` we'll add quotations around the `node`'s value.
// `StringLiteral`의 경우, 큰 따옴표로 둘러쌓인 `node`의 값을 반환합니다.
case 'StringLiteral':
return '"' + node.value + '"';
// And if we haven't recognized the node, we'll throw an error.
// 그리고 노드로 인식하지 못한 결우에, error를 던집니다.
default:
throw new TypeError(node.type);
}
@ -1016,8 +951,8 @@ function codeGenerator(node) {
*/
/**
* FINALLY! We'll create our `compiler` function. Here we will link together
* every part of the pipeline.
* 마침내! 우리는 '컴파일러' 함수를 만들 것입니다. 여기에 우리가 만든 모든 것들을 파이프라인으로
* 연결합니다.
*
* 1. input => tokenizer => tokens
* 2. tokens => parser => ast
@ -1031,18 +966,18 @@ function compiler(input) {
let newAst = transformer(ast);
let output = codeGenerator(newAst);
// and simply return the output!
// 그리고 output을 리턴합니다
return output;
}
/**
* ============================================================================
* (˃̵˂̵)و
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!YOU MADE IT!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!여러분이 만든거에요!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
* ============================================================================
*/
// Now I'm just exporting everything...
// 이제 모든것을 exports 합니다...
module.exports = {
tokenizer,
parser,

Loading…
Cancel
Save