0% au considerat acest document util (0 voturi)
3 vizualizări19 pagini

4 Metaprogramare

Metaprogramarea este un set de tehnici care permite generarea și modificarea programelor pe baza altor programe sau descrieri. Aceasta include automatizarea sarcinilor repetitive, generarea de cod pentru diverse aplicații și modificarea codului în timpul etapelor de compilare și execuție. Reflexia permite programelor să obțină informații despre ele însele și să genereze cod dinamic în timpul execuției.

Încărcat de

falastinazzam
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
3 vizualizări19 pagini

4 Metaprogramare

Metaprogramarea este un set de tehnici care permite generarea și modificarea programelor pe baza altor programe sau descrieri. Aceasta include automatizarea sarcinilor repetitive, generarea de cod pentru diverse aplicații și modificarea codului în timpul etapelor de compilare și execuție. Reflexia permite programelor să obțină informații despre ele însele și să genereze cod dinamic în timpul execuției.

Încărcat de

falastinazzam
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

TEHNICI AVANSATE DE COMPILARE

4. METAPROGRAMARE

© Răzvan ACIU
Concepte
• Metaprogramare – un ansamblu de tehnici și algoritmi care folosesc ca date de
intrare programe, componente ale lor sau descrieri pentru a se genera sau
modifica pe baza lor alte programe, date sau algoritmi
• Prin intermediul metaprogramării se pot automatiza multe sarcini repetitive de
generare, modificare sau instrumentare de cod:
• Generarea unor structuri de date și algoritmi care diferă doar prin unele
tipuri (template)
• Construcții care nu pot fi implementate prin funcții (ex: pentru debugging)
• Generare de cod pentru serializare, interfațarea cu baze de date (ORM),
template engine, expresii regulate, evaluare de performanță (profiling) etc.
-- Bază de date SQL // Clasă corespunzătoare unei înregistrări PHP
CREATE TABLE Persons ( class Person {
PersonID int, public $PersonID, $FirstName, $LastName, $Address;
FirstName varchar(255),
ORM
LastName varchar(255), Object Relational Mapping // poate să execute o comandă SQL UPDATE
Address varchar(255) function set_FirstName($name) {...}
); }
Etapa de compilare (compile time) și cea de execuție (run time)
• Etapa de compilare (compile time) - perioada în care este activ compilatorul
programului (analiza lexicală, semantică, …), iar programul în sine nu este activ
• Etapa de execuție (run time) – perioada în care progamul este activ, după ce
compilatorul l-a generat și și-a încheiat activitatea
• Unele LP permit ca în timpul execuției programului, acesta să fie modificat din
interiorul aplicației (ex: să se adauge clase sau metode noi). Această facilitate
presupune existența unui compilator care să fie disponibil (apelabil) din
interiorul aplicației (CaaS – Compiler as a Service) și este diferită de încărcarea
de pe disc a unor biblioteci dinamice (.dll / .so) care sunt gata compilate.
• În cazul CaaS pot să apară etape de compilare în interiorul etapei de execuție,
atunci când compilatorul intern este activ

const str="function add(x, y){ return x+y; }"; JavaScript


eval(str); // compilează la execuție șirul str (care trebuie să conțină cod)
[Link](add(1, 2)); // apelează funcția add care a fost compilată de eval
Modificarea/generarea codului în timpul etapei de compilare
• În timpul etapei de compilare există mai multe posibilități de modificare a
codului sau de generare automată a acestuia:
• Folosirea mai multor etape de compilare, fiecare etapă putând modifica
codul care va fi furnizat următoarei etape (ex: preprocesorul C)
• Macrouri
• Template
• Specificarea explicită ca unele construcții de limbaj să fie executate la
compilare
• Folosirea unor unelte externe
• Compilare dirijată de metadata
Folosirea mai multor etape de compilare (ex: preprocesorul C)
• În fiecare etapă de compilare se tratează anumite construcții de limbaj (ex:
directivele de preprocesor) care pot modifica sau genera cod
• Pot exista diverse operații de modificare a codului: includere de cod din alte
fișiere (#include), compilare condiționată (#if), modificarea felului în care se
realizează compilarea (#pragma) etc.
• Codul obținut într-o etapă va fi folosit ca intrare pentru etapa următoare

void *loadDynLib(const char *fileName){ C


#ifdef WIN32
return LoadLibrary(fileName);
#else
return dlopen(fileName, RTLD_NOW | RTLD_GLOBAL);
#endif
}
Macrouri
• Macrourile sunt construcții de limbaj care se apelează la fel ca funcțiile, dar care
prezintă unele diferențe fundamentale față de funcții:
• Se execută în timpul etapei de compilare
• Apelul macroului se înlocuiește în cod cu valoarea returnată de acesta
• Argumentele nu se evaluează înainte de apel ci se transmit nemodificate
• Macrourile au fost făcute celebre de către limbajul Lisp, care și la ora actuală are
cele mai puternice facilități pentru definirea și utilizarea macrourilor

#define STRINGIFY0(val) # val C++


#define STRINGIFY(val) STRINGIFY0(val) // pune ghilimele în jurul unei valori care nu e argument
#define GUARD(cond) if(cond)throw runtime_error("error in function " __FUNCTION__ ", line "
STRINGIFY(__LINE__) ": " #cond)

void f(int n){


GUARD(n<0); // if(n<0)throw runtime_error("error in function " "f" ", line " "10" ": " "n<0")
}
Template template<typename T, size_t N> C++
class Array{
• O modalitate de a particulariza simboluri T data[N];
(funcții, tipuri de date etc.), parametrizând public:
T &operator[](size_t i){return data[i];}
definițiile acestora cu parametri care vor fi template<typename Dst>
folosiți la compilare Array<Dst,N> convert(){
Array<Dst,N> r;
• Pentru fiecare set distinct de parametri se va for(size_t i=0;i<N;i++){
crea o nouă definiție a simbolului, înlocuind în r[i]=(Dst)data[i];
noua definiție aparițiile parametrilor cu valorile }
return r;
lor curente }
• Unele LP acceptă ca parametri template și };
constante, nu doar tipuri de date int main() {
Array<float, 10> a1;
Array<string, 5> a2;
auto a3=[Link]<long>();
auto a4=[Link]<char>();
return 0;
}
Specificarea explicită ca unele construcții să fie executate la compilare
• Unele LP permit să se specifice faptul că template<typename T> C++
unele construcții de limbaj (ex: apeluri de size_t len(T str){
funcții, expresii, if etc.) trebuie să fie // la compilare se determină care este ramura
care se execută și se compilează doar ea, deci
executate la compilare nu rezultă erori care ar apărea dacă s-ar
• Aceasta este posibil doar dacă expresiile compila și cealaltă ramură (ex: strlen(string) )
implicate sunt constante care se știu încă de if constexpr ( typeid(T)==typeid(char *)
|| typeid(T)==typeid(const char *) ){
la compilare (ex: nu se pot citi date dintr-un return strlen(str);
fișier sau de la tastatură) }else{
• Este o variantă mai bună decât folosirea mai return [Link]();
}
multor LP în diverse faze de compilare (ex: }
preprocesor & C), deoarece în cadrul unui int main() {
LP se pot accesa mai multe facilități ale cout<<len((char*)"salut")<<'\n';
string str{"Salut"};
acelui LP, decât prin comunicarea între două cout<<len(str)<<'\n';
LP return 0;
}
Folosirea unor unelte externe
În codul sursă se inserează elemente care vor fi procesate înainte de compilarea
propriu-zisă, folosind alte unelte software. În urma procesării vor rezulta alte fișiere
care conțin codul propriu zis. /**
* @brief A point in plane
// re2c $INPUT -o $OUTPUT -i * @details The type of the coordinates can be set
--case-ranges using the template argument
// Qt moc - Meta-Object Compiler */
int lex(const char *s) {
#include <QObject> template<typename T>
const char *YYCURSOR = s;
class Counter : public QObject{ struct Point {
/*!re2c
Q_OBJECT T x, y;
re2c:yyfill:enable = 0;
public: /**
re2c:define:YYCTYPE = char;
Counter() { m_value = 0; } * @brief Computes the distance to another
int value() const { return m_value; } point
[1-9][0-9]* { return 0; }
public slots: * @param p The point to which the distance is
* { return 1; }
void setValue(int value); calculated
*/
signals: * @return The distance to p
}
void valueChanged(int newValue); */
private: T distance(Point<T> p){
int main() {
int m_value; T dx=x-p.x, dy=y-p.y;
assert(lex("1234") == 0);
}; return (T)sqrt(dx*dx+dy*dy);
return 0;
} }
};
Metadata
• Informații care se atașează construcțiilor de limbaj, în special simbolurilor
• Descriu aspecte care nu se pot specifica în LP propriu zis, cum ar fi intenția
folosirii, opțiuni, clasificări, modalități de utilizare, interfațare cu alte aplicații etc.

// Java - annotations
public class Mammal {
public String name, description;
public int age;
}
// PHP - atributes // C# - attributes
#[Route('GET', '/info')] [TableMapping("People")] interface MyQueries extends BaseQuery {
class InfoController implements Handler{ public class Person{ @Select("select name, description,
public function handle(): Response{ [ColumnMapping("fname")] age from mammal")
return new Response([ public string FirstName {get; set;} DataSet<Mammal> getAllMammals();
'page' => 'info'
]); [ColumnMapping("lname")] @Update("delete from mammal")
} public string LastName {get; set;} int deleteAllMammals();
} } }
Compilare dirijată de metadata
• Unele metadata se adresează compilatorului și pot modifica felul în care se
realizează compilarea
• Prin această metodă devine posibil să se îmbogățească un LP fără a fi necesar să se
creeze o sintaxă sau cuvinte cheie noi. De exemplu în Java adnotarea @Override s-
a adăugat ulterior, fără a mai fi necesară adăugarea unui cuvânt cheie.
// Conditional - compilează metoda doar dacă simbolul este definit C#
// Dacă simbolul nu este definit, apelurile metodei nu se iau în considerare, ca și cum nu ar exista
// Un simbol poate fi definit în linia de comandă a compilatorului sau cu #define
[Conditional("DEBUG")]
void dbg(string text, params object[] args){
[Link]("debug: "+text+": " + [Link](',', args));
}

int add(int x, int y){


dbg("add args",x,y);
return x + y;
}
Modificarea/generarea codului în timpul etapei de execuție
• În timpul etapei de execuție modificarea sau generarea codului se bazează pe
următoarele aspecte:
• Reflexie (introspecție) – capacitatea unui program de a afla informații
despre el însuși
• Generare de cod la execuție – programul are posibilitatea ca în timpul
execuției să genereze cod care va fi executat în cadrul său
• Operarea directă în codul binar al aplicației
Reflexie (introspectie)
• Capacitatea unui program de a afla în timpul execuției informații despre el însuși
• Implică să se păstreze în codul aplicației informații despre simboluri, cod etc.
• De obicei se realizează cu un set de funcții care:
• Enumeră informațiile existente în cod
• Permite accesarea definiției unui tip de date, cu tot ce conține ea
• Acționează asupra tipurilor ca și când ar fi date. De exemplu, poate crea
indirect o instanță, fără new, având la dispoziție o definiție de tip
• Poate încărca alte module în aplicație
Exemple de folosire a reflexiei
• O aplicație web poate folosi reflexia pentru a căuta și #[Route('GET', '/info')] PHP
memora toate clasele care implementează rute web class InfoController implements Handler{
public function handle(): Response{
• Când apare o cerere web, se selectează clasa care return new Response([
implementează ruta cerută și se apelează o metodă 'page' => 'info'
predefinită a ei cu argumentele din cerere ]);
• Metoda va returna răspunsul (pagină web, imagine etc.) }
}

public class Article{ C#


[XmlAttribute(DataType = "date")]
public DateTime Posted { get; set; } • O clasă poate folosi atribute specifice pentru specificarea
public string Title { get; set; } felului în care se face serializarea (salvarea și încărcarea) ei
public string Content{ get; set; } • De exemplu, un anumit câmp se poate salva ca atribut XML
} (< … nume="valoare" …>) sau ca și conținut
<Article Posted="2025-11-14"> (<nume>valoare</nume>).
<Title>C#</Title> • Când se salvează date, se interoghează prin reflexie tipul
<Content>C# is a programming fiecărui element și se serializează în mod corespunzător
language.</Content>
</Article>
Reflexie - exemplu de enumerare și accesarea proprietăților
using [Link]; // API-ul pentru reflexie C#
...
// un atribut este o instanță a unei clase (ex: ObsoleteAttribute) și poate avea constructor cu argumente
[ObsoleteAttribute("please use Renderer2, which is more optimized.")]
class Renderer{ }
...
// Program - numele unei clase oarecare din aplicație
Assembly a=typeof(Program).Assembly; // Assembly - toată aplicația
foreach (Type type in [Link]()){ // enumeră toate tipurile definite în aplicație
if ([Link]){
// [Link] - toate atributele lui type
// caută dacă există un atribut având tipul specificat
var obsolete=[Link](attr => [Link]==typeof(ObsoleteAttribute));
if (obsolete != null){
// ConstructorArguments - array cu argumentele poziționale ale constructorului
[Link]("obsolete class " + [Link] + ": " + [Link][0]);
}
}
}
Creare indirectă de obiecte
• Funcțiile de reflexie permit crearea de obiecte pornind de la tipul acestora
• Dacă se știe numele unei clase se poate căuta tipul ei, iar apoi se creează o
instanță de acest tip
• Exemplu de folosire: un fișier conține salvate obiecte din program (ex: <Point
X="1" Y="2"/> <Circle X="-7" Y="3" R="5"/>). La citirea din fișier se obțin
numele obiectelor, iar apoi se instanțiază obiectele corespunzătoare. Ulterior va
fi necesar să se seteze proprietățile obiectelor nou create cu datele din fișier.
object NewObject(string className){ C#
// className trebuie să fie complet, inclusiv namspace-urile (ex: [Link])
// alternativ, dacă este dat doar numele clasei, se poate căuta în Assembly clasa și să se obțină numele ei complet
Type? type = [Link](className);
if (type != null){
// este necesar ca clasa să aibă un constructor fără parametri
object? obj = [Link](type);
if (obj != null) return obj;
else throw new Exception("cannot create instance of class " + className);
}else throw new Exception("unknown class: " + className);
}
Apel indirect de membri public class Point { C#
public int X{ get; set; }
• Funcțiile de reflexie permit apelul public int Y{ get; set; }
indirect al unui membru al unui tip public override string ToString() {
• Este nevoie de obiectul deținător, return "("+X+", "+Y+")";
}
numele membrului și lista de }
argumente pentru apel ...
• În acest fel se pot apela membri ai object pt = NewObject("[Link]");
căror tipuri nu se știu la compilare Type type = [Link]();
// apelează un membru (metodă, proprietate etc.)
• Exemplu: Pentru fișierul din exemplul al unui obiect (ex: pt) de tipul "type", folosind lista
anterior, după ce se instanțiază un de argumente dată
obiect (cu NewObject), se vor seta // lista de argumente are elementele de tipul
"object", deci se pot pune în ea orice instanțe (ex:
pentru el proprietățile sale specifice
int). La apelul membrului dorit instanțele vor fi
care sunt citite din fișier (ex: X și Y convertite din "object" în tipul necesar.
pentru Point). [Link]("X", [Link]
, null, pt, new object[] { 108 });
[Link](pt); // (108, 0)
Generare de cod la execuție
• Programul are posibilitatea ca în timpul execuției să genereze cod și să-l execute
în cadrul său
• Generarea de cod poate fi făcută folosind compilatorul propriu limbajului
respectiv (CaaS – Compiler as a Service, ex: API-ul
[Link] pentru C#), folosind un alt compilator pentru o
problemă specifică (DSL – Domain Specific Language) sau folosind reprezentări
de nivel scăzut, cum ar fi cod intermediar (ex: LLVM) sau asambloare (ex: BCEL –
asamblor de cod binar pentru JVM)
• Exemple de folosire:
• Generare de cod pentru optimizare de viteză (ex: compilarea expresiilor
regulate)
• Execuția unor fragmente de cod cum ar fi expresiile aritmetice și macrourile
într-o tabelă de calcul (ex: Excel)
Operarea directă în codul binar al aplicației
• Programul are posibilitatea să-și modifice propriul cod în timp ce acesta se
execută
• Se folosește în general pentru actualizarea aplicației fără oprirea ei din execuție
(hotpatching)
• În general se înlocuiește direct în memorie o bibliotecă dinamică (.dll / .so) cu o
versiune actualizată a sa
• Avantajul major este că în acest fel aplicația nu trebuie oprită pentru actualizare
și nici nu mai este nevoie de resetarea sistemului
• Ca domenii de folosire sunt aplicațiile sau sistemele care trebuie să ruleze cât
mai mult timp fără oprire: servere (web, baze de date, streaming etc.), sisteme
de monitorizare, computere de bord (pentru sateliți) etc.

S-ar putea să vă placă și