Ada naar JavaScript vertalen met A2JS
=====================================
2020-11-23
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 [3].
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 [4] 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 [1].
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 compileren [5], maar deze is bepaald niet klaar
voor productie.
Hyperlinks:
[1]:
https://git.sr.ht/~jelle/a2js_demo
[2]:
http://gnat-asis.sourceforge.net/
[3]:
https://forge.ada-ru.org/matreshka
[4]:
https://forge.ada-ru.org/matreshka/wiki/Web/A2JS
[5]:
https://blog.adacore.com/use-of-gnat-llvm-to-translate-ada-applications-to-webassembly
-----------------------------------------------------------
Tags: ada, nederlands