Bu qanday ishlaydi? Kompilyator (1-qism)

Hammamizga ma’lumki kompyuter yoki har qanday dastur bilan ishlovchi qurilmalar uchun dasturlar biror bir dasturlash tillarida yoziladi.

Bunday tillar hozirda juda ko’p. C, C , Delphi, Ruby, Perl, Python, Java va shu kabi universal dasturlash tillaridan tashqari maxsus biror yo’nalish yoki platformaga mo’ljallangan tillar, skript tillar ham bir talay. Dastur tuzish uchun dastur tuzilayotgan qurilma ichki tuzilmasini hisobga olib yuqoridagi tillardan birida dastur matni yoziladi. So’ngra bu dastur ishlashi uchun dastur matnini u mo’ljallangan qurilmaning “tiliga”, ya’ni prosessor instruksiyalari (buyruqlari) ketma-ketligiga tarjima qilish kerak bo’ladi. Bu vazifani kompilyator dasturi bajaradi.


Kompilyator – biror dasturlash tilida yozilgan dastur (matni)ni o’sha til qoidalariga muvofiq mashina kodiga yoki oraliq kodga (p-kod, baytkod) tarjima qiluvchi dastur. Ushbu tarjima jarayoni kompilyasiya deyiladi.


Agar kompilyasiya jarayonida dastur mashina kodiga tarjima qilingan bo’lsa, hosil bo’lgan kod o’sha qurilma/muhitda bevosita ishlayveradi. Agar dastur oraliq kodga tarjima qilingan bo’lsa, u holda bu kodni ishlatish uchun Virtual Mashina kerak bo’ladi. Virtual mashina – oraliq kodda ifodalangan dasturni qadamlab mashina kodiga tarjima qiladi va dasturni ishlatadi.

Shu joyda “Oraliq kodga o’tkazishni nima keragi bor, dasturni to’g’ridan to’g’ri mashina kodiga o’girish yaxshimasmi?” degan savol tug’ilishi tabiiy. Oraliq kod birinchi navbatda dasturning kross platformaliligini (dastur o’zgarishsiz yoki qisman o’zgartirish orqali boshqa platformalarda ham ishlayverishga yaroqli bo’lsa bunday dastur kross platformali deyiladi) ta’minlaydi. Har bir platforma, OS o’zining ichki arxitekturasi jihatidan bir biridan farq qiladi, shunday ekan bir muhitga moslab yozilgan mashina tilidagi kod ikkinchi boshqa bir muhitda ishlamasligi tabiiy. Oraliq kodlarda yozilgan dasturlar huddi shu muammoni hal qilishda yordam beradi. Bunda oraliq kodni ishlatish uchun kerak bo’ladigan Virtual Mashina turli muhitlar, OS lar uchun mavjud bo’lsa yetarli, ya’ni har bir dasturni boshqa muhitga moslash shart emas, faqatgina Virtual Mashinani kerakli muhitlar uchun moslansa yetarli. Bundan tashqari oraliq kod dasturni tahlil qilish (debug) jarayonini yengillashtiradi ham. Oraliq kod hosil qiluvchi til kompilyatorlariga misol qilib, Java, Python, Ruby kabilarni keltirish mumkin.

Xo’sh kompilyator qanday ishlaydi?

Har qanday tillar o’zining grammatikasi yoki sintaksisiga ega bo’ladi. Masalan, o’zbek tilida to’g’ri yozilgan gapda kesim egadan keyin kelishi kabi qoida ham til sintaksisining bir bo’lagidir. Dasturlash tillarida ham tabiiy tillar singari o’zining qonun qoidalari bo’lishi tabiiy. Kompilyasiya jarayoni huddi shu qoidalarga asoslangan holda amalga oshiriladi.

Erkin kontekstli dasturlash tillari uchun kompilyatorlar odatda asosan 3 bosqichdan iborat bo’ladi:

      Leksik tahlil

      Sintatktik tahlil

      Tarjima qilish

Har bir bosqichni batafsil ko’rib chiqamiz.

Leksik tahlil dastur matnini tilning alifbosiga mos ravishda bo’laklarga, “so’z”larga ajratish vazifasini bajaradi. Natijada identifikatorlar, literallar, bo’sh joylar, amallar va boshqa bo’laklar hosil bo’ladi. Masalan, deylik, o’zbek tiliga asoslangan qandaydir dasturlash tilida

 

agar (i

  

ko’rinishidagi matn tahminan quyidagicha bo’laklarga ajratiladi.

 

 

Bo’laklash bosqichini dasturlash qiyin ish emas. Buning uchun har bir bo’lak qanday belgilar to’plamidan iboratligi, qaysi belgidan keyin qaysi belgi kelishi kabi belgilashlarlarni aniqlab olishimiz kerak. Shu joyda bir narsani aytib o’tish lozimki, hozirda agar biror bir kompilyator yozmoqchi bo’lsangiz uni 0 dan yozish ham shart emas. Bunda Sintaktik analizator (parserlar) va Kompilyatorlar kompilyatori kabi bir qancha tayyor dasturlardan foydalanish ishni ancha yengillashtiradi. Bu dasturlar kirishiga tilning formal yozuvdagi ko’rinishi berilsa, birinchi (leksik tahlil) va ikkinchi (sintaktik tahlil) qadamlarni avtomatik bajarib beradigan, qisman uchinchi bosqich (tarjima) ni ham bajaradigan kod hosil qilib beradi. Tilning formal yozuvi (grammar) deganda tilning alifbosi, qonun qoidalarini ko’rsatuvchi yozuvga aytiladi. Bu yozuv turlaridan biri Bekus-Naur yozuvidan keng foydalaniladi.

 

Bekus-Naur yozuvida (aniqrog’i, uning kengaytirilgan variantlaridan birida) til alifbosi Belgilar to’plami, So’zlar va Qoidalar degan 3 ta element yordamida ifodalanadi. Belgilar to’plami bu biror bir So’z qanday belgilardan tashkil topishini bildiradi. Masalan Identifikator degan So’z A dan Z gacha lotin harflari, 0 dan 9 gacha raqamlar va “_” belgisidan iborat bo’lishi mumkin degan shart aynan Belgilar to’plami yordamida ko’rsatiladi.

Belgilar to’plami:

{Harflar}             = ABCDEFGHIJKLMNOPQRSTUVWXYZ
{Raqamlar}            = 0123456789
{Identifikator boshi} = {Harflar} [_]
{Identifikator oxiri} = {Raqamlar} [_]

 

Bu yerda {Harflar} nomli belgilar to’plami A dan Z gacha lotin harflaridan biridan iborat bo’lishi mumkinligi, {Raqamlar} esa 0 dan 9 gacha raqamdan biri ekanligi, {Identifikator boshi} esa {Harflar} dan va “_” belgisidan tuzilganligi va {Identifikator oxiri} esa {Raqamlar} va “_” belgisidan iborat ekanligi ko’rsatilyapti. Belgilar to’plamini berishda “ ” belgisi belgilar to’plamiga yangi belgini qo’shish, “-“ belgisi esa keraksiz belgini chiqarib tashlashni bildirishi mumkin. Identifikator boshi va oxiri deb ikkita bo’lakka bo’lishdan maqsad esa, identifikatorlar nomi faqat harf yoki “_” belgisidan boshlanishi mumkinligidan kelib chiqdi.

Belgilar to’plami qay tartibda kelishi So’zlarda ifodalanadi. Yuqoridagi belgilar to’plamidan foydalanib Identifikatorni qanday berilishini ko’ramiz:

Identifikator = {Identifikator boshi} {Identifikator oxiri}*

Bu yerda “*” belgisi uning oldida kelgan belgilar to’plami yoki belgining 0 yoki undan ko’p marta, “ ” belgisi esa 1 yoki undan ko’p marta takrorlanishi mumkinligini ko’rsatadi (huddi Regular Ifodalardagi kabi). Yana misol:

ButunSon       = {Raqamlar}
HaqiqiySon     = {Raqamlar}* “.” {Raqamlar}
MantiqiyQiymat = “rost” | “yolg’on”

 

Qo’shtirnoq ichidagi belgi uni So’zda qanday bo’lsa shunday holicha kelishini, “|” belgisi esa “yoki” amalini bildiryapti.

Va nihoyat So’zlarning qay tartibda kelishi Qoidalar yordamida beriladi:

 ::= “agar”  “bo’lsa” 
                  | “agar”  “bo’lsa”  “aks holda” 
 ::= Identifikator “=” 
...
     ::=  “;”
                  |  “;”
                  |  “;”
                  |  “;”
                  |  “;”
                  | 
                  |
...

 

E’tibor bering, qoidasini berishda rekursiyadan foydalanilyapti, bu esa buyruqlarni ichma ich va ko’p marotaba qaytarilishini ifodalash uchun kerak bo’ladi. Ko’rinib turibdiki, bunday yozuvlardan foydalanib har qanday tilning sintaksisini, alifbosini bemalol ko’rsatish mumkin. Bekus-Naur yozuvidan tashqari yana bir nechta shu kabi tilni “qolipini” tushuntirishda qo’llaniladigan yozuvlar mavjud.

 

Keling, yuqorida berilgan til alifbosi uchun oddiy bo’laklash jarayonini dasturlashni ko’rib chiqamiz. Ya’ni, berilgan matnni Identifikator, ButunSon, HaqiqiySon, MantiqiyQiymat kabi bo’laklarga ajratuvchi dastur tuzamiz. Ushbu bosqich uchun Lexer nomli klassni yozamiz. Bunda bo’laklarni saqlash uchun Token nomli qo’shimcha klass ham kerak bo’ladi:

// Ushbu klass bo'lakni ifodalash uchun

public class Token
{

    // mumkin bo'lgan bo'laklar turi

    public enum TokenType
    {

        UNKNOWN,                   // noma’lum bo’lak

        IDENTIFIER,                // identifikator

        OPERATOR,                  // operator

        NUMLITERAL,                // sonli literal

        STRINGLITERAL,             // matnli literal

        WHITESPACE,               // bo’sh joylar

        NEWLINE,                  // yangi qator

        EOP                       // dastur oxirini belgilash uchun

    }

    public string Text;     // bo'lak matni

    public TokenType Type;  // bo'lak turi

    public int TextPos;     // bo'lakni dastur matnidagi pozisiyasi

    public Token()
    {

        this.Text = "";

        this.TextPos = 0;

        this.Type = TokenType.UNKNOWN ;

    }

}

public class Lexer
{

    // konstantalar

    const char CHAR_NULL = '\0';

    // o'zgaruvchilar

    string text;

    int position;

    // konstruktor

    public Lexer()
    {

        this.position= 0;

        this.text= "";

    }

    // konstruktor

    public Lexer(string text)
    {

        this.text= text;

        this.position= 0;

    }

    // dastur matnini berish/olish

    public string Text
    {

        get { return this.text;  }

        set { this.text = value; }

    }

}

 

Lexer klassiga kiruvchi matnni belgilab o’qishga yordam beruvchi funksiyalarni qo’shamiz:

// jarayondagi joriy pozisiya

public int Position
{

    get { return this.position; }

}

// joriy belgini aniqlash

public char CurrentChar()
{

    if (position < text.Length)

        return Convert.ToChar (text.Substring(position, 1));

    else

        return CHAR_NULL ;

}

// joriy belgidan keyingi belgini aniqlash

public char NextChar()
{

    if (position 1 < text.Length)

        return Convert.ToChar(text.Substring(position 1, 1));

    else

        return CHAR_NULL;

}

// joriy belgidan oldingi belgini aniqlash

public char PrevChar()
{

    if (position - 1 >= 0)

        return Convert.ToChar(text.Substring(position - 1, 1));

    else

        return CHAR_NULL;

}

// keyingi belgiga o'tish

public void SkipChar()
{

    position ++;

}

 

Vanihoyat asosiy qism, bo’lakni “tanish” funksiyasini yozamiz:

// bo'lakni o'qish
public Token ReadToken()
{

    char c;

    string result = "";

    Token tok = new Token();

    // joriy belgini o'qish

    c = CurrentChar();

    // agar matndagi oxirgi belgi bo'lsa (EOP)

    if (c == CHAR_NULL) {

        tok.Type = Token.TokenType.EOP;

    // agar joriy belgi harf yoki _ belgisi bo'lsa
    // demak identifikatorni o'qiymiz

    } else if (char.IsLetter(c) || (c == '_')) {

        do
        {

            c = CurrentChar();

            if (char.IsLetter(c) || char.IsDigit(c) || (c == '_'))

                result += c;

            else

                break;

            SkipChar();

        } while (true);

        tok.Type = Token.TokenType.IDENTIFIER;

        tok.Text = result;

    // joriy belgi bo'sh joy bo'lsa
    // bo'sh joylar ketma-ketligini o'qiymiz

    } else if (IsWhiteSpace(c)) {

        do
        {

            result += c;

            SkipChar();

            c = CurrentChar();

        } while (IsWhiteSpace(c));

        tok.Type = Token.TokenType.WHITESPACE;

        tok.Text = result;

    // joriy belgi " belgisi bo'lsa
    // matnli literalni o'qiymiz

    } else if (c=='"' ) {

        do
        {

            result += c;

            SkipChar();

            c = CurrentChar();

            if (c == '"')

                if (PrevChar() != '\\' )
                {
                    result = result c;

                    SkipChar();

                    break;

                }

        } while (c != CHAR_NULL);

        tok.Type = Token.TokenType.STRINGLITERAL;

        tok.Text = result;

    // qator va buyruq ajratuvchi belgilarni o'qiymiz

    } else if (c==';' ) {

        do
        {

            result += c;

            SkipChar();

            c=CurrentChar();

        } while (c==';');

        tok.Type = Token.TokenType.NEWLINE;

        tok.Text = result;

    // sonli literalni o'qiymiz

    } else if (char.IsDigit(c) || (c == '.' && char.IsDigit(NextChar()))) {

        bool hasPoint = false;

        do
        {

            c = CurrentChar();

            if (char.IsDigit(c))

                result += c;

            else if (c == '.' && !hasPoint)
            {

                result += c;

                hasPoint = true;

            } else

                break;

            SkipChar();

        } while (true);

        if (c == '.') Console.WriteLine("Leksik xato!");

        // mana bu yerda sonli literalni Butun, Haqiqiy kabi
        // turlarga ajratuvchi qism bo'lishi mumkin, ya'ni
        // sonli literal "." bor yo'qligi va qiymatining
        // katta kichikligiga qarab yana kichik turlarga
        // ajratilishi mumkin
        // ...

        tok.Type = Token.TokenType.NUMLITERAL;

        tok.Text = result;

    // noma'lum belgi

    } else {

        result += c;

        SkipChar();

        tok.Type = Token.TokenType.UNKNOWN;

        tok.Text = result;

    }

    return tok;

}

public bool IsWhiteSpace(char c)
{

    if (c == ' ' || c == '\t')

        return true;

    else

        return false;

}

 

Eslatma: Ushbu kodga xatoliklarni tekshiruvchi qism qo’shilmagan.

Lexer ni ishlashini tekshirish uchun asosiy klassga quyidagini yozamiz:

static void Main(string[] args)
{

    string text = Console.ReadLine();

    Lexer lex = new Lexer( text);

    Token tok;

    int n=0;

    string result;

    do

    {

        n++;

        tok = lex.ReadToken();

        // natijani formatlash va ekranga chiqarish
        result = n.ToString().PadRight (3);

        result += (" (" + tok.Type.ToString() + ")").PadRight (18);

        result += tok.Text;

        Console.WriteLine(result);

    } while (tok.Type != Token.TokenType.EOP ); // toki birorta bo'lak qolmaguncha

    Console.ReadKey(true);

}

 

Dasturni kompilyasiya qilib ishlatamiz va kirishga misol uchun quyidagicha matn kiritamiz:

A1 “bu matn” 12.5 99

 

Natija:

1. (IDENTIFIER)      a1

2. (WHITESPACE)

3. (STRINGLITERAL)  "bu matn"

4. (WHITESPACE)

5. (NUMLITERAL)      12.5

6. (WHITESPACE)

7. (NUMLITERAL)      99

8. (EOP)


Fikr bildirish