metaprogramazioa

Zer da metaprogramatzea? hemen azalpen luze eta zabala. Laburbilduz: normalean, programa batek datuak prozesatzen ditu eta horren emaitza itzultzen du. Metaprogramazioan, programa batek bertze programa bat (baita bere ‘burua’ ere) manipulatzeko gaitasuna du.

Macro

Macro hitzak erranahi anitz izan ditzake:

  • C/C++ lengoaian macro bat funtzio baten ‘alias’ bat baino ez da. Hau da, konpilazio garaian ‘alias’ hori ageri den lekuan funtzioa txertatuko da.
  • Microsoft Excel programan, bertako datuak manipulatzeko programa bat da.

Haxe lengoaian, konpilazio garaian exekutatzen diren programak dira. Haxe lengoaia bera erabiliz nahi duguna egiteko. Macroek izugarrizko boterea dute, hauek dira egiten ahal diren gauzen adibide batzuk:

  • Kodea sortu/aldatu/ezabatu (klaseak, funtzioak, aldagaiak… edozer gauza).
  • Helburu kodean konpilazio uneko informazioa gorde (Git mezua, data, ordenagailuaren informazioa…).
  • Lengoaian berez existitzen ez diren adierazpenak onartu. Konpilazio garaian interpretatu eta lengoaiak berez onartzen dituen modura itzultzeko.
  • Helburu kodean kanpoko fitxategietako datuak txertatu. Konfigurazio fitxategi handiak, adibidez.
  • Konpilazio garaiko enkriptazioa.
  • SQL, XML, JSON edo bertze edozein sintaxi konpilazio garaian egiaztatu (errore zehatzak identifikatuz)

Irudi honetan ikus daiteke konpilazioko zein fasetan exekutatzen diren macroak:

Hiru macro mota daude Haxen: Hasieratze macroak (initialization macros), adierazpen macroak (expression macros) eta eraikitze macroak (build macros).

Hasieratze macroak (‘initialization macros’)

Programa konpilatzeko garaian exekutatuko diren funtzioak dira. Adibidez, helburu plataformaren arabera (Windows, Mac, web, Android…) dagozkien fitxategiak (irudi, audio…) karpeta batetik bertzera mugitzea eta tamainak automatikoki aldatzea edo dena delakoa. Funtzio honetan zein plataformarako konpilatzen ari garen detektatu eta horren araberako exekuzioak burutuko dira.

1
--macro Macroak.fitxategiak_kopiatu()

Adierazpen macroak (‘expression macros’)

Macro hauek C lengoaiako #define edo macro preprozesatzaileak bezala funtzionatzen dute. Funtzio itxura dute, baino macro hitzarekin definituko dira: static public macro function sortze_data(). Funtzio hauek konpilazio garaian exekutatu eta une horretan lortutako informazioa txertatuko dute deitu zaien puntuan. Bukaerako kodean funtzioaren arrastorik utzi gabe. Adierazpenak kontsumitu eta adierazpenak itzultzen dituzte.

Honen erabilpen praktiko bat programaren konpilazioa noiz izan zen edo une horretan Git-eko azken commit mezua zein zen txertatzeko balio du. Adibidez,

1
2
3
4
5
6
7
8
9
10
11
12
13
class Macroak {
macro public static function sortze_data():ExprOf<Date> {
// konpilazio garaiko data hartu
var date:Date = Date.now();

// konpilazio garaiko dataren datuak erabili
// exekuzio garaiko adierazpena osatzeko
return macro new Date(
$v{date.getFullYear()}, $v{date.getMonth()}, $v{date.getDay()},
$v{date.getHours()}, $v{date.getMinutes()}, $v{date.getSeconds()}
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
class Main  {
static public function main() {
var noiz_sortua = Macroak.sortze_data(); // new Date(2018, 05, 05, 17, 32, 14); agertuko da konpilatutako kodean

var orain = Date.now();

trace('programaren sorrera data: ' + noiz_sortua);
trace('oraingo data: ' + orain);

trace('programa honen adina ' + ((orain.getTime() - noiz_sortua.getTime())/1000) + ' segundukoa da');
}
}

Kode honen bidez sortutako JS kodea horrelako zerbait izanen da.

1
2
3
4
5
6
7
8
9
...
Test.main = function() {
var noiz_sortua = new Date(2018,5,5,17,32,14);
var orain = new Date();
console.log("programaren sorrera data: " + Std.string(noiz_sortua));
console.log("oraingo data: " + Std.string(orain));
console.log("programa honen adina " + (orain.getTime() - noiz_sortua.getTime()) / 1000 + " segundukoa da");
};
...

Eraikitze macroak (‘build macros’)

Demagun HTML fitxategi bateko id guztiak txukun-txukun izan nahi ditugula klase batean eskuragarri, kode osaketarekin guztiak erraz atzitzeko. Id guztiak eskuz kopiatu eta Id izeneko klase batean aldagai pila bat sortzen ahal ditugu. Zer gertatuko da HTML fitxategia aldatzen badugu? id guztiak berriro kopiatu behar ditugu? Ezin da hori nola edo hala automatizatu? Hemen sartzen dira eraikitze macroak eta magia beltza!!

Macro mota hau konpilazioan programako elementuak (klaseak, interfazeak, enum…) eraikitzen diren unean exekutatuko da, sintaxi zuhaitz abstrakua (Abstract Syntax Tree edo AST) definitzean. Honela egiten zaio dei macro mota honi:

1
2
3
4
@:build(Macroak.eraiki_id('index.html'))
class Id {
var lehendik_dagoen_aldagaia:String = 'hemen nago!';
}

Haxek kode osaketa eskeintzen duela aipatu dut. Kode osaketa hori ahalbidetzeko, kodea idazten ari garela ( edo . sakatzean, konpilatzailea martxan jarri eta funtzioak (( idaztean) behar dituen parametroak edo aldagaiak (. idaztean) eskeintzen dituen funtzio edota propietateak itzultzen ditu, hurrenez hurren.

Goiko adibideari jarraiki, Id. idaztean (gogoratu . idaztean konpilatzailea martxan hasiko dela), Macroak.eraiki_id funtzioari deituko zaio. Funtzio horren bidez:

  • index.html fitxategia irakurri
  • fitxategian ageri diren id guztiak identifikatu eta gorde
  • array horretako id bakoitzeko, Id klasean String motako propietate bat gehituko dugu, hemen kodea:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// funtzio honek 'Id' klaseak izanen dituen eremuak (Field) itzuliko ditu array batean
static public function build_id(html_path:String = 'index.html'):Array<Field> {

// 'Id' klaseak dituen eremuak lortu (propietateak, funtzioak...)
var fields:Array<Field> = haxe.macro.Context.getBuildFields();

// HTML fitxategiaren edukia lortu
var content:String = sys.io.File.getContent(haxe.macro.Context.resolvePath(html_path));
// HTMLko elementuen id="xxx" balioak lortzeko adierazpen erregularra definitu
var id_ereg = ~/id=["']([A-Za-z0-9_]+)["']/;
var ids = [], id;
// HTML fitxategiaren edukian adierazpen erregularrak emaitzak ematen dituen bitartean
while( id_ereg.match(content) ) {
// lortutako ID balioa array batean gorde
id = id_ereg.matched(1);
ids.remove(id);
ids.push(id);
// HTML edukian aitzinera jarraitu
content = id_ereg.matchedRight();
}

var val;
// Konpilatzailea une honetan dagoen posizioa
var pos = haxe.macro.Context.currentPos();
for (id in ids) {
// ID bakoitzari '#' zeinua jarriko diogu hasieran, jQuery-ren selektorea lortzeko (adibidez: #nire_id)
val = '#'+id;
// Id klaseak lehendik zituen eremuei berria gehitu
fields.push({
name : id, // eremuaren izena. Kasu honetan: IDa bera
access : [AStatic, APublic, AInline], // eremuaren atzitzea. Kasu honetan: 'static public inline'
pos : pos, //konpilatzailearen uneko posizioa
kind : FVar(macro :String, macro $v{val}) // eremu mota. Kasu honetan: aldagaia -> 'FVar(type, value)'
// - type: aldagaiaren mota. Kasu honetan: 'String'
// - value: aldagaiaren balioa. Kasu honetan: lehendik definitutako 'val' ('#' + id)
});
}

// klasearen eremuak itzuli
return fields;
}

Modu honetan, HTML fitxategiko identifikadoren bakoitzeko Id klasean aldagai bat sortu dugu, horrela geldituko litzateke klasea:

1
2
3
4
5
6
class Id {
var lehendik_dagoen_aldagaia:String = 'hemen nago!';
static public inline var nire_id:String = "#nire_id";
static public inline var eszena:String = "#eszena";
...
}

Honela, identifikatzaile guztiak automatikoki agertuko zaizkigu, ez ditugu gogoratu beharko eta idazterakoan ez dugu hutsik eginen. Gainera, kodean erreferentziatzen dugun HTML-ko idren bat aldatzen bada, konpilazioan errorea emanen digu, Id klasean ez delako egonen lehendik dei egin diogun id hori.

Hemen erabileraren adibide bat:

1
2
3
4
5
6
7
8
9
// JavaScript-eko 'jQuery' edo '$'-ren baliokidea da 'J'
import js.jquery.Helper.J;

class Main {
static public function main() {
// $('#nire_id').addClass('ezkutua');
J(Id.nire_id).addClass('ezkutua');
}
}

Garapen pixko bat gehiagorekin, klaseak eta etikeak ere modu honetan parseatu ditut eta eduki mota abstraktuen bidez, jQuery deiak errazago egin ditut.

Hemen kodea eta funtzionamenduaren GIF bat: