Generisanje programskog koda

Prof. dr Igor Dejanović (igord at uns ac rs)

Kreirano 2023-12-01 Fri 10:12, pritisni ESC za mapu, m za meni, Ctrl+Shift+F za pretragu

Sadržaj

1. Uvod

1.1. Cilj

  • Imamo kreiran DSL i iskaze na njemu (mograme) i šta sada?
  • Krajnji cilj nam je dobijanje izvršivog softvera na bazi naših mograma.

1.2. Semantika jezika

Pragmatični načini definisanja:

  • Transformacija iskaza jezika na jezik sa već definisanom semantikom (kompajleri/generatori koda).
    • DSL → GPL → asemblerski kôd → mašinski kôd → interpretiranje (na procesoru).
    • DSL → GPL → VM code → interpretiranje (na virtuelnoj mašini).
  • Interpretiranje.
    • DSL → interpretiranje
  • Obično se za strukturalne aspekte sistema koristi generisanje koda dok se interpreteri koriste kod aspekata ponašanja.

2. Generisanje koda

2.1. Generisanje koda (kompajliranje)

Arhitektura-kompajler.svg

2.2. Pristupi u generisanju koda

  • Naivan pristup - kombinovanje fragmenata koda - upotreba komandi print oblika.
  • API bazirano generisanje
  • Inline generisanje
  • Generisanje bazirano na šablonima i obrađivačima šablona - Template Engines.

2.3. Print pristup

  • Generisan kôd se dobija print komandom uz korišćenje spajanja i interpolacije stringova.
  • Koriste se standardni iskazi jezika domaćina za uslove i petlje.
  • Jednostavan pristup ali loše skalira. Kod složenijeg koda koji se generiše čitkost je niskog nivoa.

2.4. print pristup - primer

class Class(object):
    def __init__(self, name, attributes):
        self.name = name
        self.attributes = attributes
        
classes = []
classes.append(Class("Student", 
                [("String", "ime"),
                 ("String", "prezime"),
                 ("String", "brojIndeksa")]))
classes.append(Class("Predmet", 
                [("String", "naziv"),
                 ("String", "nastavnik"),
                 ("int", "ESBP")]))
ime_paketa = "fakultet"
print "package %s;" % ime_paketa
print
for cls in classes:
    print "public class %s {" % cls.name
    print
    for attr in cls.attributes:
        print "    protected %s %s;" % (attr[0], attr[1])
    print
    for attr in cls.attributes:
        print "    public %s get%s(){" % \
                    (attr[0], attr[1].capitalize())
        print "        return %s;" % attr[1]
        print "    }"
        print
        print "    public void set%s(%s %s){" % \
                    (attr[1].capitalize(), attr[0], attr[1])
        print "        this.%s = %s;" % (attr[1], attr[1])
        print "    }"
        print 
    print "}"
    print

2.5. Print pristup - generisani kod

package fakultet;

public class Student {
    protected String ime;
    protected String prezime;
    protected String brojIndeksa;

    public String getIme(){
        return ime;
    }

    public void setIme(String ime){
        this.ime = ime;
    }

    public String getPrezime(){
        return prezime;
    }

    public void setPrezime(String prezime){
        this.prezime = prezime;
    }

    public String getBrojindeksa(){
        return brojIndeksa;
    }

    public void setBrojindeksa(String brojIndeksa){
        this.brojIndeksa = brojIndeksa;
    }
}

2.6. API bazirano generisanje koda

gen_API.svg

2.7. Obrađivači šablona (Template Engines)

  • Za opis generisanog programskog kôda koristi se DSL za zadavanje šablona.
  • Fiksni delovi generisanog koda su definisani bez izmena.
  • Varijabilni delovi su definisani upotrebom posebnih iskaza šablon DSL-a i biće interpretirani od strane obrađivača šablona u vreme generisanja koda.

2.8. Arhitektura

gen_sabloni.svg

2.9. Primer - Jinja/Django

{% for component in components %}
  <component class="struct">
      <type class="char">{{component.type_name}}</type>
      <name class="char">{{component.fqn}}</name>

      <parameters class="struct">
          {% for parameter in component.all_properties %}
          <parameter class="struct">
                  {% if parameter.child_property -%}
                  <parent class="char">parameter.owner.name</parent>
                  {% endif %}
              <name class="char">{{parameter.name}}</name>
              <value class="double">{{parameter}}</value>
          </parameter>
          {% endfor %}
      </parameters>

      <terminals class="struct">
          {% for terminal in component %}
          <terminal class="struct">
              <name class="char">{{terminal.name}}</name>                    
              <type class="char">{{terminal.type}}</type>             
              <node class="char">{{terminal.node}}</node>
          </terminal>
          {% endfor %}
      </terminals>
  </component>
{% endfor %}

2.10. Primer - JSP

<html>
<head><title>First JSP</title></head>
<body>
  <%
    double num = Math.random();
    if (num > 0.95) {
  %>
      <h2>You'll have a luck day!</h2><p>(<%= num %>)</p>
  <%
    } else {
  %>
      <h2>Well, life goes on ... </h2><p>(<%= num %>)</p>
  <%
    }
  %>
  <a href="<%= request.getRequestURI() %>"><h3>Try Again</h3></a>
</body>
</html>

2.11. Primer - PHP

<?php
    $Fname = $_POST["Fname"];
    $Lname = $_POST["Lname"];
?>
<html>
<head>
    <title>Personal INFO</title>
</head>
    <body>
        <form method="post" action="<?php echo $PHP_SELF;?>">
            First Name:<input type="text" size="12" maxlength="12" name="Fname"><br />
            Last Name:<input type="text" size="12" maxlength="36" name="Lname"><br />
        </form>
        <?
        echo "Hello, ".$Fname." ".$Lname.".<br />";
        ?>
    </body>
</html>

2.12. Primer - Ruby ERB

<h1>Listing Books</h1>
 
<table>
  <tr>
    <th>Title</th>
    <th>Summary</th>
    <th></th>
    <th></th>
    <th></th>
  </tr>
 
<% @books.each do |book| %>
  <tr>
    <td><%= book.title %></td>
    <td><%= book.content %></td>
    <td><%= link_to "Show", book %></td>
    <td><%= link_to "Edit", edit_book_path(book) %></td>
    <td><%= link_to "Remove", book, method: :delete, data: { confirm: "Are you sure?" } %></td>
  </tr>
<% end %>
</table>
 
<br />
 
<%= link_to "New book", new_book_path %>

2.13. Primer - Xtend

def compile(Entity e) ''' 
	«IF e.eContainer.fullyQualifiedName != null»
		package «e.eContainer.fullyQualifiedName»;
	«ENDIF»
	
	public class «e.name» «IF e.superType != null
			»extends «e.superType.fullyQualifiedName» «ENDIF»{
		«FOR f:e.features»
			«f.compile»
		«ENDFOR»
		}
	'''

def compile(Feature f) '''
	private «f.type.fullyQualifiedName» «f.name»;
	
	public «f.type.fullyQualifiedName» get«f.name.toFirstUpper»() {
		return «f.name»;
	}
	
	public void set«f.name.toFirstUpper»(«f.type.fullyQualifiedName» «f.name») {
		this.«f.name» = «f.name»;
	}
	'''
}

2.14. Primer - Stratego

stratego.png

2.15. In-line generisanje koda

  • Ciljni jezik poseduje iskaze koji se u vreme kompajliranja zamenjuju drugim konstrukcijama istog jezika.
  • Ova zamena se obavlja najčešće posebnim alatom koji se naziva predprocesor.
  • Predprocesiranje može da se obavlja u više koraka, odnosno možemo imati više predprocesora koji će vršiti ekspanziju koda pre faze kompajliranja.

2.16. Primer - C predprocesor

#include <stdio.h>

#define SLICES 8
#define ADD(x) ( (x) / SLICES )

int main() 
{
    int a = 0, b = 10, c = 6;

    a = ADD(b + c);

    printf("%d\n", a);

    return 0;
}

2.17. U svim slučajevima…

  • …imamo fiksni, nepromenjivi deo programskog koda i varijabilni deo.
  • Šta će u našem slučaju biti izvor za koji definiše varijabilni deo?

2.18. Šta i kako generisati?

  • Truditi se da generisan kod bude čitak. Očuvati nazubljivanje, generisati komentare, u komentarima jasno napisati da je generisani kod u pitanju i na osnovu čega je generisan.
  • Ukoliko DSL dobro pokriva ciljni domen moguće je generisati 100% programskog koda. Ovo je obično slučaj kod zrelih DSL-ova.
  • Često u toku razvoja, i zbog praktičnih razloga, DSL ne pokriva domen u potpunosti. U tom slučaju generiše se deo programskog koda, dok se deo piše ručno na jeziku ciljne platforme.
  • U ovom slučaju moramo rešiti problem integracije ručno pisanog i generisanog koda.

2.19. Ručne izmene u generisanom kodu

  • Ponovno generisanje celih fajlova vs. očuvanje ručnih izmena.
  • Kod očuvanja ručnih izmena u generisanom kodu koriste se tzv. zaštićene sekcije (protected regions). Najčešće posebne oznake unutar komentara.
  • Zaštićene regije komplikuju implementaciju generatora i kontrolu verzija - izbegavati.
  • Kôd koji se uvek generiše potpuno ne čuvati u sistemu za kontrolu verzija bez potrebe - uvek ga možete izgenerisati! Isti je razlog zbog koga ne čuvate Java .class fajlove.

2.20. Generisani kôd i kontrola verzija

  • Kod koji se uvek generiše potpuno ne čuvati u sistemu za kontrolu verzija bez potrebe - uvek ga možete izgenerisati!
  • Razlog je isti zbog kod ne čuvate npr. Java .class fajlove.
  • … osim u slučaju kada VCS koristite za integraciju ručno pisanog koda.

2.21. Integracija generisanog i ručno pisanog koda

  • Najčešće se koriste mogućnosti ciljnog programskog jezika.
  • Na primer, ukoliko je u pitanju OO jezik možemo koristiti nasleđivanje ili ukoliko jezik podržava parcijalne klase (npr. .NET).

2.22. Obrasci za integraciju kod OO jezika - 1

Generisani kôd poziva ručno pisani.

integracija_1.svg

2.23. Obrasci za integraciju kod OO jezika - 2

Ručno pisani kôd poziva generisani.

integracija_2.svg

2.24. Obrasci za integraciju kod OO jezika - 3

Generisani kôd nasleđuje apstraktne klase ili implementira interfejse (ručno pisane). Ručno pisani kod poziva generisani preko interfejsa ili apstraktnih klasa.

integracija_3.svg

2.25. Obrasci za integraciju kod OO jezika - 4

Ručno pisani kôd nasleđuje i redefiniše generisani.

integracija_4.svg

2.26. Obrasci za integraciju kod OO jezika - 5

Generisani kôd nasleđuje i poziva ručno pisani. Na primer, implementacija apstraktnih metoda u kojoj se koriste pozivi konkretnih metoda.

integracija_5.svg

2.27. Obrasci za integraciju kod OO jezika - 6

Generisani kôd nasleđuje ručno pisani i definiše apstraktne metode. Ručno pisani kôd poziva konkretne metode kao i apstraktne metode koje definiše generisani kod. Implementacija Template Method dizajn obrasca.

integracija_6.svg

2.28. Integracija upotrebom VCS

vcs-integracija.png

3. Interpretiranje

3.1. Interpretiranje

  • Interpreter je softver koji čita mogram i dinamički ga evaluira u vreme izvršavanja (runtime).
  • Nema generisanja koda.
  • Brži round-trip u razvoju ali sporije izvršavanje.
  • Često teže debagovanje ukoliko ne postoje specijalizovani debageri.

3.2. Arhitektura pristupa

Arhitektura-interpreter.svg

3.3. Primer - Arpeggio i textX

  • Arpeggio textX su parseri koji vrši interpretiranje gramatike jezika.
  • Parser se ne generiše već se arpeggio konfiguriše gramatikom jezika i posle toga je spreman da parsira proizvoljne iskaze na definisanom jeziku.

4. Finalne napomene

4.1. Vreme analize modela - generatori vs. interpreteri

  • Analiza modela se kod generatora obavlja u vreme izgradnje dok se kod interpretera obavlja u vreme izvršavanja.
  • Postoje interpreteri koji će pre startovanja mograma obaviti analizu u cilju otkrivanja čestih grešaka.
  • Određeni interpreteri vrše validaciju mograma i zatim ga transformišu u efikasan međuformat (npr. bytecode za Java virtualnu mašinu).
  • Takođe, određeni generatori vrše generisanje koda u vreme izvršavanja (npr. JIT kompajleri).

4.2. Generisanje ili interpretacija?

  • Na ovo pitanje ne postoji opšti odgovor.
  • Zavisi od jezika, domena, zahteva u razvoju kao i zahtevanih nefunkcioanlnih osobina.
  • Često se u praksi ova dva pristupa kombinuju.

5. Literatura

  • Igor Dejanović, Jezici specifični za domen, Fakultet tehničkih nauka, Novi Sad, 2021. (dostupno u skriptarnici FTN-a)