Parašiau programavimo kalbą. Štai kaip jūs taip pat galite.

Per pastaruosius 6 mėnesius dirbau su programavimo kalba, vadinama „Pinecone“. Dar nevadinčiau to brandžiu, tačiau jame jau yra pakankamai funkcijų, kad būtų galima naudoti, pavyzdžiui:

  • kintamieji
  • funkcijos
  • vartotojo apibrėžtos struktūros

Jei jus tai domina, peržiūrėkite „Pinecone“ nukreipimo puslapį arba jo „GitHub“ repo.

Aš nesu ekspertas. Pradėdamas šį projektą neturėjau supratimo, ką darau, ir vis dar ne. Aš lankiau nulinę kalbos kūrimo kursą, tik šiek tiek apie tai skaičiau internete ir nesilaikau daugelio man duotų patarimų.

Ir vis tiek aš dariau visiškai naują kalbą. Ir tai veikia. Taigi aš turiu kažką daryti teisingai.

Šiame įraše pasinersiu po gaubtu ir parodysiu vamzdyną „Pinecone“ (ir kitas programavimo kalbas), kurį naudosite, kad šaltinio kodas taptų magija.

Aš taip pat paliesiu keletą mano padarytų kompromisų ir kodėl priėmiau sprendimus.

Tai anaiptol nėra išsami programavimo kalbos rašymo pamoka, tačiau tai yra geras atspirties taškas, jei norite sužinoti apie kalbos vystymąsi.

Darbo pradžia

„Aš visiškai neįsivaizduoju, nuo ko aš net pradėčiau“, tai aš daug girdžiu, kai sakau kitiems kūrėjams, kad rašau kalbą. Jei tai jūsų reakcija, aš dabar priimsiu keletą priimamų sprendimų ir žingsnių, kurių imamasi pradedant bet kokią naują kalbą.

Sudaryta ir interpretuota

Yra dvi pagrindinės kalbų rūšys: sudarytos ir interpretuojamos:

  • Kompiliatorius išsiaiškina viską, ką veiks programa, paverčia jį „mašininiu kodu“ (tokiu formatu, kurį kompiuteris gali paleisti labai greitai), tada išsaugo, kad jis būtų vykdomas vėliau.
  • Vertėjas eina per šaltinio kodą eilutė po eilutės ir išsiaiškina, ką jis veikia.

Techniškai bet kurią kalbą būtų galima sudaryti ar interpretuoti, tačiau viena ar kita paprastai turi daugiau prasmės konkrečiai kalbai. Paprastai vertimas žodžiu būna lankstesnis, o kompiliavimo - didesnis. Bet tai tik labai sudėtingos temos paviršiaus draskymas.

Aš labai vertinu našumą ir mačiau, kad trūksta programavimo kalbų, kurios būtų orientuotos ir į našumą, ir į paprastumą, todėl ėmiausi kompiliuoti „Pinecone“.

Tai buvo svarbus sprendimas anksti priimti, nes tai daro įtaką daugeliui kalbos dizaino sprendimų (pvz., Statinis spausdinimas yra didelė nauda kompiliuojamoms kalboms, bet ne tiek daug interpretuojamoms).

Nepaisant to, kad „Pinecone“ buvo sukurtas turint omenyje kompiliavimą, jis turi visiškai funkcionalų vertėją, kuris buvo vienintelis būdas kurį laiką jį paleisti. Tam yra keletas priežasčių, kurias paaiškinsiu vėliau.

Kalbos pasirinkimas

Aš žinau, kad tai šiek tiek meta, bet programavimo kalba pati yra programa, todėl ją reikia parašyti kalba. Pasirinkau C ++ dėl jo našumo ir didelio funkcijų rinkinio. Be to, man iš tikrųjų patinka dirbti C ++.

Jei rašote aiškinamą kalbą, yra labai prasminga ją rašyti sukompiliuota kalba (pvz., C, C ++ ar Swift), nes jūsų vertėjo ir vertėjo, kuris aiškina jūsų vertėją, kalba prarastas spektaklis dar labiau padidės.

Jei planuojate kurti, priimtinesnė yra lėtesnė kalba (pvz., „Python“ ar „JavaScript“). Kompiliavimo laikas gali būti blogas, bet, mano manymu, tai nėra toli gražu ne blogas vykdymo laikas.

Aukšto lygio dizainas

Programavimo kalba paprastai yra sudaryta kaip vamzdynas. Tai yra, jis turi kelis etapus. Kiekviename etape yra duomenys, suformatuoti konkrečiu, aiškiai apibrėžtu būdu. Ji taip pat turi funkcijas transformuoti duomenis iš kiekvieno etapo į kitą.

Pirmasis etapas yra eilutė, kurioje yra visas įvesties šaltinio failas. Paskutinis etapas yra tai, ką galima paleisti. Visa tai paaiškės, kai žingsnis po žingsnio eisime per „Pinecone“ vamzdyną.

Leksingas

Pirmasis žingsnis daugumoje programavimo kalbų yra leksika arba žymėjimas. „Lex“ yra trumpinys leksinei analizei, labai puošnus žodis, skirtas suskaidyti krūvą teksto į žetonus. Žodis „tokenizer“ yra daug prasmingesnis, tačiau „lexer“ yra taip smagu pasakyti, kad aš vis tiek jį vartoju.

Žetonai

Žetonas yra nedidelis kalbos vienetas. Žetonas gali būti kintamasis ar funkcijos pavadinimas (AKA identifikatorius), operatorius arba skaičius.

„Lexer“ užduotis

Manoma, kad lekseris ima eilutę, kurioje yra visas failų vertės šaltinio kodas, ir išspjauna sąrašą, kuriame yra visi prieigos raktai.

Būsimose dujotiekio stadijose nebus remiamasi pirminiu šaltinio kodu, todėl lekseris turi pateikti visą jiems reikalingą informaciją. Šio gana griežto formato priežastis yra ta, kad lekseris gali atlikti tokias užduotis kaip pašalinti komentarus arba nustatyti, ar kažkas yra skaičius ar identifikatorius. Jūs norite, kad ši logika būtų užrakinta leksiko viduje, kad nereikėtų galvoti apie šias taisykles, kai rašote likusią kalbą, ir jūs galite pakeisti tokio tipo sintaksę vienoje vietoje.

Lankstumas

Tą dieną, kai pradėjau kalbą, pirmiausia parašiau paprastą leksiką. Netrukus po to pradėjau mokytis apie priemones, kurios tariamai lexing'ą padarys paprastesnį ir mažiau bagažą keliantį.

Vyraujantis toks įrankis yra programa „Flex“, kurianti leksikus. Jūs suteikiate failą, kuriame yra speciali sintaksė kalbos gramatikai apibūdinti. Iš to ji sukuria C programą, kuri leksuoja eilutę ir sukuria norimą išvestį.

Mano sprendimas

Aš kol kas pasirinkau pasilikti parašytą leksiką. Galų gale nemačiau didelių „Flex“ naudojimo pranašumų, bent jau nepakankamai, kad būtų galima pagrįsti priklausomybės pridėjimą ir sudėtingą kūrimo procesą.

Mano lekseris yra tik kelių šimtų eilučių ir retai man kelia problemų. Savo lekserio sukimas taip pat suteikia man daugiau lankstumo, pavyzdžiui, galimybę pridėti operatorių prie kalbos neredaguojant kelių failų.

Analizuojama

Antrasis vamzdyno etapas yra analizatorius. Analizatorius paverčia žetonų sąrašą mazgų medžiu. Medis, naudojamas saugoti tokio tipo duomenis, yra žinomas kaip abstrakčios sintaksės medis arba AST. Bent jau „Pinecone“ AST neturi jokios informacijos apie tipus ir kurie identifikatoriai yra. Tai tiesiog struktūriniai žetonai.

Analizatoriaus pareigos

Analizatorius prideda struktūrą prie sutvarkytų žetonų, kuriuos sukuria leksikas, sąrašo. Norėdami sustabdyti neaiškumus, analizatorius turi atsižvelgti į skliaustus ir operacijų tvarką. Paprasčiausiai analizuoti operatorius nėra labai sunku, tačiau pridedant daugiau kalbos konstrukcijų, analizavimas gali tapti labai sudėtingas.

Stumbras

Vėlgi buvo priimtas sprendimas įtraukti trečiosios šalies biblioteką. Vyraujanti analizuojanti biblioteka yra bizonai. Stumbras dirba panašiai kaip „Flex“. Jūs rašote failą pasirinktiniu formatu, kuriame saugoma gramatikos informacija, tada Bisonas naudoja tai generuodamas C programą, kuri atliks jūsų analizavimą. Aš nepasirinkau naudoti bizonų.

Kodėl užsakymas yra geresnis

Naudojant „Lexx“, sprendimas naudoti savo kodą buvo gana akivaizdus. Lekseris yra tokia nereikšminga programa, kad nerašydamas savo jausdavausi beveik taip kvailai, kaip nerašydamas savo „kairės pusės“.

Su analizatoriumi - visai kas kita. Šiuo metu mano kankorėžio analizatorius yra 750 eilučių, ir aš parašiau tris iš jų, nes pirmieji du buvo šiukšliadėžės.

Iš pradžių priėmiau sprendimą dėl daugelio priežasčių ir, nors jis nevyko visiškai sklandžiai, dauguma jų pasitvirtina. Pagrindiniai yra šie:

  • Sumažinkite konteksto perjungimą darbo eigoje: konteksto perjungimas tarp C ++ ir kankorėžio yra pakankamai blogas, neįmetus Bison gramatikos gramatikos
  • Kūrimas paprastas: kiekvieną kartą, kai keičiasi gramatika, prieš pradedant kurti, bizoną reikia paleisti. Tai gali būti automatizuota, tačiau tai tampa skausmu, kai perjungiama iš versijos sistemų.
  • Man patinka kurti šaunius šūdus: aš negaminau kankorėžio, nes maniau, kad tai bus lengva, tai kodėl aš deleguočiau centrinį vaidmenį, kai galėčiau tai padaryti pats? Pasirinktinis analizatorius gali būti nereikšmingas, tačiau tai yra visiškai įmanoma.

Pradžioje nebuvau visiškai tikras, ar einu perspektyviu keliu, bet pasitikėjau tuo, ką Walteris Brightas (ankstyvosios C ++ versijos kūrėjas ir D kalbos kūrėjas) pasakė tema:

„Kiek prieštaringesnis, nesivarginčiau gaišti laiko su lekserio ar analizatoriaus generatoriais ir kitais vadinamaisiais kompiliatoriais. Jie gaišo laiką. Lekserio ir analizatoriaus rašymas yra nedidelis procentas sudarant kompiliatorių. Generatoriaus naudojimas užtruks maždaug tiek laiko, kiek rašymas ranka, ir jis ves jus prie generatoriaus (o tai svarbu perkėlus kompiliatorių į naują platformą). Generatoriai taip pat turi apgailėtiną reputaciją - jie siunčia nemalonius klaidų pranešimus “.

Veiksmo medis

Dabar mes palikome bendrų, universalių terminų sritį, ar bent jau nebežinau, kokie yra terminai. Mano supratimu, tai, ką aš vadinu „veiksmo medžiu“, labiausiai panašus į LLVM IR (tarpinį vaizdavimą).

Tarp veiksmo medžio ir abstrakčios sintaksės medžio yra subtilus, bet labai reikšmingas skirtumas. Man užtruko, kol supratau, kad tarp jų netgi turėtų būti skirtumas (tai prisidėjo prie analizatoriaus perrašymo poreikio).

Veiksmo medis ir AST

Paprasčiau tariant, veiksmo medis yra AST su kontekstu. Šis kontekstas yra tokia informacija kaip, kokio tipo funkcija grąžina, arba kad dvi vietos, kuriose naudojamas kintamasis, iš tikrųjų naudoja tą patį kintamąjį. Kadangi kodui, kuris sugeneruoja veiksmų medį, reikia išsiaiškinti ir prisiminti visą šį kontekstą, reikia daug vardų paieškos lentelių ir kitų dalykų.

Veiksmo medžio paleidimas

Kai turėsime veiksmų medį, paleisti kodą yra lengva. Kiekvienas veiksmo mazgas turi funkciją „vykdyti“, kuri įneša tam tikrą įvestį, atlieka viską, ką turėtų atlikti veiksmas (įskaitant galimą pakvietimą) ir grąžina veiksmo išvestį. Tai yra vertėjas, veikiantis.

Kompiliavimo parinktys

"Bet palauk!" Girdžiu, kaip sakote: „Ar nereikia kankorėžio sudaryti?“ Taip tai yra. Bet sudaryti yra sunkiau nei interpretuoti. Yra keli galimi požiūriai.

Sukurkite savo kompiliatorių

Iš pradžių tai man pasirodė gera mintis. Man labai patinka gaminti daiktus ir man labai norėjosi pasiteisinimo, kad man gerai sekasi surinkti.

Deja, rašyti nešiojamąjį kompiliatorių nėra taip lengva, kaip parašyti mašininį kodą kiekvienam kalbos elementui. Dėl architektūrų ir operacinių sistemų skaičiaus bet kuriam asmeniui nepraktiška rašyti daugialypės platformos kompiliatoriaus vidinę programą.

Net komandos, esančios „Swift“, „Rust“ ir „Clang“, nenori pačios dėl viso to vargti, todėl jos visos naudojasi…

LLVM

LLVM yra kompiliatorių įrankių kolekcija. Iš esmės tai yra biblioteka, kuri pavers jūsų kalbą sukompiliuotu vykdomuoju dvejetainiu. Atrodė, kad tai puikus pasirinkimas, todėl įšokau į dešinę. Deja, nepatikrinau, koks gilus vanduo, ir iškart nuskendo.

Nors LLVM nėra sudėtinga, tačiau sudėtinga sudėtinga biblioteka. Tai nėra neįmanoma naudoti, ir jie turi gerus vadovėlius, tačiau supratau, kad turėsiu šiek tiek pasimokyti, kol būsiu pasirengęs iki galo įdiegti „Pinecone“ kompiliatorių.

Transliacija

Norėjau kažkokio kompiliuoto kankorėžio ir norėjau jo greitai, todėl pasirinkau vieną metodą, kurį žinojau, kad galiu dirbti: transliaciją.

Parašiau „Pinecone to C ++“ transpilerį ir pridėjau galimybę automatiškai kompiliuoti išvesties šaltinį naudojant GCC. Šiuo metu tai veikia beveik visose „Pinecone“ programose (nors yra keletas briaunų atvejų, kurie ją pažeidžia). Tai nėra ypač nešiojamas ar keičiamas sprendimas, tačiau kol kas veikia.

Ateitis

Darant prielaidą, kad ir toliau kuriu „Pinecone“, anksčiau ar vėliau jis gaus LLVM kompiliavimo palaikymą. Įtariu, kad nė vienas moteris, kiek dirbu, transpileris niekada nebus visiškai stabilus, o LLVM nauda yra daug. Tai tik klausimas, kada turėsiu laiko parengti keletą pavyzdinių projektų LLVM ir jį užčiuopti.

Iki tol vertėjas puikiai tinka nereikšmingoms programoms, o „C ++“ verčiasi daugeliu dalykų, kuriems reikia daugiau našumo.

Išvada

Tikiuosi, kad programavimo kalbas padariau jums šiek tiek mažiau paslaptingas. Jei vis dėlto norite tokį pasigaminti patys, labai rekomenduoju. Yra daugybė išsamią išsamią išsamią informaciją, tačiau čia turėtų pakakti metmenų.

Čia yra mano aukšto lygio patarimas, kaip pradėti (atminkite, kad aš tikrai nežinau, ką darau, todėl pasiimkite jį su druska)

  • Jei abejojate, eikite aiškintis. Išaiškintas kalbas paprastai lengviau kurti, kurti ir išmokti. Aš neatgrasau jūsų rašyti kompiliuojamą, jei žinote, kad tai norite padaryti, bet jei esate ant tvoros, norėčiau interpretuoti.
  • Kalbėdami apie lekserius ir parserius, darykite viską, ko norite. Yra pagrįstų argumentų už ir prieš rašant savo. Galų gale, jei apgalvosite savo dizainą ir viską įgyvendinsite protingai, tai visiškai nesvarbu.
  • Mokykitės iš dujotiekio, su kuriuo baigiau. Kuriant dabar turimą dujotiekį, teko nemažai išbandyti. Bandžiau pašalinti AST, AST, kurie virsta veiksmo medžiais, ir kitas baisias idėjas. Šis vamzdynas veikia, todėl jo nekeiskite, nebent turite tikrai gerą idėją.
  • Jei neturite laiko ar motyvacijos įgyvendinti sudėtingą bendrosios kalbos kalbą, pabandykite įdiegti ezoterinę kalbą, pvz., „Brainfuck“. Šie vertėjai gali būti net keli šimtai eilučių.

Aš labai mažai apgailestauju dėl „Pinecone“ kūrimo. Pakeliui padariau daugybę blogų pasirinkimų, bet perrašiau didžiąją dalį tokių klaidų paveikto kodo.

Šiuo metu „Pinecone“ yra pakankamai geros būklės, kad gerai funkcionuotų ir būtų lengvai patobulinamas. Kankorėžio rašymas man buvo nepaprastai edukacinė ir maloni patirtis, ir ji tik prasideda.