asmpro
739
2019-04-03 19:55:34
3
887

누구나 만들 수 있는 컴파일러 - 4


강좌 소스 코드 git : https://github.com/hybridcompiler/HybridCompiler
블로그 : https://hybridcompiler.blogspot.com/2019/04/4.html

네 번째 프로젝트 Hybrid Interpreter 4 (HI4)

]]test
---------- Test.TestLexer 시작
========== Test.TestLexer 완료: 성공 13, 실패 0
---------- Test.TestParserLine 시작
========== Test.TestParserLine 완료: 성공 12, 실패 0
---------- Test.TestParser 시작
========== Test.TestParser 완료: 성공 2, 실패 0
]g = 5
행 1
g = 5
^
NameError: 이름이 정의되지 않았습니다.
]dim g
0
]dim h = 3 * 6 + 7
25
]dim f = 2 + 6 / 3
4
]g = h * f
100
]dim g
행 1
dim g
    ^
NameError: 이름이 이미 정의되어 있습니다.
]exit
계속하려면 아무 키나 누르십시오 . . .
위는 HI4의 실행 결과입니다.

HI4 프로젝트에는 변수 선언 및 사용 기능과 한 줄이 아닌 여러 줄을 인터프리트 할 수 있는 기능이 추가되었습니다.

기존의 HybridCompiler 솔루션에 HI4 프로젝트를 생성합니다.

솔루션 탐색기에서 솔루션을 오른쪽 클릭하여 속성을 선택한 후 시작 프로젝트로 HI4를 선택합니다.


EBNF 문법

block = "{", { statement }, "}"
statement = expression | keyword_statement | assignment_statement ;
keyword_statement = variable_declaration ;
variable_declaration = "dim", ( identifier | assignment_statement ) ;
assignment_statement = identifier, "=", expression ;

expression = term, { "+" | "-", term } ;
term = factor, { "*" | "/", factor } ;
factor =  ( [ "+" | "-" ], factor ) | integer | identifier | ( "(", expression, ")" ) ;

identifier = ( alphabet | "_" ), { alphabet | "_" | integer } ;
integer = digit, { digit } ;
alphabet = "A" | "B" | ...... | "z" ;
digit = "0" | "1" | ...... | "9" ;
많은 terminal 정의들이 추가되었습니다.

모든 terminal 정의들은 Lexer와 Parser에서 해당 정의를 구현한 부분에 주석을 달았습니다.

앞으로도 추가될 모든 terminal 정의들은 코드에 1대 1 매칭을 할 예정이기 때문에 EBNF 문법의 코드 구현에 관련해서는 특별한 경우를 제외하고는 설명을 생략하겠습니다.


Token.cs

using HILibrary;
using System.Collections.Generic;
using System.Runtime.InteropServices;

namespace HI4
{
    // Token Type
    internal enum TT : byte
    {
        Invalid,

        LF = (byte)'\n',
        CR = (byte)'\r',
        Space = (byte)' ',

        Plus = (byte)'+',
        Minus = (byte)'-',
        Mul = (byte)'*',
        Div = (byte)'/',

        LeftParens = (byte)'(',
        RightParens = (byte)')',

        Assign = (byte)'=',

        BeginBlock = (byte)'{',
        EndBlock = (byte)'}',

        EOF = 127,

        Integer,
        Identifier,
        Keyword,
    }

    //
    [StructLayout(LayoutKind.Explicit, Size = 16)]
    internal class Token
    {
        // 공통 필드

        [FieldOffset(0)] public readonly TT Type;
        [FieldOffset(1)] public readonly byte Column;
        [FieldOffset(4)] public readonly int Line;

        [FieldOffset(8)] public readonly long IntegerValue;

        [FieldOffset(8)] public readonly int IdentifierStringId;

        [FieldOffset(8)] public readonly KT KeywordType;

        public Token(TT type, byte column, int line)
        {
            Type = type;
            Column = column;
            Line = line;
        }

        public Token(long value, byte column, int line)
        {
            Type = TT.Integer;
            IntegerValue = value;
            Column = column;
            Line = line;
        }

        public Token(string identifier, byte column, int line)
        {
            Column = column;
            Line = line;

            if (Keyword.Find(identifier, out KT keywordType))
            {
                Type = TT.Keyword;
                KeywordType = keywordType;
            }
            else
            {
                Type = TT.Identifier;
                IdentifierStringId = strings.Count;
                strings.Add(identifier);
            }
        }

        public string Text
        {
            get
            {
                HIDebug.AssertEqual(Type, TT.Identifier);
                return strings[IdentifierStringId];
            }
        }

        private static List strings = new List();
    }
}
Token 클래스의 내용이 크게 변경되었습니다.

이전까지 Token 클래스는 Token 타입, Token의 열, Integer 타입 Token일 경우 값 이렇게 3개의 속성을 갖고 있었습니다.

HI4에서는 여러 줄의 코드를 받아들일 수 있게 되었기에 Token의 행의 속성이 추가되었고 변수를 사용하기 위한 식별자명 속성과 키워드를 사용하기 위한 키워드 타입 속성이 추가되었습니다.

Type, Column, Line은 모든 Token의 공통 속성이지만 IntegerValue, IdentifierStringId, KeywordType은 각자 다른 타입의 Token에서 사용되는 속성입니다.

일반 속성으로 선언이 되면 메모리가 낭비가 됩니다.

그래서 C의 union처럼 여러 속성들이 같은 위치의 메모리를 공유하기 위해 속성을 필드로 변경하고 'StructLayout'과 'FieldOffset'을 사용합니다.


Keyword.cs

using System;
using System.Collections.Generic;
using System.Linq;

namespace HI4
{
    // Keyword Type
    internal enum KT
    {
        dim,
    }

    internal static class Keyword
    {
        static Keyword()
        {
            Enum.GetValues(typeof(KT)).Cast().ToList().
                ForEach(type => keywords[type.ToString()] = type);
        }

        public static bool Find(string identifier, out KT keywordType) =>
            keywords.TryGetValue(identifier, out keywordType);

        private static Dictionary keywords = new Dictionary();
    }
}
Keyword 클래스는 문자열을 키워드 타입으로 변환하는데 사용합니다.

현재는 변수를 선언하는데 사용하는 'dim'키워드 하나만 존재하기 때문에 문자열을 키워드 타입으로 직접 변환을 하나 다양한 키워드가 추가되게 되면 키워드 타입이 아닌 키워드의 정보가 들어있는 클래스를 반환하게 될 겁니다.


Symbol.cs

using HILibrary;
using System.Collections.Generic;

namespace HI4
{
    internal class Symbol
    {
        public long IntegerValue { get; set; }
        public Token Token { get; private set; }

        public Symbol(Token token)
        {
            HIDebug.AssertEqual(token.Type, TT.Identifier);
            if (symbols.TryGetValue(token.Text, out _))
            {
                throw new HIException(ET.NameError, Id.NameAlreadyDefined, token);
            }
            Token = token;
            symbols[token.Text] = this;
        }

        public static bool Find(Token token, out Symbol symbol) =>
            symbols.TryGetValue(token.Text, out symbol);

        private static Dictionary symbols = new Dictionary();
    }
}
Symbol 클래스는 변수를 선언하고 Token을 인수로 변수 클래스를 가져오는 역할을 합니다.

현재는 한가지 타입의 변수만 존재하기 때문에 정보들이 속성으로 선언이 되어 있으나 객체, 함수, 다양한 타입의 변수 등 많은 속성이 추가되면 Token 클래스와 마찬가지로 'FieldOffset'을 사용하여 메모리 사용을 줄일 겁니다.

그 외에도 자잘하게 수정된 곳이 많으나 코드를 보면 대부분 쉽게 이해할 수 있는 내용이라 생략합니다.

이것으로 4번째 강좌를 마칩니다.

2
2
  • 댓글 3

  • asmpro
    739
    2019-04-03 20:08:32 작성 2019-04-03 20:10:53 수정됨

    HI4 프로젝트는 주석을 최대한 제거하고 코드를 보고 이해할 수 있게 다듬었기에 크게 변한 클래스나 새롭게 추가된 클래스만 설명하였습니다.

    HI4 부터는 git에 Release된 실행 파일이 있으니 소스 코드 컴파일 없이 실행 파일을 테스트해 봐도 됩니다.


    컴파일러 개발이 대부분의 프로그래머에게는 관심 분야가 아니다 보니 반응이 전혀 없어 이렇게 계속 글을 올리고 git에 올리는 게 의미가 있을까 생각이 들지만, 개인적인 지식의 정리만으로도 저 자신에게 도움이 된다고 생각이 들기에 일단 강좌를 계속하겠습니다.

    0
  • Amunt
    16
    2019-04-04 08:52:30
    올려주시는 글을 잘 보구있어요. 컴파일러에대해서 관심있었는데 마땅한 책들이 별로 없더라구요. 책들이 너무 오래되서 요즘 트렌드랑 안맞는다던가 아니면 너무 이론적인 부분이 많아서 처음인 사람에겐 어렵다던가 이런 평들이 자주 보이구요. 그래서 이런 강좌가 딱 필요했는데 올려주셔서 감사합니다.
    0
  • asmpro
    739
    2019-04-04 12:37:56

    도움이 된다니 다행입니다.

    보시다가 궁금한 점 있으시면 댓글 달아주시면 성심껏 답변해 드리겠습니다.

    0
  • 로그인을 하시면 댓글을 등록할 수 있습니다.