Zephyrnet Logosu

Minik Bir Programlama Dili Oluşturalım

Tarih:

Şu ana kadar muhtemelen bir veya daha fazla programlama diline aşinasınızdır. Peki kendi programlama dilinizi nasıl oluşturabileceğinizi hiç merak ettiniz mi? Ve bununla şunu kastediyorum:

Bir programlama dili, dizeleri çeşitli türdeki makine kodu çıktılarına dönüştüren herhangi bir kural kümesidir.

Kısacası, bir programlama dili önceden tanımlanmış bir dizi kuraldan ibarettir. Bunları faydalı kılmak için bu kuralları anlayan bir şeye ihtiyacınız var. Ve bunlar derleyiciler, tercümanlar, vb. Yani basitçe bazı kuralları tanımlayabiliriz, ardından çalışmasını sağlamak için mevcut herhangi bir programlama dilini kullanarak bu kuralları anlayabilen ve yorumlayıcımız olacak bir program yapabiliriz.

derleyici

Bir derleyici, kodları işlemcinin yürütebileceği makine koduna dönüştürür (örn. C++ derleyicisi).

Çevirmen

Bir tercüman programı satır satır inceler ve her komutu çalıştırır.

Denemek ister misin? Konsolda macenta renkli çıktı veren süper basit bir programlama dilini birlikte oluşturalım. Biz onu arayacağız eflatun.

Basit programlama dilimiz, konsola yazdırılan metni içeren bir kod değişkeni oluşturur… tabii ki macenta renkte.

Programlama dilimizi ayarlama

Ben Node.js'yi kullanacağım ama siz takip etmek için herhangi bir dili kullanabilirsiniz, konsept aynı kalacak. Bir tane oluşturarak başlayayım index.js Dosyalayın ve işleri ayarlayın.

class Magenta {
  constructor(codes) {
    this.codes = codes
  }
  run() {
    console.log(this.codes)
  }
}

// For now, we are storing codes in a string variable called `codes`
// Later, we will read codes from a file
const codes =
`print "hello world"
print "hello again"`
const magenta = new Magenta(codes)
magenta.run()

Burada yaptığımız şey adında bir sınıf ilan etmek Magenta. Bu sınıf, bir metin aracılığıyla sağladığımız metinle birlikte metnin konsola kaydedilmesinden sorumlu olan bir nesneyi tanımlar ve başlatır. codes değişken. Ve şimdilik bunu tanımladık codes Birkaç "merhaba" mesajıyla doğrudan dosyadaki değişken.

Terminal çıkışının ekran görüntüsü.
Bu kodu çalıştıracak olsaydık, konsola kayıtlı kodlarda saklanan metni alırdık.

Tamam, şimdi Lexer denen bir şey yaratmamız gerekiyor.

Lexer nedir?

Tamam, bir saniyeliğine İngilizce hakkında konuşalım. Aşağıdaki ifadeyi alın:

Nasılsın?

Burada “How” bir zarf, “are” bir fiil ve “you” bir zamirdir. Ayrıca sonunda soru işareti (“?”) var. Bunun gibi herhangi bir cümleyi veya ifadeyi JavaScript'te birçok gramer bileşenine ayırabiliriz. Bu parçaları ayırt edebilmemizin bir diğer yolu da onları küçük jetonlara bölmektir. Metni jetonlara bölen program bizim programımızdır. Lexer.

Bir lexer'dan geçen komutu gösteren diyagram.

Dilimiz çok küçük olduğundan, her biri bir değere sahip olan yalnızca iki tür belirteç vardır:

  1. keyword
  2. string

Jetonları çıkarmak için normal bir ifade kullanabilirdik. codes dize ancak performans çok yavaş olacaktır. Daha iyi bir yaklaşım, her karakter arasında döngü oluşturmaktır. code dize ve kapma jetonları. Öyleyse bir oluşturalım tokenize bizim yöntemimiz Magenta sınıfımız - bu bizim Lexer'ımız olacak.

Tam kod
class Magenta {
  constructor(codes) {
    this.codes = codes
  }
  tokenize() {
    const length = this.codes.length
    // pos keeps track of current position/index
    let pos = 0
    let tokens = []
    const BUILT_IN_KEYWORDS = ["print"]
    // allowed characters for variable/keyword
    const varChars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_'
    while (pos < length) {
      let currentChar = this.codes[pos]
      // if current char is space or newline,  continue
      if (currentChar === " " || currentChar === "n") {
        pos++
        continue
      } else if (currentChar === '"') {
        // if current char is " then we have a string
        let res = ""
        pos++
        // while next char is not " or n and we are not at the end of the code
        while (this.codes[pos] !== '"' && this.codes[pos] !== 'n' && pos < length) {
          // adding the char to the string
          res += this.codes[pos]
          pos++
        }
        // if the loop ended because of the end of the code and we didn't find the closing "
        if (this.codes[pos] !== '"') {
          return {
            error: `Unterminated string`
          }
        }
        pos++
        // adding the string to the tokens
        tokens.push({
          type: "string",
          value: res
        })
      } else if (varChars.includes(currentChar)) { arater
        let res = currentChar
        pos++
        // while the next char is a valid variable/keyword charater
        while (varChars.includes(this.codes[pos]) && pos < length) {
          // adding the char to the string
          res += this.codes[pos]
          pos++
        }
        // if the keyword is not a built in keyword
        if (!BUILT_IN_KEYWORDS.includes(res)) {
          return {
            error: `Unexpected token ${res}`
          }
        }
        // adding the keyword to the tokens
        tokens.push({
          type: "keyword",
          value: res
        })
      } else { // we have a invalid character in our code
        return {
          error: `Unexpected character ${this.codes[pos]}`
        }
      }
    }
    // returning the tokens
    return {
      error: false,
      tokens
    }
  }
  run() {
    const {
      tokens,
      error
    } = this.tokenize()
    if (error) {
      console.log(error)
      return
    }
    console.log(tokens)
  }
}

Bunu bir terminalde çalıştırırsak node index.js, konsolda yazdırılan belirteçlerin bir listesini görmeliyiz.

Kodun ekran görüntüsü.
Harika şeyler!

Kuralları ve sözdizimlerini tanımlama

Kodlarımızın sırasının bir tür kurala veya sözdizimine uyup uymadığını görmek istiyoruz. Ancak önce bu kuralların ve söz dizimlerinin ne olduğunu tanımlamamız gerekiyor. Dilimiz çok küçük olduğu için yalnızca tek bir basit söz dizimi vardır: print anahtar kelime ve ardından bir dize gelir.

keyword:print string

Öyleyse bir tane oluşturalım parse belirteçlerimiz arasında dolaşan ve geçerli bir söz dizimi oluşturup oluşturmadığımızı gören bir yöntem. Öyle ise gerekli tedbirleri alacaktır.

class Magenta {
  constructor(codes) {
    this.codes = codes
  }
  tokenize(){
    /* previous codes for tokenizer */
  }
  parse(tokens){
    const len = tokens.length
    let pos = 0
    while(pos < len) {
      const token = tokens[pos]
      // if token is a print keyword
      if(token.type === "keyword" && token.value === "print") {
        // if the next token doesn't exist
        if(!tokens[pos + 1]) {
          return console.log("Unexpected end of line, expected string")
        }
        // check if the next token is a string
        let isString = tokens[pos + 1].type === "string"
        // if the next token is not a string
        if(!isString) {
          return console.log(`Unexpected token ${tokens[pos + 1].type}, expected string`)
        }
        // if we reach this point, we have valid syntax
        // so we can print the string
        console.log('x1b[35m%sx1b[0m', tokens[pos + 1].value)
        // we add 2 because we also check the token after print keyword
        pos += 2
      } else{ // if we didn't match any rules
        return console.log(`Unexpected token ${token.type}`)
      }
    }
  }
  run(){
    const {tokens, error} = this.tokenize()
    if(error){
      console.log(error)
      return
    }
    this.parse(tokens)
  }
}

Şuna bakar mısınız, zaten çalışan bir dilimiz var!

Terminal çıkışının ekran görüntüsü.

Tamam ama bir dize değişkeninde kodlara sahip olmak o kadar da eğlenceli değil. O halde hadi koyalım eflatun adlı bir dosyadaki kodlar code.m. Bu şekilde macenta kodlarımızı derleyici mantığından ayrı tutabiliriz. Kullanıyoruz .m Bu dosyanın dilimiz için kod içerdiğini belirtmek için dosya uzantısı olarak kullanın.

O dosyadaki kodu okuyalım:

// importing file system module
const fs = require('fs')
//importing path module for convenient path joining
const path = require('path')
class Magenta{
  constructor(codes){
    this.codes = codes
  }
  tokenize(){
    /* previous codes for tokenizer */
 }
  parse(tokens){
    /* previous codes for parse method */
 }
  run(){
    /* previous codes for run method */
  }
}

// Reading code.m file
// Some text editors use rn for new line instead of n, so we are removing r
const codes = fs.readFileSync(path.join(__dirname, 'code.m'), 'utf8').toString().replace(/r/g, &quot;&quot;)
const magenta = new Magenta(codes)
magenta.run()

Gidip bir programlama dili yaratın!

Ve bununla birlikte sıfırdan küçük bir Programlama Dili'ni başarıyla oluşturduk. Bakın, bir programlama dili belirli bir şeyi başaran bir şey kadar basit olabilir. Elbette, Magenta gibi bir dilin popüler bir çerçevenin parçası olacak kadar kullanışlı olması pek mümkün değil, ancak şimdi bir tane oluşturmak için ne gerektiğini görüyorsunuz.

Gökyüzü gerçekten sınırdır. Biraz daha derine inmek istiyorsanız, daha gelişmiş bir örnek üzerinden hazırladığım bu videoyu takip etmeyi deneyin. Bu aynı zamanda dilinize değişkenler ekleyebileceğinizi de gösterdiğim videodur.

[Gömülü içerik]
spot_img

En Son İstihbarat

spot_img