Ş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.
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.
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.
Dilimiz çok küçük olduğundan, her biri bir değere sahip olan yalnızca iki tür belirteç vardır:
keyword
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.
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!
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, "")
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.