<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>namhyun-gu.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Tue, 03 Nov 2020 11:23:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>namhyun-gu.log</title>
            <url>https://images.velog.io/profiles/namhyun-gu/thumbnails/1572157684.115.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. namhyun-gu.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/namhyun-gu" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[Android Studio Canary에서 발생하는 NoClassDefFoundError: ...PropertiesKt 해결법]]></title>
            <link>https://velog.io/@namhyun-gu/Android-Studio-Canary%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-NoClassDefFoundError-...PropertiesKt-%ED%95%B4%EA%B2%B0%EB%B2%95</link>
            <guid>https://velog.io/@namhyun-gu/Android-Studio-Canary%EC%97%90%EC%84%9C-%EB%B0%9C%EC%83%9D%ED%95%98%EB%8A%94-NoClassDefFoundError-...PropertiesKt-%ED%95%B4%EA%B2%B0%EB%B2%95</guid>
            <pubDate>Tue, 03 Nov 2020 11:23:49 GMT</pubDate>
            <description><![CDATA[<p>Android Studio Canary 15를 통해 Jetpack Compose를 이용하려고 하니 다음과 같은 오류가 발생하였습니다.</p>
<blockquote>
<p>NoClassDefFoundError: org/jetbrains/kotlin/cli/common/PropertiesKt</p>
</blockquote>
<p>이 문제는 사용중인 JDK 버전때문에 발생하는 문제로
저의 경우 JDK를 <a href="https://adoptopenjdk.net/index.html?variant=openjdk8&amp;jvmVariant=hotspot">adopt-openjdk-1.8.0_272</a>으로 변경하였더니 해결되었습니다.</p>
<p>JDK를 변경하는 건 다음 순서로 들어가면 변경할 수 있습니다.</p>
<blockquote>
<p>File &gt; Project Structure &gt; JDK Location</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[두번째 목표 달성]]></title>
            <link>https://velog.io/@namhyun-gu/%EB%91%90%EB%B2%88%EC%A7%B8-%EB%AA%A9%ED%91%9C-%EB%8B%AC%EC%84%B1</link>
            <guid>https://velog.io/@namhyun-gu/%EB%91%90%EB%B2%88%EC%A7%B8-%EB%AA%A9%ED%91%9C-%EB%8B%AC%EC%84%B1</guid>
            <pubDate>Thu, 15 Oct 2020 16:02:45 GMT</pubDate>
            <description><![CDATA[<p>지난 <a href="https://velog.io/@namhyun-gu/%EC%B2%AB-%EB%AA%A9%ED%91%9C-%EB%8B%AC%EC%84%B1">첫 목표 달성</a> 글에서 Lexer, Parser, Compiler를 구현하고 mdx 파일예 json 값의 데이터를 바인딩하는데 성공했다.</p>
<p>소스코드는 <a href="https://github.com/namhyun-gu/dot-mdx">Github</a>에서 확인할 수 있다.</p>
<h2 id="동작-원리">동작 원리</h2>
<p>이후 다음 목표로 삼았던 다른 파일에서 불러오기 기능은 완성했는데 완성된 기능은 다음과 같이 동작한다.</p>
<pre><code class="language-typescript">// hello.mdx
${include &quot;header&quot;}
World

// header.mdx
# Hello

&gt; dot-mdx hello.mdx output.mdx

// output.mdx
# Hello
World</code></pre>
<p><strong>hello.mdx</strong> 파일은 Lexer에 의해 다음과 같이 토큰화된다.</p>
<pre><code class="language-typescript">[
    &lt;Token&gt;{ type: TokenType.L_Block, line: 1 },
    &lt;Token&gt;{ type: TokenType.Keyword, value: &quot;include&quot;, line: 1 },
    &lt;Token&gt;{ type: TokenType.Literal, value: &quot;header&quot;, line: 1 },
    &lt;Token&gt;{ type: TokenType.R_Block, line: 1 },
    &lt;Token&gt;{ type: TokenType.NewLine, line: 1 },
    &lt;Token&gt;{ type: TokenType.Text, value: &quot;World&quot;, line: 1 },
    &lt;Token&gt;{ type: TokenType.EOS },
]</code></pre>
<p>이번 기능에 <strong>Keyword, Literal</strong> 타입의 토큰이 추가되었으며,
<strong>Keyword</strong>  토큰은 현재 include 키워드에만 대응한다.
<strong>Literal</strong> 토큰은 &quot;header&quot; 와 같이 양 옆에 따옴표가 있는 경우에만 처리된다.</p>
<p><strong>Lexer</strong>에 의해 생성된 토큰 목록은 <strong>Parser</strong>에서 다음과 같은 AST를 생성한다.</p>
<pre><code class="language-typescript">Document([
    BlockStatement([
        IncludeDeclaration(
            Literal(&quot;hello&quot;)
        )
    ]),
    LineSeparator(),
    DocumentText(&quot;World&quot;)
]</code></pre>
<p>이전에는 Block 안에 <strong>Identifier</strong>만 정의할 수 있어서 Identifier가 정의된 Block를 <strong>IdentifierDeclaration</strong>으로 변환하였지만
include 키워드를 지원하게 됨으로써 <strong>BlockStatement</strong>와 <strong>IncludeDeclaration</strong>이 추가되었으며 기존 <strong>IdentifierDeclaration</strong>는 <strong>Identifier</strong>로 변경했다.</p>
<p><strong>BlockStatement</strong>가 추가됨에 따라 블록과 블록 내 내용에서 대해서는 다음 함수를 통해 노드를 변환하도록 동작을 변경했다.
이번 기능을 구현하면서 블록이 온전히 닫히지 않았을 때 파싱 오류를 반환하도록 추가했다.</p>
<pre><code class="language-typescript">private parseBlock(): Node | undefined {
    const blockToken = this.tokenStream.peek();
    this.tokenStream.advance();
    const blockBody: Array&lt;Token&gt; = [];
    while (this.tokenStream.peek().type != TokenType.R_Block) {
      const token = this.tokenStream.peek();
      if (token.type == TokenType.EOS) {
        throw new Error(
          `Unexpected token, Require close parenthesis (line: ${blockToken.line})`
        );
      }
      blockBody.push(token);
      this.tokenStream.advance();
    }
    this.tokenStream.advance();

    const body: Array&lt;Node&gt; = [];
    if (blockBody.length &gt; 0) {
      const bodyStream = new TokenStream(blockBody);
      const expr = this.parseInBlockExpr(bodyStream);
      if (expr) {
        body.push(expr);
      }
    }
    return new BlockStatement(body);
  }

private parseInBlockExpr(tokenStram: TokenStream): Node | undefined {
    switch (tokenStram.peek().type) {
      case TokenType.Keyword:
        return this.parseKeyword(tokenStram);
      case TokenType.Identifier:
        return this.parseIdentifier(tokenStram);
      case TokenType.Literal:
        return this.parseLiteral(tokenStram);
    }
    return undefined;
  }</code></pre>
<p>마지막으로 <strong>Compiler</strong>에서는 <strong>IncludeDeclaration</strong>를 다음과 같이 처리한다.</p>
<p>이 기능을 추가하며 컴파일러에서 파일을 제어할 수 있어야 하기에 단순히 <strong>ServiceLocator</strong>를 추가하고, <strong>SourceLoader</strong>를 가져옴으로써 해결하였다.
<strong>SourceLoader</strong>는 컴파일을 처음 시작한 파일의 폴더 경로를 갖게 되며 폴더 경로에 <strong>IncludeDeclaration</strong> 내 <strong>Literal</strong>의 값을 더해 파일을 읽어온다.</p>
<p>파일을 읽어오고 <strong>compiler</strong> 함수를 수행하여 그 결과를 현재의 컴파일러의 값에 추가한다.</p>
<pre><code class="language-typescript">private transformIncludeDeclaration(node: IncludeDeclaration) {
    const sourceLoader: SourceLoader = services.get(&quot;source-loader&quot;);
    if (sourceLoader) {
      const source = node.source.value; // Literal 노드의 값을 가져온다
      const result = compile(
        sourceLoader.load(source),
        JSON.stringify(this.data)
      );
      this.append(result);
    }
  }</code></pre>
<h2 id="다음-목표">다음 목표</h2>
<p>Lexer, Parser를 직접 구현하면서 Regex를 다루는 방법 등 많은 것을 배울 수 있었지만
&#39;\n&#39;만 있는 줄은 Lexer가 처리를 못해 무한 루프에 빠지는 등 문제가 있었으며, 처리해야할 규칙이 추가됨에 따라 가독성이 떨어지는 것 같다.</p>
<p>프로그래밍 언어론때 들었던 CFG와 같은 문법을 작성하여 파싱을 도와주는 여러 라이브러리가 있던데 이들 라이브러리들 중에 하나를 선정하여
읽기 쉽게 개선을 하는 것이 목표이며, 그리고 이번 기능을 구현할 때 급한대로 <strong>ServiceLocator</strong>를 이용하여 소스코드를 읽어오도록 했는데 더 나은 방법이 있는지 고민해봐야 될 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[첫 목표 달성]]></title>
            <link>https://velog.io/@namhyun-gu/%EC%B2%AB-%EB%AA%A9%ED%91%9C-%EB%8B%AC%EC%84%B1</link>
            <guid>https://velog.io/@namhyun-gu/%EC%B2%AB-%EB%AA%A9%ED%91%9C-%EB%8B%AC%EC%84%B1</guid>
            <pubDate>Sat, 10 Oct 2020 08:52:59 GMT</pubDate>
            <description><![CDATA[<p>프로젝트를 생성하며 목표로 잡았던 첫 기능을 오늘 끝냈는데 목표로 했던 기능은 다음과 같다.</p>
<pre><code class="language-javascript">// hello.mdx
Hello, ${name}

// hello.json
{ &quot;name&quot;: &quot;World&quot; }

&gt; dot-mdx hello.mdx hello.json
Hello, World</code></pre>
<h3 id="구현-방법">구현 방법</h3>
<p><img src="https://images.velog.io/images/namhyun-gu/post/de8c2d73-c75d-436b-8aa2-748d29b569ca/structure-resize.png" alt=""></p>
<ol>
<li>파일의 내용을 <strong>Lexer</strong>가 Regex을 통해 토큰 목록을 생성한다.</li>
<li>만들어진 토큰 목록을 <strong>Parser</strong>가 AST를 생성한다.</li>
<li><strong>Compiler</strong>가 AST를 방문하며 결과물을 출력한다.</li>
</ol>
<h3 id="lexer-동작-원리">Lexer 동작 원리</h3>
<p>파일 내용의 첫 문자부터 <strong>Regex</strong>를 이용하여 아래의 순서대로 다음 토큰에 맞는지 확인하도록 했다.</p>
<ul>
<li>L_Block (&#39;${&#39;)</li>
<li>R_Block (&#39;}&#39;)</li>
<li>Identifier (블록 내 문자열)</li>
<li>NewLine (&#39;\n&#39;)</li>
<li>Text (블록 밖의 문자열)</li>
</ul>
<p>각 토큰별 <strong>Regex</strong> 패턴에 일치하면 매치 결과를 얻을 수 있는데
이에 따라 토큰을 생성하고 해당 토큰 다음 위치로 이동하여 이 과정을 반복하도록 했다.</p>
<pre><code class="language-typescript">private scan(type: TokenType, regex: RegExp) {
    let captures;
    if ((captures = regex.exec(this.input))) {
      const length = captures[0].length;
      let token: Token;
      if (captures.length &gt; 1) {
        // 패턴에 Capture Group가 있는 경우 (Identifier, Text)
        const value = captures[1]; 
        token = &lt;Token&gt;{ type, value, line: this.lineNo };
      } else {
        token = &lt;Token&gt;{ type, line: this.lineNo };
      }
      this.move(length); // 
      return token;
    } else {
      return null;
    }
  }</code></pre>
<p>앞선 과정으로 <code>Hello, ${name}</code> 문자열을 처리하게 되면 다음 토큰 목록을 얻을 수 있다.</p>
<pre><code class="language-typescript">[
      &lt;Token&gt;{ type: TokenType.Text, value: &quot;Hello, &quot;, line: 1 },
      &lt;Token&gt;{ type: TokenType.L_Block, line: 1 },
      &lt;Token&gt;{ type: TokenType.Identifier, value: &quot;name&quot;, line: 1 },
      &lt;Token&gt;{ type: TokenType.R_Block, line: 1 },
      &lt;Token&gt;{ type: TokenType.EOS },
]</code></pre>
<h3 id="parser-동작-원리">Parser 동작 원리</h3>
<p>앞서 Lexer를 통해 토큰 목록을 얻었다면 토큰을 통해 다음과 같은 트리를 생성한다.</p>
<p><img src="https://images.velog.io/images/namhyun-gu/post/388f8468-290f-44a1-b52e-8949effee980/parser-tree.png" alt=""></p>
<p>트리는 토큰 목록을 순차적으로 탐색하며 생성하게 되며
블록을 처리할 떄는 아래의 순서로 처리하도록 했다.</p>
<ol>
<li>토큰 타입이 <strong>L_Block</strong> 일때 다음 토큰으로 이동한다.</li>
<li>다음 토큰의 타입이 <strong>Identifier</strong> 이라면, 그 다음 토큰의 타입을 확인한다.</li>
<li><strong>Identifier</strong> 다음 토큰이 <strong>R_Block</strong>이라면 <strong>IdentifierDeclaration</strong> 노드를 생성하여 리턴한다. 그렇지 않다면 오류를 발생시킨다.</li>
</ol>
<h3 id="compiler-동작-원리">Compiler 동작 원리</h3>
<p>Parser에서 만든 AST를 탐색하며 결과를 생성하는데 컴파일 시 Json 형식으로 이루어진 문자열을 읽어 <strong>IdentifierDeclaration</strong> 노드를 변환하도록 했다.</p>
<pre><code class="language-typescript">private transformIdentifierDeclaration(node: IdentifierDeclaration) {
    this.append(this.data[node.name]);
  }</code></pre>
<h3 id="다음-목표">다음 목표</h3>
<p>마크다운 문서를 만들면서 반복되는 내용에 대해 다른 파일에서 가져오는 것을 필요하다 생각했기에 이 기능이 다음 목표이다.</p>
<pre><code class="language-javascript">// hello.mdx
${include header}, World

// header.mdx
# Hello

&gt; dot-mdx hello.mdx
# Hello, World</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 생성]]></title>
            <link>https://velog.io/@namhyun-gu/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</link>
            <guid>https://velog.io/@namhyun-gu/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%EC%83%9D%EC%84%B1</guid>
            <pubDate>Tue, 06 Oct 2020 14:10:09 GMT</pubDate>
            <description><![CDATA[<p>새 프로젝트로 dot-mdx 라는 프로젝트를 진행하기로 하였는데,
이는 <a href="https://pugjs.org/api/getting-started.html">Pug</a>와 같은 마크다운 템플릿 엔진을 만드는 것을 목표로 한다.</p>
<p><del>프로젝트는 관심이 있었던 <a href="https://www.rust-lang.org/">Rust</a> 언어로 개발을 진행하였는데
Rust가 제공하는 Cargo라는 패키지 도구를 통해 프로젝트를 쉽게 생성할 수 있어 Go 언어보다 다루기 좋은 것 같다.</del>
라고 생각했으나 목표를 완성하기에 오랜 시간이 걸릴 것 같아  Typescript로 개발을 진행하기로 했다.
어느정도 완성이 되었을 때 점차 언어를 변경하는 것이 좋을 것 같다.</p>
<h3 id="첫-목표">첫 목표</h3>
<p>mdx 확장자를 가진 파일을 컴파일하여 md를 파일을 생성하며
컴파일 시에 json 파일을 제공하여 mdx에 정의된 변수를 채울 수 있도록 하는 것이 첫번째 목표이다.</p>
<ul>
<li><p>명령어 예</p>
<pre><code class="language-bash">mdx hello.mdx hello.json</code></pre>
</li>
<li><p>hello.mdx</p>
<pre><code>Hello, ${name}</code></pre></li>
<li><p>hello.json</p>
<pre><code>{
  &quot;name&quot;: &quot;Test&quot;
}</code></pre></li>
<li><p>hello.md</p>
<pre><code>Hello, Test</code></pre></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>