RSS

Ada naar JavaScript vertalen met A2JS

Als webprogrammeur heb ik aan de voorkant vaak te maken met JavaScript. Logisch, want het is de enige “high level” programmeertaal die zit ingebakken in elke moderne webbrowser. Dit zou niet erg zijn, ware het niet dat JavaScript als taal wat problematisch is. Toen hij in de jaren negentig werd ontwikkeld was dit een beetje een haastklus en de gevolgen daarvan zijn nog steeds terug te vinden. Om dit probleem te ondervangen zijn er een hoop manieren uitgevonden om andere talen naar JavaScript te compileren (of eigenlijk transpileren :-). Het is ook mogelijk om Ada naar JavaScript te compileren door middel van het A2JS gereedschap dat onderdeel is van het Matreshka project.

Moderne webapplicaties worden steeds complexer. Programmeurs hebben daarbij de voorkeur om zoveel mogelijk werk te verrichten in de webbrowser zelf. Waar voorheen de server simpelweg een HTML-pagina genereerde, is het nu vaak de browser die aan de hand van vele honderden kilobytes aan scripts dynamischerwijs het scherm opbouwt. De voornaamste taal om al dit browserwerk mee te verrichten is JavaScript. JS is een dynamische programmeertaal die is opgebouwd rond de de ECMAScript specificatie. JS is een vrij ongedwongen taaltje. Je hoeft geen types aan te geven, er wordt ook geen programmeerparadigma afgedwongen en er is geen standaard modulesysteem. De prijs voor al deze vrijheid betaal je in de valuta van programmeerdiscipline en dat is ook gelijk de kern van het probleem met deze taal. Er staat je weinig in de weg om hele geniepig foutieve code te schrijven, dus moet je heel secuur werken als je grotere JS-projecten tot een goed eind wil brengen. Dit wordt tegenwoordig wel gemakkelijker met allerhande hippe statische analysetools als Flow en typechecks aan de hand van JSDoc en Typescript, maar toch voelt het allemaal wat verminderd ergonomisch als je zorgeloos wil doorwerken.

Om het leven als webprogrammeur wat draaglijker te maken zijn er in de loop der jaren een hoop talen uitgevonden die gecompileerd kunnen worden naar JS. Dit stelt de coder in staat om in een andere omgeving haar werk te doen, maar toch hippe dynamische webapps te bouwen. Ik heb veel van deze talen gebruikt en o.a projecten gebouwd in CoffeeScript, TypeScript, Dart, PureScript en Fay. Al deze talen bieden een manier om de intrinsieke zwaktes van JS te compenseren en welke taal beter dan alle andere om zwaktes intrinsiek of extrensiek te overwinnen dan Ada de verkwikkelijke?

De mensen van het Matreshka project zijn zo vriendelijk geweest om een eigen Ada naar Javascript compiler te ontwikkelen: A2JS. Ze maken hiervoor gebruik van ASIS en dat is erg slim, want daardoor kunnen ze volledig leunen op de krachtige GNAT compiler om het zware werk te doen. ASIS is een systeempje dat inhaakt op GNAT en allerhande informatie rapporteert over Ada-code. Op deze manier kun je een complete syntaxboom genereren en via die route gebruiken in allerhande statische analysetools of in het geval van A2JS in een vertaler.

De online documentatie over A2JS is een tikje sumier en het blijkt dat A2JS niet zozeer een losse tool is, als wel een onderdeel van het complete Matreshka project. Matreshka kun je gebruiken als framework voor Ada-applicaties en lijkt zich voor een groot deel te richten op webapplicaties. De beste manier om te kijken hoe Matreshka werkt is dus om de code te downloaden en alles handmatig door te ploegen.

Matreshka is vrij netjes opgebouwd en de code voor A2JS is redelijk separaat. Er zit gelukkig een test suite bij en door die te bekijken kon ik snel zien hoe je A2JS in de praktijk kunt gebruiken. Ik heb een eenvoudige “hello world” webapplicatie gemaakt en de code daarvan vind je op mijn Sourcehut.

Om A2JS te compileren moet je eerst een werkende ASIS-installatie hebben. ASIS is een tikje gevoelig, omdat hij qua versie precies overeen moet komen met je installatie van GNAT. De gemakkelijkste manier om beide versies te laten matchen is door ze gezamenlijk te installeren, bijvoorbeeld vanuit de repository van je Linux-distributie. Ze los bij elkaar zoeken is wat meer werk en leidt niet per se tot verhoogde levensvreugde.

Zodra je A2JS goed geïnstalleerd hebt kun je JavaScript als compileerdoel selecteren in je gpr-bestand:

with "matreshka_league";

project Test is
   Work_Dir := external ("WORK_DIR");
   for Target use "javascript";

   for Object_Dir use Work_Dir & "/.objs";
   for Source_Dirs use ("src");
end Test;

Zolang je je in je project houdt aan de Ada-subset die ondersteund wordt door A2JS zal hij bij het compilen een hele verzameling js-bestanden aanmaken. Elk onderdeel wordt zijn eigen bestand en via RequireJS kan alles in samenhang worden aangeroepen in een Node- of browseromgeving.

Dit alles werkt eigenlijk vrij goed. GNAT is een geweldige compiler en eventuele Ada-gerelateerde issues zal hij zoals gewoonlijk oppikken. A2JS maakt weinig rare sprongen om je code naar JavaScript om te zetten en de manier waarop het dit doet is vrij goed te volgen in de broncode. De verschillende taalonderdelen van Ada zijn allemaal in hun eigen bestand ondergebracht en de vertalingen zijn vrij eenvoudig.

Hier heb je bijvoorbeeld de inhoud van de functie die een while-statement omzet naar JS:

Text.Append ("while (");
Down := Engine.Text.Get_Property (Cond, Name);
Text.Append (Down);
Text.Append ("){");

Down := Engine.Text.Get_Property
(List  => List,
 Name  => Name,
 Empty => League.Strings.Empty_Universal_String,
 Sum   => Properties.Tools.Join'Access);

Text.Append (Down);

Text.Append ("};");

return Text;

De combinatie van de ASIS-statements en de no-nonsense wijze waarop de syntaxboom wordt bewandeld maakt A2JS tot een fraai stuk gereedschap en gegeven de functionaliteit is de ruime negenduizend regels code niet al te veel. Zeker niet als je meerekent dat het gros van de regels bestaat uit veelvoorkomende boombewandelmantra’s.

De mensen van Matreshka zijn zo vriendelijk geweest om naast een eigen runtime library ook een WebAPI aan te bieden. Deze kun je gebruiken voor onder andere Ajax-calls, WebGL en allerhande DOM-manipulatie. Dat is vriendelijk van ze, zeker omdat je voor overige interoperabiliteit met het JavaScript universum bent aangewezen op de Foreign Function Interface en die wil wel wat toetsenbordslijtage ten gevolge hebben.

Al met al is A2JS een aardig project. GNAT is een hele krachtige compiler en dat werkt sowieso lekker. Daarnaast is de opbouw van A2JS mooi overzichtelijk. Het is wel spijtig dat er vrijwel geen online documentatie beschikbaar is en dat het volledig is ingebed in het Matreshka project. Ik vermoed dat het succesvoller zou zijn als het als los gereedschap verkrijgbaar was.

Desalniettemin denk ik dat ik A2JS zelf niet snel als JavaScript vervanger zal inzetten. Er zijn namelijk wel een aantal belangrijke nadelen:

  • A2JS genereert geen source maps. Als je dus een runtime error hebt dan weet je niet welke regel in je Ada-code de boosdoener was. Bij andere talen zoals TypeScript werkt dit heel prettig, maar hier moet je het zonder stellen. Nu moet ik zeggen dat ik jarenlang naar tevredenheid Fay heb gebruikt en dat deze ook geen source maps aanmaakte, maar daar kon je tenminste in 1 oogopslag zien waar het probleem zat door naar de gegenereerde JS te kijken. Hier is dat iets lastiger.

  • A2JS ondersteunt alleen een Ada-subset en doordat je het ook zonder de uitgebreide standaardbibliotheek moet stellen wordt het allemaal wel wat karig. Bovendien moet je er in de praktijk achter komen wat wel en niet werkt.

  • Ik ben zelf niet zo’n fan van de afhankelijkheid van RequireJS. Ik begrijp dat ze een keus moesten maken om modulariteit van de Ada packages in stand te houden in de JavaScript-wereld, maar had hier liever gezien dat ze dit aan de hand van de ouderwetse direct uitvoerende anonieme functie hadden gedaan. Nog liever zou ik ondersteuning voor ECMAScript 6 zien bij het genereren van de JS met de ingebouwde ondersteuning voor modules. Als ik tegenwoordig een vanille JS project moet bouwen, dan gebruik ik ES6 in combinatie met Rollup om de modules te bundelen en Babel om de boel te vertalen voor oudere browsers. Ik zou het zelf mooier vinden als je wat meer vrijheid had in de keuzes hierin, want je bent vaak niet de enige speler in een web-project en misschien afhankelijk van de voorkeuren van anderen.

Door de manier waarop A2JS is opgebouwd leent het zich goed voor toekomstige aanpassing en uitbreiding. Ik ben daarom ook benieuwd wat de mensen van het Matreshka Project ermee gaan doen. Het is eigenlijk zonde dat ze het nu relatief diep hebben weggestopt op hun website, want A2JS heeft ook onafhankelijk van het overkoepelende framework potentie.

Ik ga voor nu in elk geval verder met de route die ik een tijdje geleden had uitgezet. Ik ontwikkel weer zoveel mogelijk aan de serverkant en daar waar nodig gebruik ik vanille JavaScript met een hele batterij aan statische analysetools om de chaos op afstand te houden.

###PS: en WebAssembly dan? Naast het transpileren biedt WebAssembly nog een mogelijke route om andere talen in de browser te gebruiken. Deze is echter meer gericht op het ombouwen van complete applicaties en minder geschikt voor interactie met de onderliggende API’s van de browser zelf. Er bestaat voor Ada een experimentele GNAT-LLVM compiler waarmee je Ada ook naar WASM kunt compileren, maar deze is bepaald niet klaar voor productie.



Hyperlinks: