본문 바로가기

C#/근웹 연대기

c#으로 근본 없는 웹서버 개발기 25 : cshtml 파싱 - 파스노드(코드)

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

 

 

이번 파트는 파싱에서 가장 난해한 부분이다.

단지 코드만을 봐서는 이해하기 어려울 수 있기 때문이다.

그래서 이것을 어떻게 해야 이해가 쉬울지 고민을 하였고

결국 시간이 걸리더라도 가시화가되게 해보자고 결론을 내렸다.

 

아래는 노드들간의 콜 스택을 가시화한 형태다.

<!--Hello World-->
<head>
    <title>@ViewData["title"]</title>
</head>
<body>
@{
    string p="Layout Page";
    <p> @p </p>
    <div> @RenderBody() </div>
    <script>
       console.log( 123 );
    </script>
}
</body>
Need Javascript Enabled.

모든 노드들을 자바스크립트에 다 담기에는 분량이 많기에 중요도가 높은것만을 간추렸다.

몇줄 짜리 코드가 별것아닌 로직이... 사람의 눈에 살갑게 다가서기 위해서 손이 많이 들어갔다.

 

 

다음 내용은 본문으로, 노드들에 대한 코드를 파일이름으로 분류 하였다. (클릭을 하면 나온다)

 

1. CS_Node.cs
internal class CS_inNode : ParseNode
{
    public CS_inNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.CSHARP;

    protected override void Load() => BlockSection(true);
}
internal class CS_FunctionNode : ParseNode
{
    public CS_FunctionNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.FUNCTION;

    protected override void Load() => BlockSection(true);
}
internal class LayoutNode : ParseNode
{
    public LayoutNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.LAYOUT;

    protected override void Load() => ArgsSection(true);
}
internal class CS_UsingNode : ParseNode
{
    public CS_UsingNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.USING;

    protected override void Load()
    {
        bool isStatic = false;
        while (Next() != null)
        {

            if (tk == "static")
            {
                if (isStatic) Err("CRAZY SYNTAX!");
                isStatic = true;
                continue;
            }

            if (GetType(tk) != TTYPE.LITERAL) Err();
            if (OffsetS(1) == "=")
            {
                Next();
                continue;
            }
            if (GetType(Offset(+1)) == TTYPE.WHITE_SPACE) break;

            Next();
            if (tk == ";") break;
            if (tk != ".") Err();
        }
        AddStr(pi, i + 1);
    }
}
internal class CS_StrNode : ParseNode
{
    public CS_StrNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.STRING;

    protected override void Load()
    {
        bool isInterpol = Offset(-1).LastOrDefault() == '$';
        while (Next() != null)
        {
            if (tk == "{")
            {
                if (Offset(1) == "{")
                {
                    Next();
                    continue;
                }
                if (isInterpol)
                {
                    BlockSection(false, false);
                    continue;
                }
            }

            if (tk == "\"" && Offset(-1) != "\\") break;
        }
        AddStr();
    }
}

 

2. CS_BlockNode.cs
internal class CS_BlockNode : ParseNode
{
    private string name;

    public override NTYPE NType => NTYPE.CSHARP;

    public CS_BlockNode(ParseNode parent) : base(parent) { }

    protected override void Load()
    {
        name = tk;
        switch (name)
        {
            case "do":
                DoBlock(); break;
            case "else":
                NextBolockOrColon(); break;
            case "try":
            case "finally":
                BlockSection(false, false); break;
            default:
                NormalBlock(); break;
        }
        AddStr(pi, i + 1);
    }

    private void DoBlock()
    {
        BlockSection(false, false);
        ArgsSection(false, false);
        NextCommentSkip();
        if (tk != ";") Err();
    }

    void NormalBlock()
    {
        ArgsSection(false, false);
        NextBolockOrColon();
    }

    void NextBolockOrColon()
    {
        NextCommentSkip();

        if (tk == "{")
        {
            BlockSection(false, false);
            AddStr(pi, i + 1);
            return;
        }

        if (name is "switch" or "look" or "catch") Err(name + " needs block statement.");

        if (tk == ";" || Razor())
        {
            AddStr(pi, i + 1);
            return;
        }

        if (GetType(tk) != TTYPE.LITERAL) Err();

        while (Next() != null)
        {
            if (tk == ";") break;
            if (CsComment() || CsString() || Razor()) continue;
            if (tk == "{") BlockSection();
        }
    }
}

 

3. CSS_Node.cs
internal class CSS_node : ParseNode
{
    public CSS_node(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.CLIENT;

    protected override void Load()
    {
        bool comment = false;
        while (Next != null)
        {
            if (Razor()) continue;
            if (comment == false)
            {
                if (tk == "/" && Offset(1).First() is '*')
                {
                    comment = true;
                    continue;
                }
                if (tk is "\"" or "'")
                {
                    AddStr(pi, i + 1);
                    Invoke(typeof(TagStrNode));
                    continue;
                }
                if (tk == "<" && Offset(1) == "/") break;
            }
            else if (Offset(-1, 0) == "*/") comment = false;
        }
        AddStr();
    }
}

 

4. Express_Node.cs
namespace dotweb;
internal class Express_Node : ParseNode //정신적인 상속..
{
    protected Express_Node(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.EXPRESS;

    protected override void Load() => throw new NotImplementedException();
}

internal class InlineNode : Express_Node
{
    public InlineNode(ParseNode parent) : base(parent) { }
    protected override void Load() => ArgsSection(true);
}
internal class ImplicitNode : Express_Node
{
    public ImplicitNode(ParseNode parent) : base(parent) { }

    protected override void Load()
    {
        while (NextRaw() != null)
        {
            if (GetType(tk) == TTYPE.WHITE_SPACE) break;
            if ((tk is "?." or "." or "[" or "(") == false) break;
            if (tk == "(")
            {
                ArgsSection(false, false);
                continue;
            }
            if (tk == "[")
            {
                IndexSection(false, false);
                continue;
            }
            if (GetType(NextRaw()) != TTYPE.LITERAL) Err();
        }
        AddStr(pi, i--);
    }
}


 

5. Html_node.cs
internal class InTagNode : ParseNode
{
    public InTagNode(List<String> tokens) : base(tokens) { }
    public InTagNode(ParseNode parent) : base(parent) { }

 

    public override NTYPE NType => NTYPE.CLIENT;

 

    protected override void Load()
    {
        bool isRoot = (i == -1);
        bool comment = false;

 

        while (Next() != null)
        {
            if (Razor()) continue;

 

            if (comment == false)
            {
                if (tk == "-" && Offset(-3, 0) == "<!--")
                {
                    comment = true;
                    continue;
                }

 

                string ns = Offset(1);
                if (tk == "<" && (GetType(ns) == TTYPE.LITERAL || ns == "@"))
                {
                    AddStr();
                    Invoke(typeof(TagNode));
                    continue;
                }

 

                if (isRoot == false)
                {
                    if (parent is SectionNode)
                    {
                        if (tk == "}") break;
                    }
                    else
                    {
                        if (tk == "<" && ns == "/") break;
                    }
                }
            }
            else
            {
                if (tk == ">" && Offset(-2, 0) == "-->")
                {
                    comment = false;
                    continue;
                }
            }
        }
        AddStr();
    }
}
internal class SectionNode : ParseNode
{
    public string Name { get; private set; }

 

    public override NTYPE NType => NTYPE.SECTION;

 

    public SectionNode(ParseNode parent) : base(parent) { }

 

    protected override void Load()
    {
        this.Name = tk;
        Next();  // {
        pi = i + 1;
        Invoke(typeof(InTagNode));
        pi++; // }
    }
}

 

6. JS_Node.cs
internal abstract class JS_Node : ParseNode
{
    protected string comment = null; //  // or /*
    protected JS_Node(ParseNode parent) : base(parent) { }
    protected bool JsString()
    {
        if ("\"'`".Contains(tk) && comment == null)
        {
            AddStr(pi, i + 1);
            Invoke(typeof(JS_StrNode));
            return true; ;
        }
        return false;
    }
    protected bool JsComment()
    {
        if (comment == null)
        {
            if (tk == "/" && Offset(1).First() is '*' or '/')
            {
                comment = tk + Offset(1).First();
                return true;
            }
        }
        else
        {
            if (comment == "//" && Offset(1).Contains('\n'))
            {
                comment = null;
                return true;
            }
            if (comment == "/*" && Offset(-1, 0) == "*/")
            {
                comment = null;
                return true;
            }
        }
        return false;
    }

    protected bool JsCommon() => (Razor() || JsComment() || JsString());
}
internal class JS_InNode : JS_Node
{
    public JS_InNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.CLIENT;

    protected override void Load()
    {

        while (Next() != null)
        {
            if (JsCommon()) continue;
            if (comment == null)
            {
                if (parent is JS_StrNode)
                {
                    if (tk == "}") break;
                }
                else
                {
                    if (tk == "<" && Offset(1) == "/") break;
                }
            }
        }
        AddStr();
    }
}

internal class JS_StrNode : JS_Node
{
    public JS_StrNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.STRING;

    protected override void Load()
    {
        var q = Offset(0);
        while (Next() != null)
        {
            if (Razor()) continue;
            if (q == "`" && tk == "{" && Offset(-1).Last() == '$')
            {
                AddStr(pi, i + 1);
                Invoke(typeof(JS_InNode));
                continue;
            }

            if (tk == q && Offset(-1) != "\\") break;
        }
        AddStr();
    }
}

 

7. TagNode.cs
internal class TagNode : ParseNode
{
    public TagNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.CLIENT;

    protected override void Load()
    {
        var tagName = Offset(1)?.ToLower();

        Loop();

        if (Offset(-1).Last() == '/') return;

        Invoke(tagName switch
        {
            "script" => typeof(JS_InNode),
            "style" => typeof(CSS_node),
            _ => typeof(InTagNode)
        });

        Loop();
    }

    private void Loop()
    {
        while (Next() != null)
        {
            if (Razor()) continue;

            if (tk is "\"" or "'")
            {
                AddStr(pi, i + 1);
                Invoke(typeof(TagStrNode));
                continue;
            }

            if (tk == ">") break;
        }
        AddStr(pi, i + 1);
    }
}

internal class TagStrNode : ParseNode
{
    public TagStrNode(ParseNode parent) : base(parent) { }

    public override NTYPE NType => NTYPE.STRING;

    protected override void Load()
    {
        var q = Offset(0);
        while (Next() != null)
        {
            if (Razor()) continue;
            if (tk == q) break;
        }

        AddStr();
    }
}

 

 

전체 코드 사이즈가 상당하므로 티스토리 에디터모드가 버벅인다.. 더이상 내용을 추가하는 것은 힘들거 같다.

다음 포스팅은 노드로부터 코드를 조립하는 과정이 될듯하다