Hogyan tudnék létrehozni egy programozási nyelvet?

A cikk címe egy olyan kérdést tükröz, amelyet újra és újra hallok a fórumokon vagy a hozzám érkező e-mailekben.

Azt hiszem, minden kíváncsi fejlesztő feltette ezt legalább egyszer. Normális dolog, hogy lenyűgöz a programozási nyelvek működése. Sajnos a legtöbb válasz, amit olvasunk, nagyon tudományos vagy elméleti jellegű. Néhány másik túl sok implementációs részletet tartalmaz. Miután elolvastuk őket, még mindig kíváncsiak vagyunk, hogyan működnek a dolgok a gyakorlatban.

Azért mi most válaszolunk rá. Igen, megnézzük, hogy mi a folyamata annak, hogy létrehozzunk egy saját teljes nyelvet, egy fordítóprogramot hozzá, és mi nem.

Az áttekintés

A legtöbb személy, aki meg akarja tanulni, hogyan kell “programozási nyelvet létrehozni”, gyakorlatilag arra keres információt, hogyan kell fordítóprogramot készíteni. Meg akarják érteni azt a mechanikát, amely lehetővé teszi egy új programozási nyelv végrehajtását.
A fordító egy alapvető darabja a kirakósnak, de egy új programozási nyelv elkészítéséhez ennél többre van szükség:

1) Egy nyelvet meg kell tervezni: A nyelv megalkotójának néhány alapvető döntést kell hoznia a használandó paradigmákról és a nyelv szintaxisáról
2) Egy fordítót kell készíteni
3) Egy szabványos könyvtárat kell megvalósítani
4) Támogató eszközöket, például szerkesztőket és build-rendszereket kell biztosítani

Lássuk részletesebben, hogy az egyes pontok mivel járnak.

Programozási nyelv tervezése

Ha csak a saját fordítóprogramodat akarod megírni, hogy megtanuld, hogyan működnek ezek a dolgok, akkor ezt a fázist kihagyhatod. Foghatod egy létező nyelv egy részhalmazát, vagy kitalálhatod annak egy egyszerű variációját, és máris belevághatsz. Ha azonban a saját programozási nyelv megalkotását tervezed, akkor át kell gondolnod a dolgot.

A programozási nyelv tervezését két fázisra osztva képzelem el:

  1. A nagy fázis
  2. A finomítási fázis

Az első fázisban megválaszoljuk a nyelvünkkel kapcsolatos alapvető kérdéseket.

  • Milyen végrehajtási paradigmát akarunk használni? Imperatív vagy funkcionális lesz? Vagy esetleg állapotgépeken vagy üzleti szabályokon alapuló?
  • Sztatikus vagy dinamikus tipizálást akarunk?
  • Milyen programokhoz lesz a legjobb ez a nyelv? Kis szkriptekhez vagy nagy rendszerekhez fogjuk használni?
  • Mi számít nekünk leginkább: a teljesítmény? Olvashatóság?
  • Hasonlítani akarunk egy meglévő programozási nyelvhez? C fejlesztőknek szóljon, vagy könnyen megtanulható legyen a Pythonból érkezők számára?
  • Szeretnénk-e, hogy egy adott platformon (JVM, CLR) működjön?
  • Milyen metaprogramozási képességeket szeretnénk támogatni, ha van ilyen? Makrók? Sablonok? Reflection?

A második fázisban továbbfejlesztjük a nyelvet, ahogy használjuk. Olyan problémákba fogunk ütközni, olyan dolgokba, amelyeket nagyon nehéz vagy lehetetlen kifejezni a nyelvünkön, és végül továbbfejlesztjük azt. A második fázis talán nem olyan elbűvölő, mint az első, de ez az a fázis, amelyben folyamatosan hangoljuk a nyelvünket, hogy a gyakorlatban is használható legyen, ezért nem szabad alábecsülnünk.

A fordítóprogram készítése

A fordítóprogram készítése a legizgalmasabb lépés egy programozási nyelv létrehozásában. Ha már van egy fordítóprogramunk, akkor ténylegesen életre kelthetjük a nyelvünket. A fordító lehetővé teszi számunkra, hogy elkezdjünk játszani a nyelvvel, használjuk azt, és azonosítsuk, mi hiányzik a kezdeti tervezésből. Lehetővé teszi, hogy lássuk az első eredményeket. Nehéz felülmúlni azt az örömöt, amikor a vadonatúj programozási nyelvünkön írt első programot végrehajtjuk, bármilyen egyszerű is legyen az a program.

De hogyan építünk fordítót?

Mint minden összetett dolgot, ezt is lépésekben tesszük:

  1. Építünk egy elemzőt: az elemző a fordítóprogramunknak az a része, amely fogadja a programjaink szövegét, és megérti, hogy azok milyen parancsokat fejeznek ki. Felismeri a kifejezéseket, az utasításokat, az osztályokat, és belső adatstruktúrákat hoz létre ezek reprezentálására. Az elemző többi része ezekkel az adatszerkezetekkel fog dolgozni, nem pedig az eredeti szöveggel
  2. (opcionális) Lefordítjuk a parse fát egy Absztrakt Szintaxisfává. Jellemzően az elemző által előállított adatstruktúrák egy kicsit alacsony szintűek, mivel sok olyan részletet tartalmaznak, amelyek a fordítóprogramunk számára nem létfontosságúak. Emiatt gyakran szeretnénk az adatstruktúrákat átrendezni valami kicsit magasabb szintűvé
  3. Feloldjuk a szimbólumokat. A kódban olyan dolgokat írunk, mint a + 1. A fordítóprogramunknak ki kell találnia, hogy a a mire utal. Ez egy mező? Vagy egy változó? Egy metódus paramétere? Megvizsgáljuk a kódot, hogy választ kapjunk erre
  4. Érvényesítjük a fát. Ellenőriznünk kell, hogy a programozó nem követett-e el hibát. Egy boolean és egy int összegzésével próbálkozik? Vagy egy nem létező mezőhöz kíván hozzáférni? Megfelelő hibaüzeneteket kell előállítanunk
  5. Generáljuk a gépi kódot. Ezen a ponton lefordítjuk a kódot olyasmire, amit a gép végre tud hajtani. Ez lehet megfelelő gépi kód vagy bytecode valamilyen virtuális géphez
  6. (opcionális) Elvégezzük a linkelést. Bizonyos esetekben a programunkhoz előállított gépi kódot össze kell kombinálnunk az általunk bevonni kívánt statikus könyvtárak kódjával, hogy egyetlen futtatható állományt hozzunk létre

Mindig szükségünk van fordítóprogramra? Nem. Helyettesíthetjük más eszközökkel a kód futtatására:

  • Írhatunk interpreter-t: Az interpreter lényegében egy olyan program, amely elvégzi a fordító 1-4. lépéseit, majd közvetlenül végrehajtja azt, amit az absztrakt szintaxisfa megad
  • Írhatunk transzpiler-t:

Ez a két alternatíva tökéletesen érvényes, és gyakran van értelme a kettő közül az egyiket választani, mert a szükséges erőfeszítés jellemzően kisebb.

Egy cikkünkben elmagyarázzuk, hogyan kell transzpilert írni. Nézze meg, ha kíváncsi egy gyakorlati példára, kóddal.

Ebben a cikkben részletesebben elmagyarázzuk a fordító és az értelmező közötti különbséget.

A programozási nyelv szabványkönyvtára

Minden programozási nyelvnek szüksége van néhány dologra:

  • Képernyőre nyomtatás
  • A fájlrendszerhez való hozzáférés
  • Hálózati kapcsolatok használata
  • Használati felület létrehozása

Ezek az alapvető funkciók a rendszer többi részével való interakcióhoz. Ezek nélkül egy nyelv alapvetően használhatatlan. Hogyan biztosítjuk ezeket a funkcionalitásokat? Egy szabványos könyvtár létrehozásával. Ez olyan függvények vagy osztályok halmaza lesz, amelyeket a programozási nyelvünkön írt, de valamilyen más nyelven írt programokban meg lehet hívni. Például sok nyelvnek van olyan szabványkönyvtára, amely legalább részben C nyelven íródott.

A szabványkönyvtár aztán sokkal többet is tartalmazhat. Például osztályokat a főbb gyűjtemények, például listák és térképek reprezentálására, vagy olyan gyakori formátumok feldolgozására, mint a JSON vagy az XML. Gyakran tartalmaz fejlett funkciókat a karakterláncok és reguláris kifejezések feldolgozásához.

Más szóval, egy szabványos könyvtár megírása rengeteg munkát jelent. Nem elbűvölő, koncepcionálisan nem olyan érdekes, mint egy fordítóprogram írása, de mégis alapvető összetevője annak, hogy egy programozási nyelv életképes legyen.

Vannak módok arra, hogy elkerüljük ezt a követelményt. Az egyik az, hogy a nyelvet valamilyen platformon futtatjuk, és lehetővé tesszük egy másik nyelv szabványos könyvtárának újrafelhasználását. Például a JVM-en futó összes nyelv egyszerűen újra felhasználhatja a Java szabványkönyvtárát.

Támogató eszközök egy új programozási nyelvhez

Hogy egy nyelvet a gyakorlatban is használhatóvá tegyünk, gyakran szükségünk van néhány támogató eszköz megírására.

A legkézenfekvőbb egy szerkesztő. Egy speciális szerkesztő szintaxiskiemeléssel, soron belüli hibaellenőrzéssel és automatikus kitöltéssel ma már elengedhetetlen ahhoz, hogy minden fejlesztő produktív legyen.

De ma már a fejlesztők el vannak kényeztetve, és mindenféle más támogató eszközt is elvárnak. Például egy hibakereső nagyon hasznos lehet egy csúnya hiba kezeléséhez. Vagy egy mavenhez vagy gradle-hez hasonló build rendszer lehet olyasmi, amit a felhasználók később kérni fognak.

A legelején elég lehet egy szerkesztő, de ahogy nő a felhasználói bázis, úgy nő a projektek összetettsége is, és több támogató eszközre lesz szükség. Remélhetőleg abban az időben lesz egy közösség, amely hajlandó segíteni ezek létrehozásában.

Összefoglaló

Egy programozási nyelv létrehozása olyan folyamat, amely sok fejlesztő számára rejtélyesnek tűnik. Ebben a cikkben megpróbáltuk megmutatni, hogy ez csak egy folyamat. Lenyűgöző és nem könnyű, de meg lehet csinálni.

Egy programozási nyelvet többféle okból is szeretnénk létrehozni. Az egyik jó ok a szórakozás, a másik a fordítók működésének megtanulása. A nyelved a végén nagyon hasznos lehet, vagy nem, sok tényezőtől függően. Ha azonban jól szórakozol és/vagy tanulsz az építése közben, akkor érdemes egy kis időt áldozni rá.

És persze dicsekedhetsz majd a fejlesztőtársaiddal.

Ha többet szeretnél megtudni egy nyelv készítéséről, nézd meg a többi általunk létrehozott forrást: Learn how to build languages.

Az alábbi cikkeink is érdekelhetnek:

Az alábbi cikkeink is érdekelhetnek:

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.