Compilers ve Interpreters

Abdulsamet İLERİ
6 min readJun 4, 2019

Bizler, kodladığımız programları genellikle yüksek seviyeli bir dil ile yazarız. Bu yazdığımız kodun bütününü ise kaynak kod olarak adlandırırız. Yüksek seviyeli diller bilindiği üzere insanlar tarafından kolaylıkla anlaşılan çoğunlukla ingilizce olan bazı kelimeleri ve kalıpları içerir fakat bilgisayar tabiki bunu anlamaz. Anlıyacağı programın 0’ lardan ve 1’ lerden oluşması yani makine kodu şeklinde olması gerekmektedir.

Yazdığımız kaynak kodu, bilgisayarda çalışması için makine koduna çevrilmesi gerekir ve bu işlem de Compilerlar ve Interpreterlar ile sağlanır.

Compiler ve Interpreter, yüksek seviyeli bir dil ile yazılmış programı, bilgisayarın anlıyacağı makine koduna çeviren programlardır.

Compiler (Derleyici) Nedir?

Yüksek seviyeli bir dil ile yazılmış kod parçasını (JavaScript veya Java gibi) bilgisayarın veya virtual machine gibi programların direkt olarak çalıştırılabildiği düşük seviyeli kod parçasına çeviren (Assembly gibi) bir programdır.

Mesela, Java Compiler; Java kodunu, Java Virtual Machine (JVM) tarafından çalıştırılabilen Java Bytecode’e çevirir.

Google’ın geliştirdiği V8 (JavaScript engine); JavaScript kodunu, makine koduna çevirir.

GCC; C, C++, Objective C, Go ile yazılmış kodu native machine koduna çevirir.

Yukarıdaki resimdeki siyah kutuyu daha da detaylandırıp, Compiler’ın ne yaptığını anlamaya çalışalım. Bir compiler’ı 2 parçaya bölebiliriz. Front-end olarak adlandırılan ilk kısmında, Kaynak kod’da bulunan sembolleri içeren veri yapısı tutulur, düzenlenir ve gelen kaynak kod, syntax error, tanımlanan değişkenlere bakmak (gerekirse tipini çıkarmak) ve her değişkenin tanımlandığı yerden önce kullanılmadığını garantilemek gibi işlemler içinde barındırır. Herhangi bir hata ile karşılaşıldığı zaman, kullanıcıya hata mesajı gösterilir. Eğer herhangi bir hata ile karşılaşılmazsa; taranmış, yukarıdaki bahsedilen operasyonlarla işleme tabii tutulmuş ve oluşturulmuş ara kod backend kısmına input olarak verilir.

2. kısımda, front-end aşamasında düzenlenen symbol table ve onun çıktısı olan intermediate representation kullanılarak low level kod çıktısı üretilir.

Compilerin her iki asamasında (frontend ve backend) yapılan işlemler belli bir sırayı takip eder ve bir aşamanın çıktısı, diğer bir aşamanın girdisi olur.

Compilerin 1.kısmı olan Front-end’in aşamaları lexical analysis, syntax analysis, semantic analysis and intermediate code generation olarak adlandırılır.

Compilerin 2.kısmı olan Back-end ise optizimation ve code generation aşamalarını içerir.

Şimdi bu aşamaları sırasıyla inceleyelim.

Lexical Analysis

Bu aşamada compiler verilen kaynak kodu lexemes denilen anlamlı parçalara parçalar ve bu parçalardan da tokenler oluşturur.

Lexeme’yi kaynak programlama dilinin içerisinde yer alan benzersiz (unique) karakter stringleri olarak düşünebiliriz. Örnek verecek olursak, keywords dediğimiz if, while veya func, belirteçler (identifiers), strings, sayılar, operatörler, veya tek karakterler (, ), ., ; gibi.

Token ise lexemeyi tanımlayan bir objedir. İçerisinde lexeme’nin string’ini tutduğu gibi, lexeme nin tipini (keyword mü, identifier mi, operatör mü gibi) ve kaynak koddaki posizyonunu (satır veya kolon sayısı) tutar.

Eğer compiler bu kısımda, token’ini oluşturamadığı bir karakter stringi okursa hata fırlatır.

Syntax Analysis

Compiler, bu aşamada Lexical Analysis aşamasında oluşturulan tokenleri kullanarak tree-like data structure olan Abstract Syntax Tree (AST) oluşturur. Bu yapı programın syntactic ve logical yapısını oluşturur.

Bu aşama sonunda var olan syntax hataları bulunur ve kullanıcıya bilgilendirici bir mesaj halinde raporlanır. Örnek olarak yukarıdaki resmi aldığımızda, sum fonksiyonunu yazarken } karakterini unutursak, Compiler } karakterinin konulmadığını belirtecek ve hangi satır ve kolona koymamız gerektiğini söyleyecektir.

Eğer bu aşamada herhangi bir hata ile karşılaşılmazsa, Compiler, Front-end kısmının diğer bir aşaması olan semantic analysis kısmına geçer.

Semantic Analysis

Bu aşamada Compiler, Syntax Analysis aşamasında üretilen AST’yi kullanır ve programın, programlama dilindeki kurallara ne kadar uyduğunu, tutarlı olduğunu kontrol eder. Semantic analysis aşaması ayrıca Type Inference, Type Checking ve Symbol Management gibi kısımları da kapsar.

Type Inference kısmında, eğer kullanılan programlama dili tip çıkarımını destekliyorsa, Compiler, programdaki tipi verilmemiş ifadeleri anlamaya, çıkarmaya çalışır. Eğer bu çıkarım başarılı olursa, ilgili AST noduna compiler, type annotation’u ekler.

Type Checking kısmında, Compiler atanan değerlerin değişkenin tanımlandığı tiple uyumluluğunu, işlemlerde kullanılan argümanların doğru tipte olup olmadığını kontrol eder. Mesela herhangi bir string değişkenine, double değeri atılmış mı, veya boolean argümanı kabul eden bir fonksiyona double değişken mi atılmış, veya string değer int bir değerle mi bölünmüş gibi (dil buna izin veriyorsa, ayrı tabii) kontroller verilebilir.

Symbol Management kısmında, Compiler, symbol table denilen veri yapısına bakarak, bu değişken kullanılmadan önce tanımlanmış mı? Aynı isimle, aynı scope da tanımlı değişken var mı? Bu değişkenin tipi ne? Şuan ki scope da bu değişken var mı? gibi sorulara cevaplar bulur.

Bu aşama syntax analysis aşamasında üretilen AST üzerindeki nodelara değişiklik yaptığından, output olarak annotated AST ve symbol table verir.

Intermediate Code Generation

Bu aşamada Compiler, Semantic Analysis aşamasında üretilen annotated AST’yi kullanarak ara ve makine bağımsız low level kod üretir. Buradaki ara gösterime three-address code denilir.

Three address code (3AC), en basit haliyle, bir komutun bir atama operatörü ve bunun dışında en fazla 3 operatör içerebildiği bir dildir.

Çoğu 3AC komutu, a := b <operator> c or a := b formuna sahiptir.

Yukarıdaki resimde annotad AST’den 3AC kodunun nasıl üretildiği gösterilmiştir.

Bu aşamanın bitimiyle de Compiler’in Front-end kısmı son bulmuştur.

Optimization

Bu aşamada, yani back-end kısmının ilk aşamasında, kodu hem daha hızlı çalışacak ve hem de kısa bir hale getirmek için, çeşitli optimization teknikleri, ara kod üzerine uygulanır.

Mesela yukaradaki ara kod parçasına, optimization uygularak t3’ü içeren satır optimize edilmiştir, elenmiştir.

Code Generation

Bu son aşamada, derleyici optimize edilmiş ara kodu makineye bağlı koda, Assembly’ye veya hedeflenen herhangi bir düşük seviye diline çevirir.

Compiler vs. Interpreter

Interpreters ve compilers birbirine oldukça benzer yapıdadırlar. Aralarındaki temel fark Interpreter kaynak programlama dili ile yazılan komutları doğrudan işlerken, Compiler da o komutları makina diline çevirir.

Interpreter’larda, Compiler’da olduğu gibi lexing, parsing ve type checking vardır. Ama interpreter syntax treeden üretilen koda gerek kalmadan, doğrudan syntax tree üzerinde işlem yapar, ifadelere erişir ve komutları çalıştırır. Aynı syntax tree üzerinde birden fazla çalışma durumunun da olmasından dolayı, interpretation, derlenmiş bir programın çalışmasına kıyasla daha yavaş çalışır.

Aralarındaki farkları madde madde inceleyelim.

# Compiler bir programı bütün olarak alır ve çevirirken; Interpreter programı satır satır çevirir.

# Compiler, ara kod veya hedef kodu oluşturur fakat Interpreter herhangi bir ara kod oluşturmaz. Bundan dolayı Compiler, kodun oluşturulması için daha fazla memory gerektirir.

# Compiler’da, bir hata oluştuduğunda, çeviri işlemi durur ve hata giderildikten sonra bütün program yeniden çeviri işlemine tabii tutulur. Interpreter, bunun tam aksine olarak, eğer bir hata meydana geldiğinde, o anki çeviriyi engeller ve hata giderildiğine çeviriyi kaldığı yerden devam ettirir. Bu yüzden debug işlemi daha kolaydır.

# Compiler’da, Interpreter’e kıyasla hata bulma daha zordur.

# Compiler, C, C++, C#, Scala, TypeScript gibi dillerde kullanılırken, Interpreter PHP, Perl, Ruby, Python gibi dillerde çalıştırılır.

Compilation ve interpretation bir programlama dilini uygulamak için beraber kullanılabilir. Compiler intermediate level code’u ürettikten sonra makine koduna derlenmeden, interpreter tarafından yorumlanır.

Mesela Java kodu önce Object koduna derlenir, çalışma zamanında ise bu object kodu, JVM tarafından, hedef bilgisayarın makine koduna yorumlanır.

Kaynaklar

https://hackernoon.com/compilers-and-interpreters-3e354a2e41cf

https://techdifferences.com/difference-between-compiler-and-interpreter.html

https://www.guru99.com/difference-compiler-vs-interpreter.html

--

--