Xtext – DSL Framework, Bölüm 1

Xtext, size DSL (Domain-Specific Language) oluşturmanıza olanak sağlayan bir framework(tekrar kullanılabilir kütüphaneler topluluğu). Bense bu framework’ü bitirme çalışmam için kullanıyorum. Danışman hocamla birlikte, bitirme çalışmamda, kendi şifreleme protokolümüzü ve bu protokolü rahatlıkla uygulayabileceğimiz dili yaratmayı amaçlıyoruz. Xtext ise bu iş için çok uygun. Kolaylıkla dilinizin özelliklerini belirleyebilirsiniz. İstediğiniz gibi yazım kuralları seçebilirisiniz. Mesela dilinizde her satırın sonunu ‘;’ bitirebileceğiniz gibi hiç bir şey koymadan da satır sonu belirleyebilirsiniz.

Xtext Ne yapıyor?

Xtext, bilinen BNF(Backus-Naur Form)‘un biraz daha gelişmiş şekli olan EBNF(Extended BNF) kullanarak size dilinizin özelliklerini belirlemenizi sağlıyor. Böylelikle oluşturduğunuz her anahtar kelimenin neler içereceğine, hangi bileşenlerden oluşacağına, bu bileşenlerin neler içerdiğine ya da içerdiği bileşenlerin ne tür yapılar döndüreceğine karar verebiliyorsunuz. Örneğin bir toplama işlemi tanımlayacaksanız, bunu topla anahtar kelimesi ile tanımlayabileceğiniz gibi, iki sayı arasına giren + işareti koyarakta tanımlayabilirsiniz.

Nerden Baslamalı?

Benim xtext öğrenmeye çalışırken yaşadığım en büyük sorunlardan biriydi bu. Nerden başlayacağımı bilemiyordum. Fakat sonradan anladım ki, her programlama işine başlarken olduğu gibi, önce bir tasarım yapmak en iyisi. Yani dilinizin nasıl olacağını, neler içereceğini önceden tasarlamak en iyi başlama yolu. Sonra bunu EBNF olarak nasıl ifade edebileceğinzi düşünürsünüz. Şimdi herşeyden önce kurulumun nasıl yapılacağına bakalım.

Kurulum

  • Öncelikle ben Eclipse Galileo kullanıyorum. Onun için Galileo üzerinden gideceğim. Fakat üst ya da alt Eclipse versiyonları için de işlemlerin çok farklı olacağını sanmıyorum. Eğer Eclipse sisteminizde kurulu değilse şu siteden indirebilirsiniz.
  • Sonra geriye Xtext’in update sitesini Eclipse’inize kaydedip, eklentiyi indirmeniz kalıyor.
  • Bunun için önce Eclipse -> Help -> Install New Software seçip, Xtext update sitesini eklemeniz yeterli.
  • Xtext update sitesi :http://download.eclipse.org/modeling/tmf/updates/releases
  • Ben tüm kutucukları seçip yükledim, fakat bazen hata verebiliyor, hata alırsanız doğrudan indirip local olarak kurmayı deneyebilirsiniz.
  • Local olarak kurarken, site ekleme yerinden indirdiğiniz dosyanın yolunu vermeniz yeterli.

Dört islem Örnegi

Yeni birşey anlatırken bir “Hello, World!” uygulaması üzerinden anlatmak adettendir. Fakat Xtext kurulumunu düzgün yaptıysanız, yeni bir xtext projesi oluşturduğunuzda size zaten bir “Hello, World!” örneği sunacaktır. Bu sebepten ben sonraki en basit örneği yapmayı uygun gördüm. Benim yapacağım örnekte, iki tane tam sayıya dört işlemi uygulayacak dili yazmaya çalışacağım. Öncelikle dilimin nasıl olacağına karar veriyorum ve tasarımımı yaptığımda aşağıdaki gibi çok basit bir tasarım elde ediyorum.


operation bhdrkn {
 8 + 4
 14 - 2
 3 * 4
 24 / 2
}

Burda tasarlanmış olan şu. Benim bir anahtar kelimem var operation. Bu operation farklı isimler alabilir. Ben burada bhdrkn ismini vermişim. Eğer + işaretini kullanırsam toplama, – işaretini kullanırsam çıkarma işlemi yapsın istiyorum. Aynı zamanda her operation öbeğini süslü parantezlerle ayıralım istiyorum. Eğer herşeyi doğru olarak yaparsam, yeni oluşturduğum dilde bu kodu kullanabileceğim. Aynı zamanda bu kodu kullandığımda yazım hatası almayacağım. İlerde bahsedeceğim fakat şimdiden söyliyim dilinizi tanımladığınızda, size yeni bir eclipse açılıyor ve burada kendi oluşturduğunuz yazım şeklini kullanabiliyorsunuz. Kendi oluşturduğunuz şeklin dışına çıkarsanızda hata alıyorsunuz.

Porjenin Olusturulması

Şimdi Xtext projemizi oluşturalım ve esas işe başlayalım.

  • New->Project->Xtext Project seçip yeni bir proje oluşturma ekranına gelelim.
  • Project Name kısmına org.xtext.bhdrkn.fouroperation yazalım
  • Language kutusu içerisinde, Name kısmına org.xtext.bhdrkn.Fouroperation yazalım. Burada son kısmın büyük harfle başladığına dikkat edin. Bu özelliği atlarsanız, sonradan yeni bir proje oluşturmaya kalktığınızda sorun yaşayabilirsiniz.
  • Yine Language kutusunda Extensions kısmına ise op yazalım. Bu bizim dilimizin dosya uzantısını belirliyor. Nasıl Java, java uzantısını kullanıyorsa, bizim dilimizde .op uzantısını kullanacak.
  • Layout kısmında Create a Generator Project kısmının seçili olduğundan emin olduktan sonra projemizi oluşturuyoruz.

Projemiz toplam üç proje halinde oluşuyor.

  1. org.xtext.bhdrkn.fouroperation, dilin özelliklerini ve nasıl işleyeceğini bu proje altında belirleyeceğiz.
  2. org.xtext.bhdrkn.fouroperation.generator, şimdilik dilimizle ilgili örnekleri bu proje altında gerçekleyeceğiz.
  3. org.xtext.bhdrkn.fouroperation.ui, kullanıcı arayüzü ile ilgili işler yapılıyor diye biliyorum, fakat daha hiç kullanmadığımdan tam bir bilgi veremeyeceğim.

Bu üç projenin ilki Xtext projesi, zaten bunu simgesindende görebilirsiniz. Geri kalan iki projede java projesi olarak gözüküyor. Tüm projeler bir src ve bir src-gen klasörü içeriyor. Şimdiden belirteyim src-gen klasöründe değişiklik yapsanızda kod kendisi bu yaptığınız değişikliklerin üzerine yazacaktır. Ondan değişiklik yapmamanız tavsiye olunur.

Yazım Kurallarının Belirlenmesi

İlk taslağımızı yaptığımızda yazım kurallarını nasıl olacağını üç aşağı beş yukarı belirlemiştik. Şimdi ise o kuralları EBNF kullanarak oluşturmalıyız. Eğer BNF ya da EBNF hakkında hiç bir şey bilmiyorsanız ya da benim gibi önceden az biraz duymuşluğunuz varsa geri dönüp bilgilerinizi tazelemenin ve yeni bilgiler öğrenmenin tam zamanı. Ben yukarıda verdiğim wikipedia linklerinden ve onların referanslarından başlayarak başladım, tabi google’da başlamak için iyi bir yol.

Projemizi oluşturduğumuzda zaten önümüze Fouroperation.xtext isimli bir dosya açılmıştı. Şimdi bu dosya üzerinde değişiklik yapalım. Dosyayı aşağıdaki gibi değiştirelim.


grammar org.xtext.bhdrkn.Fouroperation with org.eclipse.xtext.common.Terminals

import"http://www.eclipse.org/emf/2002/Ecore" as ecore
generate fouroperation "http://www.xtext.org/bhdrkn/Fouroperation"

Operation:
 'operation' name=ID '{'
 (statements+=Statement)* '}';

Statement:
 expression=Expression;

Expression:
 Addition | Minus | Multi | Div;

Addition:
 left=NUMBER '+' right=NUMBER;

Minus:
 left=NUMBER '-' right=NUMBER;

Multi:
 left=NUMBER '*' right=NUMBER;

Div:
 left=NUMBER '/' right=NUMBER;

terminal NUMBER returns ecore::EBigDecimal:
 ('0'..'9')* ('.' ('0'..'9')+)?;

Şimdi bir kaç yerden bahsedelim. Dilimize önce Operation kısmını tanımladık. Yani her .op uzantılı dosyamız operation anahtar kelimesi ile başlamalı. Operation içerisinde en az sıfır ya da daha çok sayıda statement olabilir. Yani bir statement listemiz varmış gibi düşünebilirsiniz. Bu listemiz boş ya da dolu olabilir. Her Operation süslü parantez ile başlamalı ve bitmeli. Her bir Statement‘ımızda bir Expression dan oluşmakta. Expression ise Addition, Minus, Multi ya da Div türünden olabilir. Örneğin bir Addition, left isminde, sayısal değer taşıyan bir alanla ve ardından gelen + işareti ile başlamalı ve right isminde bir sayısal alanla son bulmalı.

Yazım Kurallarını Test Etmek

Yazım kuralını test etmek için, öncelikle otomatik olarak dosyalarımızı oluşturmalıyız. Bunun için 1. yani Xtext projesininin içinden, src/org.xtext.bhdrkn klasörüne gelip, GenerateFouroperation.mwe2 dosyasını çalıştırıyoruz. Bu bize java programlama dilinin anlayacağı biçimde classlarımızı ve daha bir çok şeyi oluşturuyor. Sorunsuz bir biçimde oluşturmamızı tamamladıktan sonra, yine 1. yani Xtext projemize geliyoruz, projemizin üzerinde sağ tıklayıp Eclipse Application olarak çalıştırıyoruz.

Çalıştırmamızın ardından bize yeni bir Eclipse açıyor. Bu Eclipse biraz farklı. Burda yeni boş bir proje yaratıyoruz. Bu projenin içerisine ise yeni bir dosya yaratıyoruz ve dosyanın adını bhdrkn.op yapıyoruz. Şimdi ilk başta oluşturduğumuz taslağı burada tekrardan yazıyoruz. Eğer yanlış bir yazım yaparsanız, Eclipse sizi uyaracaktır. Zaten oluşturduğunuz anahtar kelimeleride renkli bir şekilde göreceksiniz.

Eğer herşey olması gerektiği şekilde gittiyse, bhdrkn.op dosyasını nereye kaydettiğinizi unutmayın. Çünkü ileride bu dosyayı kullanacağız ya da benim gibi ikinci Eclipse’ide kapatmayın, sonradan Eclipse üzerinden kopyalarsınız.

Dosyayı Test Etmek

Eğer benim gibi bişeyler görmeden doğru yaptığını düşünemeyenlerdenseniz, bu yaptıklarımız size yetmeyecektir. Onun için işi bir adım daha öteye götürelim ve oluşturduğumuz bhdrkn.op dosyasından objeler yaratıp, objeleri ekrana basalım. Şimdilik sadece objeleri ekrana basalım, ileride işlemleride gerçekleştireceiğiz. Öncelikle bhdrkn.op dosyasını 2. java projesinin altına kopyalayalım. Aynı zamanda build-path’ına 1. pojeinin yani xtext projesinin yolunu verelim. Bunun için projenin üzerine sağ tıklayıp, build-path seçip, projeyi göstermemiz yeterli.

Şimdi 2. projemize gelelim, .generator ile biten java projesine yani. Burada src klasörünün altında, org.xtext.bhdrkn.fouroperation.factory isminde bir paket oluşturalım. Bu paketin altında ise, .op uzantılı bir dosyadan Operation classını oluşturmada kullanacağımız OperationFactory.java classını oluşturalım.

OperationFactory.java


package org.xtext.bhdrkn.fouroperation.factory;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtext.resource.XtextResourceSet;
import org.xtext.bhdrkn.FouroperationStandaloneSetup;
import org.xtext.bhdrkn.fouroperation.Operation;

public class OperationFactory {

 public static Operation getOperation(String fileName){
 FouroperationStandaloneSetup.doSetup();
 ResourceSet rs = new XtextResourceSet();
 Resource res = rs.getResource(URI.createFileURI(fileName), true);
 return (Operation)res.getContents().get(0);
 }
}

Şimdi ise bu class’ı kullanarak nesnelerimizi oluşturacak ve ekrana basacak olan main fonksiyonu içeren classımızı oluşturalım. Bunun için yine src/ klasörünün altına org.xtext.bhdrkn.fouroperation.firstmain paketini ve onun altınada, FirstMain.java classını oluşturalım.

FirstMain.java


package org.xtext.bhdrkn.fouroperation.firstmain;

import org.xtext.bhdrkn.fouroperation.Expression;
import org.xtext.bhdrkn.fouroperation.Operation;
import org.xtext.bhdrkn.fouroperation.Statement;
import org.xtext.bhdrkn.fouroperation.factory.OperationFactory;

public class FirstMain {

 public static void main(String[] args) {
 String fileName = "bhdrkn.op";
 Operation operation = OperationFactory.getOperation(fileName);
 for (Statement st : operation.getStatements()) {
 Expression exp = st.getExpression();
 System.out.println(exp);

 }
 }
}

Eğer herşeyi doğru olarak yaptıysak, FirstMain.java dosyasını çalıştırdığımızda, aşağıdaki ekran çıktılarını alacağız.

org.xtext.bhdrkn.fouroperation.impl.AdditionImpl@19050a0 (left: 8, right: 4)
org.xtext.bhdrkn.fouroperation.impl.MinusImpl@19d3b3a (left: 14, right: 2)
org.xtext.bhdrkn.fouroperation.impl.MultiImpl@19b808a (left: 3, right: 4)
org.xtext.bhdrkn.fouroperation.impl.DivImpl@140fee (left: 24, right: 2)

Sonra!?

Son olarak bir interpreter class yaratıp, yarattığımız objeleri ilgilendiren işlemleride gerçekleyelim. Yani operation olarak “4+8” girdiysek 12 sonucunu nasıl elde edebileceğimize bakalım. Bunun zannediyorum ki başka yollarıda mevcut, fakat Xtext eklentisiyle birlikte gelen örnekleri incelediğimde benim kullanacağıma benzer bir yol izlemişler. Daha anlaşılır olması açısından, hashTable’lar yerine if..else’ler kullanarak bu işi yapmaya çalıştım. İleride biraz daha geliştirirsem HashTable ile nasıl yapılabileceğinide anlatırım.

Şimdi öncelikle Xtext projemize yani 1. projemize gidiyoruz. Burda src klasörü altında org.xtext.bhdrkn.interpreter isimli bir paket yaratıyoruz. Bu paketin altınada ise, OperationInterpreter.java isimli bir dosya yaratıyoruz.

OperationInterpreter.java


package org.xtext.bhdrkn.interpreter;

import java.math.BigDecimal;

import org.xtext.bhdrkn.fouroperation.Addition;
import org.xtext.bhdrkn.fouroperation.Div;
import org.xtext.bhdrkn.fouroperation.Expression;
import org.xtext.bhdrkn.fouroperation.Minus;
import org.xtext.bhdrkn.fouroperation.Multi;
import org.xtext.bhdrkn.fouroperation.Statement;

public class OperationInterpreter {

 public BigDecimal calcluate(Statement s){
 return invokeFirst(s.getExpression());
 }

 protected BigDecimal invokeFirst(Expression e) {

 if(e instanceof Addition){
 return calculate((Addition)e);
 } else if ( e instanceof Minus){
 return calculate((Minus)e);
 } else if ( e instanceof Multi){
 return calculate((Multi)e);
 } else if ( e instanceof Div){
 return calculate((Div)e);
 }
 return null;
 }

 protected BigDecimal calculate(Addition a){
 return a.getLeft().add(a.getRight());
 }

 protected BigDecimal calculate(Minus m){
 return m.getLeft().subtract(m.getRight());
 }

 protected BigDecimal calculate(Multi m){
 return m.getLeft().multiply(m.getRight());
 }

 protected BigDecimal calculate(Div d){
 return d.getLeft().divide(d.getRight());
 }
}

Bu interpreter classını, başka projelerin içinde kullanmak için aynı proje içerisindeki plugin.xml dosyasına eklememiz gerekiyor. Bunun için plugin.xml dosyasını açıyoruz. Alttaki sekmelerden runtime kısmına gelip, oluşturduğumuz paketi ekliyoruz.

Şimdi bu interpreter classını kullanarak, sonuçları ekrana bastıracak bir main fonksiyonu yazalım. Bunun için 2. projenin yani generator ile biten java porjesinin içinde src klasörünün altına org.xtext.bhdrkn.fouroperation.secondmain isimli bir paket yaratıyoruz. Bu paketin içine SecondMain.java isimli bir dosya yaratıyoruz.

SecondMain.java


package org.xtext.bhdrkn.fouroperation.secondmain;

import org.xtext.bhdrkn.fouroperation.Operation;
import org.xtext.bhdrkn.fouroperation.Statement;
import org.xtext.bhdrkn.fouroperation.factory.OperationFactory;
import org.xtext.bhdrkn.interpreter.OperationInterpreter;

public class SecondMain {
 public static void main(String[] args) {
 String fileName = "bhdrkn.op";
 OperationInterpreter interpreter = new OperationInterpreter();
 Operation operation = OperationFactory.getOperation(fileName);

 for(Statement statement : operation.getStatements()){
 System.out.println("Result: " + interpreter.calcluate(statement));
 }
 }
}

Eğer herşey doğru giderse, çalıştırdığımızda aşağıdakine benzer bir ekran çıktısı elde ediyoruz.

Result: 12
Result: 12
Result: 12
Result: 12

Son

Benim başlarken çok zorluk çektiğim Xtext’e, başlarken zorluk çekmemeniz için oluşturduğum bu basit örneği paylaşmak istedim. Eğer ileride daha iyi bir şekilde yapma yöntemleri bulabilirsem onlarıda en kısa zamanda paylaşırım.

Bu proje üzerinden kendini geliştirmek isteyen olursa, şunu deneyebeilir. Bölme işlemi içerisinde sıfıra bölme için bir önlem alınmış durumda değil. Bunu interpreter içinde değilde dilin kendi içinde nasıl engelleyebileceğine bakabilir.

Ayrıca belirtmeliyim ki junit testlerine benzer unit testlerde kullanabiliyorsunuz. Zaten diğer örnekleri incelediğinizde sizde göreceksiniz bunu.

Kaynaklar

Bu projeyi oluştururken şu kaynaklardan faydalandım.

End Of Line