برنامه نویسی

اختراع چرخ: ایجاد کامپایلر در CSharp – قسمت 1Je

چرا باید کامپایلر ایجاد کنیم؟

من اعتقاد دارم با عصبانیت در دو چیز:

هر Corinthian و هر Corinthian باید یک بازی را از آن تماشا کند Colossal de itaquera هیچ سانتیاگو برنالست (با نام مستعار Neochemistry Arena) حداقل یک بار در زندگی …

و هر توسعه دهنده باید کامپایلر خود را ایجاد کند.

و ممکن است تعجب کنید: اما چرا؟

جواب ساده است: زیرا دشوار است.

بله ، سخت است برای من پس … زندگی می کند… این چیزی بسیار دشوار است …

و قبل از اینکه از ایده ایجاد کامپایلر خود خودداری کنید ، یا فقط به آن اهمیتی ندهید ، می خواهم این ویدیوی شگفت انگیز از معلم شگفت انگیز Clovis de Barros Filho را ارائه دهم …

https://www.youtube.com/watch؟v=trpby_lxjfe

این ویدیو به اینترنت سوار شد. من حتی معتقدم که شما قبلاً تماشا کرده اید. اما من او را بسیار الهام بخش می دانم ، به حدی که دو قسمت فوق العاده را که می خواهم به اشتراک بگذارم/تقویت کردم ، جدا کردم:

“شما باید الاغ خود را در صندلی خود بنشینید و ظرفیت فکر خود را بهبود بخشید …”

اشمیه

“این غرور است که شما را به سمت پیشرفت فکری سوق می دهد. مطالعه ، بهبود توانایی فکری و تفکر با صلاحیت به همان اندازه داشتن عضلات ، دویدن ، عبور از شنا سخت است: به تعهد نیاز دارد ، نیاز به فداکاری دارد ، نیاز به الاغ در صندلی دارد.”

و آن هرچه بیشتر مطالعه کنید ، بیشتر خود را وقف می کنید ، پیشرفت بیشتری خواهید داشت. باور کن من نیستم مربی

بنابراین ، امروز من یک سری از پست این یک تاریخ برای شروع است اما هیچ تاریخی برای پایان وجود ندارد: ایجاد کامپایلر در کله پا!

اما نگران نباشید و عجله نکنید. پست بوها پست ما در حال تحول ، توضیح و به ویژه یادگیری هستیم.

در واقع ، لحظه ای که این را خوانده اید پست، بدانید که دو مورد دیگر در انتظار انتشار هستند. من می خواستم ایجاد کنم بافنده دقیقاً سرعت را از دست ندهید و امیدوارم هر 15 روز یکبار محتوای جدید منتشر کنید.


انگیزه و منابع مطالعه

وقتی من برای نوشتن پست ژنراتورهای منبع .NET: تولید کد در زمان نوشتن کد! من وقت زیادی را برای خواندن کد منبع Roslyn اختصاص دادم (کامپایلر کله پا نوشته شده در کله پا) و این به من کمک کرد تا چند چیز را درک کنم. اما بسیار مفید بود.

اما در حال تماشای بزرگ Immo Landwerth (مدیر محصول در تیم .NET در مایکروسافت) بود که من به تازگی بهترین نتیجه را گرفتم! آن مرد به سادگی یک کامپایلر را در دست خود ایجاد کرد کله پابشر از ابتدا تا انتها: https://github.com/terrajobst/minsk!

و بهترین ، زنده: https://www.youtube.com/live/wghikduqbp0؟si=lwlsg28ejpxhe96z. 28 فیلم خارق العاده وجود دارد. من مطمئن شدم که یک به یک تماشا می کنم. دو بار برای اینکه باور کنیم ممکن است … این محتوا یک منبع فوق العاده از مطالعات است.

من فکر می کنم شما کمی می دانید که یک کامپایلر چیست. مهم است که این را در ذهن داشته باشید. مقالات زیادی در اینترنت وجود دارد و ما هنوز چت GPT داریم که می توانیم به دنبال کمک باشیم.

به هر حال ، می توانید به https://github.com/terrajobst/minsk/tree/master/docs دسترسی داشته باشید و علاوه بر دسترسی به درخواست کشش هر اجرای ، لیست همه قسمت ها را مشاهده کنید. کار خوب ، کار سخت از immo…

این فیلم ها به من انگیزه دادند تا کامپایلر خودم را ایجاد کنم.

و برای این کار لازم بود که مطالعه شود!

و این زمانی است که من با دکتر برایان رابرت کالاهان آشنا شدم ، جایی که او یک سریال فوق العاده به نام Let’s Wors A Compiler ساخت. این یک کلاس است. نه ، 8! 8 وجود دارد پست بسیار دقیق و بسیار خوب نوشته شده است. آن مرد حسی است … Sen-Sa-Ci-O-Nal. یک روز به آنجا می روم! یک روز می رسم …

منبع دیگر مطالعات فوق العاده این است: صنایع دستی تعامل. کتابی شگفت انگیز که توسط رابرت نیستروم نوشته شده است. یک نسخه وب رایگان وجود دارد اما می توانید نسخه دیجیتال را در آمازون خریداری کنید.

من این کتاب را از طریق پیتر عیسی بزرگ کشف کردم. در حین گپ در گروه زپ از شبهای میمون. من اشاره کردم که من در حال تهیه کامپایلر هستم تفریح و فهمیدم که عیسی عزیز ما نیز هست. من آن را شگفت انگیز دیدم ، پس از همه در پدرو عیسی ما اعتماد داریم!

من خواندن این کتاب را شروع کردم که قبلاً پست سوم را عملاً آماده کردم ، با کد اجرا شده. اما از آنچه خوانده ام ، ظاهراً من در مسیر درست هستم. او استفاده می کند جاوا به عنوان یک زبان اما من معتقدم که از آن لذت می برم.

باشد که پدرت به شما برکت دهد پیتر عیسی.


میناکاری؟

خانمها و آقایان ، من به شما تقدیم می کنم میناکاری: برنامه نویسی قابل درک جهانیبشر

من می دانم که این نام کمترین معنی را ندارد ، اما زندگی اینگونه است و من واقعاً دستمال را دوست دارم. من دو دارم ، از جمله. علاوه بر این نام ساده است: میناکاریبشر

pug build
pug run
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

شما را می بینیم … آنقدر که به نظر می رسید بد نیست … یا نه …

با توجه به و توضیح نام ، وقت آن است که پیاده روی خود را شروع کنیم.

با این حال ، اول از همه ، من می خواهم به این نکته اشاره کنم که این یک پیاده روی ساده نخواهد بود ، این یک سفر خواهد بود.

ما یک کامپایلر کوچک برای گناه ، کد بر اساس کد ، همه به روشی آموزشی و آرام خواهیم ساخت. و در طی این ادیسه نتیجه ای که با هر یک به دست می آوریم پست این ممکن است به عنوان کامپایلر یا حتی یک مترجم در ذات برداشت محاسباتی طبقه بندی نشود. ایده در اینجا این است که در مورد زایمان عملی و عینی باشید. هر یک پست عملکرد جدیدی را نشان می دهد. قدم به قدم. بدون عجله ، در آرامش …

و از این گذشته ، باید بین نوشتن کد منبع ، چنین باشد ، تجزیه کننده و تولید فایل باینری برنامه ، فاصله تقریباً بین قشر وجود دارد. به همین دلیل هیچ فایده ای ندارد که عجله داشته باشید. و شاید در میان این فرایند باید متوقف و توضیح بیشتری (و یادگیری) در مورد Ilبشر من انتخاب می کنم Il به سادگی با نگه داشتن من در داخل اکوسیستم داتنت و برای داشتن کتابخانه هایی که در تولید کد کمک می کنند Il منزل کله پا (هر دو System.Reflection.Emit خود مایکروسافت ، در مورد سیگیل کوین مونتروز افسانه ای).

به هر حال ، من نمی دانم که آیا این مشخص است ، اما علاوه بر کامپایلر ، ما یک زبان برنامه نویسی ایجاد خواهیم کرد. درست است!

ایده من این نیست که چیزی دور از ذهن ، پیچیده ، بلکه زبانی ایجاد کنید که از دستورات کافی پشتیبانی کند تا ما ایجاد کنیم سلام جهان و برخی از عملیات ریاضی را انجام دهید.

من نمی خواهم چیزی شبیه به آن زبانی ایجاد کنم که نباید نام آن را تلفظ کنیم ، که در اجرای آن null >= [] از true در نتیجه …

نکته مهم برای برجسته کردن این است که اولین زایمان (این پست) فقط به تفسیر عبارات ریاضی ساده با استفاده و احترام به ترتیب پرانتز ، ضرب ، تقسیم ، جمع و تفریق می پردازد. آن قوانین کوچک که در ابتدای کار آموختیم …

بنابراین ، اولین تحویل ما می تواند موضوعاتی مانند:

> -(1+(2*3)/4)
-2,5
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

سرگردان و همیشه …


برخی از مفاهیم مهم

مفاهیم مهم بسیاری وجود دارد که ما در مورد کامپایلر صحبت می کنیم و دو مورد از آنها هستند لکسر اشمیه تجزیه کنندهبشر

ای لکسر، یا آنالایزر واژگان ، مسئولیت خواندن تمام کد و عصاره منبع را بر عهده دارد توکنبشر
برای این ، لازم است که لکسر تمام “کلمات رزرو شده” را بدانید و آنها را طبقه بندی کنید. نتیجه این طبقه بندی یک است نشانه، ساختار داده ای که نوع ، مقدار و در کدام موقعیت را آگاه می کند رشته او ملاقات می کند.

در حال حاضر تجزیه کننده، یا آنالایزر نحوی ، لیست را دریافت می کند توکن و تجزیه و تحلیل می کند که آیا ساختار پی در پی گرامری استخراج شده از کد منبع از قوانین زبان برنامه نویسی پیروی می کند.
انجام این اعتبار سنجی برای تبدیل لیست از توکن در یک درخت نحوی (AST – درخت نحوی انتزاعی) یا نوع دیگری از بازنمایی ساختاری.

در مورد ما ، ما تولید نمی کنیم عیدبشر حداقل در آن لحظه و هنگامی که ما تولید می کنیم ، ما وقت خود را برای توضیح اینکه چیست ، چگونه کار می کنیم و حتی نمونه هایی از درخت تولید شده توسط خود را به ارمغان می آوریم روزلین از یک کد کله پابشر

همچنین ، در درون ما تجزیه کننده ما اجرای عبارات را خواهیم داشت (ارزیابی کردن) چه چیزی آن را تقریباً یک جایگزین می کند. این بسیار مفید خواهد بود ، از این گذشته ، ما می توانیم با کنسول نوشتن عبارات و اعتبار سنجی نتیجه.

یک چیز جالب و مهم

اگر بین یک زبان ، مانند پرتغالی و یک زبان برنامه نویسی موازی کنیم ، تجزیه و تحلیل واژگانی مسئول شناسایی کلمات در یک متن یا کد منبع است. بیایید به “شعر” زیر در “پرتغالی” نگاه کنیم:

ojnwerun daef fds xça
uolasda سگ aksdmasd da
ASDAS BICYCLE
نان تست های olapasdm فرانسوی

همانطور که مشاهده می کنید ، تجزیه و تحلیل ما فقط می تواند چند کلمه (مدرسه ، سگ ، دوچرخه و نان تست فرانسوی) را مشخص کند. از نظر شناختی ، ما نمی توانیم چیزی از این محتوا را جذب و درک کنیم ، زیرا هیچ معنایی ندارد ، حتی داشتن کلمات شناخته شده.

این ممکن است وقتی که در مورد زبانها صحبت می کنیم واضح به نظر برسد!

با این حال ، و همچنین حس تفسیر متن ما ، لکسر این توانایی برای استخراج (تشخیص) ندارد توکن نقشه برداری نشده است
این توضیح می دهد که چرا کامپایلر جاوا کد منبع نوشته شده در کله پا به عنوان مثال ، دقیقاً مثل اینکه فقط به زبان پرتغالی صحبت می کنید ، هنگام شنیدن یک عبارت به زبان روسی قادر به درک چیزی نخواهید بود. اگرچه شاید حتی روسها زبان روسی را درک کنند … من نمی دانم … اما به هر حال … فکر می کنم شما می فهمید …

و هنگامی که ما در مورد تجزیه و تحلیل نحوی صحبت می کنیم ، اساساً ارزیابی معنای کلمات شناخته شده در یک متن یا کد منبع را داریم.

فایده ای ندارد که متن فقط کلمات را مشخص کرده باشد اگر مجموعه معنی ندارد. به عنوان مثال:

Pizza Jacaré Cetchup Avocado na
World 51 Palmeiras Romarinho
مدرسه آناناس گربه فانوس
سس مایونز لامباری شگفت انگیز در حال اجرا

تمام کلمات فوق به زبان ما شناخته شده است ، اما هنگام تجزیه و تحلیل هر جمله متوجه می شویم که معنایی وجود ندارد.

تجزیه و تحلیل نحوی این احساس را برای ما به ارمغان می آورد و در مورد کامپایلر ، خطا ایجاد می کند.

اکنون ، نکته بسیار مهم دیگر اختلاف نظر است:

انبه رنگ آستین دارد

چی فهمیدی؟ که میوه انبه رنگ آستین پیراهن را دارد ، یا اینکه آستین پیراهن دارای رنگ میوه است؟ یا ، شما اینقدر چه کاری انجام می دهید ، زیرا این دو رنگ یکسان دارند؟

هیچ کله پا ما می توانیم مانند این سناریوهایی قرار بگیریم ، اما کامپایلر می تواند به بهترین شکل حل شود ، از این گذشته ، زبان (زبان برنامه نویسی) بسیار سفت تر است.

public class async
{
    public async Task<async> Task()
    {
        await System.Threading.Tasks.Task.Delay(10);
        return new async();
    }
} 
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

کد معنی ندارد ، این هدف نیست. اما او کامپایل می کند. کامپایلر دقیقاً می داند که هر کلمه چه کاری انجام می دهد ، نه فقط برای معنای خود بلکه به ترتیب در جایی که ظاهر می شود.

آیا نبوغ نیست؟

این رابطه بین زبان برنامه نویسی و زبان بسیار نزدیک به گاهی اوقات حتی گیج کننده است.

کافی است … بیایید کد را ببینیم …


ایجاد آنالایزر واژگان

قهوه … یک قهوه بخورید …

عبارت زیر را در نظر بگیرید:

1+2-  3
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

هنگام استخراج از توکن، لیست زیر را دریافت می کنیم:

Token 1 - Type: Number, Position: 1, Value: 1
Token 2 - Type: Plus, Position: 2, Value: "+"
Token 3 - Type: Number, Position: 3, Value: "2"
Token 4 - Type: Minus, Position: 4, Value: "-"
Token 5 - Type: Space, Position: 5, Value: "  "
Token 6 - Type: Number, Position: 7, Value: "3"
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در این مورد ساده ، ما فقط چهار نوع مختلف داریم توکن (شمارهبا به علاوهبا منهای اشمیه فضا). اگر هر شخصیتی در خارج از این نوع قرار گرفته بود ، لکسر این خطا را نشان می دهد ، زیرا او او را نقشه برداری نکرده است.

ای لکسر این کد منبع را جارو می کند و ارزیابی می کند که آیا یک یا چند کاراکتر در زبان برنامه نویسی نقشه برداری شده اند. این نقشه برداری از طریق ارباب حاوی همه انواع پذیرفته شده نشانه!

جالب نیست؟ وقتی کد خود را می نویسیم حتی به آن فکر نمی کند …


زیبایی ، حالا واقعی است !!!

ایجاد یک پروژه داتنت نوع برنامه کنسول و نامی را که می خواهید بدهید. من دادم pug.compilerبشر

پوشه ای به نام ایجاد کنید تجزیه و تحلیلبشر تمام کلاسهایی که ما ایجاد خواهیم کرد در داخل این پوشه قرار دارند.

اولین کاری که ما می خواهیم انجام دهیم ایجاد یک ارباب این در خدمت ثبت انواع مختلف است توکن که ما تحمل خواهیم کرد:

پرونده CodeAnalysis/tokentype.cs

namespace Pug.Compiler.CodeAnalysis;

public enum TokenType
{
    EOF,

    Number,

    Plus,
    Minus,
    Multiply,
    Divide,

    OpenParenthesis,
    CloseParenthesis
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

در اصل ما چهار عمل اساسی داریم ، یک نوع عددی ، پرانتز و یک را باز می کند و می بندد EOF این نشانگر پایان پرونده است. این نشانه مهم است زیرا پایان زنجیره تفسیر را تعریف می کند توکن برای تجزیه کنندهبشر بعداً این را خواهیم دید. و بر خلاف مثال ذکر شده در بالا ، من فضاها را نقشه برداری نمی کنم. در طول توسعه فهمیدم که آنها بی فایده هستند و به سادگی آنها را نادیده می گیرند …

پرونده CodeAnalysis/token.cs

namespace Pug.Compiler.CodeAnalysis;

public record Token(TokenType Type, string Value, int Position)
{
    public static Token EOF(int position) 
        => new(TokenType.EOF, string.Empty, position);

    public static Token Number(int position, string value) 
        => new(TokenType.Number, value, position);

    public static Token Plus(int position) 
        => new(TokenType.Plus, "+", position);
    public static Token Minus(int position) 
        => new(TokenType.Minus, "-", position);
    public static Token Multiply(int position) 
        => new(TokenType.Multiply, "*", position);
    public static Token Divide(int position) 
        => new(TokenType.Divide, "https://dev.to/", position);

    public static Token OpenParenthesis(int position) 
        => new(TokenType.OpenParenthesis, "(", position);
    public static Token CloseParenthesis(int position) 
        => new(TokenType.CloseParenthesis, ")", position);
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بودن ثبت هیچ راز وجود ندارد. این یک ساختار داده است که اطلاعات را از نشانهبشر وقتی لکسر اعدام شده است ، یک لیست شیطان را استخراج می کند توکن این توسط پردازش خواهد شد تجزیه کنندهبشر برجسته برای شماره این مقدار عددی نقشه برداری را ذخیره می کند. این مقدار ممکن است نشانه ای از کم و بیش غیر از داشتن نقطه ای برای جداسازی اعشاری داشته باشد. به عنوان مثال -1.23با +4.56با 7.89 یا حتی 77بشر

علاوه بر این ، ما روشهای کمکی برای ایجاد داریم توکن، این برای تسهیل نوشتن کد است.

سپس یکی از ستون های اصلی کامپایلر ما: آنالایزر واژگان:

پرونده CodeAnalysis/Lexer.cs

using System.Text;

namespace Pug.Compiler.CodeAnalysis;

public class Lexer
{
    private readonly string _text;

    private int _currentPosition;
    private char _currentChar;

    private const char EOF = '\0';

    public Lexer(string text)
    {
        _text = text;
        _currentPosition = 0;
        _currentChar = _text.Length > 0 ? _text[_currentPosition] : EOF;
    }

    public List<Token> CreateTokens()
    {
        var tokens = new List<Token>();

        while (_currentChar != EOF)
        {
            if (char.IsWhiteSpace(_currentChar))
            {
                IgnoreWhitespace();
                continue;
            }

            if (_currentChar == '-' && char.IsDigit(Peek()))
            {
                tokens.Add(ExtractNumber());
                continue;
            }

            if (char.IsDigit(_currentChar))
            {
                tokens.Add(ExtractNumber());
                continue;
            }

            var token = _currentChar switch
            {
                '+' => Token.Plus(_currentPosition),
                '-' => Token.Minus(_currentPosition),
                '*' => Token.Multiply(_currentPosition),
                "https://dev.to/" => Token.Divide(_currentPosition),
                '(' => Token.OpenParenthesis(_currentPosition),
                ')' => Token.CloseParenthesis(_currentPosition),
                _ => throw new Exception($"Unexpected character: {_currentChar}")
            };
            tokens.Add(token);

            Next();
        }

        tokens.Add(new Token(TokenType.EOF, string.Empty, _currentPosition));
        return tokens;
    }

    private Token ExtractNumber()
    {
        var result = new StringBuilder();

        if (_currentChar == '-')
        {
            result.Append('-');
            Next();
        }

        var hasDot = false;
        while (char.IsDigit(_currentChar) || _currentChar == '.')
        {
            if (_currentChar == '.')
            {
                if (hasDot)
                    throw new Exception("Invalid number format: multiple dots");

                hasDot = true;
            }

            result.Append(_currentChar);
            Next();
        }

        var value = result.ToString();
        return Token.Number(_currentPosition, value);
    }

    private char Peek()
    {
        var peekPos = _currentPosition + 1;
        return peekPos < _text.Length ? _text[peekPos] : EOF;
    }

    private void Next()
    {
        _currentPosition++;
        _currentChar = _currentPosition < _text.Length ? _text[_currentPosition] : EOF;
    }

    private void IgnoreWhitespace()
    {
        while (char.IsWhiteSpace(_currentChar))
            Next();
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

بیایید خیلی با آرامش قدم به قدم نگاه کنیم.

این اجرای پیچیده نیست. ممکن است برای شما آنقدر طبیعی نباشد که می خوانید ، اما باور کنید ، استخراج توکن انجام این کار به طور منطقی ساکت است.

اولین نکته ای که باید درک کنیم این است که این تجزیه و تحلیل از اولین تا آخرین شخصیت کد منبع ما پیش می رود. یکی یکی

می توانیم ببینیم که کلاس ما متغیر را با پارامتر به نام دریافت می کند _text و سپس ما دو متغیر را شروع کردیم ، _currentPosition وت _currentCharبشر

خود اسامی قبلاً مسئولیت هریک از آنها را تعریف می کنند. هنگام سفر به متن خود ، ما یک مکان نما که همیشه به شخصیت فعلی اشاره خواهد کرد. و همانطور که خواندن ما همیشه به جلو انجام می شود ، یعنی پیشرفت شخصیت با شخصیت ، روشی به نام Next که باعث می شود این پیشرفت مراقبت کند که هنگام رسیدن به پایان متن از آخرین شخصیت فراتر نرود.

روش جالب دیگر این است Peekبشر در بعضی موارد لازم است شخصیت بعدی را از شخصیت فعلی مشاهده کنید تا دریابید که آیا جریان به پیشرفت خود ادامه می دهد یا خیر. این روش همچنین تضمین می کند که ما از آخرین شخصیت تجاوز نخواهیم کرد.

بنابراین ، تصور کنید که من عبارت زیر را دارم: -1234بشر
هنگام سفر به این متن ، من شروع به شناسایی یک نشانه به نام می کنم منهایبشر اما این نشانه این می تواند در دو سناریو مورد استفاده قرار گیرد: برای شناسایی اینکه یک عدد منفی است یا شناسایی اینکه یک حساب تفریق وجود دارد. به یاد داشته باشید آن پیشنهاد تشریفات که در بالا گفتم؟ بنابراین ، ما یک مثال در اینجا داریم …

بنابراین مهم است ، از این نشانه منهای بدانید که شخصیت بعدی یک عدد است یا خیر.

در این مرحله ما قبلاً دو روش اساسی برای عملکرد آنالایزر واژگان توضیح داده ایم.

اکنون می توانیم روش را وارد کنیم CreateTokensبشر همانطور که از نام آن پیداست ، فرآیند اسکن را بوت می کند (از شخصیت با فهرست تا انتها ، EOF) و لیستی از توکنبشر

این نوع روشی است که معمولاً زیبا نیست. پر از if و معمولاً یکی از بیشترین خطوط داخل کامپایلر است.

یک مثال مشابه در یک زمینه کمی متفاوت ، روش است ScanSyntaxToken انجام دادن روزلین یا روش ReadToken انجام دادن مگس ایجاد شده توسط Immo Landwerth.

استخراج ما از توکن هنوز هم در نزدیکی دو مورد ذکر شده در بالا متوسط ​​است …

درست است ، ما پیشرفت کردیم.

اولین اعتبار سنجی که من ایجاد می کنم تأیید است اگر شخصیت فعلی یک فضای خالی باشد. اگر چنین است ، من فرایندی را شروع می کنم که تمام شخصیت های زیر را که فضاها هستند نادیده می گیرد.

if (char.IsWhiteSpace(_currentChar))
{
    IgnoreWhitespace();
    continue;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این اعتبار سنجی از روش استفاده می کند IgnoreWhitespace اگر شخصیت بعدی مورد نظر یک فضای خالی باشد ، پیشرفت می کند.
این برای موارد “1 + 3 – 4 *2 است.

در واقع ، کنجکاو ، به برخی از زبانی وجود دارد توکن این نقشه این شخصیت را تصمیم گرفتم که حذف کنم تا بتوانم میزان اشیاء را کاهش دهم. نه به خاطر حافظه – پس از همه من در اینجا به این موضوع اهمیت نمی دهم – بلکه برای تسهیل اشکال زدنبشر

یک نکته جالب در این استخراج ها توکن آیا این اولین بررسی می کند که آیا شخصیت با بعضی ها مطابقت دارد نوع توکن و سپس به سمت استخراج این حرکت می کند نشانهبشر

از نظر فنی ، ابتدا در داخل بررسی می کنم while اصلی if (char.IsWhiteSpace(_currentChar))بشر به آنجا نگاه کنید تطبیق من از روش استفاده می کنم IgnoreWhitespace که به نوبه خود شخصیت های بعدی را از جریان اسکن می کنند: while (char.IsWhiteSpace(_currentChar))بشر

این الگویی است که تکرار می شود …

در مرحله بعد ما دو اعتبار دیگر برای اعداد داریم و اول استفاده از سیگنال منفی را در عدد و شماره دوم بررسی می کند.

if (_currentChar == '-' && char.IsDigit(Peek()))
{
    tokens.Add(ExtractNumber());
    continue;
}

if (char.IsDigit(_currentChar))
{
    tokens.Add(ExtractNumber());
    continue;
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

من می دانم که شما ممکن است وقتی این دو شرط را می بینید ، بینی خود را چروکیده اید. بله ، معقول است که آنها را متحد کنید ، اما من می خواستم به دو صورت جدا شوم ، دقیقاً برای پاک کردن تأیید در شخصیت بعدی اگر بیان با سیگنال شروع شود. اون یکی Peek در این زمان بسیار مهم است و برای من سهولت خواندن به خیلی کمک می کند اشکال زدن کد اشکال زدن فرآیند تجزیه و تحلیل نحوی و واژگانی چندان ساده نیست … گاهی اوقات به مغزی در ما …

روش ExtractNumber از همان الگوی اتخاذ شده در اعتبارسنجی فضاهای خالی استفاده می کند. اما در این حالت بررسی کنید که آیا در دنباله کاراکتر شماره ، منهای علامت و نقطه جداکننده اعشاری داریم. حتی اگر بیش از یک امتیاز باشد ، تأیید می شود ، اگر بله ، یک استثنا را راه اندازی می کند.

این بخش از فرآیند عبارات نوع را تأیید می کند -1.234بشر

سپس ما در سناریویی قرار می گیریم که اگر فضا نباشد و اگر شماره ای نباشد ، فقط می تواند هیچ یک از این شخصیت ها باشد: +با -با *با / ( اشمیه )بشر اگر هیچ یک از آنها … بوووومماستثناءبشر

در این شرایط عزیز ما تطبیق الگوی این یک دست روی چرخ است …

من آن را بررسی می کنم تا ایده راه اندازی استثنائات را در طول تجزیه و تحلیل واژگان و نحوی بررسی کنم …

سرانجام ، پس از سفر به تمام متن ، نشانه ای از آن را اضافه کردیم EOF(انتهای پرونده) و ما استخراج نشانه های خود را تمام می کنیم.

اگر به “علوم کامپیوتر” و/یا “اتومات” رفتید ، بدانید که اجرای فوق چیزی بیش از یک نیست AFD (اتومات محدود قطعی (AFD)).

من در مورد لحظه ای که در مورد مفاهیم تجزیه و تحلیل واژگان و نحوی توضیح می دادم ، در مورد او کمی صحبت کردم ، اما فهمیدم که این باعث می شود این پست حتی گسترده تر شود … اما بسیار ارزش مطالعه آن را دارد و من در اینجا برخی را برجسته می کنم لینک جالب:

توکن آفریده! حالا بیایید به تجزیه و تحلیل نحوی خود برویم.
در این حالت ، من نامگذاری کردم نحو برای کلاس که این تحلیل و اجرای عبارت را انجام می دهد. بشر

پرونده CodeAnalysis/parser.cs

namespace Pug.Compiler.CodeAnalysis;

public class SyntaxParser(List<Token> tokens)
{
    private Token CurrentToken => tokens[_currentPosition];

    private int _currentPosition;

    public double Parse()
    {
        var result = EvaluateExpressionWithPriority();

        while (CurrentToken.Type is TokenType.Plus or TokenType.Minus)
        {
            if (CurrentToken.Type == TokenType.Plus)
            {
                CheckToken(TokenType.Plus);
                result += EvaluateExpressionWithPriority();
            }
            else if (CurrentToken.Type == TokenType.Minus)
            {
                CheckToken(TokenType.Minus);
                result -= EvaluateExpressionWithPriority();
            }
        }

        return result;
    }

    private double EvaluateExpressionWithPriority()
    {
        var result = EvaluateToken();

        while (CurrentToken.Type is TokenType.Multiply or TokenType.Divide)
        {
            if (CurrentToken.Type == TokenType.Multiply)
            {
                CheckToken(TokenType.Multiply);
                result *= EvaluateToken();
            }
            else if (CurrentToken.Type == TokenType.Divide)
            {
                CheckToken(TokenType.Divide);
                result /= EvaluateToken();
            }
        }

        return result;
    }

    private double EvaluateToken()
    {
        var token = CurrentToken;

        if (token.Type == TokenType.Plus)
        {
            CheckToken(TokenType.Plus);
            return EvaluateToken();
        }

        if (token.Type == TokenType.Minus)
        {
            CheckToken(TokenType.Minus);
            return -EvaluateToken();
        }

        if (token.Type == TokenType.Number)
        {
            CheckToken(TokenType.Number);
            return double.Parse(token.Value, System.Globalization.CultureInfo.InvariantCulture);
        }

        if (token.Type == TokenType.OpenParenthesis)
        {
            CheckToken(TokenType.OpenParenthesis);
            var result = Parse();
            CheckToken(TokenType.CloseParenthesis);
            return result;
        }

        throw new Exception($"Unexpected token in Evaluate Token: {token.Type}");
    }

    private void CheckToken(TokenType type)
    {
        if (CurrentToken.Type == type)
            _currentPosition++;
        else
            throw new Exception($"Unexpected token: {CurrentToken.Type}, expected: {type}");
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

این دوستان و دوستانم هستند. اینجاست که مهره دم را پیچانده است. خون سوئی …

همه چیز با روش شروع می شود تجزیه کردن جایی که ما یک اسکن از همه داریم توکن در سازنده کلاس دریافت شده است.

درست همانطور که ما داریم _currentChar در کلاس لکسر، ما در اینجا یک ملک داریم کنونیبشر مفهوم در اینجا منطقی شبیه به پیشرفت بین نشانه ها است. و این مسیر خطی است ، نه یک درخت (AST – درخت نحوی انتزاعی).

ایده در اینجا ارزیابی مستقیم است (تفسیر یا ارزیابی کردن، همانطور که ترجیح می دهید) در طول تجزیه و تحلیل نحوی ، یعنی ، آن را ایجاد نمی کند عید، اما در حال حاضر مقادیر را در زمان محاسبه می کند تجزیهبشر

این ارزیابی مستقیم مربوط به اولویت های اعدام است ، به حدی که من روشی دارم که فقط با عبارات و/یا سروکار دارد توکن اولویت ها ، EvaluateExpressionWithPriorityبشر

قبل از توضیح جریان پردازش ، می خواهم جزئیات هر روش را توضیح دهم.

CheckToken: Valida است یا نشانه جریان در طی فرایند انتظار می رود – جایی که ما در حال پیشرفت هستیم حلقه اگر نه ، یک استثنا منتشر می شود. با هر چک ، اشاره گر را پیش می بریم _currentPositionبشر من تصمیم گرفتم این روش را ایجاد کنم زیرا به طور گسترده ای مورد استفاده قرار می گیرد و کد بسیار تکراری بود. در توضیح زیر ، هنگام استفاده از اصطلاح “پیشرفت” ، استفاده از آن را روشن خواهد کرد.

EvaluateToken: پیشرفت های بازگشتی در لیست توکنبشر
هنگام پیدا کردن نشانه نوع Plus، پیشرفت و استفاده مجدد را اعمال می کند. در مورد نشانه Minus همین کار را می کند ، اما معکوس سیگنال مقدار نشانه (-EvaluateToken).
طرف دیگر نشانه تحت درمان قرار می گیرد Numberبشر وقتی آن را پیدا کردید ، به یک نوع تبدیل می شود دو برابر و برمی گردد (در هنگام بازگشت ، اگر نشانه قبلی Minus، ارزش آن توسط معکوس خواهد شد -بشر
سرانجام ، این روش برای یافتن پرانتز پیشرفت می کند. اساساً او ، هنگام یافتن نشانه OpenParenthesis، پیشرفت و راه اندازی مجدد فرایند با استفاده از روش Parseبشر
پس از پایان جریان ، به سمت CloseParenthesis و مقدار پردازش شده را برمی گرداند.

Parse اشمیه EvaluateExpressionWithPriority جریان مشابهی دارد. تفاوت در دستور اعدام است. در Parse با استناد به EvaluateExpressionWithPriority برای انجام فرآیند توضیح داده شده در بالا با روش EvaluateTokenبشر وی سپس عملیات جمع و تفریق را انجام می دهد. در این مرحله ما در حال حاضر اجرای محاسبه ای را داریم که به هر تکرار اضافه می شود.

شاید با توضیح روش با روش چندان واضح نبوده باشد ، اما اکنون ، من معتقدم که “تجسم” این فرآیند ساده تر خواهد بود.

اما ابتدا بیایید حدود 20 سال زمان برگردیم … یا بیشتر … به هر حال …

درست در ابتدا می آموزیم که “ابتدا آنچه را در پرانتز حل می کند و سپس آنچه خارج است” و بعد از “عملیات ضرب و تقسیم قبل از عملیات جمع و تفریق” حل می شود و حتی اگر ترتیب وضوح از ترتیب شرح داده شده در عبارت (از چپ به راست) پیروی کند.

به عنوان مثال ، در بیان 1 + 2 * (3 - 4) ما دستور اجرای زیر را داریم:

مرحله اول ، آنچه را که در پرانتز است محاسبه می کند: 3-4=−1
مرحله دوم ، ضرب: 2*−1=−2
مرحله سوم ، جمع: 1+(−2)=−1

بنابراین ، جریان اعدام این بود:

  1. Parse مهمانی EvaluateExpression فراخوانی EvaluateTokenبشر
  2. EvaluateToken 1 مصرف می کند.
  3. به EvaluateExpressionWithPriority، آنجا * یا / به زودی ، برمی گردد 1بشر
  4. به Parse، پیشرفت تا +، مصرف و تماس EvaluateExpressionWithPriority دوباره
  5. EvaluateExpressionWithPriority مهمانی EvaluateToken این وقت مصرف 2.
  6. پیشبرد یافتن *، مصرف و تماس EvaluateToken پر مصرف (، به صورت بازگشتی وارد شوید Parse برای شروع مجدد یک فرآیند ، این بار با اولویت.
  7. پیشرفت ، پرانتز را باز می کند و ما داریم 3 - 4بشر EvaluateExpression مصرف کردن 3و پیشرفت تا -، مصرف و پیشرفت تا زمانی که 4، ارزش بازگشت ارزش را مصرف و پردازش می کند -1
  8. پرانتز نزدیک و بازگشت -1

در این مرحله اولین مرحله که در بالا توضیح داده شد را انجام می دهیم. برای رسیدن به نتیجه اولویت به کار غول پیکر نگاه کنید! دنبال کردن …

  1. مصرف کردن 2، پیشرفت تا * و محاسبه را با -1 تولید فرآوری شده -2

ما مرحله دوم را بستیم …

  1. مصرف کردن 1، پیشرفت تا + و محاسبه را با (-2) در نتیجه ارزش -1بشر

تمام این کار برای انجام یک کار “ساده” …
امیدوارم که بالکن بازگشت را درک کرده باشید. اشکال زدن این فرآیند به ما کمک می کند تا جریان را درک کنیم. ساعت ها طول کشیدم تا این کد را تنظیم کنم. من حتی فکر می کنم نام روش ها می تواند بهتر باشد ، شاید من آن را در آینده تنظیم کنم …

به هر حال ، ما جریان را می بندیم. اکنون باید آزمایش کنیم!

پرونده برنامه

using Pug.Compiler.CodeAnalysis;

while (true)
{
    try
    {
        Console.Write("> ");
        var input = Console.ReadLine();
        if (string.IsNullOrEmpty(input) || input.Equals(":q", StringComparison.CurrentCultureIgnoreCase))
            break;

        var lexer = new Lexer(input);
        var tokens = lexer.CreateTokens();

        var parser = new SyntaxParser(tokens);
        var result = parser.Parse();

        Console.WriteLine(result);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

ایده در اینجا ایجاد یک تعویض ساده ای که از طریق متن نوشته می شود کنسول، اگر دستور ترک آن نباشد معتبر است :q و سفر “تفسیر” و “اعدام” عبارات آغاز می شود.

ابتدا از روش استفاده می کنیم CreateTokens انجام دادن لکسر و سپس لیست را به ما منتقل کردیم تجزیه کننده که به نوبه خود نتیجه عمل را به ما می دهد:

اگر همه چیز خوب پیش برود و هیچ چیز اشتباه پیش نمی رود ، ما مفسر عددی خود را داریم!

> 1+2+3
6
> -1+-2
-3
> --1
1
> (1+2) * (3+4)
21
> ((((((1+2)
Unexpected token: EOF, expected: CloseParenthesis
> \o/
Unexpected character: \
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید


به انتهای قسمت اول رسیدیم پستبشر
با کلیک بر روی: https://github.com/angelobelchior/pug.compiler/tree/parte1 می توانید به مخزن دسترسی پیدا کنید.

این قسمت اول متراکم بود و محتوای زیادی داشت. اما من آن را ترجیح دادم تا نوشتن مورد بعدی را هموار کنم پست مستقیم تر

من به خصوص باید برخی از مفاهیم را پشت سر بگذارم لکسر اشمیه تجزیه کننده و امیدوارم که شما ، خواننده عزیزم ، خواننده من ، درک کرده باشید.

شما خوشبختی من را تصور نمی کنید وقتی می توانم آن را بسازم. جدی این یک پیشروی کوچک از یک سفر طولانی است.

و من لذت می برم! خیلی !!!

و امیدوارم شما هم!

اگر سوالی دارید ، لطفاً آن را در نظرات بگذارید.

آغوش قوی ، تا دفعه بعد ، آب و مراقبت هایی را که در حال چرخش بسیاری از موارد دنگی است ، بنوشید …

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا