# 서울숲_D_Day13 리뷰 레포트 ## 참석자 신용호, 여재환, 이영훈, 이정민 ## 1. 코드 동작 이해 ### Tokenizer - syntax를 통해서 정의해준 token unit으로 쪼개는 역할을 한다. - switch문이나 if문을 통해서 syntax별로 분류하여 array에 담는다. ### Lexer - token에 의미(NULL, string, Number, Array)를 부여한다 - 기본적으로 Tokenizer와 같은 역할을 수행하지만 대개 extra context로의 접근이 필요하다. - switch문이나 if문을 통해서 각 token별로 의미를 넣어준다. ### Parser - Lexer를 통해 의미를 부여한 데이터를 JSON 형태로 구조화한다. - 일반적으로 Parser의 역할은 lexer에서 불러들인 토큰들을 AST의 형태로 프로그램을 표현하는 것이다. - 반복문 혹은 재귀문을 통해 array를 만나면 child에 새로운 array 객체를 생성하고 아닐경우 child 배열에 객체를 넣는다. ## 2. 코드 동작 개선 ### 신용호 - Tokeniser에서 문자열 단위로 쪼개서 해석하는 것보다 문자 단위로 쪼개서 해석해보는 것을 구현 - 이스케이프 문자를 입력에 사용했을 때의 처리를 추가 ### 여재환 - 정규표현식의 다양한 활용을 통해 코드의 간결성을 높일 수 있다. - 예외처리에 대해 보다 많은 경우의 수에 대한 고려가 필요하다. - Parser의 함수구조가 매우 복잡한데 보다 깊이있는 설계를 통해 간결한 recursion으로 바꿔보자. ### 이영훈 - flag를 두는 것보다 함수에 상태를 만들어 처리하는 것이 좋을 것 같다. - 에러처리나 조건문에서 비교를 하는 함수를 만들어 가독성을 높여야한다. ### 이정민 * parser에서 반복문을 사용하며, stack에 현재 위치를 저장하는 방식을 선택했는데, 대신 재귀를 사용해볼 것. * 더 많은 예외처리에 대비해보기. ## 3. Consideration ### 스스로 확인할 사항 #### 무한 중첩된 구조를 프로그래밍 하는데 효과적인 방법은 무엇인가? 재귀인가, 반복인가? * 특정한 basecase를 통해 반복의 끝을 표시함으로써 코드의 간결성, 가독성을 위해서는 재귀가 더 우수하다. * 그러나 재귀는 많은 스택으로 인하여 디버깅이 힘들고, 스택오버플로우가 발생하기 쉽다는 문제가 있다. * 재귀에서 중복되는 부분은 memoization이라는 기법을 통해 값을 바로 불러오는 방식으로 처리해주면 성능이 좋아진다. #### 에러처리에 대한 흐름제어를 할 수 있는가? * 전체적인 함수를 `try {} catch(error){}` 구문으로 감싸고, 함수 실행시 에러 처리를 해줄 부분에서 throw Error를 해준다. * throw Error를 만나는 즉시 함수는 try 를 빠져나가 catch로 넘어간다. 에러가 발생하지 않으면 try 안에서 정상 실행된다. * `finally{}`는 에러 발생 여부와 관계없이 try, catch 가 모두 실행된 후 실행된다. ### 다같이 확인할 사항 #### 소프트웨어 개발에서, token, lexer, parser의 역할은 각각 무엇인지 조사해보자. - 컴파일러의 front end에 해당하며 특정 언어에 대한 syntax와 semantic을 확인하는 과정이다. #### [token의 종류](https://en.wikipedia.org/wiki/Lexical_analysis#Token) + identifier: names the programmer chooses; + keyword: names already in the programming language; + separator (also known as punctuators): + punctuation characters and paired-delimiters; + operator: symbols that operate on arguments and produce results; + literal: numeric, logical, textual, reference literals; + comment: line, block. #### [lexer](https://en.wikipedia.org/wiki/Lexical_analysis) - 일련의 character를 일련의 token들로 만들어주는 작업을 담당 #### [parser](https://en.wikipedia.org/wiki/Parsing#Parser) - 특정 입력을 받아서 그것으로 데이터 구조를 만들어주는 작업을 담당 - 토큰들을 AST(Abstract syntax tree)라는 트리를 구성함으로써 프로그램의 형태를 표현. #### 이번 미션에서 구현한, token, lexer, parser의 각각 역할과 동작방식을 요약해서 설명해보자. - token은 syntax에 따라 나누는 작업으로 character등을 구분할 syntax로 나누어 준다. - lexer: tokenizer로 가공된 배열을 순회하며 bracket, string, number, null 등 원하는 분류에 따라 구분해주며 semantic을 부여한다. - `{type: string, value: 'hi'}` 등의 object array로 구현하거나 - `[type, value]` 의 배열이 중첩된 형태로 구현했다. - parser: lexer을 통해 의미가 부여된 토큰들에 대해 recursive 혹은 stack을 이용하여 iterative하게 순회함으로써 AST를 구성. ### reference - 참고: [Compiler 위키](https://en.wikipedia.org/wiki/Compiler) - [Automata theory](https://en.wikipedia.org/wiki/Automata_theory) ### abstract syntax tree - 각 노드는 소스코드에서 발생하는 구조체 - 컴파일러는 내부적으로 여러가지 컴포넌트로 구성되어 있는데 첫단계가 소스코드를 파싱하여 AST로 만드는 작업 모든 고급언어의 컴파일러가 하는 역할 - AST를 검사하고 최적화하고 정해진 기계어로 대치해서 나오는것이 기계어 덩어리, 목적코드 - 컴파일러는 파싱과정에서 문법의 일치 유무를 판단 할 수 있는데 문법상 틀리지 않았지만 연산자 우선순위가 구현되지 않았다면 결정을 못내리는 경우가 생긴다 -> 모호하다(ambiguous) - 연산자 우선순위는 AST를 구성하는 과정에서 문법적으로 옳은 두가지 이상의 경우를 만났을 때 모호함을 없애기 위한 규칙 ### 정규식을 만드는 방법 - 리터럴을 사용하는 방법 ```Javascript var re = /ab+c/; ``` - RegExp 객체의 생성자 ```Javascript var re = new RegExp("ab+c"); ``` - 생성자 함수를 사용하면 정규식이 실행 시점에 컴파일 된다. - 정규식의 패턴이 변경될 수 있는 경우 혹은 사용자 입력과 같이 다른 출처로 부터 패턴을 가져 오는 경우 생성자 사용 #### test - 대응되는 문자열이 있는지 검사하는 RegExp 메소드 입니다. true 나 false를 반환합니다. - test() 는 regexp.test(string) - test() 함수는 정규식과 비교하여 포함되지 않으면 true 반환, 포함되면 false 반환 #### match - 대응되는 문자열을 찾는 RegExp 메소드입니다. 정보를 가지고 있는 배열을 반환합니다. 대응되는 문자열을 찾지 못했다면 null을 반환합니다. - match() 는 string.match(regexp) - match() 함수는 정규식과 비교하여 포함되지 않으면 비교문구를 반환, 포함되면 null, 빈값 반환