본문 바로가기

C#/근웹 연대기

c#으로 근본 없는 웹서버 개발기 21 : cshtml 파싱 - tokenize

⚠WARNING
ASP.NET에 대한 포스팅이 아닙니다
나가실 문은 오른쪽 하단입니다

 

1. 횡설수설(橫說竪說)

머릿속에서 정리되지 않는 것들에 몰두하다 보니. 포스팅을 한 박자 쉬어버렸다.

그러나 두 박자 쉬고 세 박자 마저 쉬더라도 네박자 이상은 건너뛰지 않으려고 한다.

나 자신의 메타인지에 근거하여 네박자 이상 쉬면 글쓰기에 대한 생각이 휘발성 메모리처럼 완전히 날아가버릴 것 같기 때문이다.

그렇다

자기자신을 통제하기 힘들 때는 습관을 만드는 것이 중요하다.

생각이 바뀌면 행동이 바뀌고

행동이 바뀌면 습관이 바뀌고

습관이 바뀌면 인격이 바뀌고

인격이 바뀌면 운명이 바뀐다

윌리엄 제임스

 

2. 파싱에 대한 심득(心得)

지금까지 행하여 왔던 파싱작업에 비하면 지금 진행하려는 작업은 꽤나 복잡하다.

 

때때로, 복잡함을 단순화 하고 파편화하여 이상의 아이디얼을 추구하는 과정은 주화입마에 빠지게 한다.
이런들 어떠하리 저런들 어떠하리 모든 가능성을 열어놓으면 
만수산 드렁칧이 얽히고 꼬여서 세월이가 내월이가 될 것이다.

그래서 믿을 수 없을 만큼 Loop가 돌아버리더라도 불변의 원칙을 세워 재귀적인 생각을 원천 차단해야 한다.
그렇다
복잡할 수록 SIMPLE IS BEST라는 기본원칙에 가까이 가까이 더 가까이.
그렇다
내가 사용하는 C S#arp에는 어려운 이론은 필요 없다.

원칙 1. 이미 존재하는 이론에 집착하지 않는다. 자신만의 논리를 빌드하여 자급자족적인 창의력을 키운다.
윈칙 2. 교과서가 아닌 오류 메시지로부터 배운다. 시행착오를 거듭할수록 논리적 사고는 단단해진다.
원칙 3. 시간을 들여서 열심히 하려고 하지 말고 효율적인 구조를 생각하는 습관을 기른다.

 

3. 본론

3-1 파싱의 기본 3원칙

원칙 1. 상수, 변수 또는 키워드가 되는 문자(abcd123...)와 연산자처럼 의미를 표현하는 문자(!@#$%^&...)로 분리한다.
원칙 2. 분리된 문자와 문자 사이에 규칙성을 파악하여 하나의 의미 있는 구문으로 일괄한다.
원칙 3. 공통적으로 적용되는 규칙과 그렇지 않은 것으로 분리한다.

 복잡할땐 기본으로 회귀하자.

3-2 Tokenize

다음은 원칙 1에 기반으로 코드를 작성하였다.

Tokenizer.cs

namespace dotweb;
public partial class TemplateEngine
{
    enum TTYPE { WHITE_SPACE, FUNCTIONAL, LITERAL }
    private readonly HashSet<string> tokStr = new(("@,@(,@*,*@,@{,\",',#,`,&,~,^,<<,>>,!,&&" +
            ",||,==,!=,!==,>,<,>=,<=,?,??,??=,=,+=,-=,//,/*,*/,*=,/=,%=,*=,+" +
            ",-,*,/,++,--,=,(,),[,],{,},:,>,<,;,\\,.").Split(","))
    { "," };
    private TTYPE GetType(string str)
    {
        if (String.IsNullOrWhiteSpace(str)) return TTYPE.WHITE_SPACE;
        if (tokStr.Contains(str)) return TTYPE.FUNCTIONAL;
        return TTYPE.LITERAL;
    }

    private List<string> Tokenize(string txt)
    {
        var buff = ")";
        var rs = new List<String>();
        var preType = GetType(buff.ToString());
        foreach (Char ch in txt)
        {
            var type = GetType(ch.ToString());
            if (type != preType || (type is TTYPE.FUNCTIONAL && type != GetType(buff + ch)))
            {
                preType = type;
                rs.Add(buff);
                buff = "";
            }
            buff += ch;
        }
        rs.Add(buff);
        rs.RemoveAt(0);

        return rs;
    }
}

 

3-3 테스트 코드

using System.IO;
namespace dotweb;

public partial class TemplateEngine
{
    private DirectoryInfo dir;
    public void MakeSourceAll(string folderPath)
    {
        dir = new DirectoryInfo(folderPath);
        foreach (FileInfo info in dir.EnumerateFiles("*.cshtml", SearchOption.AllDirectories))
        {
            MakeSource(info);
        }
    }
    public string MakeSource(string path) => MakeSource(new FileInfo(path));
    public string MakeSource(FileInfo info)
    {
       
        dir ??= new DirectoryInfo(info.Directory.FullName);
        Console.WriteLine();
        Console.WriteLine(info.FullName);
        var txt = File.ReadAllText(info.FullName);
        List<string> tokens = Tokenize(txt);

        tokens.ForEach(t => Console.Write(t+", "));

        return txt;
    }

}

 

3-4 결과 확인.

 
<, html, >,
, <, head, >,
, <, title, >, @, ViewData, [, ", Title, ", ], <, /, title, >,
, <, link, , rel, =, ", stylesheet, ", , href, =, ", ., /, main, ., css, ", /, >,
, <, /, head, >,
, <, body, >,
, <, ul, , class, =, ", Test, ", >,
, @, foreach, (, var, , str, , in, , Model, ), {,
, <, li, >, @, str, <, /, li, >,
, },
, <, /, ul, >,
, <, /, body, >,
, <, /, html, >,
<, html, >,
, <, head, >,
, <, title, >, ??, ?, <, /, title, >,
, <, /, head, >,
, <, body, >,
, TEST,
, <, /, body, >,
'[23656] dotweb.dll' 프로그램이 0 코드로 종료되었습니다(0x0).

 

ETC

코딩으로 구현하기 어려운 부분은 원칙 2에 있다. 아마도 다음 포스팅이 될 듯하다.